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.

JARU Sprite Module

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
}
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...)

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)
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.

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

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

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

Optimización
  • Usa collisionList() en lugar de múltiples llamadas a collision() 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
Windows (SDL2)Soporte completo
ESP32Soporte completo con LittleFS/SD
Emscripten (Web)Soporte completo
SiFive🚧En desarrollo