← Volver al listado de tecnologías
Desarrollo de Juegos
Game Loop en Kivy
El game loop básico usa Clock.schedule_interval.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.clock import Clock
class JuegoWidget(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_interval(self.update, 1/60) # 60 FPS
def update(self, dt):
# Lógica del juego
# dt = tiempo desde última actualización
pass
Movimiento Básico
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty
from kivy.vector import Vector
class Pelota(Widget):
velocidad_x = NumericProperty(0)
velocidad_y = NumericProperty(0)
def mover(self):
self.pos = Vector(self.velocidad_x, self.velocidad_y) + self.pos
<Pelota>:
size: 50, 50
canvas:
Ellipse:
pos: self.pos
size: self.size
Detección de Colisiones
Colisión Rectangular
def colisiona(self, otro):
return self.collide_widget(otro)
Colisión por Punto
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
print('Tocado!')
return True
Colisión Circular
from kivy.vector import Vector
def colision_circular(self, otro):
distancia = Vector(self.center).distance(otro.center)
radio_suma = (self.width + otro.width) / 2
return distancia < radio_suma
Colisión con Bordes
def verificar_bordes(self, ventana_size):
# Rebote horizontal
if self.x < 0 or self.right > ventana_size[0]:
self.velocidad_x *= -1
# Rebote vertical
if self.y < 0 or self.top > ventana_size[1]:
self.velocidad_y *= -1
Ejemplo: Pong Simplificado
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty, ObjectProperty
from kivy.vector import Vector
from kivy.clock import Clock
class Pelota(Widget):
velocidad_x = NumericProperty(0)
velocidad_y = NumericProperty(0)
velocidad = ReferenceListProperty(velocidad_x, velocidad_y)
def mover(self):
self.pos = Vector(*self.velocidad) + self.pos
class Paleta(Widget):
puntaje = NumericProperty(0)
def rebotar_pelota(self, pelota):
if self.collide_widget(pelota):
pelota.velocidad_x *= -1.1
class PongGame(Widget):
pelota = ObjectProperty(None)
jugador1 = ObjectProperty(None)
jugador2 = ObjectProperty(None)
def servir(self):
self.pelota.center = self.center
self.pelota.velocidad = Vector(4, 0).rotate(30)
def update(self, dt):
self.pelota.mover()
# Rebote en paletas
self.jugador1.rebotar_pelota(self.pelota)
self.jugador2.rebotar_pelota(self.pelota)
# Rebote superior/inferior
if self.pelota.y < 0 or self.pelota.top > self.height:
self.pelota.velocidad_y *= -1
# Punto
if self.pelota.x < 0:
self.jugador2.puntaje += 1
self.servir()
if self.pelota.right > self.width:
self.jugador1.puntaje += 1
self.servir()
def on_touch_move(self, touch):
if touch.x < self.width / 3:
self.jugador1.center_y = touch.y
if touch.x > self.width * 2 / 3:
self.jugador2.center_y = touch.y
class PongApp(App):
def build(self):
juego = PongGame()
juego.servir()
Clock.schedule_interval(juego.update, 1/60)
return juego
<Pelota>:
size: 50, 50
canvas:
Ellipse:
pos: self.pos
size: self.size
<Paleta>:
size: 25, 200
canvas:
Rectangle:
pos: self.pos
size: self.size
<PongGame>:
pelota: pelota
jugador1: jugador1
jugador2: jugador2
Pelota:
id: pelota
center: root.center
Paleta:
id: jugador1
x: root.width * 0.05
center_y: root.center_y
Paleta:
id: jugador2
x: root.width * 0.95
center_y: root.center_y
Label:
text: str(jugador1.puntaje)
pos: root.width * 0.25, root.height * 0.9
Label:
text: str(jugador2.puntaje)
pos: root.width * 0.75, root.height * 0.9
Input para Juegos
Teclado
from kivy.core.window import Window
class JuegoWidget(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._keyboard = Window.request_keyboard(self._on_keyboard_closed, self)
self._keyboard.bind(on_key_down=self._on_key_down)
self._keyboard.bind(on_key_up=self._on_key_up)
self.teclas_presionadas = set()
def _on_keyboard_closed(self):
self._keyboard.unbind(on_key_down=self._on_key_down)
self._keyboard = None
def _on_key_down(self, keyboard, keycode, text, modifiers):
self.teclas_presionadas.add(keycode[1])
return True
def _on_key_up(self, keyboard, keycode):
self.teclas_presionadas.discard(keycode[1])
return True
def update(self, dt):
if 'left' in self.teclas_presionadas:
self.jugador.x -= 5
if 'right' in self.teclas_presionadas:
self.jugador.x += 5
Touch Multitouch
def on_touch_down(self, touch):
touch.grab(self)
touch.ud['inicial'] = touch.pos
return True
def on_touch_move(self, touch):
if touch.grab_current is self:
dx = touch.x - touch.ud['inicial'][0]
dy = touch.y - touch.ud['inicial'][1]
# Usar dx, dy para control
Sprites y Animación
Sprite Básico
from kivy.uix.image import Image
class Sprite(Image):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.source = 'sprite.png'
self.allow_stretch = True
Animación de Sprites
from kivy.clock import Clock
from kivy.uix.image import Image
class SpriteAnimado(Image):
def __init__(self, frames, fps=12, **kwargs):
super().__init__(**kwargs)
self.frames = frames # Lista de rutas de imagen
self.frame_actual = 0
Clock.schedule_interval(self.siguiente_frame, 1/fps)
def siguiente_frame(self, dt):
self.frame_actual = (self.frame_actual + 1) % len(self.frames)
self.source = self.frames[self.frame_actual]
Con Atlas
# Crear atlas
# python -m kivy.atlas mi_atlas 256x256 sprite*.png
from kivy.uix.image import Image
sprite = Image(source='atlas://mi_atlas/sprite1')
Sistema de Partículas Simple
from kivy.uix.widget import Widget
from kivy.graphics import Color, Ellipse
from kivy.clock import Clock
from random import uniform
class Particula:
def __init__(self, x, y):
self.x = x
self.y = y
self.vx = uniform(-2, 2)
self.vy = uniform(2, 5)
self.vida = 1.0
self.size = uniform(5, 15)
class SistemaParticulas(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.particulas = []
Clock.schedule_interval(self.update, 1/60)
def emitir(self, x, y, cantidad=10):
for _ in range(cantidad):
self.particulas.append(Particula(x, y))
def update(self, dt):
self.canvas.clear()
particulas_vivas = []
with self.canvas:
for p in self.particulas:
p.x += p.vx
p.y += p.vy
p.vy -= 0.1 # Gravedad
p.vida -= dt
if p.vida > 0:
Color(1, 0.5, 0, p.vida)
Ellipse(pos=(p.x, p.y), size=(p.size, p.size))
particulas_vivas.append(p)
self.particulas = particulas_vivas
Sonidos
from kivy.core.audio import SoundLoader
class JuegoWidget(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.sonido_golpe = SoundLoader.load('golpe.wav')
self.musica = SoundLoader.load('musica.mp3')
def reproducir_golpe(self):
if self.sonido_golpe:
self.sonido_golpe.play()
def iniciar_musica(self):
if self.musica:
self.musica.loop = True
self.musica.volume = 0.5
self.musica.play()
KivEnt - Game Engine
Para juegos más complejos, considera KivEnt.
pip install kivent_core
from kivent_core.gameworld import GameWorld
from kivent_core.managers.resource_managers import texture_manager
# KivEnt usa sistema de entidades/componentes
Optimización
Object Pooling
class PoolBalas:
def __init__(self, cantidad):
self.disponibles = [Bala() for _ in range(cantidad)]
self.activas = []
def obtener(self):
if self.disponibles:
bala = self.disponibles.pop()
self.activas.append(bala)
return bala
return None
def devolver(self, bala):
self.activas.remove(bala)
self.disponibles.append(bala)
Usar NumericProperty
# Más eficiente para valores que cambian frecuentemente
velocidad = NumericProperty(0)
Testing de Juegos
# test_juegos.py
import unittest
from kivy.vector import Vector
from kivy.uix.widget import Widget
class TestVector(unittest.TestCase):
def test_suma_vectores(self):
v1 = Vector(1, 0)
v2 = Vector(0, 1)
resultado = v1 + v2
self.assertEqual(resultado.x, 1)
self.assertEqual(resultado.y, 1)
def test_normalizar(self):
v = Vector(3, 4)
n = v.normalize()
self.assertAlmostEqual(n.length(), 1.0)
def test_angulo(self):
v = Vector(1, 0)
self.assertEqual(v.angle(Vector(0, 1)), 90)
class TestColisiones(unittest.TestCase):
def test_collide_widget(self):
w1 = Widget(pos=(0, 0), size=(100, 100))
w2 = Widget(pos=(50, 50), size=(100, 100))
self.assertTrue(w1.collide_widget(w2))
def test_no_collision(self):
w1 = Widget(pos=(0, 0), size=(50, 50))
w2 = Widget(pos=(100, 100), size=(50, 50))
self.assertFalse(w1.collide_widget(w2))
def test_collide_point(self):
w = Widget(pos=(0, 0), size=(100, 100))
self.assertTrue(w.collide_point(50, 50))
self.assertFalse(w.collide_point(150, 150))
if __name__ == '__main__':
unittest.main()
Probar en Dispositivo con ADB
# Monitorear FPS
adb shell dumpsys gfxinfo com.ejemplo.miapp reset
# Jugar por 10 segundos
adb shell dumpsys gfxinfo com.ejemplo.miapp | grep "Total frames"
# Ver uso de CPU (importante para juegos)
adb shell top -n 1 | grep miapp
# Monitorear memoria durante juego
adb shell dumpsys meminfo com.ejemplo.miapp | grep "TOTAL"
# Probar input de juego
adb shell input swipe 200 500 400 500 100 # Swipe rápido
adb shell input tap 300 400 # Tap para disparar
# Grabar gameplay
adb shell screenrecord --bit-rate 6000000 /sdcard/gameplay.mp4
Resumen
Clock.schedule_intervalpara game loop a 60 FPScollide_widgetycollide_pointpara colisionesVectorsimplifica operaciones de movimientoSoundLoaderpara efectos y música- Object pooling para optimizar creación/destrucción