Saltar al contenido principal

Módulo Sprite

El módulo Sprite proporciona un sistema completo para crear, animar y gestionar objetos gráficos 2D con soporte para detección de colisiones y física integrada. Es el núcleo para el desarrollo de videojuegos y aplicaciones interactivas en JARU.

Captura del módulo Sprite de JARU para cargar sprites, animar frames y detectar colisiones

Los sprites pueden contener múltiples fotogramas para animaciones, detectar colisiones entre sí (incluyendo sprites rotados), y opcionalmente participar en simulaciones físicas con gravedad, velocidad, fricción y elasticidad.

Uso

use Display
use Bitmap
use Sprite

Funciones del módulo

load

La función load(ruta) carga un sprite desde un archivo de definición JSON junto con sus imágenes BMP asociadas.

var jugador = Sprite.load("personaje.spr")

El archivo JSON debe tener la siguiente estructura:

{
"frameWidth": 32,
"frameHeight": 32,
"totalFrames": 8,
"fps": 12,
"suffix": 2,
"backgroundcolor": 0
}
CampoDescripción
frameWidthAncho de cada fotograma en píxeles
frameHeightAlto de cada fotograma en píxeles
totalFramesNúmero total de fotogramas
fpsFotogramas por segundo de la animación
suffixDígitos en el sufijo numérico (ej: 2 = _00, _01...)
backgroundcolorColor de fondo a tratar como transparente (valor entero ARGB, opcional)

Los archivos de imagen deben nombrarse siguiendo el patrón: nombrebase_00.bmp, nombrebase_01.bmp, etc.

new

La función new() crea un nuevo sprite a partir de un bitmap o copiando los fotogramas de otro sprite existente.

Crear desde bitmap

var imagen = Bitmap.load("nave.bmp")

// Crear en posición (0, 0)
var nave = Sprite.new(imagen)

// Crear en posición específica
var nave2 = Sprite.new(100, 50, imagen)

Clonar desde otro sprite

// Crear sprite original
var enemigo = Sprite.load("enemigo.spr")

// Crear una copia en (0, 0) que comparte los mismos gráficos
var enemigo2 = Sprite.new(enemigo)

// Crear copias en posición específica
var enemigo3 = Sprite.new(200, 100, enemigo)
var enemigo4 = Sprite.new(300, 100, enemigo)
Compartir gráficos

Cuando creas un sprite desde otro sprite, los fotogramas se comparten en memoria. Esto es muy eficiente para crear múltiples instancias del mismo personaje o enemigo.

Constantes

Tipos de cuerpo físico

ConstanteValorDescripción
Sprite.VISUAL0Sin física, solo visual
Sprite.DYNAMIC1Afectado por física (gravedad, colisiones)
Sprite.STATIC2Inmóvil, pero colisiona con otros
Sprite.KINEMATIC3Movido por código, colisiona con dinámicos

Tipos de collider

ConstanteValorDescripción
Sprite.RECTANGLE0Collider rectangular
Sprite.CIRCLE1Collider circular

Propiedades de instancia

Las propiedades se acceden sin paréntesis para leer y con = para asignar:

// Leer una propiedad
var posX = sprite.x

// Asignar una propiedad
sprite.x = 100

Posición y transformación

x

Obtiene o establece la posición X del sprite en píxeles.

// Obtener posición X
var posX = sprite.x

// Establecer posición X
sprite.x = 100

y

Obtiene o establece la posición Y del sprite en píxeles.

// Obtener posición Y
var posY = sprite.y

// Establecer posición Y
sprite.y = 200

pos

Establece la posición del sprite. Acepta dos argumentos o una lista [x, y].

// Establecer posición con dos argumentos
sprite.pos(100, 200)

// O mediante asignación de lista
sprite.pos = [100, 200]

angle

Obtiene o establece el ángulo de rotación en grados.

// Obtener ángulo
var angulo = sprite.angle

// Rotar 45 grados
sprite.angle = 45

// Rotar continuamente
sprite.angle = sprite.angle + 1

spin

Obtiene o establece la velocidad de rotación automática en grados por segundo.

// Rotar automáticamente a 90 grados por segundo
sprite.spin = 90

// Detener rotación
sprite.spin = 0

// Rotar en sentido contrario
sprite.spin = -45

spin es la rotación manual del sprite. En cuerpos DYNAMIC, las colisiones pueden añadir además rotación física (ver angularVel y rotates en la sección de Física); ambas rotaciones se suman.

pivot

Obtiene o establece el punto de pivote (centro de rotación) del sprite.

// Obtener pivot actual
var p = sprite.pivot
println("Pivot: ", p[0], ", ", p[1])

// Establecer pivot con dos argumentos
sprite.pivot(0, 0)

// O mediante asignación de lista
sprite.pivot = [16, 16]

Por defecto, el pivot está en el centro del sprite.

Dimensiones

width

Obtiene el ancho del sprite en píxeles (solo lectura).

var ancho = sprite.width

height

Obtiene el alto del sprite en píxeles (solo lectura).

var alto = sprite.height

Estado

active

Obtiene o establece si el sprite está activo. Los sprites inactivos no se renderizan ni colisionan.

// Desactivar sprite
sprite.active = false

// Activar sprite
sprite.active = true

// Verificar si está activo
if (sprite.active) then
println("El sprite está activo")
end

Apariencia

flipX

Obtiene o establece si el sprite está volteado horizontalmente.

// Voltear horizontalmente
sprite.flipX = true

// Restaurar orientación
sprite.flipX = false

// Consultar estado
if (sprite.flipX) then
println("Sprite volteado en X")
end

flipY

Obtiene o establece si el sprite está volteado verticalmente.

// Voltear verticalmente
sprite.flipY = true

// Restaurar orientación
sprite.flipY = false

Animación

frame

Obtiene o establece el fotograma actual de la animación.

// Obtener frame actual
var f = sprite.frame

// Ir al frame 3
sprite.frame = 3

frames

Obtiene el número total de fotogramas (solo lectura).

var total = sprite.frames
println("Total de frames: ", total)

frameRate

Obtiene o establece la velocidad de animación en milisegundos por fotograma.

// Obtener velocidad actual
var velocidad = sprite.frameRate

// Establecer animación más rápida (50ms por frame = 20 fps)
sprite.frameRate = 50

loop

Obtiene o establece si la animación se repite en bucle.

// Desactivar bucle (la animación se detiene en el último frame)
sprite.loop = false

// Activar bucle
sprite.loop = true

yoyo

Obtiene o establece el modo de animación ping-pong (ida y vuelta).

// Activar animación ping-pong
sprite.yoyo = true

// Desactivar
sprite.yoyo = false

dir

Obtiene o establece la dirección de la animación: 1 (adelante) o -1 (atrás).

// Reproducir animación hacia atrás
sprite.dir = -1

// Reproducir hacia adelante
sprite.dir = 1

Colisiones

collider

Obtiene o establece los parámetros del collider.

// Obtener datos del collider
var datos = sprite.collider
// Para rectángulo: [x, y, ancho, alto]
// Para círculo: [x, y, radio]

// Establecer collider rectangular
sprite.collider = [0, 0, 32, 32]

// Establecer collider circular
sprite.collider = [16, 16, 16] // centro en (16,16), radio 16

colliderType

Obtiene el tipo de collider actual (solo lectura).

var tipo = sprite.colliderType
if (tipo == Sprite.RECTANGLE) then
println("Collider rectangular")
elsif (tipo == Sprite.CIRCLE) then
println("Collider circular")
end

Física

Los cuerpos DYNAMIC no solo se desplazan: también giran al colisionar. Una caja que cae de canto vuelca, una bola rueda por una rampa — también contra un GridMap. La rotación física se suma a spin (la rotación manual) y puede desactivarse por sprite con rotates.

bodyType

Obtiene o establece el tipo de cuerpo físico.

// Convertir a cuerpo dinámico (afectado por física)
sprite.bodyType = Sprite.DYNAMIC

