Unity 입문 팀프로젝트 - 3

이준호·2023년 12월 4일
0
post-custom-banner

📌 2D 입문 팀프로젝트

ItemManager.cs에 있는 force와 coolTime의 값 때문에 문제가 많았다.

프로퍼티를 잘 몰랐어서 아래 수정하기 전에는 프로퍼티 없이 쓰면서 _player.force하며 하나하나 참조를 해가며 썼었다. 나는 상속만 받는다면 거기서 쓰는 force나 coolTime값이 다 같은 값을 참조하는줄 알고 착각했었기 때문이다.

그걸 몰랐던 나는 문제를 알게 된 이유가 Bounce와 GuidedMissile쪽을 만들어 _player.force값이 필요할 때 접근자 보호 때문에 사용을 할수가 없어서 이상하다고 느끼게 되었다. 분명 상속을 받는데? 하고 생각했는데..

그 이유때문에 튜터님에게 질문하고 프로퍼티에 대해서 듣고 적용해보며 오류가나고 잡다보니 프로퍼티에 대해서 느낌을 알게 되었다!

그냥 내가 값의 기준을 _player.force~coolTime으로 잡지 않고 아래처럼 처름부터 프로퍼티로 하여 값을 참조하고 변경했다면 훨씬 쉽고 더 코드가 간결해졌을텐데 아쉽다.

일단 아래처럼 내가 보기 편하고 관리하기 쉽게 해놨다. 다른 사람이 볼 때는 좀 더러워 보일수 있지만 일단 다른 할게 많기에 이렇게 해두고 차후에 시간이 남으면 코드들을 더 깔끔하게 이어지게 하고싶다.

➔ 수정된 부분

ItemManager.cs

  • ItemManager에서 아이템의 타입(Enum)으로 관리
  • force, coolTime같은 참조 및 변경 해야 하는 변수는 프로퍼티로 따로 운용
  • 아이템을 먹으면 ItemManager에서 Player의 현재 Bullet상태를 바꿔줌.
public class ItemManager : MonoBehaviour
{
    #region ItemTypeEnum
    public enum ItemType
    {
        Normal,
        BulletUPItem,       // bullet 갯수 증가
        PenetrateItem,      // 관통
        BounceItem,         // 튕김
        GuidedMissileItem   // 유도탄
    }
    #endregion

    #region Global_Variale
    public PlayerAttackSystem _player;

    // Basic Value
    [SerializeField] protected float force = 5f;
    [SerializeField] protected float coolTime = 1.0f;

    public float Force { get { return force; } set {  force = value; } }
    public float CoolTime {  get {  return coolTime; } set {  coolTime = value; } }
    #endregion

    private void Awake()
    {
        _player = FindObjectOfType<PlayerAttackSystem>();
    }

    #region Continuation_Item
    protected void BulletDelayLower()
    {
        coolTime -= 0.05f;
    }

    protected void BulletSpeedUp()
    {
        force += 0.5f;
    }
    #endregion

    #region OneTime_Item
    protected void BulletCountUpItem()
    {
        BulletStateReset(_player);
        _player.currentItem = ItemType.BulletUPItem;
    }

    protected void BulletPenetrateItem()
    {
        BulletStateReset(_player);
        _player.currentItem = ItemType.PenetrateItem;
    }

    protected void BulletBounceItem()
    {
        BulletStateReset(_player);
        _player.currentItem = ItemType.BounceItem;
    }

    protected void BulletGuidedMissileItem()
    {
        BulletStateReset(_player);
        _player.currentItem = ItemType.GuidedMissileItem;
    }
    #endregion

    private void BulletStateReset(PlayerAttackSystem player)
    {
        player.itemDuration = 0f;
        player.itemDuration = 5;
    }
}



PlayerAttackSystem.cs

  • 'Space'입력을 받으면 이벤트 발동시 공격 실행
  • currentItem상태에 따라 RecallBullet()에서 각각 다른 메서드 실행 ( 다른 종류의 공격 )
  • currentItem상태에 따라 찍어내는 Prefab이 다름.
  • Update()에서 아이템의 지속시간 설정.
  • Coroutine을 이용하여 공격의 쿨타임 설정.
public class PlayerAttackSystem : ItemManager
{
    #region Example
    //// Coroutine Caching
    //private IEnumerator ItemCoolTime;
    //private WaitForFixedUpdate fixedUpdate = new WaitForFixedUpdate();
    #endregion

