Created
October 19, 2014 15:46
-
-
Save dermotbalson/0111292e68f6168badd6 to your computer and use it in GitHub Desktop.
Dungeon 1.05 alpha
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
--# Main | |
--Dungeon ver 1.05 alpha | |
--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 | |
--double tap left or right to turn 90 degrees | |
--touch centre of screen to stop | |
--touching multiple times speeds up | |
--BUGS | |
--the navigation could probably be improved | |
displayMode(FULLSCREEN) | |
supportedOrientations(LANDSCAPE_ANY) | |
mapp={} | |
function setup() | |
Fx.Enabled(false) | |
FPS=60 | |
Test="" | |
SetupMap() | |
AI.init(M.width,M.height,mapp) | |
player=Player(M.startPos,M.width,mapp,M.walkAccel,M.maxSpeed) | |
SetupScene() | |
SetupModels(mapp) | |
joyMove=JoyStick{radius=100,centre=vec2(105,110)} | |
joyAim=JoyStick{radius=100,centre=vec2(WIDTH-105,110)} | |
--ShowInitialMessages() | |
--ShowHelp() | |
fill(255) | |
fontSize(12) | |
end | |
function SetupScene() | |
--"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 | |
--height of the light we are holding | |
lightHeight=vec3(0,M.height*0.25,0) | |
defaultAim=vec2(WIDTH/2,HEIGHT/2) | |
aimPoint=defaultAim | |
--moving,aiming={},{} | |
Fx.Play("hell") | |
end | |
function ShowInitialMessages() | |
Messages.Add{text="Wha...?",5} | |
Messages.Add{text="What am I doing here?",5} | |
Messages.Add{text="Where am I?",5} | |
Messages.Add{text="Wait - this feels like a light",5} | |
blackout=ElapsedTime+20 | |
Messages.Add{text="",5} --pause messages for 5 sec | |
end | |
function ShowHelp() | |
Messages.Add{text="Touch left or right of screen to turn\na tap turns 5 degrees\na little swipe turns 45 degrees\na bigger swipe turns 90 degrees", | |
length=10,size=32,colour=color(255,255,0),position=vec2(WIDTH/2,HEIGHT/3)} | |
Messages.Add{text="Tap the top of the screen to walk forward\n(and the bottom to go backwards)\nTap the middle to stop turning or moving", | |
length=10,size=32,colour=color(255,255,0),position=vec2(WIDTH/2,HEIGHT/3)} | |
Messages.Add{text="Tap the yellow dot (lower right)\nto turn the map on and off", | |
length=5,size=32,colour=color(255,255,0),position=vec2(WIDTH/2,HEIGHT/3)} | |
end | |
function SetupMap() | |
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=150 --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() | |
--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() | |
end | |
function draw() | |
background(150) | |
FPS=0.9*FPS+0.1/DeltaTime --FPS is geometric average | |
if moving then player:Moving(moving) end | |
if aiming then ChangeAim(aiming) end | |
if shooting then | |
player:Shoot() | |
shooting=false | |
end | |
player:draw() | |
--set flicker radius using noise | |
local flickerRange=range*(1+flicker*noise(ElapsedTime)) | |
--set up camera | |
SetupCamera() | |
--draw all the walls, roof and floor | |
S:draw(player.position+lightHeight,flickerRange) --pass light position and flicker level | |
F:draw(player.position+lightHeight,flickerRange) | |
--draw billboard images (they are rotated first) | |
for a,i in pairs(images) do | |
i:draw(player.position,flickerRange) | |
end | |
--draw 3D models | |
for i,d in pairs(AI.objects) do | |
if d.model.m and not d.model.m[1].shader then ConfigureModel(d.model) end | |
d:draw(player.position,flickerRange) | |
end | |
DrawMessages() | |
Fx.Update() | |
if player.weapon then sprite(M.crosshairs,aimPoint.x,aimPoint.y) end | |
joyMove:draw() | |
joyAim:draw() | |
end | |
function DrawMessages() | |
--draw FPS on screen | |
ortho() | |
viewMatrix(matrix()) | |
fill(255,255,0) | |
text("FPS="..math.floor(FPS),50,50) | |
if Test~="" then text("Test="..Test,300,25) end | |
--draw map button | |
sprite(mapButtonImg,mapButton.x,mapButton.y) | |
DrawMap(player.position) | |
--draw messages | |
if blackout then | |
if blackout>ElapsedTime then | |
pushStyle() | |
fill(0) | |
rect(WIDTH/2,HEIGHT/2,WIDTH+2,HEIGHT+2) | |
popStyle() | |
else blackout=nil return end | |
end | |
Messages.draw() | |
end | |
function SetupCamera() | |
--camera pos | |
perspective() | |
--local yy=player.position.y | |
--if speed>0 then yy=yy+math.sin(ElapsedTime/4)*3 end | |
local p=player.position | |
camera(p.x,p.y,p.z,p.x+look.x,look.y,p.z+look.z) | |
end | |
function ConfigureModel(d) | |
local m=d.m | |
for i=1,#m do --add lighting to each mesh | |
m[i]:setColors(m[i].settings.Kd) -- or color(255)) | |
m[i].shader=shader(TileShader.vertexShader,TileShader.fragmentShader) | |
if m[i].settings.map then | |
m[i].texture=OBJ.imgPrefix..m[i].settings.map | |
m[i].shader.hasTexture=true | |
else m[i].shader.hasTexture=false | |
end | |
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) | |
if t.state==ENDED then | |
player:Stop() | |
if t.id==movingID then moving=nil end | |
if t.id==aimingID then aiming=nil end | |
--moving,aiming=nil,nil | |
aimPoint=defaultAim | |
if vec2(t.x,t.y):dist(mapButton)<mapButtonRadius then | |
mapShowing=not mapShowing | |
print(FPS) | |
end | |
else | |
--check for joystick | |
local v=joyAim:touched(t) | |
if v then | |
aiming=v | |
aimingID=t.id | |
end | |
v=joyMove:touched(t) | |
if aiming and v then | |
shooting=true | |
moving=false | |
else | |
moving=v | |
movingID=t.id | |
end | |
end | |
end | |
function ChangeAim(v) | |
aimPoint=vec2(WIDTH*(v.x/2+0.5),HEIGHT*(v.y/2+0.5)) | |
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+0.5)*MapScale,y-(p.z/M.width-0.5)*MapScale,MapScale) | |
popStyle() | |
end | |
--# 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 separately 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(74,10,10) --starting tile --74,10 | |
map.walkAccel=0.1 --speed change (pixels/sec) when you touch screen | |
map.maxSpeed=0.3 | |
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,78,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,91,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} | |
} | |
map.crosshairs=CreateCrossHairs(100) | |
return map | |
end | |
function MapToPixel(v) --x,z are tile units, y is pixels | |
return vec3((v.x-0.5)*M.width,v.y,-(v.z-0.5)*M.width) | |
end | |
--modelName,AI_Object,position,rotate,markAsOccupied | |
function SetupModels(m) | |
AI.AddObject{modelName="Spider",object=AI.Spider,position=vec3(62,0,5),speed=M.maxSpeed/2,ambient=0.25} --62,5 | |
AI.AddObject{modelName="Zombie",object=AI.Zombie,position=vec3(42,0,15),speed=M.maxSpeed/2} | |
AI.AddObject{modelName="Revolver",object=AI.Revolver,position=vec3(69,4,9),rotation=vec3(0,90,0)} | |
AI.AddObject{modelName="Cage3",object=nil,position=vec3(74,0,10),rotation=vec3(0,-90,0), | |
markAsOccupied={vec2(74,9)}} | |
end | |
function CreateCrossHairs(w) | |
local i=image(w,w) | |
setContext(i) | |
stroke(255,255,0,200) | |
strokeWidth(1) | |
line(w/2,0,w/2,w) | |
line(0,w/2,w,w/2) | |
fill(0,0,0,0) | |
--ellipse(w/2,w/2,w) | |
ellipse(w/2,w/2,w*2/3) | |
setContext() | |
return i | |
end | |
--# Player | |
--Player | |
Player=class() | |
function Player:init(initialPosition,tileWidth,map,walkAccel,maxSpeed) | |
--self.position=initialPosition | |
self.tileWidth=tileWidth | |
self.map=map | |
self.health=100 | |
self.maxSpeed=maxSpeed | |
self.walkAccel=walkAccel | |
--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 | |
self.position=vec3((initialPosition.x-0.5)*self.tileWidth, | |
initialPosition.y, | |
-(initialPosition.z-0.5)*self.tileWidth) | |
--player settings | |
self.angle=0 --player orientation | |
self.turnAngle=1 --how much player turns when screen is touched | |
--..the speed with which angle changes | |
self.angleChange=1 | |
self.velocity=vec3(0,0,0) | |
self.speed=0 | |
end | |
function Player:draw() | |
self:Move() | |
if self.weapon then | |
if self.weapon.model.name=="Revolver" then | |
local x,z=1.3*self.deltaV.x,1.3*self.deltaV.z | |
local pos=self.position+vec3(x,-2.5,z) | |
local rot=vec3(0,90-self.angle,0) | |
self.weapon:SetPosRot(pos,rot) | |
end | |
end | |
self:DrawCrossHairs() | |
end | |
function Player:DrawCrossHairs() | |
end | |
function Player:ChangeVel() | |
local a=math.rad(self.angle) | |
local s,c=math.sin(a),math.cos(a) | |
self.velocity.x,self.velocity.z=self.speed*s,-self.speed*c | |
self.deltaV=vec3(s,0,-c) | |
look.x,look.z=lookDist*s,-lookDist*c | |
end | |
--check if the square the player is in contains a wall | |
function Player:Move() | |
local v=self.position+self.velocity | |
local a=PlayerTile(v) | |
if self.map[a.x][a.y]==1 then | |
self.speed=0 | |
else | |
self.position=self.position+self.velocity | |
end | |
end | |
function Player:Stop() | |
self.speed=0 | |
self.velocity=vec3(0,0,0) | |
end | |
function Player:Collect(object) | |
Messages.Add{text=object.model.name.." collected"} | |
self.weapon=object | |
end | |
function Player:Moving(v) --v is vec2 | |
--Test=v.x..", "..v.y | |
self.angle=self.angle+v.x*self.turnAngle | |
--Test=Test.." = "..self.angle | |
self.speed=math.min(self.maxSpeed,self.speed+v.y*self.walkAccel) | |
self:ChangeVel() | |
end | |
function Player:Shoot() | |
if self.weapon then self.weapon:Shoot() end | |
end | |
--# Scene | |
--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) | |
self.m.shader.hasTexture=true | |
self.m.shader.ambient=0 | |
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 | |
--# Billboard | |
--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 | |
--# Models | |
--Models | |
Models={ | |
{name="Box",scale=3,mat={0.5,0,0,0,0,0.5,0,0,0,0,0.5,0,0,2,0,1}, | |
url="https://gist.githubusercontent.com/dermotbalson/039e6dd23ea9168a4d04/raw/72719b58b0b072444574df1b0b593808b84756b9/gistfile1.txt"}, | |
{name="Medical1",scale=0.08,mat={-0.8,0,0,0,0,0.8,0,0,-0,0,-0.8,0,0,0,0,1}, | |
url="https://gist.githubusercontent.com/dermotbalson/6939d1bb89181c371263/raw/6c6badc36b50fbe99c20f80e0879069a08d5391d/gistfile1.txt"}, | |
{name="Zombie",scale=0.1,mat={-1,0,0,0,0,1,0,0,-0,0,-1,0,0,-11,0,1}, | |
url="https://gist.githubusercontent.com/dermotbalson/50ed2828d29bccdf13a0/raw/44bae47c689f43f5b0a0b857706a852bfe4b7f53/gistfile1.txt"}, | |
{name="Gate",scale=0.1, mat={-0,0,-1,0,0,1,0,0,1,0,-0,0,0,0,0,1}, | |
url="https://gist.githubusercontent.com/dermotbalson/f7ca1f4047ace425f2c8/raw/3f1ce2d07840cd32b73cdb12732ffb859cfbd66a/gistfile1.txt"}, | |
{name="Gate2",scale=0.075,mat={-0,-0.5,-0,0,-0,0,-0.5,0,0.5,0,-0,0,0,7,0,1}, | |
url="https://gist.githubusercontent.com/dermotbalson/ddbfdce7684b7985d2f0/raw/6b207ce2e992e69a4133630224822eb72d2ad55c/gistfile1.txt"}, | |
{name="Cage3",scale=3,mat=nil, | |
url="https://gist.githubusercontent.com/dermotbalson/23091122d62ce9d28bb1/raw/6b23dbe862e54f04d2bb4984b00baf4057533bf8/gistfile1.txt"}, | |
{name="Spider",scale=0.1,mat={-1,0,-0,0,0,1,0,0,0,0,-1,0,0,0,-1,1}, | |
url="https://gist.githubusercontent.com/dermotbalson/a7d1cc67e37460b90707/raw/12c35c72d3242f6376241020d0e808d06d324c32/gistfile1.txt"}, | |
{name="Swarm",scale=3,mat=nil, | |
url="https://gist.githubusercontent.com/dermotbalson/03f901b5da76cf01e5f2/raw/b27a5da4d24b2a4d002d24be860e3f510d34dff4/gistfile1.txt"}, | |
{name="Revolver",scale=0.02,mat={-0,0,-0.5,0,0,0.5,0,0,0.5,0,-0,0,0,1,0,1}, | |
url="https://gist.githubusercontent.com/dermotbalson/f10527becdc7b9a3d550/raw/1714828f4d67c7885f53a8c0e61c590370742e99/gistfile1.txt"}, | |
{name="SpyEye5",scale=1,mat={1,0,0,0,0,1,0,0,0,0,1,0,0,8,0,1}, | |
url="https://gist.githubusercontent.com/dermotbalson/7a821074cdd149812798/raw/2705147580b8d2f596127268b0067333e9a9061d/gistfile1.txt"}, | |
{name="MiniGun",scale=.2,mat=nil, | |
url="https://gist.githubusercontent.com/dermotbalson/579227d5eb6bc28bc90e/raw/898bebaa30cba2e1e52dce0473e838bdaad93dfb/gistfile1.txt"}, | |
{name="Barrel10",scale=4,mat={0.75,0,0,0,0,0.75,0,0,0,0,0.75,0,0,0,0,1}, | |
url="https://gist.githubusercontent.com/dermotbalson/68e2c8318266dd152f8b/raw/6f6bbf158e72f9764211ba5f00a6bf0e49fc52b0/gistfile1.txt"}, | |
{name="Container",scale=1.2,mat=nil, | |
url="https://gist.githubusercontent.com/dermotbalson/d71797058ef2487a9632/raw/4c3e7d102b29e1fa35efe75e5aab3198900f9df8/gistfile1.txt"}, | |
{name="Wraith",scale=2,mat={1,0,0,0,0,1,0,0,0,0,1,0,0,-2,0,1}, | |
url="https://gist.githubusercontent.com/dermotbalson/d1d6a73c0b79b9960e54/raw/3fdfcf14728615c6c317c7effafb3219e0764d25/gistfile1.txt"}, | |
{name="Swarm2",scale=1, | |
url="https://gist.githubusercontent.com/dermotbalson/b432e0601c1805a77b8f/raw/8cbc3bbe8a331350f71fa3167de969c8ec894b3e/gistfile1.txt"}, | |
{name="Leaper",scale=1, | |
url="https://gist.githubusercontent.com/dermotbalson/ecf548e68dc88f108dbc/raw/31ab54f0fe2c6b1ecbdad28d56ae5f5a5f55113d/gistfile1.txt"}, | |
{name="Antlion",scale=0.05, | |
url="https://gist.githubusercontent.com/dermotbalson/664eda75c8c0160d1276/raw/0d81abb6b68d6ffbf085e3e027caf9ded6055d18/gistfile1.txt"} | |
} | |
function LoadModel(m) | |
return OBJ(m.name,m.url,m.scale,m.mat) | |
end | |
--Object class | |
O={} | |
function O.LoadModel(p) | |
local mod={} | |
for i=1,#p do | |
mod[i]=OBJ(p[i][1],p[i][2],p[i][3]) | |
end | |
return mod | |
end | |
--OBJ library | |
OBJ=class() | |
OBJ.DataPrefix="cfg_" | |
OBJ.imgPrefix="Documents:z3D" | |
function OBJ:init(name,url,scale,mat) | |
self.name=name | |
self.callback=callback | |
self.scale=scale or 1 | |
if mat then self.modelmatrix=matrix(unpack(mat)) end | |
self.centre=vec3(0,0,0) | |
self.data=readGlobalData(OBJ.DataPrefix..name) | |
if self.data then self:ProcessData() | |
else http.request(url,function(d) self:DownloadData(d) end) end | |
end | |
function OBJ:DownloadData(data) | |
if data~=nil and string.find(data,"[obj]") then | |
saveGlobalData(OBJ.DataPrefix..self.name,data) | |
self.data=data | |
self:ProcessData() | |
else print("Error loading data for "..self.name) return end | |
end | |
function OBJ:ProcessData() | |
self.mtl={} | |
self.m={} | |
local p, v, tx, t, np, n={},{},{},{},{},{} | |
local s=self.data | |
local mname | |
local section="mtl" | |
for line in s:gmatch("[^\r\n]+") do | |
line=OBJ.trim(line) | |
if string.find(line,"%[obj%]")~=nil then section="obj" mname=nil end | |
--material definition section | |
if section=="mtl" then | |
if string.find(line,"newmtl") then | |
mname=OBJ.GetValue(line) | |
--print(mname) | |
self.mtl[mname]={} | |
else | |
local code=string.sub(line,1,2) | |
if code=="Ka" then --ambient | |
self.mtl[mname].Ka=OBJ.GetColor(line) | |
--print(mname,"Ka",OBJ.mtl[mname].Ka[1],OBJ.mtl[mname].Ka[2],OBJ.mtl[mname].Ka[3]) | |
elseif code=="Kd" then --diffuse | |
self.mtl[mname].Kd=OBJ.GetColor(line) | |
--print(mname,"Kd",OBJ.mtl[mname].Kd[1],OBJ.mtl[mname].Kd[2],OBJ.mtl[mname].Kd[3]) | |
elseif code=="Ks" then --specular | |
self.mtl[mname].Ks=OBJ.GetColor(line) | |
--print(mname,"Ks",OBJ.mtl[mname].Ks[1],OBJ.mtl[mname].Ks[2],OBJ.mtl[mname].Ks[3]) | |
elseif code=="Ns" then --specular exponent | |
self.mtl[mname].Ns=OBJ.GetValue(line) | |
--print(mname,"Ns",OBJ.mtl[mname].Ns) | |
elseif code=="ill" then --illumination code | |
self.mtl[mname].illum=OBJ.GetValue(line) | |
--print(mname,"illum",OBJ.mtl[mname].illum) | |
elseif code=="ma" then --texture map name | |
local u=OBJ.split(OBJ.GetValue(line)," ") | |
if string.find(u[1],"%.") then | |
self.mtl[mname].map=string.sub(u[1],1,string.find(u[1],"%.")-1) | |
else | |
self.mtl[mname].map=u[1] | |
end | |
self.mtl[mname].path=u[2] | |
--print(mname,line,"\n",OBJ.mtl[mname].map,"\n",OBJ.mtl[mname].path) | |
end | |
end | |
--data section | |
elseif section=="obj" then | |
--read in groups of data into separate meshes | |
local code=string.sub(line,1,2) | |
--look for material settings, a separate mesh is used for each | |
if string.find(line,"usemtl") then | |
if mname then | |
local m=mesh() | |
if self.scale~=1 then for i=1,#v do v[i]=v[i]*self.scale end end | |
m.vertices=v | |
if #t>0 then m.texCoords=t end | |
if #n>0 then m.normals=n end | |
if self.mtl[mname] then m.settings=self.mtl[mname] end | |
self.m[#self.m+1]=m | |
end | |
mname=OBJ.GetValue(line) | |
v,t,n={},{},{} | |
--print(mname) | |
end | |
if code=="v " then --point position | |
p[#p+1]=OBJ.GetVec3(line) | |
elseif code=="vn" then --point normal | |
np[#np+1]=OBJ.GetVec3(line) | |
elseif code=="vt" then --texture co-ord | |
tx[#tx+1]=OBJ.GetVec2(line) | |
elseif code=="f " then --vertex | |
local pts,ptex,pnorm=OBJ.GetList(line) | |
if #pts==3 then | |
for i=1,3 do v[#v+1]=p[tonumber(pts[i])] end | |
if ptex then for i=1,3 do t[#t+1]=tx[tonumber(ptex[i])] end end | |
if pnorm then for i=1,3 do n[#n+1]=np[tonumber(pnorm[i])] end end | |
elseif #pts==4 then | |
for i=1,3 do v[#v+1]=p[tonumber(pts[i])] end | |
if ptex then for i=1,3 do t[#t+1]=tx[tonumber(ptex[i])] end end | |
if pnorm then for i=1,3 do n[#n+1]=np[tonumber(pnorm[i])] end end | |
v[#v+1]=p[tonumber(pts[3])] | |
if ptex then t[#t+1]=tx[tonumber(ptex[3])] end | |
if pnorm then n[#n+1]=np[tonumber(pnorm[3])] end | |
v[#v+1]=p[tonumber(pts[4])] | |
if ptex then t[#t+1]=tx[tonumber(ptex[4])] end | |
if pnorm then n[#n+1]=np[tonumber(pnorm[4])] end | |
v[#v+1]=p[tonumber(pts[1])] | |
if ptex then t[#t+1]=tx[tonumber(ptex[1])] end | |
if pnorm then n[#n+1]=np[tonumber(pnorm[1])] end | |
elseif #pts>4 then | |
local cx,cy,cz=0,0,0 | |
local ttx,tty=0,0 | |
local nx,ny,nz=0,0,0 | |
for i=1,#pts do | |
local u=p[tonumber(pts[i])] cx,cy,cz=cx+u.x,cy+u.y,cz+u.z | |
if ptex then local u=tx[tonumber(ptex[i])] ttx,tty=ttx+u.x,tty+u.y end | |
--if pnorm then local u=p[tonumber(pnorm[i])] nx,ny,nz=nx+u.x,ny+u.y,nz+u.z end | |
end | |
local cp=vec3(cx/#pts,cy/#pts,cz/#pts) | |
if ptex then ct=vec2(ttx/#pts,tty/#pts) end | |
--if pnorm then cn=vec3(nx/#pts,ny/#pts,nz/#pts):normalize() end | |
local j | |
for i=1,#pts do | |
if i<#pts then j=i+1 else j=1 end | |
v[#v+1]=p[tonumber(pts[i])] | |
if ptex then t[#t+1]=tx[tonumber(ptex[i])] end | |
--if pnorm then n[#n+1]=np[tonumber(pnorm[i])] end | |
v[#v+1]=p[tonumber(pts[j])] | |
if ptex then t[#t+1]=tx[tonumber(ptex[j])] end | |
--if pnorm then n[#n+1]=np[tonumber(pnorm[j])] end | |
v[#v+1]=cp | |
if ptex then t[#t+1]=ct end | |
--if pnorm then n[#n+1]=np[tonumber(cn)] end | |
end | |
end | |
end | |
end | |
end | |
local m=mesh() | |
if self.scale~=1 then | |
for i=1,#v do v[i]=v[i]*self.scale end | |
end | |
m.vertices=v | |
if #t>0 then m.texCoords=t end | |
if #n>0 then m.normals=n end | |
m.settings=self.mtl[mname] | |
self.m[#self.m+1]=m | |
self:GetStats() | |
--download images if not stored locally | |
self.MissingImages={} | |
for i,O in pairs(self.mtl) do | |
if O.map then | |
local y=readImage(OBJ.imgPrefix..O.map) | |
if not y then self.MissingImages[#self.MissingImages+1]={O.map,O.path} end | |
end | |
end | |
if #self.MissingImages>0 then self:LoadImages() end | |
end | |
function OBJ:LoadImages() | |
print("downloading"..self.MissingImages[1][1]) | |
http.request(self.MissingImages[1][2],function(d) self:StoreImage(d) end) | |
end | |
function OBJ:StoreImage(d) | |
--print("saving"..self.MissingImages[1][1]) | |
saveImage(OBJ.imgPrefix..self.MissingImages[1][1],d) | |
table.remove(self.MissingImages,1) | |
if #self.MissingImages~=0 then self:LoadImages() end | |
end | |
function OBJ:DeleteData() | |
saveGlobalData(OBJ.DataPrefix..self.name,nil) | |
for i,O in pairs(self.mtl) do | |
if O.map then | |
print("deleting "..OBJ.imgPrefix..O.map) | |
local y=saveImage(OBJ.imgPrefix..O.map,nil) | |
end | |
end | |
end | |
function OBJ.ListStoredData() | |
local t=listGlobalData() | |
for i,u in pairs(t) do | |
if string.find(u,"cfg_") then print(u) saveGlobalData(u,nil) end | |
end | |
--local t=listGlobalData() | |
--local a=table.concat(t,",") | |
--print(a) | |
end | |
function OBJ.GetColor(s) | |
local s1=string.find(s," ") | |
local s2=string.find(s," ",s1+1) | |
local s3=string.find(s," ",s2+1) | |
return color(string.sub(s,s1+1,s2-1)*255,string.sub(s,s2+1,s3-1)*255,string.sub(s,s3+1,string.len(s))*255) | |
end | |
function OBJ.GetVec3(s) | |
local s1=string.find(s," ") | |
local s2=string.find(s," ",s1+1) | |
local s3=string.find(s," ",s2+1) | |
return vec3(math.floor(string.sub(s,s1+1,s2-1)*100)/100, | |
math.floor(string.sub(s,s2+1,s3-1)*100)/100, | |
math.floor(string.sub(s,s3+1,string.len(s))*100)/100) | |
end | |
function OBJ.GetVec2(s) | |
local s1=string.find(s," ") | |
local s2=string.find(s," ",s1+1) | |
local s3=string.find(s," ",s2+1) | |
if s3 then | |
return vec3(math.floor(string.sub(s,s1+1,s2-1)*100)/100, | |
math.floor(string.sub(s,s2+1,s3-1)*100)/100) | |
else | |
return vec2(math.floor(string.sub(s,s1+1,s2-1)*100)/100, | |
math.floor(string.sub(s,s2+1,string.len(s))*100)/100) | |
end | |
end | |
function OBJ:draw(v,rot,p,r,m,a) | |
pushMatrix() | |
if m then v,rot=m(v,rot,p,r) end | |
translate(v.x,v.y,v.z) | |
if type(rot)=="number" and rot~=0 then rotate(rot,0,1,0) | |
else | |
for i,v in pairs(rot) do | |
rotate(v.x,v.y,v.z,v.w) | |
end | |
end | |
for i=1,#self.m do | |
self.m[i].shader.mModel=modelMatrix() --part of lighting | |
self.m[i].shader.pos=p | |
self.m[i].shader.range=r | |
self.m[i]:draw() | |
end | |
popMatrix() | |
end | |
function OBJ.GetValue(s) | |
return string.sub(s,string.find(s," ")+1,string.len(s)) | |
end | |
function OBJ.trim(s) | |
while string.find(s," ") do s = string.gsub(s," "," ") end | |
return s:match'^()%s*$' and '' or s:match'^%s*(.*%S)' | |
end | |
function OBJ.split(s,sep) | |
sep=sep or "/" | |
local p={} | |
local pattern = string.format("([^%s]+)", sep) | |
string.gsub(s,pattern, function(c) p[#p+1] = c end) | |
return p | |
end | |
function OBJ.GetList(s) | |
local p,t,n={},{},{} | |
--for word in s:gmatch("%w+") do table.insert(p, word) end | |
p=OBJ.split(s," ") | |
table.remove(p,1) | |
for i=1,#p do | |
local a=OBJ.split(p[i]) | |
if #a==1 then | |
p[i]=math.abs(a[1]) | |
elseif #a==2 then | |
p[i]=math.abs(a[1]) | |
t[i]=math.abs(a[2]) | |
elseif #a==3 then | |
p[i]=math.abs(a[1]) | |
t[i]=math.abs(a[2]) | |
n[i]=math.abs(a[3]) | |
end | |
end | |
return p,t,n | |
end | |
function OBJ:GetStats() | |
local x,y,z,n=0,0,0,0 | |
local minx,maxx,miny,maxy,minz,maxz=999,-999,999,-999,999,-999 | |
for i=1,#self.m do | |
local vv=self.m[i].vertices | |
local nn=0 | |
for j=1,#vv do | |
local v=vv[i] | |
if v~=nil then | |
x,y,z=x+v.x,y+v.y,z+v.z | |
nn=nn+1 | |
if v.x<minx then minx=v.x end | |
if v.x>maxx then maxx=v.x end | |
if v.y<miny then miny=v.y end | |
if v.y>maxy then maxy=v.y end | |
if v.z<minz then minz=v.z end | |
if v.z>maxz then maxz=v.z end | |
end | |
end | |
n=n+nn | |
end | |
self.centre=vec3(x/n,y/n,z/n) | |
self.minvert=vec3(minx,miny,minz) | |
self.maxvert=vec3(maxx,maxy,maxz) | |
self.size=vec3(maxx-minx,maxy-miny,maxz-minz) | |
end | |
--# Fx | |
--Fx | |
Fx={} | |
Fx.file="Dropbox:Dungeon" | |
Fx.On=true | |
Fx.clips={ | |
["hell"]={start=0,finish=153,volume=0.33}, | |
["littlegirl"]={start=155,finish=177,volume=0.6}, | |
["spider chatter"]={start=179,finish=184,volume=0.1}, | |
["zombie bite"]={start=186,finish=189}, | |
["brains"]={start=191,finish=199}, | |
["brains 2"]={start=201,finish=204}, | |
["shotgun fire"]={start=206,finish=208}, | |
["zombie moan"]={start=211,finish=218} | |
} | |
Fx.theme="hell" | |
Fx.phaseTime=0.5 --phase in/out time | |
--Fx.status=nil | |
function Fx.Enabled(s) | |
Fx.On=s | |
end | |
function Fx.Play(clip) | |
if not Fx.On then return end | |
if not music.name then | |
music(Fx.file,false,0,0) | |
music.paused=true | |
end | |
local f=Fx.clips[clip] --get details for new clip | |
--if the theme has been interrupted, remember where it stopped | |
if clip~=Fx.theme and Fx.currentClip==Fx.theme then | |
Fx.clips[Fx.theme].offset=music.currentTime | |
end | |
f.volume=f.volume or 1 | |
f.pan=f.pan or 0 | |
f.offset=f.offset or 0 | |
music.currentTime=f.start+f.offset | |
music.volume=f.volume | |
Fx.startTime=ElapsedTime | |
Fx.endOfPhaseInTime=Fx.startTime+Fx.phaseTime | |
Fx.endTime=ElapsedTime+f.finish-f.start-f.offset | |
Fx.startOfPhaseOutTime=Fx.endTime-Fx.phaseTime | |
Fx.currentClip=clip | |
-- Fx.status="PhaseIn" | |
music.paused=false | |
end | |
function Fx.Stop() | |
music.paused=true | |
if Fx.currentClip==Fx.theme then Fx.clips[Fx.theme].offset=0 end | |
Fx.Play(Fx.theme) | |
end | |
function Fx.Update() | |
if not Fx.currentClip then return end | |
local v=Fx.clips[Fx.currentClip].volume | |
if ElapsedTime<Fx.endOfPhaseInTime then | |
music.volume=v*(ElapsedTime-Fx.startTime)/Fx.phaseTime | |
elseif ElapsedTime>Fx.startOfPhaseOutTime then | |
if ElapsedTime>Fx.endTime then Fx.Stop() | |
else | |
music.volume=v*(Fx.endTime-ElapsedTime)/Fx.phaseTime | |
end | |
end | |
end | |
--# AI | |
--AI | |
--manages interactive object behaviour | |
--each object has a class | |
AI={} | |
AI.objects={} | |
function AI.init(tileSize,wallHeight,dungeonMap) --copy of internal map of dungeon | |
AI.tileSize=tileSize | |
AI.wallHeight=wallHeight | |
AI.map=dungeonMap | |
AI.collisionDistance=tileSize | |
end | |
function AI.AddObject(arg) | |
arg.rotation=arg.rotation or vec3(0,0,0) | |
mods=mods or {} | |
if mods[arg.modelName]==nil then | |
for j,mod in pairs(Models) do | |
if mod.name==arg.modelName then | |
mods[arg.modelName]=LoadModel(mod) | |
break | |
end | |
end | |
end | |
arg.object=arg.object or AI.Base | |
arg.model=mods[arg.modelName] | |
table.insert(AI.objects,arg.object(arg)) | |
--add to collision map if requested | |
if arg.markAsOccupied then | |
if type(arg.markAsOccupied)=="boolean" then | |
AI.map[arg.position.x][arg.position.z]=1 | |
--print(vec2(arg.position.x,arg.position.z)) | |
elseif type(arg.markAsOccupied)=="table" then | |
for i,c in pairs(arg.markAsOccupied) do | |
AI.map[c.x][c.y]=1 | |
-- print(vec2(c.x,c.y)) | |
end | |
end | |
end | |
end | |
--------------------------------------- | |
--class template - all classes must derive from this | |
--------------------------------------- | |
AI.Base=class() | |
--must have at least model, position, rotation | |
function AI.Base:init(arg) --position is vec3 tile based, rotation is vec3 | |
self.model=arg.model | |
--convert to pixels | |
self.pos=vec3((arg.position.x-0.5)*AI.tileSize,arg.position.y,-(arg.position.z-0.5)*AI.tileSize) | |
--store initial rotations as a matrix | |
if arg.rotation then | |
self.rotation=arg.rotation | |
self:GetMatrix() | |
end | |
self.speed=arg.speed or 0 | |
self.ambient=arg.ambient or 0 | |
--for _,m in pairs(self.model.m) do m.shader.ambient=self.ambient end | |
self.ignoreVisibility=false | |
self:setup(arg) --object specific setup, if you want to do anything extra, create this function | |
end | |
function AI.Base:setup(arg) | |
--put your code in here | |
end | |
function AI.Base:SetPosRot(pos,rot) | |
self.pos=pos | |
if rot then | |
self.rotation=rot | |
self:GetMatrix() | |
end | |
end | |
function AI.Base:draw(playerPos,lightRange) | |
pushMatrix() | |
--<---move here | |
translate(self.pos.x,self.pos.y,self.pos.z) | |
if self.model.modelmatrix then applyMatrix(self.model.modelmatrix) end | |
applyMatrix(self.rotationMatrix) | |
--<---rotate here | |
for _,m in pairs(self.model.m) do | |
m.shader.mModel=modelMatrix() | |
m.shader.pos=playerPos | |
m.shader.range=lightRange | |
m:draw() | |
end | |
popMatrix() | |
end | |
function AI.Base:collide(playerPos,detectionRange) | |
if self:HasCollided(playerPos) then return true else return false end | |
end | |
function AI.Base:Shoot() end --for objects which shoot | |
function AI.Base:Shot() end --for when player shoots object | |
function AI.Base:HasCollided(playerPos) | |
local d=vec2(playerPos.x,playerPos.z):dist(vec2(self.pos.x,self.pos.z)) | |
if d<AI.collisionDistance then return true else return false end | |
end | |
function AI.Base:PlayerDetected(playerPos,detectionRange,ignoreVisibility) | |
if self.pos:dist(playerPos)<detectionRange then | |
if ignoreVisibility then return true | |
else | |
--get fractional tile positions | |
local selfTile=vec2(self.pos.x,-self.pos.z)/AI.tileSize | |
local playerTile=vec2(playerPos.x,-playerPos.z)/AI.tileSize | |
--print(selfTile,playerTile) | |
local distance=selfTile:dist(playerTile) | |
--pprint(distance) | |
if distance==0 then return true end | |
local difference=playerTile-selfTile | |
--step through the distance between them, checking if the squares are filled | |
local stepFraction=math.max(0.01,0.1/distance) | |
for f=stepFraction,1,stepFraction do | |
local p=selfTile+difference*f | |
local x,y=math.floor(p.x+1),math.floor(p.y+1) | |
if AI.map[x][y]==1 then return false end | |
end | |
return true | |
end | |
end | |
end | |
function AI.Base:Move(playerPos) | |
local d,f=playerPos:dist(self.pos),0 | |
if d>0 then f=math.min(1,self.speed/d) end | |
self.pos.x=self.pos.x+f*(playerPos.x-self.pos.x) | |
self.pos.z=self.pos.z+f*(playerPos.z-self.pos.z) | |
self:Orient(playerPos) | |
end | |
function AI.Base:Orient(playerPos) | |
self.rotation.y=self:AngleToPlayer(playerPos) | |
self:GetMatrix() | |
end | |
function AI.Base:DrawModel(playerPos,lightRange,rotation) | |
pushMatrix() | |
local x,y,z=self.pos.x,self.pos.y,self.pos.z | |
translate(x,y,z) | |
if self.model.modelmatrix then applyMatrix(self.model.modelmatrix) end | |
applyMatrix(self.rotationMatrix) | |
if rotation then | |
if rotation.x~=0 then rotate(rotation.x,1,0,0) end | |
--if rotation.y~=0 then rotate(rotation.y,0,1,0) end | |
if rotation.z~=0 then rotate(rotation.z,0,0,1) end | |
if rotation.y~=0 then rotate(rotation.y,0,1,0) end | |
end | |
for _,m in pairs(self.model.m) do | |
m.shader.mModel=modelMatrix() --part of lighting | |
m.shader.pos=playerPos | |
m.shader.range=lightRange | |
m.shader.ambient=self.ambient | |
m:draw() | |
end | |
popMatrix() | |
end | |
--TO DO bug when angle is exactly 90, faces wrong way | |
function AI.Base:AngleToPlayer(playerPos) | |
local diff=playerPos-self.pos | |
if diff.x==0 then | |
if diff.z<0 then return 0 else return 180 end | |
else | |
local a=math.deg(math.atan(diff.x/diff.z)) | |
if diff.z>0 then a=a+180 end | |
return a | |
end | |
end | |
function AI.Base:GetMatrix() | |
local r=self.rotation | |
resetMatrix() | |
pushMatrix() | |
rotate(r.x,1,0,0) rotate(r.z,0,0,1) rotate(r.y,0,1,0) | |
local m=modelMatrix() | |
self.rotationMatrix=matrix(m[1],m[2],m[3],m[4],m[5],m[6],m[7],m[8],m[9],m[10], | |
m[11],m[12],m[13],m[14],m[15],m[16]) | |
popMatrix() | |
end | |
------------------------------------------- | |
-- Zombie class | |
------------------------------------------- | |
AI.Zombie=class(AI.Base) | |
function AI.Zombie:setup(arg) | |
self.speed=arg.speed or 0 | |
end | |
function AI.Zombie:collide(playerPos,detectionRange) | |
if self:HasCollided(playerPos) then | |
--TO DO program damage | |
elseif self:PlayerDetected(playerPos,detectionRange,self.ignoreVisibility) then | |
if not self.seen then | |
Fx.Play("brains") | |
self.seen=true | |
end | |
self:Move(playerPos) | |
end | |
end | |
function AI.Zombie:draw(playerPos,lightRange) | |
self:collide(playerPos,lightRange) | |
self.pos.y=self.pos.y+0.15*noise(ElapsedTime) --make zombie bounce a little | |
self:DrawModel(playerPos,lightRange) | |
end | |
------------------------------------------- | |
-- Spider class | |
------------------------------------------- | |
AI.Spider=class(AI.Base) | |
function AI.Spider:setup(arg) | |
self.speed=arg.speed or 0 | |
self.range=arg.range or 0.5 --of light radius, sight is poor | |
self.floorY=self.pos.y --store floor height | |
self.pos.y=AI.wallHeight*0.8 --position in mid air | |
end | |
function AI.Spider:collide(playerPos,detectionRange) | |
if self:HasCollided(playerPos) then | |
--TO DO program damage | |
elseif self:PlayerDetected(playerPos,detectionRange,self.ignoreVisibility) then | |
if not self.seen then | |
Fx.Play("spider chatter") | |
--Messages.Add{text="Spider alert!"} | |
self.seen=true | |
end | |
if self.pos.y>self.floorY then | |
self.pos.y=self.pos.y-0.1 | |
self:Orient(playerPos) | |
else self:Move(playerPos) end | |
end | |
end | |
function AI.Spider:draw(playerPos,lightRange) | |
self:collide(playerPos,lightRange) | |
local rotation=vec3(0,0,9*noise(ElapsedTime)) | |
self.pos.x=self.pos.x+0.05*noise(ElapsedTime) | |
self.pos.y=self.pos.y+0.05*noise(ElapsedTime/2) | |
self:DrawModel(playerPos,lightRange,rotation) | |
end | |
------------------------------------------- | |
-- Revolver class | |
------------------------------------------- | |
AI.Revolver=class(AI.Base) | |
function AI.Revolver:setup() | |
local p=readImage("Tyrian Remastered:Flame 1") | |
self.flame=image(p.width,p.height*3) | |
setContext(self.flame) | |
sprite(p,0,0,p.width,p.height*3) | |
setContext() | |
self.pauseBetweenShots=0.5 | |
self.lastShotTime=0 | |
end | |
function AI.Revolver:draw(playerPos,lightRange) | |
if not self.collected and self:HasCollided(playerPos) then | |
self.collected=true | |
player:Collect(self) | |
end | |
AI.Base.draw(self,playerPos,lightRange) | |
end | |
function AI.Revolver:Shoot() | |
if self.lastShotTime and ElapsedTime<self.lastShotTime+self.pauseBetweenShots then return end | |
self.lastShotTime=ElapsedTime | |
Messages.Add{text="Bang!",0.5} | |
end | |
--------------------------------------- | |
--shared generic functions for use by all objects | |
--------------------------------------- | |
--# 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.centre = t.centre or self.radius * vec2(1,1) + vec2(5,15) | |
end | |
function JoyStick:draw() | |
pushStyle() | |
fill(213, 213, 207, 50) | |
ellipse(self.centre.x,self.centre.y,2*self.radius) | |
popStyle() | |
end | |
function JoyStick:touched(t) | |
if t.state~=ENDED then | |
local v=vec2(t.x,t.y) | |
if v:dist(self.centre)<=self.radius then | |
local w=v-self.centre | |
return vec2(w.x/self.radius,w.y/self.radius) | |
end | |
else return nil | |
end | |
end | |
--# Shaders | |
--Shaders | |
--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 | |
uniform lowp float ambient; | |
uniform bool hasTexture; | |
varying lowp vec4 vColor; | |
varying highp vec2 vTexCoord; | |
varying highp vec4 vPosition; //position of current pixel | |
float nonAmbient=1.0-ambient; | |
void main() | |
{ | |
float f=ambient+nonAmbient*max(0.,1.0-length( pos - vPosition.xyz ) / range); | |
//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 * vColor; | |
if (hasTexture) 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 pixels 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; | |
} | |
} | |
]] | |
} | |
--# Messages | |
--messages | |
Messages={} | |
Messages.text={} | |
Messages.defaultLength=5 | |
Messages.defaultColour=color(255) | |
Messages.defaultSize=64 | |
--Messages.defaultPosition=vec2(WIDTH/2, HEIGHT/2) | |
Messages.defaultFont="Noteworthy-Bold" | |
function Messages.Add(settings) --messageText,durationInSeconds=Messages.defaultLength,size=64,colour) | |
Messages.text[#Messages.text+1]={ | |
["message"]=settings.text, | |
["length"]=settings.length or Messages.defaultLength, | |
["size"]=settings.size or Messages.defaultSize, | |
["colour"]=settings.colour or Messages.defaultColour, | |
["position"]=settings.position or vec2(WIDTH/2, HEIGHT/2), --Messages.defaultPosition, | |
["font"]=settings.font or Messages.defaultFont | |
} | |
end | |
function Messages.draw() | |
if #Messages.text==0 then return end | |
m=Messages.text[1] | |
if not m.endTime then | |
m.endTime=ElapsedTime+m.length | |
elseif ElapsedTime>m.endTime then | |
table.remove(Messages.text,1) | |
return | |
end | |
Messages.WriteMessage(m) | |
end | |
function Messages.WriteMessage(m) | |
pushStyle() | |
font(m.font) | |
textWrapWidth(WIDTH*.75) | |
fontSize(m.size) | |
m.colour.a=(m.endTime-ElapsedTime)/m.length*255 | |
fill(m.colour) | |
textMode(CENTER) | |
--text(m.message,m.position.x,m.position.y) | |
text(m.message,WIDTH/2,HEIGHT/2) | |
popStyle() | |
end | |
--# Test | |
--Test | |
--[[ | |
displayMode(STANDARD) | |
function setup() | |
parameter.integer("n",0,1000,0) | |
end | |
--] | |
--[ | |
function draw() | |
background(0) | |
if tt then n=n+1 end | |
end | |
function touched(t) | |
if t.state==BEGAN then tt=true | |
elseif t.state==ENDED then | |
tt=nil | |
n=0 | |
return | |
end | |
end | |
--]] | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment