2019独角兽企业重金招聘Python工程师标准>>>

从零开始写一个武侠冒险游戏-4-第一次整合

---- 把状态,帧动画地图生成整合起来

概述

前面三章我们完成了游戏开发中的 状态原型,帧动画原型地图生成原型 这三个模块, 因为都是原型, 所以以上三个模块还有很多可以改进的地方, 这些细节我们会逐步完善, 现在让我们把这三个模块整合到一起.

代码整合

因为我们是一个模块一个模块以类的形式进行开发的, 之前这些模块都试验过可以正常跑起来, 而且现阶段模块之间的耦合比较小, 所以我们的集成工作就比较顺利, 需要修改的只是程序主框架, 也就是 setup()draw() 两个函数.

另外我们在操作控制方面还没怎么投入, 之前仅仅是在 touched(touch) 函数中写了一点简单的测试用代码, 这些工作显然是远远不够的.

开源的操纵杆类

我们目前开发的游戏是要运行在平板电脑上的, 玩家对角色的操作都通过触摸屏进行, 所以我们需要写一个操纵杆类来封装那些触摸函数, 好消息是已经有人写好了, 并且公布了源代码, 所以我们可以直接使用, 只要说明版权信息即可.

这个类写得非常简洁明了, 不过我还是加了一点注释, 操纵杆类的代码如下:

--# Stick
-- 操纵杆类  作者: @Jaybob
Stick = class()function Stick:init(ratio,x,y,b,s)self.ratio = ratio or 1self.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 50self.a = 0self.touchId = nilself.x,self.y = 0,0
endfunction Stick:draw()-- 没有 touched 函数的 Stick 类是如何找到自己对应的触摸数据的?根据点击处坐标跟操纵杆的距离来判断if touches[self.touchId] == nil then-- 循环取出 touches 表内的数据,比较其坐标跟操纵杆的距离,若小于半径则说明是在点击操纵杆for i,t in pairs(touches) doif vec2(t.x,t.y):dist(self.i) < self.b/2 then self.touchId = i endendself.v = vec2(0,0)else-- 根据对应于操纵杆的触摸的xy坐标设置 self.v,再根据它计算夹角 self.aself.v = vec2(touches[self.touchId].x,touches[self.touchId].y) - self.iself.a = math.deg(math.atan2(self.v.y, self.v.x))end-- 根据 self.v 和 self.b 计算得到 self.tself.t = math.min(self.b/2,self.v:len())if self.t >= self.b/2 thenself.v = vec2(math.cos(math.rad(self.a))*self.b/2,math.sin(math.rad(self.a))*self.b/2)endpushMatrix()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.ratioself.t = self.t/(self.b/2)*self.ratio-- 根据 self.v/self.t,重新设置 self.x/self.yself.x, self.y = self.v.x, self.v.y
end

第一次整合的代码

修改后的代码如下:

-- 主程序框架
function setup() displayMode(OVERLAY)-- 初始化状态myStatus = Status()-- 以下为帧动画代码s = -1fill(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 =""
endfunction 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 thenstep = 10 *m.i*ls.x/math.abs(ls.x)elsestep = 0end--]]--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 thentouches[touch.id] = nilelsetouches[touch.id] = touch-- for k,v in pairs(touches) do print(k,v) endend-- 用于测试修炼if touch.x > WIDTH/2 and touch.state == ENDED then myStatus:update() end-- 用于测试移动方向:点击左侧向右平移,点击右侧向左平移if touch.x > WIDTH/2 and touch.state == ENDED then s = -1elseif touch.x < WIDTH/2 then s = 1end-- c1,c2 = myMap:where(touch.x, touch.y)c1,c2 = myMap:where(m.x,m.y)-- 显示角色所处网格坐标ss = c1.." : "..c2end

其他模块都不需要大改动, 除了 Sprites 类需要修改 draw() 里的一点内容, 修改后代码为:

function Sprites:draw(w,h)...-- 确定每帧子画面在屏幕上停留的时间if ElapsedTime > self.prevTime + 0.08 thenself.prevTime = self.prevTime + 0.08 self.k = math.fmod(self.i,#self.imgs) self.i = self.i + 1    self.x = self.x + ls.xself.y = self.y + ls.yend...
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.luafunction setup() displayMode(OVERLAY)-- 初始化状态myStatus = Status()-- 以下为帧动画代码s = -1fill(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 =""
endfunction 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 thenstep = 10 *m.i*ls.x/math.abs(ls.x)elsestep = 0end--]]--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 thentouches[touch.id] = nilelsetouches[touch.id] = touch-- for k,v in pairs(touches) do print(k,v) endend-- 用于测试修炼if touch.x > WIDTH/2 and touch.state == ENDED then myStatus:update() end-- 用于测试移动方向:点击左侧向右平移,点击右侧向左平移if touch.x > WIDTH/2 and touch.state == ENDED then s = -1elseif touch.x < WIDTH/2 then s = 1end-- 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 = 100self.neili = 30self.jingli = 70self.zhili = 100self.qi = 100self.xue = 100self.gongfa = {t={},n={},j={},z={}}self.img = image(200, 300)
endfunction Status:update()-- 更新状态:自我修炼,日常休息,战斗self.neili = self.neili + 1self:xiulian()
endfunction 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()
endfunction Status:xiulian()-- 修炼基本内功先判断是否满足修炼条件: 体力,精力大于50,修炼一次要消耗一些if self.tili >= 50 and self.jingli >= 50 thenself.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.xuelocal 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 dotext(p[i],0,45)line(0,0,0,r)rotate(a)end-- 绘制圈线stroke(255, 0, 0, 102)strokeWidth(2)for i = 1,4 dolines(i,i,i,i,i,i)endfunction values()local t,n,j,z,q,x = self.tili, self.neili, self.jingli,self.zhili, self.qi, self.xuelocal 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)endstroke(255, 32, 0, 255)strokeWidth(2)smooth()-- 设定当前各参数的值-- print(values())local t,n,j,z,q,x = 3,2,3,2,4,1local 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.xuelines(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 = xself.y = y-- self.index = 1self.img = imgself.imgs = {}self.pos = posself.i=0self.k=1self.q=0self.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]))endendfunction Sprites:deal()---[[ 对原图进行预处理,把背景修改为透明,现存问题:角色内部有白色也会被去掉local v = 255for x=1,self.img.width dofor 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 thenif r == v and g == v and b == v and a == v thenself.img:set(x,y,r,g,b,0)endendend--]]
endfunction Sprites:draw(w,h)pushMatrix()pushStyle()-- 绘图模式选 CENTER 可以保证画面中的动画角色不会左右漂移rectMode(CENTER)spriteMode(CENTER)-- 确定每帧子画面在屏幕上停留的时间if ElapsedTime > self.prevTime + 0.08 thenself.prevTime = self.prevTime + 0.08 self.k = math.fmod(self.i,#self.imgs) self.i = self.i + 1    self.x = self.x + ls.xself.y = self.y + ls.yendself.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 = 50self.scaleX = 50self.scaleY = 50self.plantSeed = 20.0self.minerialSeed = 50.0-- 根据地图大小申请图像local w,h = (self.gridCount+1)*self.scaleX, (self.gridCount+1)*self.scaleYself.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 dofor j=1,self.gridCount,1 doself.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)endendself:updateMap()
end-- 根据地图数据表, 刷新地图
function Maps:updateMap()setContext(self.imgMap)   for i = 1,self.gridCount*self.gridCount,1 dolocal pos = self.mapTable[i].poslocal plant = self.mapTable[i].plantlocal mineral = self.mapTable[i].mineral-- 绘制地面self:drawGround(pos)-- 绘制植物和矿物if plant ~= nil then self:drawTree(pos, plant) endif mineral ~= nil then self:drawMineral(pos, mineral) endendsetContext()
endfunction 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 = nilif seed >= 1 and seed < 2 then result = tree1elseif seed >= 2 and seed < 3 then result = tree2elseif seed >= 3 and seed < 4 then result = tree3elseif seed >= 4 and seed <= self.plantSeed then result = nil endreturn result
end-- 随机生成矿物
function Maps:randomMinerial()local seed = math.random(1.0, self.minerialSeed)local result = nilif seed >= 1 and seed < 2 then result = mine1elseif seed >= 2 and seed < 3 then result = mine2elseif seed >= 3 and seed <= self.minerialSeed then result = nil endreturn result
endfunction 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.ypushMatrix()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.ypushMatrix()-- 绘制植物图像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.ypushMatrix()-- 绘制矿物图像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 1self.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 50self.a = 0self.touchId = nilself.x,self.y = 0,0
endfunction Stick:draw()-- 没有 touched 函数的 Stick 类是如何找到自己对应的触摸数据的?根据点击处坐标跟操纵杆的距离来判断if touches[self.touchId] == nil then-- 循环取出 touches 表内的数据,比较其坐标跟操纵杆的距离,若小于半径则说明是在点击操纵杆for i,t in pairs(touches) doif vec2(t.x,t.y):dist(self.i) < self.b/2 then self.touchId = i endendself.v = vec2(0,0)else-- 根据对应于操纵杆的触摸的xy坐标设置 self.v,再根据它计算夹角 self.aself.v = vec2(touches[self.touchId].x,touches[self.touchId].y) - self.iself.a = math.deg(math.atan2(self.v.y, self.v.x))end-- 根据 self.v 和 self.b 计算得到 self.tself.t = math.min(self.b/2,self.v:len())if self.t >= self.b/2 thenself.v = vec2(math.cos(math.rad(self.a))*self.b/2,math.sin(math.rad(self.a))*self.b/2)endpushMatrix()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.ratioself.t = self.t/(self.b/2)*self.ratio-- 根据 self.v/self.t,重新设置 self.x/self.yself.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-第一次整合

转载于:https://my.oschina.net/freeblues/blog/690718

从零开始写一个武侠冒险游戏-4-第一次整合相关推荐

  1. 从零开始写一个武侠冒险游戏-2-帧动画

    从零开始写一个武侠冒险游戏-2-帧动画 ---- 用基本绘图函数实现帧动画 作者:FreeBlues 修订记录 2016.06.10 初稿完成. 2016.08.03 增加对 XCode 项目文件的说 ...

  2. 从零开始写一个武侠冒险游戏-3-地图生成

    2019独角兽企业重金招聘Python工程师标准>>> 从零开始写一个武侠冒险游戏-3-地图生成 概述 前面两章我们设计了角色的状态, 绘制出了角色, 并且赋予角色动作, 现在是时候 ...

  3. 从零开始写一个武侠冒险游戏-6-用GPU提升性能(1)

    从零开始写一个武侠冒险游戏-6-用GPU提升性能(1) ----把帧动画的实现放在GPU上 作者:FreeBlues 修订记录 2016.06.19 初稿完成. 2016.08.05 增加对 XCod ...

  4. 从零开始写一个武侠冒险游戏-8-用GPU提升性能(3)

    从零开始写一个武侠冒险游戏-8-用GPU提升性能(3) ----解决因绘制雷达图导致的帧速下降问题 作者:FreeBlues 修订记录 2016.06.23 初稿完成. 2016.08.07 增加对 ...

  5. 如何搭建python框架_从零开始:写一个简单的Python框架

    原标题:从零开始:写一个简单的Python框架 Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发. 你为什么想搭建一个Web框架?我想有下面几个原因: 有一个 ...

  6. mysql c测试程序_Linux平台下从零开始写一个C语言访问MySQL的测试程序

    Linux 平台下从零开始写一个 C 语言访问 MySQL 的测试程序 2010-8-20 Hu Dennis Chengdu 前置条件: (1) Linux 已经安装好 mysql 数据库: (2) ...

  7. dotnet 从零开始写一个人工智能 从一个神经元开始

    现在小伙伴说的人工智能都是弱智能,可以基于神经网络来做.而神经网络是有多层网络,每一层网络都有多个神经元.那么最简单的神经网络就是只有一层,而这一层只有一个神经元,也就是整个神经网络只是有一个神经元. ...

  8. 用python从零开始写一个注册机(新手也能操作)-前言

    今天开始带领大家从零开始写一个网站的账号注册机,达到批量注册的目的. 涉及到的相关知识包含: python的基本使用 playwright库的相关用法 验证码的识别 欢迎大家关注.

  9. 从零开始写一个抖音App——Apt代码生成技术、gradle插件开发与protocol协议

    1.讨论--总结前两周评论中有意义的讨论并给予我的解答 2.mvps代码生成原理--将上周的 mvps 架构的代码生成原理进行解析 3.开发一款gradle插件--从 mvps 的代码引出 gradl ...

  10. 从零开始写一个图像处理程序之四(Prewitt 算子分析)

    上一篇: 从零开始写一个图像处理程序之三(卷积原理简介)_星空_MAX的博客-CSDN博客 具体看看卷积核为什么能够得到图像信息呢 先看Prewitt算子的结构 分别过滤出来横向边缘和纵向边缘: 拿G ...

最新文章

  1. 走过19年,每年千万下载量,科学计算开源库SciPy的前世今生
  2. 微软 python_微软推出 Pylance,改善 VS Code 中的 Python 体验
  3. linux下父子进程共享socket,多进程共享socket(Linux,Win32)
  4. android之多媒体篇(一)
  5. python变量域名_想尝试使用python进行域名分析,可是没有接触过python,想请教请教。...
  6. [leetcode] 5331. 跳跃游戏 V
  7. C++中的L和_T()
  8. 支付安全不能说的那些事
  9. SEO需要每天写文章吗?
  10. 3.图灵学院-----阿里/京东/滴滴/美团整理----高频JVM调优篇
  11. 关于数字转换成人民币大写的问题
  12. 2017暑期实习招聘-产品经理-微软WDGAE(2)-第3轮面试
  13. Linux密码破解(非远程爆破)
  14. 解读 CreateMutexWin32A
  15. 【Java系列】聊天室开发
  16. 白塑投影幕布为何深受青睐?
  17. 外星人java,外星人Alienware OEM原版系统Win7 64位 V2021.01
  18. 什么是域名服务器 (DNS) 及其工作原理?
  19. 公式e=1+1/1!+1/2!+1/3!+......,求 e 的近似值,当下一项的值精度小于10的-6次方时停止累加运算。
  20. SFDC_05(内部类)

热门文章

  1. 学习之苦也正是学习之甜------知识的本质
  2. Centos 7 开机提示 Entering emergency mode 解决方法
  3. windows怎样运行wmi服务器,windows系统wmi服务器
  4. HDOJ 5142 NPY and FFT 水
  5. 分享一下新的echarts离线文档
  6. React在ESLint下的报错收录(react-hooks/exhaustive-deps)
  7. 彻底搞懂CSS层叠上下文、层叠等级、层叠顺序、z-index
  8. 关于wkhtmltopdf生成pdf空白
  9. 创建计划行确认数量为0的销售订单
  10. Python深度学习之LSTM文本生成