Implementar patrones de diseño de programación de juegos comunes en tu proyecto de Unity puede ayudarte a construir y mantener de manera eficiente una base de código limpia, organizada y legible. Los patrones de diseño reducen el tiempo de refactorización y prueba, aceleran los procesos de desarrollo y contribuyen a una base sólida para hacer crecer su juego, equipo y negocio.
Piense en los patrones de diseño no como soluciones terminadas que puede copiar y pegar en su código, sino como herramientas adicionales que pueden ayudarle a crear aplicaciones más grandes y escalables.
Esta página explica el patrón de diseño de comandos.
El contenido aquí se basa en el libro electrónico gratuito Sube de nivel tu código con patrones de programación de juegos.
Consulte más artículos en la serie de patrones de diseño de programación de juegos de Unity en el centro de mejores prácticas de Unity o a través de estos enlaces:
El patrón de diseño de programación de comandos es uno de la Banda de los Cuatro original y es útil siempre que desee realizar un seguimiento de una serie específica de acciones. Probablemente hayas visto el patrón de comando en funcionamiento si has jugado un juego que usa la funcionalidad deshacer/rehacer o mantiene tu historial de entrada en una lista. Imagine un juego de estrategia en el que el usuario puede planificar varios turnos antes de ejecutarlos. Ese es el patrón de comando.
El patrón de comando permite representar acciones como objetos. Encapsular acciones como objetos le permite crear un sistema flexible y extensible para controlar el comportamiento de GameObjects en respuesta a la entrada del usuario. Esto funciona encapsulando una o más llamadas a métodos como un "objeto de comando" en lugar de invocar un método directamente. Luego puedes almacenar estos objetos de comando en una colección, como una cola o una pila, que funciona como un pequeño búfer.
Almacenar objetos de comando de esta manera le permite controlar el tiempo de su ejecución retrasando potencialmente una serie de acciones para su posterior reproducción. De manera similar, puede rehacerlos o deshacerlos y agregar flexibilidad adicional para controlar la ejecución de cada objeto de comando.
A continuación se muestran algunas aplicaciones comunes del patrón en diferentes géneros de juegos:
- En un juego de estrategia en tiempo real, el patrón de comando podría usarse para poner en cola acciones de unidades y edificios. Luego, el juego ejecutaría cada comando a medida que los recursos estuvieran disponibles.
- En un juego de estrategia por turnos, el jugador puede seleccionar una unidad y luego almacenar sus movimientos o acciones en una cola u otra colección. Al final del turno, el juego ejecutaría todos los comandos en la cola del jugador.
- En un juego de rompecabezas, el patrón de comando podría permitir al jugador deshacer y rehacer acciones.
- En un juego de lucha, leer las pulsaciones de botones o los movimientos del gamepad en una lista de comandos específica podría resultar en combos y movimientos especiales.
Pruebe el proyecto de muestra en GitHub que demuestra diferentes patrones de diseño de programación en el contexto del desarrollo de juegos, incluido el patrón de comando.
En este ejemplo, el jugador puede moverse por un laberinto haciendo clic en los botones del lado izquierdo. A medida que tu jugador se mueve, puedes ver un rastro de movimiento. Pero lo más importante es que puedes deshacer y rehacer tus acciones anteriores.
Para encontrar la escena correspondiente en el proyecto, vaya a la carpeta llamada "9 Command".
Para implementar el patrón de comando, necesitará un objeto general que contendrá su acción. Este objeto de comando contendrá qué lógica realizar y cómo deshacerla.
Hay varias formas de implementar esto, pero aquí hay una versión simple que usa una interfaz llamada ICommand:
interfaz pública ICommand
{
void Execute();
void Undo();
}
En este caso, cada acción del juego aplicará la interfaz ICommand (también puedes implementar esto con una clase abstracta).
Cada objeto de comando será responsable de sus propios métodos Ejecutar y Deshacer . Por lo tanto, agregar más comandos a tu juego no afectará a los existentes.
La clase CommandInvoker es entonces responsable de ejecutar y deshacer comandos. Además de los métodos ExecuteCommand y UndoCommand , tiene una pila de deshacer para contener la secuencia de objetos de comando.
En el proyecto de muestra, puedes mover a tu jugador por un pequeño laberinto. Una opción sencilla para cambiar la posición del jugador es crear un PlayerMover.
Para hacer esto, necesitarás pasar un Vector3 al método Move para guiar al jugador a lo largo de las cuatro direcciones de la brújula. También puede utilizar un raycast para detectar las paredes en la LayerMask adecuada. Por supuesto, implementar lo que desea aplicar al patrón de comando es independiente del patrón en sí.
Para seguir el patrón del comando, capture el método Move de PlayerMover como un objeto. En lugar de llamar a Move directamente, cree una nueva clase, MoveCommand, que implemente la interfaz ICommand .
clase pública MoveCommand: Yo ordeno
{
jugadorMover jugadorMover;
movimiento Vector3;
MoveCommand público (reproductor PlayerMover, Vector3 moveVector)
{
this.playerMover = player;
this.movement = moveVector;
}
Ejecución pública vacía()
{
playerMover.Move(movement);
}
vacío público Deshacer()
{
playerMover.Move(-movement);
}
}
Cualquier lógica que quieras lograr va aquí, así que invoca Move con el vector de movimiento.
ICommand también necesita un método Deshacer para restaurar la escena a su estado anterior. En este caso, la lógica Deshacer resta el vector de movimiento, esencialmente empujando al jugador en la dirección opuesta.
MoveCommand almacena cualquier parámetro que necesite ejecutar. Configúrelos con un constructor. En este caso, guarda el componente PlayerMover apropiado y el vector de movimiento.
Una vez que cree el objeto de comando y guarde los parámetros necesarios, use los métodos estáticos ExecuteCommand y UndoCommand de CommandInvoker para pasar su MoveCommand. Esto ejecuta Ejecutar o Deshacer de MoveCommand y rastrea el objeto de comando en la pila de deshacer.
El InputManager no llama directamente al método Move de PlayerMover . En su lugar, agregue un método adicional, RunMoveCommand, para crear un nuevo MoveCommand y enviarlo a CommandInvoker.
Luego, configure los diversos eventos onClick de los botones de la interfaz de usuario para llamar a RunPlayerCommand con los cuatro vectores de movimiento.
Consulte el proyecto de muestra para obtener detalles de implementación de InputManager. También puedes configurar tu propia entrada usando el teclado o el gamepad. Tu jugador ahora puede navegar por el laberinto. Haga clic en el botón Deshacer para poder retroceder hasta el cuadro inicial.
Implementar la rejugabilidad o la deshacer es tan simple como generar una colección de objetos de comando. También puede utilizar el búfer de comandos para reproducir acciones en secuencia con controles específicos.
Por ejemplo, piense en un juego de lucha en el que una serie de clics en un botón específico desencadena un movimiento o ataque combinado. Almacenar las acciones del jugador con el patrón de comando hace que configurar estos combos sea mucho más sencillo.
Por otro lado, el patrón de comando introduce más estructura, al igual que los otros patrones de diseño. Tendrá que decidir dónde estas clases e interfaces adicionales brindan suficientes beneficios para implementar objetos de comando en su aplicación.
Una vez que aprenda los conceptos básicos, podrá modificar la sincronización de los comandos y reproducirlos en sucesión o en reversa, según el contexto.
Considere lo siguiente al incorporar el patrón de comando:
- Crea más comandos: El proyecto de muestra solo incluye un tipo de objeto de comando, MoveCommand. Puede crear cualquier cantidad de objetos de comando que implementen ICommand y realizar un seguimiento de ellos mediante CommandInvoker.
- Agregar la funcionalidad de rehacer es cuestión de agregar otra pila: Cuando deshace un objeto de comando, empújelo a una pila separada que rastrea las operaciones de rehacer. De esta manera, puede recorrer rápidamente el historial de deshacer o rehacer esas acciones. Borre la pila de rehacer cuando el usuario invoque un movimiento completamente nuevo (puede encontrar una implementación en el proyecto de muestra).
- Utilice una colección diferente para su búfer de objetos de comando: Una cola puede ser más útil si desea un comportamiento de primero en entrar, primero en salir (FIFO). Si utiliza una lista, realice un seguimiento del índice actualmente activo; Los comandos antes del índice activo se pueden deshacer. Los comandos después del índice se pueden rehacer.
- Limite el tamaño de las pilas: Las operaciones de deshacer y rehacer pueden salirse de control rápidamente. Limite las pilas al menor número de comandos.
- Pase los parámetros necesarios al constructor: Esto ayuda a encapsular la lógica como se ve en el ejemplo de MoveCommand.
- CommandInvoker, al igual que otros objetos externos, no ve el funcionamiento interno del objeto de comando, solo invoca Ejecutar o Deshacer. Proporcione al objeto de comando todos los datos necesarios para funcionar cuando llame al constructor.
Encuentre más consejos sobre cómo utilizar patrones de diseño en sus aplicaciones de Unity, así como los principios SOLID, en el libro electrónico gratuito Suba de nivel su código con patrones de programación de juegos.
Puede encontrar todos los libros electrónicos y artículos técnicos avanzados de Unity en el centro de mejores prácticas . Los libros electrónicos también están disponibles en la página de mejores prácticas avanzadas en la documentación.