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.
![]()
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
}
| Campo | Descripción |
|---|---|
frameWidth | Ancho de cada fotograma en píxeles |
frameHeight | Alto de cada fotograma en píxeles |
totalFrames | Número total de fotogramas |
fps | Fotogramas por segundo de la animación |
suffix | Dígitos en el sufijo numérico (ej: 2 = _00, _01...) |
backgroundcolor | Color 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)
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
| Constante | Valor | Descripción |
|---|---|---|
Sprite.VISUAL | 0 | Sin física, solo visual |
Sprite.DYNAMIC | 1 | Afectado por física (gravedad, colisiones) |
Sprite.STATIC | 2 | Inmóvil, pero colisiona con otros |
Sprite.KINEMATIC | 3 | Movido por código, colisiona con dinámicos |
Tipos de collider
| Constante | Valor | Descripción |
|---|---|---|
Sprite.RECTANGLE | 0 | Collider rectangular |
Sprite.CIRCLE | 1 | Collider 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
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
accelerationsprite.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.
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.
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
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])
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).
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ámetro | Tipo | Descripción |
|---|---|---|
callback | function/string | Referencia directa (miFuncion), método de un objeto (objeto.metodo) o nombre en string. nil desactiva el callback |
minImpulso | integer/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)
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.
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ámetro | Tipo | Descripción |
|---|---|---|
fx | number | Componente X de la fuerza |
fy | number | Componente 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ámetro | Tipo | Descripción |
|---|---|---|
ix | number | Componente X del impulso |
iy | number | Componente 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ámetro | Tipo | Descripción |
|---|---|---|
t | number | Par 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ámetro | Tipo | Descripción |
|---|---|---|
fx | number | Componente X de la fuerza |
fy | number | Componente Y de la fuerza |
px | number | Coordenada X del punto de aplicación (mundo) |
py | number | Coordenada 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
- Usa
hits()con una lista en lugar de múltiples llamadas individuales cuando compruebes contra muchos sprites - Los sprites con
bodyType = Sprite.VISUALno 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
| Plataforma | Soporte | Notas |
|---|---|---|
| Windows | ✅ | Soporte completo |
| ESP32 | ✅ | Soporte completo con LittleFS/SD |
| Emscripten (Web) | ✅ | Soporte completo |