Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Rotating a sprite

edited January 2013 in Questions Posts: 58

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.

Tagged:

Comments

  • edited January 2013 Posts: 489

    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
    
  • edited February 2013 Posts: 196

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

  • Posts: 58

    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.

  • edited November 2013 Posts: 10

    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.

  • IgnatzIgnatz Mod
    Posts: 5,396

    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.

  • IgnatzIgnatz Mod
    Posts: 5,396

    @Madrayken - maybe you should post your code

  • Posts: 1,595

    @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

  • edited November 2013 Posts: 10

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

  • edited November 2013 Posts: 10
    --# 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
    
  • IgnatzIgnatz Mod
    Posts: 5,396

    @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

  • IgnatzIgnatz Mod
    Posts: 5,396

    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
    
  • edited November 2013 Posts: 77

    @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?

  • IgnatzIgnatz Mod
    Posts: 5,396

    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.

  • dave1707dave1707 Mod
    Posts: 7,533

    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

        

  • dave1707dave1707 Mod
    edited November 2013 Posts: 7,533

    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

        

Sign In or Register to comment.