SimpleRPG-GameScene에서 플레이어 이동 및 Attack
게임씬을 만들고 캐릭터와 몬스터를 하나씩 만들어주었다.
-애니메이션 추가 및 코드수정(HeroController.cs)
-HeroController에 MoveToMonster구현하고, GameMain 수정
-tag에 collider로 접근하므로 몬스터랑 Ground(Plane)에 collider 추가해줘야한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameMain : MonoBehaviour
{
private HeroController heroController;
private MonsterController monsterController;
private float maxDistance = 100f;
[SerializeField]
private GameObject hitFxPrefab;
private GameObject fxGo;
// Start is called before the first frame update
void Start()
{
this.heroController = GameObject.FindObjectOfType<HeroController>();
this.monsterController = GameObject.FindObjectOfType<MonsterController>();
this.monsterController.onHit = () => {
Debug.Log("이펙트 생성");
Vector3 offset = new Vector3(0, 0.5f, 0);
Vector3 tpos = this.monsterController.transform.position + offset;
Debug.LogFormat("생성위치:{0}", tpos);
//프리팹 인스턴스(복사본) 생성
fxGo = Instantiate(this.hitFxPrefab);
fxGo.transform.position = tpos;//위치 설정
fxGo.GetComponent<ParticleSystem>().Play();//파티클 실행
};
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))//마우스 좌클릭시
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Debug.DrawRay(ray.origin, ray.direction*100f, Color.red,3f);//ray 그리기
RaycastHit hit;// Raycast에서 out 키워드 사용을 위한 변수 선언
if (Physics.Raycast(ray, out hit, maxDistance)) //Ray 충돌검사
{
if (hit.collider.tag == "Monster")//몬스터 클릭시
{//태그를 통해 몬스터에 접근
Debug.LogFormat("Monster: {0}", hit.collider.tag);
//거리를 구한다.
Vector3 targetPosition = hit.collider.gameObject.transform.position;
Vector3 start = this.heroController.transform.position;
float distance = Vector3.Distance(start, targetPosition);
this.monsterController = hit.collider.gameObject.GetComponent<MonsterController>();
//각 반지름 더한거와 비교
float sumRadius = this.heroController.Radius + monsterController.Radius;
//Debug.Log(distance);
if (this.IsWithinRange(distance,sumRadius))//사거리 안에 들어옴 => attack
{
Debug.Log("Attack");
//HeroController에게 공격을 명령
this.heroController.Attack(this.monsterController);
}
else//이동
{
this.heroController.MoveToMonster(monsterController);
}
}
else if (hit.collider.tag == "Ground")
{
Debug.Log(hit.point);
this.heroController.Move(hit.point);
}
}
}
}
private bool IsWithinRange(float distance, float radius)
{
return radius > distance;//true
}
}
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class HeroController : MonoBehaviour
{
public enum eState
{
Idle, Run, Attack
}
public float radius = 1;
public float Radius
{
get
{
return this.radius;
}
}
public eState state;
private MonsterController monsterController;
public GameObject target;//타겟 게임 오브젝트
private Vector3 targetPosition;
private float moveSpeed = 2f;
private Animator anim;
private Coroutine routine;
private float impactTime = 0.399f;
// Start is called before the first frame update
void Start()
{
this.monsterController = GameObject.FindObjectOfType<MonsterController>();
this.target = monsterController.gameObject;
// this.transform.position = targetPosition;
this.anim = GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
}
public void Move(Vector3 targetPosition)
{
//타겟을 지움
this.monsterController = null;
this.targetPosition = targetPosition;
Debug.Log("Move");
//이동시작
if (this.routine != null)
{
//이미 코루틴이 실행중이다 -> 중지
this.StopCoroutine(this.routine);
}
StartCoroutine(this.CoMove());//코루틴 시작
}
public void MoveToMonster(MonsterController target)
{
this.monsterController = target;
this.targetPosition = this.monsterController.transform.position;//이동할 목표 지점을 저장
Debug.Log("MoveToMonster");
// Debug.LogFormat("targetPosition: {0},{1}",targetPosition,this.target.gameObject.transform.position);
//이동시작
if (this.routine != null)
{
//이미 코루틴이 실행중이다 -> 중지
this.StopCoroutine(this.routine);
}
this.routine = StartCoroutine(this.CoMove());
// this.anim.SetInteger("State", 1);//Run Animation
}
private IEnumerator CoMove()
{
while (true)
{
this.transform.LookAt(this.targetPosition);//방향을 바라봄
this.transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime);//정면으로 이동
this.PlayAnimation(eState.Run);//Run 애니메이션 실행
float distance = Vector3.Distance(this.transform.position, this.targetPosition);
// Debug.Log(distance);
if (this.monsterController != null) //타겟이 있을 경우
{//this.targetPosition은 Vector3라 null과 비교하면안된다. 주의.
if (distance <= (1f+1f))
{
this.PlayAnimation(HeroController.eState.Idle);
break;//break없으면 무한루프돼서 유니티 멈춘다.
}
}
else//타겟이 없으면
{
// Debug.Log(distance);
if (distance <= 0.1f)//** 0이 될수 없다 가까워질때 도착한것으로 판단 하자
{//도착
this.PlayAnimation(eState.Idle);//Idle 애니메이션 실행
break;//isMoveStart 같은 변수를 안쓸 수 있다.
}
}
yield return null;//다음 프레임 시작
}
Debug.Log("<color=yellow>도착!</color>");
}
public void PlayAnimation(eState state)
{
if (this.state != state)//중복막기
{
Debug.LogFormat("Hero: {0} => {1}", this.state, state);
this.state = state;
this.anim.SetInteger("State", (int)state);//정수 -> enum
switch (state)
{
case eState.Attack:
//이미 코루틴이 실행중이라면 중지
if (this.routine != null)
{
this.StopCoroutine(this.routine);
}
this.routine = this.StartCoroutine(this.WaitForCompleteAttackAnimation());
break;
}
}
else
{
//Debug.LogFormat("{0}는 현재 상태와 동일한 상태입니다.", state);
}
}
private void OnDrawGizmos()
{
Gizmos.color = Color.green;
GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, this.radius);
}
public void Attack(MonsterController target)
{
Debug.Log("Attack!!");
PlayAnimation(eState.Attack);
}
//코루틴 함수
//IEnumerator 반환타입을 갖는 1개 이상의 yield return을 포함하는 함수
private IEnumerator WaitForCompleteAttackAnimation()
{//상태는 바로 변하지않는다. 다음 프레임에 변한다. 코루틴으로 확인해보자
yield return null;//다음 프레임의 시작.yield return 위치에 주의.
Debug.Log("공격 애니메이션이 끝날때까지 기다림");
AnimatorStateInfo animStateInfo = this.anim.GetCurrentAnimatorStateInfo(0);
bool isAttackState = animStateInfo.IsName("Attack01");//애니매이터의 애니메이션 이름
Debug.LogFormat("isAttackState:{0}", isAttackState);
if (isAttackState)
{
Debug.LogFormat("animatorStateInfo.Length:{0}", animStateInfo.length);
}
else
{
Debug.Log("attack state가 아닙니다.");
}
//yield return new WaitForSeconds(animStateInfo.length);
yield return new WaitForSeconds(this.impactTime); //Impact
Debug.Log("<color=red>Impact</color>");
//대상에게 피해를 입힘
// this.target.state = MonsterController.eState.GetHit;
this.monsterController.PlayAnimation(MonsterController.eState.GetHit);
Debug.LogFormat("after target.state: {0}", this.monsterController.state);
yield return new WaitForSeconds(animStateInfo.length - this.impactTime); //0.72초만큼 기다림
this.monsterController.PlayAnimation(MonsterController.eState.Idle);
//Idle애니메이션을 함
//this.anim.SetInteger("State", 0);
this.PlayAnimation(eState.Idle);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using System;
public class MonsterController : MonoBehaviour
{
public float radius =1f;
public enum eState
{
Idle, Run, GetHit
}
public eState state;
public float Radius
{
get
{
return this.radius;
}
}
public Action onHit;//using System작성하고 써야함
private Animator anim;
// Start is called before the first frame update
void Start()
{
this.anim = GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
}
public void HitDamage()
{
//
//
//Debug.LogError("피해 애니메이션을 실행합니다.");
// this.anim.SetInteger("State", 2);
//this.state = eState.GetHit;
this.onHit();//대리자 사용
}
private void OnDrawGizmos()
{
Gizmos.color = Color.green;
GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, this.radius);
}
public void PlayAnimation(eState state)
{
//state = eState.GetHit;
if (this.state != state)
{
Debug.LogFormat("Monster: {0} => {1}", this.state, state);
this.state = state;
this.anim.SetInteger("State", (int)state);//정수 -> enum
switch (state)
{
case eState.GetHit:
this.HitDamage();
break;
}
}
}
}
'3d 콘텐츠 제작' 카테고리의 다른 글
아이템 획득 후 장비 착용 연습 2 (0) | 2023.08.11 |
---|---|
아이템 획득시, 장착 연습 (0) | 2023.08.11 |
씬의 몬스터 제거시 포탈 생성 연습(동적 데이터 생성 및 관리) (0) | 2023.08.10 |
Unity-공격 연습 (0) | 2023.08.09 |
Unity-플레이어 조작 구현 연습-이동하기 (0) | 2023.08.08 |