# Cocea编写的一款3D地牢游戏例程
简单介绍:就是一款用 Codea 编写的第一人称视角的 3D 迷宫游戏,你可以用你的 iPad 在这个基础上学习 3D 编程,边修改边查看效果。
这个游戏说明用 Codea 可以编出各种类型的游戏来,唯一的限制是我们的想象力!
作者:@Ignatz
原始文档链接:
http://codea.io/talk/discussion/5746/huge-3d-dungeon-updates
Git代码链接:
https://gist.github.com/dermotbalson/ae547d75672ddfbaa06e
代码中使用到的贴图资源百度网盘地址:
http://pan.baidu.com/s/1dDcSU9V
其中也包括作者关于 Codea 编程的一些电子书(简单英文):
截图:
代码:
--# Main --Dungeon ver 1.0 --navigation --touch left 1/3 of screen to turn left, right 1/3 of screen to turn right --touch top 1/3 of screen to move forward, bottom 1/3 of screen to go backwards --touch centre of screen to stop --touching multiple times speeds up --BUGS --the navigation could probably be improved --it is possible to walk through some walls (maybe my map showing which tiles are occupied, is faulty) --also I think I have a hole in a wall somewhere displayMode(FULLSCREEN) supportedOrientations(LANDSCAPE_ANY) function setup() FPS=60 M=ReadMap() --see Map tab --set up scene --pass texture, texture scale (0.05 means reduce image by 95%), tile width, and wall height S=Scene("Dropbox:3D-walls",0.05,M.width,M.height) mapp={} --table which will hold the position of all the walls for collision avoidance for i=1,M.size.x do mapp[i]={} end --initialise --add walls to mesh --add rooms for i=1,#M.rooms do for j,w in pairs(M.rooms[i].walls) do S:AddSideWall(w,mapp) end end --add corridors for i=1,#M.corr do for j,w in pairs(M.corr[i].walls) do S:AddSideWall(w,mapp) end end --images --these are added differently, see Billboard class images={} for u,i in pairs(M.images) do images[#images+1]=Billboard(i,M.width,M.height,mapp) end --create separate "scene" for floor/ceiling because it has different texture F=Scene("Dropbox:3D-gravel1s",0.05,M.width,M.height) F:AddFloorCeiling(M.floor) --create map we can show on screen MapScale=6 --scale MapTrans=75 --transparency MapImg=image(M.size.x*MapScale,M.size.y*MapScale) setContext(MapImg) rectMode(CENTER) pushStyle() fill(255,255,255,MapTrans) for i=1,M.size.x do for j=1,M.size.y do if mapp[i][j]==1 then rect(i*MapScale,j*MapScale,MapScale,MapScale) end end end popStyle() setContext() --starting position --deduct 0.5 from x and z so we start in the middle of the specified tile --concert from tile units to pixels --z is made negative, if you don‘t know why, you should go read up on 3D to avoid great confusion pos=vec3((M.startPos.x-0.5)*M.width,M.startPos.y*M.height,-(M.startPos.z-0.5)*M.width) --player settings angle=0 --player orientation turnAngle=3 --how much player turns when screen is touched --for smoother turning, when you tap to turn, targetAngle changes, and angle will move towards it.. targetAngle=0 -- --..the speed with which angle changes angleChange=0.5 --"tall" is the height of the camera (sorry about variable names) tall=M.startPos.y --get the light range range=M.range --set the flicker as % of total light radius flicker=0.3 --this is added to current position to set the camera direction look=vec3(0,tall,-1000) lookDist=1000 --this seems to duplicate the look var, need to investigate --velocity vel=vec3(0,0,0) --current speed speed=0 --height of the light we are holding lightHeight=vec3(0,M.height*0.25,0) --set position of map button mapButtonRadius=25 mapButton=vec2(WIDTH-mapButtonRadius-5,mapButtonRadius+5) mapShowing=false --create map button mapButtonImg=image(mapButtonRadius*2,mapButtonRadius*2) setContext(mapButtonImg) pushStyle() fill(255,255,0,100) ellipse(mapButtonImg.width/2,mapButtonImg.height/2,mapButtonRadius*2) popStyle() setContext() fill(255) fontSize(12) end function draw() background(150) FPS=0.95*FPS+0.05/DeltaTime --FPS is geometric average perspective() --move towards target angle if we are turning if targetAngle~=angle then if targetAngle<angle then angle=angle-angleChange else angle=angle+angleChange end ChangeVel() end --camera pos camera(pos.x,pos.y,pos.z,pos.x+look.x,look.y,pos.z+look.z) --if we aren‘t walking into a wall, we can move if CanMove(pos+vel,mapp) then pos=pos+vel else speed=0 end --set flicker radius using noise local u=range*(1+flicker*noise(ElapsedTime)) --draw all the walls S:draw(pos+lightHeight,u) --pass light position and flicker level --draw the floor and roof F:draw(pos+lightHeight,u) --draw billboard images (they are rotated first) for a,i in pairs(images) do i:draw(pos,u) end --draw FPS on screen ortho() viewMatrix(matrix()) text("FPS="..math.floor(FPS),50,50) --draw map button sprite(mapButtonImg,mapButton.x,mapButton.y) DrawMap(pos) end --check if the square the player is in contains a wall --there is a bug either in this or in my mapp table because I can --sometimes walk through walls :( function CanMove(v,m) local a=PlayerTile(v) if m[a.x][a.y]==1 then return false else return true end end function PlayerTile(v) return vec2(math.floor(v.x/M.width+1),math.floor(-v.z/M.width+1)) end --handles player movement function touched(t) --check for map touch first if t.state==BEGAN then if vec2(t.x,t.y):dist(mapButton)<mapButtonRadius then mapShowing=not mapShowing elseif t.tapCount==2 then if t.x<WIDTH/3 then targetAngle=targetAngle-90 elseif t.x>WIDTH*2/3 then targetAngle=targetAngle+90 end else if t.x<WIDTH/3 then targetAngle=targetAngle-turnAngle elseif t.x>WIDTH*2/3 then targetAngle=targetAngle+turnAngle elseif t.y<HEIGHT/3 then speed=speed-M.walk ChangeVel() elseif t.y>HEIGHT*2/3 then speed=speed+M.walk ChangeVel() elseif vec2(WIDTH/2,HEIGHT/2):dist(vec2(t.x,t.y))<150 then speed=0 targetAngle=angle ChangeVel() end end end end function DrawMap(p) if not mapShowing then return end local margin=8 local x,y=WIDTH-MapImg.width-margin,mapButtonRadius*2+margin sprite(MapImg,x+MapImg.width/2,y+MapImg.height/2) local a=PlayerTile(p) pushStyle() fill(255,255,0,MapTrans*2) ellipse(x+p.x/M.width*MapScale,y-p.z/M.width*MapScale,MapScale) popStyle() end function ChangeVel() local a=math.rad(angle) local s,c=math.sin(a),math.cos(a) vel.x,vel.z=speed*s,-speed*c look.x,look.z=lookDist*s,-lookDist*c end --handles billboarded images Billboard=class() --t is table of image,x,y,z in tile units, then ambient light fraction --w,h are map.width,map.height, m is mapp table listing all the things the player can‘t walk through --we will add the images to this table function Billboard:init(t,w,h,m) self.m=mesh() self.width,self.height=w,h m[t[2]][t[4]]=1 --mark this tie as occupied local tex --for image if type(t[1])=="string" then tex=readImage(t[1]) else tex=t[1] end --set position in centre of tile self.x,self.y,self.z=(t[2]-0.5)*self.width,t[3]*self.height,-(t[4]-0.5)*self.width self.angle=0 --for rotating to face the player self.count=0 --on;y rotate every few frames, use this counter --add image to mesh self.m:addRect(0,0,tex.width*self.y/tex.height,self.y) self.m.texture=tex --billboards have their own slightly modified shader to handle transparent pixels self.m.shader=shader(TransTileShader.vertexShader,TransTileShader.fragmentShader) self.m.shader.lightbase=t[5] --set ambient light end function Billboard:draw(p,r) --p is player position, r is current light range self.m.shader.pos=p self.m.shader.range=r --adjust rotation every 10 frames self.count=self.count+1 if self.count%10==0 then --rotate to face player local dx,dz=self.x-p.x,self.z-p.z self.angle=math.deg(math.atan(dx/-dz)) end pushMatrix() translate(self.x,self.y/2,self.z) rotate(-self.angle,0,1,0) self.m.shader.mModel = modelMatrix() self.m:draw() popMatrix() end --main class for handling map wals, floor, roof Scene=class() function Scene:init(tex,s,w,h) --tex is image, s is image scaling, w,h are tile size and wall height self.m=mesh() if type(tex)=="string" then tex=readImage(tex) end self.iw,self.ih=tex.width,tex.height self.width,self.height=w,h self.v,self.t={},{} self.s=s or 1 self.m.texture=tex self.m.shader=shader(TileShader.vertexShader,TileShader.fragmentShader) end --v1,v2,v3,v4 are positions of the four wall corners function Scene:AddWall(v1,v2,v3,v4) --figure out tex coords, ie which way the wall is pointing local d=v2-v1 local dx,dy if d.x~=0 then dx=d.x elseif d.y~=0 then dx=d.y else dx=d.z end d=v3-v2 if d.x~=0 then dy=d.x elseif d.y~=0 then dy=d.y else dy=d.z end --next bit is important for tiling, calculate texture upper limit as width of mesh / size of scaled image local tx1,tx2=0,math.abs(dx)/self.iw/self.s local ty1,ty2=0,math.abs(dy)/self.ih/self.s --vertices local n=#self.v self.v[n+1],self.v[n+2],self.v[n+3],self.v[n+4],self.v[n+5],self.v[n+6]=v1,v2,v3,v3,v4,v1 --add to existing mesh vertices which are stored in a "buffer" local b=self.m:buffer("position") b:resize(n+6) for i=1,#self.v do b[i]=self.v[i] end --now the texture mappings local n=#self.t self.t[n+1],self.t[n+2],self.t[n+3],self.t[n+4],self.t[n+5],self.t[n+6]= vec2(tx1,ty1),vec2(tx2,ty1),vec2(tx2,ty2),vec2(tx2,ty2),vec2(tx1,ty2),vec2(tx1,ty1) --add to texture buffer local b=self.m:buffer("texCoord") b:resize(n+6) for i=1,#self.t do b[i]=self.t[i] end self.m:setColors(color(255)) end --calculates corner vecs for a wall, given a vec4 containing (x1,z1,x2,z2) function Scene:AddSideWall(v,m) --m is mapp table listing tiles containing walls local x1,y1,z1=(v.x-0.5)*self.width,0,-(v.y-0.5)*self.width local x2,y2,z2=(v.z-0.5)*self.width,self.height,-(v.w-0.5)*self.width self:AddWall(vec3(x1,y1,z1),vec3(x2,y1,z2),vec3(x2,y2,z2),vec3(x1,y2,z1)) --update mapp table to show these tiles are occupied if m then for i=math.min(v.x,v.z),math.max(v.x,v.z) do m[i][v.y]=1 end for i=math.min(v.y,v.w),math.max(v.y,v.w) do m[v.x][i]=1 end end end --simpified version of AddSideWall, only this wall is horizontal function Scene:AddFloorCeiling(v) local x1,z1=(v.x-0.5)*self.width,-(v.y-0.5)*self.width local x2,z2=(v.z-0.5)*self.width,-(v.w-0.5)*self.width self:AddWall(vec3(x1,0,z1),vec3(x2,0,z1),vec3(x2,0,z2),vec3(x1,0,z2)) self:AddWall(vec3(x1,self.height,z1),vec3(x2,self.height,z1),vec3(x2,self.height,z2),vec3(x1,self.height,z2)) end --not used function Scene:AddLight(v) self.m.shader.light=vec3(v.x*self.width,v.y*self.height,v.z*self.width) end function Scene:draw(p,r) self.m.shader.pos=p self.m.shader.range=r self.m.shader.mModel = modelMatrix() self.m:draw() end --this shader used by the walls, floor, roof TileShader = { vertexShader = [[ uniform mat4 modelViewProjection; uniform mat4 mModel; attribute vec4 position; attribute vec4 color; attribute vec2 texCoord; varying lowp vec4 vColor; varying highp vec2 vTexCoord; varying highp vec4 vPosition; void main() { vColor = color; vTexCoord = texCoord; gl_Position = modelViewProjection * position; vPosition = mModel * position; //needed to set light intensity } ]], fragmentShader = [[ precision highp float; uniform lowp sampler2D texture; uniform lowp float range; //light range uniform lowp vec3 pos; //position of player varying lowp vec4 vColor; varying highp vec2 vTexCoord; varying highp vec4 vPosition; //position of current pixel void main() { float f=max(0.,1.0-length( pos - vPosition.xyz ) / range); //light reduces linearly with distance //next is the magic line of code that tiles the image across areas of any size //it is multiplied by the light intensity calculated above lowp vec4 col = f*texture2D( texture, vec2(mod(vTexCoord.x,1.0), mod(vTexCoord.y,1.0))); col.a=1.0; gl_FragColor =col; } ]] } --this shader is used by the images --the only difference is that transparent pixes are discarded TransTileShader = { vertexShader = [[ uniform mat4 modelViewProjection; uniform mat4 mModel; attribute vec4 position; attribute vec4 color; attribute vec2 texCoord; varying lowp vec4 vColor; varying highp vec2 vTexCoord; varying highp vec4 vPosition; void main() { vColor = color; vTexCoord = texCoord; gl_Position = modelViewProjection * position; vPosition = mModel * position; } ]], fragmentShader = [[ precision highp float; uniform lowp sampler2D texture; uniform lowp float range; uniform lowp float lightbase; uniform lowp vec3 pos; varying lowp vec4 vColor; varying highp vec2 vTexCoord; varying highp vec4 vPosition; void main() { lowp vec4 col = texture2D( texture, vTexCoord ); if (col.a==0.0) discard; //this is the only difference else { //lightbase below is the ambient light of this object, gives it a glow if you want one float f=max(0.,1.0-length( pos - vPosition.xyz ) / range)+lightbase; col = col*f; col.a=1.0; gl_FragColor=col; } } ]] } --# Map --Dungeon map --this map is organised as a table --the map is a rectangular grid of squares --image here: http://i1303.photobucket.com/albums/ag142/ignatz_mouse/dungeon_zpsc89352fc.png --Thinking I would have to cull (ie ony draw stuff directly around the player), I organised the map --to have corridors between all the rooms. This made it easier to say "if you are in room X, also draw --corridors "Y and Z". It proved unnecessary - so far. --anyway, that is why I have organised the walls by room, and separatey for rooms and corridors --LOCATION OF IMAGES REQUIRED --https://www.dropbox.com/sh/i7stxdfcnnh8azx/AAByAiTG7oswE7nczwZj4pxUa?dl=0 function ReadMap() local map={} map.width=10 --number of pixels per square (not a good variable name!) map.height=20 --height of walls in pixels map.size=vec2(82,114) --number of tiles wide and deep map.startPos=vec3(72,0.5,8) --starting tile map.walk=0.2 --speed change (pixels/sec) when you touch screen map.range=100 --range of light, in pixels map.rooms,map.corr={},{} --rooms and corridors stored separately --load the map coordinates local m,c=map.rooms,map.corr for i=1,23 do m[i]={} end for i=1,30 do c[i]={} end --the rooms and corr tables have provision for wall and neighbour settings --I‘m only using wall settings, as explained above --room 1 --each wall is stored in a vec4 = (x1,z1,x2,z2) in tile units --no need for a y value, we know each wall goes from 0 to map.height m[1].walls={vec4(64,11,75,11),vec4(75,11,75,4),vec4(61,4,75,4),vec4(61,10,61,4)} --m[1].neighbours={m.corr1} --this was where I was going to specify neighbours m[2].walls={vec4(41,10,56,10),vec4(56,13,56,10), vec4(40,19,40,13),vec4(40,19,53,19),vec4(56,19,56,16)} m[3].walls={vec4(21,16,21,1),vec4(21,1,30,1),vec4(30,10,30,1),vec4(30,20,30,13), vec4(21,20,30,20),vec4(21,20,21,19)} m[4].walls={vec4(32,21,34,21),vec4(32,27,32,21),vec4(32,36,32,30), vec4(38,21,40,21),vec4(40,24,40,21),vec4(40,36,40,27),vec4(35,36,40,36)} m[5].walls={vec4(13,33,13,21),vec4(16,21,22,21),vec4(22,27,22,21), vec4(22,39,22,30),vec4(13,39,22,39),vec4(13,39,13,36)} m[6].walls={vec4(8,28,11,28),vec4(11,28,11,21),vec4(5,21,11,21),vec4(5,28,5,21),vec4(5,28,6,28)} m[7].walls={vec4(2,39,2,30),vec4(2,30,6,30),vec4(8,30,11,30), vec4(11,33,11,30),vec4(11,39,11,36),vec4(2,39,11,39)} m[8].walls={vec4(50,39,50,30),vec4(50,30,59,30),vec4(59,45,59,30), vec4(50,45,59,45),vec4(50,45,50,42)} m[9].walls={vec4(23,48,23,47),vec4(23,48,29,48),vec4(32,48,37,48), vec4(37,48,37,41),vec4(35,41,37,41),vec4(23,41,32,41),vec4(23,44,23,41)} m[10].walls={vec4(10,56,16,56),vec4(10,56,10,41),vec4(10,41,19,41),vec4(19,44,19,41),vec4(19,56,19,47)} m[11].walls={vec4(34,68,34,62),vec4(34,68,39,68),vec4(34,59,34,52), vec4(34,52,48,52),vec4(48,59,48,52),vec4(48,68,48,62),vec4(43,68,48,68)} m[12].walls={vec4(61,56,61,50),vec4(61,56,66,56),vec4(69,56,72,56), vec4(72,56,72,41),vec4(67,41,72,41),vec4(61,41,64,41),vec4(61,47,61,41)} m[13].walls={vec4(13,85,19,85),vec4(13,85,13,76),vec4(13,76,16,76), vec4(19,76,27,76),vec4(27,85,27,76),vec4(21,85,27,85)} m[14].walls={vec4(29,91,29,81),vec4(29,81,32,81),vec4(35,81,39,81), vec4(43,81,45,81),vec4(45,87,45,81),vec4(37,91,45,91),vec4(29,91,34,91)} m[15].walls={vec4(50,93,50,91),vec4(50,93,58,93),vec4(50,87,50,84),vec4(50,84,55,84),vec4(59,90,59,84)} m[16].walls={vec4(50,76,50,64),vec4(50,64,61,64),vec4(61,70,61,64), vec4(61,79,61,73),vec4(59,79,61,79),vec4(51,79,55,79)} m[17].walls={vec4(74,99,74,93),vec4(74,99,82,99),vec4(82,99,82,84),vec4(74,84,82,84),vec4(74,90,74,84)} m[18].walls={vec4(50,105,50,99),vec4(50,105,59,105),vec4(59,105,59,96),vec4(51,96,59,96)} m[19].walls={vec4(28,114,28,107),vec4(28,107,34,107),vec4(28,114,34,114), vec4(34,114,34,110),vec4(34,108,34,107)} m[20].walls={vec4(36,111,36,110),vec4(36,111,48,111),vec4(48,111,48,101), vec4(45,101,48,101),vec4(36,101,42,101),vec4(36,108,36,101)} m[21].walls={vec4(63,114,63,104),vec4(63,114,77,114),vec4(77,114,77,104), vec4(69,104,77,104),vec4(63,104,66,104)} m[22].walls={vec4(23,69,23,65),vec4(23,69,27,69),vec4(29,69,30,69), vec4(30,69,30,65),vec4(26,65,30,65),vec4(23,65,24,65)} m[23].walls={vec4(21,63,24,63),vec4(26,63,27,63),vec4(27,63,27,58),vec4(21,58,27,58),vec4(21,60,21,58)} --corridors now c[1].walls={vec4(56,13,61, 13),vec4(61,13,61, 10),vec4(56,16,64, 16),vec4(64,16,64, 11)} c[2].walls={vec4(38,13,40, 13),vec4(38,21,38, 13),vec4(34,21,34, 13), vec4(30,13,34, 13),vec4(30,10,41, 10)} c[3].walls={vec4(16,19,21,19),vec4(16,21,16,19),vec4(13,16,21,16),vec4(13,21,13,16)} c[4].walls={vec4(53,24,53,19),vec4(56,24,56,19),vec4(40,24,53,24), vec4(56,24,67,24),vec4(40,27,64,27),vec4(64,41,64,27),vec4(67,41,67,24)} c[5].walls={vec4(8,30,8,28),vec4(6,30,6,28)} c[6].walls={vec4(22,27,32,27),vec4(22,30,32,30)} c[7].walls={vec4(32,41,32,36),vec4(35,41,35,36)} c[8].walls={vec4(19,47,23,47),vec4(19,44,23,44)} c[9].walls={vec4(29,62,29,48),vec4(32,59,32,48),vec4(29,62,34,62),vec4(32,59,34,59)} c[10].walls={vec4(48,59,53,59),vec4(48,62,56,62),vec4(56,62,56,50), vec4(53,59,53,50),vec4(56,50,61,50),vec4(45,50,53,50),vec4(45,50,45,39), vec4(45,39,50,39),vec4(48,42,50,42),vec4(48,47,48,42),vec4(48,47,61,47)} c[11].walls={vec4(66,70,66,56),vec4(69,77,69,56),vec4(66,77,66,73),vec4(61,73,66,73),vec4(61,70,66,70)} c[12].walls={vec4(66,90,66,78),vec4(69,90,69,79)} c[13].walls={vec4(69,93,74,93),vec4(69,90,74,90)} c[14].walls={vec4(58,93,66,93),vec4(59,90,66,90)} c[15].walls={vec4(45,91,50,91),vec4(45,87,50,87)} c[16].walls={vec4(19,88,19,85),vec4(21,88,21,85)} c[17].walls={vec4(43,79,51,79),vec4(43,76,50,76)} c[18].walls={vec4(16,76,16,56),vec4(19,60,19,56),vec4(19,70,19,63),vec4(19,76,19,73)} c[19].walls={vec4(19,63,21,63),vec4(19,60,21,60)} c[20].walls={vec4(19,73,32,73),vec4(19,70,27,70),vec4(29,70,35,70),vec4(35,81,35,70),vec4(32,81,32,73)} c[21].walls={vec4(39,81,39,68),vec4(43,81,43,79),vec4(43,76,43,68)} c[22].walls={vec4(34,99,34,91),vec4(34,99,42,99),vec4(37,96,50,96),vec4(45,99,50,99),vec4(37,96,42,96)} c[23].walls={vec4(42,101,42,99),vec4(45,101,45,99)} c[24].walls={vec4(34,110,36,110),vec4(34,108,36,108)} c[25].walls={vec4(66,104,66,93),vec4(69,104,69,93)} c[26].walls={vec4(55,84,55,79),vec4(59,84,59,79)} c[27].walls={vec4(69,79,72,79),vec4(69,77,72,77)} c[28].walls={vec4(24,65,24,63),vec4(26,65,26,63)} c[29].walls={vec4(11,36,13,36),vec4(11,33,13,33)} c[30].walls={vec4(27,70,27,69),vec4(29,70,29,69)} --floor coords, wil be used for roof too map.floor=vec4(2,1,82,114) --set of images to be billboarded --table contains image, x, y, z, s --where x,z are tile positions, y is fraction of map.height --and s is ambient lighting, 0 for none, 1 for bright, this property makes them glow in the dark map.images={ {readImage("Dropbox:Gargoyle"),53,0.5,16,0.2}, {readImage("Dropbox:Gargoyle"),53,0.5,14,0.2}, {readImage("Dropbox:Buddha1"),34,0.5,23,0.2}, {readImage("Dropbox:Buddha2"),36,0.5,11,0.4} } return map end
时间: 2024-10-09 22:06:27