Jump to content

slightly different take on attributes/behaviors


Rick
 Share

Recommended Posts

Just something I'm working on to try and make UI driven game logic. Very similar to the component stuff I was doing. Almost for my own documentation ;)

 

#include <map>
#include <string>

using namespace std;

class AttributeType
{
public:
AttributeType(){}
};

template <class T>
class Attribute : public AttributeType
{
private:
T _data;
public:
Attribute(T data)
{
	_data = data;
}

T GetValue()
{
	return _data;
}

void SetValue(T value)
{
	_data = value;
}
};

class GameObject
{
private:
map<string, AttributeType*> _attributes;
public:
void AddAttribute(string name, AttributeType* att)
{
	_attributes[name] = att;
}

template <typename T>
T GetAttributeValue(string name)
{
	return reinterpret_cast<Attribute<T>*>(_attributes[name])->GetValue();
}

template <typename T>
void SetAttributeValue(string name, T value)
{
	reinterpret_cast<Attribute<T>*>(_attributes[name])->SetValue(value);
}
};

// the base class for all behaviors
class Behavior
{
private:
map<string, AttributeType*> _parameters;
public:
void ClearParameters() { _parameters.clear(); }

template<typename T>
void PushParameter(string name, T data)
{
	_parameters[name] = new Attribute<T>(data);
}

template<typename T>
T GetParameter(string name)
{
	return reinterpret_cast<Attribute<T>*>(_parameters[name])->GetValue();
}

virtual void operator()()=0;
};

// this specific behavior reduces health of a game object
class DoDamage : public Behavior
{
public:
virtual void operator()()
{
	// get the parameters		(the names would be linked via xml/editor)
	GameObject* obj = GetParameter<GameObject*>("game.object");
	int dmgAmount = GetParameter<int>("damage.amount");

	// do the logic
	int health = obj->GetAttributeValue<int>("health");

	health -= dmgAmount;

	obj->SetAttributeValue<int>("health", health);
}
};

// input parameter: modelFilename <string> the name of the model to load
// output parameter: model <TEntity> stores the actual model
class LoadModel : public Behavior
{
public:
virtual void operator()()
{

}
};

class TEntity
{
};

int main()
{
// everything is a GameObject object
GameObject human;

// all logic is a Behavior. we would create 1 type of each behavior and that would be used
// everywhere it's needed
Behavior* damage = new DoDamage();

// we can create/set custom data for any game object
// this can be explained in xml
human.AddAttribute("health", new Attribute<int>(100));
human.AddAttribute("name", new Attribute<string>("Rick"));
human.AddAttribute("attack.power", new Attribute<float>(50.5f));
human.AddAttribute("model", new Attribute<string>("abstract::model.gmf"));

// we can get these attributes
float ap = human.GetAttributeValue<float>("attack.power");

int health = human.GetAttributeValue<int>("health");

// add parameters and call the DoDamage behavior
damage->ClearParameters();
damage->PushParameter<GameObject*>("game.object", &human);
damage->PushParameter<int>("damage.amount", 5);
(*damage)();

health = human.GetAttributeValue<int>("health");

return 0;
}

Link to comment
Share on other sites

I would allow to pass a Parameter[] to the constructor of each Behavior so you don't need to ClearParameters()

And maybe a Call method instead of constructor is preferable as class design since may happen you need to create a Behavior from scratch (or it may be called in unwanted times causing unwanted behaviours, think to Reflection in example).

 

My 2 cents :)

?? FRANCESCO CROCETTI ??

http://skaredcreations.com

Link to comment
Share on other sites

The way this is setup, each behavior is only created once for the entire game and all game objects that are connected to that behavior use just the one instance. So when it's a certain game objects turn to use a certain behavior, the previous parameters are cleared and that game objects parameters are filled in. That would be why I don't use the constructor. But I like the idea of using an array of parameters. I'll just make that passed to the function call operator.

 

In this design you can think of behaviors are just a normal function. Like if this was normal C you'd just have normal functions that work on all the different game objects in your game. The reason I didn't make it a normal function is with polymorphism you can store all the different behaviors in a container and assign them a name. Create 1 of each at the beginning of the game dynamically from dll's (in C# anyway), so people can add their own behaviors dynamically. Then when the actual flow of game logic is read from xml files you can refer to behaviors via their string name.

 

I've been playing around with Kismat a little and it seems the theme is that Events fire and run Actions that work on Game Objects.

 

One of the cool things I like about Kismat is that these events have parameters that get filled in by the even caller and one can save off those parameters to variables to be used later in the flowgraph. That's pretty handy.

Link to comment
Share on other sites

  • 1 month later...

I took this idea and modified it a wee bit. My idea was to have behavious as things attributed to game objects (but they don't have to be) which act on attributes and modify them, importand variables such as controllers are kept as part of the object for memory management purposes plus it allows things to be accessed directly if needs be. This is what I did for player movement (I'm not a great programmer, sorry if the code is a bit shoddy):

 

//####################


class AttributeType{
public:
       AttributeType(){}
};

template <class T>
class Attribute : public AttributeType{
private:
T data;

public:
Attribute(T _data) {
	 data = _data;
}

T GetValue() {
	return data;
}

void SetValue(T value) {
	data = value;
}
};

class UTNBehaviour {
public:

map<string,AttributeType*>* attributes;

void SetAttributeList(map<string,AttributeType*> * att) {
	attributes = att;
}

template <typename T>
T GetAttributeValue(map<string,AttributeType*>* att, string name) {
	return reinterpret_cast<Attribute<T>*>((*att)[name])->GetValue();
}

template <typename T>
void SetAttributeValue(map<string,AttributeType*>* att, string name, T value) {
	return reinterpret_cast<Attribute<T>*>((*att)[name])->SetValue(value);
}

virtual void operator()()=0;
virtual void operator()(map<string,AttributeType*>* att)=0;
};


float mx = 0;
float my = 0;
class PlayerMovement : public UTNBehaviour{
public:

virtual void operator()() {
	(*this)(attributes);
}

virtual void operator()(map<string,AttributeType*>* att) {
	TVec3 * rotation = GetAttributeValue<TVec3*>(att,"orientation");
	rotation->X = rotation->X + my*0.1;
	rotation->Y = rotation->Y - mx*0.1;
	UpdateController(*GetAttributeValue<TEntity*>(att,"controller"),rotation->Y,(KeyDown(KEY_W)-KeyDown(KEY_S))*5,(KeyDown(KEY_D)-KeyDown(KEY_A))*5,0,10);
}

};


//####################

class UTNGameObject{
private:
static list<UTNGameObject *> liveObjects;

public:
UTNGameObject();

map<string,AttributeType*> attributes;
map<string,UTNBehaviour*> behaviours;

void AddAttribute(string name, AttributeType* att) {
	attributes[name] = att;
}

void AddBehaviour(string name, UTNBehaviour* bhv) {
	behaviours[name] = bhv;
}

template <typename T>
T GetAttributeValue(string name) {
	return reinterpret_cast<Attribute<T>*>(attributes[name])->GetValue();
}

template <typename T>
void SetAttributeValue(string name, T value) {
	reinterpret_cast<Attribute<T>*>(attributes[name])->SetValue(value);
}

map<string,AttributeType*> * GetAttributeList() {
	return &attributes;
}

virtual ~UTNGameObject();
virtual void Update();
};

//##########

enum PLAYERTYPE {LOCAL_PLAYER,FOREIGN_PLAYER,NPC};

class UTNCharacter : public UTNGameObject{
public:
void Update();

TController controller;
TVec3 orientation;
PLAYERTYPE playertype;
};

//##########

list<UTNGameObject *> UTNGameObject::liveObjects;

UTNGameObject::UTNGameObject(){
  liveObjects.push_back(this);
}

UTNGameObject::~UTNGameObject(){
}

void UTNGameObject::Update(){
}

//##########

void UTNCharacter::Update() {

}

//##########

 

And then the creation of a player object:

 

void CreateGame() {
SetWorld(world.GetWorld(MAINWORLD));

Collisions(1,2,true);
SetWorldGravity(Vec3(0,-20,0));

TEntity scene = LoadScene("abstract::scene.sbx");
EntityType(scene,1);
//world.AddObject(&scene);

UTNCharacter *newplayer = new UTNCharacter;
newplayer->playertype=LOCAL_PLAYER;

newplayer->controller = CreateController(1.8f,0.4f,0.3f,40.0f);
newplayer->AddAttribute("controller",new Attribute<TEntity*>(&newplayer->controller));
SetBodyMass(*newplayer->GetAttributeValue<TEntity*>("controller"),60.0);
EntityType(*newplayer->GetAttributeValue<TEntity*>("controller"),2);
PositionEntity(*newplayer->GetAttributeValue<TEntity*>("controller"),Vec3(0.0,5.0,0.0));

newplayer->orientation=Vec3(0);
newplayer->AddAttribute("orientation",new Attribute<TVec3*>(&newplayer->orientation));

UTNBehaviour * bhv = new PlayerMovement;
bhv->SetAttributeList(newplayer->GetAttributeList());
newplayer->AddBehaviour("playercontrols",bhv);

world.player = &newplayer->controller;
world.playerrotation = &newplayer->orientation;
world.AddObject(newplayer);

appstate = GAME_RUNNING;
}

 

And the world update loop (UTNWorld is my framework substitute which can be rendered by a renderer object)

void UTNWorld::Update() {
SetWorld(GetWorld(MAINWORLD));

for(list<UTNGameObject *>::iterator ita=worldObjects.begin(); ita!=worldObjects.end(); ) {
	UTNGameObject *ptr=(*ita);
	++ita;

	ptr->Update(); //Fairly redundant if you use behaviours

	for(map<string,UTNBehaviour*>::iterator itb=ptr->behaviours.begin(); itb!=ptr->behaviours.end(); ) {
		UTNBehaviour * ptrb = (*itb).second;
		//(*ptrb)(&(ptr->attributes));
		(*ptrb)();
		++itb;
	}
}

PositionEntity(cam,EntityPosition(*player)+Vec3(0,1.8,0));
RotateEntity(cam,*playerrotation);
SetWorld(GetWorld(MAINWORLD));
UpdateWorld(AppSpeed());
}

 

Sorry if it's a bit confusing and a bit long, it's from a WIP and I din't want to spend too much time editing it.

 

So is this a good way of working (I can see myself easily being able to build up an array of usefull behaviours and objects this way)? As I said before, I'm a pretty poor programer in that I don't have very much real world experience so this whole thing might be disasterous.

AMD Phenom 9850 (X4 2.5GHz) | 1066MHz CL5 GEIL Black Dragon (800MHz) | Powercolor ATI 4870 1GB | DFI 790FXB-M2RSH | 24" 1920x1200 | Windows Vista 64-bit Ultimate

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...