개발 TIL

7/5 부트캠프 개발 TIL (보스몹 구현)

HJTL 2024. 7. 10. 20:54

오늘 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 스파르타 코딩클럽