基于组件的游戏编程 CBSE(Componnet Based Software Engineering)

2024-05-04 22:58

本文主要是介绍基于组件的游戏编程 CBSE(Componnet Based Software Engineering),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转自:http://www.cnblogs.com/syncg/archive/2013/01/14/2859122.html


在传统OO编程中.区别于其他语言最大的亮点在于继承.这是一把双刃剑.

优点:

  1. 将数据与逻辑组织的更紧密.
  2. 更进一步的强化了代码与现实的对应关系.

缺点:

  1. 当继承树达到一定规模后.要改某个节点的功能将会很麻烦,我以游戏举例.因为游戏里的类创造性很大.变化也很大.
    看下面类图.这种设计很常见.GameNode里有最基本的属性.比如postion x, y. scale 等等,然后是Living,活物. Item物品.等等.
     

image

 

   现在我要加入一个血条.所有的子类都要加上.怎么办呢?你看了看图.把血条的相应方法加到了GameNode.这样所有子类都能用上了.

image

   看着挺美.然后,你又觉得每个子类都要有价格..哦..再加到GameNode 里去.

image

其实现在已经有问题了.你会发现当你子类公用的类越多.你的父类越来越大.会出现头重脚轻的现象.不过.这还不是关键问题.可能你就是想这样设计:).

继承最大的问题在于.如果我想给Hero 跟 Weapon 加上一个等级的属性怎么办呢?

1.加GameNode 里? Monster本不应该有的属性却有了..

2.再加一个类?让Hero跟Weapn继承? 像下图.

image 
(当出现这样的类结构时.你就要注意了.虽然c++允许多继承.但请记住.如果此类不为子类.一定要是抽象类.具体请看<<Effectvie C++ 2nd>>, 不然.会给你的设计带来无尽的麻烦.)
如果抛开多继承的缺点不谈.还有什么问题?Level适合做父类吗?它明显不满足 IS-A的关系.Hero不是Level. Weapon也不是Level.非要说通.只能以private 去继承.这样:

 

class Hero :private Level {
 
//...
 
};

 

private 继承的主义是根据某类实现.你还得在Hero与Weapon里封装调用Level的方法才算完.绕了一圈才把一个简单的功能加完.

或者你直接在Hero. Weapon分别写Level的功能.写完后.copy. 这是程序员的禁区.技术含量为0的copy导致日后维护的出错率加倍.

 

以上问题在开发游戏时尤其明显.角色的属性,物品的属性,场景的属性改来改去.你永远不能一次性写对类之间的继承关系.所以CBSE(Componnet Based  Software Engineering)出现了.它要解决的就是这个问题.频繁的需求变动带来的类的不确定性.将其影响降到最低的解决方案.如果你的游戏类基本不变.用继承当然就非常科学.

因为CBSE带来了设计灵活也带来了代码的复杂.

那我们来看看如果,将上面的类图改成CBSE的架构会是什么样子呢.如下.

image

哦...下巴掉下来了...乱七八糟的类图...但这就是CBSE的主要思想.以组合模式将组件(IComponent)集中在一起形成实体(Entity).或者说将IS-A的关系重组成HAS-A的关系 . 到这里. 我们需要 围绕Componnet的职责范围,Component之间的通信机制做一些探索.

我们先拿出其中一个类来看.比如说Hero类

 

image

Hero类组合了4个Component.要以代码表示可能是这样

 

class Hero:public Entity
{
 public :
      Move * _move;
      Health* _health;
      Attack* _attack;
      Level*   _level;
};
 

要达到跟继承一样的效果.我们要解决2个问题:

1.怎么通过Hero表现出组件的功能?

   比如说,在直接用继承的时候,你可能直接这样调用 hero->attack(hero2);就完成了攻打英雄的动作.在组件里怎么去模拟呢?

2.组件之间怎么互相通信?

  比如hero被attack,费了血.怎么通知_health组件呢?

由以上两个问题产生了很多种实现CBSE的分支.我们先以最简单的思路去解决

第一个问题:怎么通过Hero表现出组件的功能?

拿Attack组件举例,直觉的想法就是统一 Attack与Hero之间的接口.形成如下类图

 

image

 

多继承.有点奇怪的样子..不用怕.这里继承的全是接口.安全无毒.通过上面类图.我们就可以在Hero 里实现Attack方法,类似如下

Hero::Attack(Entity* entity_)    
{
    _attack->Attack(entity_);
}
Attack::Attack(Entity* entity)
{
       entity->ConsumeHP(100);
}
 

第一个问题也算是解决了.但随之而来的就是第二个问题.我怎么通知entity_所包含的Health组件?

理当在我费血那时,所以在Attack()函数里会调用entity_类里的组件_health的ConsumeHP函数.这里又引出另一个问题.我怎么知道它有Health组件呢?

        改Attack参数?将Entity改成Hero?明显不行.Component不能依赖于实体.不然就不能发挥组件的功用了.

        所以.这个地方也是CBSE变种最多的的地方.因为有n种方法知道这个Entity具有Health组件.(这里最酷的方法当然是通过配置文件)

但我们用一个简单的代码方法.

IHealth*  health=dynamic_cast<IHealth*>(entity_);
if(NULL!=health)
{
   health->ConsumeHP(100);
}
 
Health::ConsumeHP(int health_cost_)
{
//这里扣掉血
//...
if(holder)
_holder->OnConsumeHP(cost_);   //_holder代表Component 的持有者.这里OnConsumeHP方法就通知到了entity血量的变化.做它相应的操作.
 
}
 

  OnConsumeHP这种调用是一种监听机制.理所当然也是一个接口.它跟IHealth的关系很紧密.所以放在IHealth里就很好.只不过.它不是纯虚,而是有空实现的虚方法.如果hero想得到调用,就实现它.

 

image

写到这.CBSE在你脑里应该已经有了一个简单的雏形.

多个Component组成了Entity.多个Entity组成什么呢? 比如说hero身上有Package,而Package里面有weapon.这两者在划分上都应该属于entity.哪怎么组织它们呢?看下面的类图.你会发现Weapon1,Weapon2,Package,Hero,Health,Attack 冥冥中..以某种关联存在着..你努力试着想把这种结构表达给其他人.你需要一个术语------Composite pattern,也即组合模式.  白话文.就是A类中有B类的引用...A与B实现了同样的接口.

image

引用维基Composite pattern图片:

image

 

所以当你实现这一步的时候.又要想一个问题了.怎么能够精确的索引到我想到的每个节点呢?现在给你一个头节点Hero.怎么方便的得到每个孩子节点的索引呢?比如说拿到weapon1.因为这个索引问题.又引出了许多CBSE的变种.. 没错..只要你实现了这个功能.就是对的.没有唯一的方法.你可以给每个节点打上tag.你可以将这个树状结构放入hashmap.

基于接口的组件实现在项目规模不大时很不错.但如果规模一大.比如说一个entity类有20个组件...难道要继承20个接口..?当然.地球人阻止不了你.但第二部份我将介绍另外一种CBSE.请稍等几年.

基本的CBSE我已经全部讲完了.接下来的文章我将写一个比较酷的CBSE变种.

 

 

 

 

这篇关于基于组件的游戏编程 CBSE(Componnet Based Software Engineering)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/960276

相关文章

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

Vue中组件之间传值的六种方式(完整版)

《Vue中组件之间传值的六种方式(完整版)》组件是vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用,针对不同的使用场景,如何选择行之有效的通信方式... 目录前言方法一、props/$emit1.父组件向子组件传值2.子组件向父组件传值(通过事件形式)方

Python异步编程中asyncio.gather的并发控制详解

《Python异步编程中asyncio.gather的并发控制详解》在Python异步编程生态中,asyncio.gather是并发任务调度的核心工具,本文将通过实际场景和代码示例,展示如何结合信号量... 目录一、asyncio.gather的原始行为解析二、信号量控制法:给并发装上"节流阀"三、进阶控制

Spring组件初始化扩展点BeanPostProcessor的作用详解

《Spring组件初始化扩展点BeanPostProcessor的作用详解》本文通过实战案例和常见应用场景详细介绍了BeanPostProcessor的使用,并强调了其在Spring扩展中的重要性,感... 目录一、概述二、BeanPostProcessor的作用三、核心方法解析1、postProcessB

kotlin中的行为组件及高级用法

《kotlin中的行为组件及高级用法》Jetpack中的四大行为组件:WorkManager、DataBinding、Coroutines和Lifecycle,分别解决了后台任务调度、数据驱动UI、异... 目录WorkManager工作原理最佳实践Data Binding工作原理进阶技巧Coroutine

Vue项目的甘特图组件之dhtmlx-gantt使用教程和实现效果展示(推荐)

《Vue项目的甘特图组件之dhtmlx-gantt使用教程和实现效果展示(推荐)》文章介绍了如何使用dhtmlx-gantt组件来实现公司的甘特图需求,并提供了一个简单的Vue组件示例,文章还分享了一... 目录一、首先 npm 安装插件二、创建一个vue组件三、业务页面内 引用自定义组件:四、dhtmlx

Vue ElementUI中Upload组件批量上传的实现代码

《VueElementUI中Upload组件批量上传的实现代码》ElementUI中Upload组件批量上传通过获取upload组件的DOM、文件、上传地址和数据,封装uploadFiles方法,使... ElementUI中Upload组件如何批量上传首先就是upload组件 <el-upl

Vue3中的动态组件详解

《Vue3中的动态组件详解》本文介绍了Vue3中的动态组件,通过`component:is=动态组件名或组件对象/component`来实现根据条件动态渲染不同的组件,此外,还提到了使用`markRa... 目录vue3动态组件动态组件的基本使用第一种写法第二种写法性能优化解决方法总结Vue3动态组件动态

C#多线程编程中导致死锁的常见陷阱和避免方法

《C#多线程编程中导致死锁的常见陷阱和避免方法》在C#多线程编程中,死锁(Deadlock)是一种常见的、令人头疼的错误,死锁通常发生在多个线程试图获取多个资源的锁时,导致相互等待对方释放资源,最终形... 目录引言1. 什么是死锁?死锁的典型条件:2. 导致死锁的常见原因2.1 锁的顺序问题错误示例:不同