// Convertir a estático (inmóvil pero colisiona)
sprite.bodyType = Sprite.STATIC

// Volver a solo visual (sin física)
sprite.bodyType = Sprite.VISUAL

velocity

Obtiene o establece la velocidad como lista [vx, vy].

// Obtener velocidad actual
var vel = sprite.velocity
println("Velocidad: ", vel[0], ", ", vel[1])

// Establecer velocidad
sprite.velocity = [100, -50] // 100 px/s derecha, 50 px/s arriba
Sprites muy rápidos

Dos sprites que se muevan muy deprisa pueden atravesarse sin llegar a chocar, porque entre un fotograma y el siguiente saltan de un lado al otro del otro cuerpo.

Contra el mapa de tiles esto no ocurre: la colisión con el GridMap sí tiene en cuenta el recorrido completo del sprite.

force

Obtiene o establece la fuerza pendiente como lista [fx, fy]. Es una fuerza de un solo paso: en el siguiente scene.update() el motor la integra (Δv = fuerza / masa · dt) y la deja a cero. Para un empuje mantenido (motor, viento, propulsor) hay que reasignarla en cada frame — o llamar a applyForce() dentro del bucle, que es equivalente (la asignación machaca la fuerza acumulada del frame; applyForce() suma sobre ella).

// Empuje mantenido: reasignar cada frame, antes de scene.update()
while (true)
cohete.force = [0, -300] // empuje hacia arriba
scene.update()
Display.update()
end
Alias acceleration

sprite.acceleration es un alias histórico de force y se retirará en la versión 1.0. El nombre antiguo era engañoso: el valor se divide por la masa (con mass = 4, force = [200, 0] produce una aceleración efectiva de 50 px/s², no 200) y se consume tras cada paso — después de un scene.update() la propiedad se lee como [0, 0].

friction

Obtiene o establece la fricción del sprite.

// Obtener fricción
var f = sprite.friction

// Establecer fricción
sprite.friction = 0.5

La fricción también gobierna el giro por colisión: es lo que hace que una bola ruede en vez de deslizar. Un sprite con friction = 0 (el valor por defecto) es un cojinete perfecto — nada frena su rotación una vez empieza a girar.

mass

Obtiene o establece la masa del sprite.

// Sprite pesado
sprite.mass = 10

// Sprite ligero
sprite.mass = 0.5

elasticity

Obtiene o establece la elasticidad (rebote) del sprite.

// Muy elástico (rebota mucho)
sprite.elasticity = 0.9

// Sin rebote
sprite.elasticity = 0

rotates

Obtiene o establece si las colisiones pueden hacer girar al cuerpo (solo tiene efecto en cuerpos DYNAMIC). Activado por defecto.

// Personaje de plataformas: siempre de pie
jugador.rotates = false

// Caja que vuelca y rueda (comportamiento por defecto)
caja.rotates = true

Con rotates = false el sprite recupera el comportamiento clásico: la física mueve el cuerpo pero nunca cambia su ángulo. Es lo recomendado para héroes de plataformas y en general para cualquier sprite que deba mantenerse de pie.

Colliders diminutos

Un collider de muy pocos píxeles que además pueda girar resulta inestable al apoyarse sobre otros cuerpos. Si te ocurre, desactiva el giro con rotates = false.

angularVel

Obtiene o establece la velocidad angular física en grados por segundo: el giro que las colisiones imprimen al cuerpo. Es independiente de spin (ambas rotaciones se suman). Existe un tope de seguridad de 720 grados por segundo.

// Leer la velocidad de giro actual
var w = sprite.angularVel

// Lanzar el sprite girando
sprite.angularVel = 180

// Parar el giro (por ejemplo, al reciclar un cuerpo)
sprite.angularVel = 0

angularDamping

Obtiene o establece la amortiguación angular del cuerpo (0 por defecto, sin amortiguación). Frena el giro gradualmente, incluso sin contacto con otras superficies.

La forma física de frenar un giro es la fricción de contacto (friction), pero cuando un cuerpo rueda sin deslizar la fricción ya no puede detenerlo: para eso está angularDamping.

// Bola que deja de girar poco a poco
bola.angularDamping = 0.5

// Casi sin pérdida de giro
peonza.angularDamping = 0.05

bullet

Obtiene o establece la colisión continua (CCD) del cuerpo: desactivada por defecto. Con bullet = true el motor no comprueba solo dónde está el cuerpo al final de cada paso, sino que barre el trayecto completo que ha recorrido durante el paso, de modo que un proyectil rápido no puede atravesar a otro cuerpo por quedar el impacto justo entre dos pasos.

// Bala de un shooter: a 6000 px/s recorre 100 px por paso, mucho mas
// que el grosor de una pared
var bala = Sprite.new(0, 80, plantillaBala)
bala.bodyType = Sprite.DYNAMIC
bala.bullet = true
bala.velocity = [6000, 0]

Sin bullet, un cuerpo que avanza más que su propio tamaño en un solo paso puede aparecer al otro lado de un obstáculo delgado sin haberlo tocado nunca. Es el clásico tunneling, y solo se nota con cuerpos muy rápidos o colliders muy finos: para velocidades normales la detección discreta basta y sale más barata.

Cuándo activarlo

Actívalo únicamente en los sprites que lo necesiten (balas, discos de pinball, cuerpos que caen desde muy alto). No es un interruptor global de calidad: cada cuerpo marcado paga un barrido contra el resto de cuerpos en cada paso, así que marcar la escena entera es caro y no aporta nada.

Solo surte efecto en cuerpos DYNAMIC. En STATIC, KINEMATIC o VISUAL el valor se guarda y se ignora, de modo que puedes asignar bullet antes o después de bodyType sin preocuparte del orden.

// Las dos formas son equivalentes
a.bodyType = Sprite.DYNAMIC
a.bullet = true

b.bullet = true
b.bodyType = Sprite.DYNAMIC
Un cuerpo bala nunca se duerme

El auto-dormido de la escena (scene.sleeping) excluye a los cuerpos marcados como bala. Si marcas un sprite permanente del escenario, lo estás sacando del ahorro de CPU para siempre. Para proyectiles, que viven poco, esto no importa.

La colisión contra un GridMap ya usa barrido para todos los cuerpos, marcados o no: bullet afecta únicamente a la colisión cuerpo contra cuerpo.

El motor resuelve como mucho dos impactos por paso y por bala. A partir del segundo, la bala se queda en la última posición segura y continúa en el paso siguiente: nunca atraviesa nada, pero puede parecer que se frena un instante en una esquina muy cerrada.

Métodos de instancia

Los métodos se invocan siempre con paréntesis:

sprite.nextFrame()
var resultado = sprite.hits(otro)

hits

Comprueba colisiones del sprite contra otro sprite, una lista o un array. Devuelve una lista con los sprites que colisionan, o nil si no hay colisión.

// Contra un solo sprite
var resultado = jugador.hits(enemigo)
if (resultado != nil) then
println("¡Colisión detectada!")
end

// Contra una lista de sprites
var enemigos = [enemigo1, enemigo2, enemigo3]
var colisiones = jugador.hits(enemigos)
if (colisiones != nil) then
foreach (e in colisiones)
e.active = false
end
end

El sistema de colisiones utiliza una fase amplia (broadphase) con círculos envolventes antes de la detección precisa (narrowphase). Soporta:

  • Rectángulo vs Rectángulo (incluyendo rotación con SAT)
  • Círculo vs Círculo
  • Círculo vs Rectángulo

hitsGrid

Comprueba colisiones del sprite contra un GridMap. Devuelve una lista de resultados [tileX, tileY, tileValue, normalX, normalY] por cada tile que colisiona, o nil si no hay colisión.

Sin segundo argumento (tiles sólidos y plataformas del GridMap)

var colisiones = jugador.hitsGrid(mapa)
if (colisiones != nil) then
foreach (info in colisiones)
println("Tile en (", info[0], ",", info[1], ") valor=", info[2])
println("Normal: ", info[3], ", ", info[4])
end
end

Con tiles personalizados

// Comprobar contra un tile específico (por valor)
var colisiones = jugador.hitsGrid(mapa, 5)

// Comprobar contra una lista de valores de tile
var colisiones = jugador.hitsGrid(mapa, [1, 2, 5])
Plataformas one-way

Cuando se usa sin segundo argumento, hitsGrid maneja automáticamente las plataformas definidas en el GridMap como plataformas one-way: solo registra colisión si el sprite venía desde arriba (su borde inferior previo estaba por encima del tile).

Máximo 8 tiles por llamada

hitsGrid devuelve como mucho 8 tiles. Es de sobra para sprites de tamaño normal, pero un collider que cubra muchas casillas a la vez no recibirá todas.

onGround

Devuelve true si el sprite está tocando el suelo (solo lectura). Requiere que el sprite tenga física habilitada.

if (jugador.onGround()) then
println("En el suelo")
// Permitir saltar
if (GPIO.read(15) == HIGH) then
jugador.velocity = [jugador.velocity[0], -300]
end
end

onCollision

Registra un callback que el motor de físicas ejecuta automáticamente cuando el sprite empieza a tocar otro sprite. Es la forma reactiva de tratar los impactos: en vez de consultar hits() cada frame, el motor te avisa con la fuerza del golpe.

sprite.onCollision(callback)
sprite.onCollision(callback, minImpulso)
sprite.onCollision(nil) // desactivar
ParámetroTipoDescripción
callbackfunction/stringReferencia directa (miFuncion), método de un objeto (objeto.metodo) o nombre en string. nil desactiva el callback
minImpulsointeger/float(Opcional) Impulso mínimo del contacto para disparar. Por defecto 0 (todo contacto nuevo dispara)

El callback recibe cinco parámetros:

func alChocar(yo, otro, impulso, nx, ny)
// yo: el sprite que registró el callback
// otro: el sprite contra el que ha chocado
// impulso: fuerza del golpe (impulso normal acumulado del solver)
// nx, ny: normal del contacto, orientada de 'yo' hacia 'otro'
end

Requiere una escena con físicas (scene.physics = true) y que ambos sprites participen en la simulación (DYNAMIC, STATIC o KINEMATIC). Los eventos se disparan solo al empezar el contacto: una caja apoyada en el suelo no genera eventos mientras repose, y si los cuerpos se separan y vuelven a chocar (un rebote) se genera un evento nuevo. Si ambos sprites tienen callback, cada uno recibe el suyo.

Ejemplo: proyectil que destruye al superar una fuerza

use Sprite, Scene

class Cerdo
def init(spr)
this.spr = spr
this.vivo = true
// Solo los golpes fuertes hacen daño
spr.onCollision(this.recibir, 300)
end

def recibir(yo, otro, impulso, nx, ny)
this.vivo = false
yo.active = false
println("¡Impacto de fuerza ", impulso, "!")
end
end

var escena = Scene.new()
escena.physics = true
escena.gravity = 400

var piedra = Sprite.new(20, 100, tpl)
piedra.bodyType = Sprite.DYNAMIC
piedra.mass = 4
escena.addSprite(piedra)

var objetivo = Sprite.new(200, 100, tpl)
objetivo.bodyType = Sprite.DYNAMIC
escena.addSprite(objetivo)

var cerdo = Cerdo(objetivo) // registra objetivo.onCollision(cerdo.recibir, 300)

piedra.vx = 300 // ¡lanzar!

Con un método como callback (this.recibir), el sprite mantiene viva la instancia mientras el callback esté registrado.

Para reacciones sencillas, una lambda inline evita definir función aparte:

caja.onCollision(fn(yo, otro, impulso, nx, ny):
println("¡golpe de ", impulso, "!")
end, 100)
Filtrar el ruido con minImpulso

Al asentarse tras una caída, un cuerpo puede generar varios contactos consecutivos de poca fuerza (micro-rebotes del solver). Con un minImpulso acorde al juego, solo los golpes que importan disparan el callback.

Firma exacta

El callback debe aceptar exactamente 5 parámetros (yo, otro, impulso, nx, ny). Si la firma no coincide, el evento se descarta de forma silenciosa. Las colisiones contra tiles de un GridMap no generan eventos (usa hitsGrid para eso); si el "suelo" debe avisar, créalo como sprite STATIC.

applyForce

Empuja el cuerpo con una fuerza que se consume en el siguiente paso de física (Δv = F / masa · dt) y después se descarta. Equivale a sumar sobre la propiedad force. Para un empuje mantenido, llámalo en cada frame del bucle.

// Propulsor: empuje continuo mientras se mantenga pulsado
while (true)
if (Input.pressed(ACT_THRUST)) then
nave.applyForce(0, -500)
end
scene.update()
Display.update()
end
ParámetroTipoDescripción
fxnumberComponente X de la fuerza
fynumberComponente Y de la fuerza

Requiere que el sprite participe en la física (por ejemplo, bodyType = Sprite.DYNAMIC).

applyImpulse

Aplica un impulso instantáneo: la velocidad cambia de inmediato (Δv = impulso / masa), sin esperar al paso de física. Es la herramienta natural para saltos, disparos y explosiones.

// Salto: cambio de velocidad inmediato, proporcional a la masa
jugador.applyImpulse(0, -300 * jugador.mass)

// Empujón lateral de una explosión
caja.applyImpulse(150, -80)
ParámetroTipoDescripción
ixnumberComponente X del impulso
iynumberComponente Y del impulso

Requiere que el sprite participe en la física (por ejemplo, bodyType = Sprite.DYNAMIC).

applyTorque

Aplica un par (torque) que hace girar al cuerpo luchando contra su inercia. Como applyForce, se consume en el siguiente paso de física y se descarta: para un giro sostenido, llámalo cada frame. La aceleración angular resultante es t / inercia en grados/s².

// Peonza: acelerar el giro mientras se mantiene pulsado
while (true)
if (Input.pressed(ACT_SPIN)) then
peonza.applyTorque(4000)
end
scene.update()
Display.update()
end
ParámetroTipoDescripción
tnumberPar aplicado. Positivo = sentido horario (con Y hacia abajo)

La inercia depende de la forma y la masa del collider: m·(ancho² + alto²)/12 para rectángulos y m·radio²/2 para círculos. Un cuerpo con rotates = false ignora el torque, y el giro resultante respeta el tope de seguridad de ±720 °/s y el angularDamping.

applyForceAt

Aplica una fuerza en un punto concreto del mundo: además del empuje lineal (idéntico a applyForce), genera el par r × F alrededor del centro del collider. Es el empujón excéntrico clásico: golpear una caja cerca de una esquina la desplaza y la hace girar.

// Golpe excéntrico: empuja y voltea la caja
// (el punto está 8px por debajo del centro del collider de 16x16)
caja.applyForceAt(300, 0, caja.x + 8, caja.y + 16)
ParámetroTipoDescripción
fxnumberComponente X de la fuerza
fynumberComponente Y de la fuerza
pxnumberCoordenada X del punto de aplicación (mundo)
pynumberCoordenada Y del punto de aplicación (mundo)

Si el punto coincide con el centro del collider el resultado es una fuerza pura (sin giro), y cuanto mayor es el brazo de palanca, más gira. Como toda fuerza, se consume en el siguiente paso: para un empuje sostenido, llámalo cada frame.

nextFrame

Avanza manualmente al siguiente fotograma.

sprite.nextFrame()

addImage

Añade un bitmap como nuevo fotograma al sprite.

var sprite = Sprite.new(Bitmap.load("frame0.bmp"))
sprite.addImage(Bitmap.load("frame1.bmp"))
sprite.addImage(Bitmap.load("frame2.bmp"))

println("Total frames: ", sprite.frames) // 3

mem

Devuelve el uso de memoria en bytes de todos los fotogramas del sprite.

