Skip to content

Instantly share code, notes, and snippets.

@dermotbalson
Created June 11, 2015 05:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dermotbalson/ae776c7b74cb3bf2e70e to your computer and use it in GitHub Desktop.
Save dermotbalson/ae776c7b74cb3bf2e70e to your computer and use it in GitHub Desktop.
Multi player FPS
--# Notes
--[[
3D FPS
by ignatz 2015
How to play
* Single player *
Run the program and select single player to play on your own
Use the blue joystick at bottom left to move around
The blue jump pads inside the building will take you up to the next level
(to get back down, just jump off the side of the building)
The red slider at top left allows you to zoom the view
Touch anywhere on the screen except the joystick and slider, and you shoot (up to 6 shots at present)
* Mult player *
currently wifi local network only
select "Host" on the first iPad
Select "Search and Join" on the other iPad, and they should connect
Move about and see if you can see each other (at this stage, they might end up both spawned at the same point!)
You can do all the stuff from single player, but bullets don't hurt - yet!
--]]
--# Main
-- BlockTown
--game states
states={SETUP=1,MULTIPLAYER_SELECTION=2,SINGLE_PLAYER=3,MULTI_PLAYER=4}
function setup()
status=states.SETUP
Settings()
CreateScene(T2) --parameter is name of map table in MapData, ie which map to load
status=states.MULTIPLAYER_SELECTION
displayMode(OVERLAY)
ConnectSocket() --multiplayer connection
end
function Settings()
--lighting
ambient=0.3
diffuse=0.6
lightDirection=vec3(1,1,1):normalize()
lightIntensity=1
mist=100 --unused
mistColor=color(230) --unused
camY=4 --height of camera above surface
FPS=60
--controls and control sensitivity
joy1=JoyStick({radius=150})
turnSensitivity=0.25
speed=0
speedFactor=0.5
speedSensitivity=0.25
lookFactor=0.03
velocity=vec3(0,0,0)
--zoom settings
displayMode(FULLSCREEN) --so we can get height and width on next line
zoomStart,zoomEnd=vec2(5,HEIGHT-50),vec2(250,HEIGHT-5) --position of zoom bar on screen
zoomFOV=45 --current FOV (field of view, ie width of view in degrees)
minZoom,maxZoom=1,55 --FOV range (smaller = more zoom)
zoomX=zoomStart.x+(zoomFOV-minZoom)/maxZoom*(zoomEnd.x-zoomStart.x)
end
function DownloadAssets()
end
function draw()
background(mistColor)
multihandler:update()
if status==states.MULTI_PLAYER or status==states.SINGLE_PLAYER then --we are playing
FPS=FPS*0.95+0.05/DeltaTime
AdjustPosition()
perspective(zoomFOV)
cam=playerPos+vec3(0,camY,0)
camLook=cam+look
camera(cam.x,cam.y,cam.z,camLook.x,camLook.y,camLook.z)
DrawScene(cam,camLook,mist+math.sin(ElapsedTime*.5)*10)
SendSocketData()
DrawHUD()
elseif status==states.MULTIPLAYER_SELECTION then --we are waiting for other player to connect
fill(0)
text("Waiting for connection...", WIDTH / 2, HEIGHT / 2)
elseif status==states.SETUP then
fill(0)
text("Setting up and downloading assets", WIDTH / 2, HEIGHT / 2)
end
end
function touched(t)
--test for joystick touch, zoom touch, otherwise we are shoooting
if not joy1:touched(t) and not ZoomTouch(t) then
if t.state==ENDED then weapon:Shoot(t) end
end
end
--adjusts player position for joystick movement, prevents collisions
--makes player fall to ground if in the air
--joystick movement only occurs if the joystick is moved more than turnSensitivity horizontally, or
--speedSensitivity vertically. This is so that if you only want to move sideways or vertically, you
--don't need to move your finger exactly horizontally or vertically
--in other words, you have to seriously move sideways or vertically to get a reaction
function AdjustPosition()
--update velocity
local v1=joy1:update() --get joystick movement (-1 to +1) if any
if joy1.touch then
local t=turnSensitivity
--only use joystick movement in excess of turnSensitivity
if v1.y<0 then v1.y=math.min(0,v1.y+t)/(1-t) else v1.y=math.max(0,v1.y-t)/(1-t) end
speed=v1.y*speedFactor
--update player position
local p=playerPos+look*speed --get new position, test if valid
playerPos=ManagePosition(playerPos,p) --prevent collisions, fall to ground if in air
--update look direction
local t=speedSensitivity --similar to turnSensitivity
if v1.x<0 then v1.x=math.min(0,v1.x+t)/(1-t) else v1.x=math.max(0,v1.x-t)/(1-t) end
lookAngle=lookAngle-v1.x*lookFactor --turn angle
look=vec3(math.cos(lookAngle),0,-math.sin(lookAngle)) --direction we are looking in
UpdateSettings(playerPos)
else --no player movement, just manage the position (eg falling to the ground)
playerPos=ManagePosition(playerPos)
end
end
--2D display overlaid on scene
function DrawHUD()
ortho()
viewMatrix(matrix())
fill(0,0,0)
fontSize(16)
text("FPS="..math.floor(FPS),WIDTH-50,HEIGHT-25)
weapon:draw() --draw current weapon
joy1:draw() --joystick
DrawZoom() --zoom b
end
function DrawZoom()
if not Zoom then
Zoom=mesh()
Zoom.vertices={zoomStart,vec2(zoomEnd.x,zoomStart.y),zoomEnd}
Zoom:setColors(255,0,0,125)
end
Zoom:draw()
pushStyle()
stroke(255,0,0)
strokeWidth(2)
line(zoomX,zoomStart.y,zoomX,zoomEnd.y)
popStyle()
end
--player touches zoom bar, alter FOV
function ZoomTouch(t)
if t.x>zoomStart.x and t.x<=zoomEnd.x and t.y<zoomEnd.y and t.y>zoomStart.y then
--
zoomFOV=minZoom+(maxZoom-minZoom)*(t.x-zoomStart.x)/(zoomEnd.x-zoomStart.x)
--position of vertical marker on zoom bar shows current setting
zoomX=zoomStart.x+(zoomFOV-minZoom)/(maxZoom-minZoom)*(zoomEnd.x-zoomStart.x)
return true
end
end
--# MapData
--Map Data
--contains level definitions
--map is made up of tiles
--each tile can contain one object
--T2
T2={}
T2.tileSize=8 --number of pixels in each tile
T2.mapSize=vec2(30,30) --in tiles. Even numbers for x, please, so we can split it evenly each side of 0
--all the random spawn positions (in tile values)
T2.spawn={{vec3(2,0,2),45},{vec3(2,0,29),-45},{vec3(29,0,2),135},{vec3(29,0,29),-135}}
T2.spriteSheet="Dropbox:T1" --image containing all textures
--T2.spriteURL="http://i1303.photobucket.com/albums/ag142/ignatz_mouse/T1_zpsnw3yewgw.png"
--list of images in texture. The five numbers are (x,y) of bottom left, width, height, scale factor
--scale factor is used to shrink the original image to a size that matches the rest of the scene
T2.images={
sky={0,0,2000,500,2048/2000},
block={611,1701,294,300,0.0166},
surface={1,1401,599,600,0.02},
fence={1,880,511,511,0.01},
jump={610,1390,300,300,0.05},
revolver={530,1185,200,190}
}
--most scenes will contain repeated 3D objects, like cubes for walls
--this is where you define these objects
--example
-- A = {type="B",image="C"}
--A is the character that will be used for this object in the ASCII map below
--B is the object label that will be used by the BuildObjects function (this function will
--read all the items in this table and build them. The label B is used to tell BuidObjects
--what to build, ie that function needs code that says "if the label is B then build this.."
--C is the image, if any, to use in building the object
T2.objects={
b={type="block5",image="block"}, --blocks without bottom faces, used for ground floor layer
B={type="block6",image="block"}, --blocks with all 6 faces
j={type="jump",image="jump"} --jump pad
}
--lighting is different indoors and outdoors. Diffuse lighting is best outdoors to highlight doors
--diffuse light doesn't work indoors, and ambient light works best
--because of the way maps are defined (below),outdoor tiles are empty (ie nil) and this is treated
--as the "defaults" below, so default means outdoor
--indoor tiles (that can be walked on) are marked with spaces below, so any time we are walking on
--a tile with a space, we are indoors.
--You can use other chars than spaces in the map, as long as you add their lighting settings here
--settings for outdoor (default) and indoor(spaces)
T2.tileSettings={
default={ambient=0.4,diffuse=0.6,mist=100}, --outdoor
[" "]={ambient=0.7,diffuse=0,mist=99999999} --indoor
}
--It is hard to specify a 3D map using 2D text
--This is how it works below
--There are several "sets" of tiles in the table below.
--Each one defines a starting tile. Bottom left is (1,0,1)
--the y value is 0 for ground floor, 1 for first floor, and so on
--Having defined the starting point, you define a table of tiles
--so in this example
--{start=vec3(3,0,4),tiles=
-- {"bbbbb",
-- "bb b",
-- "bb b"}
--This defines three rows of whatever b is (in the map below they are blocks)
--the bottom left row starts at (3,0,4), and ends at (7,0,4) on the right
--the middle row starts at (3,0,5) and ends at (7,0,5)
--the top row starts at (3,0,6) and ends at (7,0,6)
--so the map is pretty much how you would see it from above
--the bottom of the map is closest to you and the top is furthest (toward negative z)
--you can use any readable char you like as long as you tell Codea what to do with it
--in the ReadMap function. If you want to create an object for multiple use, eg a block,
--then you define it in the objects section higher up, along with its character code
T2.mapData={
{start=vec3(8,0,8),tiles=
{"bbbbbbbbbbb bbbb", --bottom floor, these are blocks with no bottom face
"b b b b", --spaces used for corridors and rooms
"b bbbb b",
"b b b bbb b",
"b b b bbb b",
"b bbb bbbbbbb b",
"b bb j b b",
"bbbbb b",
"b bbbbb bbbbbb",
"b b b b",
"bbbb b b b",
"b bbbbbbbbb b",
"b b b",
"bb bbbbbbbbb b",
"bb b b",
"bb bbbbbbbbbbbbb"},
},
{start=vec3(8,1,8),tiles=
{"BBBBBBBBBBBBBBBB", --roof for bottom floor, with gap for jump pad
"BBBBBBBBBBBBBBBB", --these blocks have all 6 faces
"BBBBBBBBBBBBBBBB",
"BBBBBBBBBBBBBBBB",
"BBBBBBBBBBBBBBBB",
"BBBBBBBBBBBBBBBB",
"BBBBBBBBBB BBBBB",
"BBBBBBBBBBBBBBBB",
"BBBBBBBBBBBBBBBB",
"BBBBBBBBBBBBBBBB",
"BBBBBBBBBBBBBBBB",
"BBBBBBBBBBBBBBBB",
"BBBBBBBBBBBBBBBB",
"BBBBBBBBBBBBBBBB",
"BBBBBBBBBBBBBBBB",
"BBBBBBBBBBBBBBBB"}
},
{start=vec3(9,2,9),tiles=
{"bbbbbbbb", --second floor
"b b",
"b j b b",
"b b b",
"b bbbb b",
"b b b",
"b b bbbb",
"b b ",
"bbbbbbb b",
"b bbbbb b",
"b b b",
"b b b",
"b b b",
"bbbbbbbbbbb"},
},
{start=vec3(9,3,9),tiles=
{"BBBBBBBB", --second floor roof
"BBBBBBBB",
"BBB BBBB",
"BBBBBBBB",
"BBBBBBBB",
"BBBBBBBB",
"BBBBBBBBBBB",
"BBBBBBBBBBB",
"BBBBBBBBBBB",
"BBBBBBBBBBB",
"BBBBBBBBBBB",
"BBBBBBBBBBB",
"BBBBBBBBBBB",
"BBBBBBBBBBB"}
}
}
--# Maps2
--Maps
local sceneWidth,sceneDepth,block,prevTileCode
function CreateScene(map)
M=map
mapSize=M.mapSize*M.tileSize
spriteSheet=readImage(M.spriteSheet) --master image
if spriteSheet==nil then http.request(M.spriteURL,ImageDownloaded)
else CreateScene2() end
end
function ImageDownloaded(img)
saveImage(M.spriteSheet,img)
spriteSheet=readImage(M.spriteSheet)
CreateScene2()
end
function CreateScene2()
spriteWidth,spriteHeight=spriteSheet.width,spriteSheet.height
--surface mesh
surface=CreateSurface(M.images.surface)
--build fence
fence=BuildFence(M.images.fence)
--build basic objects
objects=BuildObjects()
playerBody=CreatePlayerBody(M.tileSize*1)
weapon=Revolver(M.images.revolver,6)
--read map and create mesh containing objects
objMesh=ReadMap()
--print(objMesh.size) --mesh size before culling
CullDupFaces(objMesh) --cull unseen faces
--print(objMesh.size) --mesh size after culling
Spawn() --choose initial player position
sky=SetupSky(GetImage(M.images.sky))
end
--updates lighting if we transition indoors/outdoors
function UpdateSettings(p) --p is pixel position
local t=PixelToPos(p)
local m=memMap[t.x*1000000+t.y*1000+t.z] or "default" --get code for the tile we are in
if m~=prevTileCode then --if it's changed..
prevTileCode=m
if M.tileSettings[m] then --set settings for this code
ambient=M.tileSettings[m].ambient
diffuse=M.tileSettings[m].diffuse
mist=M.tileSettings[m].mist
end
end
end
--main drawing function ofr scene
function DrawScene(cam,look,mist)
sky:draw()
surface.shader.intensity=lightIntensity
surface:draw()
fence.shader.intensity=lightIntensity
fence:draw()
objMesh=SetLightingShader(objMesh)
objMesh:draw()
--these can be anything, currently just enemies
for _,O in pairs(Objects) do
if O.enemy then
O:Update(enemyPos,enemyRot)
O:draw()
end
end
end
function SetLightingShader(m)
m.shader.ambient=ambient
m.shader.diffuse=diffuse
m.shader.intensity=lightIntensity
m.shader.mModel=modelMatrix()
return m
end
--culls unseen faces by finding pairs of faces that occupy the same position, and deleting them
function CullDupFaces(m,seq)
local bv=m:buffer("position")
local bt=m:buffer("texCoord")
local bn=m:buffer("normal")
local bc=m:buffer("color")
local A,B={},{}
local r=100000
for i=1,m.size,6 do
local c=bv[i]+bv[i+1]+bv[i+2]+bv[i+4]
local cc=math.floor(r+c.x*100+0.5)..","..math.floor(r+c.y*100+0.5)..","..math.floor(r+c.z*100+0.5)
A[cc]=(A[cc] or 0)+1
for j=0,5 do B[i+j]=cc end
end
local v,t,n,c={},{},{},{}
local u=0
for i=1,m.size do
if A[B[i]]==1 then
u=u+1
v[u]=bv[i]
t[u]=bt[i]
n[u]=bn[i]
if bc then c[u]=bc[i] end
end
end
m.vertices=v
m.texCoords=t
m.normals=n
if bc then m.colors=c end
end
--key function that reads the map initially
function ReadMap()
memMap={}
local A={}
A.v,A.t,A.n,A.c={},{},{},{}
for _,a in pairs(M.mapData) do --for each "block" of ASCII
local p0=PosToPixel(a.start) --bottom left tile location
for i=#a.tiles,1,-1 do --process map from bottom to top, ie from nearest to furthest
for j=1,string.len(a.tiles[i]) do --for each ASCII char
e=string.sub(a.tiles[i],j,j) --get char
memMap[(a.start.x+j-1)*1000000+a.start.y*1000+(a.start.z+i-1)]=e --store in internal table
local f=objects[e] --try setting f to the object defined for e
if f then --if there is an object, eg a block...
for k=1,#f.v do --add all its vertices, coords etc to the mesh
A.v[#A.v+1]=f.v[k]+p0+vec3(j-1,0,-i+1)*M.tileSize
A.t[#A.t+1]=f.t[k]
A.n[#A.n+1]=f.n[k]
A.c[#A.c+1]=f.c[k]
end
end
end
end
end
--fill in fence around edge in memMap
for i=0,M.mapSize.x+1 do
memMap[i*1000000]="b"
memMap[i*1000000+M.mapSize.y+1]="b"
end
for i=0,M.mapSize.y+1 do
memMap[i]="b"
memMap[(M.mapSize.x+1)*1000000+i]="b"
end
--create mesh
local m=mesh()
m.vertices=A.v
m.texCoords=A.t
m.texture=mapImage
m.normals=A.n
m.colors=A.c
m=AddShader(m)
return m
end
function AddShader(m)
m.shader=shader(Diffuse.v,Diffuse.f)
m.shader.lightDirection=lightDirection
m.shader.intensity=lightIntensity
m.shader.ambient=ambient
m.shader.diffuse=diffuse
m.shader.mistColor=mistColor
return m
end
--creates surface using 2 large triangles
--note how the img data is used (from the images table in the MapData tab)
--img is NOT an image, but a little table with 5 values -
--(x,y) position of bottom left corner, on the master image
--then width and height
--then scaling factor (so to reduce image size by 4, put 0.25)
--the position and size figures are passed to the shader in a colour value
function CreateSurface(img)
local w,d=mapSize.x,mapSize.y
local x1,x2=-w/2,w/2
local y=-0.1
local z1,z2=0,-d
local v={vec3(x1,y,z1),vec3(x2,y,z1),vec3(x2,y,z2),vec3(x1,y,z2)}
--the texture coords are calculated for tiling, ie they can exceed 1
--eg the width of the coords is calculated as image width / image scaling factor
local tw,td=w/img[3]/img[5],d/img[4]/img[5]
local t={vec2(0,0),vec2(tw,0),vec2(tw,td),vec2(0,td)}
local m=mesh()
m.vertices={v[1],v[2],v[3],v[3],v[4],v[1]}
m.texCoords={t[1],t[2],t[3],t[3],t[4],t[1]}
--shader is passed the fractional values of the bottom left corner position, height and width
local c=GetTextureVector(img)
m.colors={c,c,c,c,c,c}
m.texture=M.spriteSheet
m.shader=shader(Surface.v,Surface.f)
m.shader.light=ambient+diffuse --surface doesn't have diffuse lighting
m.shader.mistColor=mistColor --unused
return m
end
function BuildFence(img) --around the sides, similar to surface, one rectangle for each side
local w,d,h0=mapSize.x,mapSize.y,-0.1
local v11,v21,v22,v12=vec3(-w/2,h0,0),vec3(w/2,h0,0),vec3(w/2,h0,-d),vec3(-w/2,h0,-d)
local h=vec3(0,M.tileSize,0)
local m=mesh()
m.vertices={v11,v21,v21+h,v21+h,v11+h,v11, v11,v12,v12+h,v12+h,v11+h,v11,
v12,v22,v22+h,v22+h,v12+h,v12, v21,v22,v22+h,v22+h,v21+h,v21}
local tw,th,td=w/img[3]/img[5],M.tileSize/img[4]/img[5],d/img[4]/img[5]
local t11, t21, t22, t12 =vec2(0,0),vec2(tw,0),vec2(tw,th),vec2(0,th)
local t11d,t21d,t22d,t12d=vec2(0,0),vec2(td,0),vec2(td,th),vec2(0,th)
m.texCoords={t11 ,t21 ,t22 ,t22 ,t12 ,t11 , t11 ,t21 ,t22 ,t22 ,t12 ,t11,
t11d,t21d,t22d,t22d,t12d,t11d, t11d,t21d,t22d,t22d,t12d,t11d}
local c=GetTextureVector(img)
local col={} for i=1,24 do col[i]=c end
m.colors=col
m.texture=M.spriteSheet
m.shader=shader(Surface.v,Surface.f)
m.shader.light=ambient+diffuse
m.shader.mistColor=mistColor
return m
end
--build all the objects required by the map
function BuildObjects()
B={} --object list
for a,b in pairs(M.objects) do --read all the objects in the laist in MapData
--you need to create code to handle each object type
if b.type=="block5" then B[a]=CreateBlock(M.images[b.image],false)
elseif b.type=="block6" then B[a]=CreateBlock(M.images[b.image],true)
elseif b.type=="jump" then B[a]=CreateBillboard(M.images[b.image],"bottom")
end
end
return B
end
--this function creates a cube, if bottom is false, it doesn't include the bottom face
--s is size of block
function CreateBlock(img,bottom,s)
local s=s or M.tileSize
local s2=s/2
local v={vec3(-s2,0, s2),vec3(s2,0, s2),vec3(s2,s, s2),vec3(-s2,s, s2),
vec3(-s2,0,-s2),vec3(s2,0,-s2),vec3(s2,s,-s2),vec3(-s2,s,-s2)}
local vert={v[1],v[2],v[3],v[3],v[4],v[1],v[6],v[5],v[8],v[8],v[7],v[6],
v[2],v[6],v[7],v[7],v[3],v[2],v[5],v[1],v[4],v[4],v[8],v[5],
v[8],v[4],v[3],v[3],v[7],v[8],v[5],v[6],v[2],v[2],v[1],v[5]}
local tw,th=s/img[3]/img[5],s/img[4]/img[5]
local t={vec2(0,0),vec2(tw,0),vec2(tw,th),vec2(0,th)}
local tex={t[1],t[2],t[3],t[3],t[4],t[1],t[1],t[2],t[3],t[3],t[4],t[1],
t[1],t[2],t[3],t[3],t[4],t[1],t[1],t[2],t[3],t[3],t[4],t[1],
t[1],t[2],t[3],t[3],t[4],t[1],t[1],t[2],t[3],t[3],t[4],t[1]}
local n={vec3(0,0,1),vec3(0,0,-1),vec3(1,0,0),vec3(-1,0,0),vec3(0,1,0),vec3(0,-1,0)}
local norm,col={},{}
local c=GetTextureVector(img)
for i=1,#n do
for j=1,6 do
norm[#norm+1]=n[i]
col[#col+1]=c
end
end
if bottom~=true then --remove bottom face if not required
for i=1,6 do
table.remove(vert,#vert)
table.remove(tex,#tex)
table.remove(norm,#norm)
table.remove(col,#col)
end
end
--don't create a mesh because we will have a single mesh
--keep all the mesh elements in tables and pass those back
return {v=vert,t=tex,n=norm,c=col}
end
--adds a block to the mesh
function AddBlock(p,tbl,obj)
pos=PosToPixel(p)
for i=1,#obj.v do
tbl.v[#bb.v+1]=obj.v[i]+pos
tbl.t[#bb.t+1]=obj.t[i]
tbl.n[#bb.n+1]=obj.n[i]
tbl.c[#bb.c+1]=obj.c[i]
end
return tbl
end
--billboard is a flat image
--pos is position, s is size
function CreateBillboard(img,pos,s)
if s==nil then s=M.tileSize/math.max(img[3],img[4]) end
local w,h=img[3]*s,img[4]*s
local v11,v21,v22,v12,n
--needs code to handle other orientations, eg vertical
if pos=="bottom" then --position it on the bottom of the tile
local m=0.1
v11,v21,v22,v12=vec3(-w/2,m,-h/2),vec3(w/2,m,-h/2),vec3(w/2,m,h/2),vec3(-w/2,m,h/2)
n=vec3(0,1,0)
end
local t11,t21,t22,t12=vec2(0,0),vec2(1,0),vec2(1,1),vec2(0,1)
local vert={v11,v21,v22,v22,v12,v11}
local tex ={t11,t21,t22,t22,t12,t11}
local norm={} for i=1,6 do norm[i]=n end
local c=GetTextureVector(img)
local col={} for i=1,6 do col[i]=c end
return {v=vert,t=tex,n=norm,c=col}
end
--adds a billboard to the mesh
function AddBillboard(p,tbl,obj)
pos=PosToPixel(p)
for i=1,#obj.v do
tbl.v[#bb.v+1]=obj.v[i]+pos
tbl.t[#bb.t+1]=obj.t[i]
tbl.n[#bb.n+1]=obj.n[i]
tbl.c[#bb.c+1]=obj.c[i]
end
return tbl
end
--turns a set of image settings (from MapData) into a colour for use by the shader
function GetTextureVector(img)
return vec4(img[1]/spriteSheet.width,img[2]/spriteSheet.height,
img[3]/spriteSheet.width,img[4]/spriteSheet.width)
end
--converts tile position to pixel values
function PosToPixel(p)
return vec3(p.x-0.5-M.mapSize.x/2,p.y,-p.z+0.5)*M.tileSize
end
--convert pixel position to tile values
function PixelToPos(p)
return vec3(math.floor((p.x+mapSize.x/2)/M.tileSize)+1,math.floor(p.y/M.tileSize),
math.floor(-p.z/M.tileSize)+1)
end
--get map code for a given position
function GetMapCode(t)
return memMap[t.x*1000000+t.y*1000+t.z]
end
--get image from image settings
function GetImage(t)
local img=spriteSheet:copy(t[1],t[2],t[3],t[4])
if t[5]~=1 then
local w,h=t[3]*t[5],t[4]*t[5]
local img2=image(w,h)
setContext(img2)
sprite(img,w/2,h/2,w)
setContext()
return img2
else return img end
end
--important function
--p0 is previous position
--p1 is new position
function ManagePosition(p0,p1)
if p1 then
local t=PixelToPos(p1)
local m=GetMapCode(t) --get map code for new position
if m=="b" then --block! have to move back
--calculate direction of movement by using tile positions
local v=PixelToPos(p0)-PixelToPos(p1) --direction back the way we came
--move back towards previous tile by 1/3 of tile size
p1=p0+vec3(v.x,0,-v.z)*M.tileSize/3
elseif m=="j" then p1.y=p1.y+M.tileSize*3 end --if we're on a jump pad, jump 3 tiles upwards
else p1=p0 end --no joystick movement
--check if we are in the air, if so fall toward ground
if p1.y>0 then
local a,b=math.modf(p1.y/M.tileSize) --a=tile position under us, b is fraction
local d
--if there is something in the square underneath, we can only fall by b,
--otherwise we can keep falling (add 1 to b to make sure we fall past the tile boundary
if GetMapCode(PixelToPos(p1-vec3(0,1,0))) or a==0 then d=b else d=1+b end
p1.y=p1.y-math.min(d,M.tileSize/50) --fall by tileSize/50 each frame
end
return p1
end
function Spawn() --spawn from random position
local s=M.spawn[math.random(1,#M.spawn)] --choose spawn position and direction
playerPos=PosToPixel(s[1])
lookAngle=math.rad(s[2])
look=vec3(math.cos(lookAngle),0,-math.sin(lookAngle))
end
--# Classes
--classes
Objects={}
-------------------------- player on this iPad -------------------
player=class()
function player:init(name,obj) --obj is body mesh to draw on screen (on other iPad)
self.name=name or "Someone"
--local p=M.spawn[math.random(1,#M.spawn)] --initial spawn - TODO prevent too close to other players
--self.pos=p[1]
--self.rot=math.rad(p[2])
self.body=obj
self.status=0 --0=standing,1=walking,2=running
self.health=100
self.kills,self.killed=0,0
--table.insert(Objects,self)
end
function player:Hurt(distance,damage)
self.health=self.health-damage*math.max(0,1-distance/self.body[3])
if self.health<0 then
self.killed=self.killed+1
Broadcast(self.name.." was killed by "..MyName)
end
end
function player:Spawn() --spawn from random position
local s=M.spawn[math.random(1,#M.spawn)] --choose spawn position and direction
self.pos=PosToPixel(s[1])
self.rot=math.rad(s[2])
self.look=vec3(math.cos(lookAngle),0,-math.sin(lookAngle))
end
----------------enemy players ------------------
--update position or rotation angle
enemies={}
enemy=class()
function enemy:init(name,obj,pos,rot)
self.name=name or "enemy"
self.pos=pos
self.rot=rot
self.body=obj
table.inser(enemies,self)
end
function enemy:Update(p,rot)
if p then self.pos=p end
if rot then self.rot=rot end
end
function enemy:draw()
pushMatrix()
translate(self.pos:unpack())
rotate(self.rot,0,1,0)
--body consists of three parts
--[1] is head and trunk
--[2] is left leg
--[3] is right leg
self.body[1]:draw()
if self.status>0 then --move legs
local s=math.sin(ElapsedTime*10)
local s1=M.tileSize*0.015*s*self.status
local s2=M.tileSize*0.015*-s*self.status
translate(0,s1,0)
self.body[2][1]:draw()
translate(0,s2-s1,0)
self.body[2][2]:draw()
else
self.body[2][1]:draw()
self.body[2][2]:draw()
end
popMatrix()
end
--# Objects
--Objects
--These will be all the interactive objects, eg players, weapons, pickups
Objects={}
--enemy players
player=class()
function player:init(name,obj) --obj is body mesh to draw on screen
self.name=name or "Someone"
local p=M.spawn[math.random(1,#M.spawn)] --initial spawn - TODO prevent too close to other players
self.pos=p[1]
self.rot=math.rad(p[2])
self.body=obj
self.status=0 --0=standing,1=walking,2=running
if obj then self.enemy=true end
self.health=100
self.kills,self.killed=0,0
table.insert(Objects,self)
end
--update position or rotation angle
function player:Update(p,rot)
if p then self.pos=p end
if rot then self.rot=rot end
end
--draw enemy player
function player:draw()
pushMatrix()
translate(self.pos:unpack())
rotate(self.rot,0,1,0)
--body consists of three parts
--[1] is head and trunk
--[2] is left leg
--[3] is right leg
self.body[1]:draw()
if self.status>0 then --move legs
local s=math.sin(ElapsedTime*10)
local s1=M.tileSize*0.015*s*self.status
local s2=M.tileSize*0.015*-s*self.status
translate(0,s1,0)
self.body[2][1]:draw()
translate(0,s2-s1,0)
self.body[2][2]:draw()
else
self.body[2][1]:draw()
self.body[2][2]:draw()
end
popMatrix()
end
function player:Hurt(distance,damage)
self.health=self.health-damage*math.max(0,1-distance/self.body[3])
if self.health<0 then
self.killed=self.killed+1
Broadcast(self.name.." was killed by "..MyName)
end
end
--create mesh for enemy player
--uses jumping image as placeholder texture, will create better one
function CreatePlayerBody(s) --s= vertical size in px
local b=CreateBlock2(vec3(0,6.4/7,0)*s,s/7,s/7,s*1/7,M.images.jump)
b=CreateBlock2(vec3(0,4.4/7,0)*s,s/4,s/7,s*3/7,M.images.jump,b)
local body=MakeBodyMesh(b)
local legs={}
legs[1]=MakeBodyMesh(CreateBlock2(vec3(-s/100,2/7,0)*s,s/12,s/12,s*4/7,M.images.jump))
legs[2]=MakeBodyMesh(CreateBlock2(vec3( s/100,2/7,0)*s,s/12,s/12,s*4/7,M.images.jump))
local hitDistance=s*0.75 --can be hurt by a shot hitting up to this distance from centre
return {body,legs,hitDistance}
end
function MakeBodyMesh(b)
local m=mesh()
m.vertices=b.v
m.texCoords=b.t
m.colors=b.c
m.normals=b.n
m=AddShader(m)
return m
end
function CreateBlock2(p,w,d,h,img,tbl)
local w2,d2,h2=w/2,d/2,h/2
local v={vec3(-w2,-h2, d2)+p,vec3(w2,-h2, d2)+p,vec3(w2,h2, d2)+p,vec3(-w2,h2, d2)+p,
vec3(-w2,-h2,-d2)+p,vec3(w2,-h2,-d2)+p,vec3(w2,h2,-d2)+p,vec3(-w2,h2,-d2)+p}
local vert={v[1],v[2],v[3],v[3],v[4],v[1],v[6],v[5],v[8],v[8],v[7],v[6],
v[2],v[6],v[7],v[7],v[3],v[2],v[5],v[1],v[4],v[4],v[8],v[5],
v[8],v[4],v[3],v[3],v[7],v[8],v[5],v[6],v[2],v[2],v[1],v[5]}
local tw,th=1,1
local t={vec2(0,0),vec2(tw,0),vec2(tw,th),vec2(0,th)}
local tex={t[1],t[2],t[3],t[3],t[4],t[1],t[1],t[2],t[3],t[3],t[4],t[1],
t[1],t[2],t[3],t[3],t[4],t[1],t[1],t[2],t[3],t[3],t[4],t[1],
t[1],t[2],t[3],t[3],t[4],t[1],t[1],t[2],t[3],t[3],t[4],t[1]}
local n={vec3(0,0,1),vec3(0,0,-1),vec3(1,0,0),vec3(-1,0,0),vec3(0,1,0),vec3(0,-1,0)}
local norm,col={},{}
local c=color(img[1]*255/spriteWidth,img[2]*255/spriteHeight,
img[3]*255/spriteWidth,img[4]*255/spriteHeight)
for i=1,6 do
for j=1,6 do
norm[#norm+1]=n[i]
col[#col+1]=c
end
end
if tbl==nil then tbl={} tbl.v,tbl.t,tbl.c,tbl.n={},{},{},{} end
for i=1,#vert do
tbl.v[#tbl.v+1]=vert[i]
tbl.t[#tbl.t+1]=tex[i]
tbl.c[#tbl.c+1]=col[i]
tbl.n[#tbl.n+1]=norm[i]
end
return tbl
end
--revolver weapon
Revolver=class()
function Revolver:init(img,bullets)
self.image=spriteSheet:copy(img[1],img[2],img[3],img[4])
self.imagePos=vec2(WIDTH-self.image.width/2,self.image.height/2-9)
self.pauseBetweenShots=0.5
self.lastShotTime=0
self.recoilRotation=0
self.bullets=bullets
self.damage=50
self.wobble=0 --hand wobbles by up to this many degrees each way
end
function Revolver:draw()
--[[
--code from dungeon that allows for weapon to be picked up, UNUSED yet
if playerPos then --called from main draw function
if not self.collected then
AI.Base.draw(self,playerPos,lightRange)
if self:HasCollided(playerPos) then
self.collected=true
player:Collect(self)
end
end
--]]
--else --called at end of main draw, in 2D section, when gun is in the hand --ver1.08
if self.recoilRotation~=0 then
pushMatrix()
pushStyle()
spriteMode(CENTER)
translate(self.imagePos.x,0)
rotate(self.recoilRotation)
translate(0,self.imagePos.y)
sprite(self.image,0,0)
popStyle()
popMatrix()
else
pushStyle()
spriteMode(CENTER)
sprite(self.image,self.imagePos.x,self.imagePos.y)
popStyle()
end
-- end
end
function Revolver:Shoot(t)
if self.bullets==0 or self.lastShotTime and ElapsedTime<self.lastShotTime+self.pauseBetweenShots then
return end
for _,e in pairs(objects) do
if e.enemy then
local s=ShootError(e.pos,t,self.wobble)
e:Hurt(s,self.damage)
end
end
self.lastShotTime=ElapsedTime
self.bullets=self.bullets-1
sound(SOUND_EXPLODE, 49204)
--Fx.Play("revolver shot")
local r1={recoilRotation=-25}
local r2={recoilRotation=0}
tween.path(2.0, self, {r1,r2} )
end
function ShootError(target,touch,wobble)
--turn touch position into two rotation angles
--add random wobble
x=fov*(touch.y/WIDTH-0.5)+(math.random()-0.5)*wobble*2
y=-fov*(touch.x/HEIGHT-0.5)+(math.random()-0.5)*wobble*2
d=cam:dist(target) --calc distance to target
--create rotation matrix, apply x,y rotations
m=matrix(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1)
m=m:rotate(y,0,1,0)
m=m:rotate(x,1,0,0)
--calculate point which is d distance in that direction
v=cam+(m*look):normalize()*d
--return distance to centre of target
return v:dist(target)
end
--# ShaderMist
--Mist shader - NOT USED
Diffuse={
v=[[
uniform mat4 modelViewProjection;
uniform mat4 mModel;
uniform float ambient;
uniform float diffuse;
uniform vec3 lightDirection;
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
attribute vec3 normal;
varying highp vec2 vTexCoord;
varying lowp vec4 vColor;
varying lowp float vLight;
varying lowp vec4 vPosition;
void main()
{
vTexCoord=texCoord;
vColor=color;
vPosition = mModel * position;
vec4 n = normalize(mModel * vec4(normal,0.0));
float d = clamp(dot(n.xyz, lightDirection ),0.0,1.0);
vLight = (ambient + diffuse * d) ;
gl_Position = modelViewProjection * position;
}
]],
f=[[
precision highp float;
uniform lowp sampler2D texture;
uniform float mist;
uniform vec4 mistColor;
uniform vec4 cam;
uniform float intensity;
varying highp vec2 vTexCoord;
varying lowp vec4 vColor;
varying lowp float vLight;
varying lowp vec4 vPosition;
void main()
{
float x=vColor.r+vColor.b*mod(vTexCoord.x,1.0);
float y=vColor.g+vColor.a*mod(vTexCoord.y,1.0);
lowp vec4 col = texture2D(texture, vec2(x,y));
float f = min(1.0, distance(vPosition,cam)/mist);
col = col * (1.0-f) * vLight + mistColor * f;
col.a=1.0;
gl_FragColor=col;
}
]]
}
Surface={
v=[[
uniform mat4 modelViewProjection;
uniform mat4 mModel;
attribute vec4 position;
attribute vec2 texCoord;
attribute vec4 color;
varying highp vec2 vTexCoord;
varying lowp vec4 vColor;
varying lowp vec4 vPosition;
void main()
{
vTexCoord=texCoord;
vPosition = mModel * position;
vColor=color;
gl_Position = modelViewProjection * position;
}
]],
f=[[
precision highp float;
uniform lowp sampler2D texture;
uniform float light;
uniform float intensity;
uniform float mist;
uniform vec4 mistColor;
uniform vec4 cam;
varying highp vec2 vTexCoord;
varying lowp vec4 vColor;
varying lowp vec4 vPosition;
void main()
{
float x=vColor.r+vColor.b*mod(vTexCoord.x,1.0);
float y=vColor.g+vColor.a*mod(vTexCoord.y,1.0);
lowp vec4 col = texture2D(texture, vec2(x,y));
float f = min(1.0, distance(vPosition,cam)/mist);
col = col * (1.0-f) *light * intensity + mistColor * f;
col.a=1.0;
col.a=1.0;
gl_FragColor=col;
}
]]
}
--# Shaders
--Shaders
Diffuse={
v=[[
uniform mat4 modelViewProjection;
uniform mat4 mModel;
uniform float ambient;
uniform float diffuse;
uniform vec3 lightDirection;
uniform float intensity;
attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;
attribute vec3 normal;
varying highp vec2 vTexCoord;
varying lowp vec4 vColor;
varying lowp float vLight;
void main()
{
vTexCoord=texCoord;
vColor=color;
vec4 n = normalize(mModel * vec4(normal,0.0));
vLight = (ambient + diffuse * clamp(dot(n.xyz, lightDirection ),0.0,1.0)) * intensity ;
gl_Position = modelViewProjection * position;
}
]],
f=[[
precision highp float;
uniform lowp sampler2D texture;
varying highp vec2 vTexCoord;
varying lowp vec4 vColor;
varying lowp float vLight;
void main()
{
lowp vec4 col = texture2D(texture, vec2(vColor.r+vColor.b*mod(vTexCoord.x,1.0),vColor.g+vColor.a*mod(vTexCoord.y,1.0))) * vLight;
col.a=1.0;
gl_FragColor=col;
}
]]
}
Surface={
v=[[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec2 texCoord;
attribute vec4 color;
varying highp vec2 vTexCoord;
varying lowp vec4 vColor;
void main()
{
vTexCoord=texCoord;
vColor=color;
gl_Position = modelViewProjection * position;
}
]],
f=[[
precision highp float;
uniform lowp sampler2D texture;
uniform float intensity;
varying highp vec2 vTexCoord;
varying lowp vec4 vColor;
void main()
{
lowp vec4 col = texture2D(texture, vec2(vColor.r+vColor.b*mod(vTexCoord.x,1.0),vColor.g+vColor.a*mod(vTexCoord.y,1.0)))*intensity;
col.a=1.0;
gl_FragColor=col;
}
]]
}
--# Sky
function SetupSky(img1)
img2=image(img1.width,img1.width/2)
pushStyle()
setContext(img2)
spriteMode(CORNER)
sprite(img1,0,img2.height-img1.height)
sprite(img1,0,img2.height-img1.height-img1.height)
setContext()
popStyle()
local color1 = color(255, 255, 255, 255)
--this sphere code comes from Jmv38, see bottom
planet1 = Sphere({
nx = 40, ny = 20 , -- mesh definition
meshOptimize = true, -- optimize mesh for sphere
c1 = color1 , c2 = color1 , -- mesh colors
cx=0, cy=0, cz=0 , -- sphere center
r = 5000 , -- radius of the sphere
rotTime1 = 20 , -- rotation time in s
hflip = true, -- to flip image horozontally
})
cam = vec3(0, 50, 300)
planet1.ms.texture=img2
return planet1
end
--the code below comes from Jmv38, who explains it here
--http://jmv38.comze.com/CODEAbis/server.php (look for 3D tutorial)
Sphere = class()
function Sphere:init(input)
-- spatial position of sphere
self.cx = input.cx or 0
self.cy = input.cy or 0
self.cz = input.cz or 0
-- angular position of sphere, defined by angles around x,y,z axis
self.ax = 0
self.ay = 0
self.az = 0
-- sphere radius and rotation
self.radius = input.r
self.tRot = input.rotTime1
-- sphere rotation 2
self.tRot2 = input.rotTime2
self.cx2 = input.cx2 or 0 -- center of rotation 2
self.cy2 = input.cy2 or 0
self.cz2 = input.cz2 or 0
self.ax2 = input.ax2 or 0 -- axis of rotation 2
self.ay2 = input.ay2 or 1
self.az2 = input.az2 or 0
-- mesh definition
self.nx = input.nx -- number of triangles in x
self.ny = input.ny -- and in y
self.c1 = input.c1 -- 2 color() objects, to see the triangles
self.c2 = input.c2
self.optimized = input.meshOptimize -- boolean
-- sphere decoration
self.url = input.url -- texture as a url (text)
self.hflip = input.hflip -- to flip image horizontally
if input.lightDir then
self.lightDir = input.lightDir:normalize() -- a vec3 pointing to the sun
end
self.shadowRatio = input.shadowRatio or 1.05 -- close to 1.05
-- create mesh and colors
local vertices,colors,tc = {},{},{}
if self.optimized then
vertices,colors,tc = self:optimMesh({ nx=self.nx, ny=self.ny, c1=self.c1, c2=self.c2 })
else
vertices,colors,tc = self:simpleMesh({ nx=self.nx, ny=self.ny, c1=self.c1, c2=self.c2 })
end
-- if a radius is given, warp to a sphere
if self.radius then
vertices = self:warpVertices({
verts=vertices,
xangle=180,
yangle=180
}) end
-- create the mesh itself
self.ms = mesh()
self.ms.vertices = vertices
self.ms.colors = colors
-- add the texture from internet
--if self.url then
-- self:load( self.url ) -- this will not be instantaneous!
--end
self.ms.texture = self.url
self.ms.texCoords = tc
-- add some shadows
if self.lightDir then self:shadows() end
end
function Sphere:shadows()
self.ms2 = mesh()
local dir = self.lightDir
local vertices2,colors2 = {},{}
local d = 0
for i,v in ipairs(self.ms.vertices) do
vertices2[i] = v
d = v:dot(dir)
d = 128 - 4*(d-0.1)*128
if d<0 then d=0 end
if d>255 then d=255 end
colors2[i] = color(0,0,0,d)
end
self.ms2.vertices = vertices2
self.ms2.colors = colors2
end
function Sphere:simpleMesh(input)
-- create the mesh tables
local vertices = {}
local colors = {}
local texCoords = {}
--local w,h = img.width/10, img.height/10
local k = 0
local s = 1
-- create a rectangular set of triangles
local x,y
local nx,ny = input.nx,input.ny
local opt = input.opt
local sx, sy = 1/ny, 1/ny
local color1 = input.c1
local color2 = input.c2
local center = vec3(1,0.5,0)
for y=0,ny-1 do
for x=0,nx-1 do
vertices[k+1] = vec3( sx*x , sy*y , 1) - center
vertices[k+2] = vec3( sx*(x+1), sy*y , 1) - center
vertices[k+3] = vec3( sx*(x+1), sy*(y+1), 1) - center
vertices[k+4] = vec3( sx*x , sy*y , 1) - center
vertices[k+5] = vec3( sx*x , sy*(y+1), 1) - center
vertices[k+6] = vec3( sx*(x+1), sy*(y+1), 1) - center
colors[k+1] = color1
colors[k+2] = color1
colors[k+3] = color1
colors[k+4] = color2
colors[k+5] = color2
colors[k+6] = color2
k = k + 6
end
end
return vertices,colors
end
function Sphere:optimMesh(input)
-- create the mesh tables
local vertices = {}
local colors = {}
local texCoords = {}
--local w,h = img.width/10, img.height/10
local k = 0
local s = 1
-- create a set of triangles with approx constant surface on a sphere
local x,y
local x1,x2 = {},{}
local i1,i2 = 0,0
local nx,ny = input.nx,input.ny
local sx, sy = nx/ny, 1/ny
local color1 = input.c1
local color2 = input.c2
local center = vec3(1,0.5,0)
local m1,m2,c
local flip = 1
if self.hflip then flip=-1 end
for y=0,ny-1 do -- for each horizontal band
-- number of points on each side of the band
local nx1 = math.floor( nx * math.abs(math.cos( ( y*sy-0.5)*2 * math.pi/2)) )
if nx1<6 then nx1=6 end
local nx2 = math.floor( nx * math.abs(math.cos( ((y+1)*sy-0.5)*2 * math.pi/2)) )
if nx2<6 then nx2=6 end
-- points on each side of the band
x1,x2 = {},{}
for i1 = 1,nx1 do x1[i1] = (i1-1)/(nx1-1)*sx end
for i2 = 1,nx2 do x2[i2] = (i2-1)/(nx2-1)*sx end
x1[nx1+1] = x1[nx1] -- just a trick to manage last triangle without thinking
x2[nx2+1] = x2[nx2]
-- start on the left
local i1,i2 = 1,1
c = 1 -- starting color
local continue = true
local n,nMax = 0,0
nMax = nx*2+1
while continue do
-- center of the 2 current segments
m1 = (x1[i1]+x1[i1+1])/2
m2 = (x2[i2]+x2[i2+1])/2
if m1<=m2 then -- the less advanced base makes the triangle
vertices[k+1] = vec3( x1[i1], sy*y , 1) - center
vertices[k+2] = vec3( x1[i1+1], sy*y , 1) - center
vertices[k+3] = vec3( x2[i2], sy*(y+1), 1) - center
texCoords[k+1] = vec2( x1[i1]/2*flip, sy*y )
texCoords[k+2] = vec2( x1[i1+1]/2*flip, sy*y )
texCoords[k+3] = vec2( x2[i2]/2*flip, sy*(y+1))
if i1<nx1 then i1 = i1 +1 end
else
vertices[k+1] = vec3( x1[i1], sy*y , 1) - center
vertices[k+2] = vec3( x2[i2], sy*(y+1), 1) - center
vertices[k+3] = vec3( x2[i2+1], sy*(y+1), 1) - center
texCoords[k+1] = vec2( x1[i1]/2*flip, sy*y )
texCoords[k+2] = vec2( x2[i2]/2*flip, sy*(y+1))
texCoords[k+3] = vec2( x2[i2+1]/2*flip, sy*(y+1))
if i2<nx2 then i2 = i2 +1 end
end
-- set the triangle color
if c==1 then col=color1 else col=color2 end
colors[k+1] = col
colors[k+2] = col
colors[k+3] = col
if c==1 then c=2 else c=1 end
if i1==nx1 and i2==nx2 then continue=false end
-- increment index for next triangle
k = k + 3
n = n + 1
if n>nMax then continue=false end -- just in case of infinite loop
end
end
return vertices,colors,texCoords
end
function Sphere:warpVertices(input)
-- move each vector to its position on sphere
local verts = input.verts
local xangle = input.xangle
local yangle = input.yangle
local s = self.radius
local m = matrix(0,0,0,0, 0,0,0,0, 1,0,0,0, 0,0,0,0) -- empty matrix
local vx,vy,vz,vm
for i,v in ipairs(verts) do
vx,vy = v[1], v[2]
vm = m:rotate(xangle*vy,1,0,0):rotate(yangle*vx,0,1,0)
vx,vy,vz = vm[1],vm[5],vm[9]
verts[i] = vec3(vx,vy,vz)
end
return verts
end
function Sphere:draw(cx)
pushMatrix()
--translate(self.cx,self.cy,self.cz)
if self.radius then s = self.radius else s = 100 end
scale(s,s,s)
self.ms:draw()
popMatrix()
end
function SetupCloud()
cloud=mesh()
local x,y=300,200
cloud:addRect(0,0,x,y)
cc=1
counter=50
end
function DrawCloud()
if counter+cc>220 or counter+cc<50 or math.random()<0.01 then cc=-cc end
counter=counter+cc
--if counter<50 then return end
cloud:setColors(color(255,255,255,counter))
--pushMatrix() translate(0,0,-40) cloud:draw() popMatrix()
pushMatrix() translate(0,0,10) cloud:draw() popMatrix()
end
--# Sockets
--Sockets
function ConnectSocket()
local connectionMade = function()
output.clear()
parameter.clear()
local e=player(playerBody)
print("Connected!")
displayMode(FULLSCREEN)
status=states.MULTI_PLAYER
end
multihandler = Multiplayer(receiveData, connectionMade)
parameter.action("Host Game", function()
multihandler:hostGame()
end)
parameter.action("Search for and Join Game", function()
multihandler:findGame()
end)
parameter.action("Join Game", function()
if other_ip then
multihandler:joinGame(other_ip, other_port)
else
parameter.text("other_ip", "")
parameter.text("other_port", "")
print("Fill in the host's ip and port, then click join game again")
end
end)
parameter.action("Single player",function()
parameter.clear()
SinglePlayer=true
displayMode(FULLSCREEN)
status=states.SINGLE_PLAYER
end)
end
function receiveData(d)
if d then
enemyPos,enemyRot = loadstring("return " .. d)()
end
end
function SendSocketData()
if not multihandler then return end
if multihandler.connected then
multihandler:sendData(VecToText(cam-vec3(0,camY,0))..","..string.format("%0.2f",lookAngle))
end
end
function VecToText(v,d)
local s="%0."..(d or 1).."f" ss=s..","
return string.format("vec3("..ss..ss..s..")",v:unpack())
end
--# Multiplayer
--Multiplayer
local socket = require("socket")
Multiplayer = class()
Multiplayer.interval=0.1
function Multiplayer:init(dcb, ccb)
self.my_ip, self.my_port = self:getLocalIP(), 5400
self.peer_ip, self.peer_port = nil, self.my_port
self.client = socket.udp()
self.client:settimeout(0)
self.connected = false
self.is_host = false
self.searching = false
self.lastUpdate=0
self.dataCallback = dcb or function() end
self.connectedCallback = ccb or function() end
end
-- Returns this iPad's local ip
function Multiplayer:getLocalIP()
local randomIP = "192.167.188.122"
local randomPort = "3102"
local randomSocket = socket.udp()
randomSocket:setpeername(randomIP,randomPort)
local localIP, somePort = randomSocket:getsockname()
randomSocket:close()
randomSocket = nil
return localIP
end
-- Set the connected status and call the connection callback if needed
function Multiplayer:setConnectedVal(bool)
self.connected = bool
if self.connected then
self.connectedCallback()
end
end
function Multiplayer:setHostVal(bool)
self.is_host = bool
end
-- Prepare to be the host
function Multiplayer:hostGame()
print("Connect to " .. self.my_ip .. ":" .. self.my_port)
self.client:setsockname(self.my_ip, self.my_port)
self:setConnectedVal(false)
self.is_host = true
self.searching = false
end
-- Find a host
function Multiplayer:findGame()
print("Searching for games...")
self.searching = true
local ip_start, ip_end = self.my_ip:match("(%d+.%d+.%d+.)(%d+)")
for i = 1, 255 do
if i ~= tonumber(ip_end) then
tween.delay(0.01 * i, function()
self.client:setsockname(ip_start .. i, self.my_port)
self.client:sendto("connection_confirmation", ip_start .. i, self.my_port)
end)
end
end
end
-- Prepare to join a host
function Multiplayer:joinGame(ip, port)
self.peer_ip, self.peer_port = ip, port
self.client:setsockname(ip, port)
self.is_host = false
self.searching = false
self:sendData("connection_confirmation")
end
-- Send data to the other client
function Multiplayer:sendData(msg_to_send)
if self.peer_ip and ElapsedTime>self.lastUpdate then
self.client:sendto(msg_to_send, self.peer_ip, self.peer_port)
self.lastUpdate=ElapsedTime+Multiplayer.interval
end
end
-- Check for data received from the other client
function Multiplayer:checkForReceivedData()
local data, msg_or_ip, port_or_nil = self.client:receivefrom()
if data then
-- Store the ip of this new client so you can send data back
self.peer_ip, self.peer_port = msg_or_ip, port_or_nil
if not self.connected and data == "connection_confirmation" then
self:sendData("connection_confirmation")
self:setConnectedVal(true)
end
-- Call callback with received data
if data ~= "connection_confirmation" then
self.dataCallback(data)
end
end
end
function Multiplayer:update()
self:checkForReceivedData()
end
--# Controls
--Controls
--contains Joystick controls and Tilting controls
--1. Tilting the iPad
--This is as easy as just returning the Gravity values, which range from -1 to +1 for both x and y
--You may want to make all three directions incremental [self.mode in Flight tab = (1,1,1)] so you
--can control how much the plane reacts to tilting, and so you can do 360 degree rolls without
--dislocating your shoulders
function Tilt()
return vec2(Gravity.x,Gravity.y)
end
--2. Joystick
JoyStick = class()
--Note all the options you can set below. Pass them through in a named table
function JoyStick:init(t)
t = t or {}
self.radius = t.radius or 100 --size of joystick on screen
self.stick = t.stick or 30 --size of inner circle
self.centre = t.centre or self.radius * vec2(1,1) + vec2(5,5)
self.position = vec2(0,0) --initial position of inner circle
self.target = vec2(0,0) --current position of inner circle (used when we interpolate movement)
self.value = vec2(0,0)
self.delta = vec2(0,0)
self.mspeed = 120
self.moving = 0
end
function JoyStick:draw()
ortho()
viewMatrix(matrix())
pushStyle()
fill(160, 182, 191, 50)
stroke(118, 154, 195, 100)
strokeWidth(1)
ellipse(self.centre.x,self.centre.y,2*self.radius)
fill(78, 131, 153, 50)
ellipse(self.centre.x+self.position.x, self.centre.y+self.position.y, self.stick*2)
popStyle()
end
function JoyStick:touched(t)
if t.state == BEGAN then
local v = vec2(t.x,t.y)
if v:dist(self.centre)<self.radius-self.stick then
self.touch = t.id
return true
end
end
if t.id == self.touch then
if t.state~=ENDED then
local v = vec2(t.x,t.y)
if v:dist(self.centre)>self.radius-self.stick then
v = (v - self.centre):normalize()*(self.radius - self.stick) + self.centre
end --set x,y values for joy based on touch
self.target=v - self.centre
else --reset joystick to centre when touch ends
self.target=vec2(0,0)
self.touch = false
end
return true
end
end
function JoyStick:update()
local p = self.target - self.position
if p:len() < DeltaTime * self.mspeed then
self.position = self.target
if not self.touch then
if self.moving ~= 0 then
self.moving = self.moving - 1
end
else
self.moving = 2
end
else
self.position = self.position + p:normalize() * DeltaTime * self.mspeed
self.moving = 2
end
return self.position/(self.radius - self.stick)
end
function JoyStick:isMoving()
return self.moving
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment