[Unity] Project. Dodge

Lingtea_luv·2025년 4월 23일

Project

목록 보기
9/38
post-thumbnail

Dodge


backlog

1. Map

Player와 Tower가 위치하는 게임 플레이 빌드

2. GameManager

게임의 진행과 종료 판정, 처리

3. Tower

게임 시작시 총알 풀 생성, 시계방향 회전
플레이어가 시야 내에 있으면 감지 후 플레이어를 향해 발사
에셋스토어 포탑 모델링 활용

4. Bullet

발사 시 포구의 정면 방향으로 비행
지정 거리를 이동하면 비활성화

5. Player

유저가 조작하는 캐릭터(상하좌우)
총알(bullet)과 충돌 시 비활성화

6. Bullet Pool

오브젝트 풀 패턴 활용
타워에 종속되지 않는 오브젝트

Dodge

Player

public class PlayerController : MonoBehaviour
{
    [SerializeField] private float moveSpeed;
    [SerializeField] private int hp;
    private Vector3 moveDir;

    private void OnEnable()
    {
        Init();
    }

    private void Update()
    {
        Move();
    }

	// AxisRaw를 활용한 기본 이동 로직
    private void Move()
    {
        float h = Input.GetAxisRaw("Horizontal");
        float v = Input.GetAxisRaw("Vertical");

        moveDir = new Vector3(h, 0, v).normalized;

        transform.Translate(moveDir * (moveSpeed * Time.deltaTime));
    }
	
    // 총알에 피격시 트리거 발동
    private void OnTriggerEnter(Collider other)
    {
        if(other.gameObject.layer == LayerMask.NameToLayer("Bullet"))
        {
            Hit();
        }
    }
    
    private void Hit()
    {
        hp--;
        
        if (hp <= 0)
        {
        	// GameManager의 이벤트를 호출하기 위한 proxy 함수 호출
            GameManager.Instance.HandleGameOver();
        }
    }
    
    private void Init()
    {
        hp = 5;
        moveSpeed = 8f;
    }
}

Tower

public class TowerController : MonoBehaviour
{
    [SerializeField] private Transform muzzlePos;
    [SerializeField] private BulletPool bulletPool;
    [SerializeField] private float rotSpeed;
    [SerializeField] private float firePower;
    [SerializeField] private float fireCoolTime;
    [SerializeField] private LayerMask playerLayer;
    [SerializeField] [Range(5f,10f)] private float detectRange;
    private Transform _targetPos;
    private WaitForSeconds _fireCool;
    private Coroutine _fireRoutine;
    private bool _isFiring;
    
    private void Awake()
    {
        Init();
    }
    
    private void Update()
    {
        Rotate();
        Detect();
    }
    
    // GameManager에서 게임 시작시 호출하는 함수
    public void StartFire()
    {
        if (_fireRoutine == null)
        {
            _fireRoutine = StartCoroutine(FireRoutine());
            _isFiring = true;
        }
    }

	// GameManager에서 게임 종료시 호출하는 함수
    public void StopFire()
    {
        if (_fireRoutine != null)
        {
            StopCoroutine(_fireRoutine);
            _fireRoutine = null;
            _isFiring = false;
        }
    }
    
    private void Rotate()
    {
        transform.Rotate(Vector3.up, rotSpeed * Time.deltaTime);
    }
	
    // 일정 주기로 총알을 발사하기 위한 코루틴
    private IEnumerator FireRoutine()
    {
        while (true)
        {
            Fire();
            yield return _fireCool;
        }
    }
    
    // 총알 풀에서 가져오기, 방향, 속도 로직
    private void Fire()
    {
        Bullet instance = bulletPool.GetPool();
        instance.gameObject.SetActive(true);
        instance.transform.position = muzzlePos.position;
        instance.transform.LookAt(transform.forward);
        instance.gameObject.GetComponent<Rigidbody>().velocity = transform.forward * firePower;
    }

	// 일정 영역 내에 플레이어가 있으면 플레이어쪽으로 회전
    private void Detect()
    {
        Collider[] colliders = Physics.OverlapSphere(transform.position, detectRange, playerLayer);
        if (colliders.Length > 0)
        {
            _targetPos = colliders[0].transform;
            transform.LookAt(_targetPos.position);
        }
        else
        {
            _targetPos = null;
        }
    }

    private void Init()
    {
        _fireCool = new WaitForSeconds(fireCoolTime);
    }
}

Bullet

public class Bullet : MonoBehaviour
{
    [SerializeField] private float liveTime;
    public BulletPool _bulletPool;
    private float _timer;
    private bool _isReturned = false;
    
