狀態
,幀動畫
和地圖生成
整合起來前面三章咱們完成了遊戲開發中的 狀態原型
,幀動畫原型
和 地圖生成原型
這三個模塊, 由於都是原型, 因此以上三個模塊還有不少能夠改進的地方, 這些細節咱們會逐步完善, 如今讓咱們把這三個模塊整合到一塊兒.git
由於咱們是一個模塊一個模塊以類的形式進行開發的, 以前這些模塊都試驗過能夠正常跑起來, 並且現階段模塊之間的耦合比較小, 因此咱們的集成工做就比較順利, 須要修改的只是程序主框架, 也就是 setup()
和 draw()
兩個函數.程序員
另外咱們在操做控制方面還沒怎麼投入, 以前僅僅是在 touched(touch)
函數中寫了一點簡單的測試用代碼, 這些工做顯然是遠遠不夠的.github
咱們目前開發的遊戲是要運行在平板電腦上的, 玩家對角色的操做都經過觸摸屏進行, 因此咱們須要寫一個操縱桿類來封裝那些觸摸函數, 好消息是已經有人寫好了, 而且公佈了源代碼, 因此咱們能夠直接使用, 只要說明版權信息便可.框架
這個類寫得很是簡潔明瞭, 不過我仍是加了一點註釋, 操縱桿類的代碼以下:dom
--# Stick -- 操縱桿類 做者: @Jaybob Stick = class() function Stick:init(ratio,x,y,b,s) self.ratio = ratio or 1 self.i = vec2(x or 120,y or 120) self.v = vec2(0,0) self.b = b or 180 --大圓半徑 self.s = s or 100 --小圓半徑 self.d = d or 50 self.a = 0 self.touchId = nil self.x,self.y = 0,0 end function Stick:draw() -- 沒有 touched 函數的 Stick 類是如何找到本身對應的觸摸數據的?根據點擊處座標跟操縱桿的距離來判斷 if touches[self.touchId] == nil then -- 循環取出 touches 表內的數據,比較其座標跟操縱桿的距離,若小於半徑則說明是在點擊操縱桿 for i,t in pairs(touches) do if vec2(t.x,t.y):dist(self.i) < self.b/2 then self.touchId = i end end self.v = vec2(0,0) else -- 根據對應於操縱桿的觸摸的xy座標設置 self.v,再根據它計算夾角 self.a self.v = vec2(touches[self.touchId].x,touches[self.touchId].y) - self.i self.a = math.deg(math.atan2(self.v.y, self.v.x)) end -- 根據 self.v 和 self.b 計算獲得 self.t self.t = math.min(self.b/2,self.v:len()) if self.t >= self.b/2 then self.v = vec2(math.cos(math.rad(self.a))*self.b/2,math.sin(math.rad(self.a))*self.b/2) end pushMatrix() fill(127, 127, 127, 100) -- 分別繪製大圓,小圓 ellipse(self.i.x, self.i.y, self.b) ellipse(self.i.x+self.v.x, self.i.y+self.v.y, self.s) --print(self.v.x, self.s) popMatrix() -- 根據 ratio 從新設置 self.v/self.t self.v = self.v/(self.b/2)*self.ratio self.t = self.t/(self.b/2)*self.ratio -- 根據 self.v/self.t,從新設置 self.x/self.y self.x, self.y = self.v.x, self.v.y end
修改後的代碼以下:函數
-- 主程序框架 function setup() displayMode(OVERLAY) -- 初始化狀態 myStatus = Status() -- 如下爲幀動畫代碼 s = -1 fill(249, 249, 249, 255) imgs = {} pos = {{0,0,110,120},{110,0,70,120},{180,0,70,120},{250,0,70,120}, {320,0,105,120},{423,0,80,120},{500,0,70,120},{570,0,70,120}} img = readImage("Documents:runner") m = Sprites(600,400,img,pos) ---[[ 初始化觸摸搖桿 touches = {} -- cam = Camera(pos.x,pos.y,pos.z,pos.x+look.x,look.y,pos.z+look.z) ls,rs = Stick(20,WIDTH-300,200),Stick(2,WIDTH-120) -- ls,rs = Stick(1),Stick(3,WIDTH-120) -- 初始化地圖 myMap = Maps() ss ="" end function draw() pushMatrix() pushStyle() -- spriteMode(CORNER) rectMode(CORNER) background(32, 29, 29, 255) -- 增長移動的背景圖: + 爲右移,- 爲左移 --sprite("Documents:bgGrass",(WIDTH/2+10*s*m.i)%(WIDTH),HEIGHT/2) --sprite("Documents:bgGrass",(WIDTH+10*s*m.i)%(WIDTH),HEIGHT/2) -- sprite("Documents:bgGrass",WIDTH/2,HEIGHT/2) ---[[ if ls.x ~= 0 then step = 10 *m.i*ls.x/math.abs(ls.x) else step = 0 end --]] --sprite("Documents:bgGrass",(WIDTH/2 - step)%(WIDTH),HEIGHT/2) --sprite("Documents:bgGrass",(WIDTH - step)%(WIDTH),HEIGHT/2) -- 繪製地圖 myMap:drawMap() -- 繪製角色幀動畫 m:draw(50,80) -- sysInfo() -- 繪製狀態欄 myStatus:drawUI() --myStatus:raderGraph() -- 繪製操縱桿 ls:draw() rs:draw() -- 顯示角色所處網格座標 fill(249, 7, 7, 255) text(ss, 500,100) --sysInfo() popStyle() popMatrix() end -- 處理玩家的觸摸移動 function touched(touch) -- 連續的觸摸數據放入 touches 表中 if touch.state == ENDED then touches[touch.id] = nil else touches[touch.id] = touch -- for k,v in pairs(touches) do print(k,v) end end -- 用於測試修煉 if touch.x > WIDTH/2 and touch.state == ENDED then myStatus:update() end -- 用於測試移動方向:點擊左側向右平移,點擊右側向左平移 if touch.x > WIDTH/2 and touch.state == ENDED then s = -1 elseif touch.x < WIDTH/2 then s = 1 end -- c1,c2 = myMap:where(touch.x, touch.y) c1,c2 = myMap:where(m.x,m.y) -- 顯示角色所處網格座標 ss = c1.." : "..c2 end
其餘模塊都不須要大改動, 除了 Sprites
類須要修改 draw()
裏的一點內容, 修改後代碼爲:工具
function Sprites:draw(w,h) ... -- 肯定每幀子畫面在屏幕上停留的時間 if ElapsedTime > self.prevTime + 0.08 then self.prevTime = self.prevTime + 0.08 self.k = math.fmod(self.i,#self.imgs) self.i = self.i + 1 self.x = self.x + ls.x self.y = self.y + ls.y end ... end
另外兩個模塊直接複製過來就能夠了, 運行截圖以下:開發工具
這裏還錄製了一段操做視頻, 看看是否是很流暢?測試
https://github.com/FreeBlues/Write-A-Adventure-Game-From-Zero/blob/master/assets/c04.mp4
動畫
另外再對狀態
類作一些小改進.
發現文字沒有對齊, 先修改一下, 讓它們對齊, 修改後的代碼以下:
function Status:drawUI() ... local w,h = textSize("體力: ") text("體力: ",30,280) text(math.floor(self.tili), 30 + w, 280) text("內力: ",30,260) text(math.floor(self.neili), 30 + w, 260) text("精力: ",30,240) text(math.floor(self.jingli), 30 + w, 240) text("智力: ",30,220) text(math.floor(self.zhili), 30 + w, 220) text("氣 : ",30,200) text(math.floor(self.qi), 30 + w, 200) text("血 : ",30,180) text(math.floor(self.xue), 30 + w, 180) ... end
爲了更精確地瞭解當前遊戲的幀速FPS
和內存佔用狀況(以便迅速發現內存泄漏), 咱們寫一個小函數:
-- 系統信息 function sysInfo() -- 顯示FPS和內存使用狀況 pushStyle() --fill(0,0,0,105) -- rect(650,740,220,30) fill(255, 255, 255, 255) -- 根據 DeltaTime 計算 fps, 根據 collectgarbage("count") 計算內存佔用 local fps = math.floor(1/DeltaTime) local mem = math.floor(collectgarbage("count")) text("FPS: "..fps.." Mem:"..mem.." KB",650,740) popStyle() end
它能夠顯示當前的 FPS
和內存.
第一次整合後的完整代碼以下:
-- c04.lua function setup() displayMode(OVERLAY) -- 初始化狀態 myStatus = Status() -- 如下爲幀動畫代碼 s = -1 fill(249, 249, 249, 255) imgs = {} pos = {{0,0,110,120},{110,0,70,120},{180,0,70,120},{250,0,70,120}, {320,0,105,120},{423,0,80,120},{500,0,70,120},{570,0,70,120}} img = readImage("Documents:runner") m = Sprites(600,400,img,pos) ---[[ 初始化觸摸搖桿 touches = {} -- cam = Camera(pos.x,pos.y,pos.z,pos.x+look.x,look.y,pos.z+look.z) ls,rs = Stick(20,WIDTH-300,200),Stick(2,WIDTH-120) -- ls,rs = Stick(1),Stick(3,WIDTH-120) -- 初始化地圖 myMap = Maps() ss ="" end function draw() pushMatrix() pushStyle() -- spriteMode(CORNER) rectMode(CORNER) background(32, 29, 29, 255) -- 增長移動的背景圖: + 爲右移,- 爲左移 --sprite("Documents:bgGrass",(WIDTH/2+10*s*m.i)%(WIDTH),HEIGHT/2) --sprite("Documents:bgGrass",(WIDTH+10*s*m.i)%(WIDTH),HEIGHT/2) -- sprite("Documents:bgGrass",WIDTH/2,HEIGHT/2) ---[[ if ls.x ~= 0 then step = 10 *m.i*ls.x/math.abs(ls.x) else step = 0 end --]] --sprite("Documents:bgGrass",(WIDTH/2 - step)%(WIDTH),HEIGHT/2) --sprite("Documents:bgGrass",(WIDTH - step)%(WIDTH),HEIGHT/2) -- 繪製地圖 myMap:drawMap() -- 繪製角色幀動畫 m:draw(50,80) -- sysInfo() -- 繪製狀態欄 myStatus:drawUI() --myStatus:raderGraph() -- 繪製操縱桿 ls:draw() rs:draw() fill(249, 7, 7, 255) text(ss, 500,100) sysInfo() popStyle() popMatrix() end -- 處理玩家的觸摸移動 function touched(touch) -- 連續的觸摸數據放入 touches 表中 if touch.state == ENDED then touches[touch.id] = nil else touches[touch.id] = touch -- for k,v in pairs(touches) do print(k,v) end end -- 用於測試修煉 if touch.x > WIDTH/2 and touch.state == ENDED then myStatus:update() end -- 用於測試移動方向:點擊左側向右平移,點擊右側向左平移 if touch.x > WIDTH/2 and touch.state == ENDED then s = -1 elseif touch.x < WIDTH/2 then s = 1 end -- c1,c2 = myMap:where(touch.x, touch.y) c1,c2 = myMap:where(m.x,m.y) -- 顯示角色所處網格座標 ss = c1.." : "..c2 end -- 系統信息 function sysInfo() -- 顯示FPS和內存使用狀況 pushStyle() --fill(0,0,0,105) -- rect(650,740,220,30) fill(255, 255, 255, 255) -- 根據 DeltaTime 計算 fps, 根據 collectgarbage("count") 計算內存佔用 local fps = math.floor(1/DeltaTime) local mem = math.floor(collectgarbage("count")) text("FPS: "..fps.." Mem:"..mem.." KB",650,740) popStyle() end --# Status -- 角色狀態類 Status = class() function Status:init() -- 體力,內力,精力,智力,氣,血 self.tili = 100 self.neili = 30 self.jingli = 70 self.zhili = 100 self.qi = 100 self.xue = 100 self.gongfa = {t={},n={},j={},z={}} self.img = image(200, 300) end function Status:update() -- 更新狀態:自我修煉,平常休息,戰鬥 self.neili = self.neili + 1 self:xiulian() end function Status:drawUI() pushMatrix() pushStyle() -- rectMode(CENTER) spriteMode(CENTER) textMode(CENTER) setContext(self.img) background(118, 120, 71, 109) fill(35, 112, 111, 114) rect(5,5,200-10,300-10) fill(70, 255, 0, 255) textAlign(RIGHT) local w,h = textSize("體力: ") text("體力: ",30,280) text(math.floor(self.tili), 30 + w, 280) text("內力: ",30,260) text(math.floor(self.neili), 30 + w, 260) text("精力: ",30,240) text(math.floor(self.jingli), 30 + w, 240) text("智力: ",30,220) text(math.floor(self.zhili), 30 + w, 220) text("氣 : ",30,200) text(math.floor(self.qi), 30 + w, 200) text("血 : ",30,180) text(math.floor(self.xue), 30 + w, 180) -- 繪製狀態欄繪製的角色 sprite("Documents:B1", 100,90) -- m:draw(150,200) setContext() -- 在狀態欄繪製雷達圖 self:raderGraph() -- 繪製狀態欄 sprite(self.img, self.img.width/2,HEIGHT-self.img.height/2) ---[[ 測試代碼 fill(143, 255, 0, 255) rect(WIDTH*7/8,HEIGHT/2,100,80) fill(0, 55, 255, 255) text("修煉", WIDTH*7/8 +50,HEIGHT/2+40) --]] popStyle() popMatrix() end function Status:xiulian() -- 修煉基本內功先判斷是否知足修煉條件: 體力,精力大於50,修煉一次要消耗一些 if self.tili >= 50 and self.jingli >= 50 then self.neili = self.neili * (1+.005) self.tili = self.tili * (1-.001) self.jingli = self.jingli * (1-.001) end end -- 角色技能雷達圖 function Status:raderGraph() pushMatrix() pushStyle() setContext(self.img) fill(60, 230, 30, 255) -- 中心座標,半徑,角度 local x0,y0,r,a,s = 150,230,40,360/6,4 -- 計算右上方斜線的座標 local x,y = r* math.cos(math.rad(30)), r* math.sin(math.rad(30)) p = {"體力","內力","精力","智力","氣","血"} axis = {t={vec2(0,r/s),vec2(0,r*2/s),vec2(0,r*3/s),vec2(0,r)}, n={vec2(-x/s,y/s),vec2(-x*2/s,y*2/s),vec2(-x*3/s,y*3/s),vec2(-x,y)}, j={vec2(-x/s,-y/s),vec2(-x*2/s,-y*2/s),vec2(-x*3/s,-y*3/s),vec2(-x,-y)}, z={vec2(0,-r/s),vec2(0,-r*2/s),vec2(0,-r*3/s),vec2(0,-r)}, q={vec2(x/s,-y/s),vec2(x*2/s,-y*2/s),vec2(x*3/s,-y*3/s),vec2(x,-y)}, x={vec2(x/s,y/s),vec2(x*2/s,y*2/s),vec2(x*3/s,y*3/s),vec2(x,y)}} -- 用於繪製圈線的函數,固定 4 個點 function lines(t,n,j,z,q,x) line(axis.n[n].x, axis.n[n].y, axis.t[t].x, axis.t[t].y) line(axis.n[n].x, axis.n[n].y, axis.j[j].x, axis.j[j].y) line(axis.x[x].x, axis.x[x].y, axis.t[t].x, axis.t[t].y) line(axis.z[z].x, axis.z[z].y, axis.j[j].x, axis.j[j].y) line(axis.x[x].x, axis.x[x].y, axis.q[q].x, axis.q[q].y) line(axis.z[z].x, axis.z[z].y, axis.q[q].x, axis.q[q].y) --print(axis.z[z].y) end -- 實時繪製位置,實時計算位置 function linesDynamic(t,n,j,z,q,x) local t,n,j,z,q,x = self.tili, self.neili, self.jingli,self.zhili, self.qi, self.xue local fm = math.fmod -- t,n,j,z,q,x = fm(t,r),fm(n,r),fm(j,r),fm(z,r),fm(q,r),fm(x,r) -- print(t,n,j,z,q,x) local c,s = math.cos(math.rad(30)), math.sin(math.rad(30)) line(0,t,-n*c,n*s) line(-n*c,n*s,-j*c,-j*s) line(0,-z,-j*c,-j*s) line(0,-z,q*c,-q*s) line(q*c,-q*s,x*c,x*s) line(0,t,x*c,x*s) end -- 平移到中心 (x0,y0), 方便以此爲中心旋轉 translate(x0,y0) -- 圍繞中心點勻速旋轉 rotate(30+ElapsedTime*10) fill(57, 121, 189, 84) strokeWidth(0) ellipse(0,0,2*r/s) ellipse(0,0,4*r/s) ellipse(0,0,6*r/s) ellipse(0,0,r*2) strokeWidth(2) -- noSmooth() stroke(93, 227, 22, 255) fill(60, 230, 30, 255) -- 繪製雷達圖 for i=1,6 do text(p[i],0,45) line(0,0,0,r) rotate(a) end -- 繪製圈線 stroke(255, 0, 0, 102) strokeWidth(2) for i = 1,4 do lines(i,i,i,i,i,i) end function values() local t,n,j,z,q,x = self.tili, self.neili, self.jingli,self.zhili, self.qi, self.xue local f = math.floor -- return math.floor(t/25),math.floor(t/25),math.floor(t/25),math.floor(t/25),math.floor(t/25),math.floor(t/25) return f(t/25),f((25+math.fmod(n,100))/25),f(j/25),f(z/25),f(q/25),f(x/25) end stroke(255, 32, 0, 255) strokeWidth(2) smooth() -- 設定當前各參數的值 -- print(values()) local t,n,j,z,q,x = 3,2,3,2,4,1 local t,n,j,z,q,x = values() -- local t,n,j,z,q,x = self.tili, self.neili, self.jingli,self.zhili, self.qi, self.xue lines(t,n,j,z,q,x) linesDynamic(t,n,j,z,q,x) setContext() popStyle() popMatrix() end --# Sprites -- 幀動畫對象類 Sprites = class() function Sprites:init(x,y,img,pos) self.x = x self.y = y -- self.index = 1 self.img = img self.imgs = {} self.pos = pos self.i=0 self.k=1 self.q=0 self.prevTime =0 -- 處理原圖,背景色變爲透明 self:deal() -- 使用循環,把各個子幀存入表中 for i=1,#self.pos do -- imgs[i] = img:copy(pos[i][1],pos[i][2],pos[i][3],pos[i][4]) self.imgs[i] = self.img:copy(table.unpack(self.pos[i])) end end function Sprites:deal() ---[[ 對原圖進行預處理,把背景修改成透明,現存問題:角色內部有白色也會被去掉 local v = 255 for x=1,self.img.width do for y =1, self.img.height do -- 取出全部像素的顏色值 local r,g,b,a = self.img:get(x,y) -- if r >= v and g >= v and b >= v then if r == v and g == v and b == v and a == v then self.img:set(x,y,r,g,b,0) end end end --]] end function Sprites:draw(w,h) pushMatrix() pushStyle() -- 繪圖模式選 CENTER 能夠保證畫面中的動畫角色不會左右漂移 rectMode(CENTER) spriteMode(CENTER) -- 肯定每幀子畫面在屏幕上停留的時間 if ElapsedTime > self.prevTime + 0.08 then self.prevTime = self.prevTime + 0.08 self.k = math.fmod(self.i,#self.imgs) self.i = self.i + 1 self.x = self.x + ls.x self.y = self.y + ls.y end self.q=self.q+1 -- rect(800,500,120,120) -- rotate(30) -- sprite(self.imgs[self.k+1],self.i*10%WIDTH+100,HEIGHT/6,HEIGHT/8,HEIGHT/8) --sprite(imgs[math.fmod(q,8)+1],i*10%WIDTH+100,HEIGHT/6,HEIGHT/8,HEIGHT/8) -- sprite(self.imgs[self.k+1], self.x, self.y,150,200) sprite(self.imgs[self.k+1], self.x, self.y, w or 30, h or 50) popStyle() popMatrix() -- sprite(imgs[self.index], self.x, self.y) end --# Maps Maps = class() function Maps:init() --[[ gridCount:網格數目,範圍:1~100,例如,設爲3則生成3*3的地圖,設爲100,則生成100*100的地圖。 scaleX:單位網格大小比例,範圍:1~100,該值越小,則單位網格越小;該值越大,則單位網格越大。 scaleY:同上,若與scaleX相同則單位網格是正方形格子。 plantSeed:植物生成概率,範圍:大於4的數,該值越小,生成的植物越多;該值越大,生成的植物越少。 minerialSeed:礦物生成概率,範圍:大於3的數,該值越小,生成的礦物越多;該值越大,生成的礦物越少。 --]] self.gridCount = 50 self.scaleX = 50 self.scaleY = 50 self.plantSeed = 20.0 self.minerialSeed = 50.0 -- 根據地圖大小申請圖像 local w,h = (self.gridCount+1)*self.scaleX, (self.gridCount+1)*self.scaleY self.imgMap = image(w,h) -- 整個地圖使用的全局數據表 self.mapTable = {} -- 設置物體名稱 tree1,tree2,tree3 = "松樹", "楊樹", "小草" mine1,mine2 = "鐵礦", "銅礦" -- 設置物體圖像 imgTree1 = readImage("Planet Cute:Tree Short") imgTree2 = readImage("Planet Cute:Tree Tall") imgTree3 = readImage("Platformer Art:Grass") imgMine1 = readImage("Platformer Art:Mushroom") imgMine2 = readImage("Small World:Treasure") -- 存放物體: 名稱,圖像 self.itemTable = {[tree1]=imgTree1,[tree2]=imgTree2,[tree3]=imgTree3,[mine1]=imgMine1,[mine2]=imgMine2} -- 尺寸爲 3*3 的數據表示例 self.mapTable = {{pos=vec2(1,1),plant=nil,mineral=mine1},{pos=vec2(1,2),plant=nil,mineral=nil}, {pos=vec2(1,3),plant=tree3,mineral=nil},{pos=vec2(2,1),plant=tree1,mineral=nil}, {pos=vec2(2,2),plant=tree2,mineral=mine2},{pos=vec2(2,3),plant=nil,mineral=nil}, {pos=vec2(3,1),plant=nil,mineral=nil},{pos=vec2(3,2),plant=nil,mineral=mine2}, {pos=vec2(3,3),plant=tree3,mineral=nil}} print("地圖初始化開始...") -- 根據初始參數值新建地圖 self:createMapTable() print("OK, 地圖初始化完成! ") end -- 新建地圖數據表, 插入地圖上每一個格子裏的物體數據 function Maps:createMapTable() --local mapTable = {} for i=1,self.gridCount,1 do for j=1,self.gridCount,1 do self.mapItem = {pos=vec2(i,j), plant=self:randomPlant(), mineral=self:randomMinerial()} --self.mapItem = {pos=vec2(i,j), plant=nil, mineral=nil} table.insert(self.mapTable, self.mapItem) end end self:updateMap() end -- 根據地圖數據表, 刷新地圖 function Maps:updateMap() setContext(self.imgMap) for i = 1,self.gridCount*self.gridCount,1 do local pos = self.mapTable[i].pos local plant = self.mapTable[i].plant local mineral = self.mapTable[i].mineral -- 繪製地面 self:drawGround(pos) -- 繪製植物和礦物 if plant ~= nil then self:drawTree(pos, plant) end if mineral ~= nil then self:drawMineral(pos, mineral) end end setContext() end function Maps:drawMap() -- sprite(self.imgMap,-self.scaleX,-self.scaleY) sprite(self.imgMap,0,0) end -- 根據像素座標值計算所處網格的 i,j 值 function Maps:where(x,y) local i = math.ceil((x+self.scaleX) / self.scaleX) local j = math.ceil((y+self.scaleY) / self.scaleY) return i,j end -- 隨機生成植物 function Maps:randomPlant() local seed = math.random(1.0, self.plantSeed) local result = nil if seed >= 1 and seed < 2 then result = tree1 elseif seed >= 2 and seed < 3 then result = tree2 elseif seed >= 3 and seed < 4 then result = tree3 elseif seed >= 4 and seed <= self.plantSeed then result = nil end return result end -- 隨機生成礦物 function Maps:randomMinerial() local seed = math.random(1.0, self.minerialSeed) local result = nil if seed >= 1 and seed < 2 then result = mine1 elseif seed >= 2 and seed < 3 then result = mine2 elseif seed >= 3 and seed <= self.minerialSeed then result = nil end return result end function Maps:getImg(name) return self.itemTable[name] end -- 重置 function Maps:resetMapTable() self.mapTable = self:createMapTable() end -- 繪製單位格子地面 function Maps:drawGround(position) local x,y = self.scaleX * position.x, self.scaleY * position.y pushMatrix() stroke(99, 94, 94, 255) strokeWidth(1) fill(5,155,40,255) -- fill(5,155,240,255) rect(x,y,self.scaleX,self.scaleY) --sprite("Documents:3D-Wall",x,y,scaleX,scaleY) popMatrix() end -- 繪製單位格子內的植物 function Maps:drawTree(position,plant) local x,y = self.scaleX * position.x, self.scaleY * position.y pushMatrix() -- 繪製植物圖像 sprite(self.itemTable[plant],x,y,self.scaleX*6/10,self.scaleY) --fill(100,100,200,255) --text(plant,x,y) popMatrix() end -- 繪製單位格子內的礦物 function Maps:drawMineral(position,mineral) local x,y = self.scaleX * position.x, self.scaleY * position.y pushMatrix() -- 繪製礦物圖像 sprite(self.itemTable[mineral],x+self.scaleX/2,y,self.scaleX/2,self.scaleX/2) --fill(100,100,200,255) --text(mineral,x+self.scaleX/2,y) popMatrix() end --# Stick -- 操縱桿類, 做者: @Jaybob Stick = class() function Stick:init(ratio,x,y,b,s) self.ratio = ratio or 1 self.i = vec2(x or 120,y or 120) self.v = vec2(0,0) self.b = b or 180 --大圓半徑 self.s = s or 100 --小圓半徑 self.d = d or 50 self.a = 0 self.touchId = nil self.x,self.y = 0,0 end function Stick:draw() -- 沒有 touched 函數的 Stick 類是如何找到本身對應的觸摸數據的?根據點擊處座標跟操縱桿的距離來判斷 if touches[self.touchId] == nil then -- 循環取出 touches 表內的數據,比較其座標跟操縱桿的距離,若小於半徑則說明是在點擊操縱桿 for i,t in pairs(touches) do if vec2(t.x,t.y):dist(self.i) < self.b/2 then self.touchId = i end end self.v = vec2(0,0) else -- 根據對應於操縱桿的觸摸的xy座標設置 self.v,再根據它計算夾角 self.a self.v = vec2(touches[self.touchId].x,touches[self.touchId].y) - self.i self.a = math.deg(math.atan2(self.v.y, self.v.x)) end -- 根據 self.v 和 self.b 計算獲得 self.t self.t = math.min(self.b/2,self.v:len()) if self.t >= self.b/2 then self.v = vec2(math.cos(math.rad(self.a))*self.b/2,math.sin(math.rad(self.a))*self.b/2) end pushMatrix() fill(127, 127, 127, 100) -- 分別繪製大圓,小圓 ellipse(self.i.x, self.i.y, self.b) ellipse(self.i.x+self.v.x, self.i.y+self.v.y, self.s) --print(self.v.x, self.s) popMatrix() -- 根據 ratio 從新設置 self.v/self.t self.v = self.v/(self.b/2)*self.ratio self.t = self.t/(self.b/2)*self.ratio -- 根據 self.v/self.t,從新設置 self.x/self.y self.x, self.y = self.v.x, self.v.y end
看一看 Github
上對咱們目前成果的代碼行統計數據:
563 lines (480 sloc) 16.7 KB
也就是說去掉註釋和空行的有效代碼行是 480
行, 用這短短的不到 500
行的代碼, 咱們就搭建起一個武俠冒險遊戲的世界. 不得不說咱們的開發工具 Codea
特別適合在 iPad
上作原型.
本章的內容比較少, 主要是把前面幾個模塊整合到一塊兒, 之因此專門用一章來寫這個, 緣由是我須要思考一下後續的開發該怎麼作, 沒錯, 這個遊戲開發項目是我一時心血來潮開始寫的, 基本上沒有專門去作什麼需求分析, 概要設計, 詳細設計什麼的, 而是從想法出發, 從一個個最簡單的原型起步, 想到哪裏寫到哪裏, 因而這麼順順利利地就把一個小框架搭起來了.
須要說明的一點是, 這種原型開發法不太適合大型項目, 不過很是適合我的開發者或者超小型團隊(程序員<=2), 尤爲適合那些有一個想法, 特別想作出個大概樣子來驗證驗證的開發者.
OK, 本章先寫這麼多, 我先去想一想後面怎麼作, 再來繼續.
從零開始寫一個武俠練功遊戲-1-狀態原型
從零開始寫一個武俠練功遊戲-2-幀動畫
從零開始寫一個武俠練功遊戲-3-地圖生成
從零開始寫一個武俠冒險遊戲-4-第一次整合