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
}
| 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...) |
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 copias que comparten los mismos gráficos (eficiente en memoria)
var enemigo2 = Sprite.new(200, 100, enemigo)
var enemigo3 = 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.
update
La función update(deltaTime) actualiza el estado de todos los sprites que tienen física habilitada. Debe llamarse una vez por fotograma.
var ultimoTiempo = clock()
while (true)
var ahora = clock()
var dt = ahora - ultimoTiempo
ultimoTiempo = ahora
// Actualizar física de todos los sprites
Sprite.update(dt)
// Renderizar...
Display.update()
end
iters
Propiedad para obtener o establecer el número de iteraciones del solver de física. Más iteraciones = física más precisa pero más lenta.
// Obtener iteraciones actuales
println("Iteraciones: ", Sprite.iters)
// Establecer más iteraciones para física más precisa
Sprite.iters = 5
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
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
Obtiene o establece la posición como una lista [x, y].
// Establecer posición con dos argumentos
sprite.pos(100, 200)
// O mediante asignación de lista
sprite.pos = [100, 200]
// Obtener posición (no soportado como getter directo)
var x = sprite.x()
var y = sprite.y()
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)
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 en la esquina superior izquierda
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
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)
pingPong
Obtiene o establece el modo de animación ping-pong (ida y vuelta).
// Activar animación ping-pong
sprite.pingPong(true)
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)
nextFrame
Avanza manualmente al siguiente fotograma.
sprite.nextFrame()
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
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
acceleration
Obtiene o establece la aceleración como lista [ax, ay].
// Aplicar aceleración
sprite.acceleration = [0, 200] // Empujar hacia abajo
gravity
Obtiene o establece la gravedad individual del sprite.
// Establecer gravedad
sprite.gravity(980) // Simular gravedad terrestre
// Sin gravedad
sprite.gravity(0)
friction
Obtiene o establece la fricción del sprite.
sprite.friction(0.5)
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)
Métodos de instancia
collision
Comprueba si hay colisión con otro sprite.
if (jugador.collision(enemigo)) then
println("¡Colisión detectada!")
end
El sistema de colisiones soporta:
- Rectángulo vs Rectángulo (incluyendo rotación con SAT)
- Círculo vs Círculo
- Círculo vs Rectángulo
collisionList
Comprueba colisiones contra una lista de sprites y devuelve los índices de los que colisionan.
var enemigos = [enemigo1, enemigo2, enemigo3, enemigo4]
var colisiones = jugador.collisionList(enemigos)
foreach (i in colisiones)
println("Colisión con enemigo ", i)
enemigos[i].active(false)
end
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 completo: Juego simple
use Sprite
use Bitmap
use Display
use GPIO
Display.open(320, 240)
var draw = Display.draw
// Cargar sprites
var jugador = Sprite.load("player.spr")
jugador.pos(160, 200)
var enemigos = []
for (var i = 0; i < 5; i++)
var e = Sprite.new(50 + i * 50, 50, Sprite.load("enemy.spr"))
e.velocity = [random() * 100 - 50, 0]
e.bodyType(Sprite.DYNAMIC)
enemigos.push(e)
end
var puntos = 0
var ultimoTiempo = clock()
while (true)
var ahora = clock()
var dt = ahora - ultimoTiempo
ultimoTiempo = ahora
// Controles del jugador
if (GPIO.read(12) == GPIO.HIGH) then
jugador.x(jugador.x() - 3)
end
if (GPIO.read(14) == GPIO.HIGH) then
jugador.x(jugador.x() + 3)
end
// Actualizar física
Sprite.update(dt)
// Detectar colisiones
var hits = jugador.collisionList(enemigos)
foreach (i in hits)
enemigos[i].active(false)
puntos = puntos + 10
end
// Renderizar
draw.cls(0x000033)
// Dibujar jugador
if (jugador.active()) then
draw.sprite(jugador)
end
// Dibujar enemigos
foreach (e in enemigos)
if (e.active()) then
draw.sprite(e)
end
end
// UI
draw.setcolor(0xFFFFFF)
draw.text(10, 10, "Puntos: " + puntos)
Display.update()
pause(16)
end
Ejemplo: Plataformas con física
use Sprite
use Bitmap
use Display
Display.open(320, 240)
var draw = Display.draw
// Crear jugador con física
var jugador = Sprite.new(Bitmap.load("player.bmp"))
jugador.pos(100, 100)
jugador.bodyType(Sprite.DYNAMIC)
jugador.gravity(500)
jugador.elasticity(0.2)
// Crear plataformas estáticas
var suelo = Sprite.new(Bitmap.load("plataforma.bmp"))
suelo.pos(0, 220)
suelo.bodyType(Sprite.STATIC)
var plataforma = Sprite.new(Bitmap.load("plataforma.bmp"))
plataforma.pos(100, 150)
plataforma.bodyType(Sprite.STATIC)
var ultimoTiempo = clock()
while (true)
var dt = clock() - ultimoTiempo
ultimoTiempo = clock()
// Saltar
if (GPIO.read(15) == GPIO.HIGH) then
jugador.velocity = [jugador.velocity()[0], -300]
end
// Mover horizontalmente
var vel = jugador.velocity()
if (GPIO.read(12) == GPIO.HIGH) then
jugador.velocity = [-100, vel[1]]
elsif (GPIO.read(14) == GPIO.HIGH) then
jugador.velocity = [100, vel[1]]
else
jugador.velocity = [0, vel[1]]
end
// Actualizar física
Sprite.update(dt)
// Renderizar
draw.cls(0x87CEEB)
draw.sprite(jugador)
draw.sprite(suelo)
draw.sprite(plataforma)
Display.update()
pause(16)
end
Consideraciones de rendimiento
- Usa
collisionList()en lugar de múltiples llamadas acollision()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 (SDL2) | ✅ | Soporte completo |
| ESP32 | ✅ | Soporte completo con LittleFS/SD |
| Emscripten (Web) | ✅ | Soporte completo |
| SiFive | 🚧 | En desarrollo |