[转载]游戏里的实体系统 – 游戏开发日志–向恺然 – 博客园.
游戏世界是一个乱哄哄的世界,乱哄哄的各种各样的人物,怪物奔跑着,发射着五 花八门的子弹;各种古里古怪的地形,会吃人的建筑物;如果“上帝”愿意,随时可以刮风下雨。哦,MG!我的头脑里容不下这么多东西了,这简直是场灾难。
我希望把他们统一起来,而不是区别对待;我希望用一种逻辑来控制管理他们,而 不是一万种。
可以做到吗?
很高兴,可以。
把这些游戏元素统一成一个整体,并且提供一种通用的方法来管理,这是非常重要 的。这就是“游戏实体系统”,把许许多多各异的元素都看成实体,不论它是操控的英雄,还是张牙舞爪的怪物,或者是个里面藏着包子的坛子(你玩过三国,不是 吗?),或者是漂浮在空中的云彩;甚至是只有游戏开发者才知道的脚本,地形,游戏的根容器……在这里,统统被看成了实体。而实体之间是通过各种各 样的消息来传递信息,做出反应的。所以这一切被称作了“基于消息的实体系统”。
大家如果读过《游戏里的调度器》,应该还记得我提到过,游戏基于轮询的结构和基于消息的结构。在基于消息的系统中,实体是懒惰的,是无所事事的,除非…除非它收到了一条 消息,而且它还需要能够理解这条消息。
- 我们把所有能看成是实体的东西都看成是实体
- 实体之间通过消息进行通信
接下来我们把上面两句话进行扩展并具体实现,请相信我,这一章非常的重要,而 且很有用,并且不是非常繁难。我衷心希望您能够掌握。
实体的管理:
假设已经把所有的一切都看作了实体,也就是说我们拥有了很多很多的实体,毫无 疑问,我们不能够让这么多的实体杂乱无章的放着,需要把它们组织起来。让我们想一想,应该如何组织这些实体呢?我们是游戏中的上帝,在创世之初做些什么 呢?或许你应该创立一个根实体,你可以命名为GameRoot,在这个实体上可以生长其他实体,具体有哪些,要看具体情况,但我想,应该有若干个场景实体 吧,在场景实体上跑动着很多NPC,那么NPC实体也就是存在的了,或许NPC手里还有一把来复枪或者激光炮之类的,这些可怕的武器还能够发射出子弹实 体;回顾一下,这会是什么?这肯定是棵树。一棵枝枝丫丫的树。当然,如果你愿意,你可以用任何其他的方式来组织管理你的实体们,你可以用数组,链表,或者 矩阵,图什么的,这都看你的心情和需要了。我认为使用树是合适而巧妙的。比如要删除一个怪物实体,实际上我们希望怪物的来复枪,枪上的子弹,怪物的衣服, 衣服上的饰物都一起消失,就像砍掉一个树枝一样,连带上面的小枝子,叶子都一切砍掉了,而不用做更多的事情。当然我不是说其他结构就做不到这些,或许会麻 烦些吧。看来一个实体管理类是必须的,它能够把实体以恰当的形式组织起来,而且能够删除实体。
实体间的通信:
刚才已经提到过,实体之间是通过消息来通信的,实体只对自己能理解的消息作出 反应,否则就抛弃它们。那么,我们就需要消息,而且是很多很多种消息,每类实体只对其中的特定消息作出反应。
对消息的处理:
实体收到了消息,而且这个消息是它应该理解的,然后它需要对这些消息作出反应。这就需要一个消息处理函数。
- 经过这样的处理,系统被高度抽象化了,被抽象化的类能够忽略很多个性化的细 节而被统一处理,这给我们开发带来莫大的好处。
- 大家请注意,经过这样处理之后,并不是把游戏开发的复杂降低了,实际上,该 怎么办还是怎么办,复杂的AI,碰撞,I/O,一个也没少;只是在我们不需要注意这些的时候,它们被“隐藏”在了这样的结构背后
- 扩展性更好,这一点是显而易见的,如果需要什么新的功能,只需要增加新的消 息处理就可以了
- 在这一的结构下,程序员需要维护的东西大大降低了,牵一发而动全身的局面得 到了改善
如果您并没有十分的理解上面所说的,我只好为我的写作能力抱歉了,不过也没有 太大的关系,或许您可以在代码中豁然开朗。
现在我们该谈谈实体类,结合上面所说的种种,来看一下实体类 Entity
public class Entity
{
public PROC Proc; // 消息处理函数的委托,具体的消息处理逻辑都在这里,你可以在任何地方把一个函数和这里挂接起来。
public Object data; //实体数据
public Entity parent; //实体在实体树中的域
public Entity prevSibling;
public Entity nextSibling;
public Entity child;
public int EntID; //标志实体的唯一标志
}
很简单的一个类。随着具体游戏,你可以给它增加一些东西,我就曾经尝试过,把 可见性,类型,序号,还有一个渲染自己的Image都当作了这个实体的属性。尽管一个抽象的实体(比如脚本实体)肯定是没有可见性和渲染用的Image 的,但没有太大关系,只要在创造实体的时候不建立这些也就是了。但还是要记住,这是一个所有实体的基类,最好还是让它简单点。或许你很奇怪,不是说实体是 通过消息来通信的吗?怎么实体类里面没有体现这个。当然你可以给实体增加这样一个选项,也可以在实体管理类里实现消息发送的功能。
接下来看看消息的代码:
public enum EM : int { EM_CLSINIT = 0, // initialize class EM_CLSFREE, // free up class EM_CLSGUID, // gets the globally unique ID of this entity class EM_CLSNAME, // gets the textual class name // CREATION AND DESTRUCTION EM_CREATE, // create entity (object) EM_START, // turn the entity 'on' EM_SHUTDOWN, // destroy entity gracefully EM_DESTROY, // destroy entity immediately // BASIC OPERATIONS EM_DRAW, // render the entity EM_DRAWEDIT, // render the entity in "editing mode" EM_DRAWDEBUG, // render the entity in "debug mode" EM_PREUPDATE, // run AI, etc. EM_POSTUPDATE, // check collision, etc. EM_SLEVENT, // process a game input event EM_DRAWSHADOW, // draw any shadow effects, sent after DRAW EM_DRAWOVERLAY, // draw "hud" overlays, send after DRAWSHADOW // DATA ACCESS EM_SETNAME, // sets the name of this entity EM_GETNAME, // gets the name of this entity EM_SETPOS, // set position (var1 = VECTOR3*) EM_GETPOS, // get postion (var1 = VECTOR3*) EM_SETVEL, // set velocity vector (var1 = VECTOR3*) EM_GETVEL, // get velocity vector (var1 = VECTOR3*) EM_SETDIR, // set direction vector (var1 = VECTOR3*) EM_GETDIR, // get direction vector (var1 = VECTOR3*) EM_SETOWNER, // tells an entity who "owns" him var1=ENTITY* EM_GETOWNER, // ask who owns an entity var1=ENTITY* EM_GIVEPOINTS, // gives and entity points EM_IMDEAD, // pronouncement that an entity has died EM_USER0, EM_ADDSPRITE, //增加一个游戏精灵 /// <summary> /// 键盘消息 /// </summary> EM_KEYDOWN, EM_KEYUP, /// <summary> /// 鼠标消息 /// </summary> EM_MOUSEDOWN, EM_MOUSEUP, EM_MOUSEMOVE, /// <summary> /// 这个消息用来更新 /// </summary> EM_UPDATE, EM_UPDATEFINAL, /// <summary> /// 默认的一个消息 /// </summary> DEFAULT }
现在我们有了实体,也有了消息,你还记得实体树吗?把你说创建的实体,插到具体的实体树里就是了。
还需要做的一件事情,把消息处理函数和实体挂接起来。真正做事的是它。
我写了一个孩子兄弟树的类,供朋友们参考。
public class CSNode<T> where T:Entity { /// <summary> /// 节点唯一的ID; /// </summary> private int id; public int ID { internal set { id = value; } get { return id; } } private T data; public T Data { set { data = value; } get { return data; } } private CSNode<T> firstChild; public CSNode<T> FirstChild { set { firstChild = value; } get { return firstChild; } } private CSNode<T> nextSibling; public CSNode<T> NextSibling { set { nextSibling = value; } get { return nextSibling; } } private CSNode<T> parent; public CSNode<T> Parent { set { parent = value; } get { return parent; } } public CSNode() { data = default(T); firstChild = null; nextSibling = null; } public CSNode(T _data) { data = _data; firstChild = null; nextSibling = null; } public CSNode(CSNode<T> _firstChild, CSNode<T> _nextSibling) { data = default(T); firstChild = _firstChild; nextSibling = _nextSibling; } public CSNode(T _data,CSNode<T> _firstChild, CSNode<T> _nextSibling) { data = _data; firstChild = _firstChild; nextSibling = _nextSibling; } /// <summary> /// 用节点的ID是否相等作为判断两个节点是不是同一节点的标志 /// </summary> /// <param name="_target">相比较的节点</param> /// <returns>true:同一节点 false:不同节点</returns> public bool EqualsNode(CSNode<T> _target) { return ID == _target.ID; } } public class CSTree<T>:ITree<CSNode<T>> where T:Entity { private int validId = 100; //这里面存在一个隐患,如果实体不断消失,不断增加,但validId总是递增的,程序运行很长时间后,或许会超过int的取值范围 private CSNode<T> root; public CSNode<T> Root { private set { root = value; } get { return root; } } public CSTree() { root = null; } public CSTree(T _rootData) { root = new CSNode<T>(_rootData); } public CSNode<T> GetRoot() { return root; } /// <summary> /// 因为很多方法用了递归,所以只好把返回的结果放在外面了,注意这个集合的值是随时会变的,使用它之前要先调用返回集合的方法。 /// 并且要注意清空 /// </summary> public List<CSNode<T>> result; //------------------------------------------------------------------------------------插入操作-----------------------------------------------------------------------------// /// <summary> /// 把一个节点插在父节点的第一个子节点位置上 /// </summary> /// <param name="_parent">父节点</param> /// <param name="_firstChild">将要插入的节点</param> /// <returns>插入是否成功</returns> public void InsertFirstChild(CSNode<T> _parent, CSNode<T> _firstChild) { if (_parent != null) { validId += 1; _firstChild.ID = validId; _firstChild.Data.ID = validId; _firstChild.Parent = _parent; _firstChild.NextSibling = _parent.FirstChild; _parent.FirstChild = _firstChild; } } /// <summary> /// 把一个指定值的新节点插在父节点的第一个子节点位置上 /// </summary> /// <param name="_parent">父节点</param> /// <param name="_firstChild">将要插入的节点</param> /// <returns>操作是否成功</returns> public bool InsertFirstChild(CSNode<T> _parent, T _data) { if (_parent != null) { CSNode<T> temp = new CSNode<T>(_data); validId += 1; temp.ID = validId; _data.ID = temp.ID; temp.Parent = _parent; temp.NextSibling = _parent.FirstChild; _parent.FirstChild = temp; return true; } return false; } /// <summary> /// 把一个新节点插在父节点的最后一个子节点的后面 /// </summary> /// <param name="_parent">父节点</param> /// <param name="_lastChild">将要插入的节点</param> /// <returns>操作是否成功</returns> public bool InsertLastChild(CSNode<T> _parent,CSNode<T> _lastChild) { if(_parent!=null) { validId += 1; _lastChild.ID = validId; _lastChild.Data.ID = validId; _lastChild.Parent = _parent; if(_parent.FirstChild==null) { _parent.FirstChild = _lastChild; } else { GetLastChild(_parent).NextSibling = _lastChild; } return true; } return false; } /// <summary> /// 把指定节点插入到源节点的指定位置 /// </summary> /// <param name="_sourceNode">父节点</param> /// <param name="_targetNode">被插入的节点</param> /// <param name="_nodePos">需要插入的位置</param> public void Insert(CSNode<T> _sourceNode, CSNode<T> _targetNode, int _nodePos) { if (_nodePos == 0) { InsertFirstChild(_sourceNode, _targetNode); } else if (_nodePos >= GetNodeDegree(_sourceNode)) { InsertLastChild(_sourceNode, _targetNode); } else { CSNode<T> current = _sourceNode.FirstChild; int nodePos = 1; while (nodePos != _nodePos) { current = current.NextSibling; nodePos++; } _targetNode.NextSibling = current.NextSibling; current.NextSibling = _targetNode; } } //-----------------------------------------------------------------------------------删除操作-------------------------------------------------------------------------// /// <summary> /// 删除目标节点 /// </summary> /// <param name="_target">目标节点</param> /// <returns>返回被删除的节点,如果没有此节点,则返回Null</returns> public CSNode<T> Delete(CSNode<T> _target) { if (_target.EqualsNode(Root)) Root = null; ClearChild(_target); if (_target.Parent == null) { MessageBox.Show("节点不存在!"); return null; } if (_target.Parent.FirstChild.EqualsNode(_target)) { _target.Parent.FirstChild = _target.NextSibling; return _target; } else { CSNode<T> current = _target.Parent.FirstChild; while (current.NextSibling!=null) { if (current.NextSibling.EqualsNode(_target)) { current.NextSibling = _target.NextSibling; return _target; } current = current.NextSibling; } return null; } } /// <summary> /// 删除目标节点指定位置的子节点 /// </summary> /// <param name="_target">目标节点</param> /// <param name="_pos">索引</param> /// <returns>被删除的节点</returns> public CSNode<T> DeleteChild(CSNode<T> _target, int _pos) { if (_pos < 0) throw new Exception(); CSNode<T> result = null; int searchPos = 0; CSNode<T> next = null; if (_pos == 0) { result = _target.FirstChild; ClearChild(result); _target.FirstChild = _target.FirstChild.NextSibling; } else { next = _target.FirstChild; searchPos++; while (searchPos != _pos) { next = next.NextSibling; searchPos++; } result = next.NextSibling; ClearChild(result); next.NextSibling = result.NextSibling; } return result; } /// <summary> /// 把父节点的所有子节点指向父节点的域和父节点的首子节点全部设为null /// </summary> /// <param name="_parent">父节点</param> public void ClearChild(CSNode<T> _parent) { CSNode<T> next; if (_parent.FirstChild != null) { next = _parent.FirstChild.NextSibling; while (next != null) { next.Parent = null; next = next.NextSibling; } _parent.FirstChild = null; } } //-----------------------------------------------------------------------------------返回节点集合操作-------------------------------------------------------------------------// /// <summary> /// 取得父节点的最后一个子节点 /// </summary> /// <param name="_parent">父节点</param> /// <returns>父节点的最后一个子节点</returns> public CSNode<T> GetLastChild(CSNode<T> _parent) { if(_parent.FirstChild==null) { return null; } CSNode<T> temp = _parent.FirstChild; while(temp.NextSibling!=null) { temp = temp.NextSibling; } return temp; } /// <summary> /// 返回指定节点的所有子节点,注意!仅仅是子节点,不包括节点的的节点 /// </summary> /// <param name="_parent">父节点</param> /// <returns>父节点的所有子节点的集合</returns> public List<CSNode<T>> GetParentChildren(CSNode<T> _parent) { if (_parent.FirstChild != null) { List<CSNode<T>> result = new List<CSNode<T>>(); result.Add(_parent.FirstChild); CSNode<T> current = _parent.FirstChild.NextSibling; while (current != null) { result.Add(current); current = current.NextSibling; } return result; } else { return null; } } /// <summary> /// 返回指定节点包括自己在内的所有兄弟节点,注意!仅仅是子节点,不包括节点的的节点 /// </summary> /// <param name="_sibling">指定节点</param> /// <returns>结果节点的集合</returns> public List<CSNode<T>> GetSiblingChildren(CSNode<T> _sibling) { CSNode<T> parent = _sibling.Parent; return GetParentChildren(parent); } /// <summary> /// 返回所有的节点,注意!这个方法仅适用于取根节点 /// </summary> /// <param name="_parent">这个参数只能是根节点</param> public void GetAllChildren(CSNode<T> _parent) { if (_parent != null) { GetAllChildren(_parent.FirstChild); result.Add(_parent); GetAllChildren(_parent.NextSibling); } } /// <summary> /// 返回目标节点的所有子节点 /// </summary> /// <param name="_parent">目标节点(父节点)</param> public void GetTargetAllChildren(CSNode<T> _parent) { if (_parent.FirstChild != null) { result.Add(_parent.FirstChild); GetTargetAllChildren(_parent.FirstChild); CSNode<T> current = _parent.FirstChild.NextSibling; while (current != null) { result.Add(current); GetTargetAllChildren(current); current = current.NextSibling; } } } //---------------------------------------------------------------------------------查找操作----------------------------------------------------------------// public CSNode<T> GetNodeFromID(int _id) { result = new List<CSNode<T>>(); CSNode<T> resultNode = null; GetAllChildren(root); foreach (CSNode<T> item in result) { if (item.ID == _id) { resultNode = item; break; } } result.Clear(); result = null; return resultNode; } //---------------------------------------------------------------------------------其他操作----------------------------------------------------------------// /// <summary> /// 判断是否是空树 /// </summary> /// <returns>true:空树 false:非空树</returns> public bool IsEmpty() { return root == null; } /// <summary> /// 得到目标节点的度 /// </summary> /// <param name="_target">目标节点</param> /// <returns>目标节点的度</returns> public int GetNodeDegree(CSNode<T> _target) { int result = 0; CSNode<T> current = _target.FirstChild ; while (current!= null) { result++; current = current.NextSibling; } return result; } /// <summary> /// 判断是否是叶子节点 /// </summary> /// <param name="_target">需要判断的节点</param> /// <returns>true:叶子节点 false:非叶子节点</returns> public bool IsLeaf(CSNode<T> _target) { return GetNodeDegree(_target) == 0; } }