请问这种对立关系怎么设计?

willko 2008-08-26 01:44:43
有步兵(攻击10) 、枪兵(攻击20)、骑兵(攻击30)

步兵打枪兵,攻击+10%
枪兵打骑兵,攻击+10%
骑兵打步兵,攻击+10%

这种类型的对联关联关系很多。。
请问怎么更好的设计这些类?谢谢
...全文
178 20 打赏 收藏 转发到动态 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
willko 2008-08-27
  • 打赏
  • 举报
回复
谢谢
如果能详细点或者提供点示例,或者资料就更好了。哈
qhfu 2008-08-27
  • 打赏
  • 举报
回复
记得 代码大全 上有一章是讲 表驱动的, 表的形式应该看具体问题而定 。。 怎么样合适就用怎么样的表。。

造将军:
有两种思路: 1 是state, 不同的state,来确定行为
2 是interpreter,通过一系列规则,条件制约
或许可以结合
willko 2008-08-27
  • 打赏
  • 举报
回复
dd
richbirdandy 2008-08-27
  • 打赏
  • 举报
回复
映射表就用map 键值对
设计无非是提高内聚 降低耦合 增强复用
也不是一两句能说清的
willko 2008-08-26
  • 打赏
  • 举报
回复
顺便问下
制约关系
例如
制作将军需要,基地5级,将军殿3级。。。那有什么好的思路呢?
谢谢大家。。
我不是做游戏的。。哈哈
willko 2008-08-26
  • 打赏
  • 举报
回复
好的,谢谢,,一定拜读。。
至于映射表。。。是不是类似ACL这种东西呢?
例如
Role Action Role Attribute
步兵 打 枪兵 攻击+10%
枪兵 打 骑兵 攻击+10%
骑兵 打 步兵 攻击+10%

richbirdandy 2008-08-26
  • 打赏
  • 举报
回复
图贴不上
lz 我建议你还是下个电子版的More effective C++吧 对这个问题讲得很清楚
richbirdandy 2008-08-26
  • 打赏
  • 举报
回复
 初始化模拟虚函数表
现在来看collisionMap的初始化。我们很想这么做:
// An incorrect implementation
SpaceShip::HitFunctionPtr
SpaceShip::lookup(const GameObject& whatWeHit)
{
static HitMap collisionMap;
collisionMap["SpaceShip"] = &hitSpaceShip;
collisionMap["SpaceStation"] = &hitSpaceStation;
collisionMap["Asteroid"] = &hitAsteroid;
...
}
但,这将在每次调用lookup时都将成员函数指针加入了collisionMap,这是不必要的开销。而且它不会编译通过,不过这是将要讨论的第二个问题。
我们需要的是只将成员函数指针加入collisionMap一次,在collisionMap构造时。这很容易完成;我们只需写一个私有的静态成员函数initializeCollisionMap来构造和初始化我们的映射表,然后用其返回值来初始化collisionMap:
class SpaceShip: public GameObject {
private:
static HitMap initializeCollisionMap();
...
};
SpaceShip::HitFunctionPtr
SpaceShip::lookup(const GameObject& whatWeHit)
{
static HitMap collisionMap = initializeCollisionMap();
...
}
不过这意味着我们要付出拷贝赋值的代价(见Item M19和M20)。我们不想这么做。如果initializeCollisionMap()返回指针的话,我们就不需要付出这个代价,但这样就需要担心指针指向的map对象是否能在恰当的时候被析构了。
幸好,有个两全的方法。我们可以将collisionMap改为一个灵巧指针(见Item M28)它将在自己被析构时delete所指向的对象。实际上,标准C++运行库提供的模板auto_ptr,正是这样的一个灵巧指针(见Item M9)。通过将lookup中的collisionMap申明为static的auto_ptr,我们可以让initializeCollisionMap返回一个指向初始化了的map对象的指针了,不用再担心资源泄漏了;collisionMap指向的map对象将在collisinMap自己被析构时自动析构。于是:
class SpaceShip: public GameObject {
private:
static HitMap * initializeCollisionMap();
...
};
SpaceShip::HitFunctionPtr
SpaceShip::lookup(const GameObject& whatWeHit)
{
static auto_ptr<HitMap>
collisionMap(initializeCollisionMap());
...
}
实现initializeCollisionMap的最清晰的方法看起来是这样的:
SpaceShip::HitMap * SpaceShip::initializeCollisionMap()
{
HitMap *phm = new HitMap;
(*phm)["SpaceShip"] = &hitSpaceShip;
(*phm)["SpaceStation"] = &hitSpaceStation;
(*phm)["Asteroid"] = &hitAsteroid;
return phm;
}
但和我在前面指出的一样,这不能编译通过。因为HitMap被申明为包容一堆指向成员函数的指针,它们全带同样的参数类型,也就是GameObject。但,hitSpaceShip带的是一个spaceShip参数,hitSpaceStation带的是SpaceStation,hitAsteroid带的是Asteroid。虽然SpaceShip、SpaceStation和Asteroid能被隐式的转换为GameObject,但对带这些参数类型的函数指针可没有这样的转换关系。
为了摆平你的编译器,你可能想使用reinterpret_casts(见Item M2),而它在函数指针的类型转换中通常是被舍弃的:
// A bad idea...
SpaceShip::HitMap * SpaceShip::initializeCollisionMap()
{
HitMap *phm = new HitMap;
(*phm)["SpaceShip"] =
reinterpret_cast<HitFunctionPtr>(&hitSpaceShip);
(*phm)["SpaceStation"] =
reinterpret_cast<HitFunctionPtr>(&hitSpaceStation);
(*phm)["Asteroid"] =
reinterpret_cast<HitFunctionPtr>(&hitAsteroid);
return phm;
}
这样可以编译通过,但是个坏主意。它必然伴随一些你绝不该做的事:对你的编译器撒谎。告诉编译器,hitSpaceShip、hitSpaceStation和hitAsteroid期望一个GameObject类型的参数,而事实不是这样的。hitSpaceShip期望一个SpaceShip,hitSpaceStation期望一个SpaceStation,hitAsteroid期望一个Asteroid。这些cast说的是其它东西,它们撒谎了。
不只是违背了原则,这儿还有危险。编译器不喜欢被撒谎,当它们发现被欺骗后,它们经常会找出一个报复的方法。这此处,它们很可能通过产生错误的代码来报复你,当你通过*phm调用函数,而相应的GameObject的派生类是多重继承的或有虚基类时。如果SpaceStation。SpaceShip或Asteroid除了GameObject外还有其它基类,你可能会发现当你调用你在这儿搜索到的碰撞处理函数时,其行为非常的粗暴。
再看一下Item M24中描述的A-B-C-D的继承体系以及D的对象的内存布局。

D中的四个类的部分,其地址都不同。这很重要,因为虽然指针和引用的行为并不相同(见Item M1),编译器产生的代码中通常是通过指针来实现引用的。于是,传引用通常是通过传指针来实现的。当一个有多个基类的对象(如D的对象)传引用时,最重要的就是编译器要传递正确的地址--匹配于被调函数申明的形参类型的那个。
但如果你对你的编译器撒谎说你的函数期望一个GameObject而实际上要的是一个SpaceShip或一个SpaceStation时,发生什么?编译器将传给你错误的地址,导致运行期错误。而且将非常难以定位错误的原因。有很多很好的理由说明为什么不建议使用类型转换,这是其中之一。
OK,不使用类型转换。但函数指针类型不匹配的还没解决只有一个办法:将所有的函数都改为接受GameObject类型:
class GameObject { // this is unchanged
public:
virtual void collide(GameObject& otherObject) = 0;
...
};
class SpaceShip: public GameObject {
public:
virtual void collide(GameObject& otherObject);
// these functions now all take a GameObject parameter
virtual void hitSpaceShip(GameObject& spaceShip);
virtual void hitSpaceStation(GameObject& spaceStation);
virtual void hitAsteroid(GameObject& asteroid);
...
};
我们基于虚函数解决二重调度问题的方法中,重载了叫collide的函数。现在,我们理解为什么这儿没有照抄而使用了一组成员函数指针。所有的碰撞处理函数都有着相同的参数类型,所以必要给它们以不同的名字。
现在,我们可以以我们一直期望的方式来写initializeCollisionMap函数了:
SpaceShip::HitMap * SpaceShip::initializeCollisionMap()
{
HitMap *phm = new HitMap;
(*phm)["SpaceShip"] = &hitSpaceShip;
(*phm)["SpaceStation"] = &hitSpaceStation;
(*phm)["Asteroid"] = &hitAsteroid;
return phm;
}
很遗憾,我们的碰撞函数现在得到的是一个更基本的CameObject参数而不是期望中的派生类类型。要想得到我们所期望的东西,必须在每个碰撞函数开始处采用dynamic_cast(见Item M2):
void SpaceShip::hitSpaceShip(GameObject& spaceShip)
{
SpaceShip& otherShip=
dynamic_cast<SpaceShip&>(spaceShip);
process a SpaceShip-SpaceShip collision;
}
void SpaceShip::hitSpaceStation(GameObject& spaceStation)
{
SpaceStation& station=
dynamic_cast<SpaceStation&>(spaceStation);
process a SpaceShip-SpaceStation collision;
}
void SpaceShip::hitAsteroid(GameObject& asteroid)
{
Asteroid& theAsteroid =
dynamic_cast<Asteroid&>(asteroid);
process a SpaceShip-Asteroid collision;
}
如果转换失败,dynamic_cast会抛出一个bad_cast异常。当然,它们从不会失败,因为碰撞函数被调用时不会带一个错误的参数类型的。只是,谨慎一些更好。
richbirdandy 2008-08-26
  • 打赏
  • 举报
回复
模拟虚函数表

有一个方法来增加选择。你可以回顾Item M24,编译器通常创建一个函数指针数组(vtbl)来实现虚函数,并在虚函数被调用时在这个数组中进行下标索引。使用vtbl,编译器避免了使用if...then...else链,并能在所有调用虚函数的地方生成同样的代码:确定正确的vtbl下标,然后调用vtbl这个位置上存储的指针所指向的函数。
没理由说你不能这么做。如果这么做了,不但使得你基于RTTI的代码更具效率(下标索引加函数指针的反引用通常比if...then...else高效,产生的代码也少),同样也将RTTI的使用范围限定在一处:你初始化函数指针数组的地方。提醒一下,看下面的内容前最好做一下深呼吸( I should mention that the meek may inherit the earth, but the meek of heart may wish to take a few deep breaths before reading what follows)。
对GameObjcet继承体系中的函数作一些修改:
class GameObject {
public:
virtual void collide(GameObject& otherObject) = 0;
...
};
class SpaceShip: public GameObject {
public:
virtual void collide(GameObject& otherObject);
virtual void hitSpaceShip(SpaceShip& otherObject);
virtual void hitSpaceStation(SpaceStation& otherObject);
virtual void hitAsteroid(Asteroid& otherobject);
...
};
void SpaceShip::hitSpaceShip(SpaceShip& otherObject)
{
process a SpaceShip-SpaceShip collision;
}
void SpaceShip::hitSpaceStation(SpaceStation& otherObject)
{
process a SpaceShip-SpaceStation collision;
}
void SpaceShip::hitAsteroid(Asteroid& otherObject)
{
process a SpaceShip-Asteroid collision;
}
和开始时使用的基于RTTI的方法相似,GameObjcet类只有一个处理碰撞的函数,它实现必须的二重调度的第一重。和后来的基于虚函数的方法相似,每种碰撞都由一个独立的函数处理,不过不同的是,这次,这些函数有着不同的名字,而不是都叫collide。放弃重载是有原因的,你很快就要见到的。注意,上面的设计中,有了所有其它需要的东西,除了没有实现Spaceship::collide(这是不同的碰撞函数被调用的地方)。和以前一样,实现了SpaceShip类,SpaceStation类和Asteroid类也就出来了。
在SpaceShip::collide中,我们需要一个方法来映射参数otherObject的动态类型到一个成员函数指针(指向一个适当的碰撞处理函数)。一个简单的方法是创建一个映射表,给定的类名对应恰当的成员函数指针。直接使用一个这样的映射表来实现collide是可行的,但如果增加一个中间函数lookup时,将更好理解。lookup函数接受一个GameObject参数,返回相应的成员函数指针。
这是lookup的申明:
class SpaceShip: public GameObject {
private:
typedef void (SpaceShip::*HitFunctionPtr)(GameObject&);
static HitFunctionPtr lookup(const GameObject& whatWeHit);
...
};
函数指针的语法不怎么优美,而成员函数指针就更差了,所以我们作了一个类型重定义。
既然有了lookup,collide的实现如下:
void SpaceShip::collide(GameObject& otherObject)
{
HitFunctionPtr hfp =
lookup(otherObject); // find the function to call
if (hfp) { // if a function was found
(this->*hfp)(otherObject); // call it
}
else {
throw CollisionWithUnknownObject(otherObject);
}
}
如果我们能保持映射表和GameObject的继承层次的同步,lookup就总能找到传入对象对应的有效函数指针。人终究只是人,就算再仔细,错误也会钻入软件。这就是我们为什么检查lookup的返回值并在其失败时抛异常的原因。
剩下的就是实现lookup了。提供了一个对象类型到成员函数指针的映射表后,lookup自己很容易实现,但创建、初始化和析构这个映射表是个有意思的问题。
这样的数组应该在它被使用前构造和初始化,并在不再被需要时析构。我们可以使用new和delete来手工创建和析构它,但这时怎么保证在初始化以前不被使用呢?更好的解决方案是让编译器自动完成,在lookup中把这个数组申明为静态就可以了。这样,它在第一次调用lookup前构造和初始化,在main退出后的某个时刻被自动析构(见Item E47)。
而且,我们可以使用标准模板库提供的map模板来实现映射表,因为这正是map的功能:
class SpaceShip: public GameObject {
private:
typedef void (SpaceShip::*HitFunctionPtr)(GameObject&);
typedef map<string, HitFunctionPtr> HitMap;
...
};
SpaceShip::HitFunctionPtr
SpaceShip::lookup(const GameObject& whatWeHit)
{
static HitMap collisionMap;
...
}
此处,collisionMap就是我们的映射表。它映射类名(一个string对象)到一个Spaceship的成员函数指针。因为map<string, HitFunctionPtr>太拗口了,我们用了一个类型重定义。(开个玩笑,试一下不用HitMap和HitFunctionPtr这两个类型重定义来写collisionMap的申明。大部分人不会做第二次的。)
给出了collisionMap后,lookup的实现有些虎头蛇尾。因为搜索工作是map类直接支持的操作,并且我们在typeid()的返回结果上总可以调用的(可移植的)一个成员函数是name()(可以确定(注11),它返回对象的动态类型的名字)。于是,实现lookup,仅仅是根据形参的动态类型在collisionMap中找到它的对应项、
lookup的代码很简单,但如果不熟悉标准模板库的话(再次参见Item M35),就不会怎么简单了。别担心,程序中的注释解释了每一步在做什么。
SpaceShip::HitFunctionPtr
SpaceShip::lookup(const GameObject& whatWeHit)
{
static HitMap collisionMap; // we'll see how to
// initialize this below
// look up the collision-processing function for the type
// of whatWeHit. The value returned is a pointer-like
// object called an "iterator" (see Item 35).
HitMap::iterator mapEntry=
collisionMap.find(typeid(whatWeHit).name());
// mapEntry == collisionMap.end() if the lookup failed;
// this is standard map behavior. Again, see Item 35.
if (mapEntry == collisionMap.end()) return 0;
// If we get here, the search succeeded. mapEntry
// points to a complete map entry, which is a
// (string, HitFunctionPtr) pair. We want only the
// second part of the pair, so that's what we return.
return (*mapEntry).second;
}
最后一句是return (*mapEntry).second而不是习惯上的mapEntry->second以满足STL的奇怪行为。具体原因见Item M18。
willko 2008-08-26
  • 打赏
  • 举报
