3D Air Hockey

This is my first 3D project, it uses the 2D physics engine to create the physics needed for the table top effect.
Here’s the code, its a two player game with a basic score system, the score doesn’t have a limit although air hockey is 7.



--# Main
-- 3Dairhockey

-- Use this function to perform your initial setup
function setup()
    displayMode(FULLSCREEN)
    
    cube = {}
    cube[1] = Cube(vec3(0,0,145)*5,vec3(440,4,20)*5)
    cube[2] = Cube(vec3(210,0,100)*5,vec3(20,4,80)*5)
    cube[3] = Cube(vec3(210,0,-100)*5,vec3(20,4,80)*5)
    cube[4] = Cube(vec3(-210,0,100)*5,vec3(20,4,80)*5)
    cube[5] = Cube(vec3(-210,0,-100)*5,vec3(20,4,80)*5)
    cube[6] = Cube(vec3(0,0,-145)*5,vec3(440,4,20)*5)
    cube[7] = Cube(vec3(0,-10,0)*5,vec3(5,1,300)*5)
    cube[7]:setColors(50,50,255,100)
    puck = Cylinder(vec3(0,0,0),60,15)
    puckp = physics.body(CIRCLE,60)
    puckp.position = vec2(0,0)
    puckp.gravityScale = 0
    puckp.restitution = 1
    puckp.friction = 0
    puckp.sleepingAllowed = false
    puckp.interpolate = true
    puckp.mask = {1}
    puckp.categories = {1}
    floorv = {vec2(200,-60)*5,vec2(200,-135)*5,vec2(-200,-135)*5,vec2(-200,-60)*5}
    floor = physics.body(CHAIN,false,unpack(floorv))
    floor.categories = {0,1}
    floor.restitution = 1
    floorv = {vec2(200,60)*5,vec2(200,135)*5,vec2(-200,135)*5,vec2(-200,60)*5}
    floor2 = physics.body(CHAIN,false,unpack(floorv))
    floor2.categories = {0,1}
    floor2.restitution = 1
    t1 = Touch()
    t2 = Touch()
    ply1 = Cylinder(vec3(0,0,WIDTH-200),100,15)
    plyp1 = physics.body(CIRCLE,100)
    plyp1.position = vec2(WIDTH-200,0)
    plyp1.gravityScale = 0
    plyp1.friction = 0.2
    plyp1.restitution = 0.2
    plyp1.sleepingAllowed = false
    plyp1.interpolate = true
    plyp1.categories = {0,1}
    
    ply2 = Cylinder(vec3(0,0,0),100,15)
    plyp2 = physics.body(CIRCLE,100)
    plyp2.position = vec2(-800,0)
    plyp2.gravityScale = 0
    plyp2.friction = 0.2
    plyp2.restitution = 0.2
    plyp2.sleepingAllowed = false
    plyp2.interpolate = true
    plyp2.categories = {0,1}
    
    splitter = physics.body(EDGE,vec2(0,-135*5),vec2(1,135*5))
    splitter.type = STATIC
    back1 = physics.body(EDGE,vec2(1000,-60*5),vec2(1001,60*5))
    back1.categories = {0}
    back1.type = STATIC
    back2 = physics.body(EDGE,vec2(-1000,-60*5),vec2(-1001,60*5))
    back2.categories = {0}
    back2.type = STATIC
    
    score1 = 0
    score2 = 0
end

function touched(t)
    if t.state == BEGAN then
        if t.x > WIDTH/2 then
            t1:touched(t)
        elseif t.x < WIDTH/2 then
            t2:touched(t)
        end
    end
    
    if t1.mov and t.id == t1.id then
        t1:touched(t)    
    end
    
    if t2.mov and t.id == t2.id then
        t2:touched(t)
    end
    
end

-- This function gets called once every frame 
function draw()
    if t1.state == MOVING then
        if t1.x > WIDTH/2 then
            t1.state = ENDED
        end
        plyp1:applyForce(vec2(t1.deltaX,-t1.deltaY)*1000)
    end
    
    if t2.state == MOVING then
        if t2.x < WIDTH/2 then
            t2.state = ENDED
        end
        plyp2:applyForce(vec2(t2.deltaX,-t2.deltaY)*1000)
    end
    
    if puckp.x > 1200 or puckp.x < -1200 then
        if puckp.x > 1200 then
            score1 = score1 + 1
        end
        if puckp.x < -1200 then 
            score2 = score2 + 1 
        end
        puckp.position  = vec2(0,0)
        puckp.linearVelocity = vec2(0,0)
        plyp1.position = vec2(600,0)
        plyp2.position = vec2(-600,0)           
    end
    
    
    
    -- This sets a dark background color 
    background(40, 40, 50)

    -- This sets the line thickness
    pushStyle()
    fill(255)
    text("Player 1 Score:"..score1,120,75)
    text("Player 2 Score:"..score2,WIDTH-120,75)
    popStyle()
    -- Do your drawing here
    perspective(90, WIDTH/HEIGHT)
 
    -- Position the camera up and back, look at origin
    camera(0,900,1,  0, 0, 0, 0,1,0)
    
    for k,v in pairs(cube) do
        v.shader = shadr
        v:draw()
    end
    puckp.linearVelocity = puckp.linearVelocity*0.992
    plyp1.linearVelocity = plyp1.linearVelocity*0.9
    plyp2.linearVelocity = plyp2.linearVelocity*0.9
    pushMatrix()
    local puckpos = puckp.position
    translate(puckpos.x,0,puckpos.y)
    puck.shader = shadr
    puck:draw()
    popMatrix()
    
    pushMatrix()
    local plypos = plyp1.position
    translate(plypos.x,0,plypos.y)
    ply1.shader = shadr
    ply1:draw()
    popMatrix()
    
    pushMatrix()
    local plypos = plyp2.position
    translate(plypos.x,0,plypos.y)
    ply2.shader = shadr
    ply2:draw()
    popMatrix()
    
    ortho()
    
    -- Restore the view matrix to the identity
    viewMatrix(matrix())
    
end


--# Cube
Cube = class()

function Cube:init(pos,size)
    -- you can accept and set parameters here
    self.pos = pos
    self.size = size
    self.verts = {
      pos+vec3(-0.5*size.x, -0.5*size.y,  0.5*size.z), -- Left  bottom front
      pos+vec3( 0.5*size.x, -0.5*size.y,  0.5*size.z), -- Right bottom front
      pos+vec3( 0.5*size.x,  0.5*size.y,  0.5*size.z), -- Right top    front
      pos+vec3(-0.5*size.x,  0.5*size.y,  0.5*size.z), -- Left  top    front
      pos+vec3(-0.5*size.x, -0.5*size.y, -0.5*size.z), -- Left  bottom back
      pos+vec3( 0.5*size.x, -0.5*size.y, -0.5*size.z), -- Right bottom back
      pos+vec3( 0.5*size.x,  0.5*size.y, -0.5*size.z), -- Right top    back
      pos+vec3(-0.5*size.x,  0.5*size.y, -0.5*size.z), -- Left  top    back
    }


    -- now construct a cube out of the vertices above
    self.cverts = {
      -- Front
      self.verts[1], self.verts[2], self.verts[3],
      self.verts[1], self.verts[3], self.verts[4],
      -- Right
      self.verts[2], self.verts[6], self.verts[7],
      self.verts[2], self.verts[7], self.verts[3],
      -- Back
      self.verts[6], self.verts[5], self.verts[8],
      self.verts[6], self.verts[8], self.verts[7],
      -- Left
      self.verts[5], self.verts[1], self.verts[4],
      self.verts[5], self.verts[4], self.verts[8],
      -- Top
      self.verts[4], self.verts[3], self.verts[7],
      self.verts[4], self.verts[7], self.verts[8],
      -- Bottom
      self.verts[5], self.verts[6], self.verts[2],
      self.verts[5], self.verts[2], self.verts[1],
    }
    self.texverts = { vec2(0.03,0.24),
                          vec2(0.97,0.24),
                          vec2(0.03,0.69),
                          vec2(0.97,0.69) }
                
    -- apply the texture coordinates to each triangle
      self.texCoords = {
      -- Front
      self.texverts[1], self.texverts[2], self.texverts[4],
      self.texverts[1], self.texverts[4], self.texverts[3],
      -- Right
      self.texverts[1], self.texverts[2], self.texverts[4],
      self.texverts[1], self.texverts[4], self.texverts[3],
      -- Back
      self.texverts[1], self.texverts[2], self.texverts[4],
      self.texverts[1], self.texverts[4], self.texverts[3],
      -- Left
      self.texverts[1], self.texverts[2], self.texverts[4],
      self.texverts[1], self.texverts[4], self.texverts[3],
      -- Top
      self.texverts[1], self.texverts[2], self.texverts[4],
      self.texverts[1], self.texverts[4], self.texverts[3],
      -- Bottom
      self.texverts[1], self.texverts[2], self.texverts[4],
      self.texverts[1], self.texverts[4], self.texverts[3],
    }
    self.m = mesh()
    self.m.vertices = self.cverts
    self.m.texCoords = self.texCoords
    --self.m.texture = readImage("Documents:checker")
    self.m:setColors(255,255,255,255)
    -- all the unique texture positions needed
end

function Cube:setColors(colr,colg,colb,alpha)
    local a = alpha or 255
    self.m:setColors(colr,colg,colb,a)
end

function Cube:draw()
    self.m:draw()
end

function Cube:touched(touch)
    -- Codea does not automatically call this method
end

Cylinder and touch:


--# Cylinder
Cylinder = class()

function Cylinder:init(pos,radius,height)
    -- you can accept and set parameters here
    self.pos = pos
    self.rad = radius
    self.h = height
    
    local vertices={}
    local edges = {}
    local numfaces=24
    local top=height
    local bottom=0
    local r=radius

--corners every second set of faces
    for face=1,numfaces do
        rot=face*math.rad(360/numfaces)
        adj=math.rad(180/numfaces)--was6
        edges[face] = vec2(r*math.sin(rot-adj),r*math.cos(rot-adj))
        vertices[(4*face)-3]=vec3(r*math.sin(rot-adj), bottom,  r*math.cos(rot-adj)) -- Left  bottom front
        vertices[(4*face)-2]=vec3( r*math.sin(rot+adj), bottom,  r*math.cos(rot+adj)) -- Right bottom front
        vertices[(4*face)-1]=vec3( r*math.sin(rot+adj),  top,  r*math.cos(rot+adj)) -- Right top    front
        vertices[(4*face)]=vec3(r*math.sin(rot-adj),  top,  r*math.cos(rot-adj)) -- Left  top    front
    end
    local cubeverts = {}
    for face=1,numfaces do
        table.insert(cubeverts,vertices[(face*4)-3])
        table.insert(cubeverts,vertices[(face*4)-2])    
        table.insert(cubeverts,vertices[(face*4)-1])
        table.insert(cubeverts,vertices[(face*4)-3])    
        table.insert(cubeverts,vertices[(face*4)-1])
        table.insert(cubeverts,vertices[(face*4)])    
    end

    local texv={}
    for i=0,numfaces-1 do
        table.insert(texv,vec2(i/numfaces,0))
        table.insert(texv,vec2((i+1)/numfaces,0))
        table.insert(texv,vec2(i/numfaces,1))
        table.insert(texv,vec2((i+1)/numfaces,1))                       
    end

    local cubetexCoords={}

    for face=1,numfaces do    
        table.insert(cubetexCoords,texv[(face*4)-3])
        table.insert(cubetexCoords,texv[(face*4)-2])    
        table.insert(cubetexCoords,texv[(face*4)])
        table.insert(cubetexCoords,texv[(face*4)-3])    
        table.insert(cubetexCoords,texv[(face*4)])
        table.insert(cubetexCoords,texv[(face*4)-1])    
    end
    local edg = triangulate(edges)
    for i=1,#edg do
        edg[i] = vec3(edg[i].x,bottom,edg[i].y)
    end
    local bottomverts = edg
    
    local edg2 = triangulate(edges)
    for i=1,#edg2 do
        edg2[i] = vec3(edg[i].x,top,edg[i].y)
    end
    local topverts = edg2
    
    self.t = top
    
    self.top = mesh()
    self.top.vertices = topverts
    self.top:setColors(200,200,200,255)
    
    self.bottom = mesh()
    self.bottom.vertices = bottomverts
    self.bottom:setColors(200,200,200,255)
    self.bottom.shader = shadr
    
    self.m = mesh()
    self.m.vertices = cubeverts
    self.m:setColors(150,150,150,255)
    self.m.texCoords = cubetexCoords
    --self.m.texture = readImage("Documents:colour")
    self.m.shader = shadr
end

function Cylinder:draw()
    self.m:draw()
    self.top:draw()
    pushMatrix()
    translate(0,self.t,0)
    self.bottom:draw()
    popMatrix()
end

function Cylinder:touched(touch)
    -- Codea does not automatically call this method
end

--# Touch
Touch = class()

function Touch:init()
    -- you can accept and set parameters here
    self.x = 0
    self.y = 0
    self.deltaX = 0
    self.deltaY = 0
    self.state = nil
    self.prevX = 0
    self.prevY = 0
    self.mov = nil
    self.id = nil
end

function Touch:draw()
    -- Codea does not automatically call this method
end

function Touch:touched(t)
    if t.state == BEGAN and self.mov == nil then
        self.mov = true
        self.id = t.id
    end
    if self.mov then
    self.t = t
    self.x = t.x
    self.y = t.y
    self.deltaX = t.deltaX
    self.deltaY = t.deltaY
    self.state = t.state
    self.prevX = t.prevX
    self.prevY = t.prevY
    end
    if t.state == ENDED and self.mov then
        self.mov = nil
        self.id = nil
    end
end

@Luatee, very nice! I did quickly find a bug, however. The puck is able to get stuck on a wall or in a corner. It seems as though when the puck location matches the location of a wall, then it will not gain velocity to bounce off the wall.

Wow, very clever combination, why would you use chains in stead of polygons to create the physic bodies of the board?

It remains me of a game I made last year:

http://www.youtube.com/watch?v=g1_5HBX97yA

@Slashin8r I know that, it’s something I think I’ve fixed now but I’m still not sure ill need to check it out more

@juaxix Thanks I thought of it a few days ago, hadn’t seen anyone doing it before, in the forums anyway. Well I’m just starting off in 3D so I’ve still got a few things to learn, well a lot of things such as lighting and shaders but what you’ve got looks amazing I’m doubting I’ll be able to make anything like that soon, just a few obstacles…

video for people who don’t want to copy and paste: http://youtu.be/0w9v_wvIGRI

cool game , nice work @luatee

How is it 3D?

It has a camera and a perspective, you’ll notice the change in the pucks when moved

Oh, okay. I messed with the camera and now it looks totally 3D, though the puck doesn’t work anymore.

Yeah the pucks work on each side of the screen in landscape, using the delta of your touch

Ok