진행한 것들

피격 효과

어제 이슈였던 간단한 피격 효과를 구현하게 되었다.

코루틴과 Dotween을 이용하여 Material 색을 변하게 하는 코드인데 DamagedAction에 의해 호출이 되어 이벤트 형식으로 작동이 된다.

public virtual void OnDamaged()
{
    if (_blinkCoroutine == null)
    {
        _blinkCoroutine = Blink();
    }
    else
    {
        StopCoroutine(_blinkCoroutine);
        myMaterial.color = Color.white;
    }

    StartCoroutine(_blinkCoroutine);
}

private IEnumerator Blink()
{
    myMaterial.DOColor(Color.red, blinkDuration);
    yield return new WaitForSeconds(blinkDuration);
    myMaterial.DOColor(Color.white, blinkDuration);
    yield return new WaitForSeconds(blinkDuration);
}

 

보스 기획

군사기지 보스에 대한 기획을 진행했다.

 

오늘의 이슈 / 내일 할 것

오늘 이슈

 

내일 할 것

보스 패턴 구현하기

 

 

오늘의 회고

  오늘은 피격 효과와 보스 패턴 기획을 진행했다. 피격 효과는 Dotween을 써서 구현했는데 내가 while문을 써서 구현하는 것보다 훨씬 간단하게 사용할 수 있어서 애용해야지라는 생각이 들었다. 보스 패턴은 3단계인 군사 기지에 대한 보스인데 현재 생각은 기존의 EnemyStateMachine을 활용하여 분리하고 패턴만 따로 클래스화 시키도록 구상중이다.

 

진행한 것들

중간 발표

현재 진행상황을 포함하여 중간 발표를 진행했다.

 

오늘의 이슈 / 내일 할 것

지난 주 이슈

1. 공격 시 구르기 안 나가는 버그

- Bool 체크로 해결

막아놨었는데 기획상 필요하다고 생각하여 다시 풀게 되었습니다.

 

2. 공격 버튼 선입력 버그  

공격 버튼을 눌러도 선 입력이 되고 Combo 파라미터가 이미 올라가 있는 버그가 있었습니다.

 

원인 :

1. 코드에서 이미 normalizeTime을 불러와서 1이 이상일 때를 체크하는데 ComboAttack 사이에 HasExitTime이 켜져 있었고 이것이 애니메이션 상태가 끝날 때까지 실행하게 되어 문제를 발생시켰다.

2. 가끔 Attack State를 탈출할 때가 있었다.

 

해결 :

1. ComboAttack 사이에 HasExitTime을 없애서 코드에서 제어가 가능하도록 했다.

2. 애니메이터에서 @Attack SubState에서 접근할 수 있는 트랜지션을 추가하여 탈출해도 다음 콤보를 실행할 수 있도록 했다.

 

오늘 이슈

- 피격 효과가 필요

- 죽었을 때 적이 사라질 필요성이 있음

 

오늘의 회고

  오늘은 지금까지 한 부분의 중간 발표를 했다. 와중에 지난 주에 못 풀었던 연속 공격의 버그를 해결했는데 코드와 애니메이터 두 곳에서 로직을 제어하려고 해서 문제가 일어났던 것이었다. 별 거 아닌 실수를 해서 허탈하기도 하고 일단 해결해서 다행이라는 생각이 들었다. 발표 때문에 정신이 없었지만 내일도 열심히 해보자 파이팅!

 

진행한 것들

데미지 계산 적용 변경

데미지 계산 적용을 변경해야 하는 경우가 생겼다.

https://jcdevelop98.tistory.com/370

 

DevLog - 이그라엘(IGRAL) 전체 테스트와 데미지 계산

진행한 것들 전체 테스트 이번 주에 만든 적 패트롤, 추적, 공격과 플레이어 연속 공격을 한 씬에 넣어서 테스트 했다. 데미지 계산 Weapon이라는 클래스를 만들어 무기에 컴포넌트로 등록시키고 O

jcdevelop98.tistory.com

 

문제점 : 무기에서 OnTriggerEnter로 공격을 체크하니 뒤에 있는 캐릭터가 맞는다는 경우가 생겼다.

놀랍게도 공격 애니메이션 도움닫기이다

 

이를 해결하기 위한 방법을 생각해보았는데

1. 애니메이션 이벤트에서 Attack이 될 때 캐릭터의 앞에 평소에 꺼져있는 Box Collider Trigger를 하나 만들어 체크하고 데미지를 준다.

2. 애니메이션 이벤트에서 Attack이 될 때 캐릭터의 앞에 Ray를 쏴서 Enemy일 시 데미지를 준다.

 

선택한 방법

2번의 Raycast를 사용하기로 했는데 그 이유는 아래의 글 처럼 즉발적으로 체크하는 경우에서는 Raycast가 유리하다고 해서이다.

https://discussions.unity.com/t/should-i-use-raycasting-or-colliders/108320

 

Should I use raycasting or colliders?

Hello, I have quite a simple question. I am currently working on a project that I wish to put guns/weapons into. Now when it comes down to guns is it preferable to use raycasting to deal damage to enemies or actually instantiate a gameobject “bullet” w

discussions.unity.com

 

다음과 같은 방식으로 구현하게 되었는데

Animation Controller에 접근하여 AttackAction에 OnAttack을 등록한 뒤 애니메이션에 등록된 이벤트 호출 함수인 AttackEvent가 AttackAction을 호출하는 방식으로 구현하게 되었다.

public abstract class AnimationController : MonoBehaviour
{
    public event Action AttackAction;

    private void AttackEvent()
    {
        AttackAction?.Invoke();
    }
}
public class PlayerWeapon : Weapon
{
    protected override void OnAttack()
    {
        HealthSO targetSO = null;
        IDamageable damageable = null;
        Vector3 offsetVec = _myTrans.position;
        offsetVec.y = 0.5f;


        RaycastHit[] hits = Physics.RaycastAll(offsetVec, _modelTrans.forward, 10f, 1 << LayerMask.NameToLayer(targetTag));
        //int combo = _playerAnimationController.AttackCombo;

        foreach (RaycastHit hit in hits)
        {
            EnemyStatHandler statHandler = hit.collider.GetComponentInParent<EnemyController>().StatHandler;

            targetSO = statHandler.Data;
            damageable = statHandler;

            Attack(_playerSO, targetSO, damageable);
        }
    }
}

 

 

오늘의 이슈 / 내일 할 것

지난 주 이슈

공격 상태가 아님에도 Attack을 호출하여 적 끼리 OnTriggerEnter가 불려서 NullReferenceException이 발생

-> Raycast로 바꾼 뒤 Layer를 체크하여 타겟일 때만 검출하도록 해서 해결!

 

오늘 이슈

연속 공격 애니메이션과 AttackCombo에 문제 있음

 

금요일 날 할 것

예비군 갔다와서 ComboMod 적용

 

 

오늘의 회고

  오늘은 지난 주에 작업한 데미지 계산을 OnTriggerEnter에서 Raycast검출로 바꿨다. 총알 같은 발사체면 Collider 검출을 하고 즉발적인 검출이면 Raycast를 사용하면 된다고 이해하였다. 내일부터 동원 예비군 훈련이 있는데 팀원들한테 조금 미안한 감정이 든다. 그만큼 다녀와서 열심히 하고 뒤쳐지지 않도록 해야겠다. 금요일부터 다시 파이팅!

진행한 것들

전체 테스트

이번 주에 만든 적 패트롤, 추적, 공격과 플레이어 연속 공격을 한 씬에 넣어서 테스트 했다.

 

 

데미지 계산

Weapon이라는 클래스를 만들어 무기에 컴포넌트로 등록시키고 OnTriggerEnter로 체크하여 데미지를 계산하고 입히게끔 했다.

attacker와 target으로 나누어 정보를 전달하였고 무적과 회피 그리고 크리티컬을 체크한 뒤 데미지를 계산하였다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Weapon : MonoBehaviour, IBattle
{
    private EntitySO _weaponSO;

    private const int PERCENT_EXCLUDE_MAX_VALUE = 101;

    private void OnTriggerEnter(Collider other)
    {
        HealthSO targetSO = null;
        IDamageable damageable = null;

        if (other.CompareTag("Player"))
        {
            PlayerStatHandler statHandler = other.GetComponentInParent<PlayerController>().StatHandler;
            targetSO = statHandler.Data;
            damageable = statHandler;

            if (_weaponSO == null)
            {
                _weaponSO = GetComponentInParent<EnemyController>().StatHandler.Data;
            }

            Attack(_weaponSO, targetSO, damageable);
        }
        else if (other.CompareTag("Enemy"))
        {
            EnemyStatHandler statHandler = other.GetComponentInParent<EnemyController>().StatHandler;
            targetSO = statHandler.Data;
            damageable = statHandler;
            
            if (_weaponSO == null)
            {
                _weaponSO = GetComponentInParent<PlayerController>().StatHandler.Data;
            }
            
            Attack(_weaponSO, targetSO, damageable);
        }
    }

    public void Attack(EntitySO attacker, HealthSO target, IDamageable targetDamageable)
    {
        if (target.IsInvincible)
            return;

        int randomEvasionProbability = UnityEngine.Random.Range(0, PERCENT_EXCLUDE_MAX_VALUE);
        if (randomEvasionProbability < target.EvasionProbability)
            return;

        float attackDamage = attacker.Attack;

        int randomCriticalProbability = UnityEngine.Random.Range(0, PERCENT_EXCLUDE_MAX_VALUE);
        if (randomCriticalProbability < attacker.CriticalProbability)
            attackDamage += attacker.Attack * attacker.CriticalMod;

        targetDamageable.Damaged(attackDamage);
    }
}

 

 

 

오늘의 이슈 / 내일 할 것

어제 이슈 : 패트롤 비동기

1. 공격 중에 점프, 구르기 이동 막기 (이동은 처음 공격 시 방향 전환만 적용)

StateMachine의 CurrentState가 AttackState면 return하도록 했다.

2. 맵 정보를 받아와서 적이 플레이어 추적하고 공격할 수 있도록 하기

Area에서 정보를 받아서 플레이어가 OnTriggerEnter 됐을 때 추적하고 OnTriggerExit 됐을 때 패트롤 상태가 되도록 했다.

 

오늘 이슈

1. 공격 상태가 아님에도 Attack을 호출하여 적 끼리 OnTriggerEnter가 불려서 NullReferenceException이 발생

 

내일 할 것

공격 상태에만 Attack을 호출할 수 있도록 하기

 

 

오늘의 회고

  오늘은 이번 주에 작업 했던 것들을 합쳐서 점검하고 무기로부터 데미지를 전달할 수 있도록 했다. 이후에 아이템 작업 하시는 분의 StatHandler의 작업이 끝나면 무기의 스텟을 캐릭터 스텟에 추가하고 총 합을 Attack의 매개변수로 넘길 수 있도록 할 것이다. 무기도 장착해주고 보여지는 것들이 하나씩 늘어가니까 뭔가 진행되고 있는 것 같아서 기분이 좋다. 이 기분대로 열심히 더 해보자 파이팅!

진행한 것들

플레이어 공격

플레이어 공격 애니메이션은 다른 것들처럼 믹사모에서 가져왔다.

플레이어의 AttackState에서의 Update문인데 AnimationController의 AttackType의 이름으로 현재 애니메이션의 Clip 이름과 비교하여 같은 상황에서 Attack 입력을 감지하면 AttackCombo 애니메이션 파라미터가 증가하게 만들어 다음 연속 동작으로 이어갈 수 있게끔 구현하였다.

또한 animator.GetCurrentClipInfo()[0]을 가져왔는데 왜 0번을 가져왔냐면 -> ClipInfo가 배열 타입으로 들어있는데 transition할 때는 2개안 할때는 1개라서 기본적으로 0번을 가져와 비교한 것이다.

public override void UpdateState()
{
    base.UpdateState();

    if (animationController.CheckCurrentClipEnded(animationController.AttackType))
    {
        stateMachine.ChangeState(stateMachine.MoveState);
    }
    else
    {
        if (animationController.IsAttackInputted && animationController.CheckCurrentClipEqual(animationController.AttackType))
        {
            animationController.SetAttackInputted(false);
            animationController.IncreaseAttackCombo();
            animationController.SetNextAttackType();
            animationController.PlayAnimation(animationsData.AttackComboHash, animationController.AttackCombo);
        }
    }
}
public bool CheckCurrentClipEnded(AttackType attackType, int layerIndex = 0)
{
    var clipInfo = animator.GetCurrentAnimatorClipInfo(layerIndex);
    if (clipInfo[0].clip.name.Equals(attackType.ToString()))
    {
        var stateInfo = animator.GetCurrentAnimatorStateInfo(layerIndex);

        if (stateInfo.normalizedTime >= animationNormalizeEndedTime)
            return true;
    }

    return false;
}

public bool CheckCurrentClipEqual(AttackType attackType, int layerIndex = 0)
{
    var clipInfo = animator.GetCurrentAnimatorClipInfo(layerIndex);
    Debug.Log($"clipInfo[0].clip.name : {clipInfo[0].clip.name}\nattackType : {attackType}");

    if (clipInfo[0].clip.name.Equals(attackType.ToString()))
        return true;
    else
        return false;
}

 

 

오늘의 이슈 / 내일 할 것

어제 이슈 : 패트롤 비동기

Async에서 동작하는 것이 아닌 Coroutine으로 동작할 수 있게끔 하여 내가 시작과 끝을 제어할 수 있게끔 수정하였다.

목표 지점에 다다랐다면 멈춰서서 순찰 애니메이션을 지정한 시간만큼 실행하고 다시 패트롤 상태로 돌입할 수 있게끔 재귀로 구성하였다.

public void CheckArrived()
{
    _currentEnumerator = CheckArrivedTargetPos();
    EnemyController.ExcuteCoroutine(_currentEnumerator);
}

public void StopCheckingArrived()
{
    EnemyController.TerminateCoroutine(_currentEnumerator);
}

private IEnumerator CheckArrivedTargetPos()
{
    if (IsTracing)
        yield break;

    CalculateDirection();

    bool isFar = true;

    while (isFar)
    {
        isFar = Direction.x < 0 ?
        _targetXPos <= EnemyController.transform.position.x : _targetXPos >= EnemyController.transform.position.x;
        yield return null;
    }

    SetDirection(Vector2.zero);

    yield return new WaitForSeconds(EnemyController.MovementData.WaitPatrolTime);

    CheckArrived();
}

private void CalculateDirection()
{
    float randomXPos = UnityEngine.Random.Range(_tileXPos - _tilehalfPowLength, _tileXPos + _tilehalfPowLength);

    Debug.Log($"_tileXPos - _tileHalfLength {_tileXPos} - {_tileHalfLength}");

    randomXPos = Mathf.Clamp(randomXPos, _tileXPos - _tileHalfLength, _tileXPos + _tileHalfLength);

    bool isLeft = EnemyController.transform.position.x > randomXPos;

    _targetXPos = randomXPos;

    int directionX = isLeft ? -1 : 1;

    SetDirection(directionX);
}

 

 

오늘 이슈

1. 플레이어 공격에서 현재 클립에 대한 정보를 얻기 어려웠다.

animator.GetCurrentAnimatorClipInfo() 메서드를 이용하여 현재 클립의 이름을 가져오고 애니메이션의 이름을 담은 enum 타입의 AttackType을 String한 값과 비교하여 같은지 비교하여 해결하였다.

 

2. <테스트 후 알게된 내용>

animator.GetCurrentState().fullPathHash의 예 : Animator.StringToHash("Base Layer.@Attack.Attack1")

* Base Layer의 띄어쓰기도 포함이다.

animator.GetCurrentState().shortNameHash의 예 : Animator.StringToHash("Attack1")

 

3. 공격 중에 점프, 구르기, 이동할 때 공격이 취소되거나 계속 움직일 수 있는데 이것은 내일 해결할 것이다.

 

 

내일 할 것

1. 공격 중에 점프, 구르기 이동 막기 (이동은 처음 공격 시 방향 전환만 적용)

2. 맵 정보를 받아와서 적이 플레이어 추적하고 공격할 수 있도록 하기

 

 

오늘의 회고

  오늘은 연속 공격을 구현했다. 굉장히 머리 아픈 작업이었는데 계속 테스트 해보고 여러 방법을 시도하다보니 좋은 방법을 찾은 것 같다. 내일은 맵에서 적과 상호작용 하면서 전체적으로 플레이어와 적이 잘 동작하는지 확인하고 완성도를 높일 생각이다. 내일도 파이팅!

 

진행한 것들

적 순찰 상태 구현

적 순찰 상태는 도착했는지 비동기로 확인하고 IsTracing 상태라면 추적 상태로 변경

using UnityEngine;

public class EnemyPatrolState : EnemyMoveState
{
    public EnemyPatrolState(EnemyStateMachine stateMachine) : base(stateMachine)
    {
    }

    public override void Enter()
    {
        base.Enter();
        movementDataHandler.CheckArrivedTargetPos();
    }

    public override void UpdateState()
    {
        base.UpdateState();
        if (movementDataHandler.IsTracing)
        {
            stateMachine.ChangeState(stateMachine.TraceState);
        }
    }

    public override void Exit()
    {
        base.Exit();
        movementDataHandler.LookPreDirectionRightAway();
    }
}

 

적의 순찰 범위는 맵에서 가져온 데이터를 기반으로 적의 좌 우 방향을 정해준다.

private void CalculateDirection()
{
    float randomXPos = UnityEngine.Random.Range(_tileXPos - _tilehalfPowLength, _tileXPos + _tilehalfPowLength);

    randomXPos = Mathf.Clamp(randomXPos, _tileXPos - _tileHalfLength, _tileXPos + _tileHalfLength);

    bool isLeft = _myTrans.position.x > randomXPos;

    _targetXPos = randomXPos;

    int directionX = isLeft ? -1 : 1;

    SetDirection(directionX);
}

 

 

오늘의 이슈 / 내일 할 것

어제 이슈 : 적의 추적 범위, 추적 빈도(저번 이슈)

 

1. 적의 추적 범위는 어느정도 되며

2. 어떤 조건에서 추적 상태를 취소시킬건지

3. 이동할 땅이 존재하는지에 대한 체크는 어떻게 할 것인지

 

해결 방안(기획적인 방법)

1. 적의 추적 범위는 맵에서 지정해주기로 함 (맵 위치 +- 맵 길이 / 2)

2. 추적 상태 취소는 맵의 Collider Trigger 범위에서 벗어났을 때 취소시킴

3. 이동할 땅이 존재하는지에 대한 체크는 맵에서 범위를 지정해주기 때문에 나갈 우려가 없음

 

 

오늘 이슈 : 패트롤 비동기

패트롤이 비동기라 다른 쓰레드에서 불리므로 EnemyPatrolState에서 불렀을 때 다시 불릴 수 있다는 불안함이 있다.

내일 EnemyPatrolState로 메서드를 빼서 동기로 실행하게끔 만들어야겠다.

public async void CheckArrivedTargetPos()
{
    if (IsTracing)
        return;

    CalculateDirection();

    bool isFar = true;

    while (isFar)
    {
        isFar = direction.x < 0 ?
        _targetXPos <= _myTrans.position.x : _targetXPos >= _myTrans.position.x;
        await Task.Delay(_movementData.CheckMilimeterSeconds);
    }

    SetDirection(Vector2.zero);

    await Task.Delay(_movementData.WaitPatrolTime);

    CheckArrivedTargetPos();
}

 

 

내일 할 것

패트롤 동기로 수정, 추적 상태 구현

 

 

 

오늘의 회고

  오늘은 패트롤 상태를 구현했다. 어제 생각했던 적의 추적 관련한 문제들이 골치 아팠는데 기획적으로 쉽게 해결할 수 있어서 좀 신기했다. 비동기로 실행할 필요도 없다고 생각을 해서 내일 동기로 바꿀 생각이다. 내일은 패트롤을 동기로 수정하면서 약간의 리팩토링을 하고 추적 상태를 구현하고 남는 시간에는 공격을 할 수 있도록 해야겠다. 내일도 파이팅!

진행한 것들

적 상태머신 틀 구현

플레이어 상태를 구현하고 적 상태도 구현하기 위해 공통적인 부분들을 부모 클래스로 하여 상속받아 구현하였다.

플레이어와 적 상태머신의 공통적인 부분을 StateMachine에 합치고

using UnityEngine;

public abstract class StateMachine
{
    protected IState currentState;


    public abstract void Init();

    public void ChangeState(IState newState)
    {
        currentState?.Exit();
        currentState = newState;
        currentState?.Enter();
    }

    public virtual void Update()
    {
        currentState.UpdateState();
    }

    public virtual void PhysicsUpdate()
    {
        currentState.PhysicsUpdateState();
    }
}

 

캐릭터 제어와 관련된 공통적인 부분은 CharacterController에 작성하였다.

