[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 21회차 미션 시작합니다.

02 디아블로 게임 - 37. 38 챕터를 진행합니다.




이번 시간은 RPG 게임 플레이 시스템 구현하기 입니다.




+ 플레이어 속성 - 아이템 착용 상태에 따른 속성 변경
+ 플레이어 UI
+ 다이얼로그 시스템 구현 - 대화 시스템
+ 퀘스트 시스템 구현
+ 환경 장치(함정/문) 구현 - 레벨에 따른..





중요한 부분은 Attribute와 ModifiableInt 클래스 입니다.

+ Attribute - ModifiableInt를 가지고 Value를 설정
+ ModifiableInt - Base값을 가지고 변경되어진 값을 사용하는 Value(?)
+ AttributeType - 속성의 Type 설정
+ StatsObject - Attribute를 설정

이제 스크립트를 구현합니다. 바로 로직구현으로 고고고~~ ㅎㅎ


[Serializable]
public class ModifiableInt_New
{
    [NonSerialized]
    private int baseValue;
    [SerializeField]
    private int modifiedValue;
    
    public int BaseValue {
        get => baseValue;
        set {
            baseValue = value;
            UpdateModifiedValue();
        }
    }
    
    public int ModifiedValue {
        get => modifiedValue;
        set => modifiedValue = value;
    }
    
    // 변경되었다는 이벤트를 받기 위한 처리
    private event Action<ModifiableInt_New> OnModifiedValue;
    
    private List<IModifier> modifiers = new List<IModifier>();
    
    public ModifiableInt_New(Action<ModifiableInt_New> method = null) {
        ModifiedValue = baseValue;
        RegisterModEvent(method);
    }
    
    public void RegisterModEvent(Action<ModifiableInt_New> method) {
        if (method != null) {
            OnModifiedValue += method;
        }
    }
    
    public void UnregisterModEvent(Action<ModifiableInt_New> method) {
        if (method != null) {
            OnModifiedValue -= method;
        }
    }
    
    private void UpdateModifiedValue() {
        int valueToAdd = 0;
        foreach (IModifier modifier in modifiers) {
            modifier.AddValue(ref valueToAdd);
        }
        
        ModifiedValue = baseValue + valueToAdd;
        
        OnModifiedValue?.Invoke(this);
    }
    
    public void AddModifier(IModifier modifier) {
        modifiers.Add(modifier);
        UpdateModifiedValue();
    }
    
    public void RemoveModifier(IModifier modifier) {
        modifiers.Remove(modifier);
        UpdateModifiedValue();
    }
}




[Serializable]
public class Attribute_New
{
    public AttributeType type;
    public ModifiableInt_New value;
}



[CreateAssetMenu(fileName = "New Stats", menuName = "Stats System/New Character Stats New")[
public class StatsObject_New : ScriptableObject
{
    public Attribute_New[] attributes;
    public int level;
    public int exp;
    public int Health { get; set; } // Property로 만들면 Serializable 되지 않는다.
    public int Mana { get; set; }
    
    public float HealthPercentage {
        get {
            int health = Health;
            int maxHealth = Health;
            foreach (Attribute_New attribute in attributes) {
                if (attribute.type == AttributeType.Health) {
                    maxHealth = attribute.value.ModifiedValue;
                }
            }
            
            return (maxHealth > 0 ? ((float)health / (float)maxHealth) : 0f);
        }
    }
   
    public float ManaPercentage {
        get {
            int mana = Mana;
            int maxMana = Mana;
            foreach (Attribute_New attribute in attributes) {
                if (attribute.type == AttributeType.Mana) {
                    maxMana = attribute.value.ModifiedValue;
                }
            }
            
            return (maxMana > 0 ? ((float)mana / (float)maxMana) : 0f);
        }
    }
   
    public event Action<StatsObject> OnChangedStats;
    
    [NonSerialized]
    private bool isInitialize = false;
    public void OnEnable() {
        InitializeAttribute();
    }
    
    public void InitializeAttribute() {
        if (isInitialize) return;
        isInitialize = true;
        foreach (Attribute_New attribute in attributes) {
            attribute.value = new ModifiableInt_New(OnModifiedValue);
        }
        level = 1;
        exp = 0;
        SetBaseValue(AttributeType.Agility, 100);
        SetBaseValue(AttributeType.Intellect, 100);
        SetBaseValue(AttributeType.Stamina, 100);
        SetBaseValue(AttributeType.Strength, 100);
        SetBaseValue(AttributeType.Health, 100);
        SetBaseValue(AttributeType.Mana, 100);
        
        Health = GetModifiedValue(AttributeType.Health);
        Mana = GetModifiedValue(AttributeType.Mana);
    }
    
    private void OnModifiedValue(ModifiableInt_New value) {
        OnChangedStats?.Invoke(this);
    }
    
    public int GetBaseValue(AttributeType type) {
        foreach (Attribute attribute in attributes) {
            if (attribute.type == type) {
                return attribute.value.BaseValue;
            }
        }
        return -1;
    }
    
    public void SetBaseValue(AttributeType type, int value) {
        foreach (Attribute attribute in attributes) {
            if (attribute.type == type) {
                attribute.value.BaseValue = value;
            }
        }
    }
    
    public int GetModifiedValue(AttributeType type) {
        foreach (Attribute attribute in attributes) {
            if (attribute.type == type) {
                return attribute.value.ModifiedValue;
            }
        }
        return -1;
    }
    
    public int AddHealth(int value) {
        Health += value;
        OnChangedStats?.Invoke(this);
        return Health;
    }
    
    public int AddMana(int value) {
        Mana += value;
        OnChangedStats?.Invoke(this);
        return Mana;
    }
}






이제 Unity Editor에서 PlayerStats를 추가하여 위와 같이 설정해 줍니다.

위와 관련 기존 코드에서 수정되는 부분들만 작성합니다.

public class PlayerCharacter
{
    [SerializeField]
    public StatsObject playerStats;
    
    public void OnEnterAttackState() {
        UnityEngine.Debug.Log("OnEnterAttackState()");
        playerStats.AddMana(-3); // 임시 테스트값
    }
    
    public void OnExitAttackState() {
        UnityEngine.Debug.Log("OnExitAttackState()");
    }
    
    public bool IsAlive => playerStats.Health > 0;
    
    public void TakeDamage(int damage, GameObject damageEffectPrefab) {
        //..
        playerStats.AddHealth(-damage);
        //..
    }
    
    private void OnUseItem(ItemObject itemObject) {
        foreach (ItemBuff buff in itemObject.data.buffs) {
            if (buff.stat == AttributeType.Health) {
                playerStats.AddHealth(buff.value);
            }
        }
    }
}


 



Unity에서 플레이어의 속성 UI를 표시하기 위해 무료 Asset을 받아서 Canvas에 위와 같이 설정합니다.


public class PlayerInGameUI_New : MonoBehaviour
{
    public StatsObject_New playerStats;
    public Text levelText;
    public Image healthSlider;
    public Image manaSlider;
    
    void Start() {
        levelText.text = playerStats.level.ToString("n0");
        healthSlider.fillAmount = playerStats.HealthPercentage;
        manaSlider.fillAmount = playerStats.ManaPercentage;
    }
    
    private void OnEnable() {
        playerStats.OnChangedStats += OnChangedStats;
    }
    
    private void OnDisable() {
        playerStats.OnChangedStats -= OnChangedStats;
    }
    
    private void OnChangedStats(StatsObject_New statsObject) {
        levelText.text = playerStats.level.ToString("n0");
        healthSlider.fillAmount = playerStats.HealthPercentage;
        manaSlider.fillAmount = playerStats.ManaPercentage;
    }
}





플레이 화면입니다.
공격을 당하면 HP가 줄어들고, 공격을 하면 Mana가 줄어들고, 물약을 먹으면 HP가 다시 채워지는 것을 확인할 수 있습니다.






이제 위와 같은 속성을 표시하기 위한 구현입니다.

public class PlayerStatsUI_New : MonoBehaviour
{
    public InventoryObject equipment;
    public StatsObject playerStats;
    public Text[] attributeTexts;
    
    private void OnEnable() {
        playerStats.OnChangedStats += OnChangedStats;
        if (equipment != null && playerStats != null) {
            foreach (InventorySlot slot in equipment.Slots) {
                slot.OnPreUpdate += OnRemoveItem;
                slot.OnPostUpdate += OnEquipItem;
            }
            UpdateAttributeTexts();
        }
    }
    
    private void OnDisable() {
        playerStats.OnChangedStats -= OnChangedStats;
        if (equipment != null && playerStats != null) {
            foreach (InventorySlot slot in equipment.Slots) {
                slot.OnPreUpdate -= OnRemoveItem;
                slot.OnPostUpdate -= OnEquipItem;
            }
            UpdateAttributeTexts();
        }
    }
    
    private void UpdateAttributeTexts() {
        attributeTexts[0].text = playerStats.GetModifiedValue(AttributeType.Agility).ToString("n0");
        attributeTexts[1].text = playerStats.GetModifiedValue(AttributeType.Intellect).ToString("n0");
        attributeTexts[2].text = playerStats.GetModifiedValue(AttributeType.Stamina).ToString("n0");
        attributeTexts[3].text = playerStats.GetModifiedValue(AttributeType.Strength).ToString("n0");
    }
    
    private void OnRemoveItem(InventorySlot slot) {
        if (slot.ItemObject == null) return;
        foreach (ItemBuff buff in slot.item.buffs) {
            foreach (Attribute attribute in playerStats.attributes) {
                if (attribute.type == buff.stat) {
                    attribute.value.RemoveModifier(buff);
                }
            }
        }
    }
    
    private void OnEquipItem(InventorySlot slot) {
        if (slot.ItemObject == null) return;
        foreach (ItemBuff buff in slot.item.buffs) {
            foreach (Attribute attribute in playerStats.attributes) {
                if (attribute.type == buff.stat) {
                    attribute.value.AddModifier(buff);
                }
            }
        }
    }
    
    private void OnChangedStats(StatsObject statsObject) {
        UpdateAttributeTexts();
    }
}





여기까지 작성하고 플레이를 시작하여 장착 테스트를 해봅니다.
모든 값들이 잘 표시됨을 확인할 수 있습니다. @~@;;;;





<위의 코드들은 제가 보면서 주요한 함수, 코드를 확인하기 위해 타이핑한 용도로, 전체 소스코드가 아님에 주의해 주세요. 전체 코드는 교육 수강을 하면 완벽하게 받으실 수가 있답니다 ^^>

패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0


 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

+ Recent posts