어제 검사 적을 구현했으면 다음에는 궁수 적을 구현해보자
궁수 적은 검사 적과 달리 근접해서 공격하는 것보다 원거리에서 공격하는 것이 주특기다.
상태머신 구현은 검사 적이랑 비슷하나 내부에서는 다른 코드로 동작을 다르게 한다.
먼저 BowEnemy를 구현하자 (SwordEnemy랑 비슷하다)
using UnityEngine;
public interface IBowType
{
public void UseArrowAttack();
public void UseArrowSkill();
}
public class BowEnemy : NormalEnemy, IBowType
{
[Header("상태 머신")]
public BowEnemyStateMachine stateMachine;
public NormalEnemyAnimation animationData;
ArrowPool arrowPool; //공격판정이 없어진 대신 ArrowPool이 생겼다.
protected override void Awake()
{
base.Awake();
stateMachine = new BowEnemyStateMachine(this);
animationData = new NormalEnemyAnimation();
animationData.Initialize();
InitStat();
boxCollider.enabled = false; //방어코드 (시작시 활성화됨)
}
private void Start()
{
arrowPool = GameManager.instance.ArrowPool; //풀을 게임 메니저에 관리하도록 했다.
}
private void Update()
{ stateMachine.Update(); }
private void FixedUpdate()
{ stateMachine.PhysicsUpdate(); }
private void InitStat()
{
stat.maxHealth = 15f;
stat.maxMana = 15f;
stat.attackDamage = 2f;
stat.attackSpeed = 2f;
stat.moveSpeed = 2f;
}
public override void EnemyDeath()
{
base.EnemyDeath();
stateMachine.ChangeState(stateMachine.deadState);
}
//FSM
public override void EnterStateMachine()
{
SetBattleMode(true);
stateMachine.ChangeState(stateMachine.idleState);
}
public override void ExitStateMachine()
{
SetBattleMode(false);
stateMachine.ChangeState(null);
}
//인터페이스
public void UseArrowAttack()
{
//화살을 발사하는 것을 보여주기 위한 코드
float mirrorXScale = transform.localScale.x > 0 ? 2f : -2f;
Vector3 spawnControll = new Vector2(0, 0.5f);
Vector3 mirror = new Vector3(mirrorXScale, 2);
GameObject obj = arrowPool.SpawnFromPool("Arrow");
obj.transform.position = transform.position + spawnControll;
obj.transform.localScale = mirror;
obj.GetComponent<Arrow>().ArrowDamage = stat.attackDamage;
}
public void UseArrowSkill() { }
}
다음 상태머신을 구현하자
public class BowEnemyStateMachine : CharacterState
{
public BowEnemy enemy;
public BowIdleState idleState;
public BowMoveState moveState;
public BowAttackState attackState;
public BowSkillState skillState;
public BowStunState stunState;
public BowDeadState deadState;
public BowEnemyStateMachine(BowEnemy _enemy)
{
enemy = _enemy;
idleState = new BowIdleState(this);
moveState = new BowMoveState(this);
attackState = new BowAttackState(this);
skillState = new BowSkillState(this);
stunState = new BowStunState(this);
deadState = new BowDeadState(this);
}
}
using UnityEngine;
public class BowIdleState : BowEnemyBaseState
{
private float flagTime = 2.3f;
private float readyTime;
public BowIdleState(BowEnemyStateMachine _stateMachine) : base(_stateMachine) { }
public override void Enter()
{
base.Enter();
StartAnimation(stateMachine.enemy.animationData.IdleParameterHash);
flagTime = Random.Range(1.6f, 1.9f);
}
public override void Exit()
{
base.Exit();
StopAnimation(stateMachine.enemy.animationData.IdleParameterHash);
}
public override void Update()
{
base.Update();
readyTime += Time.deltaTime;
if(readyTime > flagTime)
{
readyTime = 0f;
LookingPlayer();
}
}
private void LookingPlayer()
{
//적이 플레이어를 바라보게
stateMachine.enemy.TurnAround();
if (GetDistance(CharacterManager.instance.player.transform) > aimDistance)
stateMachine.ChangeState(stateMachine.moveState);
else
ChoiceAttackOrSkill();
}
}
public class BowMoveState : BowEnemyBaseState
{
private Vector2 moving = Vector2.zero;
public BowMoveState(BowEnemyStateMachine _stateMachine) : base(_stateMachine) { }
public override void Enter()
{
base.Enter();
StartAnimation(stateMachine.enemy.animationData.MoveParameterHash);
//플레이어 방향대로 전진
if (stateMachine.enemy.transform.localScale.x > 0) //왼쪽
moving = Vector2.left;
else if (stateMachine.enemy.transform.localScale.x < 0) //오른쪽
moving = Vector2.right;
}
public override void Exit()
{
base.Exit();
StopAnimation(stateMachine.enemy.animationData.MoveParameterHash);
}
public override void Update()
{
base.Update();
//플레이어와 근접해있으면
if (GetDistance(CharacterManager.instance.player.transform) < aimDistance)
stateMachine.ChangeState(stateMachine.attackState);
}
public override void PhysicsUpdate()
{
base.PhysicsUpdate();
stateMachine.enemy.rb.velocity = moving * stateMachine.enemy.stat.moveSpeed;
}
}
public class BowAttackState : BowEnemyBaseState
{
public BowAttackState(BowEnemyStateMachine _stateMachine) : base(_stateMachine) { }
public override void Enter()
{
base.Enter();
StartAnimation(stateMachine.enemy.animationData.AttackParameterHash);
stateMachine.enemy.UseArrowAttack();
}
public override void Exit()
{
base.Exit();
StopAnimation(stateMachine.enemy.animationData.AttackParameterHash);
}
public override void Update()
{
base.Update();
float normalizeTime = stateMachine.enemy.NormalizeTime(stateMachine.enemy.animator, "Attack");
if (normalizeTime < 1f) //한 루프가 끝나면
stateMachine.ChangeState(stateMachine.idleState);
}
}
public class BowSkillState : BowEnemyBaseState
{
public BowSkillState(BowEnemyStateMachine _stateMachine) : base(_stateMachine) { }
public override void Enter()
{
base.Enter();
StartAnimation(stateMachine.enemy.animationData.SkillParameterHash);
stateMachine.enemy.UseArrowSkill();
}
public override void Exit()
{
base.Exit();
StopAnimation(stateMachine.enemy.animationData.SkillParameterHash);
}
public override void Update()
{
base.Update();
float normalizeTime = stateMachine.enemy.NormalizeTime(stateMachine.enemy.animator, "Skill");
if (normalizeTime < 1f) //한 루프가 끝나면
stateMachine.ChangeState(stateMachine.idleState);
}
}
//StunState는 생략
public class BowDeadState : BowEnemyBaseState
{
public BowDeadState(BowEnemyStateMachine _stateMachine) : base(_stateMachine) { }
public override void Enter()
{
base.Enter();
StartAnimation(stateMachine.enemy.animationData.DeadParameterHash);
}
public override void Exit()
{
base.Exit();
StopAnimation(stateMachine.enemy.animationData.DeadParameterHash);
}
}
애니메이션 관련한 것은 SwordEnemy를 구현할 때 NormalEnemyAnimation을 참고하길 바란다.
구현했으면 화살을 구현하자
그리고 게임 메니저에서 Arrow풀을 만들어보자
게임 메니저 안에서 Arrow태그가 있는 Arrow오브젝트를 Prefab에 넣고 size를 맞게 조절하면
이렇게 생성된다.
이런 구조에서 BowEnemy를 다시 보면(주석참고)
private void Start()
{
arrowPool = GameManager.instance.ArrowPool; //처음에 게임메니저에 있는 ArrowPool을 여기에 가져와
}
public void UseArrowAttack()
{
float mirrorXScale = transform.localScale.x > 0 ? 2f : -2f;
Vector3 spawnControll = new Vector2(0, 0.5f);
Vector3 mirror = new Vector3(mirrorXScale, 2);
//arrowPool에서 SpawnFromPool로 obj를 가져와서
//위치를 맞춘 다음에 데미지를 Enemy의 스탯에서 가져와 발사한다.
GameObject obj = arrowPool.SpawnFromPool("Arrow");
obj.transform.position = transform.position + spawnControll;
obj.transform.localScale = mirror;
obj.GetComponent<Arrow>().ArrowDamage = stat.attackDamage;
}
다음에 화살 역할을 해줄 스크립트를 작성하면
using UnityEngine;
public class Arrow : MonoBehaviour
{
public float ArrowDamage { get; set; }
private Rigidbody2D rgdBody;
private SpriteRenderer renderer;
private Vector2 direction = Vector2.zero;
private void Awake()
{
rgdBody = GetComponent<Rigidbody2D>();
renderer = GetComponent<SpriteRenderer>();
}
private void Start()
{
renderer.flipY = transform.localScale.x < 0;
direction = renderer.flipY ? Vector2.right : Vector2.left;
}
private void FixedUpdate()
{
rgdBody.AddForce(direction * 3, ForceMode2D.Impulse);
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == ("Player"))
{
//플레이어 피격 데미지
collision.gameObject.GetComponent<Player>().hpSystem.TakeDamage(ArrowDamage);
}
gameObject.SetActive(false);
}
}
//ArrowPool.cs
using System.Collections.Generic;
using UnityEngine;
public class ArrowPool : MonoBehaviour
{
[System.Serializable]
public class Pool
{
public string tag;
public GameObject prefab;
public int size;
}
public List<Pool> pools = new List<Pool>();
public Dictionary<string, Queue<GameObject>> poolDictionary;
private void Awake()
{
poolDictionary = new Dictionary<string, Queue<GameObject>>();
foreach (var pool in pools)
{
Queue<GameObject> queue = new Queue<GameObject>();
for (int i = 0; i < pool.size; i++)
{
GameObject obj = Instantiate(pool.prefab, transform);
obj.SetActive(false);
queue.Enqueue(obj);
}
poolDictionary.Add(pool.tag, queue);
}
}
public GameObject SpawnFromPool(string tag)
{
if (!poolDictionary.ContainsKey(tag))
return null;
GameObject obj = poolDictionary[tag].Dequeue();
poolDictionary[tag].Enqueue(obj);
obj.SetActive(true);
return obj;
}
}
검사 적을 이어서 궁수 적을 구현하였다.
by 스파르타 코딩클럽
'개발 TIL' 카테고리의 다른 글
7/12 부트캠프 개발 TIL (게임에서 필요한 요소) (0) | 2024.07.19 |
---|---|
7/11 부트캠프 개발 TIL (적 구현하기 3) (1) | 2024.07.18 |
7/9 부트캠프 개발 TIL (적 구현하기 1) (0) | 2024.07.18 |
7/8 부트캠프 개발 TIL (프로젝트 최적화) (0) | 2024.07.10 |
7/5 부트캠프 개발 TIL (보스몹 구현) (0) | 2024.07.10 |