Jump to content

  • Log In with Google      Sign In   
  • Create Account

Banner advertising on our site currently available from just $5!


1. Learn about the promo. 2. Sign up for GDNet+. 3. Set up your advert!


About the Game Logic Class


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
23 replies to this topic

#1 Irlan   Crossbones+   -  Reputation: 3073

Like
0Likes
Like

Posted 20 May 2014 - 07:40 AM

So, I've just figure out that my Actor Component Model is flawed by reading L. Spiro posts saying that the game logic handles all the logic of a Character/Player/Node (that actually make a lot of sense).

In my simple implementation I have a IBehaviour class that goes in the Actor class, but AI and Game Logic is supposed to be in a AI class (the Game class in that case). Do you think the following approach is +- correct?
 

class Character : public Behaviour
{
public:
enum StateMask
{
CHARACTER_CAN_JUMP,
CHARACTER_CAN_FLY
};

Character() : mask(0) { }

virtual void Update(Actor* _pActor)
{
if ( mask & CHARACTER_CAN_JUMP ) //just an example
{
//Do something with jump code
}
}
private:
bool mask;
};

class Game
{
public:

void Update()
{

for ( size_t i = 0; i < characters.size(); ++i )
{

if ( SOME_CONDITION )
{
characters[i]->SetMask( characters[i]->GetMask() | CHARACTER_CAN_JUMP ); //just an example
}

}

}
private:

std::vector characters;

};

class Actor
{
public:
void Update( const Time& _time )
{
pBehaviour->Update( this );
}
private:

IBehaviour* pBehaviour;
};

In this case, I'm removing all the Behaviour stuff and putting into the AI class (the Game class in this case).
Then, I'll pass all data related to the Game in a game_config.xml file. When detects a Character, it's added to be managed by it, and adds a new Actor to the Scene.

Whenever my Actor gets created/destroyed, I need to notify to the Game class to remove the pBehaviour, but by doing this, I'm going in the wrong side of the Single Responsability Principle. Now my Scene knows about the Game class (the AI class (the Game class in this case) ).

Any good advice will be instantaneously voted up.


Edited by Irlan, 21 May 2014 - 07:12 AM.


Sponsor:

#2 Buckeye   GDNet+   -  Reputation: 9098

Like
2Likes
Like

Posted 20 May 2014 - 08:25 AM

Not a comment on your organization, but on the implementation of your state mask. It appears you're trying to implement a bitmask for character attributes with binary ORs ("|") not logical ORs ("||"). A more common implementation:

enum {CAN_JUMP = 1, CAN_FLY = 2, CAN_OTHER = 4, ...=8 }; // actual bit positions within the mask
...
unsigned int mask;

Remember, if you add enums beyond 2, without specifying the value, they will continue with 3, 4,... Those masks will be binary 0011, 0100, etc. What you appear to be doing would be better implemented by actual bitmasks for each bit position: 0001, 0010, 0100, 1000, etc.


Please don't PM me with questions. Post them in the forums for everyone's benefit, and I can embarrass myself publicly.

You don't forget how to play when you grow old; you grow old when you forget how to play.


#3 Irlan   Crossbones+   -  Reputation: 3073

Like
0Likes
Like

Posted 20 May 2014 - 08:48 AM

Not a comment on your organization, but on the implementation of your state mask. It appears you're trying to implement a bitmask for character attributes with binary ORs ("|") not logical ORs ("||"). A more common implementation:

enum {CAN_JUMP = 1, CAN_FLY = 2, CAN_OTHER = 4, ...=8 }; // actual bit positions within the mask
...
unsigned int mask;

Remember, if you add enums beyond 2, without specifying the value, they will continue with 3, 4,... Those masks will be binary 0011, 0100, etc. What you appear to be doing would be better implemented by actual bitmasks for each bit position: 0001, 0010, 0100, 1000, etc.

I know this. I posted without the binary format just to keep it simple.


Edited by Irlan, 20 May 2014 - 08:49 AM.


#4 SeanMiddleditch   Crossbones+   -  Reputation: 9902

Like
5Likes
Like

Posted 20 May 2014 - 11:23 AM

Veering off topic, but...
 

I know this. I posted without the binary format just to keep it simple.

If you're using recent-ish versions ofGCC or Clang or using Visual Studio 2013 + November CTP, note that a simple trick is to use constexpr function as a shorthand to make spelling out these enums easier:
 
constexpr unsigned bit(unsigned index) { return 1 << index; }

enum class foo { None, A = bit(0), B = bit(1), C = bit(2), D = bit(0)|bit(3), E };
// None = 0x0000, A = 0b0000, B = 0b0001, C = 0b0010, D = 0b0101, E = 0b0110
I also recommend a macro to turn enum classes (strong enums) into flag-like objects:
 
#define MAKE_FLAGS(E) \
using E##_t = typename std::underlying_type<E>::type; \
constexpr E operator|(E lhs, E rhs) { return (E)((E_t)lhs | (E_t)rhs); } \
constexpr E operator&(E lhs, E rhs) { return (E)((E_t)lhs & (E_t)rhs); } \
constexpr E operator^(E lhs, E rhs) { return (E)((E_t)lhs ^ (E_t)rhs); } \
constexpr E operator|=(E& lhs, E rhs) { return lhs = lhs | rhs; } \
constexpr E operator&=(E& lhs, E rhs) { return lhs = lhs & rhs; } \
constexpr E operator^=(E& lhs, E rhs) { return lhs = lhs ^ rhs; } \
constexpr E operator~(E v) { return (E)(~(E_t)lhs); } \
constexpr bool isset(E bits, E mask) { return ((E_t)bits & (E_t)mask) != E_t{}; } \
constexpr void set(E& bits, E mask) { return bits |= mask; } \
constexpr void clear(E& bits, E mask) { return bits &= ~mask; }

MAKE_FLAGS(foo); // defines operators for foo and also a foo_t you can ignore

foo flags = foo::A | foo::C;
flags &= foo::A | foo::B;
if (isset(flags, foo::B))
  clear(flags, foo::B)
You can also accomplish the above with a template wrapper class instead of a macro, though then you end up with two different names: the wrapper name and the enum namespace name.

I have a bits.h that defines the macro, the helper functions like bit(), and so on. Lots of convenience and you can take advantage of the added safety of C++11 strong enums while still using them as convenient flag types.

#5 Irlan   Crossbones+   -  Reputation: 3073

Like
0Likes
Like

Posted 20 May 2014 - 01:48 PM

Veering off topic, but...
 

I know this. I posted without the binary format just to keep it simple.

If you're using recent-ish versions ofGCC or Clang or using Visual Studio 2013 + November CTP, note that a simple trick is to use constexpr function as a shorthand to make spelling out these enums easier:
 
constexpr unsigned bit(unsigned index) { return 1 << index; }

enum class foo { None, A = bit(0), B = bit(1), C = bit(2), D = bit(0)|bit(3), E };
// None = 0x0000, A = 0b0000, B = 0b0001, C = 0b0010, D = 0b0101, E = 0b0110
I also recommend a macro to turn enum classes (strong enums) into flag-like objects:
 
#define MAKE_FLAGS(E) \
using E##_t = typename std::underlying_type<E>::type; \
constexpr E operator|(E lhs, E rhs) { return (E)((E_t)lhs | (E_t)rhs); } \
constexpr E operator&(E lhs, E rhs) { return (E)((E_t)lhs & (E_t)rhs); } \
constexpr E operator^(E lhs, E rhs) { return (E)((E_t)lhs ^ (E_t)rhs); } \
constexpr E operator|=(E& lhs, E rhs) { return lhs = lhs | rhs; } \
constexpr E operator&=(E& lhs, E rhs) { return lhs = lhs & rhs; } \
constexpr E operator^=(E& lhs, E rhs) { return lhs = lhs ^ rhs; } \
constexpr E operator~(E v) { return (E)(~(E_t)lhs); } \
constexpr bool isset(E bits, E mask) { return ((E_t)bits & (E_t)mask) != E_t{}; } \
constexpr void set(E& bits, E mask) { return bits |= mask; } \
constexpr void clear(E& bits, E mask) { return bits &= ~mask; }

MAKE_FLAGS(foo); // defines operators for foo and also a foo_t you can ignore

foo flags = foo::A | foo::C;
flags &= foo::A | foo::B;
if (isset(flags, foo::B))
  clear(flags, foo::B)
You can also accomplish the above with a template wrapper class instead of a macro, though then you end up with two different names: the wrapper name and the enum namespace name.

I have a bits.h that defines the macro, the helper functions like bit(), and so on. Lots of convenience and you can take advantage of the added safety of C++11 strong enums while still using them as convenient flag types.

 

 

Why don't you guys donate $5 bucks to get a up vote?

Just kidding...


Edited by Irlan, 20 May 2014 - 01:49 PM.


#6 Pink Horror   Members   -  Reputation: 1628

Like
0Likes
Like

Posted 20 May 2014 - 02:17 PM


