Scripting Context

    This site uses cookies. By continuing to browse this site, you are agreeing to our Cookie Policy.

    • Scripting Context

      Hey Guys,

      I have been really enhancing my game engine, and the one thing I have been working on the most, is my script component.

      After getting advice from Rez, and also using the Unity Engine I understand more how actor behaviours are defined. I have a question regarding the context of a script though, and as I am now switched over to LuaPlus it may be easier for you to answer Rez.

      When I parse a script file, say Object.lua, if that script file has a function called say, OnUpdate, the OnUpdate function will be in the global namespace in Lua. Now, if I have 200 actors all with there own OnUpdate functions, will they not get overwritten? If this is the case, what is the best solution for me to have these functions be local to the script file, and how would they be individually accessed?

      At the moment I have a script component, which has the option to parse a file before anything else, and then sets an Init, ShutDown, and Update function which are all called accordingly. Also, if I am able to make these functions local to the script file, I imagine I can make a local 'this' variable which would reference to the specific actor, which kind of answers my second question.
      PC - Custom Built
      CPU: 3rd Gen. Intel i7 3770 3.4Ghz
      GPU: ATI Radeon HD 7959 3GB
      RAM: 16GB

      Laptop - Alienware M17x
      CPU: 3rd Gen. Intel i7 - Ivy Bridge
      GPU: NVIDIA GeForce GTX 660M - 2GB GDDR5
      RAM: 8GB Dual Channel DDR3 @ 1600mhz
    • Correction, I meant to say what if I have 200 script files all being parsed, and I don't want to have a whole bunch of differently named 'Update' functions, but instead an OnUpdate function in each lua file meant for a script component, is there any way to have these be local only to the script file and even if it can be, how would they be accessed?

      I may be going about this wrong, should I create a table in Lua that will be a meta table for each actor that opens that file? I may be wrong, but I think in Unity, when you create a script file called 'Game' if you want to access that files variables/functions you just simply use the Game variable. Should I make a table based on the file name and store the functions within it?
      PC - Custom Built
      CPU: 3rd Gen. Intel i7 3770 3.4Ghz
      GPU: ATI Radeon HD 7959 3GB
      RAM: 16GB

      Laptop - Alienware M17x
      CPU: 3rd Gen. Intel i7 - Ivy Bridge
      GPU: NVIDIA GeForce GTX 660M - 2GB GDDR5
      RAM: 8GB Dual Channel DDR3 @ 1600mhz
    • Hey mholley,

      It seems to me like Unity treats things as a template, similar to Prefab vs GameObject. So your script will only exist once, and it will be your class. Each ScriptComponent that references it will be a new instance of your class. At the script level, any data you set is template data that is the default for the class. At your Actor level, you override that default data with any specific data for that instance.

      Hope that helps,
      James
    • RE: Scripting Context

      Originally posted by mholley519
      When I parse a script file, say Object.lua, if that script file has a function called say, OnUpdate, the OnUpdate function will be in the global namespace in Lua. Now, if I have 200 actors all with there own OnUpdate functions, will they not get overwritten? If this is the case, what is the best solution for me to have these functions be local to the script file, and how would they be individually accessed?

      Yes, if each of your 200 files all define a global OnUpdate() function, the function will get overwritten in the global table. I'm not sure how exactly you're attaching these files to actors, but the idea is to have a single component for each actor, which is stored in a table somewhere. From the script's point of view, the Lua script component IS the actor.

      Take a look at ActorManager.lua. The _enemies table is set up in this manner. You can have as many enemies as you want; each one will be added to the _enemies table, keyed off the actor id (which is guaranteed to be unique).

      Now take a look at the ActorManager:AddEnemy() function and see what it's doing. The scriptObject parameter is the Lua table which has the C++ ScriptComponent object bound to it. The function validates it, then starts adding a bunch of data to it. It's just a table, so you can do whatever you want.

      In your case, it sounds like you want a special OnUpdate() function per actor type. This is simple enough, you can have your version of AddEnemy() look at a type variable on the scriptObject (which is set when it's loaded from the XML) and have that determine which Lua class to instantiate. Since everything is a first-class object, you can just create a simple lookup-table.

      For example:

      Brainfuck Source Code

      1. -- NOTE: This might be syntactically wonky since I'm just pulling this out of
      2. -- my ass while on lunch.
      3. ------------------------------------------------------------------------------------------------------------------------------------------------
      4. -- Create a base script actor
      5. ------------------------------------------------------------------------------------------------------------------------------------------------
      6. ScriptActor = class(nil, {});
      7. function ScriptActor:Init(scriptObject)
      8. -- I forget the exact syntax, but you're basically setting the script object as the index for this class's
      9. -- metatable. This has the effect of inheriting from scriptObject, so you can call into the ScriptActor
      10. -- as if it were a C++ ScriptComponent.
      11. setmetatable(self, self);
      12. self.__index = scriptObject;
      13. return true; -- success
      14. end
      15. ------------------------------------------------------------------------------------------------------------------------------------------------
      16. -- Now define a few script object classes.
      17. ------------------------------------------------------------------------------------------------------------------------------------------------
      18. Goblin = class(ScriptActor, {});
      19. function Goblin:OnUpdate(deltaMs)
      20. -- do goblin stuff here....
      21. end
      22. ------
      23. Orc = class(ScriptActor, {});
      24. function Orc:OnUpdate(deltaMs)
      25. -- do orc stuff here
      26. end
      27. ------------------------------------------------------------------------------------------------------------------------------------------------
      28. -- This is the lookup table; just assign them the appropriate classes.
      29. ------------------------------------------------------------------------------------------------------------------------------------------------
      30. g_actorTypeToScriptClassTable =
      31. {
      32. "Goblin" = Goblin,
      33. "Orc" = Orc,
      34. };
      35. ------------------------------------------------------------------------------------------------------------------------------------------------
      36. -- I'm assuming you have an ActorManager class similar to mine from GCC, so here's the new version
      37. -- of the AddEnemy() function.
      38. ------------------------------------------------------------------------------------------------------------------------------------------------
      39. function ActorManager:AddEnemy(scriptObject)
      40. local actorId = scriptObject:GetActorId();
      41. if (self._enemies[actorId] ~= nil) then
      42. print("Fail sauce " .. actorId);
      43. end
      44. -- create the actor class
      45. local scriptActor = g_actorTypeToScriptClassTable[scriptObject.actorType]:Create()
      46. if (not scriptActor:Init(scriptObject))
      47. print("Actor failed Init() " .. actorId);
      48. return;
      49. end
      50. -- add it to our enemy table
      51. self._enemies[actorId] = scriptActor;
      52. end
      Display All


      Something like this should work. All you have to do now is create a script process that loops through all actors and calls their OnUpdate() function. Polymorphism takes care of the rest.

      -Rez
    • Basically all my 'attached' scripts were just the script file being parsed, not really attached at all.

      A couple questions
      - I was under the impression that the 'var' attribute in the actor xml file was for a specific instance of an actor, would this 'var' actually be a "Goblin" or an "Orc", and this is how the specific Lua Class is found to be used as a meta table?

      - What situations would I need to actually have an identifier for a specific Actor, is this needed? Originally I thought that the 'var' attribute should be something like 'Enemy_Goblin_23'. For instance would I really absolutely need to have an identifier like, 'Enemy_Goblin_23' to access this specific actor, or is most of the interactions based on collisions, triggers, etc. between pointers. I understand there are specific times where I may want to hold on to the pointer of an object in Lua by creating a variable called Player which holds onto the player actor. But do they really need a name?

      -When should I parse my files? I have been parsing them as the object's are created via an xml tag called file, should I really have almost a C++ like structure where all my classes and definitions are included through a main 'init.lua' or something of this kind?

      - Say I have an Orc actor archetype, but there is one specific Orc that needs to be carrying a bomb or something (Cue the two towers battle scene), in my level data would I load the actor from the xml file, and then additionally add on a script component that added a bomb, or would I make an entirely new BombOrc actor? I thought about this and it seemed like if I had a generic Bomb script component it would be able to be attached to Orcs, Trolls, Dwarves, etc.
      PC - Custom Built
      CPU: 3rd Gen. Intel i7 3770 3.4Ghz
      GPU: ATI Radeon HD 7959 3GB
      RAM: 16GB

      Laptop - Alienware M17x
      CPU: 3rd Gen. Intel i7 - Ivy Bridge
      GPU: NVIDIA GeForce GTX 660M - 2GB GDDR5
      RAM: 8GB Dual Channel DDR3 @ 1600mhz
    • I think going over some of the source code I am kind of understanding what is going on, let me go through the process in my head and maybe you can fill in or correct me where I am wrong.

      Source Code

      1. - Load Level File
      2. - Level File Loads Actor File 'Orc.xml'
      3. - Archetype specifies 'Orc' as 'var' type in script component
      4. - OnUpdate and OnInit functions refer to functions on the 'Orc' class, instead of having to specify Orc:OnUpdate
      5. - Level File Loads Actor File 'Orc.xml' a second time
      6. - Archetype specifies 'Orc' as 'var' type in script component
      7. - OnUpdate and OnInit functions refer to functions on the 'Orc' class
      8. - Level File specifies 'Bomb' as type in script component for specific instance
      9. - OnUpdate and OnInit functions refer to functions on the 'Bomb' class


      Is this the basic idea, also is the var meant for an instance name for when you need to have a handle to the Lua actor, or is it the type of actor?
      PC - Custom Built
      CPU: 3rd Gen. Intel i7 3770 3.4Ghz
      GPU: ATI Radeon HD 7959 3GB
      RAM: 16GB

      Laptop - Alienware M17x
      CPU: 3rd Gen. Intel i7 - Ivy Bridge
      GPU: NVIDIA GeForce GTX 660M - 2GB GDDR5
      RAM: 8GB Dual Channel DDR3 @ 1600mhz
    • Originally posted by mholley519
      - I was under the impression that the 'var' attribute in the actor xml file was for a specific instance of an actor, would this 'var' actually be a "Goblin" or an "Orc", and this is how the specific Lua Class is found to be used as a meta table?

      The var attribute is meant to simplify singleton objects. For example, the player could be implemented by using var. All it does is assigns any ScriptComponent instantiated from that data to the object. If you instantiated two actors that referenced the same var, the second would overwrite the first. As a more tangible example, you might have an actor for the voice-over (which we did in Barbie) and this could be added to a global variable.

      The var attribute should be used sparingly or not at all, since it doesn't allow for any side effects. You can't call a function or add anything; it literally just does the assignment.


      - What situations would I need to actually have an identifier for a specific Actor, is this needed? Originally I thought that the 'var' attribute should be something like 'Enemy_Goblin_23'. For instance would I really absolutely need to have an identifier like, 'Enemy_Goblin_23' to access this specific actor, or is most of the interactions based on collisions, triggers, etc. between pointers. I understand there are specific times where I may want to hold on to the pointer of an object in Lua by creating a variable called Player which holds onto the player actor. But do they really need a name?

      You always have a unique ID for an actor. That's what the actor ID is for, it allows you to uniquely identify actors in your game. You didn't hit every orc, you hit orc 0x43a59213. In terms of naming every object some human-readable value, that really depends on the game. Every Sim on The Sims has a unique name and we can find them through that name (though we never really do; it's all done by ID and weak ref). A more tangible example is in an adventure game where there's a script that needs to give stage directions to an actor. The player just found the black lotus so Mr Jenkins and Colonel Todd have to go to the foyer to murder Mrs. Peabody. We also need a cutscene with Joe the Carpenter to start now. These are made much easier if you have named characters, although you can get the same effect by having a table that maps their name to their actor ID. On RatRace, every single actor had its own table in the global namespace. To teleport Delia somewhere, we'd do this:

      Source Code

      1. Delia.SeClump:SetPos(50, 0, 100);


      Every minigame and cutscene started with a SetupPositions() function that go everyone in position.


      -When should I parse my files? I have been parsing them as the object's are created via an xml tag called file, should I really have almost a C++ like structure where all my classes and definitions are included through a main 'init.lua' or something of this kind?

      That's probably fine. Parsing & executing the file is obviously slow so you want to do it at the right time. Again, it really depends on the game. Rat Race was very level-oriented where each level represented an episode (think Sam & Max). We loaded all Lua files on level init. We load all Python files at initialization time on The Sims as well. I've worked on games where everything was ad-hoc, though.


      - Say I have an Orc actor archetype, but there is one specific Orc that needs to be carrying a bomb or something (Cue the two towers battle scene), in my level data would I load the actor from the xml file, and then additionally add on a script component that added a bomb, or would I make an entirely new BombOrc actor? I thought about this and it seemed like if I had a generic Bomb script component it would be able to be attached to Orcs, Trolls, Dwarves, etc.

      If it has different behavior, it should be a different actor type. You won't just have Orc, you'll have OrcBomber, OrcArcher, OrcGrunt, OrcCommando, etc. If this is common where you want to share behavior, you can always create simple inheritance hierarchies or even create a monster component system so you can mix & match types.

      -Rez
    • Hey Rez,

      I have been making alot of progress on reading and coding, I am also happy as I have gotten Decoda finally working as well as LuaPlus.

      I have created a really basic Actor Manager, but while trying to pass the script object through the AddActor function in lua, for some reason the variable wont make it through, I tried a simple integer as well and it isn't being called, I know it is calling the function as decoda hits a breakpoint inside of it. Here is the C++ calling code as well as my Lua script

      C++:

      Source Code

      1. LuaObject scriptActorManager = m_pLuaState->GetGlobals().Lookup("g_ActorManager");
      2. if(!scriptActorManager.IsNil())
      3. {
      4. LuaObject addActorObj = scriptActorManager.Get("AddActor");
      5. if(!addActorObj.IsNil())
      6. {
      7. if(addActorObj.IsFunction())
      8. {
      9. LuaFunction<void> addActor(addActorObj);
      10. addActor(5);
      11. }
      12. }
      13. }
      Display All


      Script:

      Source Code

      1. dofile("Assets\\Scripts\\class.lua");
      2. ActorManager = class(nil,
      3. {
      4. });
      5. g_actorTypeToScriptType = {
      6. TestObject = "TestObject"
      7. }
      8. function ActorManager:AddActor(number)
      9. if(number ~= nil) then
      10. print(number);
      11. end
      12. end
      13. g_ActorManager = ActorManager:Create();
      Display All


      For some reason nothing will make it through the function and I imagine I am calling something improperly.

      Also, could you try and explain what require does? I can't get it to find my files at all, I read a reference on it but it was kind of confusing, I like the idea of only having files added once, I imagine I could do a C style block though with maybe

      Source Code

      1. if(_ACTORMANAGER_LUA_ == nil)
      2. {
      3. _ACTORMANAGER_LUA = 0;
      4. }
      PC - Custom Built
      CPU: 3rd Gen. Intel i7 3770 3.4Ghz
      GPU: ATI Radeon HD 7959 3GB
      RAM: 16GB

      Laptop - Alienware M17x
      CPU: 3rd Gen. Intel i7 - Ivy Bridge
      GPU: NVIDIA GeForce GTX 660M - 2GB GDDR5
      RAM: 8GB Dual Channel DDR3 @ 1600mhz
    • Every member function in Lua has a first parameter that is the "self" variable. Using the colon operator just hides this variable. For example:

      Source Code

      1. -- these two function calls are exactly the same
      2. g_actorManager:AddActor(actor); -- with colon
      3. g_actorManager.AddActor(g_actorManager, actor); -- without colon
      4. -- this is also the same
      5. f = g_actorManager.AddActor; -- assign it to another variable
      6. f(g_actorManager, actor); -- calling it requires the explicit self pointer


      As you can see, colon is just syntax sugar to make it a bit less tedious to use member functions. When you assign the value of a member function to another function, you also have to add the self parameter.

      You're doing the same thing. You're attempting to call this member function without passing in the self parameter. In fact, you're passing in 5 as the self parameter. This should fix your problem:

      Source Code

      1. LuaFunction<void> addActor(addActorObj);
      2. addActor(scriptActorManager, 5); // [rez] note the addition of the self parameter


      The require() function works like dofile() except that it doesn't allow a script to execute twice, so you safely call require() on multiple objects. You should be able to replace your dofile() with a require() and have it work, though I vaguely remember something about the paths being different. dofile() might work off the current directory where require() has its own path. I honestly don't remember since I haven't had to deal with it for several years. I rewrote require() in my own engine to use my resource system.

      -Rez
    • That seemed to do the trick, I was sure I already tried that but as soon as I checked it out this morning it worked.
      PC - Custom Built
      CPU: 3rd Gen. Intel i7 3770 3.4Ghz
      GPU: ATI Radeon HD 7959 3GB
      RAM: 16GB

      Laptop - Alienware M17x
      CPU: 3rd Gen. Intel i7 - Ivy Bridge
      GPU: NVIDIA GeForce GTX 660M - 2GB GDDR5
      RAM: 8GB Dual Channel DDR3 @ 1600mhz