Tu primer juego
Construye paso a paso un juego 2D con Pincel: ventana, movimiento, colisiones y puntos.
Pincel es el motor de juegos 2D de la biblioteca estándar. Vamos a construir un juego pequeño por pasos: un cuadrado que se mueve con el teclado y recoge monedas. Cada paso funciona por sí solo, y el final es el juego completo.
Para probarlo, guarda el código en un archivo (por ejemplo juego.cometa) y ejecuta cometa ejecutar juego.cometa. La primera compilación descarga las dependencias gráficas y tarda un poco.
La idea: un tipo con dos métodos
Un juego es un tipo con dos métodos públicos que Pincel llama por ti, unas 60 veces por segundo:
actualizar(dt)cambia el estado: mueve cosas, cuenta puntos.dtson los segundos desde la última vez.pintar()dibuja el estado actual. No debe cambiarlo.
Separar «qué pasa» de «cómo se ve» mantiene el código ordenado. Y inicio() solo entrega el juego a pincel.ejecutar, que abre la ventana y espera a que se cierre.
Paso 1: una ventana
usar std/pincel
usar std/pincel/graficos
tipo Juego
pub fn actualizar(dt decimal) retornar
pub fn pintar()
graficos.limpiar(.AzulNoche)
graficos.texto_depuracion("¡Hola desde Pincel!", 110, 80)
fn inicio()
pincel.ejecutar(Juego {}, 320, 180, titulo = "Mi juego", escala = 3) atrapar |error|
imprimir(error)
- Cada módulo se importa por separado:
std/pincel(el motor) ystd/pincel/graficos(dibujo). 320, 180es el tamaño lógico: las coordenadas van de 0 a 319 en x y de 0 a 179 en y, con el(0, 0)arriba a la izquierda y laycreciendo hacia abajo.escala = 3solo agranda la ventana.ejecutardevuelve un resultado (lección 10), por eso se maneja conatrapar |error|.actualizarestá vacío por ahora;retornaren una línea basta.
Paso 2: estado y movimiento
El estado del juego vive en los campos del tipo. Añadimos la posición del jugador y la movemos con el teclado:
usar std/mate
usar std/pincel/entrada
const velocidad decimal = 100
tipo Juego
pos mate.Vec2
pub fn actualizar(dt decimal)
var direccion = mate.Vec2 {}
si entrada.tecla_mantenida(.Derecha)
direccion.x = 1
si entrada.tecla_mantenida(.Izquierda)
direccion.x = -1
si entrada.tecla_mantenida(.Abajo)
direccion.y = 1
si entrada.tecla_mantenida(.Arriba)
direccion.y = -1
@pos = @pos + direccion.normalizado() * velocidad * dt
pub fn pintar()
graficos.limpiar(.VerdePino)
graficos.rectangulo_v(@pos, {16, 16}, .Celeste)
mate.Vec2es un vector{x, y}. Se suma y se multiplica por un número directamente.tecla_mantenidaes verdadero mientras la tecla está pulsada. (tecla_presionadasolo lo es en el cuadro en que se pulsa: útil para saltar o disparar.)- Multiplicar por
dthace que la velocidad sea «100 píxeles por segundo» sin importar cuántos cuadros se dibujen. Sindt, el juego iría más rápido en un ordenador más rápido. normalizado()deja la dirección con longitud 1, para que ir en diagonal no sea más rápido.
Empezamos en la esquina porque el Juego {} de inicio deja pos en {0, 0}. Para otro punto de partida: Juego {pos: {x: 152, y: 82}}.
Paso 3: una moneda y colisiones
Ahora una moneda que aparece en un lugar al azar. Al tocarla sumamos un punto y la movemos. Para saber si dos rectángulos se tocan usamos mate.Rect y interseca:
usar std/azar
tipo Juego
pos mate.Vec2
moneda mate.Vec2
puntos entero
fn mover_moneda()
@moneda = {x: azar.entero(0, 312), y: azar.entero(0, 172)}
pub fn actualizar(dt decimal)
// ... el movimiento del paso 2 ...
var caja_jugador = mate.Rect {pos: @pos, tamano: {16, 16}}
var caja_moneda = mate.Rect {pos: @moneda, tamano: {8, 8}}
si caja_jugador.interseca(caja_moneda)
@puntos += 1
@mover_moneda()
- Los métodos sin
pub(mover_moneda) son internos del tipo; Pincel solo necesitaactualizarypintar. azar.entero(0, 312)da un entero de 0 a 311 (el final se excluye, como en los rangos).- Recuerda
@(lección 6):@moneday@mover_moneda()son el campo y el método de este juego.
Paso 4: el juego completo
Unimos todo, mantenemos al jugador dentro de la pantalla y mostramos los puntos. Esto es un programa completo que puedes copiar y ejecutar:
usar std/mate
usar std/azar
usar std/pincel
usar std/pincel/graficos
usar std/pincel/entrada
const velocidad decimal = 100
tipo Juego
pos mate.Vec2
moneda mate.Vec2
puntos entero
fn mover_moneda()
@moneda = {x: azar.entero(0, 312), y: azar.entero(12, 172)}
pub fn actualizar(dt decimal)
var direccion = mate.Vec2 {}
si entrada.tecla_mantenida(.Derecha) || entrada.tecla_mantenida(.D)
direccion.x = 1
si entrada.tecla_mantenida(.Izquierda) || entrada.tecla_mantenida(.A)
direccion.x = -1
si entrada.tecla_mantenida(.Abajo) || entrada.tecla_mantenida(.S)
direccion.y = 1
si entrada.tecla_mantenida(.Arriba) || entrada.tecla_mantenida(.W)
direccion.y = -1
@pos = @pos + direccion.normalizado() * velocidad * dt
@pos.x = mate.limitar(@pos.x, 0, 304)
@pos.y = mate.limitar(@pos.y, 12, 164)
var caja_jugador = mate.Rect {pos: @pos, tamano: {16, 16}}
var caja_moneda = mate.Rect {pos: @moneda, tamano: {8, 8}}
si caja_jugador.interseca(caja_moneda)
@puntos += 1
@mover_moneda()
pub fn pintar()
graficos.limpiar(.VerdePino)
graficos.rectangulo_v(@moneda, {8, 8}, .Amarillo)
graficos.rectangulo_v(@pos, {16, 16}, .Celeste)
graficos.texto_depuracion("Puntos: ${@puntos}", 4, 2)
fn inicio()
var juego = Juego {pos: {x: 152, y: 82}}
juego.mover_moneda()
pincel.ejecutar(juego, 320, 180, titulo = "Monedas", escala = 3) atrapar |error|
imprimir(error)
mate.limitar(valor, minimo, maximo) mantiene un número dentro de un rango. Fíjate en que dibujamos la moneda antes que al jugador: lo que se dibuja después queda encima.
Ideas para seguir
- Añade un tiempo límite: un campo
restante decimalal que le restasdtenactualizar, y muestra «Fin» cuando llegue a 0 (examples/pincel/09_atrapa.cometalo hace, con imagen y sonido). - Cambia los cuadrados por sprites dibujados con texto usando
lienzo(ejemplo07_sprites). - Usa
retro.textoyretro.iconopara texto e iconos de aspecto retro sin archivos de imagen.
Sigue con las guías: juegos, pixel art con lienzo, interfaces y curvas de animación; y explora todos los módulos en la biblioteca.
Ejemplo: mover un personaje
// Pincel: mover un personaje con el teclado (flechas o WASD).
usar std/mate
usar std/pincel
usar std/pincel/graficos
usar std/pincel/entrada
const velocidad decimal = 100
tipo Jugador
pos mate.Vec2
saltos entero
pub fn actualizar(dt decimal)
var direccion = mate.Vec2 {}
// tecla_mantenida es verdadero mientras la tecla está pulsada.
si entrada.tecla_mantenida(.Derecha) || entrada.tecla_mantenida(.D)
direccion.x = 1
si entrada.tecla_mantenida(.Izquierda) || entrada.tecla_mantenida(.A)
direccion.x = -1
si entrada.tecla_mantenida(.Abajo) || entrada.tecla_mantenida(.S)
direccion.y = 1
si entrada.tecla_mantenida(.Arriba) || entrada.tecla_mantenida(.W)
direccion.y = -1
// normalizado() evita ir más rápido en diagonal.
@pos = @pos + direccion.normalizado() * velocidad * dt
// Mantenerse dentro de la pantalla.
@pos.x = mate.limitar(@pos.x, 0, 304)
@pos.y = mate.limitar(@pos.y, 0, 164)
// tecla_presionada solo es verdadero en el cuadro en que se pulsa.
si entrada.tecla_presionada(.Espacio)
@saltos = @saltos + 1
pub fn pintar()
graficos.limpiar(.VerdePino)
graficos.rectangulo_v(@pos, {16, 16}, .Celeste)
graficos.texto_depuracion("Flechas/WASD: mover Espacio: contar", 4, 4)
graficos.texto_depuracion("Espacio pulsado ${@saltos} veces", 4, 164)
fn inicio()
pincel.ejecutar(Jugador {pos: {x: 152, y: 82}}, 320, 180, titulo = "Teclado", escala = 3) atrapar |error|
imprimir(error)