0
\$\begingroup\$

I'm trying to find a way to allow both input and AI to call actions of an actor in a component based structure for a 2d turn based game. The actions make use of the components. To keep the system flexible I don't want to statically set what actions the actor has in the actor class.

Right now I have action classes who have ids and I'm using maps to hold the actions and allow the input and AI to execute actions by calling a function in the actor class. Now I've run into all sorts of issues like it not working well with using different parameter types or amounts and other things. So is there a better way to do what I try to achieve?

This is my actor class that holds a map with actions that take two ints:

class Actor
{
    friend Scene; /* allows the scene to move the acotr */

public:
    Actor();
    ~Actor();

    int getCellX() { return m_CellX; }
    int getCellY() { return m_CellY; }

    void addComponent(Component* component);

    void addAction(PositionAction*);

    bool executeAction(string, int, int);

private:
    int m_CellX = 0;
    int m_CellY = 0;

    map<string, Component*> m_Components;

    map<string, PositionAction*> m_PositionActions;
    map<string, TargetableActorAction*> m_TargetableActorActions;
};

This shows the hierarchy of the action class:

class Action
{
public:
    Action();
    ~Action();

    virtual string getID() = 0; /* used to identify the action in the map */
};

class PositionAction : public Action
{
public:
    PositionAction();
    ~PositionAction();

    virtual bool execute(int, int) = 0;
};

class MoveAction : public PositionAction
{
public:
    MoveAction(MoveComponent&);
    ~MoveAction();

    bool execute(int, int);

    string getID() { return "move"; }

private:
    MoveComponent& m_MoveComponent;
};

And this is what the move action does:

MoveAction::MoveAction(MoveComponent& moveComponent) : m_MoveComponent(moveComponent)
{
}


MoveAction::~MoveAction()
{
}

bool MoveAction::execute(int dX, int dY)
{
    /* will check whether some conditions hold */

    /* move the actor, returns false if failed */
    bool sucess = m_MoveComponent.move(dX, dY);

    return sucess;
}

This creates the player actor with a move component and the move action.

Actor* player = new Actor();

MoveComponent* moveComponent = new MoveComponent(scene);

player->addComponent(moveComponent);
player->addAction(new MoveAction(*moveComponent));

return player;

And you can execute an action like this:

actor.executeAction("move", 1, 0);
\$\endgroup\$
  • \$\begingroup\$ Can you show an example of what is not working? \$\endgroup\$ – Surt Sep 17 '14 at 14:36
  • \$\begingroup\$ @Surt It works as intented. I posted the system I have now to give a better idea of what I'm trying to do. A problem with this system is that if I want to add an action with different parameters, I'll have to add a new map in actor, a new class that inherits from action, a new add action function and a new action execute function. I'm interested in whether there are better ways. \$\endgroup\$ – Cyborg Sep 17 '14 at 21:46
0
\$\begingroup\$

It sounds like what you want is named delegates. If I understand it correctly, your actions are just a call to a function in an existing component. Then all you need is to store a member function pointer and a pointer to the component, instead of a custom class.

You can do this directly with member function pointers, or wrap it in a delegate class. It would look something like this:

class Actor
{
    // ... same as before
    typedef delegate<bool(int,int)> Action;
    void addAction(const string& actionName, Action action);
    bool executeAction(const string& actionName, int arg1, int arg2);
private:
    map<string, Action> m_Actions;
};

Actor* player = new Actor();
player->addAction("move", Actor::Action(moveComponent, &MoveComponent::move));

The trick is to build a templatized delegate class that will handle any component and, as long as your signature stays the same (return a bool, take two ints) then you can abstract all the actions easily.

If you need to take multiple components, then you'll have to fall back to your original method, especially if you want to bind the components in advance. If not, then you can have the action perform the lookup, if it's quick enough, and implement actions as free functions:

class Actor
{
    // ... same as before
    typedef delegate<bool(Actor,int,int)> Action;
};

bool MoveAction(Actor* actor, int dX, int dY)
{
    MoveComponent* moveComponent = actor.getComponent("move");
    if (moveComponent) return moveComponent->move(dX, dY);
    return false;
}

Actor* player = new Actor();
player->addAction("move", Actor::Action(MoveAction));

There are many resources for how to implement a delegate class like that. This is a great starting point, maybe simpler than this implementation which works well and is broadly used. There may be better ways to do this in C++11.

Roughly speaking, you use templates to bind the member function or free function pointer to an automatically generated function that knows how to call it.

\$\endgroup\$
  • \$\begingroup\$ Thanks for your answer and the resources, probably a lot more in the direction I'm looking for. I think I didn't make this clear but I do want to use different parameters, but all with the same return type. The actions also all go through some common checks that checks whether they are allowed to be executed (an example: check whether a player is not stunned). I think, ideally, is to have some sort of event system that functions of components can listen to and matching events are grouped and contained in several components. Those events also do some checks before triggering them. \$\endgroup\$ – Cyborg Oct 12 '14 at 21:41
0
\$\begingroup\$

There isn't necessarily a reason to have all these action classes you are defining but instead have a component that represents specific state about a certain use case. So your movement component has a series of booleans that indicate forward, backward, left, right, etc.

Now each game tick, you have system that inspects all movement components and sees what their current state is and calculates a velocity and direction and passes that to physics for simulation.

In the case of players, your PlayerController interprets keyboard/mouse operations and sets state on the movement component. Similarly your AIController interprets behavior tree results for AI and sets appropriate state on the movement component. From this point, they both follow the same path of execution through the movement system which interacts with physics.

\$\endgroup\$
  • \$\begingroup\$ Thanks for your answer, but I'm not sure if this will work well for this situation. With this method, the user of the actor needs to know what component causes what which is what I'm trying to prevent. It might be good to mention that I'm creating a turn based 2D game (I'll edit the question to add that). Ideally, the user just tries to execute an action and gets back if an action succeeded (for example because he doesn't have it or because of a collision) and be able to act on that. \$\endgroup\$ – Cyborg Sep 18 '14 at 10:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.