기록 보관소

[Unity/유니티] 기초-뱀서라이크: 능력 업그레이드 구현[11] 본문

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

[Unity/유니티] 기초-뱀서라이크: 능력 업그레이드 구현[11]

JongHoon 2023. 7. 12. 20:57

개요

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

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

 

📚유니티 기초 강좌

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

www.youtube.com


뱀서라이크: 능력 업그레이드 구현[11]

1. 아이템 데이터 만들기

ItemData 스크립트 생성

//ItemData Script

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

[CreateAssetMenu(fileName = "Item", menuName = "Scriptble Object/ItemData")]

public class ItemData : ScriptableObject {
    public enum ItemType { Melee, Range, Glove, Shoe, Heal }

    [Header("# Main Info")]
    public ItemType itemType;
    public int itemId;
    public string itemName;
    public string itemDesc;
    public Sprite itemIcon;

    [Header("# Level Info")]
    public float baseDamage;
    public int baseCount;
    public float[] damages;
    public int[] counts;

    [Header("# Weapon")]
    public GameObject projectile;

}
  • CreateAssetMenu() : 커스텀 메뉴를 생성하는 속성

커스텀 메뉴가 생성됐다
Data 폴더 생성 후 Scriptble Object -> ItemData 생성
Item 0를 설정해준다.
Item 0을 복사하고 변경해준다
Item 2도 마찬가지로 진행
Item 3
마지막 Item 4


2. 레벨업 버튼 제작

Canvas의 자식으로 빈 오브젝트를 만들어서 수정
LevelUp 아래에 Button(Legacy) 추가 및 수정
Text(Legacy)도 수정해준다
Image를 추가해서 변경해준다
업그레이드 버튼 담당 스크립트 Item 생성

//Item Script

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

public class Item : MonoBehaviour {
    public ItemData data;
    public int level;
    public Weapon weapon;

    Image icon;
    Text textLevel;

    void Awake() {
        icon = GetComponentsInChildren<Image>()[1];
        icon.sprite = data.itemIcon;

        Text[] texts = GetComponentsInChildren<Text>();
        textLevel = texts[0];
    }

    void LateUpdate() {
        textLevel.text = "Lv." + (level + 1);
    }
}

Item 0에 Item 스크립트를 넣고, Data를 할당.
LevelUp에 Vertical Layout Group 컴포넌트를 넣고, Item 0을 복사해서 수정한다. 각 Item별 Data도 변경한다.
테스트 실행을 해보면 이렇게 잘 반영되는 것을 확인할 수 있다

  • 버튼 클릭 이벤트와 연결할 함수 추가
//Item Script

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

public class Item : MonoBehaviour {
    public ItemData data;
    public int level;
    public Weapon weapon;

    Image icon;
    Text textLevel;

    void Awake() {
        icon = GetComponentsInChildren<Image>()[1];
        icon.sprite = data.itemIcon;

        Text[] texts = GetComponentsInChildren<Text>();
        textLevel = texts[0];
    }

    void LateUpdate() {
        textLevel.text = "Lv." + (level + 1);
    }

    public void OnClick() {
        switch(data.itemType) {
            case ItemData.ItemType.Melee:
            case ItemData.ItemType.Range:

                break;
            case ItemData.ItemType.Glove:
                break;
            case ItemData.ItemType.Shoe:
                break;
            case ItemData.ItemType.Heal:
                break;
        }

        level++;

        if (level == data.damages.Length) {
            //작성한 레벨 데이터 개수를 넘지 않도록 만들기
            GetComponent<Button>().interactable = false;
        }
    }
}

Button 컴포넌트에 Item 스크립트(바로 아래 컴포넌트)를 넣고 OnClick() 연결. 나머지도 다 한다.
오작동 방지를 위해 Navigation을 None으로 변경
테스트 실행. 처음에는 다 레벨 1이다.
클릭해보니 문제 없이 아이템 레벨이 오르고, 최대 레벨을 찍으면 반투명화되어 클릭이 막힌다.


3. 무기 업그레이드

Player의 자식 오브젝트 Weapon0와 Weapon 1 삭제

//Item Script

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

public class Item : MonoBehaviour {
    public ItemData data;
    public int level;
    public Weapon weapon;

    Image icon;
    Text textLevel;

    void Awake() {
        icon = GetComponentsInChildren<Image>()[1];
        icon.sprite = data.itemIcon;

        Text[] texts = GetComponentsInChildren<Text>();
        textLevel = texts[0];
    }

    void LateUpdate() {
        textLevel.text = "Lv." + (level + 1);
    }

    public void OnClick() {
        switch(data.itemType) {
            case ItemData.ItemType.Melee:
            case ItemData.ItemType.Range:
                if (level == 0) {
                    GameObject newWeapon = new GameObject();
                    weapon = newWeapon.AddComponent<Weapon>();
                    weapon.Init(data);
                }
                else {
                    float nextDamage = data.baseDamage;
                    int nextCount = 0;

                    nextDamage += data.baseDamage * data.damages[level];
                    nextCount += data.counts[level];

                    weapon.LevelUp(nextDamage, nextCount);
                }
                break;
            case ItemData.ItemType.Glove:
                break;
            case ItemData.ItemType.Shoe:
                break;
            case ItemData.ItemType.Heal:
                break;
        }

        level++;

        if (level == data.damages.Length) {
            //작성한 레벨 데이터 개수를 넘지 않도록 만들기
            GetComponent<Button>().interactable = false;
        }
    }
}
//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() {
        switch (id) {
            case 0:
                transform.Rotate(Vector3.back * speed * Time.deltaTime);
                break;
            default:
                timer += Time.deltaTime;

                if (timer > speed) {
                    timer = 0f;
                    Fire();
                }
                break;
        }

        // .. Test Code ..
        if (Input.GetButtonDown("Jump")) {
            LevelUp(10, 1);
        }
    }
    
    public void LevelUp(float damage, int count) {
        this.damage = damage;
        this.count += count;

        if (id == 0)
            Batch();
    }

    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;
        count = data.baseCount;

        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;
                Batch();
                break;
            default:
                speed = 0.4f;
                break;
        }
    }

    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, -1, Vector3.zero); //-1 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);
    }
}

테스트 실행. 무기를 제거했어서 시작 직후에는 무기가 없다.
무기를 클릭하니 무기가 생겼다.
더 클릭해보니 강화도 된다. 또한 Player 자식 오브젝트로 Weapon 0(삽)이 생성된 것을 확인할 수 있다.


4. 장비 업그레이드

장비를 담당할 Gear 스크립트 파일 생성

//Gear Script

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

public class Gear : MonoBehaviour {
    public ItemData.ItemType type;
    public float rate;

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

        // Property Set
        type = data.itemType;
        rate = data.damages[0];
        ApplyGear();
    }

    public void LevelUp(float rate) {
        this.rate = rate;
        ApplyGear();
    }

    void ApplyGear () { //장비가 새롭게 추가되거나 레벨업 할때 호출
        switch (type) {
            case ItemData.ItemType.Glove:
                RateUp();
                break;
            case ItemData.ItemType.Shoe:
                SpeedUp();
                break;
        }
    }

    void RateUp() {
        Weapon[] weapons = transform.parent.GetComponentsInChildren<Weapon>();

        foreach (Weapon weapon in weapons) {
            switch (weapon.id) {
                case 0:
                    weapon.speed = 150 + (150 * rate);
                    break;
                default:
                    weapon.speed = 0.5f * (1f - rate);
                    break;
            }
        }
    }

    void SpeedUp() {
        float speed = 3;
        GameManager.instance.player.speed = speed + speed * rate;
    }
}
//Item Script

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

public class Item : MonoBehaviour {
    public ItemData data;
    public int level;
    public Weapon weapon;
    public Gear gear;

    Image icon;
    Text textLevel;

    void Awake() {
        icon = GetComponentsInChildren<Image>()[1];
        icon.sprite = data.itemIcon;

        Text[] texts = GetComponentsInChildren<Text>();
        textLevel = texts[0];
    }

    void LateUpdate() {
        textLevel.text = "Lv." + (level + 1);
    }

    public void OnClick() {
        switch(data.itemType) {
            case ItemData.ItemType.Melee:
            case ItemData.ItemType.Range:
                if (level == 0) {
                    GameObject newWeapon = new GameObject();
                    weapon = newWeapon.AddComponent<Weapon>();
                    weapon.Init(data);
                }
                else {
                    float nextDamage = data.baseDamage;
                    int nextCount = 0;

                    nextDamage += data.baseDamage * data.damages[level];
                    nextCount += data.counts[level];

                    weapon.LevelUp(nextDamage, nextCount);
                }

                level++;
                break;
            case ItemData.ItemType.Glove:
            case ItemData.ItemType.Shoe:
                if (level == 0) {
                    GameObject newGear = new GameObject();
                    gear = newGear.AddComponent<Gear>();
                    gear.Init(data);
                }
                else {
                    float nextRate = data.damages[level];
                    gear.LevelUp(nextRate);
                }

                level++;
                break;
            case ItemData.ItemType.Heal:
                GameManager.instance.health = GameManager.instance.maxHealth;
                break;
        }

        if (level == data.damages.Length) {
            //작성한 레벨 데이터 개수를 넘지 않도록 만들기
            GetComponent<Button>().interactable = false;
        }
    }
}
//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() {
        switch (id) {
            case 0:
                transform.Rotate(Vector3.back * speed * Time.deltaTime);
                break;
            default:
                timer += Time.deltaTime;

                if (timer > speed) {
                    timer = 0f;
                    Fire();
                }
                break;
        }

        // .. Test Code ..
        if (Input.GetButtonDown("Jump")) {
            LevelUp(10, 1);
        }
    }
    
    public void LevelUp(float damage, int count) {
        this.damage = 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;
        count = data.baseCount;

        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;
                Batch();
                break;
            default:
                speed = 0.4f;
                break;
        }

        //플레이어가 가지고 있는 모든 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, -1, Vector3.zero); //-1 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);
    }
}
  • BroadcastMessage : 특정 함수 호출을 모든 자식에게 방송하는 함수

테스트 실행. 장비를 먼저 업그레이드한 뒤 무기를 얻어도 그 영향을 받아서 빠르게 공격한다.