cocos2d-x开发: 场景实体(entity)管理
2015-01-29 22:47
447 查看
公司现在开新项目,主题的框架部分都是我自己在做,不用受到别人的牵制,所以还算是比较的自由,很好发挥. 游戏并不大,所以需要用到的地方并不多.今天花了一些时间写了场景entity管理的部分代码,还没有完全的完善.
我的思路是这样的, entity manager提供注册一个update( dt )的帧频回调事件, 在每一次回调的时候都会遍历管理的所有的entity,调用entity的update( dt )帧频回调.何为帧频回调?我解释一下,cocos2d-x在c++那边是可以重写onDraw()方法实现的.在lua这边的话,如果是从cocos2d::node派生的子类,也就是使用 node = class( "node", function() return cc.Node:create() end),然后也可以注册schedulexxxx系列的方法,具体的可以去参考源码.由于我的管理类和entity都不是node派生的,所以我在提供了管理类的update( dt )回调,通过cc.Director:getInstance():getScheduler(),然后调用scheduler:scheduleScripteFunc( function(dt) end,0,false )方式实现的.
管理类的源码如下:
提供一个概念就是runtime entity id, 我们不能保证一个类型只可以创建一个对象,这是不合理的,所以除了entity的uniqure_id之外,就提供了运行时候的id。由于这个运行时候的id是动态的,所以在remove的时候需要更新一下,也就是上面table.remove下面的操作。下面是entity的代码:
这是一个基类的实现,不算复杂,也很好理解,就没什么好说的了.为什么要写一个entity base类呢? 因为现在的项目可能会用序列帧,也可能会用骨骼动画. 如果是骨骼动画的话,那么所有的action都比较好处理, bounding_box也很好获得.相应的接口就是
然后就可以很简单的使用AABB或者是OBB进行碰撞检测以及设置AI等这些杂七杂八的东西了. 我们都知道序列帧使用的时候就没有那么方便了,在面对.png和Plist这样的组合的时候,动作的处理需要自己去解析,而Boundingbox也需要根据当前执行的动作做状态监测.好吧,废话说的有点多了,我简单实现了部分骨骼实体的封装,代码如下:
我只是单纯的去获取创建Armatrue骨骼,没有加载资源,因为资源加载部分肯定是单独做的,这里只是顺便提一下.现在实现的部分代码只是单纯的画出了Boundingbox的区域,其他的还都没做.下面给出我的unittest部分的源码:
把这些代码加到test_controller中就好了.代码如下:
unittest这一套是我自己写的,只是为了自己用着方便, 如果需要知道如何实现的,请去参考前面文章.我在写代码分离模块的时候写过这部分的代码.
cocos版本是cocos2d-x 3.3 final. 如果是用 <3.3版本或者是2.x版本,相信修改少量的代码就可以了.就到这里了。
2015-1-30 修正: 将entity.lua 属性初始化放到一个entity:_init()函数中.我不知道看过这篇文章的人有没有看出问题. 这里的问题是update调用的callback_list_将包含所有entity派生子类的callback函数。 导致在一次update过程中执行的速度非常的慢.我原本以为是gl渲染速度的问题.
虽然我开启debug模式打印boundingbox,但是跑到100就会很卡. 果断是发现entity:update()中执行有问题. 所以做了一次调整之后轻松跑到两百个骨骼同时在一个场景中渲染.而且我还开启了debug gl绘制模式.
我的思路是这样的, entity manager提供注册一个update( dt )的帧频回调事件, 在每一次回调的时候都会遍历管理的所有的entity,调用entity的update( dt )帧频回调.何为帧频回调?我解释一下,cocos2d-x在c++那边是可以重写onDraw()方法实现的.在lua这边的话,如果是从cocos2d::node派生的子类,也就是使用 node = class( "node", function() return cc.Node:create() end),然后也可以注册schedulexxxx系列的方法,具体的可以去参考源码.由于我的管理类和entity都不是node派生的,所以我在提供了管理类的update( dt )回调,通过cc.Director:getInstance():getScheduler(),然后调用scheduler:scheduleScripteFunc( function(dt) end,0,false )方式实现的.
管理类的源码如下:
local entity_manager = class( "entity_manager", nil ) function entity_manager:ctor() self.entity_list_ = {} self.entity_nums_ = 0 end function entity_manager:register_entity( entity ) if entity == nil then return end local entity_rd entity_rd = self.entity_nums_ + 1 entity:set_runtime_id( entity_rd ) table.insert( self.entity_list_, entity ) self.entity_nums_ = entity_rd end function entity_manager:remove_entity( entity ) if entity == nil then return end local entity_rd entity_rd = entity:get_runtime_rd() if entity_rd > self.entity_nums_ then return end entity:remove_from_node() table.remove( self.entity_list_, entity_rd ) for index, v_t in ipairs( self.entity_list_ ) do if index >= entity_rd then v_t:set_runtime_id( index ) end end self.entity_nums_ = #self.entity_list_ end function entity_manager:update( dt ) for _, v_t in ipairs( self.entity_list_ ) do v_t:update( dt ) end end return entity_manager
提供一个概念就是runtime entity id, 我们不能保证一个类型只可以创建一个对象,这是不合理的,所以除了entity的uniqure_id之外,就提供了运行时候的id。由于这个运行时候的id是动态的,所以在remove的时候需要更新一下,也就是上面table.remove下面的操作。下面是entity的代码:
local entity = class( "entity", nil ) entity.debug_mode_ = true entity.debug_color_ = cc.c4f( 0, 1, 0, 1 ) entity.callback_list_ = {} entity.runtime_id_ = nil function entity:set_debug_mode( mode ) self.debug_mode_ = mode end function entity:get_debug_mode() return self.debug_mode_ end function entity:set_debug_color( color ) self.debug_color_ = color end function entity:get_debug_color() return self.debug_color_ end function entity:set_runtime_id( runtime_id ) self.runtime_id_ = runtime_id end function entity:get_runtime_id() return self.runtime_id_ end function entity:register_callback( callback, target ) if callback == nil then return end for _, v_t in ipairs( self.callback_list_ ) do if v_t[1] == callback and v_t[2] == target then return end end table.insert( self.callback_list_, { callback, target } ) end function entity:remove_callback( callback, target ) if callback == nil then return end for index, v_t in ipairs( self.callback_list_ ) do if v_t[1] == callback and v_t[2] == target then table.remove( self.callback_list_, index ) end end end function entity:update( dt ) local callback local target for _, v_t in ipairs( self.callback_list_ ) do callback = v_t[1] target = v_t[2] if target ~= nil then callback( target, dt ) else callback( dt ) end end end return entity
这是一个基类的实现,不算复杂,也很好理解,就没什么好说的了.为什么要写一个entity base类呢? 因为现在的项目可能会用序列帧,也可能会用骨骼动画. 如果是骨骼动画的话,那么所有的action都比较好处理, bounding_box也很好获得.相应的接口就是
armature:getAnimation():play( action_const_name ) local bounding_box = armature:getBoundingBox()
然后就可以很简单的使用AABB或者是OBB进行碰撞检测以及设置AI等这些杂七杂八的东西了. 我们都知道序列帧使用的时候就没有那么方便了,在面对.png和Plist这样的组合的时候,动作的处理需要自己去解析,而Boundingbox也需要根据当前执行的动作做状态监测.好吧,废话说的有点多了,我简单实现了部分骨骼实体的封装,代码如下:
local entity = require "src.firework.entity.entity" local skeleton_entity = class( "skeleton_entity", entity ) function skeleton_entity:ctor( armature_const_name ) self.skeleton_armature_ = nil self.draw_debug_node_ = nil self.skeleton_armature_ = ccs.Armature:create( armature_const_name ) self.draw_debug_node_ = cc.DrawNode:create() self.skeleton_armature_:addChild( self.draw_debug_node_ ) self:init_callbacks() end function skeleton_entity:play( const_action_name ) self.skeleton_armature_:getAnimation():play( const_action_name ) end function skeleton_entity:init_callbacks() self:register_callback( self.draw_debug_bounding_box, self ) end function skeleton_entity:set_anchor_point( anchor_point ) self.skeleton_armature_:setAnchorPoint( anchor_point ) end function skeleton_entity:get_anchor_point() return self.skeleton_armature_:getAnchorPoint() end function skeleton_entity:set_position( position ) self.skeleton_armature_:setPosition( position ) end function skeleton_entity:get_position() return self.skeleton_armature_:getPosition() end function skeleton_entity:add_to_node( node ) node:addChild( self.skeleton_armature_ ) end function skeleton_entity:remove_from_node() self:remove_callback( self.draw_debug_bounding_box, self ) self.skeleton_armature_:getParent():removeChild( self.skeleton_armature_ ) end function skeleton_entity:get_bounding_box() return self.skeleton_armature_:getBoundingBox() end function skeleton_entity:draw_debug_bounding_box( dt ) local bounding_box = self:get_bounding_box() local lb = self.skeleton_armature_:convertToNodeSpace( cc.p( bounding_box.x, bounding_box.y ) ) local lt = self.skeleton_armature_:convertToNodeSpace( cc.p( bounding_box.x, bounding_box.y + bounding_box.height ) ) local rt = self.skeleton_armature_:convertToNodeSpace( cc.p( bounding_box.x + bounding_box.width, bounding_box.y + bounding_box.height ) ) local rb = self.skeleton_armature_:convertToNodeSpace( cc.p( bounding_box.x + bounding_box.width, bounding_box.y ) ) self.draw_debug_node_:clear() self.draw_debug_node_:drawLine( lb, lt, self:get_debug_color() ) self.draw_debug_node_:drawLine( lt, rt, self:get_debug_color() ) self.draw_debug_node_:drawLine( rt, rb, self:get_debug_color() ) self.draw_debug_node_:drawLine( rb, lb, self:get_debug_color() ) end return skeleton_entity
我只是单纯的去获取创建Armatrue骨骼,没有加载资源,因为资源加载部分肯定是单独做的,这里只是顺便提一下.现在实现的部分代码只是单纯的画出了Boundingbox的区域,其他的还都没做.下面给出我的unittest部分的源码:
local test_case = require "src.unittest.test_case" local skeleton_entity = require "src.firework.entity.skeleton_entity" local entity_manager = require "src.firework.entity.entity_manager" local visible_rect = require "src.firework.visible_rect" local test_entity_manager_case = class( "test_entity_manager_case", test_case ) local scene = cc.Scene:create() if cc.Director:getInstance():getRunningScene() then cc.Director:getInstance():replaceScene( scene ) else cc.Director:getInstance():runWithScene( scene ) end function test_entity_manager_case:run_impl() ccs.ArmatureDataManager:getInstance():addArmatureFileInfo( "Hero/Hero0.png", "Hero/Hero0.plist", "Hero/Hero.ExportJson" ) local entity_manager_ins = entity_manager.new() local skeleton_entity_ins = skeleton_entity.new( "Hero" ) skeleton_entity_ins:play( "loading" ) skeleton_entity_ins:set_anchor_point( cc.p( 0.5, 0.5 ) ) skeleton_entity_ins:set_position( visible_rect:center() ) skeleton_entity_ins:add_to_node( scene ) ccs.ArmatureDataManager:getInstance():addArmatureFileInfo( "tauren/tauren0.png", "tauren/tauren0.plist", "tauren/tauren.ExportJson" ) local skeleton_entity_ins1 = skeleton_entity.new( "tauren" ) skeleton_entity_ins1:play( "loading" ) skeleton_entity_ins1:set_anchor_point( cc.p( 0.5, 0.5 ) ) skeleton_entity_ins1:set_position( visible_rect:right_center() ) skeleton_entity_ins1:add_to_node( scene ) entity_manager_ins:register_entity( skeleton_entity_ins ) entity_manager_ins:register_entity( skeleton_entity_ins1 ) local scheduler = cc.Director:getInstance():getScheduler() scheduler:scheduleScriptFunc( function ( dt ) entity_manager_ins:update( dt ) end, 0, false ) end return test_entity_manager_case
把这些代码加到test_controller中就好了.代码如下:
local fmt_logger = require "src.firework.fmt_logger" local test_controller = class( "test_controller", nil ) function test_controller:ctor() fmt_logger.trace("---------------------------------------------------------") fmt_logger.info(" running mode: [" .. self.__cname .. "] ") end function test_controller:run() require "src.unittest.test_case" get_test_case_sample().new():run() local test_fmt_logger_case = require "src.unittest.firework.test_fmt_logger_case" test_fmt_logger_case.new():run() local test_default_dispatcher_case = require "src.unittest.firework.test_default_dispatcher_case" test_default_dispatcher_case.new():run() local test_g_firework_case = require "src.unittest.firework.test_g_firework_case" test_g_firework_case.new():run() local test_event_dispatcher_case = require "src.unittest.firework.test_event_dispatcher_case" test_event_dispatcher_case.new():run() local test_measure_manager_case = require "src.unittest.firework.test_measure_manager_case" test_measure_manager_case.new():run() local test_layer_update_case = require "src.unittest.firework.test_layer_update_case" --test_layer_update_case.new():run() local test_entity_manager_case = require "src.unittest.firework.test_entity_manager_case" test_entity_manager_case.new():run() end return test_controller
unittest这一套是我自己写的,只是为了自己用着方便, 如果需要知道如何实现的,请去参考前面文章.我在写代码分离模块的时候写过这部分的代码.
cocos版本是cocos2d-x 3.3 final. 如果是用 <3.3版本或者是2.x版本,相信修改少量的代码就可以了.就到这里了。
2015-1-30 修正: 将entity.lua 属性初始化放到一个entity:_init()函数中.我不知道看过这篇文章的人有没有看出问题. 这里的问题是update调用的callback_list_将包含所有entity派生子类的callback函数。 导致在一次update过程中执行的速度非常的慢.我原本以为是gl渲染速度的问题.
虽然我开启debug模式打印boundingbox,但是跑到100就会很卡. 果断是发现entity:update()中执行有问题. 所以做了一次调整之后轻松跑到两百个骨骼同时在一个场景中渲染.而且我还开启了debug gl绘制模式.
相关文章推荐
- 【iOS-Cocos2d游戏开发】使用cocosBuiler制作cocos2d场景
- 【iOS-Cocos2d游戏开发】仿愤怒的小鸟 场景缩放
- 【Cocos2d-X开发学习笔记】第04期:渲染框架之场景类(CCScene)的使用
- cocos2d-x源码剖析之场景管理
- 守卫者游戏开发教程之cocos2d-iphone2.0第二部分(选择游戏场景)
- [3D游戏开发]场景管理
- 游戏系统开发笔记(八)——场景对象管理
- entityManagerFactory(实体管理工厂)
- 【Cocos2d-X游戏实战开发】捕鱼达人之开始场景的创建(三)
- cocos2d开发学习四:场景过渡效果,以及过渡场景的作用
- OgreOde——遍历场景中所有的实体(Entity)
- 【2.0版本cocos2d-iphone 开发之转换场景时函数的调用顺序】
- Eclipse Cocos2d-x 开发自动管理
- cocos2d-x游戏开发(十二)场景切换:滑门效果
- 【iOS-Cocos2d游戏开发】使用cocosBuiler制作cocos2d场景
- 【Cocos2d-x游戏引擎开发笔记(10)】自定义场景和场景跳转
- 【Cocos2d-X游戏实战开发】捕鱼达人之游戏场景的创建(六)
- cocos2d-x游戏开发(十)执行单元场景CCScene
- 【Cocos2d-X开发学习笔记】第19期:动作管理类(CCActionManager)的使用
- 【Cocos2d-X开发学习笔记】第19期:动作管理类(CCActionManager)的使用