GameMonkey Script

GameMonkey Script Forums
It is currently Wed Aug 15, 2018 1:39 am

All times are UTC




Post new topic Reply to topic  [ 6 posts ] 
Author Message
PostPosted: Fri Jun 04, 2010 1:58 pm 
Offline

Joined: Fri Jun 04, 2010 11:45 am
Posts: 8
Hi,

though I am programming games and applications as a hobby for years now, I never worked with scripts. But now that school is finished and I have time to explore new techniques, I started to learn scripting and I am writing on a litte project to use the learned.

First off I looked around in the script world, I knew python was a powerful language because of the addons for blender3d that were written for it. But python requires a lot of installing and is too much VC-style (too much / too heavy). I like to settle for light things like codeblocks. So I kept looking and found lua. Lua seemed to be apropriate: Light weight, high functionality and a good amount of tutorials. So I got around, compiled it and did the tutorials. After I understood all the table things and stumbled upon luna, I thought I was ready for a field test.

So I created a setup, a simple Myst III style point and click enviroment with some rendered 3d scenery. The c++ programm was the engine and everything else should be managed by the script. With luna I created class bindings and made it possible to create ingame clickable links, interactive images and a little riddle.

But I also had a problem, the objects where created by the script, but when changing the position of the player I needed the objects to be gone (on the one hand because buttons in mid air would look stupid, on the other hand because of memory usage). I then realized that loading scripts into the luaVM was permanent, it is not possible to manage scripts so they stay to themselves. For example, if you declare one variable in one script you could use the variable in another when loading it. Of course it was said that you could simply set var=nil and the garbage collector would find it and delete the value, thus removing the object. Did that, did not work, the objects were still there, and where deleted some time later (which is bad, because until than the buttons were stuck on the screen).
This is what I did (in lua):
Code:
--Setup the inteface, two buttons that have to be clicked,
--one note if the key is missing, and one button to get back
local button1 = Image{
            name="button_1",      --id
            path="Button_in.png",   --file
            x=5,               --position
            y=0,
            z=10,
            hosted = true         --mounted to the camera?
            }

local button2 = Image{
            name="button_2",
            path="Button_in.png",
            x=-5,
            y=0,
            z=10,
            hosted = true
            }

local button3 = Image{
            name="back",
            path="Back.png",
            x=8,
            y=-3,
            z=10,
            hosted = true
            }

local button4 = Image{
            name="keynote",
            path="key_missing.png",
            x=0,
            y=6,
            z=10,
            hosted = true
            }

--request variable from the engine (like php session variables e.g. $_SESSION['key_taken'])
key_taken = QueryValue("key_taken")


--if the key was taken, set the note invisible
if key_taken==true then
   button4:SetVisible(false)
end

--additional values for the button tables
button1.on = false
button2.on = false


--Callback from program
--This does basically the following:
--When one of the buttons is clicked, it checks whether the key was taken or not
--In case there is no key, clicking the two main buttons does not do anything
--If the key was taken, clicking the buttons works like checkboxes, when both buttons are
--checked, the door opens (SetValue) and a sound is played
function OnImageClick(name)
   if key_taken==true then
      if name == "button_1" then
         if button1.on == false then
            button1:SetTexture("Button_a.png")
            button1.on = true;
         else
            button1:SetTexture("Button_in.png")      
            button1.on = false;
         end
      end
      
      if name == "button_2" then
         if button2.on == false then
            button2:SetTexture("Button_a.png")
            button2.on = true;
         else
            button2:SetTexture("Button_in.png")      
            button2.on = false;
         end
      end

      if name=="button_1" or name=="button_2"  then
         if button1.on == true and button2.on == true then
            playSound("../Data/Sound/gate.ogg")   
            SetValue("door_open",true)
         else
            if QueryValue("door_open") == true then
               playSound("../Data/Sound/gate.ogg")
               SetValue("door_open",false)
            end
         end
      end
   end
   
   --"back"-button was clicked
   if name == "back" then
      --I would have to delete the buttons here:
      button1 = nil
      button2 = nil
      button3 = nil
      button4 = nil
      --change the scene
      startTransition("../Data/MainRoomAirlock/")   
   end
end

When a scene is loaded scripts like this are executed and handle the behaviour of the current scene. When I stated my problem in the lua irc I was bombarded with statements like "That's bad / You are doing it wrong" but I did not get accurate answers about what to do to fix this. The only way to solve the garbage problem I found, was closing the engine and starting it up again, which was even worse in the eyes of the lua pros.
So I searched for something to solve the problem. I did not find anything, lua is very well documented itself, but when it comes to interfacing with other programs the number of tutorials suddenly dissolve. While searching alot with the keyword 'lua' in it, I found GameMonkey which mentions lua on the frontpage ;)
Studying GM I found a method resetMachine (or something like that) which I think does what I want. Also it is possible to multithread scripts, which would allow me to use local scopes in running loops without stopping the main programm (I think).
Basically I want scripts that keep to themselves. I want to make objects that can be scripted, without using a virtual machine for each object and I want to be able to control when an object is deleted.
I think scripts should be little programs that you can use and call. Lua loads every script into the machine and makes them one big script. I don't want one big script.

So much to say, I am new to scripting, the only script I ever used for a real project was javascript for a webpage, so maybe I understand it all wrong, for I have never done it before.

I read the documentation of GM, but I could not answer this myself, does GM provide the things I need?

greetings


Last edited by FTC on Mon Jun 07, 2010 10:37 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
PostPosted: Fri Jun 04, 2010 10:31 pm 
Offline

Joined: Fri Jan 14, 2005 2:28 am
Posts: 439
Unfortunately most garbage collected languages don't give you explicit control over the lifetime of the objects. In GM, if you know you have nulled out all references to an object, you could perform a full garbage collection, perhaps your startTransition function or some native side function that gets called as part of every transition can perform a full garbage collection. CollectGarbage( true ) should delete any objects that have no references, but it isn't foolproof, as you rely on each script to release all their references. It'd probably make things much easier if you utilized GM threads and essentially your screen could be wrapped in a thread, and since everything would be local variables to that thread function, when the thread finishes, things would have their references released automatically pretty much.


Top
 Profile  
Reply with quote  
PostPosted: Sat Jun 05, 2010 2:33 am 
Offline

Joined: Mon Dec 15, 2003 1:38 pm
Posts: 703
Welcome to the forum FTC :) Even in full featured managed languages like Java and C#, if you need explicit control, use explicit controls. Resource handle objects for things like network and disk related objects should be explicitly release to efficiently control those limited resources. When such a resource is cleaned up, handles to them may still be present, but will fail (hopefully gracefully) or can be queried for their status.

Just a few other comments... In GM, individual scripts can access data between themselves via the global table. The host application can also own resources and pass them between otherwise independent functions. People often choose GM over the now similar Lua because of the simple native C - > script interfaces. If the garbage collector is configured reasonably, and script is being used reasonably (more for control instead of intense computation), you should be able to get value from it and not care about resources beyond those you need explicit control. Also, it is normal practice for an application to have a single gmMachine instance.


Top
 Profile  
Reply with quote  
PostPosted: Sat Jun 05, 2010 5:03 pm 
Offline

Joined: Fri Jun 04, 2010 11:45 am
Posts: 8
Thanks for the replies. I think I will use threads for each scene and delete all old objects within the host programm in the startTransition function.
Another question :) I want to make npcs that are as much scripted as possible. So I would have a base class in the host program to query waypoints, create the npc's model etc. The AI should be done by the script maybe in an OnAIUpdate() method. But what would be the best way to do this? Since I cannot exactly tell the scripts apart within the gm machine how could I bind a script behaviour to an npc? I have seen something like this in the NeoAxis Engine. I know how I can bind a specific c++ class to use it in GM, but if I had a c++ base class how could I tell the different configurations apart? They would all use the same function names but every configuration's function code would be different.

I hope it is understandeble what I want to say
greetings


Top
 Profile  
Reply with quote  
PostPosted: Sat Jun 05, 2010 9:43 pm 
Offline

Joined: Fri Jan 14, 2005 2:28 am
Posts: 439
What I recommend it to bind your 'AI object' to script which has whatever native side supporting functionality an AI normally needs, such as locomotion, aiming control, etc. Expose script functions through the 'AI object' type to script, so that the script can tell the AI to do stuff. Take advantage of the threading as much as possible with this. Don't call an 'update' script function every frame, utilize the threads to write your scripted AI logic in a top down fashion.

Depending on how complex your AI will ultimately be, there are a couple things you can do. For my use, since the AI is pretty complex, I exposed a script object type that represented a state within a hierarchical FSM. A simpler place to start, until you get a grasp of the concept, is a 1 script control mechanism. It goes something like this.

  • You create your 'AI object' on the native side, however you end up deciding when and where to spawn AI entities.
  • Presumably there is a 'script' associated with a particular AI, based on the type of AI it is or whatever.
  • Execute the associated script, but you pass the 'AI object' into the script as the 'this' variable(the a_this parameter of the gmMachine::Execute*). This allows you to spawn an arbitrary number of AI entities that all run the same script. Since each instance references its own individual 'AI object' that is a reference to that particular AI instance, each AI is a separate entity from the others.
  • Your native side AI object should know what thread Id is associated with itself, so when the AI is deleted, or killed, or whatever, the native side can gmMachine::KillThread

Now ideally, this script you executed when the entity was spawned in would never finish, and would run constantly throughout the lifetime of the AI. This method isn't terribly scalable though if your AI gets very complex, but I recommend it as a place to start. An example AI script might look like this. Note that the function calls assume a somewhat fleshed out version of native side functionality that is exposed to script. I recommend doing all the low level AI logic you need on the native side. Locomotion, animation control, aiming, weapon fire control, etc. Then you can provide minimalistic interfaces as part of the interface to the exposed AI script object type.

Code:
// resource gather sort of dude from an RTS maybe
while( 1 ) // this AI logic lasts forever.
{
    if ( this.ResourceFull( "wood" ) )
    {
        // this is an asyncronous function that doesn't return until the activity is completed
        // is is assumed that entering the town hall cashes in the resources carried by the AI
        this.EnterNearestBuilding( "Town Hall" );
    }
    else
    {
      // resource gathering loop until the AI is full
      while( !this.ResourceFull( "wood" ) )
      {
         // if near enough to gather
         if ( this.NearResource( "wood" ) )
         {
            if ( this.IsPlayingAnimation( "Chop Wood" ) )
            {
               // game logic could be done in script, though I recommend not
               //this.AddResource( "wood", 10 );
               // instead have the native side detect collection units and reward resources to the unit
            }
            else
            {
               this.PlayAnimation( "Chop Wood" );
            }
         }
         else
         {
            error = this.GotoNearestResource( "wood" );
            // error could be a string or enumeration representing an error condition
            // "No Resource", "No Path To Resource", etc
            if ( error != null )
            {
               this.Say( "Yo Player!, I can't gather resources! : ", error );
            }
         }
         sleep( 1 );
      }
    }
}


It's a simplistic example, but I want to demonstrate how a script operates entirely off of the 'this'. In the same way c++ instances of an object are differentiated by 'this'.

To take it a step further, as part of your AI object binding, you can give the object setdot/getdot functionality that will allow the script to store arbitrary data inside a GM table on the object. This gives you a place to store state data if you need to.

The other thing to understand is just how easy your script can become if you provide asynchronous functions that block the script. Movement related functionality is often a good candidate for this. It's often very nice when your script can be executed line by line and a single line of script can represent an arbitrary passage of time to accomplish a fragment of specific behavior.

Code:
result = this.Goto( x,y,x );
if ( result = MoveSuccess )
{
    this.Say( "made it!" );
} else
{
    if ( result == MoveNoPath ) ...
}


It can also make a very flexible method of creating arbitrary objects within your game world, all driven by events and minimal functionality made available by your game engine.

Code:
// keyed treasure chest
this.Locked = true;
this.StrengthToBreakLock = 15;

while( 1 )
{
    // block and wait until someone uses this object
    // on the native side, when someone uses the object,
   // you would gmMachine::Signal the objects threadId with the string "OBJECT_USED", or "OBJECT_BASHED" or whatever
   // you would also set the user/basher on the object so the script could query it(this.User())
    result = block( "OBJECT_USED", "OBJECT_BASHED" );
    if ( result == "OBJECT_USED" )
   {
      // can it be unlocked?
      if ( this.Locked )
      {
         if ( this.User().HasItem( "skeleton key" ) )
         {
            this.Locked = false;
         }
      }
   }
   else if ( result == "OBJECT_BASHED" )
   {
      if ( this.Locked )
      {
         if ( this.User().GetStrength() > this.StrengthToBreakLock )
         {
            this.Locked = false;
         }
      }
   }
   
   if ( this.Locked == false )
   {
      // open the chest
      this.Open();
      
      // then maybe you want it to close automatically after several seconds
      sleep( 3 );
      this.Close();
   }
}


This is a simple example of the usefulness of the GM thread blocking functionality. To eliminate the need for your object to receive continuous updates, if it's something that is simple enough to be represented as a script, the block() functionality can be used to put it in a waiting state until it recieves the input that it is blocking on. In this case it's waiting on one of 2 inputs, representing the type of interaction you want it to respond to. This object can be unlocked with a key or bashed open by a strong character. When it is not in use, the object sits idle, and requires no updates or 'thinking'.

I use simple RPG or RTS type examples because they are typically very narrowly scoped and simple behaviors. This single script methodology doesn't scale very well for a complex AI who may have a bunch of different discreet behaviors. It does demonstrate the principle. For my complex AI, the main difference is that I have a script per behavior, but otherwise they work similar to how these examples. The AI has a behavior tree, FSM, whatever, and there may be a script associated with each individual behavior/state, etc. Ultimately though your higher level logic tree would be more responsible for choosing which behaviors to run.


Top
 Profile  
Reply with quote  
PostPosted: Sun Jun 06, 2010 9:27 am 
Offline

Joined: Fri Jun 04, 2010 11:45 am
Posts: 8
Ok I know now that GM is what I need :)
I now just need to learn how to use it.

Thanks for the help
greetings


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 6 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron
Powered by phpBB® Forum Software © phpBB Group