C++ question

If you have any questions on programming, this is the place to ask them, whether you're a newbie or an experienced programmer. Discussion on programming in general is also welcome. We will help you with programming homework, but we will not do your work for you! Any porting requests must be made in Developmental Ideas.
Post Reply
BlackAura
DC Developer
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

Post by BlackAura »

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.
Alexvrb
DCEmu Ultra Poster
DCEmu Ultra Poster
Posts: 1754
Joined: Wed Jul 17, 2002 11:25 am
Has thanked: 0
Been thanked: 0

Post by Alexvrb »

typeid? but...
nymus
DC Developer
DC Developer
Posts: 968
Joined: Tue Feb 11, 2003 4:12 pm
Location: In a Dream
Has thanked: 5 times
Been thanked: 6 times

Post by nymus »

a rushed suggestion:

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? */
        }
};

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?
Last edited by nymus on Wed Sep 08, 2004 5:52 pm, edited 4 times in total.
behold the mind
inspired by Dreamcast
Vorrtexx
Insane DCEmu
Insane DCEmu
Posts: 138
Joined: Sun Apr 06, 2003 5:29 am
Has thanked: 0
Been thanked: 0
Contact:

Post by Vorrtexx »

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. ;)

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;
}

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.
Last edited by Vorrtexx on Wed Sep 08, 2004 3:22 pm, edited 1 time in total.
Rand Linden
bleemcast! Creator
bleemcast! Creator
Posts: 882
Joined: Wed Oct 17, 2001 7:44 pm
Location: Los Angeles, CA
Has thanked: 0
Been thanked: 0
Contact:

Post by Rand Linden »

<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
DC Developer
DC Developer
Posts: 968
Joined: Tue Feb 11, 2003 4:12 pm
Location: In a Dream
Has thanked: 5 times
Been thanked: 6 times

Post by nymus »

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.
That's how I do it too!! :P 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.

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
Rand Linden
bleemcast! Creator
bleemcast! Creator
Posts: 882
Joined: Wed Oct 17, 2001 7:44 pm
Location: Los Angeles, CA
Has thanked: 0
Been thanked: 0
Contact:

Post by Rand Linden »

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.
BlackAura
DC Developer
DC Developer
Posts: 9951
Joined: Sun Dec 30, 2001 9:02 am
Has thanked: 0
Been thanked: 1 time

Post by BlackAura »

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++...
nymus
DC Developer
DC Developer
Posts: 968
Joined: Tue Feb 11, 2003 4:12 pm
Location: In a Dream
Has thanked: 5 times
Been thanked: 6 times

Post by nymus »

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.
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...

Seems like a job for reference counting, arrays of objects, polymorphism and intelligent destrutor semantics... I hate those things.
This is why I don't usually attempt to write game logic in C++...
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.
behold the mind
inspired by Dreamcast
BlackAura
DC Developer
DC Developer
Posts: 9951
Joined: Sun Dec 30, 2001 9:02 am
Has thanked: 0
Been thanked: 1 time

Post by BlackAura »

Seems like a job for reference counting, arrays of objects, polymorphism and intelligent destrutor semantics... I hate those things.
Yeah, me too.

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.
q_006
Mental DCEmu
Mental DCEmu
Posts: 415
Joined: Thu Oct 10, 2002 7:18 pm
Has thanked: 0
Been thanked: 0
Contact:

Post by q_006 »

i've heard great things about Lua. supposedly it integrates very well with C and C++.
nymus
DC Developer
DC Developer
Posts: 968
Joined: Tue Feb 11, 2003 4:12 pm
Location: In a Dream
Has thanked: 5 times
Been thanked: 6 times

Post by nymus »

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.

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);
                }
        }
}

//TODO: check that an object still exists after collision... and others....
behold the mind
inspired by Dreamcast
Post Reply