커맨드패턴 시연영상
시연영상은 아래 코드해석해보고 정말 모르겠을때 보기. 영상보면 시간이 너무 오래걸린다
어떻게 사용하는지 최소 구현코드로 작성.
플레이어 하나씩만 이동시킬수있는데 인보커를 갈아끼면 특정 캐릭터마다 조종권한을 얻어서 다른 캐릭터들을 움직일 수 있는 코드 작성했음
1. PlayerCtrlBasic.cs
using UnityEngine;
//커맨드 패턴중 Receiver에 해당
[RequireComponent(typeof(Rigidbody))]
public class PlayerCtrlBasic : MonoBehaviour
{
Rigidbody rb;
Animator animator;
Vector3 moveDir;
private void Start()
{
rb = GetComponent<Rigidbody>();
animator = GetComponent<Animator>();
}
private void Update()
{
if (moveDir != Vector3.zero)
{
transform.rotation = Quaternion.LookRotation(moveDir);
transform.Translate(Vector3.forward * Time.deltaTime * 4f);
}
}
//문제점: 이 캐릭터를 AI가 조종해야 한다면?(CC기 걸리거나 컷씬이 나와서 이 캐릭터가 공격 모션을 해야 한다면?
//네트워크에서 다른 사람이 이 캐릭터를 조종하게 하려면? 아니면 클릭한 캐릭터를 조종하고싶다면?
//이런 문제들 때문에 커맨드 패턴으로 "명령을 받는 형태"로 만들거임
public void Move(Vector3 dir)
{
moveDir = dir;
animator.SetFloat("MoveFloat", dir.magnitude);
}
public void Attack()
{
animator.SetTrigger("AttackTrigger");
}
public void Jump()
{
rb.AddForce(Vector3.up * 5f, ForceMode.Impulse);
}
}
using UnityEngine;
//커맨트패턴중 Command에 해당
//기본 커맨드 패턴 정리할것이므로 기본코드에는 ExecuteReplay빼고 정리하기
public interface ICommandBasic
{
void Execute();
}
public class MoveCommandBasic : ICommandBasic
{
PlayerCtrlBasic player; //명령 수행 대상
Vector3 direction; //명령 수행에 필요한 정보
public MoveCommandBasic(PlayerCtrlBasic plr, Vector3 dir)
{
player = plr;
direction = dir;
}
public void Execute()
{
player.Move(direction);
}
}
public class JumpCommandBasic : ICommandBasic
{
PlayerCtrlBasic player;
public JumpCommandBasic(PlayerCtrlBasic plr)
{
player = plr;
}
public void Execute()
{
player.Jump();
}
}
public class AttackCommandBasic : ICommandBasic
{
PlayerCtrlBasic player;
public AttackCommandBasic(PlayerCtrlBasic plr)
{
player = plr;
}
public void Execute()
{
player.Attack();
}
}
using UnityEngine;
using UnityEngine.InputSystem;
//커맨드 패턴중 Invoker. 키입력 등 커맨드를 찍어낼 곳
public class InputHandlerBasic : MonoBehaviour
{
[SerializeField] PlayerCtrlBasic player; //제어를 할 캐릭터 하나
private void Awake()
{
InputSystem.actions["Move"].performed += OnMove;
InputSystem.actions["Move"].canceled += OnMove;
InputSystem.actions["Attack"].performed += OnAttack;
InputSystem.actions["Jump"].performed += OnJump;
}
private void OnDestroy()
{
InputSystem.actions["Move"].performed -= OnMove;
InputSystem.actions["Move"].canceled -= OnMove;
InputSystem.actions["Attack"].performed -= OnAttack;
InputSystem.actions["Jump"].performed -= OnJump;
}
private void OnMove(InputAction.CallbackContext ctx)
{
Vector2 input = ctx.ReadValue<Vector2>();
Vector3 dir = new Vector3(input.x, 0, input.y);
//커맨드 패턴을 가져옴 (아래 3줄 주석된거 record개념 추가전에 있던 코드, 커맨드패턴)
ICommandBasic moveCommand = new MoveCommandBasic(player, dir);
moveCommand.Execute();
}
private void OnAttack(InputAction.CallbackContext ctx)
{
if (!ctx.performed) return;
ICommandBasic attackCommand = new AttackCommandBasic(player);
attackCommand.Execute();
}
private void OnJump(InputAction.CallbackContext ctx)
{
if (!ctx.performed) return;
ICommandBasic jumpCommand = new JumpCommandBasic(player);
jumpCommand.Execute();
}
}
응용(TimeStamp 이용해서 record기능을 추가한 코드도 만들자)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
class TimedCommad
{
public float time;
public ICommandBasic command;
}
//커맨드 패턴중 Invoker. 키입력 등 커맨드를 찍어낼 곳
public class InputHandler : MonoBehaviour
{
[SerializeField] PlayerCtrlBasic player; //제어를 할 캐릭터 하나
//timeStamp
float _recordStartTime = 0f;
Vector3 _initPosition;
List<float> timeList = new List<float>();
//실제 TimeStamp관련 사용할거라면 아래처럼
bool isRecording = false;
List<TimedCommad> commandRecord = new List<TimedCommad>();
private void Awake()
{
InputSystem.actions["Move"].performed += OnMove;
InputSystem.actions["Move"].canceled += OnMove;
InputSystem.actions["Attack"].performed += OnAttack;
InputSystem.actions["Jump"].performed += OnJump;
}
private void OnDestroy()
{
InputSystem.actions["Move"].performed -= OnMove;
InputSystem.actions["Move"].canceled -= OnMove;
InputSystem.actions["Attack"].performed -= OnAttack;
InputSystem.actions["Jump"].performed -= OnJump;
}
private void OnMove(InputAction.CallbackContext ctx)
{
Vector2 input = ctx.ReadValue<Vector2>();
Vector3 dir = new Vector3(input.x, 0, input.y);
//커맨드 패턴을 가져옴 (아래 3줄 주석된거 record개념 추가전에 있던 코드, 커맨드패턴)
ICommandBasic moveCommand = new MoveCommandBasic(player, dir);
moveCommand.Execute();
//기록
if(isRecording)
{
TimedCommad timedCommad = new TimedCommad();
timedCommad.time = Time.time;
timedCommad.command = moveCommand;
commandRecord.Add(timedCommad);
}
}
private void OnAttack(InputAction.CallbackContext ctx)
{
if (!ctx.performed) return;
ICommandBasic attackCommand = new AttackCommandBasic(player);
attackCommand.Execute();
//기록
if (isRecording)
{
TimedCommad timedCommad = new TimedCommad();
timedCommad.time = Time.time;
timedCommad.command = attackCommand;
commandRecord.Add(timedCommad);
}
}
private void OnJump(InputAction.CallbackContext ctx)
{
if (!ctx.performed) return;
ICommandBasic jumpCommand = new JumpCommandBasic(player);
jumpCommand.Execute();
//기록
if (isRecording)
{
TimedCommad timedCommad = new TimedCommad();
timedCommad.time = Time.time;
timedCommad.command = jumpCommand;
commandRecord.Add(timedCommad);
}
}
//시작 버튼 클릭하면 시간과 위치 기록(버튼으로 등록)
public void StartRecordingUseTimesTamp()
{
isRecording = true;
_recordStartTime = Time.time;
_initPosition = player.transform.position;
Debug.Log(_recordStartTime);
}
//Stop
public void StopRecordingUseTimesTamp()
{
isRecording = false;
}
//재생버튼. 재생하면 코루틴으로 시간에 따라 리플레이처럼 실행한다
public void PlayRecordionUseTimesTamp()
{
isRecording = false;
StartCoroutine(playerRecordCor());
}
IEnumerator playerRecordCor()
{
player.transform.position = _initPosition;
//실행시킨 시간
for(int i=0; i< commandRecord.Count; i++)
{
float delayTime = 0;
if (i == 0) delayTime = commandRecord[i].time;
else delayTime = commandRecord[i].time - commandRecord[i - 1].time;
yield return new WaitForSeconds(delayTime);
//기다린후 실행
Debug.Log("동작 실행");
commandRecord[i].command.Execute();
}
}
}