使用 Cosos2dX-Lua 制作一个横版过关游戏 (3)

总之,运行后就是这样
p4

1.创建Robot类
2.添加 Hero 和 Robot 受伤 和 死亡 动作
3.添加 Robot 的AI 使之会跟踪玩家并攻击
4.添加音效

在 ActionSprite.lua 中添加方法

function ActionSprite:createBoundingBoxWithOrigin(origin,size)
local boundingBox = {}
boundingBox.original = CCRect()
boundingBox.actual = CCRect()

boundingBox.original.origin = origin
boundingBox.original.size = size
boundingBox.actual.origin = ccpAdd(ccp(self:getPositionX(),self:getPositionY()),ccp(boundingBox.original.origin.x,boundingBox.original.origin.y))
boundingBox.actual.size = size

return boundingBox
end

这里创建一个包围盒,其中 original 为 角色原始矩形
actual 为真实矩形,它会根据角色的坐标,不断修改该矩形的 x,y, 这个函数只在初始化时调用

继续添加一个函数,这个函数将在修改角色坐标时调用,不断修改 actual 矩形

function ActionSprite:transformBoxes()
local hitBox = self:getHitBox()
local attackBox = self:getAttackBox()
local originX

if self:getScaleX() == -1 then
originX = - attackBox.original.size.width - hitBox.original.size.width
else
originX = 0
end

local position = ccp(self:getPositionX(),self:getPositionY())
hitBox.actual.origin = ccpAdd(position, ccp(hitBox.original.origin.x, hitBox.original.origin.y));
attackBox.actual.origin = ccpAdd(position, ccp(attackBox.original.origin.x + originX, attackBox.original.origin.y));

self:setHitBox(hitBox)
self:setAttackBox(attackBox)
end

添加函数 setPositions() 调用此函数来替换之前 在 GameLayer.lua 中 诸如 Hero:setPosition() 替换为 Hero.getClass().setPosition(Hero,x,y)

function ActionSprite:setPosition(posX,posY)
self:setPosition(posX,posY)
ActionSprite.transformBoxes(self)
end

这个函数在执行的时候,会修改玩家角色的坐标,然后更新该角色的actual 矩形
这个矩形将被用到碰撞检测,判断攻击是否成功

添加函数 hurtWithDamage(damage),knockout(), 外部传入的伤害会由角色的HurtPoint 抵消,当 HurtPoint 为0后,调用死亡动画

function ActionSprite:hurtWithDamage(damage)
local ActionState = self:getActionState()
local HurtPoint = self:getHurtPoint()
if ActionState ~= ACTION_STATE_KNOCKOUT then
self:stopAllActions()
self:createHurtAction();
self:runAction(self:getHurtAction())
ActionState = ACTION_STATE_HURT
self:setActionState(ActionSprite)

HurtPoint = HurtPoint - damage
self:setHurtPoint(HurtPoint)
if HurtPoint <= 0 then
ActionSprite.knockout(self)
end
end
end

function ActionSprite:knockout()
self:stopAllActions()
self:createKnockOutAction()
self:runAction(self:getKnockOutAction())
self:setActionState(ACTION_STATE_KNOCKOUT)
self:playDeathSound()
end

创建 Robot.lua 包路径 scenes.GameObjects.Robot.lua

local Robot = class("Robit", function()
return display.newSprite("#robot_idle_00.png")
end)

require("scenes.Define")
require("config")
local Prototype = require("scenes.GameObjects.ActionSprite"):extend(Robot)
--Animation Cache
local ROBOT_IDLE = "RobotIdle"
local ROBOT_ATTACK = "RobotAttack"
local ROBOT_WALK = "RobotWalk"
local ROBOT_HURT = "RobotHurt"
local ROBOT_KNOCKOUT = "RobotKnockOut"

--attribute
local WalkSpeed
local Damage

--measurements
local CenterToSide
local CenterToBottom

function Robot:ctor(name)
WalkSpeed = 80
self.HurtPoint = 100
Damage = 10
CenterToSide = 29
CenterToBottom = 39
self.ActionState = ACTION_STATE_NONE
self.Name = name
self.NextDecisionTime = 0

self.HitBox = Prototype.createBoundingBoxWithOrigin(self,ccp(-self:getCenterToSides(), -self:getCenterToBottom()),
CCSizeMake(self:getCenterToSides() * 2, self:getCenterToBottom() * 2))
self.AttackBox = Prototype.createBoundingBoxWithOrigin(self,ccp(self:getCenterToSides(), -5), CCSizeMake(25, 20))
end

在构造函数 ctor() 中,初始化了2个矩形 HitBox 和 AttackBox.
HitBox 为当角色属于被击中的一方时,将此矩形用于碰撞检测
AttackBox 为当角色属于攻击的一方时,将此矩形用于碰撞检测


juse like this, 攻击时的矩形在手部,其余部分为被攻击矩形

继续填充 Robot.lua 的代码 添加 受伤 和 死亡的动作 以及一些 getter,setter

function Robot:getClass()
return Prototype
end

function Robot:getName()
return self.Name
end

function Robot:update(dt)
Prototype.update(self,dt)
end

function Robot:createIdleAction()
local frames = display.newFrames("robot_idle_%02d.png",0,5)
local animation = display.newAnimation(frames,1/12)
display.setAnimationCache(ROBOT_IDLE,animation)
self.IdleAction = CCRepeatForever:create(CCAnimate:create(animation))
end

function Robot:createAttackAction()
local frames = display.newFrames("robot_attack_%02d.png",0,5)
local animation = display.newAnimation(frames,1/24)
display.setAnimationCache(ROBOT_ATTACK,animation)
local idelFunc = CCCallFunc:create(function() self:idle() end)
self.AttackAction = CCSequence:createWithTwoActions(CCAnimate:create(animation),idelFunc)
end

function Robot:createWalkAction()
local frames = display.newFrames("robot_walk_%02d.png",0,6)
local animation = display.newAnimation(frames,1/12)
display.setAnimationCache(ROBOT_WALK,animation)
local idelFunc = CCCallFunc:create(function() self:idle() end)
self.WalkAction = CCRepeatForever:create(CCAnimate:create(animation))
end

function Robot:createHurtAction()
local frames = display.newFrames("robot_hurt_%02d.png",0,3)
local animation = display.newAnimation(frames,1/12)
display.setAnimationCache(ROBOT_HURT,animation)
local idelFunc = CCCallFunc:create(function() self:idle() end)
self.HurtAction = CCSequence:createWithTwoActions(CCAnimate:create(animation),idelFunc)
end

function Robot:createKnockOutAction()
local frames = display.newFrames("robot_knockout_%02d.png",0,5)
local animation = display.newAnimation(frames,1/12)
display.setAnimationCache(ROBOT_KNOCKOUT,animation)
self.KnockOutAction = CCSequence:createWithTwoActions(CCAnimate:create(animation),CCBlink:create(2,10))
end

function Robot:getWalkSpeed()
return WalkSpeed
end

function Robot:getCenterToSides()
return CenterToSide
end

function Robot:getCenterToBottom()
return CenterToBottom
end

function Robot:getDesiredPosition()
return self.DesiredPosition
end

function Robot:setDesiredPosition(param)
self.DesiredPosition = param
end

function Robot:getActionState()
return self.ActionState
end

function Robot:setActionState(param)
self.ActionState = param
end

function Robot:getVelocity()
return self.Velocity
end

function Robot:setVelocity(param)
self.Velocity = param
end

function Robot:getWalkAction()
return self.WalkAction
end

function Robot:getAttackAction()
return self.AttackAction
end

function Robot:getIdleAction()
return self.IdleAction
end

function Robot:getHurtAction()
return self.HurtAction
end

function Robot:getKnockOutAction()
return self.KnockOutAction
end

function Robot:setHitBox(box)
self.HitBox = box
end

function Robot:getHitBox()
return self.HitBox
end

function Robot:setAttackBox(box)
self.AttackBox = box
end

function Robot:getAttackBox()
return self.AttackBox
end

function Robot:getHurtPoint()
return self.HurtPoint
end

function Robot:getDamage()
return Damage
end

function Robot:setHurtPoint(param)
self.HurtPoint = param
end

function Robot:getNextDecisionTime()
return self.NextDecisionTime
end

function Robot:setNextDecisionTime(param)
self.NextDecisionTime = param
end

function Robot:idle()
Prototype.idle(self)
end

function Robot:attack()
Prototype.attack(self)
end

function Robot:playAttackSound()
audio.playSound(GAME_SFX.HIT1)
end

function Robot:playDeathSound()
audio.playSound(GAME_SFX.BOT_DEATH)
end

function Robot:hurtWithDamage(damage)
Prototype.hurtWithDamage(self,damage)
end

function Robot:walkWithDirection(direction)
Prototype.walkWithDirection(self,direction)
end

return Robot

打开 Hero.lua 添加构造函数中的 包围盒创建, 受伤,死亡动作,以及他们的 getter and setter

Hero.lua 的构造函数修改如下

function Hero:ctor(name)
WalkSpeed = 80
self.HurtPoint = 100
Damage = 20
CenterToSide = 29
CenterToBottom = 39
self.Name = name

self.HitBox = Prototype.createBoundingBoxWithOrigin(self,ccp(-self:getCenterToSides(), -self:getCenterToBottom()),
CCSizeMake(self:getCenterToSides() * 2, self:getCenterToBottom() * 2))
self.AttackBox = Prototype.createBoundingBoxWithOrigin(self,ccp(self:getCenterToSides(), -10), CCSizeMake(20, 20));
end

添加死亡和受伤函数 以及其他 getter setter

function Hero:createHurtAction()
local frames = display.newFrames("hero_hurt_%02d.png",0,3)
local animation = display.newAnimation(frames,1/12)
display.setAnimationCache(HERO_HURT,animation)
local idelFunc = CCCallFunc:create(function() self:idle() end)
self.HurtAction = CCSequence:createWithTwoActions(CCAnimate:create(animation),idelFunc)
end

function Hero:createKnockOutAction()
local frames = display.newFrames("hero_knockout_%02d.png",0,5)
local animation = display.newAnimation(frames,1/12)
display.setAnimationCache(HERO_KNOCKOUT,animation)
self.KnockOutAction = CCSequence:createWithTwoActions(CCAnimate:create(animation),CCBlink:create(2,10))
end

function Hero:getHurtAction()
return self.HurtAction
end

function Hero:getKnockOutAction()
return self.KnockOutAction
end

function Hero:setHitBox(box)
self.HitBox = box
end

function Hero:getHitBox()
return self.HitBox
end

function Hero:setAttackBox(box)
self.AttackBox = box
end

function Hero:getAttackBox()
return self.AttackBox
end

function Hero:hurtWithDamage(damage)
Prototype.hurtWithDamage(self,damage)
end

现在,是时候在场景上添加Robot了
修改 GameLayer.lua 的构造函数如下

function GameLayer:ctor()
self.Actors = display.newBatchNode(CONFIG_ROLE_SHEET_IMAGE)
self:addChild(self.Actors,1)
self.ActorList = {}
self.RobotList = {}
self.CurrentIndex = 0
self:initTileMap()
self:initHero();
self:initRobots();

self.touchLayer = display.newLayer()
self:addChild(self.touchLayer,2)
self:setNodeEventEnabled(true)

local updateFunc = function(dt) self:onUpdate(dt) end
self:scheduleUpdate(updateFunc)
end

这里创建一个 RobotList 来过滤掉主角,单独保存机器人

在构造函数前 添加一个 TimeVal 变量,用于之后计算机器人思考的间隔时间
local TimeVal = 0
添加一个 local RobotCount = 50 用来决定机器人的数量

实现 initHero();

