• cocos2d-x开发: 场景实体(entity)管理


    公司现在开新项目,主题的框架部分都是我自己在做,不用受到别人的牵制,所以还算是比较的自由,很好发挥. 游戏并不大,所以需要用到的地方并不多.今天花了一些时间写了场景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 )方式实现的.

    管理类的源码如下:

     1 local entity_manager = class( "entity_manager", nil )
     2 
     3 function entity_manager:ctor()
     4     self.entity_list_ = {}
     5     self.entity_nums_ = 0
     6 end
     7 
     8 function entity_manager:register_entity( entity )
     9     if entity == nil then
    10         return
    11     end
    12 
    13     local entity_rd
    14     entity_rd = self.entity_nums_ + 1
    15     entity:set_runtime_id( entity_rd )
    16 
    17     table.insert( self.entity_list_, entity )
    18     self.entity_nums_ = entity_rd
    19 end
    20 
    21 function entity_manager:remove_entity( entity )
    22     if entity == nil then
    23         return
    24     end
    25 
    26     local entity_rd
    27     entity_rd = entity:get_runtime_rd()
    28 
    29     if entity_rd > self.entity_nums_ then
    30         return
    31     end
    32     entity:remove_from_node()
    33     table.remove( self.entity_list_, entity_rd )
    34     for index, v_t in ipairs( self.entity_list_ ) do
    35         if index >= entity_rd then
    36             v_t:set_runtime_id( index )
    37         end
    38     end
    39     self.entity_nums_ = #self.entity_list_
    40 end
    41 
    42 function entity_manager:update( dt )
    43     for _, v_t in ipairs( self.entity_list_ ) do
    44         v_t:update( dt )
    45     end
    46 end
    47 
    48 return entity_manager

    提供一个概念就是runtime entity id, 我们不能保证一个类型只可以创建一个对象,这是不合理的,所以除了entity的uniqure_id之外,就提供了运行时候的id。由于这个运行时候的id是动态的,所以在remove的时候需要更新一下,也就是上面table.remove下面的操作。下面是entity的代码:

     1 local entity = class( "entity", nil )
     2 
     3 entity.debug_mode_ = true
     4 entity.debug_color_ = cc.c4f( 0, 1, 0, 1 )
     5 entity.callback_list_ = {}
     6 entity.runtime_id_ = nil
     7 
     8 function entity:set_debug_mode( mode )
     9     self.debug_mode_ = mode
    10 end
    11 
    12 function entity:get_debug_mode()
    13     return self.debug_mode_
    14 end
    15 
    16 function entity:set_debug_color( color )
    17     self.debug_color_ = color
    18 end
    19 
    20 function entity:get_debug_color()
    21     return self.debug_color_
    22 end
    23 
    24 function entity:set_runtime_id( runtime_id )
    25     self.runtime_id_ = runtime_id
    26 end
    27 
    28 function entity:get_runtime_id()
    29     return self.runtime_id_
    30 end
    31 
    32 function entity:register_callback( callback, target )
    33     if callback == nil then
    34         return
    35     end
    36 
    37     for _, v_t in ipairs( self.callback_list_ ) do
    38         if v_t[1] == callback and v_t[2] == target then
    39             return
    40         end
    41     end
    42 
    43     table.insert( self.callback_list_, { callback, target } )
    44 end
    45 
    46 function entity:remove_callback( callback, target )
    47     if callback == nil then
    48         return
    49     end
    50 
    51     for index, v_t in ipairs( self.callback_list_ ) do
    52         if v_t[1] == callback and v_t[2] == target then
    53             table.remove( self.callback_list_, index )
    54         end
    55     end
    56 end
    57 
    58 function entity:update( dt )
    59     local callback
    60     local target
    61     for _, v_t in ipairs( self.callback_list_ ) do
    62         callback = v_t[1]
    63         target = v_t[2]
    64         if target ~= nil then
    65             callback( target, dt )
    66         else
    67             callback( dt )
    68         end
    69     end
    70 end
    71 
    72 return entity

    这是一个基类的实现,不算复杂,也很好理解,就没什么好说的了.为什么要写一个entity base类呢? 因为现在的项目可能会用序列帧,也可能会用骨骼动画. 如果是骨骼动画的话,那么所有的action都比较好处理, bounding_box也很好获得.相应的接口就是

    1 armature:getAnimation():play( action_const_name )
    2 
    3 local bounding_box = armature:getBoundingBox() 

    然后就可以很简单的使用AABB或者是OBB进行碰撞检测以及设置AI等这些杂七杂八的东西了. 我们都知道序列帧使用的时候就没有那么方便了,在面对.png和Plist这样的组合的时候,动作的处理需要自己去解析,而Boundingbox也需要根据当前执行的动作做状态监测.好吧,废话说的有点多了,我简单实现了部分骨骼实体的封装,代码如下:

     1 local entity = require "src.firework.entity.entity"
     2 
     3 local skeleton_entity = class( "skeleton_entity", entity )
     4     
     5 function skeleton_entity:ctor( armature_const_name )
     6     self.skeleton_armature_ = nil
     7     self.draw_debug_node_ = nil
     8 
     9     self.skeleton_armature_ = ccs.Armature:create( armature_const_name )
    10     self.draw_debug_node_ = cc.DrawNode:create()
    11     self.skeleton_armature_:addChild( self.draw_debug_node_ )
    12     self:init_callbacks()        
    13 end    
    14 
    15 function skeleton_entity:play( const_action_name )
    16     self.skeleton_armature_:getAnimation():play( const_action_name )
    17 end
    18 
    19 function skeleton_entity:init_callbacks()
    20     self:register_callback( self.draw_debug_bounding_box, self )
    21 end
    22 
    23 function skeleton_entity:set_anchor_point( anchor_point )
    24     self.skeleton_armature_:setAnchorPoint( anchor_point )
    25 end
    26 
    27 function skeleton_entity:get_anchor_point()
    28     return self.skeleton_armature_:getAnchorPoint()
    29 end
    30 
    31 function skeleton_entity:set_position( position )
    32     self.skeleton_armature_:setPosition( position )
    33 end
    34 
    35 function skeleton_entity:get_position()
    36     return self.skeleton_armature_:getPosition()
    37 end
    38 
    39 function skeleton_entity:add_to_node( node )
    40     node:addChild( self.skeleton_armature_ )
    41 end
    42 
    43 function skeleton_entity:remove_from_node()
    44     self:remove_callback( self.draw_debug_bounding_box, self )
    45     self.skeleton_armature_:getParent():removeChild( self.skeleton_armature_ )
    46 end
    47 
    48 function skeleton_entity:get_bounding_box()
    49     return self.skeleton_armature_:getBoundingBox()
    50 end
    51 
    52 function skeleton_entity:draw_debug_bounding_box( dt )
    53     local bounding_box = self:get_bounding_box()
    54     local lb = self.skeleton_armature_:convertToNodeSpace( cc.p( bounding_box.x, bounding_box.y ) )
    55     local lt = self.skeleton_armature_:convertToNodeSpace( cc.p( bounding_box.x, bounding_box.y + bounding_box.height ) )
    56     local rt = self.skeleton_armature_:convertToNodeSpace( cc.p( bounding_box.x + bounding_box.width, bounding_box.y + bounding_box.height ) )
    57     local rb = self.skeleton_armature_:convertToNodeSpace( cc.p( bounding_box.x + bounding_box.width, bounding_box.y ) )
    58 
    59     self.draw_debug_node_:clear()
    60     self.draw_debug_node_:drawLine( lb, lt, self:get_debug_color() )
    61     self.draw_debug_node_:drawLine( lt, rt, self:get_debug_color() )
    62     self.draw_debug_node_:drawLine( rt, rb, self:get_debug_color() )
    63     self.draw_debug_node_:drawLine( rb, lb, self:get_debug_color() )
    64 end
    65 
    66 return skeleton_entity

    我只是单纯的去获取创建Armatrue骨骼,没有加载资源,因为资源加载部分肯定是单独做的,这里只是顺便提一下.现在实现的部分代码只是单纯的画出了Boundingbox的区域,其他的还都没做.下面给出我的unittest部分的源码:

     1 local test_case = require "src.unittest.test_case"
     2 local skeleton_entity = require "src.firework.entity.skeleton_entity"
     3 local entity_manager = require "src.firework.entity.entity_manager"
     4 local visible_rect = require "src.firework.visible_rect"
     5 local test_entity_manager_case = class( "test_entity_manager_case", test_case )
     6 
     7 local scene = cc.Scene:create()
     8 if cc.Director:getInstance():getRunningScene() then
     9     cc.Director:getInstance():replaceScene( scene )
    10 else
    11     cc.Director:getInstance():runWithScene( scene )
    12 end
    13 
    14 function test_entity_manager_case:run_impl()
    15     ccs.ArmatureDataManager:getInstance():addArmatureFileInfo( "Hero/Hero0.png", "Hero/Hero0.plist", "Hero/Hero.ExportJson" )
    16     local entity_manager_ins = entity_manager.new()
    17     local skeleton_entity_ins = skeleton_entity.new( "Hero" ) 
    18     skeleton_entity_ins:play( "loading" )
    19     skeleton_entity_ins:set_anchor_point( cc.p( 0.5, 0.5 ) )
    20     skeleton_entity_ins:set_position( visible_rect:center() )
    21     skeleton_entity_ins:add_to_node( scene )
    22 
    23     ccs.ArmatureDataManager:getInstance():addArmatureFileInfo( "tauren/tauren0.png", "tauren/tauren0.plist", "tauren/tauren.ExportJson" )
    24     local skeleton_entity_ins1 = skeleton_entity.new( "tauren" )
    25     skeleton_entity_ins1:play( "loading" )
    26     skeleton_entity_ins1:set_anchor_point( cc.p( 0.5, 0.5 ) )
    27     skeleton_entity_ins1:set_position( visible_rect:right_center() )
    28     skeleton_entity_ins1:add_to_node( scene )
    29 
    30     entity_manager_ins:register_entity( skeleton_entity_ins )
    31     entity_manager_ins:register_entity( skeleton_entity_ins1 )
    32     
    33     local scheduler = cc.Director:getInstance():getScheduler()
    34     scheduler:scheduleScriptFunc( function ( dt )
    35         entity_manager_ins:update( dt )
    36     end, 0, false )
    37 end
    38 
    39 return test_entity_manager_case

    把这些代码加到test_controller中就好了.代码如下:

     1 local fmt_logger = require "src.firework.fmt_logger"
     2 
     3 local test_controller = class( "test_controller", nil )
     4 
     5 function test_controller:ctor()
     6     fmt_logger.trace("---------------------------------------------------------")
     7     fmt_logger.info("        running mode: [" .. self.__cname .. "]               ")
     8 end
     9 
    10 function test_controller:run()
    11     
    12     require "src.unittest.test_case"
    13     get_test_case_sample().new():run()
    14 
    15     local test_fmt_logger_case = require "src.unittest.firework.test_fmt_logger_case"
    16     test_fmt_logger_case.new():run()
    17 
    18     local test_default_dispatcher_case = require "src.unittest.firework.test_default_dispatcher_case"
    19     test_default_dispatcher_case.new():run()
    20 
    21     local test_g_firework_case = require "src.unittest.firework.test_g_firework_case"
    22     test_g_firework_case.new():run()
    23 
    24     local test_event_dispatcher_case = require "src.unittest.firework.test_event_dispatcher_case"
    25     test_event_dispatcher_case.new():run()
    26 
    27     local test_measure_manager_case = require "src.unittest.firework.test_measure_manager_case"
    28     test_measure_manager_case.new():run()
    29 
    30     local test_layer_update_case = require "src.unittest.firework.test_layer_update_case"
    31     --test_layer_update_case.new():run()
    32 
    33     local test_entity_manager_case = require "src.unittest.firework.test_entity_manager_case"
    34     test_entity_manager_case.new():run()
    35 
    36 end
    37 
    38 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绘制模式.

  • 相关阅读:
    【测试基础】晕晕乎乎的bug
    【测试基础】你写过测试计划和测试报告吗?
    【工具】数据库基本概念及MySQL安装
    【测试基础】Linux 系统及进程管理类命令
    【测试基础】Linux系统组成、目录结构及基本命令
    【测试基础】Linux打包、解包、解压缩命令这一篇全搞定
    【配置】Linux的安装及配置
    【测试基础】Linux查找、过滤基本命令总结
    Css中zoom属性的介绍
    2022软件代码开发技术:作业一
  • 原文地址:https://www.cnblogs.com/respawn/p/4261220.html
Copyright © 2020-2023  润新知