用 mesh
改寫地圖類, 帶來的一大好處是控制邏輯能夠變得很是簡單, 做爲一個地圖類, 最基本的控制邏輯就是顯示哪一部分和地圖如何捲動, 而這兩點能夠經過 mesh
的紋理貼圖很是容易地解決, 由於在 OpenGL ES 2.0/3.0
中, 能夠經過設置紋理座標來決定如何在地圖上顯示紋理貼圖, 而這些控制邏輯若是不用 mesh
, 本身去寫, 就有些繁瑣了, 不信你能夠試試.git
另外咱們以前實現的地圖類的地圖繪製是極其簡陋的, 好比地面就是一些單色的矩形塊, 本章咱們將會把很小的紋理貼圖素材拼接起來生成更具表現力和真實感的地面.github
基於 OpenGL ES 2.0/3.0
的紋理貼圖特性, 咱們既可使用一塊很小的紋理, 而後用拼圖的方式把大屏幕鋪滿, 也可使用一塊很大的超出屏幕範圍的圖片作紋理, 而後選擇其中一個尺寸跟屏幕尺寸至關的區域來顯示.編程
在本章中, 這兩種方法都會用到, 前者用來生成一張大大的地圖, 後者用來顯示這塊大地圖的局部區域.數組
地圖類的處理相對來講複雜一些, 正如咱們在 概述
中提到的, 要在兩個層面使用 mesh
, 第一層是用小素材紋理經過拼圖的方式生成一張超過屏幕尺寸的大地圖圖片, 第二層是把這張大地圖圖片做爲紋理素材, 經過紋理座標的設置來從大地圖圖片素材中選擇一個尺寸恰好是屏幕大小的區域, 而後把它顯示在屏幕上.框架
由於咱們是前面的基礎上改寫, 也就是說用來生成大地圖圖片的代碼已經寫好了, 因此咱們能夠選擇先從簡單的開始, 那就是先實現第二層面: 用大圖片做爲紋理貼圖, 利用 mesh
的紋理座標來實現顯示小區域和地圖捲動等功能.dom
具體辦法就是先在初始化函數 Maps:init()
中用 mesh:addRect()
新建一個屏幕大小的矩形, 而後加載已經生成的大地圖圖片做爲紋理貼圖, 再經過設置紋理座標 mesh:setRectTex(i, x, y, w, t)
取得對應於紋理貼圖上的一塊屏幕大小的區域; 而後再在 Maps:drawMap()
函數中根據角色移動來判斷是否須要捲動地圖, 以及若是須要捲動向哪一個方向捲動, 最後在 Maps:touched(touch)
函數中把紋理座標的 (x, y)
跟觸摸數據關聯起來, 這樣咱們屏幕上顯示的地圖就會隨着角色移動到屏幕邊緣而自動把新地圖平移過來.函數
在初始化函數 Maps:init()
中主要是這些處理:性能
w,h
,self.imgMap
, 咱們的大地圖就要繪製在這個圖形對象上,self.x, self.y
, 這裏把大地圖的左下角座標設爲 (0,0)
,mesh
對象 self.m
,self.m
上新增一個矩形, 該矩形中心座標爲 (WIDTH/2, HEIGHT/2)
, 寬度爲 WIDTH
, 高度爲 HEIGHT
, 也就是一個跟屏幕同樣大的矩形,self.imgMap
設爲 self.m
的紋理貼圖,[0,1]
, 因此須要咱們把座標的絕對數值轉換爲 [0,1]
區間內的相對數值, 也就是用屏幕寬高除以大地圖的寬高 local u,v = WIDTH/w, HEIGHT/h
mesh:setRectTex()
設置進去就是下面這些代碼:測試
... -- 根據地圖大小申請圖像 local w,h = (self.gridCount+1)*self.scaleX, (self.gridCount+1)*self.scaleY self.imgMap = image(w,h) -- 使用 mesh 繪製地圖 -- 設置當前位置爲矩形中心點的絕對數值,分別除以 w, h 能夠獲得相對數值 self.x, self.y = w/2-WIDTH/2, h/2-HEIGHT/2 self.m = mesh() self.mi = self.m:addRect(WIDTH/2, HEIGHT/2, WIDTH, HEIGHT) self.m.texture = self.imgMap -- 利用紋理座標設置顯示區域,根據中心點座標計算出左下角座標,除以紋理寬度獲得相對值,w h 使用固定值(小於1) local u,v = WIDTH/w, HEIGHT/h self.m:setRectTex(self.mi, self.x/w, self.y/h, u, v) ...
在繪製函數 Maps:drawMap()
中要作這些處理:動畫
myS
當前所在的座標 (myS.x, myS.y)
是否是已經處於地圖邊緣, 若是是則開始切換地圖(也就是把地圖捲動過來), 切換的辦法就是給地圖的紋理座標的起始點一個增量操做,self.x = self.x - WIDTH/1000
,self.x = self.x + WIDTH/1000
,self.y = self.y + HEIGHT/1000
,self.y = self.y - HEIGHT/1000
,w,h
獲得位於 [0,1]
區間內的座標的相對值,self.m:setRectTex()
的參數.代碼是這些:
... -- 更新紋理貼圖, --若是地圖上的物體有了變化 self.m.texture = self.imgMap local w,h = self.imgMap.width, self.imgMap.height local u,v = WIDTH/w, HEIGHT/h -- 增長判斷,若角色移動到邊緣則切換地圖:經過修改貼圖座標來實現 print(self.x,self.y) local left,right,top,bottom = WIDTH/10, WIDTH*9/10, HEIGHT/10, HEIGHT*9/10 local ss = 800 if myS.x <= left then self.x= self.x - WIDTH/ss end if myS.x >= right then self.x= self.x + WIDTH/ss end if myS.y <= bottom then self.y = self.y - HEIGHT/ss end if myS.y >= top then self.y = self.y + HEIGHT/ss end -- 根據計算獲得的數據從新設置紋理座標 self.m:setRectTex(self.mi, self.x/w, self.y/h, u, v) ...
另外, 咱們使用了一個局部變量 local ss = 800
來控制屏幕捲動的速度, 由於考慮到玩家角色可能行走, 也可能奔跑, 而咱們這是一個武俠遊戲, 可能會設置 輕功
之類的技能, 這樣當角色以不一樣速度運動到屏幕邊緣時, 地圖捲動的速度也各不相同, 看起來真實感更強一些.
補充說明一點, 爲方便編程, 咱們使用的 self.x, self.y
都用了絕對數值, 可是在函數 self.m:setRectTex()
中須要的是相對數值, 因此做爲參數使用時都須要除以 w, h
, 這裏我在調程序的時候也犯過幾回暈.
在函數 Maps:touched(touch)
中, 把觸摸位置座標 (touch.x, touch.y)
跟玩家角色座標 (myS.x, myS.y)
創建關聯, 這裏這麼寫主要是爲了方便咱們如今調試用.
代碼很簡單:
if touch.state == BEGAN then myS.x, myS.y = touch.x, touch.y end
另外還須要在 setup()
函數中設置一下 (myS.x, myS.y)
的初值, 讓它們位於屏幕中央就能夠了.
myS.x, myS.y = WIDTH/2, HEIGHT/2
完整代碼以下:
-- c06-02.lua -- 主程序框架 function setup() displayMode(OVERLAY) myS = {} myS.x, myS.y = WIDTH/2, HEIGHT/2 myMap = Maps() myMap:createMapTable() end function draw() background(40, 40, 50) -- 繪製地圖 myMap:drawMap() sysInfo() end function touched(touch) myMap:touched(touch) end -- 使用 mesh() 繪製地圖 Maps = class() function Maps:init() self.gridCount = 100 self.scaleX = 40 self.scaleY = 40 self.plantSeed = 20.0 self.minerialSeed = 50.0 -- 根據地圖大小申請圖像 local w,h = (self.gridCount+1)*self.scaleX, (self.gridCount+1)*self.scaleY -- print(w,h) self.imgMap = image(w,h) -- 使用 mesh 繪製地圖 -- 設置當前位置爲矩形中心點的絕對數值,分別除以 w, h 能夠獲得相對數值 self.x, self.y = (w/2-WIDTH/2), (h/2-HEIGHT/2) self.m = mesh() self.mi = self.m:addRect(WIDTH/2, HEIGHT/2, WIDTH, HEIGHT) self.m.texture = self.imgMap -- 利用紋理座標設置顯示區域,根據中心點座標計算出左下角座標,除以紋理寬度獲得相對值,w h 使用固定值(小於1) local u,v = WIDTH/w, HEIGHT/h self.m:setRectTex(self.mi, self.x/w, self.y/h, u, v) -- 整個地圖使用的全局數據表 self.mapTable = {} -- 設置物體名稱 tree1,tree2,tree3 = "松樹", "楊樹", "小草" mine1,mine2 = "鐵礦", "銅礦" -- 後續改用表保存物體名稱 self.trees = {"松樹", "楊樹", "小草"} self.mines = {"鐵礦", "銅礦"} -- 設置物體圖像 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() end function Maps:drawMap() -- sprite(self.imgMap,-self.scaleX,-self.scaleY) -- sprite(self.imgMap,0,0) -- 更新紋理貼圖, --若是地圖上的物體有了變化 self.m.texture = self.imgMap local w,h = self.imgMap.width, self.imgMap.height local u,v = WIDTH/w, HEIGHT/h -- 增長判斷,若角色移動到邊緣則切換地圖:經過修改貼圖座標來實現 -- print(self.x,self.y) local left,right,top,bottom = WIDTH/10, WIDTH*9/10, HEIGHT/10, HEIGHT*9/10 local ss = 800 if myS.x <= left then self.x= self.x - WIDTH/ss end if myS.x >= right then self.x= self.x + WIDTH/ss end if myS.y <= bottom then self.y = self.y - HEIGHT/ss end if myS.y >= top then self.y = self.y + HEIGHT/ss end -- 根據計算獲得的數據從新設置紋理座標 self.m:setRectTex(self.mi, self.x/w, self.y/h, u, v) -- self:updateMap() self.m:draw() end function Maps:touched(touch) if touch.state == BEGAN then myS.x, myS.y = touch.x, touch.y end 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) -- myT:switchPoint(myT.taskID) end end print("OK, 地圖初始化完成! ") self:updateMap() end -- 根據地圖數據表, 刷新地圖,比較耗時,能夠考慮使用協程,每 1 秒內花 1/60 秒來執行它; -- 協程還可用來實現時間系統,氣候變化,植物生長,它賦予咱們操縱遊戲世界運行流程的能力(至關於控制時間變化) -- 或者不用循環,只執行改變的物體,傳入網格座標 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:touched(touch) if touch.state == BEGAN then myS.x, myS.y = touch.x, touch.y end 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
如今開始把第一層面改寫爲用 mesh
繪圖, 也就是說以 mesh
方式來生成大地圖, 具體來講就是改寫這些函數:
Maps:updateMap()
負責把全部的繪製函數整合起來, 繪製出整副地圖Maps:drawGround()
負責繪製單位格子地面Maps:drawTree()
負責繪製單位格子內的植物Maps:drawMineral()
負責繪製單位格子內的礦物這裏稍微麻煩一些, 由於咱們打算用小紋理貼圖來拼接, 因此一旦小紋理肯定, 那麼這些屬性就不須要顯式指定了:
self.scaleX
= 40self.scaleY
= 40它們實際上就是小紋理貼圖的 寬度
和 高度
, 假設使用名爲 tex
的小紋理, 那麼這兩個值就分別是 tex.width
和 tex.height
, 雖然咱們通常提倡使用正方形的紋理, 不過這裏仍是區分了 寬度
和 高度
.
而矩形的大小, 則能夠經過屬性 self.gridCount = 100
來設定須要用到多少塊小紋理, 這裏設置的是 100
, 表示橫向使用 100
塊小紋理, 縱向使用 100
塊小紋理.
看起來此次改寫涉及的地方比較多.
這裏仍是經過 mesh
的紋理貼圖功能來實現, 不過跟在第一層面的用法不一樣, 這裏咱們會使用很小的紋理貼圖, 好比大小爲 50*50
像素單位, 經過紋理座標的設置和 shader
把它們拼接起來鋪滿整個地圖, 之因此要用到 shader
, 是由於在這裏, 咱們提供紋理座標的取值大於 [0,1]
的範圍, 必須在 shader
中對紋理座標作一個轉換, 讓它們從新落回到 [0,1]
的區間.
好比假設咱們程序提供的紋理座標是 (23.4, 20.8)
, 前面的整數部分 (23, 20)
表明的都是整塊的紋理圖, 至關於橫向有 23
個貼圖, 縱向有 20
個貼圖, 那麼剩下的小數部分 (0.4, 0.8)
就會落在一塊小紋理素材圖內, 這個 (0.4, 0.8)
纔是咱們真正要取的點.
咱們先從地面開始, 先新建一個名爲 m1
的 mesh
, 接着在這個 mesh
上新建一個大大的矩形, 簡單來講就是跟咱們的地圖同樣大, 再加載一個尺寸較小的地面紋理貼圖, 經過紋理座標的設置和 shader
的處理把它以拼圖的方式鋪滿整個矩形, 最後用函數 m1:draw()
把它繪製到 self.img
上, 不過爲方便調試, 咱們先臨時增長一個屬性 self.img1
, 全部改寫部分先在它上面繪製, 調試無誤後再繪製到 self.imgMap1
上.
初始化函數 Maps:init()
中須要增長的代碼
-- 使用 mesh 繪製第一層面的地圖 self.m1 = mesh() self.m1.texture = readImage("Documents:3D-Wall") local tw,th = self.m1.texture.width, self.m1.texture.height local mw,mh = (self.gridCount+1)*tw, (self.gridCount+1)*th -- 臨時調試用, 調試經過後刪除 self.imgMap1 = image(mw, mh) -- local ws,hs = WIDTH/tw, HEIGHT/th local ws,hs = mw/tw, mh/th print(ws,hs) self.m1i = self.m1:addRect(mw/2, mh/2, mw, mh) self.m1:setRectTex(self.m1i, 1/2, 1/2, ws, hs) -- 使用拼圖 shader self.m1.shader = shader(shaders["maps"].vs,shaders["maps"].fs)
由於須要修改的地方較多, 爲避免引入新問題, 因此保留原來的處理, 臨時增長几個函數, 專門用於調試:
-- 臨時調試用 function Maps:updateMap1() setContext(self.imgMap) m1:draw() setContext() end
另外須要在增長一個專門用於拼圖的 shader
, 把小塊紋理圖拼接起來鋪滿:
-- Shader shaders = { maps = { vs=[[ // 拼圖着色器: 把小紋理素材拼接起來鋪滿整個屏幕 //--------vertex shader--------- attribute vec4 position; attribute vec4 color; attribute vec2 texCoord; varying vec2 vTexCoord; varying vec4 vColor; uniform mat4 modelViewProjection; void main() { vColor = color; vTexCoord = texCoord; gl_Position = modelViewProjection * position; } ]], fs=[[ //---------Fragment shader------------ //Default precision qualifier precision highp float; varying vec2 vTexCoord; varying vec4 vColor; // 紋理貼圖 uniform sampler2D texture; void main() { vec4 col = texture2D(texture,vec2(mod(vTexCoord.x,1.0), mod(vTexCoord.y,1.0))); gl_FragColor = vColor * col; } ]]} }
原來咱們的 mapTable
是一個一維數組, 如今把它改成二維數組, 這樣在知道一個網格的座標 i, j
後能夠很快地查找出該網格在數據表中的信息 mapTable[i][j]
, 很是方便對地圖中的物體(植物/礦物)進行操做, 首先是改寫地圖數據表生成函數 Maps:createMapTable()
, 這裏須要注意的一點是 用 Lua
的 table
實現二維數組時, 須要顯示地建立每一行, 改成以下:
function Maps:createMapTable() --local mapTable = {} for i=1,self.gridCount,1 do self.mapTable[i] = {} for j=1,self.gridCount,1 do self.mapItem = {pos=vec2(i,j), plant=self:randomPlant(), mineral=self:randomMinerial()} table.insert(self.mapTable[i], self.mapItem) -- self.mapTable[i][j] = self.mapItem -- myT:switchPoint(myT.taskID) end end print("OK, 地圖初始化完成! ") self:updateMap1() end
也能夠這樣 self.mapTable[i][j] = self.mapItem
來爲數組的每一個位置賦值.
修改了數據表結構後, 不少針對數據表的相關操做也要作對應修改, 如 Maps:updateMap()
函數:
function Maps:updateMap() setContext(self.imgMap) -- 用 mesh 繪製地面 self.m1:draw() -- 用 sprite 繪製植物,礦物,建築 for i = 1,self.gridCount,1 do for j=1,self.gridCount,1 do local pos = self.mapTable[i][j].pos local plant = self.mapTable[i][j].plant local mineral = self.mapTable[i][j].mineral -- 繪製植物和礦物 if plant ~= nil then self:drawTree(pos, plant) end if mineral ~= nil then self:drawMineral(pos, mineral) end end end setContext() end
還有其餘幾個函數就不一一列舉了, 由於修改的地方很清晰.
這個遊戲程序寫了這麼久了, 玩家控制的角色尚未真正對地圖上的物體作過交互, 這裏咱們增長几個用於操做地圖上物體的函數:
首先提供一個查看對應網格信息的函數 Maps:showGridInfo()
:
function Maps:showGridInfo(i,j) local item = self.mapTable[i][j] print(item.pos, item.tree, item.mineral) if item.tree ~= nil then fill(0,255,0,255) text(item.pos.."位置處有: "..item.tree.." 和 ..", 500,200) end end
而後是一個刪除物體的函數 Maps:removeMapObject()
:
function Maps:removeMapObject(i,j) local item = self.mapTable[i][j] if item.pos == vec2(i,j) then item.plant = nil item.mineral = nil end end
咱們以前寫過一個根據座標數值換算對應網格座標的函數 ``, 如今須要改寫一下, 把計算單位換成小紋理貼圖的寬度和高度:
function Maps:where(x,y) local w, h = self.m1.texture.width, self.m1.texture.height local i, j = math.ceil(x/w), math.ceil(y/h) return i,j end
還存在點小問題, 精度須要提高, 後續改進.
要修改函數 Maps:drawTree()
, 原來是根據 self.scaleX, self.scaleY
和網格座標 i, j
來計算繪製到哪一個格子上的, 如今由於地面改用 mesh
的紋理貼圖繪製, 因此就要用地面紋理貼圖的 width, height
來計算了.
-- 臨時調試用 function Maps:drawTree(position,plant) local w, h = self.m1.texture.width, self.m1.texture.height local x, y = w * position.x, h * position.y print("tree:"..x..y) pushMatrix() -- 繪製植物圖像 sprite(self.itemTable[plant],x,y,w*6/10,h) popMatrix() end
一樣須要修改的還有 Maps:drawMineral()
函數:
function Maps:drawMineral(position,mineral) local w, h = self.m1.texture.width, self.m1.texture.height local x, y = w * position.x, h * position.y pushMatrix() -- 繪製礦物圖像 sprite(self.itemTable[mineral], x+w/2, y , w/2, h/2) --fill(100,100,200,255) --text(mineral,x+self.scaleX/2,y) popMatrix() end
通過上面這些改動, 基本上是完成了, 不過刪除地圖上的物體後, 須要重繪地圖, 若是把數據表 mapTable
全都遍歷一遍, 至關於整副地圖都重繪一遍, 顯然沒這個必要, 因此咱們打算只重繪那些被刪除了物體的網格, 由於知道確切座標, 因此咱們能夠用這樣一個函數來實現:
--局部重繪函數 function Maps:updateItem(i,j) setContext(self.imgMap) local x,y = i * self.m1.texture.width, j * self.m1.texture.height sprite(self.m1.texture, x, y) setContext() self.m.texture = self.imgMap end
-- c06-02.lua -- 主程序框架 function setup() displayMode(OVERLAY) -- 角色位置,用於調試 myS = {} myS.x, myS.y = WIDTH/2, HEIGHT/2 -- 生成地圖 myMap = Maps() myMap:createMapTable() print("左下角在地圖的座標:"..myMap.x,myMap.y) local i,j = myMap:where(myMap.x,myMap.y) print("左下角對應網格座標:"..i.." : "..j) -- print(myMap.mapTable[9][10].pos, myMap.mapTable[9][10].plant) -- 測試格子座標計算 ss = "" end function draw() background(40, 40, 50) -- 繪製地圖 myMap:drawMap() sysInfo() -- 顯示點擊處的格子座標 fill(255, 0, 14, 255) -- text(ss,500,100) end function touched(touch) myMap:touched(touch) if touch.state == ENDED then c1,c2 = myMap:where(myMap.x + touch.x, myMap.y + touch.y) myMap:showGridInfo(c1,c2) myMap:removeMapObject(c1,c2) print("點擊處的座標絕對值:", (myMap.x + touch.x)/200, (myMap.y + touch.y)/200) print("c1:c2 "..c1.." : "..c2) ss = c1.." : "..c2 end end -- 系統信息: 顯示FPS和內存使用狀況 function sysInfo() pushStyle() 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 -- 使用 mesh() 繪製地圖 Maps = class() function Maps:init() self.gridCount = 20 self.scaleX = 200 self.scaleY = 200 self.plantSeed = 20.0 self.minerialSeed = 50.0 -- 根據地圖大小申請圖像,scaleX 可實現縮放物體 --local w,h = (self.gridCount+1)*self.scaleX, (self.gridCount+1)*self.scaleY local w,h = (self.gridCount+0)*self.scaleX, (self.gridCount+0)*self.scaleY print("大地圖尺寸: ",w,h) self.imgMap = image(w,h) -- 使用 mesh 繪製第一層面的地圖地面 self.m1 = mesh() self.m1.texture = readImage("Documents:hm1") local tw,th = self.m1.texture.width, self.m1.texture.height local mw,mh = (self.gridCount+1)*tw, (self.gridCount+1)*th -- 臨時調試用, 調試經過後刪除 self.imgMap1 = image(mw, mh) -- local ws,hs = WIDTH/tw, HEIGHT/th local ws,hs = mw/tw, mh/th print("網格數目: ",ws,hs) self.m1i = self.m1:addRect(mw/2, mh/2, mw, mh) self.m1:setRectTex(self.m1i, 1/2, 1/2, ws, hs) -- 使用拼圖 shader self.m1.shader = shader(shaders["maps"].vs,shaders["maps"].fs) -- 使用 mesh 繪製第二層面的地圖 -- 屏幕左下角(0,0)在大地圖上對應的座標值(1488, 1616) -- 設置屏幕當前位置爲矩形中心點的絕對數值,分別除以 w, h 能夠獲得相對數值 self.x, self.y = (w/2-WIDTH/2), (h/2-HEIGHT/2) self.m = mesh() self.mi = self.m:addRect(WIDTH/2, HEIGHT/2, WIDTH, HEIGHT) self.m.texture = self.imgMap -- 利用紋理座標設置顯示區域,根據中心點座標計算出左下角座標,除以紋理寬度獲得相對值,w h 使用固定值(小於1) -- 這裏計算獲得的是大地圖中心點處的座標,是遊戲剛開始運行的座標 local u,v = WIDTH/w, HEIGHT/h self.m:setRectTex(self.mi, self.x/w, self.y/h, u, v) -- 整個地圖使用的全局數據表 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.trees = {"松樹", "楊樹", "小草"} self.mines = {"鐵礦", "銅礦"} -- 設置物體圖像 self.items = {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} --[=[ self.itemTable = {[self.trees[1]].self.items["imgTree1"],[self.trees[2]].self.items["imgTree2"], [self.trees[3]].self.items["imgTree3"],[self.mines[1]].self.items["imgMine1"], [self.mines[3]].self.items["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() end -- 新建地圖數據表, 插入地圖上每一個格子裏的物體數據 function Maps:createMapTable() --local mapTable = {} for i=1,self.gridCount,1 do self.mapTable[i] = {} for j=1,self.gridCount,1 do self.mapItem = {pos=vec2(i,j), plant=self:randomPlant(), mineral=self:randomMinerial()} table.insert(self.mapTable[i], self.mapItem) -- self.mapTable[i][j] = self.mapItem -- myT:switchPoint(myT.taskID) end end print("OK, 地圖初始化完成! ") self:updateMap() end -- 更新整副地圖:繪製地面, 繪製植物, 繪製礦物 function Maps:updateMap() setContext(self.imgMap) -- 用 mesh 繪製地面 self.m1:draw() -- 用 sprite 繪製植物,礦物,建築 for i = 1,self.gridCount,1 do for j=1,self.gridCount,1 do local pos = self.mapTable[i][j].pos local plant = self.mapTable[i][j].plant local mineral = self.mapTable[i][j].mineral -- 繪製植物和礦物 if plant ~= nil then self:drawTree(pos, plant) end if mineral ~= nil then self:drawMineral(pos, mineral) end end end setContext() end function Maps:drawMap() -- 更新紋理貼圖, --若是地圖上的物體有了變化 self.m.texture = self.imgMap local w,h = self.imgMap.width, self.imgMap.height local u,v = WIDTH/w, HEIGHT/h -- 增長判斷,若角色移動到邊緣則切換地圖:經過修改貼圖座標來實現 -- print(self.x,self.y) local left,right,top,bottom = WIDTH/10, WIDTH*9/10, HEIGHT/10, HEIGHT*9/10 local ss = 800 if myS.x <= left then self.x= self.x - WIDTH/ss end if myS.x >= right then self.x= self.x + WIDTH/ss end if myS.y <= bottom then self.y = self.y - HEIGHT/ss end if myS.y >= top then self.y = self.y + HEIGHT/ss end -- 根據計算獲得的數據從新設置紋理座標 self.m:setRectTex(self.mi, self.x/w, self.y/h, u, v) -- self:updateMap() self.m:draw() end function Maps:touched(touch) if touch.state == BEGAN then myS.x, myS.y = touch.x, touch.y end end --局部重繪函數 function Maps:updateItem(i,j) setContext(self.imgMap) local x,y = i * self.m1.texture.width, j * self.m1.texture.height sprite(self.m1.texture, x, y) setContext() self.m.texture = self.imgMap end -- 根據像素座標值計算所處網格的 i,j 值 function Maps:where(x,y) local w, h = self.m1.texture.width, self.m1.texture.height local i, j = math.ceil(x/w), math.ceil(y/h) return i, j end -- 角色跟地圖上物體的交互 function Maps:removeMapObject(i,j) local item = self.mapTable[i][j] if item.pos == vec2(i,j) then item.plant = nil item.mineral = nil self:updateItem(i,j) end end -- 顯示網格內的物體信息 function Maps:showGridInfo(i,j) local item = self.mapTable[i][j] print("showGridInfo: ", item.pos, item.tree, item.mineral) if item.tree ~= nil then fill(0,255,0,255) text(item.pos.."位置處有: ", item.tree, 500,200) end 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:drawTree(position,plant) local w, h = self.m1.texture.width, self.m1.texture.height local x,y = w * position.x, h * position.y -- print("tree:"..x.." : "..y) pushMatrix() -- 繪製植物圖像 sprite(self.itemTable[plant], x, y, w*6/10, h) --fill(100,100,200,255) --text(plant,x,y) popMatrix() end -- 繪製單位格子內的礦物 function Maps:drawMineral(position,mineral) local w, h = self.m1.texture.width, self.m1.texture.height local x, y = w * position.x, h * position.y pushMatrix() -- 繪製礦物圖像 sprite(self.itemTable[mineral], x+w/2, y , w/2, h/2) --fill(100,100,200,255) --text(mineral,x+self.scaleX/2,y) popMatrix() end -- Shader shaders = { maps = { vs=[[ // 拼圖着色器: 把小紋理素材拼接起來鋪滿整個屏幕 //--------vertex shader--------- attribute vec4 position; attribute vec4 color; attribute vec2 texCoord; varying vec2 vTexCoord; varying vec4 vColor; uniform mat4 modelViewProjection; void main() { vColor = color; vTexCoord = texCoord; gl_Position = modelViewProjection * position; } ]], fs=[[ //---------Fragment shader------------ //Default precision qualifier precision highp float; varying vec2 vTexCoord; varying vec4 vColor; // 紋理貼圖 uniform sampler2D texture; void main() { vec4 col = texture2D(texture,vec2(mod(vTexCoord.x,1.0), mod(vTexCoord.y,1.0))); gl_FragColor = vColor * col; } ]]} }
跟幀動畫整合在一塊兒的代碼在這裏: c06.lua
如今咱們能夠方便地更換地面紋理貼圖, 看看這兩個不一樣的貼圖效果:
]
到目前爲止, 咱們對地圖類的改寫基本完成, 調試經過後, 剩下的就是利用 shader
來爲地圖增長一些特效了.
原本打算寫寫下面這些特效:
下雨,下雪,雷電,迷霧,狂風
春夏秋冬四季變化
光線隨時間改變明暗程度
讓河流動起來
湖泊表面閃爍
用廣告牌實現的樹木
讓地面產生動態陰影變化
搞一個立方體紋理特貼圖
可是一看本章已經寫了太長的篇幅了, 因此決定把這些內容放到後面單列一章, 所以本章到此結束.
本章成功實現了以下目標:
mesh
繪製地圖, 用 mesh
顯示地圖mesh
的紋理座標機制解決了地圖自動捲動shader
.臨時想到的問題, 後續解決:
Github項目地址, 源代碼放在 src/
目錄下, 圖片素材放在 assets/
目錄下, 整個項目文件結構以下:
Air:Write-A-Adventure-Game-From-Zero admin$ tree . ├── README.md ├── Vim 列編輯功能詳細講解.md ├── assets │ ├── IMG_0097.PNG │ ├── IMG_0099.JPG │ ├── IMG_0100.PNG │ ├── c04.mp4 │ ├── cat.JPG │ └── runner.png ├── src │ ├── c01.lua │ ├── c02.lua │ ├── c03.lua │ ├── c04.lua │ ├── c05.lua │ ├── c06-01.lua │ ├── c06-02.lua │ └── c06.lua ├── 從零開始寫一個武俠冒險遊戲-0-開發框架Codea簡介.md ├── 從零開始寫一個武俠冒險遊戲-1-狀態原型.md ├── 從零開始寫一個武俠冒險遊戲-2-幀動畫.md ├── 從零開始寫一個武俠冒險遊戲-3-地圖生成.md ├── 從零開始寫一個武俠冒險遊戲-4-第一次整合.md ├── 從零開始寫一個武俠冒險遊戲-5-使用協程.md ├── 從零開始寫一個武俠冒險遊戲-6-用GPU提高性能(1).md └── 從零開始寫一個武俠冒險遊戲-6-用GPU提高性能(2).md 2 directories, 24 files Air:Write-A-Adventure-Game-From-Zero admin$
從零開始寫一個武俠冒險遊戲-1-狀態原型
從零開始寫一個武俠冒險遊戲-2-幀動畫
從零開始寫一個武俠冒險遊戲-3-地圖生成
從零開始寫一個武俠冒險遊戲-4-第一次整合
從零開始寫一個武俠冒險遊戲-5-使用協程
從零開始寫一個武俠冒險遊戲-6-用GPU提高性能(1)
從零開始寫一個武俠冒險遊戲-6-用GPU提高性能(2)