作者:生前何必久睡死后必定长眠
链接: thetriaroadgame.zip 提取码: 2jph
人物的动作系统核心我采用了状态模式来实现,人物的动作主要有跳跃,攻击,行走,站立四个状态。
切换关系如下 :
状态之间的关系如图所示 :
实现思路 :
具体实现 :
实现状态模式(内部方法,外部无法直接访问)
state.moveLeft()--向左移动
state.moveRight()--向右移动
state.moveUp()--向上移动
state.moveDown()--向下移动
state.jump()--跳跃
state.normalATK()--普通攻击
state.skill1ATK()--技能1
state.skill2ATK()--技能2
state.skill3ATK()--技能3
留出入方法(外部方法,用于间接访问内部方法)
--切换状态该方法在外部调用 用来改变动作 actions 为字符串数组
function playerState.changeState(actions)
statesGet = actions
end
--执行状态 该方法在外部调用 用于执行存储完毕的动作 利用lua的泛型for循环实现
function playerState.goAction()
for _, v in pairs(statesGet) do
state[v]()
end
end
调用
stateArr=['moveLeft','jump']
playerState.changeState(stateArr)
playerState.goAction()
我使用了龙骨动画^REFERENCE2来播放播放龙骨动画。核心API在于
db = dragonBones.CCFactory:buildArmatureDisplay('龙骨名称')
获取骨骼
ani = db:getAnimation()
获取动画器
在获取了以上两个关键变量后 我们利用
ani:play("动画名称")
第一次动画播放
anistate = ani:fadeIn('动画名称')
对于播放的动画进行切换
anistate:setResetToPose(false)
attackState:addBoneMask("骨骼名称")
完成两个需要同时进行的动作的动画融合
效果如下
相关代码:
--加载龙骨的函数
local function loadTheDBAnimation(self)
-- json 只需解析一次, runtime 自带 cache 功能
if not app._isLoaded then--避免重复缓存
dragonBones.CCFactory:loadDragonBonesData("animations/samllMajio/smallMajio_ske.json")
dragonBones.CCFactory:loadTextureAtlasData("animations/samllMajio/smallMajio_tex.json")
app._isLoaded = true
end
local db = dragonBones.CCFactory:buildArmatureDisplay("Armature小魔女角色--加载骨骼
local ani = db:getAnimation()--获取动画器
ani:play("stay")--播放初始动画
return db
end
--------------------------------------------------
--遍历获取的状态,如果有移动状态,就播放移动动画
for k, v in ipairs(statesGet) do
if v == "moveUp" or v == "moveDown" or v == "moveLeft" or v == "moveRight" and not ani:getState("attack")
and not ani:getState("bigMagic") then
--播放奔跑动画
if not ani:getState("walk") then
if ani:isCompleted() or ani:getState("stay") then
local walk = ani:fadeIn("walk", 0.01)
end
end
end
end
我使用了事件分发器来完成。
通过app.Pushcenter.addListener('事件名',回调函数,self)
来接收每次切换房间时房间的大小与位置信息。
在人物切换房间的方法内添加app.Pushcenter.pushEvnet('事件名',房间信息)
来发送在获取了房间信息后对信息进行计算。
得出我所需要的边界信息得出边界信息后。
在人物移动的方法里每帧进行判断。
判断当前角色位置加上角色大小是否在边界内若超出边界则无法再往外移动, 但是行走动画照常播放。
实现代码:
--获取并计算可行走的区域的边界
app.PushCenter.addListener(
"overTheBond",
function(walkable)
local walkableSize = ccui.Helper:seekWidgetByName(walkable, "walkable1"):getContentSize()
bondLeft = walkable:getPositionX()
bondRight = bondLeft + walkableSize.width
bondBottom = walkable:getPositionY()
bondTop = bondBottom + walkableSize.height
end,
roleOfPlayer)
--判断是否超出了边界,判断是否正在攻击,如果两个条件有任一满足,则之间返回,不进行移动
if roleOfPlayer:getPositionX() - roleOfPlayer:getBoundingBox().width * 0.2 <= bondLeft or ani:getState("attack")
or ani:getState("bigMagic") then
return
end
roleOfPlayer:setPosition(roleOfPlayer:getPositionX() - moveSpeed, roleOfPlayer:getPositionY())
效果如下 :
摄像机最初是非常简单的跟随角色移动,导致了玩家的画面中可以看到不少没有渲染的区域,如下:
实现代码:
local posX = app.alltheTabel.player:getPositionX()--角色的x坐标
local posY = app.alltheTabel.player:getPositionY()--角色的y坐标
camera:pos(posX - display.cx, posY - display.cy)--减去一半的屏幕大小将角色居中
在借鉴Itay Keren先生对于相机控制的研究的文章<<横版游戏的摄像机移动理论与实践>>后,我做了一定的优化,使用了一个摄像机窗体的变种方法实现相机跟随,效果如下:
在这里我依旧使用了之前在人物边界控制的方法,获取了当前房间的信息,不过与之前不同的是,我的摄像机的边界比人物的行走的边界要小很多,如下图所示:
摄像机的移动边界和地面边界所相差的区域是我的设计分辨率的一半,为的是让摄像机能完全显示当前场景却不超出场景. 优化代码1计算边界:
app.PushCenter.addListener(
--添加监听事件
"overTheBond",
function(map)
--计算上下左右边界
bond.left = map:getPositionX() + display.cx--减去屏幕一半的屏幕宽度
bond.right = map:getPositionX() + map:getContentSize().width - display.cx
bond.bottom = map:getPositionY() + display.cy
bond.top = map:getPositionY() + map:getContentSize().height
end,
camera
)
优化代码2边界判断: 这一段在移动摄像机之前进行
if posY >= bond.top then
posY = bond.top
elseif posY <= bond.bottom then
posY = bond.bottom
end
if posX >= bond.right then
posX = bond.right
elseif posX <= bond.left then
posX = bond.left
end
目前只是简单有一个相机的跟随,但对于摄像机的运镜没有任何处理,比如人物跳起&落地时&当人物被攻击时&放大招时应有相应的摄像机反馈,缺少这些运镜手段使得游戏的打击感无限下降.
实现效果如下:
在玩家操作系统这方面我写了两种操作方式键盘操作和虚拟按键操作,一种用于应对电脑端,一种应对移动端
按键和键盘操作没什么复杂的地方就是简单的注册事件,唯一需要注意的就是因为lua这边没有对键值进行常量绑定,所以需要自己去测键值
在操作系统这里最重要的地方应该是摇杆和对应的调用人物动作系统的相关状态
先看看实现效果:
这个摇杆实际上只有摇杆的背景注册了触摸事件,那个摇杆只是给人视觉上的效果
这个实现是先监听触摸事件,然后获取触摸点的位置
在获取了触摸点的位置之后将摇杆向触摸点的位置移动
关于这个摇杆的边界实际上很简单,通过对比摇杆和摇杆背景中心点的距离来判断是否会超出边界
当超出边界时将摇杆阻拦
边界上点的计算:
local function caculateTheBoundPosOfCircle(x0, y0, x1, y1, r)
local angle = math.deg(cc.pGetAngle(cc.p(x0, y0), cc.p(x1, y1)))
local x2 = x0 + r * math.cos(angle * math.pi / 180)
local y2 = y0 + r * math.sin(angle * math.pi / 180)
return cc.p(x2, y2)
end
通过上面这个函数来计算边界上点的位置,这个函数实现思想是通过计算角度然后转换成弧度,再带入圆方程中算出边界点
cc.pGetAngle(cc.p(x0,y0),cc.p(x1,y1))
这个方法是获取角度值
在返回了x2,y2之后我们就能将摇杆设在边界上同时看上去可以有种很圆滑的感觉
在控制系统中我用了一个table类型的变量states来存储我每一次操作所对应的动作状态 在键盘和按钮中states的变化很简单,就是单纯的按下按键然后在states中插入相对应的状态,释放按键就从states中移除这个状态
较为复杂的是摇杆中对应的移动状态,因为我在人物动作系统中写了四个方向独立的状态来控制移动,所以在这里我需要对这四种进行不同的处理
处理方式就是将摇杆背景逻辑上划分为9格(如下图所示),然后判断摇杆所处的区域,比如我的摇杆在3号区域时,我的states中就存入向上和向右两个状态
实现代码例如下面所示:
local thePercent = rockerBG:getContentSize().height / 6--划分的区域的大小
local xOffset = posOfrocker.x - circlePointPos.x--获取x,y的偏移量,摇杆相对于圆心的偏移
local yOffset = posOfrocker.y - circlePointPos.y
if xOffset >= thePercent then
if findTheStateOfArr("moveRight") then
table.insert(stateArr, "moveRight")
end
elseif xOffset <= -thePercent then
if findTheStateOfArr("moveLeft") then
table.insert(stateArr, "moveLeft")
end
end
在states状态存入时,我注册了一个节点自定义事件来调用在动作系统中留下的两个接口,如下:
player:schedule(function()
playerstate.changeState(stateArr)
playerstate.goAction()
end, 0.01)
这样就可以达到在玩家输入控制时,角色给予及时的动作反馈
因为没有做平台的判定导致两套操作方式都同时在运行,从而使得游戏运行效率可能有所降低