Whenever my Actor gets created/destroyed, I need to notify to the Game class to remove the pBehaviour, but by doing this, I'm going in the wrong side of the Single Responsability Principle. Now my Scene knows about the Game class (the AI class (the Game class in this case) ).

 

Why is your scene destroying Actors?



#7 L. Spiro   Crossbones+   -  Reputation: 18195

Like
1Likes
Like

Posted 20 May 2014 - 03:37 PM

Not a comment on your organization, but on the implementation of your state mask. It appears you're trying to implement a bitmask for character attributes with binary ORs ("|") not logical ORs ("||"). A more common implementation:

enum {CAN_JUMP = 1, CAN_FLY = 2, CAN_OTHER = 4, ...=8 }; // actual bit positions within the mask
...
unsigned int mask;

See reply by SeanMiddleditch. Always prefer (1 << x), either hand-coded or via a macro or constant expression over raw numbers for bit-based enumerations. One of the things I would propose to the new C++ standard is to add bitenum { A, B, C }; to do this for us as it is very common.
 
 

In my simple implementation I have a IBehaviour class that goes in the Actor class, but AI and Game Logic is supposed to be in a AI class (the Game class in that case). Do you think the following approach is +- correct?

Although I said the game knows about the game logic, I suppose I was technically talking about each game states, not the Game class itself.
CMainMenuState and CCreditsState will have entirely different logic from CMainGameState, and indeed game logic could change between parts of the main game (see Sonic the Hedgehog 2 which mixed primitive 3D gameplay between its side-scrolling gameplay).
The game state is what decides the logic for each part of the game.


Other than that, what you are implementing is a state machine. You can swap behavior classes in and out of an actor and change how it behaves, so a single actor can have one of many “behaviors” at any given time. This is mostly useful for AI, not a player-controlled character (the human’s mind is the state machine that decides what behaviors to inflict upon his or her in-game persona). While it provides a fairly nice way to encapsulate various enemy behaviors, be warned that it can get tedious and possibly become spaghetti trying to code them all one-by-one as you figure out what code needs to moved around to avoid duplication etc.


The biggest problem with your implementation (I won’t get into the circular dependency, which I would solve by making IBehavior know about ActorBase rather than Actor, or make IBehavior know about Actor but only AIActor has an IBehavior, etc.) is that you are setting flags to indicate potentially valid states.
The only time you need to know if the player can jump is when the player tries to jump. In order to figure out “just in case” a player can jump you are doing some kind of ground test etc. every frame when 99% of the time that information isn’t needed on that frame.
Somewhere in the game logic (the state class that handles the main gameplay) there simply needs to be a boolean indicating, “Yes, the player can jump now,” or, “No, the player cannot jump now,” which is tested only when the player tries to jump, and immediately handled appropriately (not saved as a flag to be deferred for later processing).
Most (if not all) “can I perform this action” tests are done similarly, otherwise you will get a ridiculous explosion of state-setting tests which will invariably lead to bugs. While making Dolphin Trainer DS, my coworker tried to do this, and as a result there are out-lying cases where the dolphin would land back in the water after a jump but fail to correct its alignment and start swimming with its nose pointing down.

For this reason, while it was important to note that your enumerations were not actually bit masks, actually you can just get rid of it entirely.
Bit masks are more suitable for keeping track of character statuses. I don’t mean potential statuses such as “can jump” but currently active statuses such as “power jump is active”.


L. Spiro

#8 SeanMiddleditch   Crossbones+   -  Reputation: 9902

Like
0Likes
Like

Posted 20 May 2014 - 04:02 PM

One of the things I would propose to the new C++ standard is to add bitenum { A, B, C }; to do this for us as it is very common.


I already tried floating that on the ISOCPP lists and it got shot down pretty quickly. Whether or not it would be favorably received by the committee proper is another story; someone would have to muster the energy and force of will to write a paper on it, submit it, and then defend it at the next official committee meeting (or find a champion if you can't travel there).

C++ is far more likely to get a std::bit constexpr function and maybe the template wrapper alternative to the MAKE_FLAGS macro.

#9 Irlan   Crossbones+   -  Reputation: 3073

Like
0Likes
Like

Posted 20 May 2014 - 04:38 PM

 

Not a comment on your organization, but on the implementation of your state mask. It appears you're trying to implement a bitmask for character attributes with binary ORs ("|") not logical ORs ("||"). A more common implementation:

enum {CAN_JUMP = 1, CAN_FLY = 2, CAN_OTHER = 4, ...=8 }; // actual bit positions within the mask
...
unsigned int mask;
See reply by SeanMiddleditch. Always prefer (1 << x), either hand-coded or via a macro or constant expression over raw numbers for bit-based enumerations. One of the things I would propose to the new C++ standard is to add bitenum { A, B, C }; to do this for us as it is very common.
 
 

In my simple implementation I have a IBehaviour class that goes in the Actor class, but AI and Game Logic is supposed to be in a AI class (the Game class in that case). Do you think the following approach is +- correct?

Although I said the game knows about the game logic, I suppose I was technically talking about each game states, not the Game class itself.
CMainMenuState and CCreditsState will have entirely different logic from CMainGameState, and indeed game logic could change between parts of the main game (see Sonic the Hedgehog 2 which mixed primitive 3D gameplay between its side-scrolling gameplay).
The game state is what decides the logic for each part of the game.


Other than that, what you are implementing is a state machine. You can swap behavior classes in and out of an actor and change how it behaves, so a single actor can have one of many “behaviors” at any given time. This is mostly useful for AI, not a player-controlled character (the human’s mind is the state machine that decides what behaviors to inflict upon his or her in-game persona). While it provides a fairly nice way to encapsulate various enemy behaviors, be warned that it can get tedious and possibly become spaghetti trying to code them all one-by-one as you figure out what code needs to moved around to avoid duplication etc.


The biggest problem with your implementation (I won’t get into the circular dependency, which I would solve by making IBehavior know about ActorBase rather than Actor, or make IBehavior know about Actor but only AIActor has an IBehavior, etc.) is that you are setting flags to indicate potentially valid states.
The only time you need to know if the player can jump is when the player tries to jump. In order to figure out “just in case” a player can jump you are doing some kind of ground test etc. every frame when 99% of the time that information isn’t needed on that frame.
Somewhere in the game logic (the state class that handles the main gameplay) there simply needs to be a boolean indicating, “Yes, the player can jump now,” or, “No, the player cannot jump now,” which is tested only when the player tries to jump, and immediately handled appropriately (not saved as a flag to be deferred for later processing).
Most (if not all) “can I perform this action” tests are done similarly, otherwise you will get a ridiculous explosion of state-setting tests which will invariably lead to bugs. While making Dolphin Trainer DS, my coworker tried to do this, and as a result there are out-lying cases where the dolphin would land back in the water after a jump but fail to correct its alignment and start swimming with its nose pointing down.

For this reason, while it was important to note that your enumerations were not actually bit masks, actually you can just get rid of it entirely.
Bit masks are more suitable for keeping track of character statuses. I don’t mean potential statuses such as “can jump” but currently active statuses such as “power jump is active”.


L. Spiro

 

Understood. Makes more sense. What have fucked everything is that an Interface (Behaviour) knows about a concrete type (Actor) that doesn't have nothing to do with.

 

class AIActor is a bag of behaviours. Actor can have a AIActor.

AIActor updates an Actor.

class AIActor
{
public:
    void Update(Actor* _pActor)
    {
         if ( !behaviours.empty() )
         {
            behaviours.top()->Update( _pActor );
         }
    }
private:
     std::stack<Behaviour*> behaviours;
};


Edited by Irlan, 20 May 2014 - 06:04 PM.


#10 Irlan   Crossbones+   -  Reputation: 3073

Like
0Likes
Like

Posted 20 May 2014 - 06:15 PM

 


Whenever my Actor gets created/destroyed, I need to notify to the Game class to remove the pBehaviour, but by doing this, I'm going in the wrong side of the Single Responsability Principle. Now my Scene knows about the Game class (the AI class (the Game class in this case) ).

 

Why is your scene destroying Actors?

 

Because manages them.



#11 L. Spiro   Crossbones+   -  Reputation: 18195

Like
2Likes
Like

Posted 20 May 2014 - 06:35 PM

class AIActor is a bag of behaviours.

I disagree. An AI can have at one time only one state/behavior.
There does not need to be a notification to the game state class that an actor has been deleted and so its behavior must be deleted.
Firstly, this can be achieved more easily via the simple use of smart pointers, but secondly it shouldn’t be done at all. The pool of behaviors doesn’t need to be released until the game state is over.


L. Spiro

#12 Irlan   Crossbones+   -  Reputation: 3073

Like
0Likes
Like

Posted 20 May 2014 - 06:47 PM

 

class AIActor is a bag of behaviours.

I disagree. An AI can have at one time only one state/behavior.
There does not need to be a notification to the game state class that an actor has been deleted and so its behavior must be deleted.
Firstly, this can be achieved more easily via the simple use of smart pointers, but secondly it shouldn’t be done at all. The pool of behaviors doesn’t need to be released until the game state is over.


L. Spiro

 

That's what I do.

The Scene (for instance) is initialized inside the Gameplay (A GameState) class.

 

Gameplay::Init()->Scene->Load()

Gameplay::Destroy()->Scene->ClearActors()

 

I'm not using a actor/behaviour pool yet. sad.png


Edited by Irlan, 20 May 2014 - 06:47 PM.


#13 L. Spiro   Crossbones+   -  Reputation: 18195

Like
1Likes
Like

Posted 20 May 2014 - 06:57 PM

Actors should be created and deleted on-the-fly as they are to come to exist in the game scene and when they are removed from it respectively.
Keeping around dead actors is nothing but overhead, if not in CPU usage then in memory, but typically both.


L. Spiro

#14 Irlan   Crossbones+   -  Reputation: 3073

Like
0Likes
Like

Posted 20 May 2014 - 07:06 PM

Actors should be created and deleted on-the-fly as they are to come to exist in the game scene and when they are removed from it respectively.
Keeping around dead actors is nothing but overhead, if not in CPU usage then in memory, but typically both.


L. Spiro

I know, I always use new/delete and I'm not talking about memory allocation because I have a lot of experience with it.

What I'm saying is the Scene create and delete (manages) all actors.



#15 Irlan   Crossbones+   -  Reputation: 3073

Like
0Likes
Like

Posted 20 May 2014 - 07:35 PM

The problem is: if my Game class have a pointer to an Actor (the player) and the Scene delete all of them, the pointer will be a dangling pointer.



#16 L. Spiro   Crossbones+   -  Reputation: 18195

Like
1Likes
Like

Posted 20 May 2014 - 07:53 PM

Your game state doesn’t have pointers to actors. It accesses them through the scene manager(s).

(Do not make scene managers static, singletons, or otherwise “only one”. The game state class can make as many scene managers as it wants (even if most of the time there will be only one). Very useful for multi-perspective games. It was a pain in the ass when I had to make a golfing game with only one scene manager where the 3D golf world was overlayed by a 3D silhouette of a golfer in a totally different perspective and environment—a totally different setup from split-screen where there would still be only one scene but 2 cameras.)


L. Spiro

#17 Irlan   Crossbones+   -  Reputation: 3073

Like
0Likes
Like

Posted 20 May 2014 - 08:37 PM

Your game state doesn’t have pointers to actors. It accesses them through the scene manager(s).

(Do not make scene managers static, singletons, or otherwise “only one”. The game state class can make as many scene managers as it wants (even if most of the time there will be only one). Very useful for multi-perspective games. It was a pain in the ass when I had to make a golfing game with only one scene manager where the 3D golf world was overlayed by a 3D silhouette of a golfer in a totally different perspective and environment—a totally different setup from split-screen where there would still be only one scene but 2 cameras.)


L. Spiro

 

So, my Scene has a pointer to a main Actor?

If so when updating my Scene, I'll calculate the Camera (worldMatrix) values based on that Actor?

Why do you mean?

 

The only global that have is the main function, but I don't think I can make a Object. laugh.png


Edited by Irlan, 20 May 2014 - 08:39 PM.


#18 L. Spiro   Crossbones+   -  Reputation: 18195

Like
1Likes
Like

Posted 20 May 2014 - 09:21 PM

So, my Scene has a pointer to a main Actor?

No.
Currently your Game class has std::vector characters. This belongs in the scene manager.


L. Spiro



#19 Irlan   Crossbones+   -  Reputation: 3073

Like
0Likes
Like

Posted 21 May 2014 - 05:26 AM

 

So, my Scene has a pointer to a main Actor?

No.
Currently your Game class has std::vector characters. This belongs in the scene manager.


L. Spiro

 

My scene has actors and characters or by character you mean an actor?

My scene just need  to know about an actor isn't?



#20 L. Spiro   Crossbones+   -  Reputation: 18195

Like
1Likes
Like

Posted 21 May 2014 - 05:50 AM

Every actor in the game, be it a character or otherwise, is managed by the scene manager.
I mean what I said in the last post: Your Game class example has a member called “std::vector characters”.  It’s pseudocode so I don’t know what the vector actually contains, but I would assume it is an array of Actor * (why would Character inherit from Behavior instead of Actor? A Character is an Actor).

 

To be clear: a Character is a form of actor and should inherit from Actor.

SceneManager should have std::vector<Actor *> m_vActors, which is an array of all characters/actors in the scene.

 

 

L. Spiro






Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS