Cómo construir un JRPG: una guía para desarrolladores de juegos
() translation by (you can also view the original English article)
Este artículo es una descripción general de alto nivel para crear un JRPG (Juego de rol japonés) como los primeros juegos de Final Fantasy. Veremos la arquitectura y los sistemas que conforman el esqueleto de un JRPG, cómo administrar los modos de juego, cómo usar mapas de mosaico para mostrar el mundo y cómo codificar un sistema de combate RPG.
Nota: Este artículo está escrito usando un lenguaje pseudocódigo similar a Java, pero los conceptos son aplicables a cualquier entorno de desarrollo de juegos.
Contenido
- El improbable lugar de nacimiento de los JRPG
- La charla de genero
- Cinco razones por las que debe hacer un JRPG
- Arquitectura
- Administrar el estado del juego
- Mapas
- Combate
- Revisión
El improbable lugar de nacimiento de los JRPG

En 1983, Yuji Horii, Koichi Nakamura y Yukinobu Chida volaron a Estados Unidos y asistieron a AppleFest '83, una reunión de desarrolladores mostrando sus últimas creaciones para Apple II. Quedaron impresionados por la última versión de un juego de rol llamado Wizardry.
Al regresar a Japón, decidieron crear Dragon Warrior, un juego de rol similar pero simplificado para NES. Fue un éxito masivo, definiendo el género JRPG. A Dragon Warrior no le fue tan bien en Estados Unidos, pero unos años más tarde otro juego lo hizo.
En 1987, se lanzó el Final Fantasy original, generando una de las franquicias de videojuegos más vendidas en la Tierra que se convirtió, al menos en Occidente, en el icónico JRPG.
La charla de genero
Los géneros de juego nunca se definen con precisión: son más una colección borrosa de convenciones. Los juegos de rol tienden a tener un sistema de nivelación, uno o varios personajes de jugadores con habilidades y estadísticas, armas y armaduras, modos de combate y exploración y narrativas fuertes; el progreso del juego a menudo se logra avanzando a través de un mapa.
Los juegos de rol japoneses son juegos de rol creados con el molde de Dragon Warrior; son más lineales, el combate a menudo está basado en turnos y generalmente hay dos tipos de mapas: un mapa mundial y un mapa local. Los JRPG arquetípicos incluyen Dragon Warrior, Final Fantasy, Wild Arms, Phantasy Star y Chrono Trigger. El tipo de JRPG del que vamos a hablar en este artículo es similar al de Final Fantasy.



Cinco razones por las que debe hacer un JRPG
1. Han superado la prueba del tiempo
Juegos como Final Fantasy VI y Chrono Trigger son muy divertidos de jugar. Si haces un JRPG, estás aprendiendo un formato de juego atemporal que los jugadores modernos aún son muy receptivos. Constituyen un gran marco para agregar su propio giro y experimentar, ya sea en la narrativa, la presentación o la mecánica. ¡Es genial si puedes hacer un juego que aún se juega y disfruta décadas después de su primer lanzamiento!
2. La mecánica del juego es ampliamente aplicable
Call of Duty, uno de los juegos de FPS más populares del mundo, utiliza elementos de rol; el auge de los juegos sociales en torno a FarmVille fue básicamente un clon de SNES RPG Harvest Moon; e incluso los juegos de carreras como Gran Turismo tienen niveles y experiencia.
3. Restricciones Fomentar la Creatividad
De la misma manera que un escritor puede sentirse intimidado por una hoja de papel en blanco, un desarrollador de juegos puede verse paralizado por la gran cantidad de opciones posibles al diseñar un nuevo juego. Con un JRPG se han decidido muchas opciones para usted, por lo que no tiene esa parálisis de elección, puede seguir las convenciones para la mayoría de las decisiones y desviarse de las convenciones en los puntos que le interesan.
4. Es factible como un proyecto en solitario
¡Final Fantasy fue codificado casi por completo por un programador único, Nasir Gebelli, y lo estaba haciendo en conjunto! Con las herramientas y los idiomas modernos, es mucho más fácil crear este tipo de juegos. La mayor parte de la mayoría de los juegos de rol no es la programación sino el contenido, pero este no tiene que ser el caso de tu juego. Si vuelve a marcar un poco el contenido y se enfoca en la calidad sobre la cantidad, entonces un JRPG es un gran proyecto en solitario.
Tener un equipo puede ayudar con cualquier juego, y es posible que desee externalizar el arte y la música, o utilizar algunos de los excelentes recursos de Creative Commons de lugares como opengameart.org. (Nota del editor: nuestro sitio hermano GraphicRiver también vende hojas de sprites).
5. ¡Por el beneficio!
Los JRPG tienen un seguimiento dedicado y una serie de JRPG independientes (como los que se muestran a continuación) han tenido un buen rendimiento comercial y están disponibles en plataformas como Steam.



Arquitectura
Los JRPG comparten tantas convenciones y mecanismos que es posible dividir un JRPG típico en varios sistemas:

En el desarrollo de software, un patrón se ve una y otra vez: estratificación. Esto se refiere a la forma en que los sistemas de un programa se construyen uno encima del otro, con capas de aplicación amplia en la parte inferior y capas que tratan de manera más íntima el problema que se tiene cerca de la parte superior. Los JRPG no son diferentes y se pueden ver como un número de capas: las capas inferiores se ocupan de las funciones gráficas básicas y las capas superiores se ocupan de las misiones y las estadísticas de los personajes.
Como se puede ver en el diagrama de la arquitectura anterior, hay muchos sistemas que componen un JRPG pero la mayoría de los sistemas se pueden agrupar como modos separados del juego. Los JRPG tienen modos de juego muy distintos; tienen un mapa del mundo, mapa local, modo de combate y varios modos de menú. Estos modos son piezas de código completamente independientes y separadas, lo que hace que cada una sea fácil de desarrollar.
Los modos son importantes pero serían inútiles sin contenido del juego. Un RPG contiene muchos archivos de mapas, definiciones de monstruos, líneas de diálogo, scripts para ejecutar cutscenes y código de juego para controlar cómo progresa el jugador. Cubrir cómo construir un JRPG en detalle llenaría un libro completo, así que nos concentraremos en algunas de las partes más importantes. Manejar los modos de juego de forma limpia es fundamental para producir un JRPG manejable, por lo que es el primer sistema que exploraremos.
Administrar el estado del juego
La siguiente imagen muestra
el game loop juego bombeando, llamando a una función de actualización en
cada cuadro. Este es el latido del corazón del juego y casi todos los
juegos están estructurados de esta manera.



¿Alguna vez ha comenzado un proyecto pero se ha estancado porque le resultaba demasiado difícil agregar nuevas características o estaba plagado de errores misteriosos? Tal vez trataste de meter todo tu código en la función de actualización con poca estructura y descubriste que el código se convirtió en un enredo críptico. Una excelente solución para este tipo de problemas es separar el código en diferentes estados del juego, dando una visión mucho más clara de lo que está sucediendo.
Una herramienta común de gamedev es la máquina de estado; se usa en todas partes, para manejar animaciones, menús, flujo de juegos, IA ... es una herramienta esencial para tener en nuestro kit. Para el JRPG podemos usar una máquina de estado para manejar los diferentes modos de juego. Echaremos un vistazo a una máquina de estado normal y luego la mezclaremos un poco, para que sea más adecuada para el JRPG. Pero primero tomemos un poco de tiempo para considerar el flujo general del juego que se muestra a continuación.



En un JRPG típico, probablemente comiences en el modo de juego de mapas local, deambulen libremente por la ciudad e interactúen con sus habitantes. Desde la ciudad, puedes irte. Aquí ingresarás a un modo de juego diferente y verás el mapa mundial.
El mapa del mundo se parece mucho al mapa local, pero a una escala mayor; puedes ver montañas y pueblos, en lugar de árboles y vallas. Mientras estés en el mapa mundial, si vuelves a la ciudad, el modo volverá al mapa local.
En el mapa mundial o en el mapa local, puedes abrir un menú para ver a tus personajes y, a veces, en el mapa mundial serás lanzado al combate. El diagrama anterior describe estos modos de juego y transiciones; este es el flujo básico del juego JRPG y es de lo que crearemos nuestros estados de juego.
Manejo de la complejidad con una máquina de estado
Una máquina de estado, para nuestros propósitos, es una pieza de código que contiene todos los diversos modos de nuestros juegos, que nos permite pasar de un modo a otro, y que actualiza y representa el modo actual.
Dependiendo del lenguaje de implementación, una máquina de estado
por
lo general consiste en una clase StateMachine
y una interfaz, IState
,
que implementan todos los estados.
Una máquina de estado se describe mejor esbozando un sistema básico en pseudocódigo:
1 |
class StateMachine |
2 |
{
|
3 |
Map<String, IState> mStates = new Map<String, IState>(); |
4 |
IState mCurrentState = EmptyState; |
5 |
|
6 |
public void Update(float elapsedTime) |
7 |
{
|
8 |
mCurrentState.Update(elapsedTime); |
9 |
}
|
10 |
|
11 |
public void Render() |
12 |
{
|
13 |
mCurrentState.Render(); |
14 |
}
|
15 |
|
16 |
public void Change(String stateName, optional var params) |
17 |
{
|
18 |
mCurrentState.OnExit(); |
19 |
mCurrentState = mStates[stateName]; |
20 |
mCurrentState.OnEnter(params); |
21 |
}
|
22 |
|
23 |
public void Add(String name, IState state) |
24 |
{
|
25 |
mStates[name] = state; |
26 |
}
|
27 |
}
|
Este código muestra una máquina de estado simple sin verificación de errores.
Veamos cómo se usa el código de máquina de estado anterior en un juego. Al
comienzo del juego se creará una StateMachine
, se agregarán todos los
diferentes estados del juego y se establecerá el estado inicial. Cada
estado se identifica de manera única por un nombre String
que se
utiliza al llamar a la función de cambio de estado. Solo hay un estado
actual, mCurrentState
, y se representa y actualiza cada ciclo de
juego.
El código podría verse así:
1 |
StateMachine gGameMode = new StateMachine(); |
2 |
|
3 |
// A state for each game mode
|
4 |
gGameMode.Add("mainmenu", new MainMenuState(gGameMode)); |
5 |
gGameMode.Add("localmap", new LocalMapState(gGameMode)); |
6 |
gGameMode.Add("worldmap", new WorldMapState(gGameMode)); |
7 |
gGameMode.Add("battle", new BattleState(gGameMode)); |
8 |
gGameMode.Add("ingamemenu", new InGameMenuState(gGameMode)); |
9 |
|
10 |
gGameMode.Change("mainmenu"); |
11 |
|
12 |
// Main Game Update Loop
|
13 |
public void Update() |
14 |
{
|
15 |
float elapsedTime = GetElapsedFrameTime(); |
16 |
gGameMode.Update(elapsedTime); |
17 |
gGameMode.Render(); |
18 |
}
|
En el
ejemplo, creamos todos los estados requeridos, los agregamos a
StateMachine
y establecemos el estado inicial en el menú principal. Si ejecutamos este código, MainMenuState
se renderizará y actualizará
primero. Esto
representa el menú que ves en la mayoría de los juegos cuando arrancas
por primera vez, con opciones como Comenzar juego y Cargar juego.
Cuando
un usuario selecciona Iniciar juego, MainMenuState
llama a algo así
como gGameMode.Change("localmap", "map_001")
y el LocalMapState
se
convierte en el nuevo estado actual. Este estado luego actualizaría y
renderizaría el mapa, permitiendo al jugador comenzar a explorar el
juego.
El siguiente diagrama muestra una visualización de una máquina de
estado que se mueve entre WorldMapState
y BattleState
. En
un juego, esto equivaldría a un jugador deambulando por el mundo,
siendo atacado por monstruos, entrando en modo combate y luego volviendo
al mapa.



Echemos un vistazo rápido a la interfaz de estado y una clase EmptyState
que la implementa:
1 |
public interface IState |
2 |
{
|
3 |
public virtual void Update(float elapsedTime); |
4 |
public virtual void Render(); |
5 |
public virtual void OnEnter(); |
6 |
public virtual void OnExit(); |
7 |
}
|
8 |
|
9 |
public EmptyState : IState |
10 |
{
|
11 |
public void Update(float elapsedTime) |
12 |
{
|
13 |
// Nothing to update in the empty state.
|
14 |
}
|
15 |
|
16 |
public void Render() |
17 |
{
|
18 |
// Nothing to render in the empty state
|
19 |
}
|
20 |
|
21 |
public void OnEnter() |
22 |
{
|
23 |
// No action to take when the state is entered
|
24 |
}
|
25 |
|
26 |
public void OnExit() |
27 |
{
|
28 |
// No action to take when the state is exited
|
29 |
}
|
30 |
}
|
La interfaz IState
requiere que cada estado tenga cuatro métodos antes
de que pueda usarse como un estado en la máquina de estado: Update()
,
Render()
, OnEnter()
y OnExit()
.
Update()
y Render()
se llaman cada cuadro para el estado actualmente
activo; OnEnter()
y OnExit()
se invocan al cambiar de estado. Aparte
de eso, todo es bastante sencillo. Ahora que sabes esto puedes crear todo tipo de estados para todas las diferentes partes de tu juego.
Esa es la máquina de estado básica. Es útil para muchas situaciones,
pero cuando se trata de modos de juego podemos mejorarlo. Con
el sistema actual, el cambio de estado puede tener una gran sobrecarga:
a veces, cuando cambies a un BattleState
, querremos abandonar
WorldState
, correr la batalla y luego regresar al WorldState
en la
configuración exacta que tenía antes de la batalla. Este tipo de
operación puede ser torpe usando la máquina de estado estándar que hemos
descrito. Una mejor solución sería usar una pila de estados.
Hacer la lógica del juego más fácil con una pila de estado
Podemos cambiar la
máquina de estado estándar en una pila de estados, como se muestra en el
siguiente diagrama. Por ejemplo, MainMenuState
se empuja en la pila
primero, al comienzo del juego. Cuando comenzamos un nuevo juego,
LocalMapState
se ve empujado a eso. En este punto, MainMenuState
ya no
se procesa ni se actualiza, sino que está listo para que regresemos.
A
continuación, si comenzamos una batalla, el BattleState
se empuja
en la parte superior; cuando la batalla termina, se saca de la pila y
podemos reanudar en el mapa exactamente donde lo dejamos. Si morimos en
el juego, LocalMapState
aparece y regresamos a MainMenuState
.
El
siguiente diagrama le da una visualización de una pila de estado, que
muestra que InGameMenuState
se está presionando en la pila y luego se
quitó.



