728x90

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

 

콘텐츠 크기 피터 - Unity 매뉴얼

콘텐츠 크기 피터는 자체 레이아웃 요소의 크기를 제어하는 레이아웃 컨트롤러의 기능을 수행합니다. 크기는 게임 오브젝트의 레이아웃 요소 컴포넌트에서 제공하는 최소 또는 기본 크기에 따

docs.unity3d.com

https://docs.unity3d.com/kr/2021.3/Manual/script-ScrollRect.html

 

스크롤 사각 영역 - Unity 매뉴얼

공간을 많이 차지하는 콘텐츠를 작은 영역에 표시해야 할 때 스크롤 사각 영역을 활용할 수 있습니다. 스크롤 사각 영역(Scroll Rect)은 콘텐츠를 스크롤하는 기능을 제공합니다.

docs.unity3d.com

 

 

+ Recent posts