CocosCodea

Hey guys,

Codea is by far my favorite app ever to be released on the app store. Recently I was playing with it and really wished for more advanced features, like a more fully featured class system, hierarchical scene management, tweening, and some other things. Cocos2d-iphone is a great fully-featured game engine in Obective-C for iOS that provides these things very nicely. Since it’s likely that nobody will ever add Lua bindings for all of cocos2d-iphone and include it in Codea, I decided to port Cocos2d to Lua/Codea. I’ve been working feverishly over the last week to get as much as I could in, and it’s gotten to a point where I don’t want to keep it to myself anymore. I’ve got most of the core ported (minus a couple of features). There’s still a lot missing, like sprite sheet support, batched sprite drawing, tile maps, bitmapped fonts, particles, and effects, but it’s in a state where you can really start doing some cool things with it. It’s not a straight port; I’ve taken liberties with some of the API, added some low hanging fruit that was missing, and implemented some of the functionality in a different way (for example, using Codea’s rendering system under the hood).

Currently it’s not documented, there may be some bugs, and the code is highly volatile. There are a few crappy examples, but I’m going to spend the next couple of hours trying to add some more to show off what CocosCodea can do.

The repository is here: https://github.com/apendley/CocosCodea

It’s a lot of files, I know. Hopefully this will not matter anymore once TLL includes project referencing in Codea :slight_smile:

Please give me any comments and suggestions. If you want to contribute, that would be lovely. If not, that’s okay too. I’ve had a blast really digging into Codea and Lua, and it’s been a great learning experience so far. I’ll update here periodically with progress. Cheers!

Wow that’s an amazing project (and concept). Love to see where you take this.

Cocos2D is a great API - though I think we can achieve the tween stuff more elegantly in Lua (I.e., tween.lua). Modeling a scene graph on Cocos would be cool.

Thanks @Simeon :slight_smile:

I like tween.lua, but I find cocos2d actions to much more powerful. I do think the cocos2d actions API is a bit cumbersome though, and would not mind improving it in CocosCodea. Lua has actually improved the API some, but I’m definitely hoping to improve it further. Incidentally, you should be able to use tween.lua alongside CocosCodea pretty easily if you prefer it. Alternatively, some parts of CocosCodea can be used independently, like the scheduler, the class/mixin system, and the targeted touch system.

This is amazing! Thank you so much, @toadkick.

@toadkick John has done a version of tween with sequences, callbacks (just insert any function/closure into a sequence), and loops. I think it’s just as versatile as the Cocos2D actions (and a lot less code to write).

@pepinganos thanks!

@Simeon: oh cool, I haven’t seen his version. I will look for it so I can check it out for inspiration, I’m definitely looking for ways to reduce code volume where possible to make CocosCodea easier to use with the iPad’s keyboard. I’ve shortened several of the action’s names, and I’ve been playing around with a new way to define complex sequences of actions inspired by some of the stuff in tween.lua. Can’t wait to find John’s version to see how he handles sequences.

Here’s an example of what I’m playing with so far, to make an action for a character that moves to (100, 100) and back with CCEaseSineInOut easing, and repeats 2 times:


node:runActions{
    CCMoveBy(2, 100, 100), 
    CCMoveBy(2, -100, -100),
    repeat = 2, ease = CCEaseSineInOut,
}

compared to the much more confusing:


local s = CCSequence(CCMoveBy(2, 100, 100), CCMoveBy(2, -100, -100))
node:runAction(CCRepeat(CCEaseSineInOut(s), 2))

It’s still more verbose than I’d like, but I think it’s an improvement. Another interesting thing to note is that Lua enabled me to improve the CCCall* actions somewhat so they require a less boilerplate code.

Hey @Simeon, do have a link to John’s version? I can’t seem to find it anywhere.

Hey guys, just an update to say that I’ve got CCSpriteBatchNode implemented (using Codea’s mesh() object), which means it is now possible to batch draw sprites with CocosCodea.

Currently there is no support for tools like TexturePacker or Zwoptex, but you can use a sprite sheet and manually set the texture coordinates for your sprites at the moment. Then next step for me will probably be to implement CCSpriteFrame and CCSpriteFrameCache, which is a step in the direction of TP/Zwoptex implementation, and will make it easier to deal with batched sprites/sprite sheets in general. Cheers!

That’s great news — sorry about the delay on John’s tweener library, I’ll upload it soon.

Here’s the prototype (TweenLibrary.lua)

It attaches itself to the draw loop automatically. I’ll post an example main file in a sec.

tween = {}
tween.list = {}
tween.easeTypes = {}
tween.ease = "linear"

tween.easeTypes.linear = function(t,b,c,d)
    return c*t/d + b
end

tween.easeTypes.inOutQuad = function(t,b,c,d)
    t = t / (d/2)
    if t < 1 then return c/2*t*t + b end
    t = t - 1
    return -c/2 * (t*(t-2) - 1) + b
end

function tween._update()
    for k,tw in ipairs(tween.list) do
        tw.time = tw.time + DeltaTime
        tw.update(tw.time)
        if tw.time >= tw.duration then
            if tw.repeats and (tw.repeats == -1 or tw.count < tw.repeats) then
                tw.time = 0
                tw.count = tw.count + 1
                tw.update(0)
            else
                table.remove(tween.list, k)
            end
        end 
    end
end

function tween._tweenForToken(token)
    for k,tw in ipairs(tween.list) do
        if tw.token == token then
            return tw
        end
    end
    return nil
end

