Hola @hokuto!! mil disculpas por la demora.
En muchos juegos de naves que ví los niveles están diseñados casi manualmente. Es decir, siempre se repite el mismo patrón cuando el jugador vuelve a jugar el mismo nivel. Así que creo que lo mejor es implementarlo igual, sin usar rutinas aleatorias o temporizadores.
Ojo, hay juegos en donde las naves enemigas aparecen aleatoriamente; no reconozco muy bien si en el juego que muestras esto es aleatorio o pre-diseñado… pero, vamos a suponer que es pre-diseñado, vas a ver que se puede construir un juego interesante incluso asumiendo eso:
En un nivel pre-diseñado se suelen definir de antemano los enemigos que van a aparecer en el juego y en qué momento van a aprecer. Por ejemplo, en este caso, se me ocurrió que podría haber tres tipos de enemigos:
- enemigoDesdeDerecha
- ernemigoDiagonal
- enemigoElastico
Cada uno de estos enemigos debería entrar en la escena con un movimiento característico, movimientos que incluso podemos bocetar antes de comenzar a escribir código. Mirá como me imagino estos tres tipos de enemigos:
El EnemigoDesdeDerecha podría entrar en la escena con un moviento de derecha a izquierda, subiendo y bajando suavemente.
El EnemigoDiagonal puede entrar de arriba hacia abajo, moviéndose un poquito a la derecha cuando está bajando.
Y el último tipo de enemigo, llamado EnemigoElastico, baja y sube. Es más agresivo que los demás, podría tomar desprevenido al jugador. Imaginá que en una situación de juego normal, el jugador va a estar moviéndose entre disparos, acorralado, sin poder detenerse en prestar atención al movimiento pre-diseñado de los enemigos.
En fin, con ese diseño, una vez prediseñados los enemigos, podrías comenzar a implementar estos actores en pilas creando tres clases de actores, uno para cada tipo de enemigo:
class EnemigoDesdeDerecha(pilasengine.actores.Actor):
def iniciar(self, x, y, velocidad):
self.aprender('puedeExplotar')
self.imagen = "aceituna.png"
self.velocidad = velocidad
self.x = 400
self.y_inicial = y
self.contador = 0
def actualizar(self):
self.x -= self.velocidad
self.contador += 0.1
# Hace un movimiento suave de arriba hacia abajo
self.y = self.y_inicial + math.sin(self.contador) * 50
# Si sale de la pantalla
if self.x < -400:
self.eliminar()
class EnemigoDiagonal(pilasengine.actores.Actor):
def iniciar(self, x, y, velocidad):
self.aprender('puedeExplotar')
self.imagen = "aceituna.png"
self.velocidad = velocidad
self.x = x
self.y = 300
self.y_para_cambiar_direccion = y
self.direccion = "hacia_abajo"
def actualizar(self):
if self.direccion is "hacia_abajo":
self.y -= self.velocidad
# Si llega abajo, regresa arriba
if self.y < self.y_para_cambiar_direccion:
self.direccion = "hacia_arriba"
else:
self.y += self.velocidad
# Si esta subiendo, y sale de la pantalla se elimina
if self.y > 400:
self.eliminar()
self.x += self.velocidad / 2.0
class EnemigoElastico(pilasengine.actores.Actor):
def iniciar(self, x, y, velocidad):
self.aprender('puedeExplotar')
self.imagen = "aceituna.png"
self.velocidad = velocidad
self.x = x
self.y = 400
self.y_inicial = 400
self.contador = 0
def actualizar(self):
self.contador += 0.01
self.y = self.y_inicial - math.sin(self.contador * self.velocidad) * 600
if self.y > self.y_inicial:
self.eliminar()
Al principio el código puede parece mucho, pero no te preocupes, generalmente el código se escribe poco a poco; escribí tres clases juntas para que el juego parezca más interesante, solo por eso. Con una clase alcanzaba sinceramente.
Ahora bien, con tres tipos de enemigos, es fácil pensar en un nivel desafiante para el jugador de juego; este paso dependen mucho de vos; de hacer pruebas y errores, hacer que algún amigo juegue a tu juego y ver si realmente está bien o mal…
Acá voy a imaginar un nivel muy simple:
- primero aparecen tres actores desde la derecha.
- luego otros tres un poco más arriba.
- después 3 en diagonal
- y muy cerca de esos 3 agresivos, de arriba hacia abajo.
- por último si el jugador sobrevivió, un mensaje que diga “esto se solo el comienzo”.
Para llevar eso al juego lo mejor es crear una lista con todos los pasos, y luego crear un temporizador que lo ponga en funcionamiento.
Este es un video de cómo quedaría lo que me imagino, luego vemos el código:
Mirá como los personajes aparecen, siempre siguiendo el mismo patrón. Esto es así porque en realidad los actores se lanzan a la escena usando un diseño pre-diseñado así:
enemigos = pilas.actores.Grupo()
def agregar_enemigo(clase, x, y, velocidad):
enemigos.agregar(clase(x=x, y=y, velocidad=velocidad))
nivel = [
{'tiempo': 2, 'actor': pilas.actores.EnemigoDesdeDerecha, 'x': 0, 'y': 0, 'velocidad': 2},
{'tiempo': 3, 'actor': pilas.actores.EnemigoDesdeDerecha, 'x': 0, 'y': 0, 'velocidad': 2},
{'tiempo': 4, 'actor': pilas.actores.EnemigoDesdeDerecha, 'x': 0, 'y': 0, 'velocidad': 2},
{'tiempo': 6, 'actor': pilas.actores.EnemigoDesdeDerecha, 'x': 0, 'y': 100, 'velocidad': 2},
{'tiempo': 7, 'actor': pilas.actores.EnemigoDesdeDerecha, 'x': 0, 'y': 100, 'velocidad': 2},
{'tiempo': 8, 'actor': pilas.actores.EnemigoDesdeDerecha, 'x': 0, 'y': 100, 'velocidad': 2},
{'tiempo': 10, 'actor': pilas.actores.EnemigoDiagonal, 'x': -200, 'y': 0, 'velocidad': 4},
{'tiempo': 11, 'actor': pilas.actores.EnemigoDiagonal, 'x': -200, 'y': 0, 'velocidad': 4},
{'tiempo': 12, 'actor': pilas.actores.EnemigoDiagonal, 'x': -200, 'y': 0, 'velocidad': 4},
{'tiempo': 14, 'actor': pilas.actores.EnemigoElastico, 'x': -100, 'y': 0, 'velocidad': 1},
{'tiempo': 15, 'actor': pilas.actores.EnemigoElastico, 'x': 0, 'y': 0, 'velocidad': 1},
{'tiempo': 16, 'actor': pilas.actores.EnemigoElastico, 'x': 100, 'y': 0, 'velocidad': 1},
{'tiempo': 20, 'actor': pilas.actores.Terminado, 'x':0, 'y': 0, 'velocidad': 0},
]
for x in nivel:
pilas.tareas.agregar(x['tiempo'], agregar_enemigo, x['actor'], x['x'], x['y'], x['velocidad'])
Hay dos partes importantes en este código, por un lado está la lista nivel. En esa lista debería colocar cada aparición de actores en la escena, especificando el tiempo de aparición (en segundos), luego qué actor tiene que aparecer, y por último algunos parámetros de posición (para no hacerlo tan monótono).
Armar esta lista es parte del diseño de nivel, es esa parte de la construcción del juego que implica pruebas y más pruebas, hacer que la gente juegue y ver qué tan divertido es el juego. El 80% del juego está ahí digamos
La segunda parte de ese código consiste en esa llamada a pilas.tareas.agregar
, que básicamente le dice a la computadora en qué momento tiene que crear el actor.
Para resumir, te dejo el código completo que escribí mientras pensaba en escribirte. Podés pegar este código directamente en el editor de pilas para verlo funcionar:
# coding: utf-8
import pilasengine
import math
pilas = pilasengine.iniciar()
class EnemigoDesdeDerecha(pilasengine.actores.Actor):
def iniciar(self, x, y, velocidad):
self.aprender('puedeExplotar')
self.imagen = "aceituna.png"
self.velocidad = velocidad
self.x = 400
self.y_inicial = y
self.contador = 0
def actualizar(self):
self.x -= self.velocidad
self.contador += 0.1
# Hace un movimiento suave de arriba hacia abajo
self.y = self.y_inicial + math.sin(self.contador) * 50
# Si sale de la pantalla
if self.x < -400:
self.eliminar()
class EnemigoDiagonal(pilasengine.actores.Actor):
def iniciar(self, x, y, velocidad):
self.aprender('puedeExplotar')
self.imagen = "aceituna.png"
self.velocidad = velocidad
self.x = x
self.y = 300
self.y_para_cambiar_direccion = y
self.direccion = "hacia_abajo"
def actualizar(self):
if self.direccion is "hacia_abajo":
self.y -= self.velocidad
# Si llega abajo, regresa arriba
if self.y < self.y_para_cambiar_direccion:
self.direccion = "hacia_arriba"
else:
self.y += self.velocidad
# Si esta subiendo, y sale de la pantalla se elimina
if self.y > 400:
self.eliminar()
self.x += self.velocidad / 2.0
class EnemigoElastico(pilasengine.actores.Actor):
def iniciar(self, x, y, velocidad):
self.aprender('puedeExplotar')
self.imagen = "aceituna.png"
self.velocidad = velocidad
self.x = x
self.y = 400
self.y_inicial = 400
self.contador = 0
def actualizar(self):
self.contador += 0.01
self.y = self.y_inicial - math.sin(self.contador * self.velocidad) * 600
if self.y > self.y_inicial:
self.eliminar()
class Terminado(pilasengine.actores.Actor):
def iniciar(self, x, y, velocidad):
self.imagen = "invisible.png"
self.pilas.actores.Texto("Esto es solo el principio ...")
pilas.actores.vincular(EnemigoDesdeDerecha)
pilas.actores.vincular(EnemigoDiagonal)
pilas.actores.vincular(EnemigoElastico)
pilas.actores.vincular(Terminado)
enemigos = pilas.actores.Grupo()
def agregar_enemigo(clase, x, y, velocidad):
enemigos.agregar(clase(x=x, y=y, velocidad=velocidad))
nivel = [
{'tiempo': 2, 'actor': pilas.actores.EnemigoDesdeDerecha, 'x': 0, 'y': 0, 'velocidad': 2},
{'tiempo': 3, 'actor': pilas.actores.EnemigoDesdeDerecha, 'x': 0, 'y': 0, 'velocidad': 2},
{'tiempo': 4, 'actor': pilas.actores.EnemigoDesdeDerecha, 'x': 0, 'y': 0, 'velocidad': 2},
{'tiempo': 6, 'actor': pilas.actores.EnemigoDesdeDerecha, 'x': 0, 'y': 100, 'velocidad': 2},
{'tiempo': 7, 'actor': pilas.actores.EnemigoDesdeDerecha, 'x': 0, 'y': 100, 'velocidad': 2},
{'tiempo': 8, 'actor': pilas.actores.EnemigoDesdeDerecha, 'x': 0, 'y': 100, 'velocidad': 2},
{'tiempo': 10, 'actor': pilas.actores.EnemigoDiagonal, 'x': -200, 'y': 0, 'velocidad': 4},
{'tiempo': 11, 'actor': pilas.actores.EnemigoDiagonal, 'x': -200, 'y': 0, 'velocidad': 4},
{'tiempo': 12, 'actor': pilas.actores.EnemigoDiagonal, 'x': -200, 'y': 0, 'velocidad': 4},
{'tiempo': 14, 'actor': pilas.actores.EnemigoElastico, 'x': -100, 'y': 0, 'velocidad': 1},
{'tiempo': 15, 'actor': pilas.actores.EnemigoElastico, 'x': 0, 'y': 0, 'velocidad': 1},
{'tiempo': 16, 'actor': pilas.actores.EnemigoElastico, 'x': 100, 'y': 0, 'velocidad': 1},
{'tiempo': 20, 'actor': pilas.actores.Terminado, 'x':0, 'y': 0, 'velocidad': 0},
]
for x in nivel:
pilas.tareas.agregar(x['tiempo'], agregar_enemigo, x['actor'], x['x'], x['y'], x['velocidad'])
nave = pilas.actores.NaveRoja(y=-200)
nave.definir_enemigos(enemigos)
pilas.ejecutar()
¡Abrazo!