공부/Unity

[Unity] 갤러그 만들기 - 2 (코루틴, 오브젝트 풀링, 코루틴 캐싱, 슬라이더)

굴러다니다니 2023. 4. 6. 16:13
728x90

적을 등장하게 만들어봅시다

Create Empty로 EnemySpawner를 만들어 준 뒤, component추가로 enemySpanwer 스크립트를 만들어줍니다.

코루틴을 이용해 적을 일정 시간마다 만드려합니다.

private IEnumerator SpawnEnemy_co()
    {
        WaitForSeconds wfs = new WaitForSeconds(spawnTime);
        while (true)
        {
            float positionX = Random.Range(stageData.LimitMin.x, stageData.LimitMax.x);
            Vector2 position = new Vector2(positionX, stageData.LimitMax.y + 1f);
            Instantiate(enemyPrefabs, position, Quaternion.identity);
            yield return wfs;
        }
    }

Coroutine을 실행하는 동안 stage의 최대 x값, 최소 x값 사이에서 랜덤으로 X값인 positonX를 정하고

positionX를 x좌표로, stage의 최대 y값에 1을 더해 화면 밖에서 적을 생성하게 new Vector position을 정해줍니다.

이 위치에서 prefab해둔 enemy를 생성하면 완료입니다.

하지만 코루틴에서 waitforseconds를 만들어서 돌려주는 작업을 yield return WaitForSeconds(spawnTime)으로 한다면 while문을 돌면서 계속 메모리를 잡아먹기 때문에

코루틴 캐싱을 통해 이를 잡아줍시다.

while문 이전에 이를 new로 선언해 컴퓨팅에서 오랜 시간 걸리는 작업의 결과를 미리 저장해둬 시간과 비용을 절감했습니다 우하하.

(spawnTime을 맨 위에 float로 선언해주고, stageData를 가져와줍니다. -> script 전문 참고)

 

이렇게 하면 무한생성은 되지만, 총알로 맞추지 못하면 destroy되는 식을 안써서 안사라지는데 위의 식을 오브젝트 풀링 방식으로 바꿔봅시다. -지난 시간 Unirun 스크립트 참고

 

gameobject의 배열을 enemies로 선언해주고, 미리 만들어지는 위치를 poolposition으로 선언, 최대 개수를 count로 선언한 뒤 Awake에서 코루틴을 불러오기 전에 초기화를 실행하고, 코루틴 식에서 instantiate를 제거하고 해당 인덱스에 해당되는 배열의 enemy를 활성화 / 비활성화 해줍니다.

 

EnemySpawner.cs 전문

    [SerializeField] private Stage_Data stageData;

    [SerializeField] private GameObject enemyPrefabs;

    private GameObject[] enemies;
    private int current_index = 0;
    private Vector2 Poolposition = new Vector2(-50f, -50f);
    public int count = 10;

    [SerializeField] private float spawnTime; 

    private void Awake()
    {
        enemies = new GameObject[count];
        for (int i = 0; i < count; i++)
        {
            enemies[i] = Instantiate(enemyPrefabs, Poolposition, Quaternion.identity);
        }
        StartCoroutine(SpawnEnemy_co());
    }

    private IEnumerator SpawnEnemy_co()
    {
        WaitForSeconds wfs = new WaitForSeconds(spawnTime);
        while (true)
        {
            float positionX = Random.Range(stageData.LimitMin.x, stageData.LimitMax.x);

            Vector2 position = new Vector2(positionX, stageData.LimitMax.y + 1f);
            enemies[current_index].SetActive(false);
            enemies[current_index].SetActive(true);
            enemies[current_index].transform.position = position;
            current_index++;
            if (current_index >= count)
            {
                current_index = 0;
            }
            yield return wfs;
        }
    }

 

이렇게 진행한다면 적이 성공적으로 잘 만들어집니다. (component 창에서 spawnTime 따로 설정해놔야됨)

하지만, 총알로 맞춘다면 destroy로 배열의 gameobject가 사라진 상태라 활성화를 시킬 수 없어, 충돌시 destroy(gameobject)가 아닌 gameobject.setactive(false)로 코드를 바꿔줍시다 우하하

 

 

적에게 부딪혔을 시 플레이어의 체력이 깎이게 만들어봅시다.

Playercontrol.cs

    private float MaxHp = 3f;
    private float currentHp;

    public float MAXHP => MaxHp; //다른 스크립트에서 접근 가능하게
    public float CurrentHP => currentHp;

    private SpriteRenderer spriteRenderer;
    
    ,
    ,
    ,
    
    public void TakeDamage(float damage)
    {
        currentHp -= damage;

        StopCoroutine(hitColorAnimation());
        StartCoroutine(hitColorAnimation());

        if (currentHp <= 0)
        {
            onDie();
        }
    }

스크립트의 위에 최대 체력과 현재 체력 변수를 선언해 접근이 가능하게 만들어줍니다.

또한 아래에서 SpriteRenderer에 접근할 코드를 쓸 것이기에 SpriteRenderer도 같이 선언해줍시다.

 

TakeDamage 함수를 만들어 damage만큼 플레이어의 체력을 감소시키고, 플레이어가 맞았을 때 플레이어의 색을 잠시 빨강으로 바꿔줄 hitColorAnimation 코루틴을 만들어 이를 정지 (이미 실행중이다면 정지, 없으면 지나감) / 실행해주며, 체력이 0 이하가 될 시 죽습니다.

 

위의 식에 이어 코드를 써줍시다.

    private void onDie()
    {
        Destroy(gameObject);
    }

    private IEnumerator hitColorAnimation()
    {
        spriteRenderer.color = Color.red;
        yield return new WaitForSeconds(0.1f);
        spriteRenderer.color = Color.white;
    }

onDie함수에서는 플레이어의 오브젝트를 파괴합니다.

코루틴 hitColorAnimation 함수는 플레이어의 색을 바다오아 빨강으로 바꾸고 0.1초뒤에 원래 색으로 돌아가는 식입니다.

 

플레이어의 현재 체력을 확인하기 위해 UI -> Slider를 선택해 적당히 추가해주고 스크립트를 작성해줍시다.

public class PlayerHPViewer : MonoBehaviour
{
    [SerializeField] private Slider sliderHP;
    [SerializeField] private PlayerControl player;

    void Start()
    {
        TryGetComponent(out sliderHP);
    }

    void Update()
    {
        sliderHP.value = player.CurrentHP / player.MAXHP;
    }
}

저희는 GetComponent보다 속도가 20배 가량 빠른 TryGetComponent를 이제 사용할 것입니다.

슬라이더의 value는 체력의 비율을 나타내기 때문에 저렇게 나눠서 출력해줍니다.

연결시켜본다면

적이 나오고 이에 부딪힌다면, 적과 플레이어가 모두 사라지는 모습을 볼 수 있습니다.

추가로 씬을 만들어 버튼을 누르면 씬이 로드되는 코드도 진행해 Intro, GameOver로 연결해주었습니다.

(Using ,,, .SceneManagement 필수!)

728x90