回复
谢谢大家
可不可以把映射表说清楚点。。。。。
我很多年没用CPP了。。。。。。。
不好意思。。。
e_sharp 2008-08-26
  • 打赏
  • 举报
回复
用映射表,方便扩展
healer_kx 2008-08-26
  • 打赏
  • 举报
回复
首先必须肯定的是使用表,table可以去耦合,并合适参数的调整,有利于OCP。
然后问题的关键在于表的设计,我觉得星际就比较复杂了,你得考虑单位的升级情况,得考虑躲闪情况的话,这个表就要多几个Cols。
killercat 2008-08-26
  • 打赏
  • 举报
回复
FSM 解决:

int nCurrentState;
//...
switch (nCurrentState)
{
case TYPE1:
if (nAttackType == TYPE2)
{
//...
}
break;
case TYPE2:
if ...
break;
case TYPE3:
if ...
break;
// ...
default:
// Error State
break;
}
wudeshou82666 2008-08-26
  • 打赏
  • 举报
回复
顶1楼
xqls_xqls 2008-08-26
  • 打赏
  • 举报
回复
没有接触过,学习了。
qqwx_1986 2008-08-26
  • 打赏
  • 举报
回复
mark
fibbery 2008-08-26
  • 打赏
  • 举报
回复
我觉得单就查表肯定是不行的!

楼主应该是做游戏开发的!

两种兵种作用的结果,肯定是要考虑攻击力、防御力等各项参数的综合结果。

class CPerson
{
public:
virtual GetParam(.....)const;
virtual ModifyParam(.....);
virtual Fight(CPerson * p);
};
class C枪兵:public CPerson
{...};
class C....

Fight(CPerson * p)
{
p->GetParam(....);获得
....//计算各种指标需要的变化,这一部分可以考虑外部运算规则定义
ModifyParam(....);//修改各项值
}
lzr4304061988012 2008-08-26
  • 打赏
  • 举报
回复
up 1 lou
richbirdandy 2008-08-26
  • 打赏
  • 举报
回复
参考More effective C++ 条款31 基于多个对象的虚函数
对这类问题讲得比较详细

qhfu 2008-08-26
  • 打赏
  • 举报
回复
建立一张 映射表,通过 查表 获取攻击能力。。

64,648

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