기록 보관소

[Unity/유니티] 기초-뱀서라이크: 로직 보완하기[16] 본문

유니티 프로젝트/뱀서라이크

[Unity/유니티] 기초-뱀서라이크: 로직 보완하기[16]

JongHoon 2023. 8. 30. 23:39

개요

유니티 독학을 위해 아래 링크의 골드메탈님의 영상들을 보고 직접 따라 해보면서 진행 상황을 쓰고 배웠던 점을 요약한다.

https://youtube.com/playlist?list=PLO-mt5Iu5TeYI4dbYwWP8JqZMC9iuUIW2 

 

📚유니티 기초 강좌

유니티 게임 개발을 배우고 싶은 분들을 위한 기초 강좌

www.youtube.com


뱀서라이크: 로직 보완하기[16]

1. 무한 맵 재배치 보완

//Reposition Script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Reposition : MonoBehaviour {
    Collider2D coll;    //모든 모양의 Collider를 포함

    void Awake() {
        coll = GetComponent<Collider2D>();
    }

    void OnTriggerExit2D(Collider2D collision) {    //IsTrigger가 체크된 Collider에서 나갔을 때 동작
        if (!collision.CompareTag("Area"))
            return;

		//거리를 구하기 위해 플레이어 위치와 타일맵 위치 미리 저장
        Vector3 playerPos = GameManager.instance.player.transform.position;
        Vector3 myPos = transform.position;

        switch (transform.tag) {
            case "Ground":
                float diffX = playerPos.x - myPos.x;
                float diffY = playerPos.y - myPos.y;
                float dirX = diffX < 0 ? -1 : 1;
                float dirY = diffY < 0 ? -1 : 1;
                diffX = Mathf.Abs(diffX);
                diffY = Mathf.Abs(diffY);

                if (diffX > diffY) {    //X축 이동시
                    transform.Translate(Vector3.right * dirX * 40); //X축으로 2칸 이동
                }
                else if (diffX < diffY) {   //Y축 이동시
                    transform.Translate(Vector3.up * dirY * 40);    //Y축으로 2칸 이동
                }
                break;
            case "Enemy":
                if (coll.enabled) { //몬스터가 죽으면 collider2D 컴포넌트를 disable 시키도록 구현할 예정
                    transform.Translate(playerDir * 20 + new Vector3(Random.Range(-3f, 3f), Random.Range(-3f, 3f), 0f)); //플레이어 이동 방향에 따라 맞은 편에서 등장하도록 이동
                }
                break;

        }
    }
}
  • 플레이어 입력 방향에 따라 맵이 재배치 되는 방식에서 플레이어와 타일맵의 거리 차이를 이용해 재배치하는 방식으로 변경
  • 코드 아랫 부분에 몬스터를 재배치하는 부분에서 이전 방식에 사용하는 playerDir 변수가 아직 있다. 이는 다음 파트에서 고친다.

2. 몬스터 재배치 보완

//Reposition Script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Reposition : MonoBehaviour {
    Collider2D coll;    //모든 모양의 Collider를 포함

    void Awake() {
        coll = GetComponent<Collider2D>();
    }

    void OnTriggerExit2D(Collider2D collision) {    //IsTrigger가 체크된 Collider에서 나갔을 때 동작
        if (!collision.CompareTag("Area"))
            return;

		//거리를 구하기 위해 플레이어 위치와 타일맵 위치 미리 저장
        Vector3 playerPos = GameManager.instance.player.transform.position;
        Vector3 myPos = transform.position;

        switch (transform.tag) {
            case "Ground":
                float diffX = playerPos.x - myPos.x;
                float diffY = playerPos.y - myPos.y;
                float dirX = diffX < 0 ? -1 : 1;
                float dirY = diffY < 0 ? -1 : 1;
                diffX = Mathf.Abs(diffX);
                diffY = Mathf.Abs(diffY);

                if (diffX > diffY) {    //X축 이동시
                    transform.Translate(Vector3.right * dirX * 40); //X축으로 2칸 이동
                }
                else if (diffX < diffY) {   //Y축 이동시
                    transform.Translate(Vector3.up * dirY * 40);    //Y축으로 2칸 이동
                }
                break;
            case "Enemy":
                if (coll.enabled) {
                    Vector3 dist = playerPos - myPos;
                    Vector3 ran = new Vector3(Random.Range(-3, 3), Random.Range(-3, 3));
                    transform.Translate(ran + dist * 2);
                }
                break;

        }
    }
}
  • 앞의 맵 재배치와 같이 거리를 이용하는 방식으로 변경

3. 투사체 멈춤 보완

//Weapon Script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Weapon : MonoBehaviour {
    public int id;
    public int prefabId;
    public float damage;
    public int count;
    public float speed;

    float timer;
    Player player;

    void Awake() {
        player = GameManager.instance.player;
    }

    void Update() {
        if (!GameManager.instance.isLive)
            return;

        switch (id) {
            case 0:
                transform.Rotate(Vector3.back * speed * Time.deltaTime);
                break;
            default:
                timer += Time.deltaTime;

                if (timer > speed) {
                    timer = 0f;
                    Fire();
                }
                break;
        }
    }
    
    public void LevelUp(float damage, int count) {
        this.damage = damage * Character.Damage;
        this.count += count;

        if (id == 0)
            Batch();

        player.BroadcastMessage("ApplyGear", SendMessageOptions.DontRequireReceiver);
    }

    public void Init(ItemData data) {
        // Basic Set
        name = "Weapon " + data.itemId;
        transform.parent = player.transform;
        transform.localPosition = Vector3.zero;

        // Property Set
        id = data.itemId;
        damage = data.baseDamage * Character.Damage;
        count = data.baseCount + Character.Count;

        for (int index = 0; index < GameManager.instance.pool.prefabs.Length; index++) {
            if (data.projectile == GameManager.instance.pool.prefabs[index]) {  //프리펩이 같은건지 확인
                prefabId = index;
                break;
            }
        }

        switch (id) {
            case 0:
                speed = 150 * Character.WeaponSpeed;
                Batch();
                break;
            default:
                speed = 0.5f * Character.WeaponRate;
                break;
        }

        // Hand Set
        Hand hand = player.hands[(int)data.itemType];
        hand.spriter.sprite = data.hand;
        hand.gameObject.SetActive(true);

        //플레이어가 가지고 있는 모든 Gear에 대해 ApplyGear를 실행하게 함. 나중에 추가된 무기에도 영향을 주기 위함.
        player.BroadcastMessage("ApplyGear", SendMessageOptions.DontRequireReceiver);
    }

    void Batch() {  //생성된 무기를 배치하는 함수
        for (int index = 0; index < count; index++) {
            Transform bullet;

            if (index < transform.childCount) {
                bullet = transform.GetChild(index); //기존 오브젝트가 있으면 먼저 활용
            }
            else {
                bullet = GameManager.instance.pool.Get(prefabId).transform; //모자라면 풀링에서 가져옴
                bullet.parent = transform;
            }

            bullet.localPosition = Vector3.zero;        //무기 위치 초기화
            bullet.localRotation = Quaternion.identity; //무기 회전값 초기화

            Vector3 rotVec = Vector3.forward * 360 * index / count; //개수에 따라 360도 나누기
            bullet.Rotate(rotVec);
            bullet.Translate(bullet.up * 1.5f, Space.World); //무기 위쪽으로 이동
            bullet.GetComponent<Bullet>().Init(damage, -100, Vector3.zero); //-100 is Infinity Per. 무한 관통.
        }
    }

    void Fire() {
        if (!player.scanner.nearestTarget)
            return;

        Vector3 targetPos = player.scanner.nearestTarget.position;
        Vector3 dir = targetPos - transform.position;
        dir = dir.normalized;

        Transform bullet = GameManager.instance.pool.Get(prefabId).transform;
        bullet.position = transform.position;
        bullet.rotation = Quaternion.FromToRotation(Vector3.up, dir);   //FromToRotation(지정된 축을 중심으로 목표를 향해 회전하는 함수
        bullet.GetComponent<Bullet>().Init(damage, count, dir);

        AudioManager.instance.PlaySfx(AudioManager.Sfx.Range);
    }
}
//Bullet Script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour {
    public float damage;    //피해량 변수
    public int per;         //관통 변수

    Rigidbody2D rigid;

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

    public void Init(float damage, int per, Vector3 dir) {
        this.damage = damage;
        this.per = per;

        if (per >= 0) {
            rigid.velocity = dir * 15f;
        }
    }

    void OnTriggerEnter2D(Collider2D collision) {
        if (!collision.CompareTag("Enemy") || per == -100)
            return;

        per--;

        if (per < 0) {
            rigid.velocity = Vector2.zero;
            gameObject.SetActive(false);
        }
    }
}
  • Weapon 스크립트에서 근거리 무기 관통력은 -1로 설정했는데, Bullet 스크립트에서 몬스터에게 닿으면 관통력을 1씩 깎아 0으로 만들어 파괴하는 방식으로 구현한 것이 가끔 -1이 되어 계속 남아있는 경우가 있다고 한다.
    • 그래서 근접 무기의 관통력을 -1에서 -100으로 변경하고 관련 스크립트를 수정.

EnemyCleaner의 Per도 -100으로 변경


4. 투사제 삭제 추가

//Bullet Script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour {
    public float damage;    //피해량 변수
    public int per;         //관통 변수

    Rigidbody2D rigid;

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

    public void Init(float damage, int per, Vector3 dir) {
        this.damage = damage;
        this.per = per;

        if (per >= 0) {
            rigid.velocity = dir * 15f;
        }
    }

    void OnTriggerEnter2D(Collider2D collision) {
        if (!collision.CompareTag("Enemy") || per == -100)
            return;

        per--;

        if (per < 0) {
            rigid.velocity = Vector2.zero;
            gameObject.SetActive(false);
        }
    }


    void OnTriggerExit2D(Collider2D collision) {
        if (!collision.CompareTag("Area") || per == -100)
            return;

        gameObject.SetActive(false);
    }
}
  • 범위를 벗어난 투사체를 비활성화하는 함수 추가

5. 레벨 디자인

// Spawner Script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Spawner : MonoBehaviour {
    public Transform[] spawnPoint;
    public SpawnData[] spawnData;
    public float levelTime;

    int level;  //소환 레벨
    float timer;

    void Awake() {
        spawnPoint = GetComponentsInChildren<Transform>();
        levelTime = GameManager.instance.maxGameTime / spawnData.Length;    //최대 시간에 몬스터 데이터 크기로 나눠 자동으로 구간 시간 계산
    }

    void Update() {
        if (!GameManager.instance.isLive)
            return;

        timer += Time.deltaTime;
        //FloorToInt : 소수점 아래는 버리고 Int형으로 바꾸는 함수. CeilToInt : 소수점 아래를 올리고 Int형으로 바꾸는 함수.
        level = Mathf.Min(Mathf.FloorToInt(GameManager.instance.gameTime / levelTime), spawnData.Length - 1);

        if (timer > spawnData[level].spawnTime) {
            timer = 0;
            Spawn();
        }
    }

    void Spawn() {
        GameObject enemy = GameManager.instance.pool.Get(0);    //프리펩이 하나가 되었으므로 0으로 변경
        enemy.transform.position = spawnPoint[Random.Range(1, spawnPoint.Length)].position; // Random Range가 1부터 시작하는 이유는 spawnPoint 초기화 함수 GetComponentsInChildren에 자기 자신(Spawner)도 포함되기 때문에.
        enemy.GetComponent<Enemy>().Init(spawnData[level]);
    }
}

//직렬화(Serialization) : 개체를 저장/전송하기 위해 변환
[System.Serializable]
public class SpawnData {
    public float spawnTime; //몬스터 소환 시간
    public int spriteType;  //몬스터 스프라이트 타입
    public int health;      //몬스터 체력
    public float speed;     //몬스터 스피드
}
  • 단순 10으로 나눴던 레벨을 최대 시간에 몬스터 데이터 크기로 나눠 자동으로 구간 시간을 계산하도록 변경

테스트를 위해 낮췄던 레벨별 요구 경험치량과 게임 시간 수정
테스트 실행. 잘 반영되었다.