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

总之,运行后就是这样
p4

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

发表评论

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