12/08/2018, 14:09

Command pattern trong Unity với C#

Chào các bạn, mình là Huy. Tên thường gọi mình là Huy trố. Hôm nay mình giới thiệu với các bạn đôi chút về Command Pattern. In object-oriented programming, the command pattern is a behavioral design pattern in which an object is used to encapsulate all information needed to perform an action ...

Chào các bạn, mình là Huy. Tên thường gọi mình là Huy trố. Hôm nay mình giới thiệu với các bạn đôi chút về Command Pattern.

In object-oriented programming, the command pattern is a behavioral design pattern in which an object is used to encapsulate all information needed to perform an action or trigger an event at a later time. This information includes the method name, the object that owns the method and values for the method parameters.(Trích từ wiki)

Một ví dụ trong Unity, bạn có thể viết một function đơn giản như này:

if (Input.GetKeyDown(KeyCode.A))
{
	MoveA();
}

Bạn dự định sẽ làm gì nếu người sử dụng muốn move với nút U chứ không phải là A? Để làm được điều này chúng ta phải tìm cách để thay MoveA(); bằng một cái gì đó chung chung hơn. Và đó là lý do tại sao chúng ta sử dụng command pattern. Như vậy, bắt đầu chúng ta sẽ định nghĩa một class base hoặc parent. Đặt luôn tên cho nó là Command:

public abstract class Command
{
	public abstract void Execute();
}

Và sau đó, MoveA sẽ thừa kế từ base class đó. Chúng ta cũng cần một class, nó sẽ không làm gì cả, vì vậy chúng ta có thể chuyển sang nút U từ nút A khi move.

public class MoveObject : Command
{
	public override void Execute()
	{
		MoveA();
	}
}
public class DoNothing : Command
{
	public override void Execute()
	{
	}
}

Trước đó chúng ta đã có code chỉ move khi mà nhấn A, giờ chúng ta có thể thay thế MoveA(); ngay lập tức với Execute(); khi chúng ta nhấn nút U.

Command btnU = new MoveObject();
Command btnA = new DoNothing();

if (Input.GetKeyDown(KeyCode.U))
{
	btnU.Execute();
}
if (Input.GetKeyDown(KeyCode.A))
{
	btnA.Execute();
}

Ngoài ra thì bạn cũng có thể tạo ra chức năng undo nhờ vào việc đưa các command đã action vào một cái stack, sau mỗi lần undo chỉ việc xem xét lại cái command được lấy ra.

Ý tưởng của ví dụ này là bạn có một đối tượng là cái box. Bạn di chuyển nó bằng các phím AWDS. Phím Z để undo di chuyển và R là để replay lại các di chuyển từ đầu.

Cái mà bạn cần là một scene với một box và một empty object. Thêm script đặt tên là InputHandler.cs vào empty object và attach box object vào script/

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

namespace CommandPattern
{
	public class InputHandler : MonoBehaviour
	{

		public Transform box;
		private Command btnW, btnS, btnA, btnD, btnB, btnZ, btnR;
		public static List<Command> oldCmds = new List<Command> ();
		private Vector3 boxPosStart;
		private Coroutine replayCor;
		public static bool startReplay;
		private bool replaying;

		void Start ()
		{
			btnB = new DoNothing ();
			btnW = new MoveForward ();
			btnS = new MoveReverse ();
			btnA = new MoveLeft ();
			btnD = new MoveRight ();
			btnZ = new UndoCommand ();
			btnR = new ReplayCommand ();

			boxPosStart = box.position;
		}

		void Update ()
		{
			if (!replaying) {
				HandleInput ();
			}

			StartReplay ();
		}

		//Check if we press a key, if so do what the key is binded to
		public void HandleInput ()
		{
			if (Input.GetKeyDown (KeyCode.A)) {
				btnA.ExeCmd (box, btnA);
			} else if (Input.GetKeyDown (KeyCode.B)) {
				btnB.ExeCmd (box, btnB);
			} else if (Input.GetKeyDown (KeyCode.D)) {
				btnD.ExeCmd (box, btnD);
			} else if (Input.GetKeyDown (KeyCode.R)) {
				btnR.ExeCmd (box, btnZ);
			} else if (Input.GetKeyDown (KeyCode.S)) {
				btnS.ExeCmd (box, btnS);
			} else if (Input.GetKeyDown (KeyCode.W)) {
				btnW.ExeCmd (box, btnW);
			} else if (Input.GetKeyDown (KeyCode.Z)) {
				btnZ.ExeCmd (box, btnZ);
			}
		}

