基于组件的游戏编程 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

相关文章

PyQt6中QMainWindow组件的使用详解

《PyQt6中QMainWindow组件的使用详解》QMainWindow是PyQt6中用于构建桌面应用程序的基础组件,本文主要介绍了PyQt6中QMainWindow组件的使用,具有一定的参考价值,... 目录1. QMainWindow 组php件概述2. 使用 QMainWindow3. QMainW

Python 异步编程 asyncio简介及基本用法

《Python异步编程asyncio简介及基本用法》asyncio是Python的一个库,用于编写并发代码,使用协程、任务和Futures来处理I/O密集型和高延迟操作,本文给大家介绍Python... 目录1、asyncio是什么IO密集型任务特征2、怎么用1、基本用法2、关键字 async1、async

Python开发文字版随机事件游戏的项目实例

《Python开发文字版随机事件游戏的项目实例》随机事件游戏是一种通过生成不可预测的事件来增强游戏体验的类型,在这篇博文中,我们将使用Python开发一款文字版随机事件游戏,通过这个项目,读者不仅能够... 目录项目概述2.1 游戏概念2.2 游戏特色2.3 目标玩家群体技术选择与环境准备3.1 开发环境3

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.

Vue3组件中getCurrentInstance()获取App实例,但是返回null的解决方案

《Vue3组件中getCurrentInstance()获取App实例,但是返回null的解决方案》:本文主要介绍Vue3组件中getCurrentInstance()获取App实例,但是返回nu... 目录vue3组件中getCurrentInstajavascriptnce()获取App实例,但是返回n

shell编程之函数与数组的使用详解

《shell编程之函数与数组的使用详解》:本文主要介绍shell编程之函数与数组的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录shell函数函数的用法俩个数求和系统资源监控并报警函数函数变量的作用范围函数的参数递归函数shell数组获取数组的长度读取某下的

SpringQuartz定时任务核心组件JobDetail与Trigger配置

《SpringQuartz定时任务核心组件JobDetail与Trigger配置》Spring框架与Quartz调度器的集成提供了强大而灵活的定时任务解决方案,本文主要介绍了SpringQuartz定... 目录引言一、Spring Quartz基础架构1.1 核心组件概述1.2 Spring集成优势二、J

揭秘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.子组件向父组件传值(通过事件形式)方