3D 플랫포머 게임으로 장르를 정한 것과 액션 게임에 대한 장르적 연출이 부족하다고 들었다.
그래서 팀원들끼리 의논해본 결과 플랫포머의 특성에 맞게 각 플랫폼과 상호작용 할 수 있는 기능들을 늘리고액션 게임에서의 속도감을 위해 캐릭터 속도 스탯을 조정하거나 공격 캔슬을 만든다던지 또는 타격 연출을 추가 한다던지 해서 조정할 생각이다.
오늘의 이슈 / 내일 할 것
오늘 이슈
- 기획 피드백
내일 할 것
- 캐릭터 스탯 조정하기
- 공격 연출 추가하기
오늘의 회고
오늘은 기획 피드백을 받고 선택과 집중을 하게 되었다. 플랫포머와 액션 장르에 대한 피드백을 받았고 머리를 망치로 한 대 맞은 것처럼 부족한 부분이 드러나게 되었다. 게임의 재미를 위해 장르적 특성을 좀 더 보여줄 필요가 있어 보였다. 팀원과 얘기한대로 진행해보고 점점 나아지도록 하자. 내일도 파이팅!
코루틴과 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을 활용하여 분리하고 패턴만 따로 클래스화 시키도록 구상중이다.
공격 버튼을 눌러도 선 입력이 되고 Combo 파라미터가 이미 올라가 있는 버그가 있었습니다.
원인 :
1. 코드에서 이미 normalizeTime을 불러와서 1이 이상일 때를 체크하는데 ComboAttack 사이에 HasExitTime이 켜져 있었고 이것이 애니메이션 상태가 끝날 때까지 실행하게 되어 문제를 발생시켰다.
2. 가끔 Attack State를 탈출할 때가 있었다.
해결 :
1. ComboAttack 사이에 HasExitTime을 없애서 코드에서 제어가 가능하도록 했다.
2. 애니메이터에서 @Attack SubState에서 접근할 수 있는 트랜지션을 추가하여 탈출해도 다음 콤보를 실행할 수 있도록 했다.
오늘 이슈
- 피격 효과가 필요
- 죽었을 때 적이 사라질 필요성이 있음
오늘의 회고
오늘은 지금까지 한 부분의 중간 발표를 했다. 와중에 지난 주에 못 풀었던 연속 공격의 버그를 해결했는데 코드와 애니메이터 두 곳에서 로직을 제어하려고 해서 문제가 일어났던 것이었다. 별 거 아닌 실수를 해서 허탈하기도 하고 일단 해결해서 다행이라는 생각이 들었다. 발표 때문에 정신이 없었지만 내일도 열심히 해보자 파이팅!
공격 상태가 아님에도 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으로 동작할 수 있게끔 하여 내가 시작과 끝을 제어할 수 있게끔 수정하였다.
목표 지점에 다다랐다면 멈춰서서 순찰 애니메이션을 지정한 시간만큼 실행하고 다시 패트롤 상태로 돌입할 수 있게끔 재귀로 구성하였다.
오늘은 패트롤 상태를 구현했다. 어제 생각했던 적의 추적 관련한 문제들이 골치 아팠는데 기획적으로 쉽게 해결할 수 있어서 좀 신기했다. 비동기로 실행할 필요도 없다고 생각을 해서 내일 동기로 바꿀 생각이다. 내일은 패트롤을 동기로 수정하면서 약간의 리팩토링을 하고 추적 상태를 구현하고 남는 시간에는 공격을 할 수 있도록 해야겠다. 내일도 파이팅!
플레이어 상태를 구현하고 적 상태도 구현하기 위해 공통적인 부분들을 부모 클래스로 하여 상속받아 구현하였다.
플레이어와 적 상태머신의 공통적인 부분을 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()
{
}
}
내일 할 것
패트롤 상태 구현
오늘의 회고
오늘은 적 상태머신을 만들기 위해 플레이어 컨트롤러와 플레이어 상태머신의 공통적인 부분을 분리한 뒤 이 부분을 적 컨트롤러와 적 상태머신이 상속받게 했다. 생각보다 큰 작업이어서 테스트하는데도 시간이 좀 걸렸고 좋았던 부분은 다시 내 코드를 보면서 아 이런 방식으로 작성했었지 하면서 다시 공부하게 되는 시간이 됐던 것이다. 내일은 패트롤 상태를 구현하게 되는데 빨리빨리 구현해서 프로토타입을 완성할 수 있었으면 좋겠다. 내일도 파이팅!
오늘은 빌드 테스트를 하고 구르기 버그를 잡았다. 안드로이드 빌드 테스트 중 시간을 너무 많이 쓴 것 같지만 이번에 확실하게 알게 되어서(알 수 밖에 없었지만 ㅋㅋ..) 오히려 좋았다. 그리고 항상 플레이어 쪽에 버그가 많이 나오는데 코드가 점점 증가해서 그런가 신경써야 될 부분들이 많아져서 그런 것 같다. 그래도 버그를 잡는 과정에서 완성도가 늘어난다고 생각해서 잘 하고있다고 생각한다. 내일도 파이팅!