• 从零开始写一个武侠冒险游戏-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$ 
    
  • 相关阅读:
    小禾满月了
    Gitlab-CI使用及.gitlab-ci.yml配置入门一篇就够了
    什么是CLI?
    什么是root帐户?
    Linux 的目录结构是怎样的?
    什么叫 CC 攻击?什么叫 DDOS 攻击?
    什么是 inode ?
    判断一文件是不是字符设备文件,如果是将其拷贝到 /dev 目录下?
    编写 Shell 程序,实现自动删除 50 个账号的功能,账号名为stud1 至 stud50 ?
    请问当用户反馈网站访问慢,如何处理?
  • 原文地址:https://www.cnblogs.com/freeblues/p/5731390.html
Copyright © 2020-2023  润新知