Example of 3D tile based engine (to use for zelda like games)

Hello,

. @Brandn3wbian and the few that were expecting this a couple of days ago, sorry for the delay, i’ve been busy with work, but here it is.
Tile based games are very easy to work with (simple ai, collision detection, etc…) so I figured I would share some code to show how it works.
You can make quite a few types of games that fit the constraints of this engine style, such as zelda, bomberman, pacman, diablo, etc…

Note that the art used here is just cubes and builtin codea textures, so it’s ugly as hell :wink:

With nice assets though, it can very easily look gorgeous (think zelda in 3D) like Oceanhorn.
http://www.youtube.com/watch?v=W4pW3o5HVHU

Note that I updated my Blender to Codea exporter, and you can easily create better assets with it:
http://twolivesleft.com/Codea/Talk/discussion/962/blender-scene-exporter-for-codea

Also, the code here is made as simple as possible and can be easily optimised (1D array, etc…)
Anyways, here is the source (pastebin is here)


--# Main


-- Use this function to perform your initial setup
displayMode(FULLSCREEN)
function setup()
    stick = Stick()

    pylon = Wall("Cargo Bot:Crate Red 2")
    wall = Wall("Cargo Bot:Crate Yellow 2")
    floor = Floor("Cargo Bot:Crate Green 2")

    world = World()
    hero = Hero(3,3)

    TO_DEG = 180/math.pi
end

-- This function gets called once every frame
function draw()
    background(0)
    local TO_DEG = TO_DEG
    local hero = hero

    perspective(60)
    camera(hero.x, 3, 1 + hero.z, hero.x, 0, hero.z, 0, 1, 0)

-- Draw world
    pushMatrix()
    world:draw()
    popMatrix()

-- Draw hero
    translate(hero.x, hero.y, hero.z)
    rotate(stick.direction*TO_DEG, 0, 1, 0)

    -- roll animation
    if stick.active then
        rotate(-ElapsedTime*10*TO_DEG, 0, 0, 1)
    end

    scale(.25, .25, .25)
    hero:draw()

-- Restore orthographic projection
    ortho()
    viewMatrix(matrix())
    resetMatrix()

    -- fade out overlay
    sprite("Cargo Bot:Background Fade", WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)

    if stick.active then
        local ceil = math.ceil
        stick:draw()

        -- move hero based on stick direction
        local mvtx = math.cos(stick.direction)/50*stick.dist
        local mvtz = -math.sin(stick.direction)/50*stick.dist
        hero.x = hero.x + mvtx
        hero.z = hero.z + mvtz

        -- convert to table coordinates
        hero.px = ceil(hero.x - .5)
        hero.py = ceil(hero.z - .5)

        -- lazy collision check
        if world.data[hero.py][hero.px] ~= 0 then
            hero.x = hero.x - mvtx
            hero.z = hero.z - mvtz
            hero.px = ceil(hero.x - .5)
            hero.py = ceil(hero.z - .5)
        end
    end

end

function touched(touch)
    stick:touched(touch)
end

--# World
World = class()

function World:init()

    -- define the world
    self.data =
    {
        {1, 1, 1, 1, 1, 1, 1, 1},
        {1, 2, 0, 0, 0, 0, 2, 1},
        {1, 0, 0, 0, 0, 0, 0, 1},
        {1, 0, 0, 1, 2, 0, 0, 1},
        {1, 0, 0, 2, 1, 0, 0, 1},
        {1, 0, 0, 0, 0, 0, 0, 1},
        {1, 2, 0, 0, 0, 0, 2, 1},
        {1, 1, 1, 1, 1, 1, 1, 1}
    }
end

function World:draw()
    local floor, wall, pylon = floor, wall, pylon
    local offSet = 3
    local px, py = hero.px, hero.py

    -- look around the hero to draw whatever is around him
    translate(px - offSet, 0, py - offSet)
    for y = py - offSet, py + offSet do
        for x = px - offSet, px + offSet do
            if self.data[y] then
                local val = self.data[y][x]
                if val == 0 then
                    floor:draw()
                elseif val == 1 then
                    wall:draw()
                elseif val == 2 then
                    pylon:draw()
                end
            end
            translate(1,0,0)
        end
        translate(-(1 + 2 * offSet), 0, 1)
    end
end


--# Hero
Hero = class()

function Hero:init(x, z)
    self.x, self.y, self.z = x,0,z
    self.px, self.py = math.ceil(.5+x), math.ceil(.5+z)

    self.mdl = Wall("Cargo Bot:Crate Blue 2")
end

function Hero:draw()
    self.mdl:draw()
end


--# Wall
Wall = class()