		void StartReplay ()
		{
			if (startReplay && oldCmds.Count > 0) {
				startReplay = false;

				if (replayCor != null) {
					StopCoroutine (replayCor);
				}

				replayCor = StartCoroutine (ReplayCommands (box));
			}
		}

		IEnumerator ReplayCommands (Transform boxTrans)
		{
			replaying = true;

			boxTrans.position = boxPosStart;

			for (int i = 0; i < oldCmds.Count; i++) {
				oldCmds [i].MoveCmd (boxTrans);

				yield return new WaitForSeconds (0.3f);
			}

			replaying = false;
		}
	}
}

Dưới đây là CommandPattern.cs

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

namespace CommandPattern
{
	//Lop cha
	public abstract class Command
	{
		//Box se di chuyen khoang cach bao xa
		protected float _moveDistance = 1f;

		//Di chuyen va co the se save
		public abstract void ExeCmd(Transform box, Command command);

		//Undo Comand cu
		public virtual void UndoCmd(Transform box) { }

		//Move box
		public virtual void MoveCmd(Transform box) { }
	}

	//
	// Child classes
	//

	public class MoveForward : Command
	{
		//Goij khi nhan 1 key
		public override void ExeCmd(Transform box, Command command)
		{
			//Move Box nay
			MoveCmd(box);

			//Save command
			InputHandler.oldCmds.Add(command);
		}

		//Undo cu command
		public override void UndoCmd(Transform box)
		{
			box.Translate(-box.forward * _moveDistance);
		}

		//Move box
		public override void MoveCmd(Transform box)
		{
			box.Translate(box.forward * _moveDistance);
		}
	}

	public class MoveReverse : Command
	{
		public override void ExeCmd(Transform box, Command command)
		{

			MoveCmd(box);

			InputHandler.oldCmds.Add(command);
		}

		public override void UndoCmd(Transform boxTrans)
		{
			boxTrans.Translate(boxTrans.forward * _moveDistance);
		}

		public override void MoveCmd(Transform boxTrans)
		{
			boxTrans.Translate(-boxTrans.forward * _moveDistance);
		}
	}

	public class MoveLeft : Command
	{
		public override void ExeCmd(Transform boxTrans, Command command)
		{
			MoveCmd(boxTrans);

			InputHandler.oldCmds.Add(command);
		}

		public override void UndoCmd(Transform boxTrans)
		{
			boxTrans.Translate(boxTrans.right * _moveDistance);
		}

		public override void MoveCmd(Transform boxTrans)
		{
			boxTrans.Translate(-boxTrans.right * _moveDistance);
		}
	}

	public class MoveRight : Command
	{
		public override void ExeCmd(Transform boxTrans, Command command)
		{
			MoveCmd(boxTrans);

			InputHandler.oldCmds.Add(command);
		}

		public override void UndoCmd(Transform boxTrans)
		{
			boxTrans.Translate(-boxTrans.right * _moveDistance);
		}

		public override void MoveCmd(Transform boxTrans)
		{
			boxTrans.Translate(boxTrans.right * _moveDistance);
		}
	}

	public class DoNothing : Command
	{
		public override void ExeCmd(Transform boxTrans, Command command)
		{
		}
	}

	public class UndoCommand : Command
	{
		public override void ExeCmd(Transform boxTrans, Command command)
		{
			List<Command> oldCommands = InputHandler.oldCmds;

			if (oldCommands.Count > 0)
			{
				Command latestCommand = oldCommands[oldCommands.Count - 1];
				latestCommand.UndoCmd(boxTrans);
				oldCommands.RemoveAt(oldCommands.Count - 1);
			}
		}
	}

	public class ReplayCommand : Command
	{
		public override void ExeCmd(Transform boxTrans, Command command)
		{
			InputHandler.startReplay = true;
		}
	}
}

Link demo đây, down về dùng thử nhé: https://drive.google.com/drive/folders/0B7td9WR1ZtQ0SzFiaDZvb2VWcFE?usp=sharing

0