Jump to content

[c++] Scripts via callback (Rick's example)


Skrakle
 Share

Recommended Posts

I'm trying to use these classes to write my scripts:

 

 

#ifndef _ISCRIPT_H
#define _ISCRIPT_H

// derive your "script" classes from this interface to make it an "entity script"
class IScript
{
protected:
Entity* entity;
public:
IScript(Entity* e) { entity = e; }
virtual void Collision(Entity* entity, float* position, float* normal, float speed);
virtual void Draw();
virtual void DrawEach(Camera* camera);
virtual void PostRender(Camera* camera);
virtual void UpdateMatrix();
virtual void UpdatePhysics();
virtual void UpdateWorld();
};

// example "script" that you would have to make
class PlayerScript : public IScript
{
public:
PlayerScript() :IScript(entity) {} // default constructor
virtual void Collision(Entity* entity, float* position, float* normal, float speed)
{
}

virtual void Update()
{
}

};



// define each global hook function here and the same idea applies to all. cast the user data on the entity
// to IScript and if it's not NULL call that instance method
void CollisionHook(Entity* entity0, Entity* entity1, float* position, float* normal, float speed)
{
IScript* script = (IScript*)entity0->GetUserData();

if (script != NULL)
{
script->Collision(entity1, position, normal, speed);
}
}

void UpdateWorldHook(Entity* entity)
{
IScript* script = (IScript*)entity->GetUserData();

if (script != NULL)
{
script->UpdateWorld();
}
}

void UpdatePhysicsHook(Entity* entity)
{
IScript* script = (IScript*)entity->GetUserData();

if (script != NULL)
{
script->UpdatePhysics();
}
}



#endif

 

 

 

 

I'm getting multiple unresolved external errors: LNK2001: unresolved external symbol "public: virtual void __thiscall IScript::Collision(class Leadwerks::Entity *,float *,float *,float)" (?Collision@IScript@@UAEXPAVEntity@Leadwerks@@PAM1M@Z) C:\Users\Admin\Documents\Leadwerks\Projects\TestProject\Projects\Windows\App.obj TestProject

 

 

APP.CPP

#include "App.h"
#include "IScript.h"

using namespace Leadwerks;


App::App() : window(NULL), context(NULL), world(NULL), camera(NULL) {}
App::~App() { delete world; delete window; }

bool App::Start()
{

window = Leadwerks::Window::Create("TestProject", 250, 0, 1024, 768);
context = Context::Create(window);
world = World::Create();

IScript* script = new PlayerScript();


}

 

What am i missing?

Link to comment
Share on other sites

In your derived PlayerScript-class, you defined your methods as "virtual", which they aren't.

They are only "virtual" in their parent-class.

The compiler is searching for their implementations, which it can't find.

Just leave out the "virtual"-keyword in the PlayerScript-class

Link to comment
Share on other sites

I removed virtual from the PlayerScript class like you suggested and left it in the parent and i'm still getting unresolved externals.

 

class IScript
{
protected:
   Entity* entity;
public:
   IScript(Entity* e) { entity = e; }
   virtual void Collision(Entity* entity, float* position, float* normal, float speed);
};

class PlayerScript : public IScript
{
public:
   PlayerScript() :IScript(entity) {}

   void Collision(Entity* entity, float* position, float* normal, float speed)
   {
   }

};

Link to comment
Share on other sites

You also have to make your functions in the base class "pure virtual". This is done by setting them to 0.

The function from the base-class should read:

 

virtual void Collision(Entity* entity, float* position, float* normal, float speed)=0;

 

For more info on the difference between virtual and pure virtual refer to http://stackoverflow.com/questions/1306778/c-virtual-pure-virtual-explained

  • Upvote 1
Link to comment
Share on other sites

PlayerScript() :IScript(entity) {}

 

What is entity here? It's been awhile since I've worked heavily in C++ but I believe your PlayerScript ctor also needs the Entity entity in it's param list.

 

 

PlayerScript(Entity* entity) : IScript(entity)

{

 

}

Link to comment
Share on other sites

PlayerScript() :IScript(entity) {}

 

What is entity here? It's been awhile since I've worked heavily in C++ but I believe your PlayerScript ctor also needs the Entity entity in it's param list.

 

 

PlayerScript(Entity* entity) : IScript(entity)

{

 

}

The entity is in the parent class and parameter is passed in the PlayerScript constructor:

PlayerScript() :IScript(entity) {} // default constructor

 

 

 

You also have to make your functions in the base class "pure virtual". This is done by setting them to 0.

The function from the base-class should read:

 

virtual void Collision(Entity* entity, float* position, float* normal, float speed)=0;

 

For more info on the difference between virtual and pure virtual refer to http://stackoverflow.com/questions/1306778/c-virtual-pure-virtual-explained

That did it, i'll go read up on the link you provided, thanks a lot!

Link to comment
Share on other sites

The entity is in the parent class and parameter is passed in the PlayerScript constructor:
PlayerScript() :IScript(entity) {} // default constructor

 

 

hmm, how do you create these objects? I assume like:

 

 

IScript* e;

e = new PlayerScript();

 

 

This would mean you aren't attaching any LE entity because inside PlayerScript ctor you are passing it's parent "entity" variable to it's parent (which entity will be null) and assigning it to itself (which again will be null). How are you ever getting the entity in question to that variable? This should compile but I would think it would give a run-time error.

 

Is this working for you? I know I've been out of the C++ game for some time but this seems strange.

Link to comment
Share on other sites

The entity is in the parent class and parameter is passed in the PlayerScript constructor:
PlayerScript() :IScript(entity) {} // default constructor

 

 

hmm, how do you create these objects? I assume like:

 

 

IScript* e;

e = new PlayerScript();

 

 

This would mean you aren't attaching any LE entity because inside PlayerScript ctor you are passing it's parent "entity" variable to it's parent (which entity will be null) and assigning it to itself (which again will be null). How are you ever getting the entity in question to that variable? This should compile but I would think it would give a run-time error.

 

Is this working for you? I know I've been out of the C++ game for some time but this seems strange.

 

 

In App::Start, after loading the entity, i use this:

IScript* script = new PlayerScript();
entities[0]->SetUserData((void*)script);
entities[0]->AddHook(Entity::CollisionHook, (void*)CollisionHook);

 

I've tested the callbacks, they are being triggered. I'd like to know if when the app terminates, do i have to remove the hooks or LE does it automatically?

Link to comment
Share on other sites

Yeah, what you have works, but you aren't setting your IScript entity member anywhere and I don't see where you are using it either. I think if you try to use it you'd get an error saying it was NULL.

You're right, entity does return NULL when first instantiated and SetUserData((void*)script); seems to set it as i'm able to view the entity position in the callback trigger.

Link to comment
Share on other sites

SetUserData((void*)script); wouldn't be setting your IScripts entity variable.

 

I think you are able to view then entity position in the callback trigger because your callback function has the same name for it's parameter as your member variable ie. "entity" and when inside a function it'll use the function parameter if there is a name conflict.

 

So when you do something like the below notice your function parameter is also named entity just like your member variable. Inside that function when you use the entity variable it's referring to the parameter 'entity' not your class member 'entity'. Inside your callbacks trying changing it to this->entity and I think it would fail.

 

You might not even need that member variable 'entity' honestly. I always used it because I set the callbacks inside the base class instead of outside of it like you are doing. I wanted my code that looped over each entity to be clean of that stuff and have the class be self contained in what it does. If you were to do that then you'd have to also pass the entity into your child classes so you can pass it along to your base class.

  • Upvote 1
Link to comment
Share on other sites

SetUserData((void*)script); wouldn't be setting your IScripts entity variable.

 

I think you are able to view then entity position in the callback trigger because your callback function has the same name for it's parameter as your member variable ie. "entity" and when inside a function it'll use the function parameter if there is a name conflict.

 

So when you do something like the below notice your function parameter is also named entity just like your member variable. Inside that function when you use the entity variable it's referring to the parameter 'entity' not your class member 'entity'. Inside your callbacks trying changing it to this->entity and I think it would fail.

 

You might not even need that member variable 'entity' honestly. I always used it because I set the callbacks inside the base class instead of outside of it like you are doing. I wanted my code that looped over each entity to be clean of that stuff and have the class be self contained in what it does. If you were to do that then you'd have to also pass the entity into your child classes so you can pass it along to your base class.

 

Ahh i see what you mean now, i would've scratched my head at some point wondering why the iscript entity would be null. So i added this in the PlayerScript:

void SetEntity(Entity* entity) {
ent = entity;  // i renamed the member for ent
}

 

 

And i'm setting it up like this:

IScript* script = new PlayerScript();
script->SetEntity(entities[0]);

 

It's all working now, thanks for noticing that!

Link to comment
Share on other sites

That's one way to do it, but honestly if you kept the way you had the IScript ctor and just passed the entity to the PlayerScript() (and any other children of IScript) it would work as well.

 

IScript* script = new PlayerScript(entities[0]);

 

 

I'm shocked what you have even works. You said you added SetEntity() at the PlayerScript level but I think you must have meant at the IScript level since your script pointer is an IScript and wouldn't be able to see SetEntity() function as the PlayerScript level unless you casted script to PlayerScript which in your example code you didn't.

Link to comment
Share on other sites

Here's how both classes look like now:

class IScript
{
protected:
Entity* ent;
public:
IScript() {}
virtual void Collision(Entity* entity, float* position, float* normal, float speed) = 0;
virtual void UpdateWorld() = 0;
virtual void UpdatePhysics() = 0;
virtual void SetEntity(Entity* entity) = 0;
};

 

class PlayerScript : public IScript
{
public:
PlayerScript() :IScript() {}
void Collision(Entity* entity, float* position, float* normal, float speed) {

}

void UpdateWorld() {
}

void UpdatePhysics() {
}

void SetEntity(Entity* entity) {
ent = entity;
}


};

I've changed my constructor for PlayerScript(Entity* e) :IScript() { ent = e; } and removed SetEntity method and now using IScript* script = new PlayerScript(entities[0]); like you suggested.

 

Thanks!

Link to comment
Share on other sites

Generally speaking with something like this your children would just be pass-thru's to get the variable to it's parent so it's parent can set the variable itself so you don't have to do it in each future child you create.

 

 

PlayerScript(Entity* e) : IScript(e)
{
}


IScript(Entity* e) : ent(e)
{
}

 

Right now you only have a PlayerScript child and aren't doing much in IScript but you may in the future have more children of IScript and do more common things that all IScript children should have (this is the main idea behind having a base class right).

 

If you wanted to take it even futher you can set the hooks inside IScript ctor even.

 

IScript(Entity* e) :ent(e)
{
e->SetUserData((void*)this);
e->AddHook(Entity::CollisionHook, (void*)CollisionHook);
// etc
}

 

 

It's nice because now your IScript class is doing a job. It's job is to set the user data and hooks. So now you can easily reuse or release this class to the world and we/you don't have worry about setting the hooks anymore (or if you reuse it down the line you don't have to worry either) during entity looping.

 

So think about the purpose of this class. It's to handle the hooks so you can derive your own child scripts from it. Therefore it should register against the hooks for us.

 

Now I'm not saying you will release this but that's the mentality to have with object oriented programming. Pretend you will release it or at least that you will reuse it in other projects (because you probably will) and make it easy on us/your future self. Now your C hook functions could even be inside the IScript.cpp file (not part of the class but just inside the .cpp file itself so everything about this script assigning functionality is contained in 1 header and 1 source file). Now all you have to do for future projects or tell your future users of this class to just include IScript.h/cpp into our projects, derive our own script classes from it, and create instances of the object and that's it! The rest is encapsulated from the user of your class (user or your future self). You now have a nice self contained class doing it's job. Making our job easier smile.png

  • Upvote 1
Link to comment
Share on other sites

I'm passing several objects in my IScript initializer:

class PlayerScript : public IScript
{
public:
PlayerScript(Entity* e, Map_Entity* me, Window* w) :IScript() {
m_entity = e;
map_entity = me;
m_window = w;
}

void Collision(Entity* entity, float* position, float* normal, float speed) {
}

 

 

Map_Entity is a simple class with variables that i'm passing to the script when initialized:

class Map_Entity {
public:
float speed;
std::string boneWeapon;
std::string boneShield;
uint8 anim_speeds[5];
};

 

Problem is, the Map_Entity data passed to the script doesn't retain their values after exiting the constructor method. I could copy every values manually or do a memcpy but i'd rather not if i don't have to. I've been reading for hours and looking at a lot of examples and still can't figure it out.

Link to comment
Share on other sites

In App.h

Map_Entity* map_characters[100];

 

 

In App.cpp (App::Start)

entities[0] = Model::Load("Models/Characters/Dwarf/dwarfmale_run.mdl");
map_characters[0] = helpers->InitModel(entities[0], ENTITY_TYPE::PC, "Rygar", Vec3(0, 0, -5.73), 0.02, 1, COLLISION_TRIGGER, Entity::CharacterPhysics, "dwarfmale_run_bone_64", "dwarfmale_run_bone_LFingerRing");
IScript* script = new PlayerScript(entities[0], map_characters[0], window);

 

 

In Helpers.h

#ifndef _HELPERS_H
#define _HELPERS_H

using namespace Leadwerks;

class Map_Entity;

enum ENTITY_TYPE : uint8 {
NPC = 0,
PC = 1
};

class Helpers {
public:
Helpers() {}
Map_Entity* InitModel(Entity* entity, ENTITY_TYPE entity_type, std::string name, Vec3 position, float scale, float mass,
const int collision_type,
const int physics_mode, std::string boneWeapon, std::string boneShield);

};
#endif

 

In Helpers.cpp

#include "../entities/entity.h"
#include "helpers.h"
using namespace Leadwerks;



Map_Entity* Helpers::InitModel(Entity* entity, ENTITY_TYPE entity_type, std::string name, Vec3 position,
float scale, float mass, const int collision_type, const int physics_mode, std::stringboneWeapon, std::string boneShield) {

Map_Entity* new_entity = new Map_Entity();

if (new_entity != NULL) {
delete new_entity;
}

if (entity != NULL) {
entity->SetScale(scale);
entity->SetMass(mass);
entity->SetPosition(position);
entity->SetGravityMode(true);
entity->SetCollisionType(collision_type);
entity->SetPhysicsMode(physics_mode);
entity->Hide();

new_entity->boneWeapon = boneWeapon;
new_entity->boneShield = boneShield;
new_entity->aniSpeed = 35;
new_entity->entity_type = entity_type;
new_entity->name = name;
new_entity->set_mass_delay = 0;
new_entity->anim_length[0] = entity->GetAnimationLength("Run");
new_entity->anim_starttime = 0;
 for (int aa = 0; aa < sizeof(new_entity->anim_speeds) / sizeof(*new_entity->anim_speeds); aa++) {
new_entity->anim_speeds[aa] = 30;
}
new_entity->speed = 5;

return new_entity;
}


return NULL;
};

Link to comment
Share on other sites

why do you delete new_entity if != null? you created a new one in memory then you delete the memory.

Damn that was meant for an Entity class, which was part of my Map_Entity class previously but removed because accessing members or methods within the script caused a crash and forgot to get rid of that delete.

 

Thanks again for pointing out my silly mistake! laugh.png

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