從零開始寫一個武俠冒險遊戲-4-第一次整合

從零開始寫一個武俠冒險遊戲-4-第一次整合

---- 把狀態,幀動畫地圖生成整合起來

概述

前面三章咱們完成了遊戲開發中的 狀態原型,幀動畫原型地圖生成原型 這三個模塊, 由於都是原型, 因此以上三個模塊還有不少能夠改進的地方, 這些細節咱們會逐步完善, 如今讓咱們把這三個模塊整合到一塊兒.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

增長一個調試函數 sysInfo

爲了更精確地瞭解當前遊戲的幀速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

簡短的代碼 VS. 強大的表現能力

看一看 Github 上對咱們目前成果的代碼行統計數據:

  • 563 lines (480 sloc) 16.7 KB

也就是說去掉註釋和空行的有效代碼行是 480 行, 用這短短的不到 500 行的代碼, 咱們就搭建起一個武俠冒險遊戲的世界. 不得不說咱們的開發工具 Codea 特別適合在 iPad 上作原型.

本章的內容比較少, 主要是把前面幾個模塊整合到一塊兒, 之因此專門用一章來寫這個, 緣由是我須要思考一下後續的開發該怎麼作, 沒錯, 這個遊戲開發項目是我一時心血來潮開始寫的, 基本上沒有專門去作什麼需求分析, 概要設計, 詳細設計什麼的, 而是從想法出發, 從一個個最簡單的原型起步, 想到哪裏寫到哪裏, 因而這麼順順利利地就把一個小框架搭起來了.

須要說明的一點是, 這種原型開發法不太適合大型項目, 不過很是適合我的開發者或者超小型團隊(程序員<=2), 尤爲適合那些有一個想法, 特別想作出個大概樣子來驗證驗證的開發者.

OK, 本章先寫這麼多, 我先去想一想後面怎麼作, 再來繼續.

全部章節連接

從零開始寫一個武俠練功遊戲-1-狀態原型
從零開始寫一個武俠練功遊戲-2-幀動畫
從零開始寫一個武俠練功遊戲-3-地圖生成
從零開始寫一個武俠冒險遊戲-4-第一次整合

相關文章
相關標籤/搜索