En este artículo hablaré sobre mi experiencia personal en el desarrollo de un pequeño juego en Rust. Me llevó unas 24 horas crear una versión funcional (principalmente trabajaba por las tardes o los fines de semana). El juego está lejos de terminar, pero creo que la experiencia será útil. Te diré lo que aprendí y algunas de las observaciones hechas al construir el juego desde cero.
Skillbox recomienda: Curso práctico de dos años "Soy un desarrollador web PRO" .
Le recordamos: para todos los lectores de "Habr": un descuento de 10.000 rublos al registrarse en cualquier curso de Skillbox con el código de promoción "Habr".
¿Por qué el óxido?
Elegí este lenguaje porque escuché muchas cosas buenas al respecto y veo que se está volviendo cada vez más popular en el campo del desarrollo de juegos. Antes de escribir el juego, tenía poca experiencia desarrollando aplicaciones simples en Rust. Esto fue suficiente para sentir una cierta libertad al escribir el juego.
¿Por qué exactamente el juego y qué tipo de juego?
¡Hacer juegos es divertido! Me gustaría por más razones, pero para los proyectos de "hogar" elijo temas que no están muy relacionados con mi trabajo habitual. Que tipo de juego Quería hacer algo así como un simulador de tenis que combina Cities Skylines, Zoo Tycoon, Prison Architect y el tenis en sí. En general, resultó un juego sobre una academia de tenis, donde la gente viene a jugar.
Entrenamiento técnico
Quería usar Rust, pero no sabía exactamente cómo "desde cero" necesitaría comenzar. No quería escribir sombreadores de píxeles y usar arrastrar y soltar, así que estaba buscando las soluciones más flexibles.
Encontré recursos útiles que comparto con ustedes:
Exploré varios motores de juegos de Rust, y finalmente elegí Piston y ggez. Me los encontré cuando trabajaba en un proyecto anterior. Al final, elegí ggez, porque parecía más adecuado para implementar un pequeño juego en 2D. La estructura modular de Piston es demasiado compleja para un desarrollador novato (o alguien que trabaja con Rust por primera vez).
Estructura del juego
Pasé un poco de tiempo pensando en la arquitectura del proyecto. El primer paso es hacer la "tierra", la gente y las canchas de tenis. La gente debe moverse por las canchas y esperar. Los jugadores deben tener habilidades que mejoren con el tiempo. Además, debe haber un editor que le permita agregar nuevas personas y tribunales, pero esto ya no es gratuito.
Pensando en todo, me puse a trabajar.
Creación de juegos
Inicio: círculos y abstraccionesTomé un ejemplo de ggez y obtuve un círculo en la pantalla. Asombroso Ahora algunas abstracciones. Me pareció que es bueno ignorar la idea de un objeto de juego. Cada objeto debe ser renderizado y actualizado como se indica aquí:
Este código me permitió obtener una excelente lista de objetos que puedo actualizar y renderizar en un bucle igualmente excelente.
mpl event::EventHandler for MainState { fn update(&mut self, context: &mut Context) -> GameResult<()> {
Se necesita main.rs porque contiene todas las líneas de código. Pasé un poco de tiempo para separar los archivos y optimizar la estructura del directorio. Así es como todo comenzó a verse después de eso:
recursos -> aquí es donde están todos los activos (imágenes)
src
- entidades
- game_object.rs
- circle.rs
- main.rs -> lazo principalPersonas, pisos e imágenesEl siguiente paso es crear un objeto de juego Persona y cargar imágenes. Todo debe basarse en fichas de 32 * 32 de tamaño.
Canchas de tenisDespués de estudiar cómo se ven las canchas de tenis, decidí hacerlas con fichas de 4 * 2. Inicialmente, era posible hacer una imagen de este tamaño o juntar 8 mosaicos separados. Pero luego me di cuenta de que solo se necesitan dos mosaicos únicos, y por eso.
En total, tenemos dos fichas de este tipo: 1 y 2.
Cada sección de la cancha consta de la loseta 1 o la loseta 2. Se pueden organizar como de costumbre o se pueden voltear 180 grados.
El modo principal de construcción (montaje)Después de lograr la representación de sitios, personas y mapas, me di cuenta de que también se necesitaba un modo de compilación básico. Se implementó así: cuando se presiona el botón, se selecciona el objeto y el clic lo coloca en el lugar correcto. Entonces, el botón 1 le permite seleccionar una cancha, y el botón 2 le permite seleccionar un jugador.
Pero aún debe recordar lo que queremos decir con 1 y 2, por lo que agregué una estructura metálica para que quede claro qué objeto está seleccionado. Así es como se ve.
Preguntas sobre arquitectura y refactorizaciónAhora tengo varios objetos de juego: personas, canchas y pisos. Pero para que los wireframes funcionen, debe informar a cada entidad del objeto si los objetos mismos están en modo de demostración o si simplemente se dibuja un marco. Esto no es muy conveniente.
Me pareció que necesitaba repensar la arquitectura para que se revelaran algunas limitaciones:
- la presencia de una entidad que se muestra y actualiza es un problema, porque esta entidad no podrá "saber" lo que debe representar: una imagen y una estructura alámbrica;
- falta de una herramienta para intercambiar propiedades y comportamiento entre entidades individuales (por ejemplo, la propiedad is_build_mode o el comportamiento de representación). Se podría usar la herencia, aunque no hay una forma normal de implementarla en Rust. Lo que realmente necesitaba era el diseño;
- se necesitaba una herramienta para la interacción de entidades entre ellos para asignar personas a los tribunales;
- Las entidades mismas eran una mezcla de datos y lógica, que rápidamente se salió de control.
Investigué un poco más y descubrí la arquitectura ECS: Entity Component System , que se usa comúnmente en los juegos. Aquí están los beneficios de ECS:
- los datos están separados de la lógica;
- diseño en lugar de herencia;
- arquitectura orientada a datos.
ECS se caracteriza por tres conceptos básicos:
- entidades: el tipo de objeto al que hace referencia el identificador (puede ser un jugador, una pelota u otra cosa);
- componentes: las entidades consisten en ellos. Un ejemplo es un componente de representación, diseño y otros. Es un almacén de datos;
- sistemas: utilizan tanto objetos como componentes, además contienen comportamiento y lógica que se basan en estos datos. Un ejemplo es un sistema de representación que itera sobre todas las entidades con componentes para la representación y se dedica a la representación.
Después de estudiar, quedó claro que ECS resuelve tales problemas:
- utilizar el diseño en lugar de la herencia para la organización del sistema de entidades;
- deshacerse de un hash de código debido a los sistemas de control;
- usando métodos como is_build_mode para almacenar la lógica de la estructura en el mismo lugar, en el sistema de representación.
Esto es lo que sucedió después de implementar ECS.
recursos -> aquí es donde están todos los activos (imágenes)
src
- componentes
- position.rs
- person.rs
- tennis_court.rs
- floor.rs
- wireframe.rs
- mouse_tracked.rs
- recursos
- mouse.rs
- sistemas
- rendering.rs
- constantes.rs
- utils.rs
- world_factory.rs -> funciones de fábrica mundial
- main.rs -> lazo principalAsignar personas a los tribunales.
ECS hizo la vida más fácil. Ahora tenía una forma sistemática de agregar datos a entidades y agregar lógica basada en estos datos. Y esto, a su vez, permitió organizar la distribución de personas por tribunal.
Lo que hice
- agregó datos sobre los tribunales asignados a la Persona;
- datos agregados sobre personas distribuidas a TennisCourt;
- CourtChoosingSystem agregado, que le permite analizar personas y sitios, encontrar canchas disponibles y distribuir jugadores a ellos;
- agregó el sistema PersonMovementSystem, que busca a las personas asignadas a los tribunales, y si no están allí, envía a las personas donde sea necesario.
Para resumir
Realmente disfruté trabajando en este juego simple. Además, me complace haber usado Rust para escribirlo porque:
- El óxido te da lo que necesitas;
- él tiene excelente documentación, Rust es muy elegante;
- la constancia es genial;
- No tiene que recurrir a la clonación, la copia u otras acciones similares, que a menudo hice en C ++;
- Las opciones son muy convenientes para el trabajo, también manejan los errores perfectamente;
- si el proyecto pudiera compilarse, en un 99% funciona, y exactamente como debería. Me parece que los mensajes de error del compilador son los mejores que he visto.
El desarrollo del juego en Rust apenas está comenzando. Pero ya existe una comunidad estable y bastante grande que trabaja para abrir Rust a todos. Por lo tanto, miro el futuro del lenguaje con optimismo, esperando los resultados de nuestro trabajo común.
Skillbox recomienda: