Created
June 11, 2015 05:48
-
-
Save dermotbalson/ae776c7b74cb3bf2e70e to your computer and use it in GitHub Desktop.
Multi player FPS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--# 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