ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 게임개발 8일차 (간단한 슈팅게임 6)
    게임 개발 2025. 6. 2. 21:26

     

     

     

     


    오늘은 적 HP바 / 캐릭터-플레이어,적 상속 / 자동쏘기+속도제한을 구현했다.

     


    public class Character : MonoBehaviour {
        public virtual void Move() {
            // 기본 이동 구현
        }
    }

    public abstract class Character : MonoBehaviour {
        public abstract void Attack();
    }

     

    위처럼 기본 클래스에 virtual - override로 추가구현을 할 수도 있고

    abstract - override로 자식에서 완전구현을 하도록 만들 수도 있다.



    Bullet.cs의 일부이다.
    private void OnTriggerEnter2D(Collider2D collision) // 충돌 시작할 때
    {
        if (collision.tag == "Wall") // 벽에 닿으면
        {
            gameObject.SetActive(false); // 총알 꺼짐
        }
        else if (collision.tag == "Enemy") // 적에 닿으면
        {
            Enemy enemy = collision.GetComponent<Enemy>(); // 적 오브젝트 정보를 가져오고
            if (enemy != null) // 혹시나 GetComp가 enemy를 못가져올 수 있으니 안전장치
            {
                if (enemy.IsDead()) // 적이 죽어있다면
                {
                    return; // 종료시켜서 총알 통과시킴
                }
            }
            gameObject.SetActive(false); // 적이 살아있으면 총알 꺼짐
        }
    }

     

    저런식으로 GetComp를 실패했을 때 무시하고 아래 코드가 실행되면 런타임오류가 발생될 수 있으니 안전장치를 걸자.


    public class KillManager : MonoBehaviour
    {
        public static KillManager Instance;

        void Awake() // 활성화될 때
        {
            if (Instance == null) // 인스턴스가 없었으면
            {
                Instance = this; // 이 오브젝트가 이 클래스의 유일한 인스턴스가 됨
            }
            else
            { 
                Destroy(gameObject); // 중복을 막기위해 이 오브젝트 파괴
            }
        }

     

    이 방식을 싱글톤 방식이라고 한다. 게임 전체에서 단 하나만 존재해야 하는 오브젝트 (KillManager, BGMManager) 등에 넣어서 사용한다고 한다. 게임전체에서 참조할 수 있는 public static으로 KillManager.Instance를 지정하고 단 하나만 존재하게 한 후 부르고 싶을 때 KillManager.Instance.Addkill() 이런식으로 이용한다고 한다.


    유니티에서 시간을 지연 시킨 후 동작 시키기 위해서(죽는애니메이션을 보여주는 시간등등) 사용하는 함수에는

    Invoke와 Coroutine이 있다.

    Invoke(<어떤함수>, 1.5f)으로 아주 간단하게 구현가능하지만 정교한 제어가 어려워서 결국 Coroutine을 사용해야 한다고 한다.

     

    private Coroutine shootCoroutine;

    void Start()
        {
            StartCoroutine(SpawnBombs());  // 코루틴 시작
        }

     

    IEnumerator SpawnBombs() //  IEnumerator은 중간에 상태를 저장하며 순서대로 실행할 수 있게 하는 특별한 자료형
        {
            for (int i = 0; i < 5; i++)
            {
                Debug.Log($"💣 폭발 {i + 1}번!");

                yield return new WaitForSeconds(3f);  // 3초 기다림 (참고로 null은 다음 프레임까지만 기다림)
            }
            Debug.Log("💥 폭발 종료!");
        }


    총발사 최대속도를 0.3초로 제한하고 누르고 있으면 최대속도로 쏘도록 하는 기능을 구현했다.

     

    private Coroutine shootCoroutine;
    private float lastShootTime = 0f;
    private float shootCooldown = 0.3f;

    void Update(){    

        if (Input.GetMouseButtonDown(0)) // 좌클릭 누르기 시작함
        {
            shootCoroutine = StartCoroutine(AutoShoot());  // 자동발사 기능 시작
        }
        if (Input.GetMouseButtonUp(0))  // 좌클릭 뗌
        {
            StopCoroutine(shootCoroutine);  // 자동발사 기능 해제
        }


        if (Input.GetMouseButtonDown(0)) // 좌클릭 누르기 시작함
        {
            TryManualShoot();  // 자동발사와 별개로 동작하는 발사 함수 가동
        }
    }

     

    void TryManualShoot()  // 수동발사함수
    {
        if (Time.time - lastShootTime >= shootCooldown)  // 쿨타임이 돌았다면
        {
            Shoot();  // 발사
            lastShootTime = Time.time;  // 현재시각으로 last슛타임을 설정
        }
    }


    IEnumerator AutoShoot()
    {
        while (true)
        {
            if (Time.time - lastShootTime >= shootCooldown)
            {
                Shoot();
                lastShootTime = Time.time;
            }
            yield return new WaitForSeconds(0.05f); // 0.05초마다 반복하면서 0.5초 지났는지 체크
        }
    }

     

    Time.time 게임 시작 후 흐른 시간 (초) 타이머, 쿨다운, 주기적 실행 등에 사용
    Time.deltaTime 한 프레임이 걸린 시간 (애니메이션 등에서 자주 사용) 움직임·속도 계산에 사용
    Time.fixedTime FixedUpdate 기준의 시간 (물리엔진용)

     


    적의 HPBar를 구현했다. 선생님과 만든 방식은 단순하게 캐릭터 스크립트를 플레이어 오브젝트에 삽입해서 하는 방식이었지만 플레이어와 적 스크립트 자체를 캐릭터 스크립트를 상속하게 만든 후 hp 바를 구현했다.

    적 프리팹 -> HPBar라는 빈 오브젝트(위치용) -> 캔버스 -> 빨간바 이미지로 계층을 만든 후

    (플레이어 HP바는 화면이라는 오버레이 밑에 고정되어 있기 때문에 플레이어 오브젝트 밑이 아닌 개별의 캔버스 아래에 있었다)

    public class Enemy : Character
    {
        public RectTransform enemyHPBarRect;
        private float maxHPBarWidth;

        void Awake()
        {
            if (enemyHPBarRect != null)
                maxHPBarWidth = enemyHPBarRect.sizeDelta.x;
        }
        public override void Initialize()
        {
            base.Initialize();
            UpdateHPBar();
        }
        public override bool Hit(float damage)
        {
            base.Hit(damage);
            if (HP < 0) HP = 0;
            UpdateHPBar();
            return HP > 0;
        }
        void UpdateHPBar()
        {
            if (enemyHPBarRect != null)
            {
                float ratio = HP / maxHP; // HP 비율 0~1
                enemyHPBarRect.sizeDelta = new Vector2(maxHPBarWidth * ratio, enemyHPBarRect.sizeDelta.y);
            }
        }

     

    부모에서

    public virtual bool Hit(float damage)
    {
        HP -= damage;
        return HP > 0;
    }

    로 구현하고

    public override bool Hit(float damage)
    {
        base.Hit(damage);
        if (HP < 0) HP = 0;
        UpdateHPBar();
        return HP > 0;
    }

    자식에서 이렇게 이어 붙이는 방식이 아주 흥미로웠다.

    이 게임은 여기까지 만들고 3D로 넘어가보려고 한다!!! 재밌었다!!!!!

Designed by Tistory.