從零開始寫一個武俠冒險游戲-1-狀態原型


從零開始寫一個武俠冒險游戲-1-狀態原型

  • 作者:FreeBlues
  • 修訂記錄
    • 2016.06.05 初稿完成.
    • 2016.08.03 增加對 XCode 項目文件的說明.

概述

真正的從零開始, 全部手寫, 一段代碼一段代碼, 一個函數一個函數地從頭開始構思/編寫一個武俠冒險游戲.

  • 運行環境要求: Codea + iPad, 可運行本項目所有源代碼; 或者OSX + XCode, 可在模擬器上運行本項目
  • 編譯環境要求: OSX + XCode, 可將本項目編譯為獨立可發布的 APP

先寫一個人物狀態欄原型

我們這個游戲以人物的狀態變化為核心, 一切都從這個核心出發, 所以最先編寫的就是這個人物狀態原型.

最簡狀態類原型

首先定義一下角色的狀態欄:體力,內力,精力,智力,氣,血,寫個狀態類把它們表現出來. 代碼:

-- 角色狀態類
Status = class()

function Status:init()
	-- 體力,內力,精力,智力,氣,血
	self.tili = 100
	self.neili = 0
	self.jingli = 100
	self.zhili = 100
	self.qi = 100
	self.xue = 100
	-- 申請一個 200* 300 的圖片, 用來繪制狀態
	self.img = image(200, 300)
end

為方便調試,再寫一個繪制方法, 代碼如下:

function Status:drawUI()
	-- 把所有內容先繪制到 self.img 上
	setContext(self.img)
	background(119, 121, 72, 255)
	fill(36, 112, 111, 255)
	rect(5,5,200-10,300-10)
	fill(70, 255, 0, 255)
	textAlign(LEFT)
	text("體力: "..self.tili,50,280)
	text("內力: "..self.neili,50,260)
	text("精力: "..self.jingli,50,240)
	text("智力: "..self.zhili,50,220)
	text("氣 : "..self.qi,50,200)
	text("血 : "..self.xue,50,180)
	setContext()
	sprite(self.img, 200,300)
end

主程序

最后是一個主程序框架:

-- 主程序框架
function setup()
	displayMode(OVERLAY)
	myStatus = Status()
end

function draw()
	background(32, 29, 29, 255)
	myStatus:drawUI()
end

把上述代碼合起來,就是一個基本的角色狀態了.

增加狀態更新方法

角色的狀態不可能凝固不變,因此,我們需要為角色狀態類增加一個用來更新值的方法,原型調試階段,簡單實現最基本功能就可以了:

function Status:update()
	-- 更新狀態:自我修煉,日常休息,戰斗
	self.neili = self.neili + 10
end

暫時用觸摸動作來觸發這個方法,在屏幕右半部分點擊一次就會調用一次,增加觸摸事件:

function touched(touch)
	-- 調試用: 點擊在屏幕右側, 會觸發 Status:update() , 刷新 self.neili 的值
	if touch.x > WIDTH/2 and touch.state == ENDED then myStatus:update() end
end

現在已經可以初步跑起來了, 點擊會刷新狀態值.

增加數據可視化:雷達圖

基本雷達圖

我們為了更直觀地了解人物狀態的變化, 寫一個雷達圖繪制函數, 具體調試過程就不贅述了, 總之是先寫最簡單的骨架結構, 再一步步填充豐滿, 代碼如下:

-- 角色技能雷達圖
function Status:raderGraph()
    setContext(self.img)
    pushMatrix()
    pushStyle()
    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)}}

    -- 用於繪制圈線的函數
    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)
    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

    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()    
    lines(t,n,j,z,q,x)

    popStyle()
    popMatrix()
    setContext()
end

增加動態刷新

基本的雷達圖代碼寫完了, 現在繪制出來的還是一個靜態的雷達圖, 我們希望能根據狀態值的刷新而實時更新雷達圖, 那么就是把這段代碼修改一下, 因為這段代碼只是把 3,2,3,2,4,1 這些固定值賦給這幾個局部變量:

local t,n,j,z,q,x = 3,2,3,2,4,1   
lines(t,n,j,z,q,x)

在函數 Status:raderGraph() 內部寫一個函數, 把這幾個局部變量 t,n,j,z,q,x 跟狀態類的各個屬性 self.tili 等關聯起來, 也就是做一個賦值:

-- 處理人物狀態值, 根據它的值大小將其縮減為 1,2,3,4 並返回
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 f(t/25),f((25+math.fmod(n,100))/25),f(j/25),f(z/25),f(q/25),f(x/25)
end        

