기존에는 무기별로 Weapon0, Weapon1처럼 무기별로 오브젝트를 만들어줬지만 좀 더 정리된 형태로 개선하기 위해 아이템 데이터를 생성을 담당할 스크립트를 따로 만들어 관리해줄 수 있다.
MonoBehaviour 대신 SciptableObject를 상속받아서 사용한다.
[CreateAssetMenu(fileName = "Item",menuName = "Scriptable 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 intemDesc;
public Sprite itemIcon;
[Header("# Level Data")]
public float baseDamage;//초기 데미지
public int baseCount; //초기 카운트
public float[] damages; //레벨 업 데미지
public int[] counts; //레벨 업 카운트
[Header("# Weapon")]
public GameObject projectile;
}
CreateAssetMenu : 커스텀 메뉴를 생성하는 속성
Undead Survivor 폴더에 Data 폴더를 만든 후 Create > Scriptble Object에서 ItemData를 눌러 스크립트블 오브젝트를 생성했다.
스크립트블 오브젝트 : 다양한 데이터를 저장하는 에셋
만든 에셋에 하나의 아이템이 가진 속성들을 설정해주었다.
총 5개의 아이템을 제작해주었다.
캔버스에 레벨업 버튼을 자식 오브젝트로 넣어줄 빈 오브젝트를 LevelUp이라는 이름으로 하나 생성해주었다.
Width = 22, Height = 150으로 설정
오른쪽 아래로 앵커 (위치 + 기준점)
자식 오브젝트로 Button을 만들어준다.
- Width = 22, Height = 30으로 설정
- Source Image를 Panel로 설정
- Text이름을 Text Level로 변경 후 정가운데로 앵커 설정
- Pos Y = -9로 설정
- Horizontal, Vertical Overflow를 Overflow로 설정
- 글꼴 neodgm으로 글자 사이즈 5로 설정
아이템 아이콘을 보여줄 이미지 오브젝트를 자식 오브젝트로 추가
- 이름은 icon으로 설정
- Pos Y = 3으로 설정
아이템을 관리해줄 item 스크립트를 다음과 같이 작성해주었다.
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
icon = GetComponentsInChildren<Image>()[1]; //첫번째[0]는 자기자신
icon.sprite = data.itemIcon; //itemData의 아이콘으로 초기화
Text[] texts = GetComponentsInChildren<Text>(); //자식의 Text 컴포넌트 가져오기
textLevel = texts[0]; //item 오브젝트에는 Text가 없어서 자식에 있는 Text만 오기 때문에 첫번째[0]로 초기화
}
void LateUpdate()
{
textLevel.text = "Lv." + (level + 1);
}
}
item 0에 item 컴포넌트를 추가해준 후 LevelUp 오브젝트에 Vertical Layout Group을 넣어준 후 item 0을 Ctrl+D로 5개 만들어주었다.
추가적으로 버튼 클릭 이벤트를 작성해 준 후 item의 button 컴포넌트의 On click에 바로 밑에 있는 Item(Scipt)를 드래그드랍해서 등록해준 뒤 Item.OnClick 이벤트에 연결시켜주었다.
오작동을 방지하기 위해 모든 버튼의 Navigation을 None으로 변경
기존의 플레이어가 가진 Weapon 오브젝트 삭제한 후 스크립트를 수정했다.
case ItemData.ItemType.Melee: // 근접, 원거리 무기는 같은 로직을 사용
case ItemData.ItemType.Range: // case를 붙여준다.
if (level == 0) //레벨이 0일 때 버튼을 누르면 웨폰 오브젝트를 생성
{
GameObject newWeapon = new GameObject();
//새로운 오브젝트에 Weapon 컴포넌트 추가
//AddComponent 함수 반환 값을 미리 선언한 변수에 저장.
weapon = newWeapon.AddComponent<Weapon>();
weapon.Init(data);
}
break;
AddComponent : 오브젝트에 컴포넌트를 추가
- 해당 컴포넌트를 함수 반환 값으로 같이 반환
Weapon을 생성한 후 Init을 이용해 초기화를 해주는데 이 때 ItemData의 값으로 초기화하기 위해 data를 매개변수로 넘겨받도록 수정했다.
public void Init(ItemData data)
{
//Basic Set
name = "Weapon " + data.itemId; //이름 설정
transform.parent = player.transform; //부모 오브젝트 설정
//플레이어 안에서 위치를 0, 0, 0으로 맞추기 때문에 LocalPostion 사용
transform.localPosition = Vector3.zero;
//Property Set
id = data.itemId;
damage = data.baseDamage;
count = data.baseCount;
//~~(생략)~~
}
Q. 왜 프리팹 아이디를 그냥 ItemData에 넣으면 편한데 프리팹을 넣고 init에서 프리팹 id를 따로 찾나요?
A. 스크립트블 오브젝트의 독립성을 위해 인덱스가 아닌 프리팹으로 설정
아이템 생성까지 됐으므로 레벨 업 코드를 추가 작성해주었다.
case ItemData.ItemType.Melee: // 근접, 원거리 무기는 같은 로직을 사용
case ItemData.ItemType.Range: // case를 붙여준다.
if (level == 0) //레벨이 0일 때 버튼을 누르면 웨폰 오브젝트를 생성
{
GameObject newWeapon = new GameObject();
//새로운 오브젝트에 Weapon 컴포넌트 추가
//AddComponent 함수 반환 값을 미리 선언한 변수에 저장.
weapon = newWeapon.AddComponent<Weapon>();
weapon.Init(data);
}
else
{
float nextDamage = data.baseDamage;
int nextCount = 0;
nextDamage += data.baseDamage * data.damages[level]; //damages를 백분율이기 때문에 곱해서 더해줌
nextCount += data.counts[level]; //count는 단순히 counts의 레벨을 인덱스로해서 가져온 값을 더해줌
weapon.LevelUp(nextDamage, nextCount); //Weapon의 LevelUp 함수를 이용해 레벨업
}
break;
무기가 Weapon 스크립트가 있듯이 장비도 Gear 스크립트를 추가로 작성해주었다.
case ItemData.ItemType.Glove: // 무기가 아닌 장비들은 같은 로직을 사용
case ItemData.ItemType.Shoe:
if (level == 0) //레벨이 0일 때 버튼을 누르면 웨폰 오브젝트를 생성
{
GameObject newGear = new GameObject();
//새로운 오브젝트에 Weapon 컴포넌트 추가
//AddComponent 함수 반환 값을 미리 선언한 변수에 저장.
gear = newGear.AddComponent<Gear>();
gear.Init(data);
}
else
{
float nextRate = data.damages[level];
gear.LevelUp(nextRate);
}
break;
나중에 추가된 무기는 ApplyGear을 받지 못해 공격속도와 Speed가 높은 Level에 따른 값으로 변하지 않는다.
나중에 추가된 무기도 레벨의 영향을 받아야 하므로 Weapon에서도 ApplyGear 함수를 실행시킬 수 있도록 init과 levelup에 다음과 같은 코드를 추가해주었다.
//Weapon이 레벨업하면 ApplyGear로 레벨업한 무기에 Gear 레벨을 적용
player.BroadcastMessage("ApplyGear"); //플레이어에게 broadcast해주도록 부탁
//플레이어가 가지고 있는 모든 Gear에 한해서 ApplyGear가 실행
BroadCastMessage(함수명, 인자값) : 특정 함수 호출을 모든 자식에게 방송하는 함수
- 두 번째 인자값으로 SendMessageOptions.DontRequireReceiver를 추가하면 Reveiver가 없어도 오류가 생하지 앟는다.
마지막으로 체력을 회복시켜주는 아이템 음료수는 다음과 같이 구현했다.
case ItemData.ItemType.Heal: // 일회성 아이템의 안에서는 LevelUp 함수 실행을 X
GameManager.Instance.health = GameManager.Instance.maxHealth;
break;
레벨 값을 올리는 로직을 무기, 장비 case에서만 실행되도록 수정해야 한다.
레벨업이 damages의 수에 도달하면 더 이상 실행하지 않기 때문에 일회성 아이템에서는 레벨업을 진행하면 안 된다.
실제 레벨업 시스템은 추후에 경험치와 레벨에 연동해서 수정할 예정이다.