    #region Global_Variale
    TopDownCharacterController _controller;

    Rigidbody2D bulletRigid;    // bullet Prefab Clone Rigidbody

    GameObject bullet; // ( bullet는 player가 발사하는 총알 Prefab )
    GameObject PenetrateItemBullet;
    GameObject bounceBullet;
    GameObject guidedMissileBullet;

    private bool coolTimeCheck = true;

    // ItemType Reset Value
    public ItemType currentItem;

    public float itemDuration = 0f;
    #endregion

    #region Initialization_Settings
    private void Awake()
    {
        _controller = GetComponent<TopDownCharacterController>();
        bullet = Resources.Load<GameObject>("Prefabs/Bullet");
        PenetrateItemBullet = Resources.Load<GameObject>("Prefabs/PenetrateItemBullet");
        bounceBullet = Resources.Load<GameObject>("Prefabs/BounceBullet");
        guidedMissileBullet = Resources.Load<GameObject>("Prefabs/GuidedMissileBullet");

    }

    private void Start()
    {
        _controller.OnAttackEvent += Attack;
    }
    #endregion

    #region Player_AttackLogic
    private void Update()
    {
        BulletStateCheck();
    }

    private void Attack()
    {
        // CoolTime Check
        if (coolTimeCheck == true) StartCoroutine(CollTime(CoolTime));
    }

    private void RecallBullet()
    {
        switch (currentItem)
        {
            case ItemType.Normal:
                ItemNormalBullet();
                break;
            case ItemType.BulletUPItem:
                ItemBulletCountUp();
                break;
            case ItemType.PenetrateItem:
                ItemBulletPenetrateItem();
                break;
            case ItemType.BounceItem:
                ItemBulletBounce();
                break;
            case ItemType.GuidedMissileItem:
                ItemBulletGuidedMissile();
                break;
            default:
                break;
        }
    }

    private void ApplyAttck(GameObject obj)
    {
        bulletRigid = obj.GetComponent<Rigidbody2D>();
        bulletRigid.AddForce(transform.up * Force, ForceMode2D.Impulse);
    }

    private IEnumerator CollTime(float time)
    {
        // CoolTime Setting
        coolTimeCheck = false;

        RecallBullet();

        while (time > 0.0f)
        {
            time -= Time.deltaTime;
            yield return new WaitForFixedUpdate();
        }

        // CoolTime Reset
        coolTimeCheck = true;
    }
    #endregion

    #region BulletState
    private void ItemNormalBullet()
    {
        GameObject playerbullet = Instantiate(bullet);
        playerbullet.transform.position = new Vector3(transform.position.x, transform.position.y + 0.4f, transform.position.z);
        ApplyAttck(playerbullet);
    }

    private void ItemBulletCountUp()
    {
        GameObject playerBullet1 = Instantiate(bullet);
        GameObject playerBullet2 = Instantiate(bullet);
        playerBullet1.transform.position = new Vector3(transform.position.x - 0.2f, transform.position.y + 0.4f, transform.position.z);
        playerBullet2.transform.position = new Vector3(transform.position.x + 0.2f, transform.position.y + 0.4f, transform.position.z);
        ApplyAttck(playerBullet1);
        ApplyAttck(playerBullet2);
    }

    private void ItemBulletPenetrateItem()
    {
        GameObject playerbullet = Instantiate(PenetrateItemBullet);
        playerbullet.transform.position = new Vector3(transform.position.x, transform.position.y + 0.4f, transform.position.z);
        ApplyAttck(playerbullet);
    }

    private void ItemBulletBounce()
    {
        GameObject playerbullet = Instantiate(bounceBullet);
        playerbullet.transform.position = new Vector3(transform.position.x, transform.position.y + 0.4f, transform.position.z);
        ApplyAttck(playerbullet);
    }

    private void ItemBulletGuidedMissile()
    {
        GameObject playerbullet = Instantiate(guidedMissileBullet);
        playerbullet.transform.position = new Vector3(transform.position.x, transform.position.y + 0.4f, transform.position.z);
    }
    #endregion

    private void BulletStateCheck()
    {
        if (currentItem != ItemType.Normal && itemDuration > 0f)
        {
            itemDuration -= Time.deltaTime;
        }
        if (itemDuration <= 0f && currentItem != ItemType.Normal)
        {
            itemDuration = 0f;
            currentItem = ItemType.Normal;
        }
    }
}



ItemScripts

  • 예시) Bonce Item
  • 아이템 스크립트 들은 ItemManager를 상속
  • player의 currentItem타입을 바꿔주는 메서드와 파괴 처리만 들어가있다.
public class BulletBounceItem : ItemManager
{
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.tag == "Player")
        {
            // Bullet Bounce
            BulletBounceItem();

            Destroy(gameObject);    // 추후 오브젝트 풀링
        }
    }
}







➔ 추가된 부분

Bounce Bullet Item

  • 플레이어가 Bullet을 발사하면 각도는 무조건 90도가 고정이다. 그래서 첫번째의 바운스는 랜덤으로 105~ 255도의 값중 한 방향으로 바운스하게 설정.
  • 첫번째의 바운스 이후로는 반사각으로 바운스.
  • 5번의 바운스 후 오브젝트 삭제.

public class BounceBulletPrefabLogic : ItemManager
{
    const float _Radian = 180f;
    bool firstWallCheck = true;
    Rigidbody2D _rigid;

    private int[] arrAngles = { 105, 135, 165, 195, 225, 255 }; // 225 : Right Bounce, 135 : Left

    private int bulletLifeCount = 0;

    private void Awake()
    {
        _rigid = GetComponent<Rigidbody2D>();
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.collider.CompareTag("TopWall"))
        {
            if (firstWallCheck == true)
            {
                // Random Angle Setting
                int randomAngle = Random.Range(0, arrAngles.Length);
                Vector3 tmps = transform.eulerAngles;
                tmps.z = arrAngles[randomAngle];
                transform.eulerAngles = tmps;
                _rigid.AddForce(transform.up * Force, ForceMode2D.Impulse);
                firstWallCheck = false;
            }
            else
            {
                // Bounce Angle Setting
                Vector3 tmp = transform.eulerAngles;
                tmp.z = _Radian - tmp.z;
                transform.eulerAngles = tmp;
                // Bounce After AddForce
                _rigid.AddForce(transform.up * Force, ForceMode2D.Impulse);
            }
        }
        else if (collision.collider.CompareTag("Wall"))
        {
            Vector3 tmp = transform.eulerAngles;
            tmp.z = (_Radian * 2) - tmp.z;
            transform.eulerAngles = tmp;
            _rigid.AddForce(transform.up * Force, ForceMode2D.Impulse);
        }
        bulletLifeCount++;

        // bullet Destory Condition ( 오브젝트 풀링 필요 )
        if(bulletLifeCount >= 6)
        {
            Destroy(gameObject);
        }
    }
}



GuidedMissile Bullet Item

  • Ball의 Tag를 가진 gameObject들을 Balls 리스트에 추가
  • 비교값을 위해 Balls[0]과 bullet의 거리를 nearDis에 대입
  • Null값을 대비해 LockOnTarget = Balls[0] 으로 설정.
  • Balls를 foreach에 돌려 bullet과 거리를 비교
  • 가장 가까운 오브젝트를 LockOnTarget에 할당.
  • FixedUpdate()에서 MoveTowards를 사용하여 bullet이 LockOnTarget을 따라간다.
public class GuidedMissileBulletPrefabLogic : ItemManager
{
    List<GameObject> Balls;

    GameObject LockOnTarget;

    float nearDis;

    // LockOn Setting
    private void Awake()
    {
        Balls = new List<GameObject>(GameObject.FindGameObjectsWithTag("Ball"));
        nearDis = Vector3.Distance(gameObject.transform.position, Balls[0].transform.position);
    }

    // LockOn Search
    private void Start()
    {
        // Null Value Ready
        LockOnTarget = Balls[0];

        // Search Near Distance Target
        foreach (GameObject index in Balls)
        {
            float distance = Vector3.Distance(gameObject.transform.position, index.transform.position);
            
            if (distance < nearDis)
            {
                LockOnTarget = index;
            }
        }
        Debug.Log(LockOnTarget);
    }

    // LockOn Target Attack
    private void FixedUpdate()
    {
        transform.position = Vector3.MoveTowards(transform.position, LockOnTarget.transform.position, Force * Time.deltaTime);
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.collider.CompareTag("Ball"))
        {
            Destroy(gameObject);    // 추후 오브젝트 풀링
        }
    }
}
profile
No Easy Day
post-custom-banner

0개의 댓글