일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- 가상현실
- 앱 배포
- meta
- Oculus
- CGV
- 팀프로젝트
- XR
- 드래곤 플라이트
- VR
- 길건너 친구들
- input system
- Photon Fusion
- 유니티
- 멀티플레이
- 연습
- 팀 프로젝트
- 유니티 Json 데이터 연동
- 개발일지
- meta xr
- 유니티 GUI
- 오브젝트 풀링
- 포트폴리오
- 모작
- 드래곤 플라이트 모작
- 오큘러스
- OVR
- ChatGPT
- 유니티 UI
- 개발
- HAPTIC
Archives
- Today
- Total
EasyCastleUNITY
간단 RPG Game Main (몬스터가 전부 죽으면, 포탈 생성) 본문
앞에 포스팅에서 몬스터가 죽으면 포탈이 생성되는 것을 추가하였다.
몬스터에 한 번 도착하면, 죽을 때 까지 공격하는 것은 아직 이해하고 연습이 부족한지 구현하지 못했다.
-> 이를 통해 내가 아직 코루틴을 사용하는 방법에 대해 미숙하다는 것을 알게 되었다.
GameMain
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GameMain : MonoBehaviour
{
[SerializeField]
private HeroController heroController; //영웅 컨트롤러
[SerializeField]
private MonsterController monsterController1; //몬스터 컨트롤러
[SerializeField]
private MonsterController monsterController2; //몬스터 컨트롤러
[SerializeField]
private GameObject hitFxPrefab; //피격 이펙트
[SerializeField]
private GameObject gravePrefab;
[SerializeField]
private GameObject TurtleHp;
[SerializeField]
private GameObject ChestHp;
[SerializeField]
private GameObject PortalPrefab;
public int monsterCount;
private bool isCreate = false;
// Start is called before the first frame update
void Start()
{
Debug.LogFormat("monsterCount: {0}", monsterCount);
//영웅 이동에 대한 대리자 익명 메서드
this.heroController.onMoveComplete = (target) =>
{
Debug.Log("<color=cyan>이동을 완료 했습니다.</color>");
//타겟이 있다면 공격 애니메이션 실행
if (target != null )
{
this.heroController.Attack(target);
}
};
//몬스터가 공격을 받으면 실행하는 익명 메서드
this.monsterController1.onHit = () => {
Debug.Log("이펙트 생성");
Vector3 offset = new Vector3(0, 0.5f, 0);
Vector3 tpos = this.monsterController1.transform.position + offset;
Debug.LogFormat("이펙트 생성위치: {0}", tpos);
//프리팹 인스턴스 생성
GameObject fxGo = Instantiate(this.hitFxPrefab);
//위치 설정
fxGo.transform.position = tpos;
//파티클 실행
fxGo.GetComponent<ParticleSystem>().Play();
};
this.monsterController2.onHit = () => {
Debug.Log("이펙트 생성");
Vector3 offset = new Vector3(0, 0.5f, 0);
Vector3 tpos = this.monsterController2.transform.position + offset;
Debug.LogFormat("이펙트 생성위치: {0}", tpos);
//프리팹 인스턴스 생성
GameObject fxGo = Instantiate(this.hitFxPrefab);
//위치 설정
fxGo.transform.position = tpos;
//파티클 실행
fxGo.GetComponent<ParticleSystem>().Play();
};
//몬스터가 공격을 받으면 해당 위치에 무덤 생성
this.monsterController1.onDie = () =>{
Debug.LogFormat("{0} 사망",monsterController1.gameObject.name);
Debug.Log("무덤 생성");
Vector3 tpos = this.monsterController1.transform.position;
Debug.LogFormat("무덤 생성위치: {0}", tpos);
//프리팹 인스턴스 생성
GameObject graveGo = Instantiate(this.gravePrefab);
//위치 설정
graveGo.transform.position = tpos;
Destroy(monsterController1.gameObject);
this.monsterCount--;
Debug.LogFormat("monsterCount: {0}", monsterCount);
};
this.monsterController2.onDie = () => {
Debug.LogFormat("{0} 사망", monsterController2.gameObject.name);
Debug.Log("무덤 생성");
Vector3 tpos = this.monsterController2.transform.position;
Debug.LogFormat("무덤 생성위치: {0}", tpos);
//프리팹 인스턴스 생성
GameObject graveGo = Instantiate(this.gravePrefab);
//위치 설정
graveGo.transform.position = tpos;
Destroy(monsterController2.gameObject);
this.monsterCount--;
Debug.LogFormat("monsterCount: {0}", monsterCount);
};
}
//사거리 계산
private bool isWithinRange(float distance, float radius)
{
return radius >= distance; //true
}
// Update is called once per frame
void Update()
{
//화면을 클릭하면 클릭한 위치로 영웅이 이동
if(Input.GetMouseButtonDown(0))
{
//Ray 생성
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
float maxDistance = 1000f; //Ray의 최고 길이
//화면에 Ray를 출력
Debug.DrawRay(ray.origin, ray.direction * maxDistance , Color.yellow ,2f);
//충돌검사
RaycastHit hit; //out 매개변수를 쓰려면 미리 정의를 해야된다.
if(Physics.Raycast(ray,out hit, maxDistance))
{
//클릭한 오브젝트가 몬스터라면
if (hit.collider.tag == "Monster")
{
//거리를 구한다
float distance = Vector3.Distance(this.heroController.gameObject.transform.position,
hit.collider.gameObject.transform.position);
MonsterController monsterController =
hit.collider.gameObject.GetComponent<MonsterController>();
//각 반지름 더한거와 비교
float sumRadius = this.heroController.Radius + monsterController.Radius;
Debug.LogFormat("<color=green>distance:{0}, sumRadius:{1}</color>",
distance, sumRadius);
//사거리 안에 들어옴
if (distance <= sumRadius)
{
//공격
}
else
{
//이동
this.heroController.Move(monsterController);
}
}
else if (hit.collider.tag == "Ground")
{
//충돌정보가 hit 변수에 담김
Debug.Log(hit.point); //월드 상의 충돌 지점 위치
//Hero Gameobject 이동
this.heroController.Move(hit.point);
}
}
}
Text text1 = TurtleHp.GetComponent<Text>();
Text text2 = ChestHp.GetComponent<Text>();
text1.text = string.Format("TurtleHp: {0}",monsterController1.Hp);
text2.text = string.Format("ChestHp: {0}",monsterController2.Hp);
if(monsterCount <= 0 && isCreate==false)
{
GameObject portalGo = Instantiate(this.PortalPrefab);
portalGo.transform.position = new Vector3(-2.0f, 0, -3.5f);
this.isCreate = true;
}
}
}
HeroController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//Game
public class HeroController : MonoBehaviour
{
public enum eState
{
Idle,Run,Attack
}
//이동 구현을 코루틴으로 함
private Vector3 targetPosition; //타겟이 되는 위치
private Coroutine moveRoutine; //전에 실행하던 코루틴을 저장하는 변수
private Animator anim; //영웅의 애니메이터
[SerializeField]
private float radius = 1.0f; //영웅의 사거리
private MonsterController monsterTarget; //target이 된 몬스터의 MonsterController 컴포넌트
public System.Action<MonsterController> onMoveComplete; //대리자
private eState state;
private float impactTime = 0.399f; //실제로 영웅의 애니메이션이 공격하는 시간
public float Radius //사거리 속성
{
get
{
return this.radius;
}
}
// Start is called before the first frame update
void Start()
{
this.anim = this.GetComponent<Animator>();
}
//목적지가 몬스터가 아닐 때 하는 움직임
public void Move(Vector3 targetPosition)
{
//타겟을 지움
this.monsterTarget = null;
//이동할 목표 지점을 저장
this.targetPosition = targetPosition;
Debug.Log("Move");
//이동 애니메이션 실행
this.PlayAnimation(eState.Run);
if (this.moveRoutine != null)
{
//이미 코루틴이 실행중이다 -> 중지
this.StopCoroutine(this.moveRoutine);
}
this.moveRoutine = StartCoroutine(this.CoMove());
}
//목적지가 몬스터이면 하는 움직임
public void Move(MonsterController target)
{
this.monsterTarget = target;
this.targetPosition = this.monsterTarget.gameObject.transform.position;
this.PlayAnimation(eState.Run);
if (this.moveRoutine != null)
{
//이미 코루틴이 실행중이다 -> 중지
this.StopCoroutine(this.moveRoutine);
}
this.moveRoutine = this.StartCoroutine(this.CoMove());
}
private IEnumerator CoMove()
{
while (true)
//무한 반복이 되어 유니티가 멈출 수도 있음
//그러므로 yield return 필수
{
//방향을 바라봄
this.transform.LookAt(targetPosition);
//이미 바라봤으니깐 정면으로 이동 (relateTo: Self/지역좌표)
//방향 * 속도 * 시간
this.transform.Translate(Vector3.forward * 2f * Time.deltaTime);
//목표지점과 나 사이의 거리를 계산, 즉 1프레임 마다 거리를 계산
float distance = Vector3.Distance(this.transform.position, this.targetPosition);
//타겟이 있을 경우
if (this.monsterTarget != null)
{
if (distance <= (1f + 1f))
{
break;
}
}
else
{
if (distance <= 0.1f)
{
//도착
break;
}
}
yield return null; //다음 프레임 시작
}
Debug.Log("<color=yellow>도착!</color>");
this.PlayAnimation(eState.Idle);
//대리자 호출
this.onMoveComplete(this.monsterTarget);
}
public void Attack(MonsterController target)
{
this.transform.LookAt(target.transform.position);
this.monsterTarget = target;
//공격애니메이션을 1회 실행
this.PlayAnimation(eState.Attack);
//Debug.LogError("Stop");
}
private void PlayAnimation(eState state)
{
//this.state : prev 현재, state: current: 미래
//중복막기
if (this.state != state)
{
Debug.LogFormat("State: {0} -> {1}", this.state, state);
this.state = state;
this.anim.SetInteger("State", (int)state);
switch (state)
{
case eState.Attack:
this.StartCoroutine(this.WaitForCompleteAttackAnimation());
break;
}
}
else
{
Debug.LogFormat("{0}은 현재와 동일한 상태입니다.", state);
}
}
//코루틴 함수
//Update와 동일하게 동작 가능
private IEnumerator WaitForCompleteAttackAnimation()
{
yield return null; //1프레임 건너뜀
Debug.Log("공격 애니메이션이 끝날때까지 기다림");
AnimatorStateInfo animStateInfo = this.anim.GetCurrentAnimatorStateInfo(0);
//실제 애니메이션 이름으로 찾아야 한다
bool isAttackState = animStateInfo.IsName("Attack01");
Debug.LogFormat("isAttackState: {0}", isAttackState);
if (isAttackState)
{
Debug.LogFormat("Hero's animStateInfo.length: {0}", animStateInfo.length);
}
else
{
Debug.LogFormat("Attack State가 아닙니다");
}
yield return new WaitForSeconds(this.impactTime);
Debug.Log("<color=red>Impact!!!!</color>");
//대상에게 피해를 입힘
this.monsterTarget.HitDamage();
yield return new WaitForSeconds(animStateInfo.length - this.impactTime);
//Idle 애니메이션을 함
//this.anim.SetInteger("State", 0);
this.PlayAnimation(eState.Idle);
}
private void OnDrawGizmos()
{
GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, 1, 40);
}
}
MonsterController
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MonsterController : MonoBehaviour
{
public enum eMonsterState
{
Idle,GetHit,Die
}
[SerializeField]
private float radius = 1.0f; //몬스터의 사거리
[SerializeField]
private int hp = 10;
private Animator anim; //몬스터 애니메이터
private eMonsterState monsterState; //enum
private float hitTime = 0.01f; //애니메이션 실행 시간
public System.Action onHit; //몬스터가 hit되면 메서드를 넘기는 대리자
public System.Action onDie; //몬스터가 죽으면 메서드를 넘기는 대리자
public float Radius //사거리 속성
{
get { return this.radius; }
}
public int Hp //hp 속성
{
get { return this.hp; }
}
// Start is called before the first frame update
void Start()
{
this.anim=GetComponent<Animator>();
GameObject gameMainGo = GameObject.Find("GameMain");
gameMainGo.GetComponent<GameMain>().monsterCount++;
}
// Update is called once per frame
void Update()
{
}
public void HitDamage()
{
//피격애니메이션을 1회 실행
//this.PlayAnimation(eMonsterState.GetHit);
if(this.hp>0)
this.PlayAnimation(eMonsterState.GetHit);
this.hp--;
this.onHit();
if (this.hp <= 0)
{
this.hp = 0;
this.anim.SetInteger("MonsterState",(int)eMonsterState.Die);
this.onDie();
}
}
private void PlayAnimation(eMonsterState monsterState)
{
//중복막기
if (this.monsterState != monsterState)
{
Debug.LogFormat("Monster: {0} -> {1}", this.monsterState, monsterState);
this.monsterState = monsterState;
this.anim.SetInteger("MonsterState", (int)monsterState);
switch (monsterState)
{
case eMonsterState.GetHit:
this.StartCoroutine(this.WaitForCompleteGetHitAnimation());
break;
}
}
else
{
Debug.LogFormat("{0}은 현재와 동일한 상태입니다.", monsterState);
}
}
//코루틴
private IEnumerator WaitForCompleteGetHitAnimation()
{
yield return null;
Debug.Log("피격 애니메이션이 끝날때까지 기다림");
AnimatorStateInfo animStateInfo = this.anim.GetCurrentAnimatorStateInfo(0);
//실제 애니메이션 이름으로 찾아야 한다
bool isGetHit = animStateInfo.IsName("GetHit");
Debug.LogFormat("isGetHit: {0}", isGetHit);
if (isGetHit)
{
Debug.LogFormat("Monster's animStateInfo.length: {0}", animStateInfo.length);
}
else
{
Debug.LogFormat("GetHit State가 아닙니다");
}
yield return new WaitForSeconds(this.hitTime);
Debug.Log("<color=red>Hit!!!!!</color>");
yield return new WaitForSeconds(animStateInfo.length - this.hitTime);
this.PlayAnimation(eMonsterState.Idle);
}
//이벤트 함수
private void OnDrawGizmos()
{
GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, 1, 40);
}
}
'유니티 기초' 카테고리의 다른 글
간단 RPG Test3 (불완전한 장착) (0) | 2023.08.10 |
---|---|
간단 RPG Test 3-1 (0) | 2023.08.10 |
간단 RPG 게임 메인(수정중) (0) | 2023.08.09 |
간단 RPG Test2-2 (0) | 2023.08.09 |
간단 RPG Test2-1 (0) | 2023.08.09 |