Rotating a sprite

I have a sprite that needs to be rotated at a custom angle. I know there is a math.rotate() function, but can this function be transferred to a sprite or image? I need to be able to individually rotate many sprites, all at custom rotations. Finding the right rotation value will be difficult in it self, but I want to know how rotating sprites works before I get started.

Hello @edat44. One thing you can do, is illustrated by the code below (updated: comments added):


--
-- Sprite
--

-- Create a class to hold data about the sprite
Sprite = class()

-- Initialise a sprite with its image
function Sprite:init(img)
    self.image = img
    self.x = 0
    self.y = 0
    self.angle = 0
end

-- The sprite can draw itself
function Sprite:draw()
    pushMatrix() -- Preserve the current matrix
    pushStyle() -- Preserve the current style
    resetMatrix() -- Reset the matrix: 'origin' in the bottom left corner
    resetStyle() -- Reset the style
    translate(self.x, self.y) -- Shift (translate) origin of Viewer by (x, y)
    rotate(self.angle) -- Rotate the Viewer about that origin
    spriteMode(CENTER)
    sprite(self.image, 0, 0) -- Draw at the (shifted, rotated) origin
    popStyle() -- Restore the style to what it was
    popMatrix() -- Restore the matrix to what it was
end

supportedOrientations(LANDSCAPE_ANY)
function setup()
    mySprite = {}
    local img = readImage("Planet Cute:Character Princess Girl")
    for i = 1, 10 do
        mySprite[i] = Sprite(img)
        mySprite[i].x = math.random(WIDTH)
        mySprite[i].y = math.random(HEIGHT)
        mySprite[i].angle = math.random(360)
    end
end

function draw()
    background(0)
    for i = 1, 10 do
        mySprite[i].angle = mySprite[i].angle +
            DeltaTime * math.random(90, 270)
        mySprite[i]:draw()
    end
end

. @edat44 and for those interested, another way is to use mesh() instead of sprites.


Pic = class()
function Pic:init(src)
    self.surface = mesh()
    self.surface.texture = src
    self.w, self.h = spriteSize(src)
    self.id = self.surface:addRect(0, 0, self.w, self.h)
end

function Pic:draw(x, y, r)
    self.surface:setRect(self.id, x, y, self.w, self.h, r)
    self.surface:draw()
end

function setup()
    img = Pic("Documents:mySpriteTexture")
end

function draw()
    background(0)
    img:draw(CurrentTouch.x, CurrentTouch.y, math.sin(ElapsedTime))
end

Not tested but this should work.

Cheers

Thanks @mpilgrem. I was even able to use code with the new parameter and tween features in the newest Codea update to smoothly animate the sprite.

Sorry to necro this thread, but does anyone have any thoughts on the differences in performance between these two methods?

Edit: I’ve just tested them both, and the Sprite version is faster - perhaps due to the Pic version requiring extra parameters, even if you don’t want to scale.

Generally, meshes outperform sprites, not least because sprites are actually drawn using meshes, behind the scenes. The difference may not be noticeable unless you have a lot of images.

If you’re going to do any serious graphics, though, I would focus on meshes.

That’s really weird. Using close-to-identical code, I can get 400-odd sprites, but only 300-or-so meshes! I’m a bit baffled.

@Madrayken - maybe you should post your code

@Madrayken, if you’re creating meshes every frame and drawing loads of different meshes then it can get slow, I find using addRect and multiple rectangles much faster than 100 meshes with 1 rectangle or a sprite. Creating rectangles for one mesh every frame is a lot faster than creating a whole new mesh

Clearly, I need to understand meshes better. :slight_smile: Thanks.
Posting code below. No idea why the formatting has gone so horribly wrong…

--# Main
NUMSPRITES = 5
GRAVITY = -9.8
HALF_PI = math.pi * 0.5

-- Use this function to perform your initial setup

function setup()
    spriteSet = {}
    for i = 1, NUMSPRITES do
        spriteSet[i] = Pic()

        --spriteSet[i] = Sprite()

    end
    
    background(100, 120, 160)
    font("Georgia")
    displayMode(FULLSCREEN)
    fill(255)
    fontSize(20)    
    textWrapWidth(70)
    spriteMode()
end

-- This function gets called once every frame

function draw()
    background(93, 93, 111, 255)
    
    strokeWidth(1)
    stroke(255)
    noSmooth()
    for i = 1, NUMSPRITES do
        spriteSet[i]:update()
        spriteSet[i]:draw()
    end
    
    -- Put an FPS counter in the corner of your screen

    fill(255,255,255)
    textMode(CORNER)

    fps = string.format("%.2f", 1/DeltaTime)
    text(math.ceil(fps),10,10)end

function touched(touch)
    print("Hello World!")
end

--# Pic
Pic = class()
function Pic:init()
    src = "Dropbox:Head"
    
    self.pos = vec2(math.random(WIDTH), math.random(HEIGHT))
    self.velocity = vec2(math.random(RANDOM_X_VEL * 2) - RANDOM_X_VEL, 0)
    self.angle = math.random(math.deg(360))

    self.surface = mesh()
    self.surface.texture = src
    self.w, self.h = spriteSize(src)
    self.id = self.surface:addRect(0, 0, self.w, self.h)
end

function Pic:update()
    self.velocity.y = self.velocity.y + GRAVITY * DeltaTime
    self.pos = self.pos + self.velocity
    if self.pos.y < 0 then
        self.velocity.y = self.velocity.y * -1
        self.pos.y = self.pos.y * -1
    end
    
    if self.pos.x < 0 then
        self.velocity.x = self.velocity.x * -1
        self.pos.x = self.pos.x * -1
    elseif self.pos.x > WIDTH then
        self.velocity.x = self.velocity.x * -1
        self.pos.x = WIDTH - (self.pos.x - WIDTH)
    end
    self.angle = math.atan2(self.velocity.y, self.velocity.x) - HALF_PI
end

function Pic:draw()
    self.surface:setRect(self.id, self.pos.x, self.pos.y, self.w, self.h, self.angle)
    self.surface:draw()
end


--# Sprite

Sprite = class()

RANDOM_X_VEL = 10
function Sprite:init()
    self.pos = vec2(math.random(WIDTH), math.random(HEIGHT))
    self.velocity = vec2(math.random(RANDOM_X_VEL * 2) - RANDOM_X_VEL, 0)
    self.angle = math.random(math.deg(360))
end

function Sprite:update()
    self.velocity.y = self.velocity.y + GRAVITY * DeltaTime
    self.pos = self.pos + self.velocity
    if self.pos.y < 0 then
        self.velocity.y = self.velocity.y * -1
        self.pos.y = self.pos.y * -1
    end
    
    if self.pos.x < 0 then
        self.velocity.x = self.velocity.x * -1
        self.pos.x = self.pos.x * -1
    elseif self.pos.x > WIDTH then
        self.velocity.x = self.velocity.x * -1
        self.pos.x = WIDTH - (self.pos.x - WIDTH)
    end
    self.angle = math.atan2(self.velocity.y, self.velocity.x)
    self.angle = math.deg(self.angle) - 90
end

function Sprite:draw()
    pushMatrix() -- ensure we don't ruin the matrix
    translate(self.pos.x, self.pos.y)
    rotate(self.angle)
    sprite("Dropbox:Head", 0, 0)
    popMatrix()
end

function Sprite:touched(touch)

    -- Codea does not automatically call this method

end

@Madrayken - I’ve fixed your code formatting above. The trick is to put three ~ on the line before your code, and three more on the line under your code.

You can make the sprites faster still by reading the image into a variable in setup, and spriting that in draw, ie in setup

img = readImage(“Dropbox:Head”)

and in draw

sprite(img, 0, 0)

I’m not sure why the meshes are slower at this stage

This code runs meshes really fast, by putting all the images in a single mesh (because they all use the same texture image). This is much faster because each mesh has a setup process.

It also explains why the sprites were faster, because Codea batches sprites behind the scenes - I’m guessing it creates a single mesh from all of them (if you weren’t aware, sprites are actually meshes as well).

--# Main
NUMSPRITES = 300
GRAVITY = -9.8
HALF_PI = math.pi * 0.5
RANDOM_X_VEL=10

-- Use this function to perform your initial setup

function setup()
    img=readImage("Planet Cute:Character Pink Girl")
    mm=mesh()
    mm.texture=img
    spriteSet = {}
    for i = 1, NUMSPRITES do
        spriteSet[i] = Pic(mm)
    end

    background(100, 120, 160)
    font("Georgia")
    displayMode(FULLSCREEN)
    fill(255)
    fontSize(20)    
    textWrapWidth(70)
    spriteMode()
end

function draw()
    background(93, 93, 111, 255)

    strokeWidth(1)
    stroke(255)
    noSmooth()
    for i = 1, NUMSPRITES do
        spriteSet[i]:update()
    end
    mm:draw()
    -- Put an FPS counter in the corner of your screen

    fill(255,255,255)
    textMode(CORNER)

    fps = string.format("%.2f", 1/DeltaTime)
    text(math.ceil(fps),10,10)end

--# Pic
Pic = class()
function Pic:init(m)
    self.pos = vec2(math.random(WIDTH), math.random(HEIGHT))
    self.velocity = vec2(math.random(RANDOM_X_VEL * 2) - RANDOM_X_VEL, 0)
    self.angle = math.random(math.deg(360))
    self.surface=m
    self.w, self.h = spriteSize(src)
    self.id = m:addRect(0, 0, self.w, self.h)
end

function Pic:update()
    self.velocity.y = self.velocity.y + GRAVITY * DeltaTime
    self.pos = self.pos + self.velocity
    if self.pos.y < 0 then
        self.velocity.y = self.velocity.y * -1
        self.pos.y = self.pos.y * -1
    end

    if self.pos.x < 0 then
        self.velocity.x = self.velocity.x * -1
        self.pos.x = self.pos.x * -1
    elseif self.pos.x > WIDTH then
        self.velocity.x = self.velocity.x * -1
        self.pos.x = WIDTH - (self.pos.x - WIDTH)
    end
    --self.angle = math.atan2(self.velocity.y, self.velocity.x) - HALF_PI
    self.angle = math.atan2(self.velocity.y, self.velocity.x)
    self.angle = math.deg(self.angle) - 90
    self.surface:setRect(self.id, self.pos.x, self.pos.y, self.w, self.h, self.angle)
end

@Ignatz - I have used self.w, self.h = spriteSize (img) instead of scr. FPS = 60
Good job.

Thanks sooooo much, Ignatz. I was tearing out my hair over both those issues!

As an aside - is this ‘make them all one mesh’ technique a good one for a simple tile-based background, or do you have a better suggestion?

If the background isn’t ever going to change, just draw it all onto a single image at the beginning with setContext, then sprite that in draw.

If is a dynamic background and you do have to draw it fully all the time, then yes, a single mesh is quicker than a whole lot of meshes. However, if each tile has its own image, you can’t combine them in one mesh (because a mesh can only have one texture image) unless you cram all the tiles onto one image and select the right piece to use for each tile.

There was quite a lot of discussion on this for a game called RPGMaker - it might be worth searching the forum for that.

Here’s another example of rotating sprites. Let it run for at least 2 minutes.


displayMode(FULLSCREEN)

function setup()
    x=100
    y=100
    tab={}
    table.insert(tab,sp(WIDTH/2+50,HEIGHT/2+50))
    for z=1,40 do
        table.insert(tab,sp(x,y))
    end   
end

function draw()
    background(40, 40, 50)
    for a,b in pairs(tab) do
        b:draw()
    end
end

sp=class()

function sp:init(x,y)
    self.x=x
    self.y=y
    self.ang=0
end

function sp:draw()
    translate(self.x,self.y)
    rotate(self.ang)
    sprite("Planet Cute:Heart",0,0)
    self.ang = self.ang + .05
end

Here’s the above program, but the rotation angle is based on where you slide your finger up or down the screen.

EDIT: Added math.floor()


displayMode(FULLSCREEN)

function setup()
    dt=0
    x=100
    y=100
    tab={}
    table.insert(tab,sp(WIDTH/2+50,HEIGHT/2+50))
    for z=1,40 do
        table.insert(tab,sp(x,y))
    end   
end

function draw()
    background(40, 40, 50)
    fill(255)
    text(dt,WIDTH/2,HEIGHT-50)
    for a,b in pairs(tab) do
        b:draw()
    end
end

function touched(t)
    dt=math.floor(180/HEIGHT*t.y)
end

sp=class()

function sp:init(x,y)
    self.x=x
    self.y=y
end

function sp:draw()
    translate(self.x,self.y)
    rotate(dt)
    sprite("Planet Cute:Heart",0,0)
end