[RequireComponent(typeof(Rigidbody))]
public abstract class CharacterController : MonoBehaviour
{
    public AnimationController AnimationController { get; private set; }
    public Rigidbody Rigidbody { get; private set; }

    [field: SerializeField] public MovementData MovementData { get; private set; }
    [field: SerializeField] public GroundData GroundData { get; private set; }

    protected virtual void Awake()
    {
        Rigidbody = GetComponent<Rigidbody>();
        AnimationController = GetComponentInChildren<AnimationController>();
        AnimationController.Init();
    }
}

 

오늘의 이슈 / 내일 할 것

적의 추적 범위, 추적 빈도

이슈

1. 적의 추적 범위는 어느정도 되며

2. 어떤 조건에서 추적 상태를 취소시킬건지

3. 이동할 땅이 존재하는지에 대한 체크는 어떻게 할 것인지

생각해본 것

 

1. 생성한 이후 맵 전체에서 고유한 플레이어를 추적

2. 일정 거리가 벗어난 5초 뒤에는 추적 상태 해제

3. 적 앞 쪽에 아래 방향으로 Ray를 쏴서 Ground면 이동 아니면 다시 패트롤 상태로 돌입 (추후 Raycast 성능 확인 필요)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyPatrolState : EnemyStateBase
{
    public EnemyPatrolState(EnemyStateMachine stateMachine) : base(stateMachine)
    {
    }

    public override void Enter()
    {
        
    }

    public override void Exit()
    {
        
    }

    public override void OnDead()
    {
        
    }
}

 

내일 할 것

패트롤 상태 구현

 

 

오늘의 회고

  오늘은 적 상태머신을 만들기 위해 플레이어 컨트롤러와 플레이어 상태머신의 공통적인 부분을 분리한 뒤 이 부분을 적 컨트롤러와 적 상태머신이 상속받게 했다. 생각보다 큰 작업이어서 테스트하는데도 시간이 좀 걸렸고 좋았던 부분은 다시 내 코드를 보면서 아 이런 방식으로 작성했었지 하면서 다시 공부하게 되는 시간이 됐던 것이다. 내일은 패트롤 상태를 구현하게 되는데 빨리빨리 구현해서 프로토타입을 완성할 수 있었으면 좋겠다. 내일도 파이팅!

진행한 것들

빌드

JDK로부터 시작한 빌드 이슈..

분량이 좀 있어서 따로 페이지를 만들었다.

https://jcdevelop98.tistory.com/365

 

안드로이드 빌드 오류 - JDK, SDK, NDK, Gradle

빌드 시 오류 발생 JDK와 Gradle JDK not Found 안드로이드로 스위칭 후 빌드 시 아래의 오류가 보인다면 JDK문제일 것이다. 다른 버전 설치하면서 헤메기 전에 (나는 헤맸지만) 우선 공식 문서를 살펴보

jcdevelop98.tistory.com

해결한 모습

 

 

오늘의 이슈 / 내일 할 것

구르기 중 점프되고 막히는 버그

점프 로직에서 IsRolling을 체크하여 막음

 

 

내일 할 것

공격 상태 / 적 상태 구현

 

 

오늘의 회고

  오늘은 빌드 테스트를 하고 구르기 버그를 잡았다. 안드로이드 빌드 테스트 중 시간을 너무 많이 쓴 것 같지만 이번에 확실하게 알게 되어서(알 수 밖에 없었지만 ㅋㅋ..) 오히려 좋았다. 그리고 항상 플레이어 쪽에 버그가 많이 나오는데 코드가 점점 증가해서 그런가 신경써야 될 부분들이 많아져서 그런 것 같다. 그래도 버그를 잡는 과정에서 완성도가 늘어난다고 생각해서 잘 하고있다고 생각한다. 내일도 파이팅!

진행한 것들

플레이어 더블 점프

JumpCountHandler에서 JumpCount를 관리하고

public class JumpCountHandler
{
    public JumpCountHandler(int maxJumpCount)
    {
        SetJumpCount(maxJumpCount);
    }

    public int JumpCount { get; private set; }

    public void DecreaseJumpCount()
    {
        JumpCount--;
    }

    public void SetJumpCount(int count)
    {
        JumpCount = count;
    }
}

PlayerJumpState에서 Jump 상태 변경을 관리한다.

using UnityEngine;

public class PlayerJumpState : PlayerAirState
{
    private void Jump()
    {
        if (jumpCountSetter.JumpCount > 0)
        {
            animationController.ReStartIfAnimationIsPlaying(animationsData.JumpParameterHash);

            jumpCountSetter.DecreaseJumpCount();
            Vector3 velocity = rigid.velocity;
            velocity.y = playerController.StatHandler.Data.JumpForce;
            rigid.velocity = velocity;
        }
    }
}

마지막으로 자연스러운 더블점프 동작을 위해 현재 점프 상태이고 또 점프가 들어오면 처음부터 애니메이션을 다시 재생하도록 했다.

using UnityEngine;

public class AnimationController : MonoBehaviour
{
    [field: SerializeField] public PlayerAnimationsData AnimationData { get; private set; }
    private Animator _animator;

    public void ReStartIfAnimationIsPlaying(int animationParameterHash, int layerIndex = 0)
    {
        if (_animator.GetCurrentAnimatorStateInfo(layerIndex).shortNameHash.Equals(animationParameterHash))
            _animator.Play(animationParameterHash);
    }
}

 

플레이어 구르기

구르기에서는 무적, 쿨타임, 구르기 시 움직임 제한 기능이 필요했다.

 

먼저 PlayerRollState에서 구르기 애니메이션과 구르기 끝났는지를 체크하고

public class PlayerRollState : PlayerStateBase
{
    private void CheckRollingEnded()
    {
        if (animationController.CheckAnimationEnded(animationsData.RollParameterHash))
        {
            stateMachine.RollDataHandler.SetIsRolling(false);

            if (stateMachine.IsGrounded)
                stateMachine.ChangeState(stateMachine.MovementState);
            else
                stateMachine.ChangeState(stateMachine.FallState);
        }
    }
}

RollDataHandler에서 구르기 수행 시 무적과 쿨타임을 계산한다.

using UnityEngine;

public class RollDataHandler
{
    public bool CanRoll { get; private set; }
    public bool IsInvincible { get; private set; }
    public bool IsRolling { get; private set; }
    private float _rollingCoolTime;
    private float _currentRollingElapsedTime;
    private float _invincibleTime;

    public RollDataHandler(float rollingCoolTime, float invincibleTime)
    {
        SetRollingCoolTime(rollingCoolTime);
        _currentRollingElapsedTime = rollingCoolTime;
        _invincibleTime = invincibleTime;
        CanRoll = true;
    }

    public void SetRollingCoolTime(float rollingCoolTime)
    {
        _rollingCoolTime = rollingCoolTime;
    }

    public void ResetCurrentRollingElapsedTime()
    {
        CanRoll = false;
        _currentRollingElapsedTime = 0f;
        IsInvincible = true;
    }

    public void CalculateCoolTime()
    {
        if (_currentRollingElapsedTime >= _rollingCoolTime)
        {
            CanRoll = true;
            return;
        }

        _currentRollingElapsedTime += Time.deltaTime;
        _currentRollingElapsedTime = 
            _currentRollingElapsedTime > _rollingCoolTime ? 
            _rollingCoolTime : _currentRollingElapsedTime;

        CalculateInvincible();
    }

    public void SetIsRolling(bool isRolling)
    {
        IsRolling = isRolling;
    }

    private void CalculateInvincible()
    {
        if (_currentRollingElapsedTime < _invincibleTime)
            return;

        IsInvincible = false;
    }
}

 

 

오늘의 이슈 / 내일 할 것

더블 점프 구현을 위한 JumpCount 이슈

문제점 : JumpCount를 IsGronded일 때 JumpCountMax로 초기화를 해줬으나 Debug.Log를 찍어보면 초기화가 안 되는 이슈, PlayerJumpState에서의 문제인 줄 알고 PlayerAirState에 옮겼으나 결과는 같았음
원인 : 호출 스택을 살펴보니 PlayerFallState 부모의 PlayerAirState에서만 초기화되는 경우였음 (PlayerJumpState JumpCount != PlayerFallState JumpCount)
해결 : 공통적으로 참조하는 StateMachine에서 JumpCount를 관리하기로 함
** 또한 IsGrounded도 같은 이슈여서 StateMachine에서 관리하는 것으로 처리

 

플레이어가 날라가는 이슈

문제점 : 플레이어 날아가는 이슈

원인 : 점프 값에도 Speed가 곱해져 날라가던 것이었다.

해결 : 이동에만 Speed를 곱해주니 해결

 

내일 할 것 : 구르기 시 이동 입력 받기, 빌드

 

 

오늘의 회고

 오늘은 캐릭터 더블 점프와 구르기를 구현했다. 더블 점프에 상태머신이 겹쳐서 참조 변수를 각각 따로 참조하게 되는 실수를 저질렀는데 이후에 호출 스택을 보고 처리하길 잘했던 것 같다. 그리고 구르기 시 Input을 받지 않는 작업은 InputSystem의 Interaction Hold 부분을 좀 더 공부해보고 적용해봐야겠다. 내일도 파이팅!

진행한 것들

캐릭터 상태머신

아래 글에서 진행한대로 플레이어 애니메이션과 관련된 부분은 상태머신으로 관리하기로 했다. 상태머신은 내가 의도한대로 플레이어가 동작 하도록 하기 위해 적용했다.

https://jcdevelop98.tistory.com/349

 

내일배움캠프 40일차 TIL - FSM

오늘의 학습 키워드 Finite State Machine 공부한 내용 Finite State Machine 한 번에 한 개의 상태만 가질 수 있고 다른 상태로 전이가 가능한 모델을 FSM(유한 상태 머신)이라고 부른다. 게임에서는 애니메

jcdevelop98.tistory.com

 

모든 State는 IState를 상속받고 

public interface IState
{
    void Enter();
    void Exit();
    void UpdateState();
    void PhysicsUpdateState();
    void OnDead();
}

 

StateMachine에서 현재 IState인  _currentState를 ChangeState(IState newState)로 바꿔준다.

public class StateMachine
{
    private IState _currentState;

    public PlayerController PlayerController { get; private set; }

    public PlayerMoveState MovementState { get; private set; }
    public PlayerJumpState JumpState { get; private set; }
    
    public StateMachine(PlayerController playerController)
    {
        PlayerController = playerController;

        MovementState = new PlayerMoveState(this);
    }

    public void Init()
    {
        ChangeState(MovementState);
    }


    public void ChangeState(IState newState)
    {
        _currentState?.Exit();
        _currentState = newState;
        _currentState?.Enter();
    }

    public void Update()
    {
        _currentState.UpdateState();
    }

    public void PhysicsUpdate()
    {
        _currentState.PhysicsUpdateState();
    }
}

 

각 State는 애니메이션을 동작시키는데 예를 들어 PlayerMoveState가 있으면 Enter에서 플레이어의 애니메이션 bool 파라미터를 true로 만들고 Exit에서 false로 만들게끔 했다.

public class PlayerMoveState : PlayerStateBase
{
    public PlayerMoveState(StateMachine stateMachine) : base(stateMachine)
    {
        
    }

    public override void Enter()
    {
        playerController.Animator.SetBool(animationsData.MoveParameterHash, true);
    }

    public override void UpdateState()
    {
        base.UpdateState();
        playerController.Animator.SetFloat(animationsData.SpeedRatioParameterHash, speedRatio);
    }

    public override void Exit()
    {
        playerController.Animator.SetBool(animationsData.MoveParameterHash, false);
    }

    public override void OnDead()
    {
        base.OnDead();
    }
}

 

애니메이션은 블렌드 트리를 사용하였다.

 

 

오늘의 이슈 / 내일 할 것

이슈 : Mixamo에서 가져온 Animation에서 humanoid로 변경 후 Import Message Warning이 뜸

 

시도한 것 : 'Take001'이라는 애니메이션이 문제여서 Animation 탭의 Clips에서 제거함, 그러나 왜인지 Source Take 부분에는 남아 있고 Import Message Warning도 존재함

 

내일 할 것 : 캐릭터 점프, 슬라이딩

 

 

 

오늘의 회고

 오늘은 캐릭터 점프와 슬라이딩 쪽을 구현할 예정이었으나 캐릭터 상태머신과 애니메이션이 더 필요하다고 하여 상태머신 쪽을 먼저 구현하게 되었다. Input을 받아와 이동에 적용하는데 구조를 변경하는 작업이 동반되어서 시간이 좀 걸렸던 것 같다. 리팩토링은 할 땐 귀찮은데 하고 나면 괜히 뿌듯한 작업인 것 같다. 내일도 파이팅!

+ Recent posts