    private void OnEnable()
    {
        _timer = liveTime;
        _isReturned = false;
    }

	// 일정 시간 지나면 자동으로 파괴(회수)되는 로직
    private void Update()
    {
        _timer -= Time.deltaTime;

        if (_timer <= 0)
        {
            Return();
        }
    }
	
    // 플레이어와 충돌 시 파괴(회수)
    private void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.layer == LayerMask.NameToLayer("Player"))
        {
            Return();
        }
    }
	
    // 총알의 파괴(회수) 로직
    private void Return()
    {
    	// 중복 회수를 막기 위한 플래그
        if (_isReturned) return;
        _isReturned = true;
        
        // 회수당할 풀이 있는 경우 회수
        if (_bulletPool != null)
        {
            gameObject.SetActive(false);
            _bulletPool.ReturnPool(this);
        }
        // 없는 경우 파괴
        else
        {
            Destroy(gameObject);
        }
    }
}

BulletPool

public class BulletPool : MonoBehaviour
{
	// Queue로 구현한 오브젝트 풀
    public Queue<Bullet> bullets;
    [SerializeField] private Bullet bulletPrefab;
    [SerializeField] private int size;

    private void Awake()
    {
        Init();
    }
	
    // 오브젝트 풀 기본 로직
    private void Init()
    {
        bullets = new Queue<Bullet>();
        
        for (int i = 0; i < size; i++)
        {
            Bullet instance = Instantiate(bulletPrefab);
            instance.gameObject.SetActive(false);
            bullets.Enqueue(instance);
        }
    }

	// 오브젝트 풀 GetPool 로직
    public Bullet GetPool()
    {
    	// 풀에 총알이 없는 경우 신규 생성
        if (bullets.Count == 0)
        {
            Bullet instance1 = Instantiate(bulletPrefab);
            return instance1;
        }
        // 풀에 총알이 있는 경우 소환
        else
        {
            Bullet instance2 = bullets.Dequeue();
            instance2._bulletPool = this;
            return instance2;
        }
        
    }
    
    // 오브젝트 풀 ReturnPool 로직
    public void ReturnPool(Bullet bullet)
    {
    	// 풀에 총알이 없어서 신규 생성된 총알의 경우 파괴
        if (bullet._bulletPool == null)
        {
            Destroy(bullet.gameObject);
        }
        // 풀에서 소환된 총알의 경우 반환
        else
        {
            bullet.gameObject.SetActive(false);
            bullets.Enqueue(bullet);
        }
    }
}

GameManager

public class GameManager : MonoBehaviour
{
	// 매니저 싱글톤 구현
    public static GameManager Instance { get; private set; }

    [SerializeField] private GameObject canvas;    
    [SerializeField] private GameObject player;
    [SerializeField] private GameObject[] towers;
    [SerializeField] private Transform spawnPos;
    [SerializeField] private Button retryBtn;
        
    // 게임 오버시 실행될 이벤트
    public event Action OnGameOvered;
    
    public bool isgameOver;
    
    // 싱글톤 기본 로직
    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(Instance);
        }
        Instance = this;
        DontDestroyOnLoad(Instance);
        
        Init();
    }

    private void OnEnable()
    {
        OnGameOvered += GameOver;
        retryBtn.onClick.AddListener(GameStart);
    }

    private void OnDisable()
    {
        OnGameOvered -= GameOver;
        retryBtn.onClick.RemoveListener(GameStart);
    }
    
    // OnGameOvered 이벤트 proxy 함수
    public void HandleGameOver()
    {
        OnGameOvered?.Invoke();
    }
    
    // 게임 시작 시 호출되는 함수
    private void GameStart()
    {
        canvas.gameObject.SetActive(false);
        player.gameObject.SetActive(true);
        player.transform.position = spawnPos.position;
        isgameOver = false;
        foreach (var tower in towers)
        {
            tower.gameObject.GetComponent<TowerController>().StartFire();
        }
    }
    
    // 게임 오버 시 호출되는 함수
    private void GameOver()
    {
        player.gameObject.SetActive(false);
        canvas.gameObject.SetActive(true);
        isgameOver = true;
        foreach (var tower in towers)
        {
            tower.gameObject.GetComponent<TowerController>().StopFire();
        }
    }

    private void Init()
    {
        player.gameObject.SetActive(false);
        canvas.gameObject.SetActive(true);
        isgameOver = false;
    }
}
profile
뚠뚠뚠뚠

0개의 댓글