Object HashCode
Unity의 오브젝트를 비교할 때, HashCode를 통해 비교가 진행된다.
GetHashCode를 통해 오브젝트의 InstanceID를 Hash로 변환하여 비교하는데 해당 함수를 override 하면 비교기준을 바꿀 수 있다.
public override int GetHashCode() => this.m_InstanceID;
private static bool CompareBaseObjects(Object lhs, Object rhs)
{
bool flag1 = (object) lhs == null;
bool flag2 = (object) rhs == null;
if (flag2 & flag1)
return true;
if (flag2)
return !Object.IsNativeObjectAlive(lhs);
return flag1 ? !Object.IsNativeObjectAlive(rhs) : lhs.m_InstanceID == rhs.m_InstanceID;
}
State Pattern
스테이트 패턴은 객체가 다양한 상태를 가지며 상태에 따라 동작이 달라지게 만드는 패턴이다.
게임에서 흔히 Idle, Walk, Jump 등의 동작을 캐릭터가 수행하며 해당 동작이 수행될 때마다 애니메이션이나 다른 설정값들이 변경될 수 있으며 동작이 달라질 수 있다.
State Pattern을 사용한 예로는 FSM이 있다.
FSM은 유한 상태 기계라는 뜻으로 유한한 개수의 상태를 가지며 각 상태간의 전이를 통해 논리를 구현한다.
FSM은 주로 애니메이션, AI 등에서 사용된다.
이를 구현한 예로, 범용적인 StateMachine을 만들어 보자.
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class StateMachine : MonoBehaviour
{
[SerializeField] private String defaultState;
private IState currentState;
private Dictionary<Type, IState> states = new Dictionary<Type, IState>();
public void Run()
{
var blackBoard = GetComponent<BaseBB>();
GetComponents<IState>().ToList().ForEach(state => AddState(state, blackBoard));
ChangeState(Type.GetType(defaultState));
}
private void AddState<T>(T state, BaseBB blackBoard) where T : IState
{
state.FSM = this;
states.Add(state.GetType(), state);
state.InitState(blackBoard);
}
public void ChangeState<T>() where T : IState
{
ChangeState(typeof(T));
}
private void ChangeState(Type stateType)
{
currentState?.ExitState();
if (!states.TryGetValue(stateType, out currentState)) return;
currentState?.EnterState();
}
public void UpdateState()
{
currentState?.UpdateState(Time.deltaTime);
}
}
StateMachine은 State들을 관리하는 주체인 컴포넌트이다.
StateMachine은 직접 행동을 구현하는 것이 아니라 State간의 전이만을 담당한다.
State에서 필요한 동작은 State에서 직접 구현하면 된다.
즉, 동작을 호출하는 객체와 동작을 수행하는 객체를 나눌 수 있게 된다.
다음은 State중 하나인 Idle 상태일 때를 구현한 클래스이다.
using System;
using UnityEngine;
using UnityEngine.InputSystem;
public class IdleState : MonoBehaviour, IState
{
public StateMachine FSM { get; set; }
private BBCharacterMove BB { get; set; }
public void InitState(BaseBB blackBoard)
{
BB = blackBoard as BBCharacterMove;
}
public void EnterState()
{
BB.animator.CrossFade("Idle", 0.1f);
BB.animator.SetFloat(BB.Speed, 0.0f);
}
public void UpdateState(float deltaTime)
{
if (BB.jumpInput.triggered && BB.rb.velocity.y == 0)
{
FSM.ChangeState<JumpState>();
return;
}
var value = BB.moveInput.ReadValue<Vector2>();
if (0 < value.sqrMagnitude)
{
FSM.ChangeState<WalkState>();
}
}
public void ExitState()
{
}
}
State가 변경되었을 때만 동작하도록 만들 수 있지만 더욱 세부적인 동작을 위해 3단계로 나누었다.
- EnterState: 상태가 전이된 상태
- UpdateState: 상태가 전이된 이후 업데이트
- ExitState: 상태를 종료하고 다음 상태로 전이할 준비
IdleState로 전이되어 EnterState가 실행되면 이동을 종료하고 "Idle" 애니메이션을 실행한다.
현재 상태가 IdleState인 상태에서는 UpdateState가 실행되어 Jump, Walk로 전이할 것인지 판단한다.
만약, 입력이 일어나 상태 전이가 필요하다면 ExitState를 실행하여 정리할 수 있지만 현재는 별다른 작업이 없어 비어둔 상태이다.
확장 메서드
C#에서는 클래스 외부의 함수를 클래스 내부의 함수인 것처럼 확장하는 것이 가능하다.
다음은 그 예시이다.
public static class IntegerExtension
{
public static int Power(this int myInt, int exponent)
{
int result = myInt;
for(int i = 1; i < exponent; i++)
{
result *= myInt;
}
return result;
}
}
int num = 3;
Console.WriteLine(num.Power(2));
Console.WriteLine(num.Power(3));
num이라는 정수형 변수에는 Power라는 함수가 없다.
하지만, IntegerExtension클래스에서 확장 메서드를 통해 정수형 변수에 Power라는 변수를 추가했다.
Power함수를 보면 첫 번째 매개변수로 this int myInt라는 매개변수를 받았다.
여기서 this는 어떠한 클래스에 확장을 할것인지 지정하는 키워드라고 생각하면 된다.
그렇다면 이러한 확장 메서드가 왜 사용되는가 생각해보자.
우선, 기본 자료형에 대해 확장 메서드를 구현해 놓으면 해당 메서드를 실행하는 다른 객체가 없더라도 해당 자료형에서 바로 사용할 수 있다.
예를 들어, 위와 같은 제곱한 결과를 반환하는 함수를 사용하려면 해당 함수를 포함한 객체가 존재해야 하며 static으로 선언한다 하더라도 클래스를 선언해야 하는 것은 여전하다.
또한, 제곱과 같은 간다한 기능을 수행하기 위해 이를 지원하는 클래스를 찾아 함수를 호출하는 것은 귀찮을 것이다.
따라서, 이러한 작업을 간단하게 만들 수 있다.
또한, 사용자가 직접 정의하지 않은 라이브러리나 외부 모듈을 사용한다고 했을 때, 기능을 추가하고 싶을 수 있다.
이럴 때, 확장 메서드를 통해 해당 클래스에 추가적으로 기능을 만들 수 있다.