오늘 TIL은 쓸 내용이 없어서 이 내용이라도 구현하고자 한다
.최종 프로젝트 때 사용할 3번째 보스를 구현했다.
보스의 동작은 Ready > Fly > Throw > Fall 순으로 진행된다.
사용 기능은 FSM을 사용하였고 물리는 Rigidbody의 주로 사용하는 함수를 작성하였다.
먼저 기반 스크립트부터 보자
using UnityEngine;
public class Boss3 : Enemy
{
public Boss3StateMachine boss3StateMachine;
//폭탄 오브젝트 풀
public ObjectPool Bombs;
public Transform targetPlayer { get; private set; } //플레이어 목표 지점
public float CurrentGravity { get; private set; }
private void Awake()
{
CharacterManager.instance.enemy = this;
rb = GetComponent<Rigidbody2D>();
boxCollider = GetComponent<BoxCollider2D>();
animator = GetComponent<Animator>();
Bombs = GetComponent<ObjectPool>(); //임시로 폭탄이 떨어지는 걸 보려고
//targetPlayer = CharacterManager.instance.player.transform;
boss3StateMachine = new Boss3StateMachine(this);
CurrentGravity = rb.gravityScale;
}
private void Start()
{
OnIgnoreCollisionPlayer(true);
boss3StateMachine.ChangeState(boss3StateMachine.readyState);
}
private void FixedUpdate()
{
boss3StateMachine.PhysicsUpdate();
targetPlayer = CharacterManager.instance.player.transform; //이걸 어떻게 하면 좋을까?
}
private void Update()
{
boss3StateMachine.Update();
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Ground"))
{
if (boss3StateMachine.currentState.Equals(boss3StateMachine.fallState))
boss3StateMachine.ChangeState(boss3StateMachine.readyState);
}
}
}
using UnityEngine;
public class Enemy : MonoBehaviour
{
public Rigidbody2D rb;
public BoxCollider2D boxCollider;
public Animator animator;
public SpriteRenderer exclamationMarkSprite;
public bool isField = false;
public EnemyStatData enemyStatData;
}
이 코드는 위에서 상속시킨 코드인데 필드에서만 사용했다.
베이스를 토대로 상태머신을 만들자
public class Boss3StateMachine : CharacterState
{
public Boss3 boss3;
//상태머신
public Boss3ReadyState readyState { get; }
public Boss3FlyState flyState { get; }
public Boss3ThrowState throwState { get; }
public Boss3FallState fallState { get; }
public Boss3DeadState deadState { get; }
public Boss3StateMachine(Boss3 _boss3)
{
boss3 = _boss3;
readyState = new Boss3ReadyState(this);
flyState = new Boss3FlyState(this);
throwState = new Boss3ThrowState(this);
fallState = new Boss3FallState(this);
deadState = new Boss3DeadState(this);
}
}
public class Boss3BaseState : IState
{
protected Boss3StateMachine stateMachine;
protected bool isPlayerRight = false;
public Boss3BaseState(Boss3StateMachine _stateMachine)
{
this.stateMachine = _stateMachine;
}
public virtual void Enter() { }
public virtual void Exit() { }
public virtual void PhysicsUpdate() { }
public virtual void Update() { }
}
그리고 각 상태에 필요한 기능을 스크립트로 나눠서 구현하자
ReadyState
using UnityEngine;
public class Boss3ReadyState : Boss3BaseState
{
float readyDelay = 0f;
public Boss3ReadyState(Boss3StateMachine _stateMachine) : base(_stateMachine)
{
}
public override void Update()
{
base.Update();
ReadyToFly();
}
//잠시 후에 flyState로 전환
private void ReadyToFly()
{
readyDelay += Time.deltaTime;
if (readyDelay > 1f)
stateMachine.ChangeState(stateMachine.flyState);
}
}
FlyState
using UnityEngine;
public class Boss3FlyState : Boss3BaseState
{
//FlyState 필드
private float maxHeight;
private float flySpeed;
public Boss3FlyState(Boss3StateMachine _stateMachine) : base(_stateMachine)
{
//날으는 속성 설정
maxHeight = 3.75f;
flySpeed = 2.5f;
}
public override void PhysicsUpdate()
{
base.PhysicsUpdate();
FlyUp();
}
private void FlyUp()
{
stateMachine.boss3.rb.gravityScale = 0f;
if (stateMachine.boss3.transform.position.y < maxHeight)
stateMachine.boss3.rb.AddForce(Vector2.up * flySpeed, ForceMode2D.Force);
else
{
//올라갔으면 폭탄 투하
stateMachine.boss3.rb.velocity = Vector2.zero;
stateMachine.ChangeState(stateMachine.throwState);
}
}
}
ThrowState 사실 여기서 코루틴을 사용해서 폭탄이 다 떨어뜨리면 내려가게 구현하고 싶은데 MonoBehavior에 상속받은 클래스에서만 사용이 가능해서 Time으로 대체하였다.
using UnityEngine;
public class Boss3ThrowState : Boss3BaseState
{
private float nowDelay = 0f; //현재 딜레이
private float timeDelay = 1f; //목적 딜레이 (일정시간)
public Boss3ThrowState(Boss3StateMachine _stateMachine) : base(_stateMachine)
{
}
public override void Enter()
{
base.Enter();
nowDelay = 0f; //시간 초기화
ThrowStart();
}
public override void Update()
{
base.Update();
//일정시간이 지나면 fallState로
nowDelay += Time.deltaTime;
if(nowDelay > timeDelay)
{
stateMachine.ChangeState(stateMachine.fallState);
return;
}
}
private void ThrowStart()
{
//위치를 보스있는데로
//3개의 폭탄을 앞으로 투하
ThrowBomb();
}
private void ThrowBomb()
{
int poolSize = stateMachine.boss3.Bombs.PoolDictionary["Bomb"].Count;
for (int i = 0; i < poolSize; i++)
{
GameObject bomb = stateMachine.boss3.Bombs.SpawnFromPool("Bomb");
bomb.transform.position = stateMachine.boss3.transform.position;
Boss3Bomb aimBomb = bomb.GetComponent<Boss3Bomb>();
if (stateMachine.boss3.transform.position.x > stateMachine.boss3.targetPlayer.position.x)
{
bomb.transform.Translate(Vector2.left);
aimBomb.SetDirection(Vector2.left);
}
else
{
bomb.transform.Translate(Vector2.right);
aimBomb.SetDirection(Vector2.right);
}
}
}
}
떨어질 폭탄을 생성하고 무한한 사용을 위해 폭탄 코드를 구현하고 오브젝트 풀로 관리
먼저 Bomb스크립트 부터 보면
using UnityEngine;
public class Boss3Bomb : MonoBehaviour
{
private Rigidbody2D rgdby;
private float playerDistance;
private float randomDistance;
private void Awake()
{
rgdby = GetComponent<Rigidbody2D>();
}
public void SetDirection(Vector2 dir)
{
//플레이어 방향으로 전환
playerDistance = Vector2.Distance(transform.position ,CharacterManager.instance.player.transform.position);
randomDistance = Random.Range(playerDistance / 2, playerDistance); //현재 플레이어 위치반에서 플레이어의 정위치까지
rgdby.AddForce(dir * randomDistance, ForceMode2D.Impulse); //떨어질때 나가는 거리
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Ground"))
{
gameObject.SetActive(false);
}
if (collision.gameObject.CompareTag("Player"))
{
gameObject.SetActive(false);
}
}
}
그리고 Bomb을 관리할 오브젝트 풀
using System.Collections.Generic;
using UnityEngine;
public class ObjectPool : MonoBehaviour
{
[System.Serializable]
public class Pool
{
public string tag;
public GameObject prefab;
public int size;
}
public List<Pool> Pools;
public Dictionary<string, Queue<GameObject>> PoolDictionary;
private void Awake()
{
PoolDictionary = new Dictionary<string, Queue<GameObject>>();
Queue<GameObject> objectPool = new Queue<GameObject>();
foreach (var pool in Pools)
{
for (int i = 0; i < pool.size; i++)
{
GameObject obj = Instantiate(pool.prefab);
obj.SetActive(false);
objectPool.Enqueue(obj);
}
PoolDictionary.Add(pool.tag, objectPool);
}
}
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;
}
}
보스 캐릭터를 얘로 잡긴 했는데 폭탄이 들어가면 어색할 수도 있으니 나중에 마법구나 어둠의 소환으로 대체해보자
FallState
using UnityEngine;
public class Boss3FallState : Boss3BaseState
{
private Transform findPlayer;
private float playerDistance; //플레이어간의 거리
public Boss3FallState(Boss3StateMachine _stateMachine) : base(_stateMachine)
{
}
public override void Enter()
{
base.Enter();
SettingTarget();
FallDown();
}
private void SettingTarget()
{
findPlayer = CharacterManager.instance.player.transform;
playerDistance = Vector2.Distance(findPlayer.position, stateMachine.boss3.transform.position);
}
private void FallDown()
{
stateMachine.boss3.rb.gravityScale = stateMachine.boss3.CurrentGravity;
stateMachine.boss3.rb.AddForce(findPlayer.position, ForceMode2D.Impulse);
}
}
적용하면 맨 첫번째 동영상에서 보이듯이 나타날 것이다.
by 스파르타 코딩클럽
'개발 TIL' 카테고리의 다른 글
7/9 부트캠프 개발 TIL (적 구현하기 1) (0) | 2024.07.18 |
---|---|
7/8 부트캠프 개발 TIL (프로젝트 최적화) (0) | 2024.07.10 |
7/4 부트캠프 개발 TIL (Debug의 최적화) (0) | 2024.07.09 |
7/3 부트캠프 개발 TIL (타일 룰, 애니메이션) (0) | 2024.07.08 |
7/2 부트캠프 개발 TIL (타일맵 콜라이더) (0) | 2024.07.05 |