# 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.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()
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: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:draw()
popMatrix()

pushMatrix()
local plypos = plyp1.position
translate(plypos.x,0,plypos.y)
ply1:draw()
popMatrix()

pushMatrix()
local plypos = plyp2.position
translate(plypos.x,0,plypos.y)
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: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()

-- you can accept and set parameters here
self.pos = pos
self.h = height

local vertices={}
local edges = {}
local numfaces=24
local top=height
local bottom=0

--corners every second set of faces
for face=1,numfaces do
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.m = mesh()
self.m.vertices = cubeverts
self.m:setColors(150,150,150,255)
self.m.texCoords = cubetexCoords
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:

@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