Ahora que tenemos una idea de cómo funciona la pila, veamos algunos códigos para implementarla:
1 |
public class StateStack |
2 |
{
|
3 |
Map<String, IState> mStates = new Map<String, IState>(); |
4 |
List<IState> mStack = List<IState>(); |
5 |
|
6 |
public void Update(float elapsedTime) |
7 |
{
|
8 |
IState top = mStack.Top() |
9 |
top.Update(elapsedTime) |
10 |
}
|
11 |
|
12 |
public void Render() |
13 |
{
|
14 |
IState top = mStack.Top() |
15 |
top.Render() |
16 |
}
|
17 |
|
18 |
public void Push(String name) |
19 |
{
|
20 |
IState state = mStates[name]; |
21 |
mStack.Push(state); |
22 |
}
|
23 |
|
24 |
public IState Pop() |
25 |
{
|
26 |
return mStack.Pop(); |
27 |
}
|
28 |
}
|
Este código de pila de estado anterior no tiene comprobación de errores y es bastante sencillo. Los estados se pueden insertar en la pila mediante la llamada Push()
y
se abren con una llamada Pop()
, y el estado en la parte superior de la
pila es el que se actualiza y se procesa.
El uso de un enfoque basado en la pila es bueno para los menús, y con una pequeña modificación también se puede usar para cuadros de diálogo y notificaciones. Si te sientes aventurero, puedes combinar ambos y tener una máquina de estado que también admita pilas.
El uso de StateMachine
,
StateStack
o alguna combinación de ambos crea una estructura excelente
para construir tu RPG.
Acciones siguientes:
- Implemente el código de máquina de estado en su lenguaje de programación favorito.
- Crea un
MenuMenuState
yGameState
heredando deIState
. - Establezca el estado del menú principal como el estado inicial.
- Haga que ambos estados rindan imágenes diferentes.
- Al presionar un botón, haga que el estado cambie del menú principal al estado del juego.
Mapas
Los mapas describen
el mundo; los desiertos, las naves espaciales y las junglas se pueden
representar usando un mapa de azulejos. Un mapa de mosaico es una forma
de usar un número limitado de imágenes pequeñas para crear una más
grande. El siguiente diagrama muestra cómo funciona:



El diagrama anterior tiene tres partes: la paleta de mosaicos, una visualización de cómo se construye el mapa de mosaicos y el mapa final representado en la pantalla.
La paleta de mosaicos es una colección de todos los mosaicos utilizados para crear un mapa. Cada mosaico en la paleta está identificado de manera única por un número entero. Por ejemplo, el azulejo número 1 es hierba; observe los lugares donde se usa en la visualización del mapa de azulejos.
Un mapa de mosaicos es solo una matriz de números, cada número relacionado con un mosaico en la paleta. Si quisiéramos hacer un mapa lleno de hierba, podríamos tener un gran conjunto lleno con el número 1, y cuando renderizáramos esos mosaicos, veríamos un mapa de césped compuesto de muchos mosaicos pequeños de hierba. La paleta de mosaicos generalmente se carga como una textura grande que contiene muchas fichas más pequeñas, pero cada entrada en la paleta podría ser fácilmente su propio archivo gráfico.
La razón por la que no hacemos esto es solo por simplicidad y eficiencia. Si tiene una matriz de enteros, ese es un bloque continuo de memoria. Si tiene una matriz de matrices, ese es un bloque de memoria para la primera matriz que contiene punteros, con cada puntero apuntando a una fila de mosaicos. Esta indirección puede ralentizar las cosas, y dado que estamos dibujando el mapa en cada fotograma, ¡cuanto más rápido, mejor!
Veamos un código para describir un mapa de mosaicos:
1 |
//
|
2 |
// Takes a texture map of multiple tiles and breaks it up into
|
3 |
// individual images of 32 x 32.
|
4 |
// The final array will look like:
|
5 |
// gTilePalette[1] = Image // Our first grass tile
|
6 |
// gTilePalette[2] = Image // Second grass tile variant
|
7 |
// ..
|
8 |
// gTilePalette[15] = Image // Rock and grass tile
|
9 |
//
|
10 |
Array gTilePalette = SliceTexture("grass_tiles.png", 32, 32) |
11 |
|
12 |
gMap1Width = 10 |
13 |
gMap1Height = 10 |
14 |
Array gMap1Layer1 = new Array() |
15 |
[
|
16 |
2, 2, 7, 3, 11, 11, 11, 12, 2, 2, |
17 |
1, 1, 10, 11, 11, 4, 11, 12, 2, 2, |
18 |
2, 1, 13, 5, 11, 11, 11, 4, 8, 2, |
19 |
1, 2, 1, 10, 11, 11, 11, 11, 11, 9, |
20 |
10, 11, 12, 13, 5, 11, 11, 11, 11, 4, |
21 |
13, 14, 15, 1, 10, 11, 11, 11, 11, 6, |
22 |
2, 2, 2, 2, 13, 14, 11, 11, 11, 11, |
23 |
2, 2, 2, 2, 2, 2, 11, 11, 11, 11, |
24 |
2, 2, 2, 2, 2, 2, 5, 11, 11, 11, |
25 |
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, |
26 |
];
|
Compare el código anterior con el diagrama y está bastante claro cómo se construye un mapa de azulejos a partir de una pequeña serie de mosaicos. Una vez que un mapa se describe así, podemos escribir una función de representación simple para dibujarlo en la pantalla. Los detalles exactos de la función cambiarán según la configuración de la vista y las funciones de dibujo. Nuestra función de renderización se muestra a continuación.
1 |
static int TilePixelSize = 32; |
2 |
|
3 |
// Draws a tilemap from the top left, at pixel position x, y
|
4 |
// x, y - the pixel position the map will be rendered from
|
5 |
// map - the map to render
|
6 |
// width - the width of the map in tiles
|
7 |
public void RenderMap(int x, int y, Array map, int mapWidth) |
8 |
{
|
9 |
// Start by indexing the top left most tile
|
10 |
int tileColumn = 1; |
11 |
int tileRow = 1; |
12 |
|
13 |
for(int i = 1; map.Count(); i++) |
14 |
{
|
15 |
// Minus 1 so that the first tile draws at 0, 0
|
16 |
int pixelPosX = x + (tileColumn - 1) * TilePixelSize; |
17 |
int pixelPosY = y + (tileRow - 1) * TilePixelSize; |
18 |
|
19 |
RenderImage(x, y, gTilePalette[gMap1Layer1[i]]); |
20 |
|
21 |
// Advance to the next tile
|
22 |
tileColumn += 1; |
23 |
if(tileColumn > mapWidth) |
24 |
{
|
25 |
tileColumn = 1; |
26 |
tileRow += 1; |
27 |
}
|
28 |
}
|
29 |
}
|
30 |
|
31 |
-- How it's used in the main update loop |
32 |
public void Update() |
33 |
{
|
34 |
// Actually draw a map on screen
|
35 |
RenderMap(0, 0, gMap1Layer1, gMap1Width) |
36 |
}
|
El mapa que hemos usado hasta ahora es bastante básico; la mayoría de los JRPG usarán varias capas de mapas de mosaico para crear escenas más interesantes. El siguiente diagrama muestra nuestro primer mapa, con tres capas más añadidas, lo que da como resultado un mapa mucho más agradable.



Como vimos anteriormente, cada mapa de mosaicos es solo una matriz de números y, por lo tanto, se puede hacer un mapa en capas completo a partir de una matriz de esas matrices. Por supuesto, renderizar el mapa de azulejos es realmente solo el primer paso para agregar exploración a tu juego; los mapas también necesitan información sobre colisión, soporte para entidades en movimiento e interactividad básica utilizando desencadenantes.
Un desencadenador es una pieza de código que solo se dispara cuando el jugador la "dispara" realizando alguna acción. Hay muchas acciones que un disparador puede reconocer. Por ejemplo, mover el personaje del jugador a un azulejo puede desencadenar una acción; esto sucede comúnmente cuando se mueve hacia una puerta de entrada, un teletransportador o la ficha de borde del mapa. Se pueden colocar desencadenantes en estas fichas para teletransportar el personaje a un mapa de interior, mapa del mundo o mapa local relacionado.
Otro desencadenador podría depender del botón "usar" que se está presionando. Por ejemplo, si el jugador sube a un letrero y presiona "usar", se dispara un disparador y aparece un cuadro de diálogo que muestra el texto del letrero. Los desencadenantes se usan en todas partes para ayudar a unir mapas y proporcionar interactividad.
Los JRPG a menudo tienen muchos mapas bastante detallados y complicados, por lo que te recomiendo que no intentes hacerlos a mano, es una idea mucho mejor usar un editor de mapas de niveles. Puede usar una de las excelentes soluciones gratis existentes o hacer las suyas propias. Si quieres probar una herramienta existente, entonces definitivamente recomiendo consultar Tiled, que es la herramienta que utilicé para crear estos mapas de ejemplo.
Acciones siguientes:
- Descargar Tiled.
- Consigue algunas fichas de opengameart.org.
- Crea un mapa y cárgalo en tu juego.
- Agrega un personaje de jugador.
- Mueve el personaje de un azulejo a otro.
- Haga que el personaje se mueva suavemente de un mosaico a otro.
- Agregue detección de colisión (puede usar una nueva capa para almacenar información de colisión).
- Agregue un disparador simple para intercambiar mapas.
- Agregue un disparador para leer los letreros: considere usar la pila de estado de la que hablamos anteriormente para mostrar el cuadro de diálogo.
- Haga un estado de menú principal con la opción "Comenzar juego" y un estado de mapa local y vincúlelos.
- Diseña algunos mapas, añade algunos PNJ, prueba una simple misión de búsqueda: ¡deja volar tu imaginación!
Combate
¡Finalmente, a la lucha! ¿De qué sirve un JRPG sin combate? El combate es donde muchos juegos eligen innovar, introduciendo nuevos sistemas de habilidades, nueva estructura de combate o diferentes sistemas de hechizos, hay bastante variación.
La mayoría de los sistemas de combate usan una estructura por turnos con solo un combatiente al que se le permite realizar una acción a la vez. Los primeros sistemas de batalla basados en turnos eran simples, con cada entidad dando un giro en orden: turno del jugador, turno del enemigo, turno del jugador, turno del enemigo, y así sucesivamente. Esto rápidamente dio paso a sistemas más intrincados que ofrecen más margen de maniobra para las tácticas y la estrategia.
Vamos a ver de cerca los sistemas de combate basados en tiempo activo, donde los combatientes no necesariamente obtienen el mismo número de turnos. Las entidades más rápidas pueden obtener más turnos y el tipo de acción tomada también afecta el tiempo que toma un turno. Por ejemplo, un guerrero que corta con una daga puede tardar 20 segundos, pero un mago que invoque a un monstruo puede tardar dos minutos.



La captura de pantalla anterior muestra el modo de combate en un JRPG típico. Los personajes controlados por el jugador están a la derecha, los personajes enemigos a la izquierda y un cuadro de texto en la parte inferior muestra información sobre los combatientes.
Al comienzo del combate, los monstruos del monstruo y del jugador se agregan a la escena y luego hay una decisión sobre el orden en que las entidades toman sus turnos. Esta decisión puede depender en parte de cómo se lanzó el combate: si el jugador fue emboscado, todos los monstruos atacarán primero, de lo contrario, generalmente se basa en una de las estadísticas de la entidad, como la velocidad.
Todo lo que el jugador o los monstruos hacen es una acción: atacar es una acción, usar magia es una acción, incluso decidir qué acción tomar a continuación es una acción. El orden de las acciones se rastrea mejor usando una cola. La acción en la parte superior es la acción que tendrá lugar a continuación, a menos que una acción más rápida lo evite. Cada acción tendrá una cuenta atrás que disminuirá a medida que pase cada fotograma.
El flujo de combate se controla usando una máquina de estado con dos estados; un estado para marcar las acciones y otro estado para ejecutar la acción superior cuando llegue el momento. Como siempre, la mejor manera de entender algo es mirar el código. El siguiente ejemplo implementa un estado de combate básico con una cola de acción:
1 |
class BattleState : IState |
2 |
{
|
3 |
List<IAction> mActions = List<IAction>(); |
4 |
List<Entity> mEntities = List<Entity>(); |
5 |
|
6 |
StateMachine mBattleStates = new StateMachine(); |
7 |
|
8 |
public static bool SortByTime(Action a, Action b) |
9 |
{
|
10 |
return a.TimeRemaining() > b.TimeRemaining() |
11 |
}
|
12 |
|
13 |
public BattleState() |
14 |
{
|
15 |
mBattleStates.Add("tick", new BattleTick(mBattleStates, mActions)); |
16 |
mBattleStates.Add("execute", new BattleExecute(mBattleStates, mActions)); |
17 |
}
|
18 |
|
19 |
public void OnEnter(var params) |
20 |
{
|
21 |
mBattleStates.Change("tick"); |
22 |
|
23 |
//
|
24 |
// Get a decision action for every entity in the action queue
|
25 |
// The sort it so the quickest actions are the top
|
26 |
//
|
27 |
|
28 |
mEntities = params.entities; |
29 |
|
30 |
foreach(Entity e in mEntities) |
31 |
{
|
32 |
if(e.playerControlled) |
33 |
{
|
34 |
PlayerDecide action = new PlayerDecide(e, e.Speed()); |
35 |
mActions.Add(action); |
36 |
}
|
37 |
else
|
38 |
{
|
39 |
AIDecide action = new AIDecide(e, e.Speed()); |
40 |
mActions.Add(action); |
41 |
}
|
42 |
}
|
43 |
|
44 |
Sort(mActions, BattleState::SortByTime); |
45 |
}
|
46 |
|
47 |
public void Update(float elapsedTime) |
48 |
{
|
49 |
mBattleStates.Update(elapsedTime); |
50 |
}
|
51 |
|
52 |
public void Render() |
53 |
{
|
54 |
// Draw the scene, gui, characters, animations etc
|
55 |
|
56 |
mBattleState.Render(); |
57 |
}
|
58 |
|
59 |
public void OnExit() |
60 |
{
|
61 |
|
62 |
}
|
63 |
}
|
El código anterior demuestra el control del flujo de modo de batalla usando una máquina de estado simple y una cola de acciones. Para empezar, todas las entidades involucradas en la batalla tienen una decisión-acción agregada a la cola.
Una acción de decisión para el jugador mostrará un menú con las opciones estables RPG Attack, Magic y Item; Una vez que el jugador decide sobre una acción, la decisión-acción se elimina de la cola y se agrega la acción recién elegida.
Una acción de decisión para la IA inspeccionará la escena y decidirá qué hacer a continuación (usando algo como un árbol de comportamiento, árbol de decisiones o una técnica similar) y luego también eliminará su acción de decisión y agregará su nueva acción a la cola.
La clase
BattleTick
controla la actualización de las acciones, como se muestra a
continuación:
1 |
class BattleTick : IState |
2 |
{
|
3 |
StateMachine mStateMachine; |
4 |
List<IAction> mActions; |
5 |
|
6 |
public BattleTick(StateMachine stateMachine, List<IAction> actions) |
7 |
: mStateMachine(stateMachine), mActions(action) |
8 |
{
|
9 |
}
|
10 |
|
11 |
// Things may happen in these functions but nothing we're interested in.
|
12 |
public void OnEnter() {} |
13 |
public void OnExit() {} |
14 |
public void Render() {} |
15 |
|
16 |
public void Update(float elapsedTime) |
17 |
{
|
18 |
foreach(Action a in mActions) |
19 |
{
|
20 |
a.Update(elapsedTime); |
21 |
}
|
22 |
|
23 |
if(mActions.Top().IsReady()) |
24 |
{
|
25 |
Action top = mActions.Pop(); |
26 |
mStateMachine:Change("execute", top); |
27 |
}
|
28 |
}
|
29 |
}
|
BattleTick
es un estado secundario del estado BattleMode y simplemente funciona
hasta que la cuenta regresiva de la acción superior es cero. A continuación, saca la acción superior de la cola y cambia al estado de ejecución.



