C++ question
-
- DC Developer
- Posts: 9951
- https://www.artistsworkshop.eu/meble-kuchenne-na-wymiar-warszawa-gdzie-zamowic/
- Joined: Sun Dec 30, 2001 9:02 am
- Has thanked: 0
- Been thanked: 1 time
C++ question
Is there something similar to the Java instanceof operator in C++?
Basically, I have a class hierarchy representing the objects in a game. I might have a projectile, and I only want it to collide with enemies, for example. The simplest way I can think of to do this would be to check if the object in question is an instance of the enemy class, or any of it's subclasses.
If there's a cleaner way to do this, any hints would be appreciated.
Basically, I have a class hierarchy representing the objects in a game. I might have a projectile, and I only want it to collide with enemies, for example. The simplest way I can think of to do this would be to check if the object in question is an instance of the enemy class, or any of it's subclasses.
If there's a cleaner way to do this, any hints would be appreciated.
-
- DC Developer
- Posts: 968
- Joined: Tue Feb 11, 2003 4:12 pm
- Location: In a Dream
- Has thanked: 5 times
- Been thanked: 6 times
a rushed suggestion:
I think it might work. I'm not sure about the explicit destruction semantics. maybe you could use a "void destroy()". Maybe there's a way to use templates?
Code: Select all
class object
{
public:
object(const params&) { /* construction */ }
virtual ~object() { /* destruction eg explode */ }
public:
virtual void collide(object *const target)
{
//nothing done for base objects. maybe just move
}
};
class projectile
: public object
{
public:
projectile(const params& in_params)
: object(in_params)
{}
~projectile() { /* destruction */ }
public:
void collide(object *const target)
{
*target.~target(); /* explicit destruction of target? */
*this.~projectile(); /* explicit destruction of self? */
}
};
Last edited by nymus on Wed Sep 08, 2004 5:52 pm, edited 4 times in total.
behold the mind
inspired by Dreamcast
inspired by Dreamcast
-
- Insane DCEmu
- Posts: 138
- Joined: Sun Apr 06, 2003 5:29 am
- Has thanked: 0
- Been thanked: 0
- Contact:
One method may be to have an Object Identifier Class as the base class for all your other classes. For example, say you have different objects in a world such as Enemy, Bucket, Friend, etc. These could all derive from a base object identifier class.
In the Base class you could define a type, which identifies what that object is. So if an instance of an Enemy class is created, then the object type is an Enemy, and so on.
Here is an example of some code, although there's probably an easy way to do it.. This is probably not the way to do it, and I have no idea how inefficient this code is or if it would work, but it may give you some ideas.
I previously had:
std::vector<uint *> myList;
typedef std::vector<uint *>::iterator myIter;
to store the addresses of the Class object instances but I think after a test it will also work with CObjIdentifier, as it's the base class for the objects.
In the Base class you could define a type, which identifies what that object is. So if an instance of an Enemy class is created, then the object type is an Enemy, and so on.
Here is an example of some code, although there's probably an easy way to do it.. This is probably not the way to do it, and I have no idea how inefficient this code is or if it would work, but it may give you some ideas.
Code: Select all
#include <kos.h>
#include <vector>
enum enumObjectType {
ot_Enemy,
ot_Plant,
ot_Bucket,
ot_Friendly
};
class CObjIdentifier {
public:
CObjIdentifier();
void setObjectType(enumObjectType oType);
enumObjectType getObjectType();
bool isObjectType(enumObjectType oType);
protected:
enumObjectType _oType;
};
CObjIdentifier::CObjIdentifier(){}
void CObjIdentifier::setObjectType(enumObjectType oType) {
_oType = oType;
}
enumObjectType CObjIdentifier::getObjectType(){
return _oType;
}
bool CObjIdentifier::isObjectType(enumObjectType oType){
return (_oType == oType ? true : false);
}
class CEnemy : public CObjIdentifier {
public:
CEnemy(){ _oType = ot_Enemy;};
void insertFunctionsHere(){};
};
class CFriendly : public CObjIdentifier {
public:
CFriendly(){ _oType = ot_Friendly;};
void insertFunctionsHere(){};
};
int main(int argc, char **argv) {
//Store pointers to different objects.
std::vector<CObjIdentifier *> myList;
typedef std::vector<CObjIdentifier *>::iterator myIter;
CEnemy oEnem1;
CFriendly oFriend1;
//Used for last example
myList.push_back(&oEnem1);
myList.push_back(&oFriend1);
//Check if Object is of Type Enemy
if(oEnem1.isObjectType(ot_Enemy)){
printf("Enemy Object\n");
}
/*
If for some reason you were processing a list of objects
And you have no way to determine object type,
Cast to ObjIdentifier and check type.
If list can contain different object types? and you know
they all derive from CObjIdentifier
*/
for (myIter i = myList.begin(); i < myList.end(); ++i)
{
CObjIdentifier *obj = *i;
if(obj->isObjectType(ot_Enemy))
printf("Enemy Object\n");
else
printf("Some Other Object Here...\n");
}
/* remove objects from vector here */
return 0;
}
std::vector<uint *> myList;
typedef std::vector<uint *>::iterator myIter;
to store the addresses of the Class object instances but I think after a test it will also work with CObjIdentifier, as it's the base class for the objects.
Last edited by Vorrtexx on Wed Sep 08, 2004 3:22 pm, edited 1 time in total.
-
- bleemcast! Creator
- Posts: 882
- Joined: Wed Oct 17, 2001 7:44 pm
- Location: Los Angeles, CA
- Has thanked: 0
- Been thanked: 0
- Contact:
<Offtopic>
nymus -- nice coding style!
One thing I do to make it easier to read classes is tab out the functions so that the return types are separated from the function names (i.e. the function names all line up cleanly).
It takes a little more on-screen space, but it makes looking at the list of functions so much easier for larger classes.
Otherwise, your style is pretty much exactly how I do it... (which, of course, is neither here nor there)
Rand.
nymus -- nice coding style!
One thing I do to make it easier to read classes is tab out the functions so that the return types are separated from the function names (i.e. the function names all line up cleanly).
It takes a little more on-screen space, but it makes looking at the list of functions so much easier for larger classes.
Otherwise, your style is pretty much exactly how I do it... (which, of course, is neither here nor there)
Rand.
-
- DC Developer
- Posts: 968
- Joined: Tue Feb 11, 2003 4:12 pm
- Location: In a Dream
- Has thanked: 5 times
- Been thanked: 6 times
That's how I do it too!! I also prefer to define inline methods outside the class.... I'm still trying to find a consistent coding style for my project, especially with all the c++ quirks like const pointers and templates.Rand Linden wrote: One thing I do to make it easier to read classes is tab out the functions so that the return types are separated from the function names (i.e. the function names all line up cleanly).
Rand.
Somehow, typing into an input box takes away the pleasure of seeing your class all formatted, highlighted and stuff, plus it's annoying when you press tab and the focus moves out of the input box.
behold the mind
inspired by Dreamcast
inspired by Dreamcast
-
- bleemcast! Creator
- Posts: 882
- Joined: Wed Oct 17, 2001 7:44 pm
- Location: Los Angeles, CA
- Has thanked: 0
- Been thanked: 0
- Contact:
I don't usually post a bunch of code for the very reason you pointed out -- it's hard to make it readable in anything other than a "proper" editor.
Still, it's very cool to know that someone else has a similar style out there!
(yes, I define inlines outside as well... unless they're truly trivial one- or two-token functions -- and still very rarely at that).
IMHO, adhering to a reasonably strict formatting style makes maintenance and development *so* much easier.
Rand.
Still, it's very cool to know that someone else has a similar style out there!
(yes, I define inlines outside as well... unless they're truly trivial one- or two-token functions -- and still very rarely at that).
IMHO, adhering to a reasonably strict formatting style makes maintenance and development *so* much easier.
Rand.
-
- DC Developer
- Posts: 9951
- Joined: Sun Dec 30, 2001 9:02 am
- Has thanked: 0
- Been thanked: 1 time
Nymus - The explicit destructor thing looks like it might work, but it'll only work if I want to destroy an object, and I won't have any way to destroy the object without blowing it up. It also won't work if I want the enemy to take damage instead of being destroyed.
Vorrtexx - The object type ID might work. That's more-or-less how games like Quake do things.
I did also think of two other possibilities. Use a dynamic_cast , which is apparently quite slow if you have a complex class hierarchy. Probably not the best of ideas. typeid seems not to take the class hierarchy into account at all, so it'd only work if I had one enemy class, and no derived classes.
The other is to have something like an isDamageable method on the base object class. That way, I just check that the object we've collided with is damagable, and isn't the parent object. That'll also work fine, as long as I don't care about enemies shooting each other.
Although not particularly clean design, the object type ID might work for this. The problem with that method is that it doesn't take an object's class into account, so I couldn't easily distinguish between different types of enemy, for example.
Another problem is if I needed to call a method which is only present in a derived class, and not in the object base class, I'd have to resort to casting again.
This is why I don't usually attempt to write game logic in C++...
Vorrtexx - The object type ID might work. That's more-or-less how games like Quake do things.
I did also think of two other possibilities. Use a dynamic_cast , which is apparently quite slow if you have a complex class hierarchy. Probably not the best of ideas. typeid seems not to take the class hierarchy into account at all, so it'd only work if I had one enemy class, and no derived classes.
The other is to have something like an isDamageable method on the base object class. That way, I just check that the object we've collided with is damagable, and isn't the parent object. That'll also work fine, as long as I don't care about enemies shooting each other.
Although not particularly clean design, the object type ID might work for this. The problem with that method is that it doesn't take an object's class into account, so I couldn't easily distinguish between different types of enemy, for example.
Another problem is if I needed to call a method which is only present in a derived class, and not in the object base class, I'd have to resort to casting again.
This is why I don't usually attempt to write game logic in C++...
-
- DC Developer
- Posts: 968
- Joined: Tue Feb 11, 2003 4:12 pm
- Location: In a Dream
- Has thanked: 5 times
- Been thanked: 6 times
You could have an object be composed of a number of different objects ie in a layered fashion which, I think, sounds great if you're working with sprites. I'm thinking of a weak object having just one drawable and the tougher objects having more than one drawable. You could then pass the topmost drawable of an object to collide and invdke the destructor for it which would deregister it from its object so you could have the next sprite be a damaged-looking one etc until finally, you destroy the last drawable and then destroy the whole object...BlackAura wrote:Nymus - The explicit destructor thing looks like it might work, but it'll only work if I want to destroy an object, and I won't have any way to destroy the object without blowing it up. It also won't work if I want the enemy to take damage instead of being destroyed.
Seems like a job for reference counting, arrays of objects, polymorphism and intelligent destrutor semantics... I hate those things.
Working on my project, I've come to realise that it's harder to try to program logic than to program the results/consequences of it.... and I think C++ is very good for this.This is why I don't usually attempt to write game logic in C++...
behold the mind
inspired by Dreamcast
inspired by Dreamcast
-
- DC Developer
- Posts: 9951
- Joined: Sun Dec 30, 2001 9:02 am
- Has thanked: 0
- Been thanked: 1 time
Yeah, me too.Seems like a job for reference counting, arrays of objects, polymorphism and intelligent destrutor semantics... I hate those things.
The only other reasonable alternative I can think of is to have a base object class with methods for virtually everything, and the other objects just overload a couple of those methods. In that case, there's pretty much no point in using C++ any more, and doing it in C would be no more difficult.
An embedded scripting langauge of some kind would be better.
-
- DC Developer
- Posts: 968
- Joined: Tue Feb 11, 2003 4:12 pm
- Location: In a Dream
- Has thanked: 5 times
- Been thanked: 6 times
I've given this a little more thought and come up with a few ideas:
Use dumb collisions where we say every collision inflicts damage and have each object respond to this damage. In this case, every object can have a "health_constant" and if a collision happens, every object receives the health_constant from the object it collided with and this affects its own health
Sounds great because as an object is damaged, its health_constant might be affected thus objects receiving damage from it are hurt less as it suffers more. If it doesn't affect other objects, it won't call the "receive_damage" method for the "other" object if it does collide with another object.
Use intelligent interaction where each object, on collision, decides whether it should inflict damage on the other object. This will probably involve object ids like Vortexx suggested.
I think the hardest part is designing the collision structures.
Should every object check whether it has collided with another after every move? Should a collision engine do it? If an object detects that it has collided with some objects, it decides whether to call a "receive_damage(*this)" for each object it has collided with. Objects that don't inflict damage won't call this function, maybe just move the object acording to a physics engine. At this point, it could perform checks for object ids to check the type of object it has hit and decide whether to call "receive_damage(*this)" for that object.
I think there's no way to avoid the decision making involved in determining whether an object is an enemy or not. It should be easier and more efficient to check the id of an object directly (instead of relying on dynamic cast or polymorphism or templates) since there is a "finite" number of character_classes in a game and each object will only check the ones it is interested in (by making interact_with(object&) virtual) and calling receive_damage(*this) for the rest or not depending on whether it is a hostile object or a passive one.
The "health_constant" should probably be part of another class heirachy "object_state" that could manage health, sprite damage etc.
//TODO: check that an object still exists after collision... and others....
Use dumb collisions where we say every collision inflicts damage and have each object respond to this damage. In this case, every object can have a "health_constant" and if a collision happens, every object receives the health_constant from the object it collided with and this affects its own health
Sounds great because as an object is damaged, its health_constant might be affected thus objects receiving damage from it are hurt less as it suffers more. If it doesn't affect other objects, it won't call the "receive_damage" method for the "other" object if it does collide with another object.
Use intelligent interaction where each object, on collision, decides whether it should inflict damage on the other object. This will probably involve object ids like Vortexx suggested.
I think the hardest part is designing the collision structures.
Should every object check whether it has collided with another after every move? Should a collision engine do it? If an object detects that it has collided with some objects, it decides whether to call a "receive_damage(*this)" for each object it has collided with. Objects that don't inflict damage won't call this function, maybe just move the object acording to a physics engine. At this point, it could perform checks for object ids to check the type of object it has hit and decide whether to call "receive_damage(*this)" for that object.
I think there's no way to avoid the decision making involved in determining whether an object is an enemy or not. It should be easier and more efficient to check the id of an object directly (instead of relying on dynamic cast or polymorphism or templates) since there is a "finite" number of character_classes in a game and each object will only check the ones it is interested in (by making interact_with(object&) virtual) and calling receive_damage(*this) for the rest or not depending on whether it is a hostile object or a passive one.
The "health_constant" should probably be part of another class heirachy "object_state" that could manage health, sprite damage etc.
Code: Select all
class object
{
public:
//these are finite so using dynamic cast here would not help
enum e_object_class
{
protagonist,
ally,
enemy,
other
};
public:
object(const int& in_health_constant, const e_object_class&);
virtual ~object() {}
public:
virtual void interact_with(object& other);
virtual void receive_damage(const object& other);
public:
int health_constant() const;
enum object_class() const;
private:
/**
these are internal fields. The object_class expecially should not be
changed except by intelligent objects eg an npc might become a friend
if, after interacting with you, it decides you are a good (or bad) guy
*/
int _health_constant;
e_object_class _object_class;
};
inline object::(const int& in_hlt_cst, const e_object_class& in_obj_cls)
: _health_constant(in_hlt_cst)
_object_class(in_obj_cls)
{}
inline object::~object()
{
/**
I am dying! Types derived from me might explode etc
*/
}
inline void object::interact_with(object& other)
{
/**
I have been called from a collision detection engine that
determined I had collided with the "other" Types derived from
me might talk or fight
*/
}
inline void object::receive_damage(const object& other)
{
/**
An object has interacted with me and decided to inflict
damage on me. Types derived from me (that accept damage)
MUST call me first because I am responsible for
reducing the health constant
*/
_health_constant -= other._health_constant;
if(0 <= _health_constant) this->~object(); //calls ALL destructors
}
int object::health_constant() const
{
return _health_constant;
/**
Wha??? I'm thinking that my health is a result of my abiltity
to withstand damage. The higher my damage constant, the more
health I have at the beginning of the game. Objects with lower
damage constant will leave me with still enough health to be
a major contender in the game
*/
}
e_object_class object::object_class() const
{
return _object_class;
/**
If another object type likes my type, it will not harm me
*/
}
///////////////////////////////////////////////////////////
class protagonist
: public interactive_object //NOTE: interactive_object
{
public:
protagonist();
~protagonist();
public:
void interact_with(object&);
void receive_damage(const object&);
};
void protagonist::interact_with(object& other)
{
switch(other.object_class())
{
case protagonist:
//we have stuff in common. same team? let's talk
other.interact_with(*this);
receive_damage(other);
case ally:
// friendly fire? bird with message? food?
other.interact_with(*this);
//eat it etc...
receive_damage(other);
break;
case enemy:
//fight
other.receive_damage(*this);
break;
case other:
//what are you???
default:
//rest on it, push it...
}
}
void protagonist::receive_damage(const object& other)
{
object::receive_damage(other);
//say ouch!!
//what? you dare hit me???
//who are you??
//fight!!
other.receive_damage(*this);
}
class enemy
: public interactive_object
{
public:
enemy(const int&, const enemy_params&);
virtual ~enemy();
public:
virtual void interact_with(object&);
virtual void receive_damage(const object&);
};
void enemy::interact_with(object&)
{
switch(other.object_class())
{
case ally:
//you are in league with the protagonist!
other.receive_damage(*this);
break;
case enemy:
//we are on the same team
other.interact_with(*this);
break;
case other:
//whatever
default:
//lean on the object if tired, eat it, push it...
}
}
///////////////////////////////////////////////////////////
class food
: public object
{
public:
food(): object(-900, ally) {}
};
class poison
: public object
{
public:
poison(900, enemy) {}
};
class enemy_missile
: public missile
{
public:
enemy_missile(): missile(20000, enemy) {}
};
class protagonist_weapon
: public missile
{
public:
protagonist_weapon(): missile(0, ally) {}
};
///////////////////////////////////////////////////////////
class game
{
public:
game();
~game();
public:
void operator()(const run_params&);
void do_input();
void do_collisions();
void do_drawing();
private:
input_lib _inputs;
graphics_lib _graphics;
collisions_lib _collisions;
//...
std::list<object*> _all_objects;
std::vector<protagonist*> _protagonists;
std::list<ally*> _allies;
std::list<enemies*> _enemies;
std::list<others*> _others;
};
game::operator()(const run_params&)
{
//whatever
do_input();
do_collisions();
do_drawing();
}
void game::do_input()
{
input_type in;
_inputs >> in;
switch(in)
{
case p1_input:
_protagonists[0].respond();
break;
//...
}
}
void game::do_drawing()
{
while(!(_all_objects.end() == objects_iterator))
{
*iterator.draw();
//or draw(*iterator);
}
}
void game::do_collisions()
{
while(!(_all_objects.end() == objects_iterator))
{
collisions_type = collision_detect(*iterator);
while(!(collisions_type.end() == collisions_iterator))
{
*iterator.interact_with(*collisions_iterator);
}
}
}
behold the mind
inspired by Dreamcast
inspired by Dreamcast