function Wall:init(tex)
    -- all the unique vertices that make up a cube
    local vertices =
    {
        vec3(-0.5, -0.5,  0.5), -- Left  bottom front
        vec3( 0.5, -0.5,  0.5), -- Right bottom front
        vec3( 0.5,  0.5,  0.5), -- Right top    front
        vec3(-0.5,  0.5,  0.5), -- Left  top    front
        vec3(-0.5, -0.5, -0.5), -- Left  bottom back
        vec3( 0.5, -0.5, -0.5), -- Right bottom back
        vec3( 0.5,  0.5, -0.5), -- Right top    back
        vec3(-0.5,  0.5, -0.5), -- Left  top    back
    }

    -- now construct a cube out of the vertices above
    local verts =
    {
        -- Front
        vertices[1], vertices[2], vertices[3],
        vertices[1], vertices[3], vertices[4],
        -- Right
        vertices[2], vertices[6], vertices[7],
        vertices[2], vertices[7], vertices[3],
        -- Back
        vertices[6], vertices[5], vertices[8],
        vertices[6], vertices[8], vertices[7],
        -- Left
        vertices[5], vertices[1], vertices[4],
        vertices[5], vertices[4], vertices[8],
        -- Top
        vertices[4], vertices[3], vertices[7],
        vertices[4], vertices[7], vertices[8],
       -- Bottom
        vertices[5], vertices[6], vertices[2],
        vertices[5], vertices[2], vertices[1],
    }

    -- all the unique texture positions needed
    local texvertices =
    {
        vec2(0,0),
        vec2(1,0),
        vec2(0,1),
        vec2(1,1)
    }

    -- apply the texture coordinates to each triangle
    local texCoords =
    {
        -- Front
        texvertices[1], texvertices[2], texvertices[4],
        texvertices[1], texvertices[4], texvertices[3],
        -- Right
        texvertices[1], texvertices[2], texvertices[4],
        texvertices[1], texvertices[4], texvertices[3],
        -- Back
        texvertices[1], texvertices[2], texvertices[4],
        texvertices[1], texvertices[4], texvertices[3],
        -- Left
        texvertices[1], texvertices[2], texvertices[4],
        texvertices[1], texvertices[4], texvertices[3],
        -- Top
        texvertices[1], texvertices[2], texvertices[4],
        texvertices[1], texvertices[4], texvertices[3],
        -- Bottom
        texvertices[1], texvertices[2], texvertices[4],
        texvertices[1], texvertices[4], texvertices[3],
    }

    self.model = mesh()
    self.model.vertices = verts
    self.model.texture = tex
    self.model.texCoords = texCoords
    self.model:setColors(255,255,255,255)
end

function Wall:draw()
    self.model:draw()
end
--# Floor
Floor = class()

function Floor:init(tex)
    -- all the unique vertices that make up a cube
    local vertices =
    {
        vec3( 0.5,  -0.5,  0.5), -- Right top    front
        vec3(-0.5,  -0.5,  0.5), -- Left  top    front
        vec3( 0.5,  -0.5, -0.5), -- Right top    back
        vec3(-0.5,  -0.5, -0.5), -- Left  top    back
    }


    -- now construct a cube out of the vertices above
    local verts =
    {
        -- Bottom
        vertices[3], vertices[4], vertices[2],
        vertices[3], vertices[2], vertices[1],
    }

    -- all the unique texture positions needed
    local texvertices =
    {
        vec2(0,0),
        vec2(1,0),
        vec2(0,1),
        vec2(1,1)
    }

    -- apply the texture coordinates to each triangle
    local texCoords =
    {
        -- Bottom
        texvertices[1], texvertices[2], texvertices[4],
        texvertices[1], texvertices[4], texvertices[3],
    }

    self.model = mesh()
    self.model.vertices = verts
    self.model.texture = tex
    self.model.texCoords = texCoords
    self.model:setColors(255,255,255,255)
end

function Floor:draw()
    self.model:draw()
end

--# Stick
Stick = class()

function Stick:init()
    self.direction = 0
    self.dist = 0

    self.active = false
    self.origin = vec2(150, 150)
    self.center = self.origin
    self.pos = self.origin

    self.stick_bg = readImage("Space Art:Eclipse")
    self.stick = readImage("Space Art:UFO")

end

function Stick:draw()
    sprite(self.stick_bg, self.center.x, self.center.y)
    sprite(self.stick, self.pos.x, self.pos.y)
end

function Stick:touched(touch)
    if touch.state == BEGAN then
        self.center = vec2(touch.x, touch.y)
        self.active = true
    end

    self.pos = vec2(touch.x, touch.y)
    self.direction = math.atan2(self.pos.y - self.center.y, self.pos.x - self.center.x)

    self.dist = math.min(2, self.pos:dist(self.center)/32)

    if touch.state == ENDED then
        self.center = self.origin
        self.pos = self.center
        self.active = false
    end


end

Cheers

Awsome!

Very nice! I modified two readImage lines in Stick.lua to get it to run on 1.4.6 :

self.stick_bg = readImage("Small World:Dialog Icon")
    self.stick = readImage("Small World:Bush")

This should let it use some (arbitrarily chosen) sprites which were available.

Cheers,
Richard