把這條語句

local t,n,j,z,q,x = 3,2,3,2,4,1

換成下面這條:

local t,n,j,z,q,x = values() 

很好, 現在我們的雷達圖就可以隨着狀態值的變化而變化了

增加實時旋轉

不過這種變化還是不太明顯, 我們再增加一個旋轉效果, 增加如下語句到相應位置:

-- 圍繞中心點勻速旋轉
rotate(30+ElapsedTime*10)

最終雷達圖

這樣就會產生一個實時旋轉的效果了, 全部代碼如下:

-- 角色狀態類
Status = class()

function Status:init() 
    -- 體力,內力,精力,智力,氣,血
    self.tili = 100
    self.neili = 90
    self.jingli = 70
    self.zhili = 100
    self.qi = 100
    self.xue = 100
    self.img = image(200, 300)
end

function Status:update()
    -- 更新狀態:自我修煉,日常休息,戰斗
    self.neili = self.neili + 1
end

function Status:drawUI()
    setContext(self.img)
    background(119, 121, 72, 255)
    pushStyle()
    fill(36, 112, 111, 255)
    rect(5,5,200-10,300-10)
    fill(70, 255, 0, 255)
    textAlign(RIGHT)
    text("體力: "..self.tili,50,280)
    text("內力: "..self.neili,50,260)
    text("精力: "..self.jingli,50,240)
    text("智力: "..self.zhili,50,220)
    text("氣  : "..self.qi,50,200)
    text("血  : "..self.xue,50,180)
    sprite("Documents:B1", 100,90)
    popStyle()
    setContext()
    self:raderGraph()
    sprite(self.img, 400,300)
end

-- 角色技能雷達圖
function Status:raderGraph()
    setContext(self.img)
    pushMatrix()
    pushStyle()
    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)}}

    -- 用於繪制圈線的函數
    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)
    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 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()    
    lines(t,n,j,z,q,x)

    popStyle()
    popMatrix()
    setContext()
end

-- main 主程序框架
function setup()
    displayMode(OVERLAY)
    myStatus = Status()
end

function draw()
    background(32, 29, 29, 255)
    myStatus:drawUI()
    -- myStatus:raderGraph()
    fill(143, 255, 0, 255)
    rect(WIDTH/2,HEIGHT/2,200,200)
    fill(0, 55, 255, 255)
    text("修煉", WIDTH/2+100, HEIGHT/2+100)
end

function touched(touch)
    if touch.x > WIDTH/2 and touch.state == ENDED then myStatus:update() end
end

截圖

把雷達圖函數加載, 運行無誤, 接下來可以選擇的方向就多了--也意味着工作量和復雜度也大大增加了.

本章小結

所有章節鏈接

Github項目地址

Github項目地址, 源代碼放在 src/ 目錄下, 圖片素材放在 assets/ 目錄下, XCode項目文件放在 MyAdventureGame 目錄下, 整個項目文件結構如下:

Air:Write-A-Adventure-Game-From-Zero admin$ tree
.
├── MyAdventureGame
│   ├── Assets
│   │   ├── ...
│   ├── Libs 
│   │   ├── ...
│   ├── MyAdventureGame
│   │   ├──...
│   ├── MyAdventureGame.codea
│   │   ├──...
│   ├── MyAdventureGame.xcodeproj
│   │   ├──...
│   └── libversion
├── README.md
├── Vim 列編輯功能詳細講解.md
├── assets
│   ├── ...
│   └── runner.png
├── src
│   ├── c01.lua
│   ├── c02.lua
│   ├── c03.lua
│   ├── c04.lua
│   ├── c05.lua
│   ├── c06-01.lua
│   ├── c06-02.lua
│   ├── c06-03.lua
│   └── c06.lua
├── 從零開始寫一個武俠冒險游戲-0-開發框架Codea簡介.md
├── 從零開始寫一個武俠冒險游戲-1-狀態原型.md
├── 從零開始寫一個武俠冒險游戲-2-幀動畫.md
├── 從零開始寫一個武俠冒險游戲-3-地圖生成.md
├── 從零開始寫一個武俠冒險游戲-4-第一次整合.md
├── 從零開始寫一個武俠冒險游戲-5-使用協程.md
├── 從零開始寫一個武俠冒險游戲-6-用GPU提升性能(1).md
├── 從零開始寫一個武俠冒險游戲-6-用GPU提升性能(2).md
└── 從零開始寫一個武俠冒險游戲-6-用GPU提升性能(3).md

2 directories, 26 files
Air:Write-A-Adventure-Game-From-Zero admin$ 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM