Tilengine - The 2D retro graphics engine forum

Full Version: como se puede crear un disparo
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2
Estaba mirando el ejemplo de naves de c para saber como realizar un disparo y la verdad es que no he entendido nada del codigo.Lo he hecho a mi manera pero no he conseguido nada,simplemente aparece el disparo al pulsar un boton pero este no se mueve y solo se crea el numero que hay puesto en el create.

Te voy a poner el codigo para que me digas que estoy haciendo mal.
Code:
import tilengine
import random
#inicio-------------------------------------------------------------
engine = tilengine.Engine.create(640,480,2,64,4)
ventana = tilengine.Window.create()
ventana.disable_crt_effect()
engine.set_background_color(tilengine.Color(120,215,242))
#jugador------------------------------------------------------------
class Jugador(object):
   def __init__(self):
       self.x = 320
       self.y = 420
       self.velocidad = 5
       #obtener el sprite para poder crear muchos sprites
       self.sprite_id = engine.get_available_sprite()
       self.imagen = tilengine.Spriteset.fromfile("hero")
       engine.sprites[self.sprite_id].setup(self.imagen)
       engine.sprites[self.sprite_id].set_position(self.x,self.y)
       engine.sprites[self.sprite_id].set_scaling(2,2)
       engine.sprites[self.sprite_id].enable_collision(True)
       self.disparo_jugador = DisparoJugador(self.x,self.y)
       
   def update(self):
       self.mover()
       self.colision()
       self.disparar()
   
   def mover(self):
       if(ventana.get_input(tilengine.Input.LEFT) and self.x > 0):
           engine.sprites[self.sprite_id].set_position(self.x,self.y)
           engine.sprites[self.sprite_id].set_flags(tilengine.Flags.FLIPX)
           self.x -= self.velocidad
       elif(ventana.get_input(tilengine.Input.RIGHT) and self.x < 608):
           engine.sprites[self.sprite_id].set_position(self.x,self.y)
           engine.sprites[self.sprite_id].set_flags(0)
           self.x += self.velocidad
       if(ventana.get_input(tilengine.Input.UP) and self.y > 16):
           engine.sprites[self.sprite_id].set_position(self.x,self.y)
           self.y -= self.velocidad
       elif(ventana.get_input(tilengine.Input.DOWN) and self.y < 430):
           engine.sprites[self.sprite_id].set_position(self.x,self.y)
           self.y += self.velocidad
   
   def colision(self):
       if(engine.sprites[enemigo.sprite_id].check_collision()):
           engine.sprites[self.sprite_id].set_scaling(1,1)
       else:
           engine.sprites[self.sprite_id].set_scaling(2,2)
           
   def disparar(self):
       if(ventana.get_input(tilengine.Input.A)):
           self.disparo_jugador = DisparoJugador(self.x,self.y)
           self.disparo_jugador.update()
   
#enemigo------------------------------------------------------------
class Enemigo(object):
   def __init__(self):
       random.seed()
       self.x = random.randint(32,608)
       self.y = random.randrange(32,129)
       #obtener el sprite para poder crear muchos sprites
       self.sprite_id = engine.get_available_sprite()
       self.imagen = tilengine.Spriteset.fromfile("hero2")
       engine.sprites[self.sprite_id].setup(self.imagen)
       engine.sprites[self.sprite_id].set_position(self.x,self.y)
       engine.sprites[self.sprite_id].set_scaling(2,2)
       engine.sprites[self.sprite_id].enable_collision(True)
       
   def update(self):
       pass

#disparo jugador-----------------------------------------------------
class DisparoJugador(object):
   def __init__(self,x,y):
       self.x = x
       self.y = y
       self.velocidad = 2
       #obtener el sprite para poder crear muchos sprites
       self.sprite_id = engine.get_available_sprite()
       self.imagen = tilengine.Spriteset.fromfile("hero2")
       engine.sprites[self.sprite_id].setup(self.imagen)
       engine.sprites[self.sprite_id].set_picture(25)
       engine.sprites[self.sprite_id].set_position(self.x,self.y)
       engine.sprites[self.sprite_id].set_scaling(1,1)
       engine.sprites[self.sprite_id].enable_collision(True)
       
   def update(self):
       self.mover()
       self.eliminar()
   
   def mover(self):
       engine.sprites[self.sprite_id].set_position(self.x,self.y)
       self.y -= self.velocidad
       
   def eliminar(self):
       if(self.y < 32):
           engine.sprites[self.sprite_id].disable()

#instanciar objetos--------------------------------------------------
jugador = Jugador()
enemigo = Enemigo()

#bucle---------------------------------------------------------------
while(ventana.process()):
   jugador.update()
   
   ventana.draw_frame()

Por cierto,hay alguna manera de colocar los frames por segundo que yo quiera,parece que tilengine va a 60 fps y yo suelo usar en otros engine 30 fps para ganar rendimiento.
Tu personaje se mueve por la pantalla al pulsar las teclas, pero el sprite no cambia ya que no se lo indicas. Puedes hacer animación de dos maneras:
El motor de render de Tilengine como tal no tiene ninguna vinculación a un número de frames en concreto, la única condición es que la unidad de tiempo pasado a TLN_UpdateFrame() coincida con el que utilizan las secuencias, y que desde tu entorno actualices a la frecuencia correcta.

No obstante, si usas el entorno de ventana por defecto -como casi todo el mundo- creado con TLN_CreateWindow() y TLN_DrawFrame(), el refresco va sincronizado con la frecuencia del monitor, habitualmente 60 fps, y se asume que la unidad de tiempo son fotogramas (frames). Es una "limitación" del entorno de ventana más que del motor en sí. Reconozco que es mejorable, pero como tradicionalmente todo va a 60 fps, no supone un problema real. Y no debería faltarte rendimiento, incluso una raspberry mueve bien los ejemplos de Tilengine. 60 fps aportan mucha mayor suavidad que 30, y los juegos clásicos de arcade y consola 2D suelen ir a 50/60 fps casi siempre.
No has entendido la pregunta.

Yo lo que quiero es disparar objetos con la clase jugador,si te fijas en el metodo disparar de la clase jugador esta llama a la clase disparo jugador para crear un disparo que se mueva hacia arriba,sin embargo el objeto se crea pero no se mueve y no se que estoy haciando mal,si miras la clase disparo jugador tiene un metodo mover que se llama desde su metodo update,si la llamo aparte se mueve pero si la llamo desde el jugador no se mueve.

Otra cosa que no te he dicho y que aprovecho para comentar es que tengo tres sprites que es la clase enemigo,cuando el jugador colisiona con el sprite de sonic este cambia su escala pero solo funciona al colisionar con uno,con los otros dos no funciona y no se como hacerlo.

Te paso el archivo para que lo mires cuando puedas porque no se como continuar.Saludos
Te lo pongo por googledrive porque el foro no me deja subirlo.
https://drive.google.com/file/d/1l28zE1z...sp=sharing
Hola!

Eso de compartir el proyecto completo es perfecto para poder probarlo, gracias

Te adjunto una versión modificada con lo que pretendes conseguir. Te recomiendo algún programa visor de diferencias (tipo WinMerge o similar) para que veas exactamente lo que he modificado respecto a tu versión. Lo adjunto dentro de un .zip porque el foro no deja adjuntar directamente archivos python.

El cambio más importante, es que todos los objetos que creas deben estar en una lista, y en cada vuelta del bucle se debe llamar el método update() sobre cada elemento de la lista. El problema en tu versión anterior es que sólo ejecutabas update() sobre el jugador, y sólo una vez sobre el disparo en el momento de ser creado. Por eso no se movía. Verás que he creado la lista actores[], a la que añado jugador, enemigo, y cada instancia del disparo que se crea. Ahora todo se mueve y actualiza como esperas. De igual forma, cuando un disparo cruza la parte superior de la pantalla y se desactiva su sprite, también lo quito de la lista actores[].

Al comprobar la colisión, lo hace sobre cada elemento de la lista actores[] que sea del tipo Enemigo, así puedes tener muchos enemigos activos.

Otro pequeño cambio es la variable fuego en Jugador: es para que sólo se genere un disparo cada vez que pulsas la tecla. Si no, se genera un disparo en cada fotograma mientras esté pulsada, creando un efecto no deseado. Si quisieras implementar autodisparo en vez de un disparo por pulsación, habría que sustituir fuego por un contador de fotogramas, para que sólo te deje generar un disparo cada n fotogramas.

También he sustituido las llamadas a engine.sprites[self.sprite_id].xxx directamente por sprite.xxx. Hace lo mismo, pero el código queda más compacto evitando repetición.

Ánimo!
Gracias. Wink 

Voy a mirarlo y si hay algo que no entienda te pregunto,a partir de ahora cada vez que te haga una pregunta subire el proyecto con lo que estoy haciendo porque creo que asi queda mas clara la pregunta.

Saludos y no salgas de casa. Shy
Hola de nuevo,tengo algunas preguntas.

1-"isinstance(actor, Enemigo)",no habia visto nunca esta funcion,puedes explicarme para que sirve.

2-En tu ejemplo solo habia un sprite sonic,he añadido mas enemigos a la lista pero solo sigue saliendo un solo sprite de sonic,como puedo hacer para que salgan mas sprites de sonic en pantalla.

3- He añadido el metodo colision en la clase de disparo jugador,este metodo sirve para que cuando el disparo colisione con el sprite de sonic se desactive el disparo pero al colisionar aparece un error relacionado con la lista y no se muy bien como solucionar esto.

Te paso el ejemplo actualizado.
https://drive.google.com/file/d/1KNcJxbb...sp=sharing
Veo que has implementado el autodisparo tú solo, perfecto  Cool

La función isinstance() devuelve si el objeto pasado en el primer parámetro pertenece a la clase pasada en el segundo parámetro:
https://www.w3schools.com/python/ref_fun...stance.asp

Tu segundo y tercer problema están relacionados:

Fíjate que lo que haces, es instanciar un único enemigo y añadirlo tres veces a la lista:

Code:
enemigo = Enemigo()
actores.append(enemigo)
actores.append(enemigo)
actores.append(enemigo)

Lo que en realidad pretendes es crear tres instancias diferentes, y añadirlas a la lista:

Code:
actores.append(Enemigo())
actores.append(Enemigo())
actores.append(Enemigo())

No hace falta que primero asignes la instancia a una variable y luego la pases a la lista, puedes pasarle directamente el constructor que te genera una instancia nueva tal como hago.

Por eso sólo veías un enemigo, y luego la lista daba error al intentar eliminar un elemento que no existía.

Colisiones
Te pongo sober aviso de lo que te vas a encontrar. La funcionalidad enable_collision() / check_collision() de momento te vale para saber si un determinado sprite está colisionando a nivel de píxel con otro, pero no te permite saber con cual. Si hay muy pocos objetos o tipos de objetos como hasta ahora ya te valía, pero ahora que empiezas a añadir más tipos de objetos y más cantidad, no puedes usarla por sí sola. Debe ser el complemento de un algoritmo de colisiones 2D que te permita discriminar exactamente cuales son los objetos.

Te recomiendo encarecidamente el siguiente artículo, está en castellano, muy bien explicado, y usa python:
https://www.genbeta.com/desarrollo/teori...os-basicos

No salgo de casa Angel
Gracias por la informacion. Wink

Yo pensaba que con la funcion de colision que trae tilengine me serviria para cosas sencillas como para un juego sencillo de naves,pero parece que hay que entrar en terreno pantanoso,vere si consigo algo yo solo,si no volvere a molestarte. Angel
No es terreno pantanoso, es algo que todo juego 2D debe implementar, pero a la vez es sencillo y lo puedes reutilizar en cualquier juego tal cual.

Por cada actor, tienes que mantener como propiedad interna su rectángulo envolvente o "hitbox" como se la conoce también. El rectángulo se obtiene sumando el ancho y el alto (que debería ser constante durante toda la vida del actor) a la posición x,y que va cambiando.

Luego necesitas una función que te devuelve si dos rectángulos intersectan. Eso está más que inventado, sólo tienes que copiarlo del enlace que te he puesto antes.

Finalmente, en el método update() de cada objeto, tienes que recorrer la lista de actores[] y mirar si para el objeto que estás ejecutando, su "hitbox" intersecta con el "hitbox" de otro actor del tipo que te interese. Por ejemplo en tu objeto DisparoJugador sólo te interesa mirar si intersecta con objetos de tipo Enemigo, pero no con Jugador ni con DisparoJugador, ya que no hay interacción entre ellos.

Normalmente en la práctica el hitbox se reduce un poco respecto al tamaño real del sprite para dar un poco de margen. Si no, puedes encontrarte que aunque visualmente veas que dos objetos no han colisionado, porque sus píxeles no se tocan, el juego detecta una colisión y tu personaje muere porque una bala le ha pasado cerca pero sin tocarlo. Injusto! Al encoger el hitbox este efecto se minimiza. Pero si te pasas, puede pasarte lo contrario: que visualmente veas que dos objetos chocan, pero no llegue a detectarlo. Al final es un equilibrio entre falsos positivos y falsos negativos: cuanto más intentes minimizar uno, más se te disparará el otro.

La función de colisión de tilengine puede servirte para darte ese extra de precisión a nivel de píxel: una vez detectas una colisión de rectángulos, entonces miras si en ambos sprites (tiene que ser en los dos) devuelven check_collision() a True. Si no, puedes descartar la colisión aunque sus hitbox intersecten.

De todas formas la mayoría de engines 2D no tienen este nivel de precisión de píxel, y aproximan con rectángulos o círculos como en el enlace que te he pasado. Realmente muy pocos modelos de gameplay justifican esa precisión de pixel. Si no lo activas puedes ganar algo de rendimiento, ya que de promedio un sprite con colisión de píxel activada puede requerir el doble de tiempo de CPU en dibujarse que uno sin activar.

Ánimo!
Una pregunta.

¿Como puedo saber el ancho y alto del sprite donde quiero implementar la funcion de colision?
Pages: 1 2