Oh thanks @RichardMN, didn’t even realize :smiley:

Thank you so much for showing how it works @xavier <3

Are you using moddels made in blender in the video above?

. @Warox I don’t know if the game used blender to create the models. It’s the same studio that made Death Rally, so they probably went for a more professional (pricey) solution.
The video above is just to show what 3D tile game engines can look like. The project is called Oceanhorn, more info can be found here: http://oceanhorn.blogspot.fr

Note that Blender, while free, is a viable alternative to 3DSMax and the likes

Cheers

This looks simply marvelous, @Xavier. I think it’s the niftiest thing I’ve seen in Codea.

. @Mark the video shows Oceanhorn, an iOS project from the makers of Death Rally. I only included it to show that tile engines weren’t outdated, and can look amazing with nice art. I apologize for the confusion!

Cheers

Nice example! Thanks. I’ve imported OBJ file format from meshlab with this code

https://gist.github.com/tnlogy/4690383

saw that you made some character animations in your earlier video as well, have you done any kind of skeleton animations or similar?

. @tnlogy Well, I have done keyFrame animation in Codea (the stuff you get in quake 1 and 2). I have the project saved somewhere, I should look for it again, I suppose I understand lua better now than a year ago and could make it faster, and this was before the mesh class and its fancy functions… I was using my software 3D renderer haha…

Still, the obvious problem I see is speed when interpolating one keyFrame to the next, since in a proper model you loop through hundreds of vertices, and do a few table lookups, operations and function calls.

It’s fine when it’s the only thing you have to do, but for speed and simplicity, the animation I use in my upcoming game is actually just rotations and translations of the meshes (like breathing for the body, and swing animation for the sword, for example)… It’s fine with unrealistic models :wink:

Cheers

nice example @Xavier, but can you explain me this:

– Restore orthographic projection
ortho()
viewMatrix(matrix())
resetMatrix()

I know that it’s about lights in game but I don’t get it. Thanks

Hi @Cabernet - This is basically to reset back to “2D” mode, so I can draw GUI elements (stick, buttons, etc…).

You want to draw them last since they usually use transparency.

The following code enables perspective projection with a Field of View of 60 degrees. The camera is at the eye, and points to the target. The last fields is the up vector (defines which way is up)


    perspective(60)
    camera(eye.x, eye.y, eye.z, target.x, target.y, target.z, 0, 1, 0)

And this resets it back to the default Codea orthographic projection (meaning no vanishing point), so you can do your basic 2D drawings


    ortho()
    viewMatrix(matrix())
    resetMatrix()

Note that Codea uses OpenGL, and that means it’s all 3D. By default, it’s just the illusion of 2D ^^

Don’t hesitate to ask if something isn’t clear. That’s the whole point of sharing code !

Cheers

edit:clarified

Hahaha! Nice one! ( it’s just the illusion of 2D)

:wink:

I’d be interested in the rigging/skeletal stuff…I wanted to make a rigged animal like a dog, but the time investment to write rigging code is real high.

is there any better more compact way to create vec3 moddels as shown in the code ?

. @warox I’m not sure what you mean, but you can use http://twolivesleft.com/Codea/Talk/discussion/962/blender-scene-exporter-for-codea. It will export a blender model to an image format, which makes it easy to manage, if that’s what you mean.

. @aciolino The animation code is actually straightforward, go through vertices, and interpolate each vertex position to next frame.
Character rigging however is a painful and long process, especially if you’re like me and have little experience doing it :frowning:

I wish I had the time to learn more on the matter … There should be some tutorials around that show best practice in order to make me faster/better at it ^^

Hi Xavier, I would be very interested in checking out how you handle 3d animations in codea. My problems are the opposite to yours, I work as a 3d animator and rigging/animating i can do, but my Lua/codea skills are very basic, I am getting there :wink: So anything you can throw my way would be greatly appreciated!

. @nozzy well I need to find the lua code… It should be archived on my mac somewhere :confused:

You should know that it’s a bit obsolete now though. It was part of a 3D renderer I had build in Codea before we had access all the cool things we have now, I wanted to be able to store animations, but it wasn’t smooth enough…
I want to rewrite one now that were have shaders available, should be fine then :stuck_out_tongue:

In the meantime, Jeff Lamarche made a nice set of tutorials a few years ago, and one talks about keyframe animations. You can find it here: http://iphonedevelopment.blogspot.fr/2009/12/opengl-es-from-ground-up-part-9a.html
This is where I found out more about keyframe animation and how it works.
Note that It’s obviously not ideal to use Lua to do the interpolation, but it should be a quick read and help you understand. Let me know if you have any questions.

Here is a shader implementation example if you’re interested: http://www.opengl.org/wiki/Keyframe_Animation

You will obviously need to build buffers in Codea in order to use the vertex shader for keyframe animation.

I will release the code once i make a new version, but I just started a new job last week, so I have little spare time :frowning:

Cheers