먼저 AniEventControl 스크립트를 만들고 Enemy 오브젝트에 자식으로 되어있는 Spider 오브젝트에 스크립트를 붙힘니다
AniEventControl 스크립트 작성
using System.Collections; using System.Collections.Generic; using UnityEngine;
public class AniEventControl : MonoBehaviour { // Start is called before the first frame update void Start() {
}
public void SendAttackEnemy() { transform.parent.gameObject.SendMessage("AttackCalculate"); } // Update is called once per frame void Update() {
} }
그리고 Spider 오브젝트를 선택하고 Animation 을 들어갑니다
읽기전용으로 되어 있는 attack1을 바꿔줘야 합니다
attack1 애니메이션을 Ctrl + D를 눌러서 복사하고 이름을 attack1_new 로 바꾸어서 그것을 드래그하여 animaton에 등록 합니다
그리고 Spider 오브젝트를 선택하고 Window ->Animation ->Animation 을 열고 attack1_new 애니메이션에 공격 타이밍에 Add event 를 누른 다음 Function 을 SendAttackEnemy() 함수를 등록합니다
그리고 EnemyAni 스크립트를 선택하여 수정합니다
attack1 이라 되어 있던 이름을 attack1_new 로 바꿈니다
플레이어 HP가 공격을 받고 0이되었을때 사망처리 하기 위해서
그리고 PlayerFSM 스크립트를 열고 수정합니다
using System.Collections; using System.Collections.Generic; using UnityEngine;
public class PlayerFSM : MonoBehaviour { public enum State { Idle, Move, Attack, AttackWait, Dead } //idle 상태를 기본 상태로 지정 public State currentState = State.Idle;
//마우수 클릭 지점,플레이어가 이동할 목적지의 좌표를 저장할 예정 Vector3 curTargetPos;
GameObject curEnemy;
public float rotAnglePerSecond = 360f; //1초에 플레이어의 방향을 360도 회전한다
public float moveSpeed = 2f; //초당 2미터의 속도로 이동
float attackDelay = 2f; // 공격을 한번하고 다시 공격할때 까지의 지연시간
float attackTimer = 0f; //공격을 하고 난 뒤에 경과되는 시간을 계산하기 위한 변수
float attackDistance = 1.5f; // 공격 거리 (적과의 거리)
float chaseDistance = 2.5f; // 전투 중 적이 도망가면 다시 추적을 시작 하기 위한 거리
//transform.LookAt(목표지점 위치) 목표지점을 향해 오브젝트를 회전 시키는 함수 transform.LookAt(curTargetPos); ChangeState(State.AttackWait, PlayerAni.ANI_ATKIDLE); } void AttackWaitState() { if (attackTimer > attackDelay) { ChangeState(State.Attack, PlayerAni.ANI_ATTACK);
if (curEnemy == null) { //플레이어의 위치와 목표지점의 위치가 같으면, 상태를 Idle 상태로 바꾸라는 명령 if (transform.position == curTargetPos) { ChangeState(State.Idle, PlayerAni.ANI_IDLE); } } else if (Vector3.Distance(transform.position,curTargetPos) < attackDistance)//Vector3.Distance(A,B):A와B사이의 거리 { ChangeState(State.Attack, PlayerAni.ANI_ATTACK); } } void Update() { UpdateState(); } }
게임을 실행시키고 몬스터가 플레이어를 공격하면 플레이어의 HP 가 줄고 0이되면 사망 처리 되는 것을 확인 한다
유니티가 제공하는 유니티 이벤트 기능인 UnityEvent 를 만들고 AddListenr() 함수를 등록해 보겠습니다
플레이어가 몬스터를 공격하였을때 몬스터가 죽으면 몬스터의 죽는 순간의 이벤트를 처리하고
몬스터가 죽음과 동시에 플레이어의 행동을 다시 Idle로 변환하는 SendMessage() 를 처리하여 보겠습니다
CharackterParams 스크립트를 선택하고 스크립트를 수정합니다
//CharackterParams 스크립트수정
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events;//유니티 이벤트를 사용하기 위해서는 네임스페이스를 추가해야함
//CharacterParams는 플레이어의 파라미터 클래스와 몬스터 파라미터 클래스의 부모 클래스 역할을 하게됨 public class CharacterParams : MonoBehaviour { //퍼블릭 변수가 아니라 약식프로퍼티,속성으로 지정 //퍼블릭 변수와 똑같이 사용할수 있지만 유니티 인스펙터에 노출되는 것을 막고 보안을 위해정식 프로퍼티로 전환이 쉬워짐 public int level { get; set; } public int maxHp { get; set; } public int curHp { get; set; } public int attackMin { get; set; } public int attackMax { get; set; } public int defense { get; set; } public bool isDead { get; set; }
[System.NonSerialized] public UnityEvent deadEvent = new UnityEvent();
void Start() { InitParams(); }
//나중에 CharacterParams 클래스를 상속한 자식클래스 에서 //InitParams 함수 에 자신만의 명령어를 추가하기만 하면 자동으로 필요한 명령어들이 실행 public virtual void InitParams() {
}
public int GetRandomAttack() { int randAttack = Random.Range(attackMin, attackMax + 1); return randAttack; }
public void SetEnemyAttack(int enemyAttackPower) { curHp -= enemyAttackPower; UpdateAfterReceiveAttack(); }
//캐릭터가 적으로 부터 공격을 받은 뒤에 자동으로 실행될 함수를 가상함수로 만듬 protected virtual void UpdateAfterReceiveAttack() { print(name + "'s HP: " + curHp);
using System.Collections; using System.Collections.Generic; using UnityEngine;
public class EnemyFSM : MonoBehaviour { public enum State { Idle, //정지 Chase, //추적 Attack, //공격 Dead, //사망 NoState //아무 일도 없는 상태 }
public State currentState = State.Idle;
EnemyParams myParams;
EnemyAni myAni;
Transform player;
PlayerParams playerParams;
float chaseDistance = 5f; // 플레이어를 향해 몬스터가 추적을 시작할 거리 float attackDistance = 2.5f; // 플레이어가 안쪽으로 들오오게 되면 공격을 시작 float reChaseDistance = 3f; // 플레이어가 도망 갈 경우 얼마나 떨어져야 다시 추적
float rotAnglePerSecond = 360f; // 초당 회전 각도 float moveSpeed = 1.3f; // 몬스터의 이동 속도
void ChaseState() { //몬스터가 공격 가능 거리 안으로 들어가면 공격 상태 if (GetDistanceFromPlayer() < attackDistance) { ChangeState(State.Attack, EnemyAni.ATTACK); } else { TurnToDestination(); MoveToDestination(); } }
// Update is called once per frame void Update() { UpdateState(); } }
PlayerFSM 스크립트를 선택하고 수정합니다
using System.Collections; using System.Collections.Generic; using UnityEngine;
public class PlayerFSM : MonoBehaviour { public enum State { Idle, Move, Attack, AttackWait, Dead } //idle 상태를 기본 상태로 지정 public State currentState = State.Idle;
//마우수 클릭 지점,플레이어가 이동할 목적지의 좌표를 저장할 예정 Vector3 curTargetPos;
GameObject curEnemy;
public float rotAnglePerSecond = 360f; //1초에 플레이어의 방향을 360도 회전한다
public float moveSpeed = 2f; //초당 2미터의 속도로 이동
float attackDelay = 2f; // 공격을 한번하고 다시 공격할때 까지의 지연시간
float attackTimer = 0f; //공격을 하고 난 뒤에 경과되는 시간을 계산하기 위한 변수
float attackDistance = 1.5f; // 공격 거리 (적과의 거리)
float chaseDistance = 2.5f; // 전투 중 적이 도망가면 다시 추적을 시작 하기 위한 거리
//transform.LookAt(목표지점 위치) 목표지점을 향해 오브젝트를 회전 시키는 함수 transform.LookAt(curTargetPos); ChangeState(State.AttackWait, PlayerAni.ANI_ATKIDLE); } void AttackWaitState() { if (attackTimer > attackDelay) { ChangeState(State.Attack, PlayerAni.ANI_ATTACK);
if (curEnemy == null) { //플레이어의 위치와 목표지점의 위치가 같으면, 상태를 Idle 상태로 바꾸라는 명령 if (transform.position == curTargetPos) { ChangeState(State.Idle, PlayerAni.ANI_IDLE); } } else if (Vector3.Distance(transform.position,curTargetPos) < attackDistance)//Vector3.Distance(A,B):A와B사이의 거리 { ChangeState(State.Attack, PlayerAni.ANI_ATTACK); } } void Update() { UpdateState(); } }
게임을 실행하여 몬스터가 HP 를 소모 하면 죽는 행동과 플레이어가 IDLE 상태로 변하는 것을 확인 합니다
using System.Collections; using System.Collections.Generic; using UnityEngine;
//CharacterParams는 플레이어의 파라미터 클래스와 몬스터 파라미터 클래스의 부모 클래스 역할을 하게됨 public class CharacterParams : MonoBehaviour { //퍼블릭 변수가 아니라 약식프로퍼티,속성으로 지정 //퍼블릭 변수와 똑같이 사용할수 있지만 유니티 인스펙터에 노출되는 것을 막고 보안을 위해정식 프로퍼티로 전환이 쉬워짐 public int level { get; set; } public int maxHp { get; set; } public int curHp { get; set; } public int attackMin { get; set; } public int attackMax { get; set; } public int defense { get; set; } public bool isDead { get; set; }
void Start() { InitParams(); }
//나중에 CharacterParams 클래스를 상속한 자식클래스 에서 //InitParams 함수 에 자신만의 명령어를 추가하기만 하면 자동으로 필요한 명령어드이 실행 public virtual void InitParams() {
}
public int GetRandomAttack() { int randAttack = Random.Range(attackMin, attackMax + 1); return randAttack; }
public void SetEnemyAttack(int enemyAttackPower) { curHp -= enemyAttackPower; UpdateAfterReceiveAttack(); }
//캐릭터가 적으로 부터 공격을 받은 뒤에 자동으로 실행될 함수를 가상함수로 만듬 protected virtual void UpdateAfterReceiveAttack() { print(name + "'s HP: " + curHp); }
}
그리고 새로운 스크립트를 생성하고 이름을 EnemyParams라 합니다
EnemyParams 스크립트 작성
using System.Collections; using System.Collections.Generic; using UnityEngine;
public class EnemyParams : CharacterParams { public string name; public int exp { get; set; } public int rewardMoney { get; set; } public override void InitParams() { name = "Monster"; level = 1; maxHp = 50; curHp = maxHp; attackMin = 2; attackMax = 5; defense = 1;
using System.Collections; using System.Collections.Generic; using UnityEngine;
public class PlayerParams : CharacterParams { public string name { get; set; } public int curExp { get; set; } public int expToNextLevel { get; set; } public int money { get; set; }
Player 오브젝트를 선택하고 PlayerParams 스크립트를 Player 에 붙힘니다
Enemy 오브젝트를 선택하고 EnemyParams 스크립트를 Enemy에 붙힘니다
그리고 PlayerFSM 스크립트를 선택하고 수정하겠습니다
using System.Collections; using System.Collections.Generic; using UnityEngine;
public class PlayerFSM : MonoBehaviour { public enum State { Idle, Move, Attack, AttackWait, Dead } //idle 상태를 기본 상태로 지정 public State currentState = State.Idle;
//마우수 클릭 지점,플레이어가 이동할 목적지의 좌표를 저장할 예정 Vector3 curTargetPos;
GameObject curEnemy;
public float rotAnglePerSecond = 360f; //1초에 플레이어의 방향을 360도 회전한다
public float moveSpeed = 2f; //초당 2미터의 속도로 이동
float attackDelay = 2f; // 공격을 한번하고 다시 공격할때 까지의 지연시간
float attackTimer = 0f; //공격을 하고 난 뒤에 경과되는 시간을 계산하기 위한 변수
float attackDistance = 1.5f; // 공격 거리 (적과의 거리)
float chaseDistance = 2.5f; // 전투 중 적이 도망가면 다시 추적을 시작 하기 위한 거리
//transform.LookAt(목표지점 위치) 목표지점을 향해 오브젝트를 회전 시키는 함수 transform.LookAt(curTargetPos); ChangeState(State.AttackWait, PlayerAni.ANI_ATKIDLE); } void AttackWaitState() { if (attackTimer > attackDelay) { ChangeState(State.Attack, PlayerAni.ANI_ATTACK);
//MoveTo(캐릭터가 이동할 목표 지점의 좌표) public void MoveTo(Vector3 tPos) { curEnemy = null; curTargetPos = tPos; ChangeState(State.Move, PlayerAni.ANI_WALK); }
void TurnToDestination() { // Quaternion lookRotation(회전할 목표 방향) : 목표 방향은 목적지 위치에서 자신의 위치를 빼면 구함 Quaternion lookRotation = Quaternion.LookRotation(curTargetPos - transform.position);
if (curEnemy == null) { //플레이어의 위치와 목표지점의 위치가 같으면, 상태를 Idle 상태로 바꾸라는 명령 if (transform.position == curTargetPos) { ChangeState(State.Idle, PlayerAni.ANI_IDLE); } } else if (Vector3.Distance(transform.position,curTargetPos) < attackDistance)//Vector3.Distance(A,B):A와B사이의 거리 { ChangeState(State.Attack, PlayerAni.ANI_ATTACK); } } void Update() { UpdateState(); } }
게임을 실행하고 플레이어가 적을 공격하였을때 콘솔창에 적의 HP 가 깍이는 것을 확인합니다