function tween.sequence(...)
    local seq = {}
    seq.list = {}
    seq.duration = 0.0
    seq.time = 0.0
    seq.offset = 0.0
    seq.index = 1
    
    -- count duration of sequence
    for k,token in ipairs(arg) do
        if type(token) ~= "function" then
            local tw = tween._tweenForToken(token)
            if tw == nil then tw = token end
            tween.remove(tw)
            table.insert(seq.list, tw)
            seq.duration = seq.duration + tw.duration
        else
            table.insert(seq.list, token)
        end 
    end
    
    seq.current = seq.list[1]
    
    seq.update = function(time)
        if time == 0 then
            seq.offset = 0
            seq.index = 1
            seq.current = seq.list[1]
        end
        
        seq.current.time = time - seq.offset
        seq.current.update(seq.current.time)
        
        if seq.current.time >= seq.current.duration and seq.current.repeats and 
            (seq.current.repeats == -1 or seq.current.count < seq.current.repeats) then
                
            seq.current.time = 0
            seq.current.count = seq.current.count + 1
            seq.current.update(0)
            seq.time = seq.time - seq.current.duration
        elseif seq.current.time >= seq.current.duration and seq.index < #seq.list then
            
            -- non-loop tween, goto the next tween
            seq.offset = seq.offset + seq.current.duration
            seq.index = seq.index + 1
            seq.current = seq.list[seq.index]
            
            if type(seq.current) == "function" then
                seq.current()
                if seq.index < #seq.list then
                    seq.index = seq.index + 1
                    seq.current = seq.list[seq.index]
                    seq.current.update(0)
                end
            else
                seq.current.update(0)
            end
        end
    end    
    
    table.insert(tween.list, seq)
    
    return seq
end

function tween.remove(tw)
    for k,v in ipairs(tween.list) do
        if v == tw then
            table.remove(tween.list, k)
            return
        end
    end
end

function tween.loop(tw, repeats)
    local loop = {}
    loop.tween = tw
    loop.duration = tw.duration
    loop.time = 0.0
    loop.count = 1
    loop.repeats = repeats or -1
    tween.remove(tw)
    loop.update = function(time)
        loop.tween.update(time)
    end
    table.insert(tween.list, loop)
    
    return loop  
end

function tween.delay(d)
    local delay = {}
    delay.update = function(time) end
    delay.time = 0
    delay.duration = d
    table.insert(tween.list, delay)
    
    return delay
end
    
function tween._setup()
    local mt = {}
    mt.__call = function(caller,target, args)
        local token = {}
        local mt2 = {}
        
        mt2.__newindex = function(t, key, value)
            
            -- check if the key being animated can be interpolated
            local start = target[key]
            local tp = type(start)
            local valid = false
            if tp == "number" then
                valid = true
            elseif tp == "table" or tp == "userdata" then
                mt = getmetatable(start)
                if mt.__add and mt.__sub and mt.__mul then
                    valid = true
                end
            end
            assert(valid, "cannot tween value (must define add/subtract/multiply operators)")
            
            
            local tweener = {}

            tweener.update = function(time)
                if time == 0 then
                    tweener.valueA = target[key]
                    tweener.delta = tweener.valueB - tweener.valueA
                end 
                target[key] = tweener.ease(time, tweener.valueA, tweener.delta, tweener.duration)
            end
            
            tweener.duration = (args and args.duration) or 1.0
            tweener.time = 0.0
            tweener.target = target
            tweener.key = key
            tweener.valueA = start
            tweener.valueB = value
            tweener.ease = tween.easeTypes[(args and args.ease) or tween.ease]
            tweener.update(0)
            tweener.token = token
            table.insert(tween.list, tweener)
        end
        
        mt2.__index = function(t, key)
            return target[key]
        end
        
        setmetatable(token, mt2)
        return token
    end
           
    local _draw = draw
    draw = function() 
        tween._update()       
        _draw()
    end
    setmetatable(tween, mt)
end

tween._setup()

Here’s the Main.lua file showing how to use it.

-- Use this function to perform your initial setup
function setup()
    print("Hello World!")
    
    circle = {pos = vec2(WIDTH/2,HEIGHT/2), radius = 50, col = color(255, 0, 0, 255)}
    t1 = tween(circle, {ease = "inOutQuad"})
    t1.pos = vec2(0,0)
    t2 = tween(circle)
    t2.pos = circle.pos
    l1 = tween.loop(tween.sequence(t1, t2, function() print("blah!") end), 3)
    tween.sequence(l1, function() print("blob!") end)
    
    t3 = tween(circle, {duration = 5})
    t3.radius = 75
    
    --tween(circle.col) = {r = 0, g = 0, b = 0}
    
    --circle.test = "blah"
    --tween(circle).test = "something"
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)

    -- This sets the line thickness
    strokeWidth(5)

    fill(circle.col)
    ellipse(circle.pos.x, circle.pos.y, circle.radius)

    -- Do your drawing here
    
end

The interesting thing about this tween library is that tween(object) returns a new object which you can set a property to animate. You can also pass these tween tokens to some other functions, such as tween.sequence (run tweens one after the other), and tween.loop (run tweens multiple times). There are some things missing, such as pausing, stopping and finding tweens by name. There may also be some stability issues and issues with deep nesting of sequences and loops.

This is great! It’s about time I learn a little more about Cocos2D

@John -very impressive. Did I understand correctly that I couldn’t use your TweenLibrary to rotate a sprite but translation would be ok?

@Simeon - Maybe this should be included in Codea as the object based drawing system with renamed functions. This would be great. Thanks!

For my part I’d be happy if Juice (when it’s released) includes scene graph support (maybe it already does?). Now that we have tweening and faster sprite rendering built in, frankly I really don’t have much use for 90% of what’s in CocosCodea anymore :smiley: One day I would like to revisit this though and streamline it a bit…after much more experience hacking around in Lua I’m pretty sure most of what’s in CocosCodea could be done more elegantly, with a more Lua-ish feel.