유니티 기초
간단 RPG Test2-1
EasyCastleT
2023. 8. 9. 13:21
버튼을 통한 공격 및 그에 걸맞는 애니메이션 작동
Test2_PlayerAttackSceneMain (버튼의 이벤트를 연결, 사거리 지정)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace Test2
{
public class Test2_PlayerAttackSceneMain : MonoBehaviour
{
[SerializeField]
private Button btnAttack;
[SerializeField]
private HeroController heroController;
[SerializeField]
private MonsterController monsterController;
// Start is called before the first frame update
void Start()
{
//이벤트 등록
this.btnAttack.onClick.AddListener(() =>{
//사거리 체크
//두점과의 거리, 두 오브젝트의 radius의 합을 알아야 한다.
//B-A : C (방향벡터) magnitude (벡터의 길이)
Vector3 b = monsterController.transform.position;
Vector3 a = heroController.transform.position;
Vector3 c = b - a; //정규화되지 않은 방향벡터 (방향과 크기)
float distance = c.magnitude;
Debug.LogFormat("distance:{0}", distance);
Debug.DrawLine(a, b); //영웅과 몬스터 사이의 길이를 출력
//시작위치, 방향 , 출력되는 시간, 색깔, 타입
//DrawArrow.ForDebug(a, c.normalized , 3f,Color.red,ArrowType.Solid);
//Vector3.Distance
//두 컴포넌트의 radius(반지름) 합
float sumRadius = this.heroController.Radius + this.monsterController.Radius;
Debug.LogFormat("sumRadius: {0}", sumRadius);
Debug.LogFormat("<color=yellow>distance: {0}, sumRadius: {1} </color>",
distance, sumRadius);
Debug.LogFormat("isWithinRange: <color=lime>{0}</color>",
this.isWithinRange(distance, sumRadius));
if(this.isWithinRange(distance, sumRadius))
{
//HeroController에게 공격을 명령
this.heroController.Attack(this.monsterController);
}
});
}
//사거리 계산
private bool isWithinRange(float distance, float radius)
{
return radius >= distance; //true
}
// Update is called once per frame
void Update()
{
}
}
}
HeroController : 영웅의 전체적인 제어
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Test2
{
public class HeroController : MonoBehaviour
{
public enum eState
{
Idle,Attack
}
[SerializeField]
private float radius = 1.0f;
private Animator anim;
private eState state;
private float impactTime = 0.399f;
private MonsterController target;
public float Radius
{
get
{
return this.radius;
}
}
// Start is called before the first frame update
void Start()
{
this.anim = GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
}
public void Attack(MonsterController target)
{
this.target = target;
//공격애니메이션을 1회 실행
this.PlayAnimation(eState.Attack);
}
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.target.HitDamage();
yield return new WaitForSeconds(animStateInfo.length-this.impactTime);
//Idle 애니메이션을 함
//this.anim.SetInteger("State", 0);
this.PlayAnimation(eState.Idle);
}
private void OnDrawGizmos()
{
Gizmos.color = Color.yellow;
GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, radius);
}
}
}
MonsterController: 몬스터를 전체적으로 제어
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Test2
{
public class MonsterController : MonoBehaviour
{
public enum eMonsterState
{
Idle,GetHit
}
[SerializeField]
private float radius = 1.0f;
private Animator anim;
private eMonsterState monsterState;
private float hitTime = 0.166f;
public float Radius
{
get
{
return this.radius;
}
}
// 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()
{
//피격애니메이션을 1회 실행
//this.PlayAnimation(eMonsterState.GetHit);
this.PlayAnimation(eMonsterState.GetHit);
}
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()
{
Gizmos.color = Color.yellow;
GizmosExtensions.DrawWireArc(this.transform.position, this.transform.forward, 360, radius);
}
}
}
1.사거리에 들어오지 않으면, 버튼을 눌러도 공격하지 않는다.
2.사거리에 들어오면 공격을 한다.
2-1. 영웅이 실질적으로 공격하는 애니메이션을 끝나는 시간에, 몬스터 피격애니메이션을 실행
2-2. 이 부분을 코루틴을 통해 지연시키는 방식으로 작성을 하였다.