내일배움캠프 40일차 TIL, 유니티 심화주차 강의

황오영·2024년 6월 13일
0

TIL

목록 보기
40/56
post-thumbnail

심화주차 강의 - 케릭터 스탯

  • 오늘 심화주차 강의내용을 정리.

switch식

  • switch 식(expression) : C# 8.0부터 지원하는 방식인데 swtich문에서 case를 뺴서 간결하게 정리 한다.
  • 람다식을 사용하여 (패턴/값) => 수식처럼 표현하는 방식.
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using System;

public class CharacterStatHandler : MonoBehaviour
{
    // 기본 스탯과 버프 스탯들의 능력치를 종합해서 스탯을 계산하는 컴포넌트
    [SerializeField] private CharacterStat baseStats;
    public CharacterStat CurrentStat { get; private set; } = new();
    public List<CharacterStat> statsModifiers = new List<CharacterStat>();

	//최소값 보정을 위한 수치
    private readonly float MinAttackDelay = 0.03f;
    private readonly float MinAttackPower = 0.5f;
    private readonly float MinAttackSize = 0.4f;
    private readonly float MinAttackSpeed = .1f;

    private readonly float MinSpeed = 0.8f;

    private readonly int MinMaxHealth = 5;

    private void Awake()
    {
        if (baseStats.attackSO != null)
        {
            baseStats.attackSO = Instantiate(baseStats.attackSO);
            CurrentStat.attackSO = Instantiate(baseStats.attackSO);
        }
        UpdateCharacterStat();
    }

    // 외부에서 스탯 변화 얻은 부분 (추가용)
    public void AddStatModifier(CharacterStat statModifier)
    {
        statsModifiers.Add(statModifier);
        UpdateCharacterStat();
    }

    // 외부에서 스탯 변화 얻은 부분 (제거용)
    public void RemoveStatModifier(CharacterStat statModifier)
    {
        statsModifiers.Remove(statModifier);
        UpdateCharacterStat();
    }
    
    // 스탯 갱신용
    private void UpdateCharacterStat()
    {
        // 베이스 스텟 먼저 적용합니다.
        ApplyStatModifiers(baseStats);

        // 변경되는 수치들을 반영합니다.
        // 이때 statsChangeType의 순서 0 : Add, 1 : Multiple, 2 : Override를 반영하여 0, 1, 2 순으로 정렬합니다. 정렬을 추가해 순차적으로 적용
        foreach (var modifier in statsModifiers.OrderBy(o => o.statsChangeType))
        {
            ApplyStatModifiers(modifier);
        }
    }

	//스탯 실적용 메소드
    private void ApplyStatModifiers(CharacterStat modifier)
    {
        // Func는 마지막 제네릭 타입을 결과자료형으로 하는 델리게이트를 말합니다. (Action은 리턴이 없으니까요!)
        // 예를 들어, Func<float, float, float>는 float 2개를 받아 float를 리턴하는 델리게이트인 것이죠!
        // 아래 switch문은 switch식 구문으로, modifier.statsChangeType의 값에 따라 아래와 같이 
        Func<float, float, float> operation = modifier.statsChangeType switch
        {
            // 아래 문법은 람다 함수로 처리한 것으로, (메소드 파라미터) => 결과 처럼 나타냅니다.
            // 즉 modifier.statsChangeType이 Add라면, operation은 current와 change 두 개의 float를 받아 current + change(말그대로 Add)하는 함수를 던져주는 것입니다.
            // 이렇게 하는 이유는 modifier의 타입에 따라 처리하는 방법 자체가 달라지기 때문에 이를 메소드로 넘겨주는 것이라고 생각하면 되겠습니다!
            StatsChangeType.Add => (current, change) => current + change,
            StatsChangeType.Multiple => (current, change) => current * change,
            // _는 default case에 대한 상황입니다. Add, Multiple이 아니고 Override라는 의미인데, 만약 타입이 더 늘어날 수 있다면 조심해야되겠죠?
            _ => (current, change) => change,
        };

        // 이렇게 메소드를 파라미터처럼 넘겨주는 것! Action을 활용해서 이미 많이 해보셨죠?
        UpdateBasicStats(operation, modifier);
        UpdateAttackStats(operation, modifier);
        // CurrentStat.attackSO가 RangedAttackSO인지 확인하면서 맞을 경우 이를 currentRanged로 저장하는 문법입니다.
        if (CurrentStat.attackSO is RangedAttackSO currentRanged && modifier.attackSO is RangedAttackSO newRanged)
        {
            UpdateRangedAttackStats(operation, currentRanged, newRanged);
        }

        // 아래와 같이 나타내던 코드를 위와 같이 나타냈다고 생각하면 될 것 같습니다.
        //if (CurrentStat.attackSO is RangedAttackSO && modifier.attackSO is RangedAttackSO)
        //{
        //    UpdateRangedAttackStats(operation, CurrentStat.attackSO as RangedAttackSO, modifier.attackSO as RangedAttackSO);
        //}
    }

    private void UpdateBasicStats(Func<float, float, float> operation, CharacterStat modifier)
    {
        CurrentStat.maxHealth = Mathf.Max((int)operation(CurrentStat.maxHealth, modifier.maxHealth), MinMaxHealth);
        CurrentStat.speed = Mathf.Max(operation(CurrentStat.speed, modifier.speed), MinSpeed);
    }

    private void UpdateAttackStats(Func<float, float, float> operation, CharacterStat modifier)
    {
        if (CurrentStat.attackSO == null || modifier.attackSO == null) return;

        var currentAttack = CurrentStat.attackSO;
        var newAttack = modifier.attackSO;

        // 변경을 적용하되, 최소값을 적용합니다.
        currentAttack.delay = Mathf.Max(operation(currentAttack.delay, newAttack.delay), MinAttackDelay);
        currentAttack.power = Mathf.Max(operation(currentAttack.power, newAttack.power), MinAttackPower);
        currentAttack.size = Mathf.Max(operation(currentAttack.size, newAttack.size), MinAttackSize);
        currentAttack.speed = Mathf.Max(operation(currentAttack.speed, newAttack.speed), MinAttackSpeed);
    }

    private void UpdateRangedAttackStats(Func<float, float, float> operation, RangedAttackSO currentRanged, RangedAttackSO newRanged)
    {
        currentRanged.multipleProjectilesAngle = operation(currentRanged.multipleProjectilesAngle, newRanged.multipleProjectilesAngle);
        currentRanged.spread = operation(currentRanged.spread, newRanged.spread);
        currentRanged.duration = operation(currentRanged.duration, newRanged.duration);
        currentRanged.numberofProjectilesPerShot = Mathf.CeilToInt(operation(currentRanged.numberofProjectilesPerShot, newRanged.numberofProjectilesPerShot));
        currentRanged.projectileColor = UpdateColor(operation, currentRanged.projectileColor, newRanged.projectileColor);
    }

	//색깔 바꿔주는 오퍼레이터
    private Color UpdateColor(Func<float, float, float> operation, Color current, Color modifier)
    {
        return new Color(
            operation(current.r, modifier.r),
            operation(current.g, modifier.g),
            operation(current.b, modifier.b),
            operation(current.a, modifier.a));
    }
}
  • 스크립터블 오브젝트 이용해서 스탯을 List에 넣어두고 관리하는 방식. Func operation이용해서 계산을 해주고 swtich식을 통해 간결하게 코드를 짜니 훨신 보기 쉽고 좋아서 익숙해지면 간결하게 잘 짤 수 있을것같다.
  • 특히 스탯시스템류는 많이 쓰니 정리 잘 해야겠다.

오늘의 회고

  • 최종프로젝트는 타이쿤류 스타듀벨리, 마이 리틀포레스트같은 레퍼런스 게임으로 기반으로 기획해보려고한다. 좀 고민인데 숙련주차 하면서 정리해 봐야겠다.
  • 이번 숙련주차는 서버도 간단히 사용하는거라 잘 정리하고 카메라류도 다루니 잘 정리해놔야겠다.
profile
게임개발을 꿈꾸는 개발자

0개의 댓글