5、cocos2d-Lua的demo--虫子和虫子精灵

我们作个比方,场景好比舞台的一幕,models下的类相当于剧本中的角色(注意只是剧本中的),是死的,并没有被演活,那么精灵就好比一个个地演员,把角色演活。

因此虫子类仅仅是类,虫子精灵负责按照虫子类描述的特性执行其动作。

但是这个demo项目在这方面处理得相当蹩脚,且容我整理后再梳理清楚。这一节主要是优化GameView和虫子类以及虫子精灵的分工。

逻辑层应当只负责宏观的逻辑执行,它最好不要去涉及到具体某些类或对象的初始化逻辑,特别是“角色”众多时应该用类来统一管理,然后派生类各自实现自己不同的地方,逻辑层只负责调用它们的公共接口或函数即可。 遵循这个原则,最后把虫子基类、蚂蚁类、蜘蛛类优化为如下(原始代码不再作分析,因为太混乱,难以梳理):

虫子基类:
local BugBase = class("BugBase")

function BugBase:ctor()
self.name = nil
self.imageFileName = nil
self.position_ = cc.p(0, 0)
self.rotation_ = 0
self.dist_ = 0
self.destination_ = cc.p(0, 0)
self.speed_ = 1
self.touchRange_ = 0
self.animationTimes_ = nil
end

function BugBase:getName()
if self.name==nil then
printError('uninit name')
end
return self.name
end

function BugBase:getImageFileName()
if self.imageFileName==nil then
printError('uninit imageFileName')
end
return self.imageFileName
end

function BugBase:getPosition()
return self.position_
end

function BugBase:getRotation()
return self.rotation_
end

function BugBase:getDist()
return self.dist_
end

function BugBase:getAnimationTimes()
if self.animationTimes_==nil then
printError('uninit animationTimes_')
end
return self.animationTimes_
end

function BugBase:setDestination(destination)
self.destination_ = clone(destination)
self.dist_ = math.random(display.width / 2 + 100, display.width / 2 + 200)

local rotation = math.random(0, 360)
self.position_ = self:calcPosition(rotation, self.dist_, destination)
self.rotation_ = rotation - 180
return self
end

local fixedDeltaTime = 1.0 / 60.0
function BugBase:step(dt)
self.dist_ = self.dist_ - self.speed_ * (dt / fixedDeltaTime)
self.position_ = self:calcPosition(self.rotation_ + 180, self.dist_, self.destination_)
return self
end

function BugBase:calcPosition(rotation, dist, destination)
local radians = rotation * math.pi / 180
return cc.p(destination.x + math.cos(radians) * dist,
destination.y - math.sin(radians) * dist)
end

function BugBase:checkTouch(x, y)
local dx, dy = x - self.position_.x, y - self.position_.y
local offset = math.sqrt(dx * dx + dy * dy)
return offset <= self.touchRange_
end

return BugBase

优化之处:
去掉了虫子类型,有了派生类干嘛还用枚举来区分不同的类?神经病一样,典型的C语言思想,没有把类好好利用好。

增加了私有变量:name(虽然源码里面没有使用,但是看起来会舒服清晰很多),imageFileName(用于指示该类的图片资源),animationTimes_(动画延迟时间),并分别为它们设定了对应的get函数。


蚂蚁类:
local BugBase = import(".BugBase")

local BugAnt = class("BugAnt", BugBase)

function BugAnt:ctor()
BugAnt.super.ctor(self)
self.name = 'BugAnt'
self.imageFileName = 'BugAnt.png'
self.speed_ = 1.0
self.touchRange_ = 70
self.animationTimes_ = 0.15
end

return BugAnt
蜘蛛类:
local BugBase = import(".BugBase")

local BugSpider = class("BugSpider", BugBase)

function BugSpider:ctor()
BugSpider.super.ctor(self)
self.name = 'BugSpider'
self.imageFileName = 'BugSpider.png'
self.speed_ = 1.5
self.touchRange_ = 50
self.animationTimes_ = 0.1
end

return BugSpider
派生类各自在自己的构造函数中初始化自己的数据,什么?担心会忘记设置?因此在基类的构造函数中把这几个私有变量初始化为nil,并在对应的get函数中进行判断,如果没有被初始化过则输出错误信息,基类的作用就是为派生类制造规范的行为准则,一切就这么简单方便!


接下来是虫子精灵和虫子死亡精灵:
虫子精灵:
local BugSprite = class("BugSprite", function(bugObj)
local imageFileName = bugObj:getImageFileName()
local texture = display.getImage(imageFileName)
if texture==nil then
texture = display.loadImage(imageFileName)
end
local frameWidth = texture:getPixelsWide() / 3
local frameHeight = texture:getPixelsHigh()

local spriteFrame = display.newSpriteFrame(texture, cc.rect(0, 0, frameWidth, frameHeight))
local sprite = display.newSprite(spriteFrame)
sprite.animationName_ = imageFileName
sprite.frameWidth_ = frameWidth
sprite.frameHeight_ = frameHeight

--判断动画是否已经缓存,如果没有则创建
if display.getAnimationCache(sprite.animationName_)==nil then
-- create sprite frame based on image
local frames = {}
for i = 0, 1 do
local frame = display.newSpriteFrame(texture, cc.rect(frameWidth * i, 0, frameWidth, frameHeight))
frames[#frames + 1] = frame
end

-- create animation
local animation = display.newAnimation(frames, bugObj:getAnimationTimes())
-- caching animation
display.setAnimationCache(sprite.animationName_, animation)
end

return sprite
end)

function BugSprite:ctor(bugObj)
self.model_ = bugObj
end

function BugSprite:getModel()
return self.model_
end

function BugSprite:start(destination)
self.model_:setDestination(destination)
self:updatePosition()
self:playAnimationForever(display.getAnimationCache(self.animationName_))
return self
end

function BugSprite:step(dt)
self.model_:step(dt)
self:updatePosition()
return self
end

function BugSprite:updatePosition()
self:move(self.model_:getPosition())
:rotate(self.model_:getRotation())
end

return BugSprite

虫子死亡精灵:
local DeadBugSprite = class("DeadBugSprite", function(bugObj)
local texture = display.getImage(bugObj:getImageFileName())
local frameWidth = texture:getPixelsWide() / 3
local frameHeight = texture:getPixelsHigh()
local spriteFrame = display.newSpriteFrame(texture, cc.rect(frameWidth * 2, 0, frameWidth, frameHeight))
return display.newSprite(spriteFrame)
end)

return DeadBugSprite
这里一并分析说明,原始的逻辑是GameView里面告诉虫子精灵和死亡虫子精灵初始化的资源文件在哪里并如何初始化,神经病一样!正确的做法是:演员自己去看剧本,自行初始化自己的资源,因此这里优化后的虫子精灵只需通过虫子基类的get函数来获取对应的资源进行初始化即可。
这里需要注意的是:
local imageFileName = bugObj:getImageFileName()
local texture = display.getImage(imageFileName)
if texture==nil then
texture = display.loadImage(imageFileName)
end
一开始是getImage是为nil的,只需要调用loadImage加载一次即可,后面的精灵对象便可以使用getImage获取了。死亡虫子精灵获取资源时也应该这样,但是由于死亡虫子精灵和虫子精灵使用的资源是同一个图片,因此前面加载过了这里就可以直接使用了,这里对资源的操作暂时不做修改了。

死亡虫子精灵和虫子精灵的类初始化函数(暂时这么叫吧,以便于区分后面的构造函数)由原来的接受资源文件名改为直接接受虫子对象,然后通过get函数来获取它们具体的资源文件名,这样清晰多了。那么GameView的初始化就变得相当简单了,我们看看原来GameView是如何添加虫子和虫子精灵的:
function GameView:addBug()
local bugType = BugBase.BUG_TYPE_ANT
if math.random(1, 2) % 2 == 0 then
bugType = BugBase.BUG_TYPE_SPIDER
end

local bugModel
if bugType == BugBase.BUG_TYPE_ANT then
bugModel = BugAnt:create()
else
bugModel = BugSpider:create()
end

local bug = BugSprite:create(GameView.IMAGE_FILENAMES[bugType], bugModel)
:start(GameView.HOLE_POSITION)
:addTo(self.bugsNode_, GameView.ZORDER_BUG)

self.bugs_[bug] = bug
return self
end
再看看优化后的:
function GameView:addBug()
local bugClass = {BugAnt, BugSpider}
local index = math.random(1, #bugClass)
local bugObj = bugClass[index]:create()

local bug = BugSprite:create(bugObj)
:start(GameView.HOLE_POSITION)
:addTo(self.bugsNode_, GameView.ZORDER_BUG)

self.bugs_[bug] = bug
return self
end
打个比方,好比现在这一幕剧情需要一群群众演员,导演的要求是这些群众演员随机出现即可,只需要演员各自扮演好即可。这里,有多少虫子类就添加多少个,这里就是蚂蚁和蜘蛛,把它们放在一个数组里,然后随机选取一个让虫子精灵表演,虫子精灵由于已经被我们优化为了只接受一个虫子对象,它内部会调用虫子的get函数获取资源信息进行初始化,也就是演员各自表演好自己的角色。 优化去掉了GameView的onCreate函数中对资源的初始化过程:
-- create animation for bugs
for bugType, filename in pairs(GameView.IMAGE_FILENAMES) do
-- load image
local texture = display.loadImage(filename)
local frameWidth = texture:getPixelsWide() / 3
local frameHeight = texture:getPixelsHigh()

-- create sprite frame based on image
local frames = {}
for i = 0, 1 do
local frame = display.newSpriteFrame(texture, cc.rect(frameWidth * i, 0, frameWidth, frameHeight))
frames[#frames + 1] = frame
end

-- create animation
local animation = display.newAnimation(frames, GameView.BUG_ANIMATION_TIMES[bugType])
-- caching animation
display.setAnimationCache(filename, animation)
end
上面一段代码直接删除,还可以删除的部分:
local BugBase   = import("..models.BugBase")

GameView.IMAGE_FILENAMES = {}
GameView.IMAGE_FILENAMES[BugBase.BUG_TYPE_ANT] = "BugAnt.png"
GameView.IMAGE_FILENAMES[BugBase.BUG_TYPE_SPIDER] = "BugSpider.png"

GameView.BUG_ANIMATION_TIMES = {}
GameView.BUG_ANIMATION_TIMES[BugBase.BUG_TYPE_ANT] = 0.15
GameView.BUG_ANIMATION_TIMES[BugBase.BUG_TYPE_SPIDER] = 0.1
经过以上梳理,现在的逻辑和流程清晰多了,下一节开始MOD成红警塔防游戏。

你可能感兴趣的:(5、cocos2d-Lua的demo--虫子和虫子精灵)