1. 개발 현황
1. 추상화
- Weapon 종류(Melee, Range 등)에 무관하게 Weapon를 부모로 변경
- Tag 통합
2. 전역 함수 구현
- 프리팹 생성 시 동일한 함수를 활용하도록 변경
(e.g. 몬스터, 필드 오브젝트는 전역 함수 호출 활용, 아이템 생성)
- static 선언 활용 메모리 낭비 방지
3. 디버프 로직 구현
- 몬스터 내부에 디버프 관련 변수 및 로직 추가
4. 강화 시스템 구현 중
- HUD 구현 중 (Scroll View 구현 완료)
2. 상세 개발 내용
1. 추상화
- Main 변수 및 동작을 부모 위치로 옮김
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Weapon : MonoBehaviour
{
public Enum.DebuffType mDebuff;
public Enum.EffectType mEffect;
public float mDamage;
public float mDebuffPower;
public void Init(float damage, float debuffPower = 0, Enum.DebuffType debuff = Enum.DebuffType.None, Enum.EffectType effect = Enum.EffectType.None)
{
mDebuff = debuff;
mEffect = effect;
mDamage = damage;
mDebuffPower = debuffPower;
}
}
- 추가적인 동작이 필요한 경우 Weapon을 상속받아 변경
public class Range : Weapon
{
bool mIsLive;
int mPer;
float mSpeed;
Vector3 mDir;
private void FixedUpdate()
{
if (!mIsLive)
return;
transform.position += mDir * mSpeed * Time.deltaTime;
if (Vector3.Distance(transform.position, GameManager.instance.mPlayer.transform.position) > 20)
{
mIsLive = false;
gameObject.SetActive(false);
}
}
public void Init(float damage, int per, Vector3 dir, float speed, float debuffPower = 0, Enum.DebuffType debuff = Enum.DebuffType.None, Enum.EffectType effect = Enum.EffectType.None)
{
mIsLive = true;
mDebuff = debuff;
mEffect = effect;
mDamage = damage;
mPer = per;
mDir = dir;
mSpeed = speed;
mDebuffPower = debuffPower;
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (!collision.CompareTag("Enemy"))
return;
mPer--;
if (mEffect == Enum.EffectType.Stop)
{
transform.localScale = new Vector3(1.0f, 1.0f);
mSpeed = 0;
}
if (mPer == -1)
{
mIsLive = false;
gameObject.SetActive(false);
}
}
}
2. 전역 함수 구현
- 프리팹을 활용하여 객체를 생성하는 함수 관리를 하나의 클래스로 변경
- 이때, static을 적용 이유는, 메모리를 효율적으로 활용하기 위해 하나의 클래스 객체만 생성하여 함수를 관리함.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
static public class FuncPool
{
public static void SpawnEnemy(int level)
{
GameObject enemy = GameManager.instance.mEnemyPool.Get();
enemy.transform.position = GameManager.instance.mPlayer.transform.position;
Vector3 pos = Random.insideUnitCircle;
if (pos.magnitude == 0)
pos.x = 1;
pos = pos.normalized;
enemy.transform.position += pos * 20;
enemy.GetComponent<Enemy>().Init(level);
}
public static void SpawnFieldObject()
{
GameObject item = GameManager.instance.mFieldObjectPool.Get();
item.transform.position = GameManager.instance.mPlayer.transform.position;
Vector3 pos = Random.insideUnitCircle;
if (pos.magnitude == 0)
pos.x = 1;
pos = pos.normalized;
item.transform.position += pos * 20;
int value = Random.Range(0, GameManager.instance.mFieldObjectSprite.Length);
item.GetComponent<FieldObject>().Init(value);
}
public static void DropGold(int value, Vector3 pos)
{
GameObject item = GameManager.instance.mDropPool.Get();
item.transform.position = pos;
item.transform.position += new Vector3(Random.value * 2 - 1, Random.value * 2 - 1);
item.transform.rotation = Quaternion.identity;
item.GetComponent<DropItem>().mData = value;
switch (value)
{
case > 50:
item.GetComponent<DropItem>().mType = Enum.DropItemSprite.Gold2;
item.GetComponent<SpriteRenderer>().sprite = GameManager.instance.mDropItemSprite[(int)Enum.DropItemSprite.Gold2];
break;
case > 10:
item.GetComponent<DropItem>().mType = Enum.DropItemSprite.Gold1;
item.GetComponent<SpriteRenderer>().sprite = GameManager.instance.mDropItemSprite[(int)Enum.DropItemSprite.Gold1];
break;
default:
item.GetComponent<DropItem>().mType = Enum.DropItemSprite.Gold0;
item.GetComponent<SpriteRenderer>().sprite = GameManager.instance.mDropItemSprite[(int)Enum.DropItemSprite.Gold0];
break;
}
item.GetComponent<SpriteRenderer>().color = new Color(1, 1, 1, 0.5f);
}
public static void DropExp(int value, Vector3 pos)
{
GameObject item = GameManager.instance.mDropPool.Get();
item.transform.position = pos;
item.transform.rotation = Quaternion.identity;
item.GetComponent<DropItem>().mData = value;
switch (value)
{
case > 50:
item.GetComponent<DropItem>().mType = Enum.DropItemSprite.Exp2;
item.GetComponent<SpriteRenderer>().sprite = GameManager.instance.mDropItemSprite[(int)Enum.DropItemSprite.Exp2];
break;
case > 10:
item.GetComponent<DropItem>().mType = Enum.DropItemSprite.Exp1;
item.GetComponent<SpriteRenderer>().sprite = GameManager.instance.mDropItemSprite[(int)Enum.DropItemSprite.Exp1];
break;
default:
item.GetComponent<DropItem>().mType = Enum.DropItemSprite.Exp0;
item.GetComponent<SpriteRenderer>().sprite = GameManager.instance.mDropItemSprite[(int)Enum.DropItemSprite.Exp0];
break;
}
item.GetComponent<SpriteRenderer>().color = new Color(1, 1, 1, 0.5f);
}
public static void DropItem(Vector3 pos)
{
GameObject item = GameManager.instance.mDropPool.Get();
item.transform.position = pos;
item.transform.rotation = Quaternion.identity;
float val = Random.value;
switch (val)
{
case < 0.1f:
item.GetComponent<DropItem>().mType = Enum.DropItemSprite.Mag;
item.GetComponent<SpriteRenderer>().sprite = GameManager.instance.mDropItemSprite[(int)Enum.DropItemSprite.Mag];
break;
case < 0.3f:
item.GetComponent<DropItem>().mType = Enum.DropItemSprite.Health;
item.GetComponent<SpriteRenderer>().sprite = GameManager.instance.mDropItemSprite[(int)Enum.DropItemSprite.Health];
break;
}
}
}
- 실제 활용 예시
public class Enemy : MonoBehaviour
{
private void Dead()
{
mIsLive = false;
mColl.enabled = false;
mRigid.simulated = false;
mSpriter.sortingOrder = 1;
mAnim.SetBool("Dead", true);
GameManager.instance.mPlayerData.Kill++;
// 경험치 드롭
FuncPool.DropExp(mDropExp, transform.position);
// 골드 드롭
if (Random.value <= mDropGoldChance)
{
FuncPool.DropGold(mDropGold, transform.position);
}
GameManager.instance.PlaySFX(Enum.SFX.Dead);
gameObject.SetActive(false);
}
}
public class DropItem : MonoBehaviour
{
public Enum.DropItemSprite mType;
public int mData;
public bool mEnable;
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.CompareTag("Player"))
{
mEnable = false;
switch (mType)
{
case Enum.DropItemSprite.Exp0:
case Enum.DropItemSprite.Exp1:
case Enum.DropItemSprite.Exp2:
GameManager.instance.GetExp(mData);
break;
case Enum.DropItemSprite.Health:
GameManager.instance.GetHealth(mData);
break;
case Enum.DropItemSprite.Mag:
foreach (GameObject item in GameManager.instance.mDropPool.mPool)
{
if (item.GetComponent<DropItem>().mType == Enum.DropItemSprite.Exp0 || item.GetComponent<DropItem>().mType == Enum.DropItemSprite.Exp1 || item.GetComponent<DropItem>().mType == Enum.DropItemSprite.Exp2)
item.GetComponent<DropItem>().mEnable = true;
}
break;
case Enum.DropItemSprite.Gold0:
case Enum.DropItemSprite.Gold1:
case Enum.DropItemSprite.Gold2:
GameManager.instance.GetGold(mData);
break;
default:
Debug.Assert(false, "Error");
break;
}
gameObject.SetActive(false);
return;
}
if (collision.CompareTag("PickupCollider"))
mEnable = true;
}
}
3. 디버프 로직 구현
- 몬스터 내부에 디버프 관련 변수 및 로직 추가
public class Enemy : MonoBehaviour
{
bool mIsLive;
public float[] mDebuffPowers;
private void FixedUpdate()
{
if (!GameManager.instance.mIsLive)
return;
if (!mIsLive || mAnim.GetCurrentAnimatorStateInfo(0).IsName("Hit"))
return;
if (mDebuffPowers[(int)Enum.DebuffType.TicDamage] > 0)
Hit(mDebuffPowers[(int)Enum.DebuffType.TicDamage] * Time.fixedDeltaTime);
Vector2 dirVec = mTarget.position - mRigid.position;
Vector2 nextVec = dirVec.normalized * mMovementSpeed * mDebuffPowers[(int)Enum.DebuffType.SlowCoef] * Time.fixedDeltaTime;
mRigid.MovePosition(mRigid.position + nextVec);
mRigid.velocity = Vector2.zero;
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (!(collision.CompareTag("Weapon")) || !mIsLive)
return;
Hit(collision.GetComponent<Weapon>().mDamage);
mDebuffPowers[(int)collision.GetComponent<Weapon>().mDebuff] += collision.GetComponent<Weapon>().mDebuffPower;
}
private void OnTriggerExit2D(Collider2D collision)
{
if (!(collision.CompareTag("Weapon")) || !mIsLive)
return;
mDebuffPowers[(int)collision.GetComponent<Weapon>().mDebuff] -= collision.GetComponent<Weapon>().mDebuffPower;
}
}
4. 강화 시스템 구현 중
- HUD 구현 중 (Scroll View 구현 완료)
- 구현 방법
1) Scroll Rect 추가
Movement Type: Clamped(지정된 범위 내에 한정하여 움직이도록 설정)
Scroll Sensitivity: 25 (스크롤 당 25 pixel이 움직이도록 설정)
2) Content 항목
2-1) Grid Layout Group 적용
- 하위 객체를 그리드 형태로 제어하기 위해 추가
2-2) Content Size Fitter 적용
- Content Size(Height)가 객체의 Vertical Height에 맞춰 가변적으로 제어되도록 설정
이 이유는, Vertical Scrollbar는 Viewport의 Vertical Height 범위에 한정하여 제어가 이뤄짐.
따라서, Height가 객체의 높이에 맞게 가변적으로 설정될 수 있도록 Content Size Filter를 적용함
3) Template HUD 구현
- 추후 아이템 추가 시 동적 생성의 베이스가 될 Template HUD 구현
3. TODO
1. 강화 시스템 구현 완료 (골드 활용)
2. 상자 개봉 로직 추가
3. 무기 & 퍽 조합 시 무기 업그레이드 로직 추가
4. 게임 이어하기 로직 추가
5. 게임 세이브 슬롯 추가 (3개)
4. 참고 자료
https://docs.unity3d.com/kr/2020.3/Manual/script-ContentSizeFitter.html
https://docs.unity3d.com/kr/2021.3/Manual/script-ScrollRect.html