var memoria = sprite.mem()
println("Memoria usada: ", memoria, " bytes")

Ejemplo 1: Movimiento en línea recta

El sprite se mueve hacia la derecha a velocidad constante. Cuando sale de la pantalla, reaparece por la izquierda.

use Display, Bitmap, Sprite

var draw=Display.draw

// Abrir display en modo paisaje
Display.open(320, 240)
Display.orientation(1)

// Cargar la imagen y crear el sprite
var imagen = Bitmap.load("Images/nave.bmp")
var nave = Sprite.new(imagen)

// Posición inicial y velocidad
nave.x = 0
nave.y = 100
var velocidad = 4

// Bucle principal
while (true)
// Actualizar posición
nave.x = nave.x + velocidad

// Si sale por la derecha, volver a entrar por la izquierda
if (nave.x > 320) then
nave.x = 0 - nave.width
end

// Dibujar y actualizar pantalla
draw.sprite(nave)
Display.update()
pause(32)
end

Ejemplo 2: Rebote en los bordes

use Display, Bitmap, Sprite

var draw=Display.draw

// Dimensiones de la pantalla
var PANTALLA_W = 320
var PANTALLA_H = 240

Display.open(PANTALLA_W, PANTALLA_H)
Display.orientation(1)

// Cargar imagen y crear sprite
var imagen =Bitmap.load("Images/nave.bmp")
var nave = Sprite.new(imagen)

// Posición inicial (centro de pantalla)
nave.x = 160
nave.y = 120

// Velocidad en cada eje (píxeles por frame)
var vx = 3
var vy = 2

// Bucle principal
while (true)
// Mover el sprite
nave.x = nave.x + vx
nave.y = nave.y + vy

// Rebotar en el borde derecho e izquierdo
if (nave.x + nave.width >= PANTALLA_W) then
nave.x = PANTALLA_W - nave.width
vx = vx * -1
end
if (nave.x <= 0) then
nave.x = 0
vx = vx * -1
end

// Rebotar en el borde inferior y superior
if (nave.y + nave.height >= PANTALLA_H) then
nave.y = PANTALLA_H - nave.height
vy = vy * -1
end
if (nave.y <= 0) then
nave.y = 0
vy = vy * -1
end

// Dibujar y actualizar pantalla
draw.sprite(nave)
Display.update()
pause(32)
end

Ejemplo 3 : Rotar mientras se mueve

use Display, Bitmap, Sprite

var draw=Display.draw

// Dimensiones de la pantalla
var PANTALLA_W = 320
var PANTALLA_H = 240

Display.open(PANTALLA_W, PANTALLA_H)
Display.orientation(1)

var imagen =Bitmap.load("Images/nave.bmp")
var nave = Sprite.new(imagen)

nave.x = 160
nave.y = 120
nave.spin = 10 // 120 grados por segundo

var vx = 3
var vy = 2

while (true)
nave.x = nave.x + vx
nave.y = nave.y + vy

if (nave.x + nave.width >= PANTALLA_W) then
nave.x = PANTALLA_W - nave.width
vx = vx * -1
end
if (nave.x <= 0) then
nave.x = 0
vx = vx * -1
end
if (nave.y + nave.height >= PANTALLA_H) then
nave.y = PANTALLA_H - nave.height
vy = vy * -1
end
if (nave.y <= 0) then
nave.y = 0
vy = vy * -1
end

draw.sprite(nave)
Display.update()
pause(32)
end

Consideraciones de rendimiento

Optimización
  • Usa hits() con una lista en lugar de múltiples llamadas individuales cuando compruebes contra muchos sprites
  • Los sprites con bodyType = Sprite.VISUAL no participan en cálculos de física
  • Comparte gráficos creando sprites desde otros sprites en lugar de cargar el mismo archivo múltiples veces
  • El sistema usa detección de fase amplia (bounding circles) antes de la detección precisa para optimizar

Plataformas soportadas

PlataformaSoporteNotas
WindowsSoporte completo
ESP32Soporte completo con LittleFS/SD
Emscripten (Web)Soporte completo