El diagrama de arriba muestra una cola de acción al comienzo de una batalla. Nadie ha tomado una acción y cada uno está ordenado por su tiempo para tomar una decisión.
La Planta Gigante tiene una cuenta atrás de 0, por lo
que en la siguiente marca ejecuta su acción AIDecide
. En este caso, la
acción AIDecide
provoca que el monstruo decida atacar. La acción de
ataque es casi inmediata y se agrega nuevamente a la cola como la
segunda acción.
En
la siguiente iteración de BattleTick
, el jugador podrá elegir qué
acción debe tomar su "Mark" enana, lo que cambiará la cola nuevamente. La siguiente iteración de BattleTick
después de eso, la Planta atacará a
uno de los enanos. La acción de ataque se eliminará de la cola y pasará
al estado
BattleExecute
, y animará el ataque de la planta y realizará todos los
cálculos de combate necesarios.
Una vez que el ataque del monstruo
termina, se agregará otra acción AIDecide
a la cola del monstruo. BattleState
continuará de esta manera hasta el final del combate.
Si alguna entidad muere durante el combate, todas sus acciones deben ser eliminadas de la cola; no queremos que los monstruos muertos reanimen y ataquen repentinamente durante el juego (¡a menos que estemos fabricando zombis o algún tipo de no-muerto!).
La cola de acción y la máquina de estado simple son el corazón del sistema de combate y ahora debería tener una buena idea de cómo encaja. No es lo suficientemente completo como para ser una solución independiente, pero se puede utilizar como plantilla para construir algo más completamente funcional e intrincado. Las acciones y los estados son una buena abstracción que ayuda a administrar la complejidad del combate y facilita la expansión y el desarrollo.
Acciones siguientes:
- Escribe el estado
BattleExecute
. - Tal vez agregue más estados,
como
BattleMenuState
yAnimationState
. - Representa fondos y enemigos con estadísticas básicas de salud.
- Escribe una acción de ataque simple y ejecuta una simple batalla de ataques comerciales.
- Dale a las entidades habilidades especiales o magia.
- Crea un enemigo que se curará si está por debajo del 25% de salud.
- Crea un mapa mundial para lanzar el estado de batalla.
- Crea un estado de
BattleOver
que muestre el botín y la ganancia de XP.
Revisión
Hemos tenido una mirada de alto nivel sobre cómo hacer un JRPG, sumergiéndonos en algunos de los detalles más interesantes. Hemos cubierto cómo estructurar el código usando una máquina de estado o pila, cómo usar mapas de mosaico y capas para mostrar nuestro mundo, y cómo controlar el flujo de combate usando una cola de acción y una máquina de estados. Las características que hemos cubierto son una buena base para construir y desarrollar.
Pero también hay mucho que no se ha cubierto en absoluto. Hacer un JRPG completo incluye XP y sistemas de nivelación, guardar y cargar el juego, un montón de código GUI para los menús, animaciones básicas y efectos especiales, estados para manejar escenas, mecánica de combate (como dormir, posar, bonos elementales y resistencias), ¡por nombrar solo algunas cosas!
Sin embargo, no necesitas todas estas cosas para un juego; Para La Luna, básicamente, solo tenía exploración de mapas y diálogo. Puede agregar nuevas funciones de forma gradual a medida que crea su juego.
A dónde ir desde aquí
La parte más difícil de hacer cualquier juego es terminarlo, así que empieza pequeño, piensa en un mini-rpg; escapar de una mazmorra, una única búsqueda de búsqueda, y luego construirlo. Si te encuentras atascado, reduce el alcance del juego, simplifícalo y finalízalo. Puede que descubras que a medida que desarrollas obtienes muchas ideas nuevas y emocionantes, lo cual es bueno, escríbelas, pero resiste la tentación de aumentar el alcance de tu juego o, peor aún, comenzar una nueva.
Hacer un JRPG es difícil; hay muchos sistemas y, sin un buen mapa, puede ser difícil saber cuál abordar primero. ¡Una completa guía paso a paso para construir un JRPG llenaría un libro! Afortunadamente, estoy escribiendo ese libro, así que si quieres una guía más detallada para hacer un JRPG, por favor échale un vistazo.
Recursos utilizados
Se usaron varios recursos y activos de creative commons para ayudar a armar este artículo:
- Town Map Art de Zabin, Daneeklu, Jetrel, Hipótesis, Redshrike, Bertram.
- World Map Art por MrBeast.
- Background Art por Delfos.
- Potrait Art por Justin Nichol.
- Monster Art por Blarumyrran.
- Gift of Knowledge icon de Lorc y Dark Wood pattern de Omar Alvardo.
- Tiled para la creación de mapas.
- Conceptos de mi libro How to Make a RPG. Si desea una cobertura más detallada de la creación de un JRPG, entonces pruébelo.