开发者

Accomplish this task in C++; Migration from AS3.0

I've got way too much information to work with, so for now I'll consider this question answered until I can sort it all out and decide on the final implementation! Thanks a ton gf and Simon Buchan. I wish I could accept both of your answers, since they're both definite possibilities!

Additional / Revised Conceptual Information as suggested:

What I am aiming to do;

I am making a game. In this game every object used is an instance of the DOBJ class. The TUR class extends the DOBJ class. The SHO class extends the TUR class.

Each TUR class has an array of SHO's stored in it's SHOARR array. Each SHO instance needs to be given a set of instructions.

I know for a fact I could make 1000's of different SHO classes that have their instructions set during construction.

However, considering I will have so many different acting SHO instances, I was interested in another way to pass a set of instructions. Through the contruction of the SHO would be the ideal.

The instructions I am attempting to pass to each SHO are simple if statements;

if(frame > 64) { rotation += 4; };

if(state == 0 && frame < 32) { xs = 12; ys = 12; state = 1; };

Original question

Migration from ActionScript3.0 to C++ is proving to be a trial indeed. Thanks to those who have answered my questions thus far and also to those who opened stackoverflow in the first place. Onto the question... (TL;DR near the bottom to get straight to the question)

I'm attempting to apply the same logic that I could apply in AS3.0 to my project in C++ and it's just not going very well.

In AS3.0 I was used to slapping any and every datatype into an Array. It made things pretty simple. Now that I've run into C++ dev, I realized that I can't exactly do that anymore.

So now I'm stuck with this problem of rewriting a little AI system in a new language, where the driving point of the system isn't even compatible!

Here's an example of a piece of the code I was writing in AS3.0;

AI[NUM][1]( OBJ, AI[NUM][2], AI[NUM][3] );

AI being an array, NUM being an integer, OBJ being an instance of a class.

This line obviously called the function in the second element of the first array in the main array with the arguments being a class in which to perform the function on, whatever was in the third element of the first array of the main array, and likewise the fourth element.

In this case;

AI[NUM][1] would be a function AI[NUM][2] would be a variable AI[NUM][3] would be a number

Generally, my AI was run on calling a function to change or compare the variable with a number.

An example would be;

CompareST( someObject, "x", 500 );

and return true if someObject's x variable was smaller than (ST) 500.

The AI array itself was just filled with arrays of calls similar to this.

Quite new to C++ I had no idea how to go about this, so I did a bit of searching and reading of many different websites and came to the conclusion that I should look into function pointers.

However, after reading a bit into them, I've come to the conclusion that it won't help me realize my goal. While it did help me call functions like I wanted to call them, it doesn't help me stack different datatypes into one large array of arrays.

TL;DR

EDIT++:

What I need for each object is a set of instructions to be checked every frame. However, for each instance of the class, the instructions have to be different.

I plan on having a LOT of different instances, so making a class for each one is unreasonable.

Thus, I needed a way to pass a set of instructions to each one through it's constructor and read + execute them at any time their think() function is called.

My ultimate goal (aside from finding out about a better way to go about this) would be to be able to have an array of function calls, like;

A[n][0]( O, A[n][1], A[n][2] );

Where; O is the instance the function is altering A[n][0] is a function (Equality or Comparison) A[n][1] is the variable, eg; "x", O["x"] (or a pointer to that variable in the case of C++) A[n][2] is the value to alter the variable by, or compare it to.

And I'm not sure how I would rewrite this into C++, or alter it to work in another way.

Aftermath / Additional Information

What I'm actually aiming to do is be able to give an object a set of instructions at the time of it's creation, through the constructor. For example upon creation give an object instructions to wait 64 frames, and then rotate in the opposite direction, would have been something like this;

t.AI = [ [ 1, AIF.CompareET, "S开发者_C百科TATE", 0, AIF.CompareGT, "FRAME", 64, 0, AIF.EqualityAT, "baseRotation", 180, AIF.EqualityET, "STATE", 1 ] ];

In pseudocode;

(The 1 in the array denotes how to read the rest of the array, in this case everything before the odd 0 [ The one that comes after 64 ] is a comparison. If any of those fail, anything after the 0 will not be looked at )

Compare STATE is equal to (ET) 0, if true

Compare FRAME is greather than (GT) 64, if true

Add 180 to (AT) baseRotation, Set STATE equal to 1

Sorry that this turned out really long. I hope it's understandable, and I'm not asking something stupidly difficult to explain.


You can store functions using function pointers or functors. Variant types though are not natively supported by C++, you have to use custom solutions there.

One possibility would be to use Boost.Any (or better, Boost.Variant if you only use a fixed set of types):

typedef void (*Function)(Object*, const std::string&, boost::any&);
std::vector<Function> functions;

Given some function:

void f(Object* obj, const std::string& name, boost::any& value) {
    // ...
}

you could store and call it similar to your example:

functions.push_back(&f);
functions[0](obj, "x", boost::any(500));

To utilize a declarative syntax, there are three options that come to my mind:

  • you use a similar approach and have central "interpreter" function, e.g. based on a switch (don't forget to switch to integers or pointers-to-members instead of strings if you need performance)
  • you invent your own language and generate C++ code from description files
  • you compose function objects in a declarative way

To do composition, you could use Boost.Bind or something like custom objects that represent operations:

struct Operation {
    virtual ~Operation() {}
    virtual bool operator()(Object&) = 0;
};    

template<class T>
struct GreaterThen : Operation {
    typedef T Object::*Member;
    Member member;
    const T value;
    CompareGT(Member member, const T& value) : member(member), value(value) {}
    bool operator()(Object& obj) { return (obj.*member > value); }
};

template<class T>
struct SetTo : Operation {
    typedef T Object::*member;
    Member member;
    const T value;
    SetTo(Member member, const T& value) : member(member), value(value) {}
    bool operator()(Object& obj) { obj.*member = value; return true; }
};

Now we can build operation lists:

typedef std::vector<Operation*> OpList;
OpList operation;
operations.push_back(new GreaterThen<int>(&Object::Frame, 64));
operations.push_back(new SetTo<int>(&Object::State, 1));

We can use helper functions to avoid having to specify the template types:

template<class T>
Operation* opGreaterThen(T Object::*mem, const T& val) {
    return new GreaterThen<T>(mem, val);
}

Assuming a similar helper for SetTo and using Boost.Assign the above becomes:

OpList operations = boost::assign::list_of
    (opGreaterThen(&Object::Frame, 64)) 
    (opSetTo      (&Object::State,  1));

Executing the operations becomes the following then:

OpList::iterator it = operation.begin();
for( ; it != operations.end(); ++it) {
    Operation& op = *it; // just for readability
    if(!op(someObject)) break; // stop if operation returns false        
}


Wow.

Reading through that slowly suggests what you're trying to end up with is an array of function calls and you can choose a different function with the same parameters (but different implementation) for different actions and choose the correct one for the correct case.

If that is the case, you're looking for function pointers. Try this tutorial.

You should be able to use a function pointer with an argument set and point it to the correct function based on your needs. You won't need an array of function pointers for this either - any function that matches the definition should do. From the tutorial, declare a function pointer like this:

int (TMyClass::*functptr)(classname, int, int) = NULL;                // C++

Then assign it later:

this.functptr = &TMyClass::doitthisway; 


While it is possible (although a pain) to have an array of arbitrary types, you pretty much never need it, since you have to know something about what is where to do anything interesting with it: for example, your 'TL;DR' example seems to look something like:

struct AIRule {
    // Can only handle comparing ints, see later for more general solution.
    typedef bool compare_type(AIObject*, AIObject::*int, int);
    compare_type* compare;
    AIObject* object;
    AIObject::int* member;
    int comparand;
};

So now you can do something like:

bool ai_equal(AIObject* object, AIObject::int* member, int comparand) {
    return object->*member == comparand;
}

...
    ai[n].compare = &ai_equal;
    ai[n].object = some_object;
    ai[n].member = &AIObject::some_member;
    ai[n].comparand = 50;
...
    if (ai[n].compare(ai[n].object, ai[n].member, ai[n].comparand)) {
        ...
    }

This just moves the any type problem from the rules array to member though. C++ needs to know at least how many bytes a member is, and a string (for example) can be much bigger than an int. You can get around this by using pointers: which essentially is C++'s version of any, but you then need to delete it yourself (or you will leak memory!), at which point the interface method below becomes simpler.

If I was doing what you seem to want, I would use inheritance:

struct Sprite {
    int frame;
    double rotation;

    Sprite() {
        frame = 0;
        rotation = 0.0;
    }
    virtual ~Sprite() {}

    virtual void think() {
        ++frame;
    }

    virtual void draw() {
        ...
    }
};

struct RotatingSprite : public Sprite {
    int state;

    MyShape() {
        state = 0;
    }

    void think() {
        Sprite::think();
        if (state == 0 && frame > 64) {
            state = 1;
            rotation += 180.0;
        }
    }
};

Or a function pointer:

struct Sprite {
    int frame;
    double rotation;
    void (*think)(Sprite*);

    Sprite() {
        frame = 0;
        rotation = 0.0;
    }
};

void rotate_think(Sprite* sprite) {
    if (sprite->state == 0 && sprite->frame > 64) {
        sprite->state = 1;
        sprite->rotation += 180.0;
    }
}

...
    sprite->think = &rotate_think;

If you really need to do it dynamically I would recommend using the ++ part of C++. For the predicates (a predicate is just something that returns a boolean, like isLowerCase()) create an AIPredicate interface, and the actions an AIAction interface:

struct AIPredicate {
    // "When you delete an AIPredicate, delete the full type, not just this interface."
    virtual ~AIPredicate() {}
    // "You can treat this as a function (operator()) but I'm not providing an implementation here ( = 0)"
    virtual bool operator()(AIObject* object) = 0;
};

struct AIAction {
    virtual ~AIAction() {}
    virtual void operator()(AIObject* object) = 0;
};

struct AIRule {
    // std::auto_ptr (or std::unique_ptr if you can use C++0x) will delete predicate for you.
    // Add "#include <memory>" to your includes if it complains (most std headers will include it already)
    std::auto_ptr<AIPredicate> predicate;
    std::auto_ptr<AIAction> action;
};

Now you can make types like:

struct AIFrame : public AIPredicate {
    // Implement the operator() member AICondition promises.
    bool operator()(AIObject* object) {
        return object->foo < 100;
    }
};

...
    // Use .reset() instead of = if you use std::unique_ptr.
    ai[n].predicate = new AIFooIsLow();

If you want to have a very general predicate type, you can use the very powerful (and complicated) templates feature:

// The naming convention I'm using here is 'T'TitleCase for template parameters, TitleCase for types,
// lower_case for arguments and variables and '_'lower_case for members.
template<typename TMemberType, AIObject::TMemberType* TMember>
struct AIMemberEquals : public AIPredicate {
    // Constructor: Initializes a new instance after it is created.
    AIMemberEquals(TMemberType comparand) {
        // Save comparand argument so we can use it in operator().
        _comparand = comparand;
    }

    bool operator()(AIObject* object) {
        return object->*TMember == comparand;
    }
    // Stores the value to compare.
    TMemberType _comparand;
};

Unfortunately, creating templates looks a bit crazy:

ai[n].predicate = new AIMemberEquals<int, &AIObject::some_member>(100);

Read it as "create a new instance of (the type that AIMemberEquals applied to int and (the some_member member of AIObject) creates), with the argument 100".

When you have multiple predicates memory management becomes a bit more difficult without C++0x's unique_ptr or shared_ptr, types that will delete the object for you, since std::auto_ptr doesn't work in containers:

#include <vector>

struct AIData {
    // vector is fairly close to AS3's Array type, it is a good default for
    // arrays of changing or unknown size.
    std::vector<AIPredicate*> predicates;

    // Destructor: will be run before the memory for this object is freed.
    ~AIData() {
        for (int i = 0; i != predicates.size(); ++i) {
            delete predicates[i];
        }
    }
};

...
    ai[n].predicates.push_back(new AIFooIsLow());
...
    for (int i = 0; i != ai[n].predicates.size(); ++i) {
        (*ai[n].predicates[i])(ai[n].object);
    }

In C++0x:

struct AIData {
    // unique_ptr will delete it for you, so no ~AIData() needed.
    std::vector<unique_ptr<AIPredicate>> predicates;
};

Your final example could in C++ look something like:

std::auto_ptr<Shape> shape(new Shape());
...
std::auto_ptr<AIRule> rule(new AIRule());

rule->predicates.push(new AIMemberEquals<int, &Shape::state>(0));
rule->predicates.push(new AIMemberGreater<int, &Shape::frame>(64));

rule->actions.push(new AIAddMember<double, &Shape::rotation>(180.0));
rule->actions.push(new AISetMember<int, &Shape::state>(1));

shape->ai.push(rule); // .push(std::move(rule)); if you are using unique_ptr

Certainly not as pretty, but it works and is fairly flexible.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