function GameLayer:initRobots()
for RobotIndex = 1, RobotCount do
local RobotCell = Robot.new("Robot"..RobotIndex)
self:addActors(RobotCell)
self.RobotList[#self.RobotList + 1] = RobotCell
local minX = SCREEN_SIZE.width + RobotCell:getCenterToSides()
local maxX = TileMap:getMapSize().width * TileMap:getTileSize().width - RobotCell:getCenterToSides()
local minY = RobotCell:getCenterToBottom()
local maxY = 3 * TileMap:getTileSize().height + RobotCell:getCenterToBottom()
RobotCell.getClass().setPosition(RobotCell,math.random(minX, maxX),math.random(minY, maxY))
RobotCell:setScaleX(-1)
RobotCell:setDesiredPosition(ccp(RobotCell:getPositionX(),RobotCell:getPositionY()))
RobotCell:idle()
end
end

添加 function GameLayer:updateRobots(dt) 函数,这个函数将负责创建一个简单的机器人AI

function GameLayer:updateRobots(dt)
TimeVal = TimeVal + dt
local alive = 0
local distanceSQ
local randomChoice
local curTime = TimeVal * 1000
local count = #self.RobotList
local heroPosition = ccp(Hero:getPositionX(),Hero:getPositionY())
for i = 1, count do
local robot = self.RobotList[i]
local robotPosition = ccp(robot:getPositionX(),robot:getPositionY())
robot:update(dt)
if robot:getActionState() ~= ACTION_STATE_KNOCKOUT then
alive = alive + 1
if curTime > robot:getNextDecisionTime() then
distanceSQ = ccpDistanceSQ(robotPosition,heroPosition)
if distanceSQ <= 250 then
robot:setNextDecisionTime(curTime + math.random() * 1000)
randomChoice = math.random(0,1)

if randomChoice <= 0.2 then
if heroPosition.x > robotPosition.x then
robot:setScaleX(1)
else
robot:setScaleX(-1)
end

robot:setNextDecisionTime(curTime + math.random() * 2000)
robot:attack()
if robot:getActionState() == ACTION_STATE_ATTACK then
if math.abs(heroPosition.y - robotPosition.y) < 10 then
if Hero:getHitBox().actual:intersectsRect(robot:getAttackBox().actual) then
Hero:hurtWithDamage(robot:getDamage())
end
end
end
else
robot:idle()
end
elseif distanceSQ <= SCREEN_SIZE.width * SCREEN_SIZE.width then
robot:setNextDecisionTime(curTime + math.random() * 1000)
randomChoice = math.random(0,2)
if randomChoice == 0 then
local moveDirection = ccpNormalize(ccpSub(heroPosition,robotPosition))
robot:walkWithDirection(moveDirection)
else
robot:idle()
end
end
end
end
end
end

TimeVal 会不断累加帧时间,然后通过消逝的时间长度来觉得机器人下一次的思考时间,并存进NextDecisionTime 中
逻辑如下,当机器人和玩家距离 小于250像素的时候, 记性一次随机数判断,有20%的概率判断机器人转身,并攻击玩家,其他情况下原地发呆
当机器人y坐标和英雄y坐标小于10,并且包围盒碰撞,则对英雄造成伤害。
其余的情况,机器人会朝着英雄靠近

之后我们将这个函数添加进update函数中,如下

function GameLayer:onUpdate(dt)
Hero:update(dt)
self:updatePositions()
self:setViewPointCenter(Hero:getPosition());
self:renderActors()
self:updateRobots(dt)
end

最后修改 updatePositions() 函数,遍历我们的机器人,让机器人更新他们的坐标,以及避免他们走出地图

function GameLayer:updatePositions()
local position = Hero:getDesiredPosition()
local posX
local posY
if position.x ~= Hero:getPositionX() or position.y ~= Hero:getPositionY() then
posX = math.min(TileMap:getMapSize().width * TileMap:getTileSize().width - Hero:getCenterToSides(),
math.max(Hero:getCenterToSides(), position.x))

posY = math.min(3 * TileMap:getTileSize().height + Hero:getCenterToBottom(),
math.max(Hero:getCenterToBottom(), position.y))

Hero.getClass().setPosition(Hero,posX,posY)
end

local count = #self.RobotList
for i = 1, count do
local robot = self.RobotList[i]
position = robot:getDesiredPosition()
if position.x ~= robot:getPositionX() or position.y ~= robot:getPositionY() then
posX = math.min(TileMap:getMapSize().width * TileMap:getTileSize().width - robot:getCenterToSides(),
math.max(robot:getCenterToSides(), position.x))

posY = math.min(3 * TileMap:getTileSize().height + robot:getCenterToBottom(),
math.max(robot:getCenterToBottom(), position.y))

robot.getClass().setPosition(robot,posX,posY)
end
end
end

接下来,运行游戏,你就能看到一堆机器人开始冲向你了 😀

最后给游戏添加音效吧~
打开 config.lua 添加音效的 名称 和 对应的地址

--音效
GAME_SFX = {
HIT0 = "pd_hit0.wav",
HIT1 = "pd_hit1.wav",
HERO_DEATH = "pd_herodeath.wav",
BOT_DEATH = "pd_botdeath.wav",
BGM = "LevelWinSound.mp3",
}

之后打开 game.lua 让我们在进入游戏前,先预载这些音乐
在 startup() 函数中 game.enterMainScene() 的前一行加上

-- preload all sounds
for k, v in pairs(GAME_SFX) do
audio.preloadSound(v)
end

然后我们在进入场景后,播放背景音乐
打开 GameScene.lua 在构造函数的最后一行添加 第一个参数是音效名称,第二个参数是循环播放

audio.playEffect(GAME_SFX.BGM,true)

如果找不到GAME_SFX.BGM, 我们需要先 require(“config”)

接着在 Hero 和 Robot 中添加 下面的函数, 如果没有 require(“config”), 也需要加上
Hero.lua

function Hero:playAttackSound()
audio.playSound(GAME_SFX.HIT0)
end

function Hero:playDeathSound()
audio.playSound(GAME_SFX.HERO_DEATH)
end

Robot.lua

function Robot:playAttackSound()
audio.playSound(GAME_SFX.HIT1)
end

function Robot:playDeathSound()
audio.playSound(GAME_SFX.BOT_DEATH)
end

最后修改 ActionSprite.lua 在攻击和死亡的时候调用之前的函数
在 knockout() 函数的最后一行加上
self:playDeathSound()

在 attack() 函数的 if then — end 结构内的最后一行加上
self:playAttackSound()

之后~运行你的游戏看看吧~

本游戏的代码已开源,包含游戏资源
git地址
https://github.com/dreamfairy/PrompaLua

发表评论

电子邮件地址不会被公开。 必填项已用*标注