总之,运行后就是这样
1.创建Robot类
2.添加 Hero 和 Robot 受伤 和 死亡 动作
3.添加 Robot 的AI 使之会跟踪玩家并攻击
4.添加音效
在 ActionSprite.lua 中添加方法
1 2 3 4 5 6 7 8 9 10 11 12 | 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 矩形
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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)
1 2 3 4 | function ActionSprite:setPosition(posX,posY) self:setPosition(posX,posY) ActionSprite.transformBoxes(self) end |
这个函数在执行的时候,会修改玩家角色的坐标,然后更新该角色的actual 矩形
这个矩形将被用到碰撞检测,判断攻击是否成功
添加函数 hurtWithDamage(damage),knockout(), 外部传入的伤害会由角色的HurtPoint 抵消,当 HurtPoint 为0后,调用死亡动画
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | 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 的构造函数修改如下
1 2 3 4 5 6 7 8 9 10 11 12 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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 的构造函数如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | 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函数中,如下
1 2 3 4 5 6 7 | function GameLayer:onUpdate(dt) Hero:update(dt) self:updatePositions() self:setViewPointCenter(Hero:getPosition()); self:renderActors() self:updateRobots(dt) end |
最后修改 updatePositions() 函数,遍历我们的机器人,让机器人更新他们的坐标,以及避免他们走出地图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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 添加音效的 名称 和 对应的地址
1 2 3 4 5 6 7 8 | --音效 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() 的前一行加上
1 2 3 4 | -- preload all sounds for k, v in pairs(GAME_SFX) do audio.preloadSound(v) end |
然后我们在进入场景后,播放背景音乐
打开 GameScene.lua 在构造函数的最后一行添加 第一个参数是音效名称,第二个参数是循环播放
1 | audio.playEffect(GAME_SFX.BGM,true) |
如果找不到GAME_SFX.BGM, 我们需要先 require(“config”)
接着在 Hero 和 Robot 中添加 下面的函数, 如果没有 require(“config”), 也需要加上
Hero.lua
1 2 3 4 5 6 7 | function Hero:playAttackSound() audio.playSound(GAME_SFX.HIT0) end function Hero:playDeathSound() audio.playSound(GAME_SFX.HERO_DEATH) end |
Robot.lua
1 2 3 4 5 6 7 | 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