Game Development Community

'Activate' World Objects

by Nicolai Dutka · in General Discussion · 04/07/2009 (9:58 am) · 17 replies

EDIT: This entire post was re-written on 01/15/2010 to make it more like a guide.

I've got this all working and thought I might share. Basically, this code uses a ray cast a short distance directly in front of the player and attempts to 'activate' objects the ray finds. This can be used to run ANY code on ANY object (such as talking to an NPC in an RPG game, opening doors, pulling levers, and more!) This code is the heart and soul of most of the interactive elements in my current project and I have no doubt I will use it again in many future projects.

(PS: This code has been tested and found working on TGE 1.5.2, TGEA 1.8.1, and T3D 1.0)

So here's a basic overview of what we need:

Activate.cs - The main script file that holds all of our activation code
game.cs (or scriptExec.cs for T3D) - We just need somewhere to execute our activate.cs file
default.bind.cs - The most common place to put your key bindings
Static Shape - You can use StaticShape, Item, or with some very small changes, any given $TypeMask you want (explained below)
Quote:
Important Note: I have a global variable for my main player called $player. I set this up in my player's 'spawn code'.

TGEA:
scriptsAndAssets/server/scripts/game.cs
T3D:
scripts/server/gameCore.cs AND core/scripts/server/spawn.cs

Look for where the %player is defined: %player =

Add below: $player = %player; //Now we can call on our player any time with any code with incredible ease -ND


Setup:

1. Create a new file called 'activate.cs'. Place it in your main scripts folder (next to game.cs or scriptExec.cs).

For TGEA: scriptsAndAssets/server/scripts/game.cs
For T3D: scripts/server/scriptExec.cs

2. Put the following code in your activate.cs file:

////////////////////////////////////////////////////////////
// Activate
////////////////////////////////////////////////////////////

function activate()
{
  //'Activate' can be used to activate AND deactivate objects that have already been activated.
  //First let's try to deactivate anything we might already have activated:
  //All of these 'deactivation' checks have a 'return;' on them to make sure the code stops until next time

    if($player.isCarrying){
      toggleCarryObject();
      return;
    }

    if($player.telekinesis)
    {
      toggleTelekinesis();
      return;
    }

    if($player.isBuilding)
    {
      toggleBuilding();
      return;
    }

    if($player.usingPC)
    {
      togglePC();
      return;
    }
    if($player.usingTeleporter)
    {
      toggleTeleporter();
      return;
    }

  //Now it's time to cast the ray in front of the player and search for 'activatable' objects

    // Check for 'activatable' objects in front of the player
    %distance = 1.64;    // Distance in front of eyePoint. Tweak this for 'how far to look in front of the player for activatable objects'
    %start = $player.getEyePoint();
    %vector = $player.getEyeVector();
    %vectorN = VectorNormalize(%vector);
    %vectorS = VectorScale(%vectorN,%distance);
    %end = VectorAdd(%vectorS,%start);

    %ray = ContainerRayCast(%start,%end,$TypeMasks::StaticShapeObjectType | $TypeMasks::ItemObjectType);

    if(%ray)
    {
       //ray collided with a staticshape. check to see if it is 'activatable'

       //A couple echoes to help with debugging
       echo("Ray: "@ %ray);
       %ray = getWord(%ray, 0);
       echo("Ray: "@ %ray);

       if(%ray.isCarryObject){
         toggleCarryObject(%ray);
       }

       //  Is the %ray a door?
       if(%ray.isDoor)
       {
           //Our ray found a door, run code to check for locks, keys, etc, then open door as needed, message the player, etc.
       }

       // Is the %ray a Lever?
       if(%ray.Lever)
       {
          //Our ray found a lever.  Run code to activate the lever
       }
  
       // Is the %ray a Buildable Object?
       if(%ray.isBuildable)
       {
         //Our ray found a 'buildable' object.  Run code to let the player build it, etc.
       }
  
       // Is the %ray a PC?
       if(%ray.isPC && !$player.usingPC)
       {
         togglePC();
       }

        // Is the %ray a Teleporter?
       else if(%ray.isTeleport && !$player.usingTeleporter)
       {
         toggleTeleporter();
       }
  
    }  //End if(%ray)
}

You will have to write your own code for each of the activatable objects. Sorry, but I just can't give everything away! :P

2. Ok so the next step is to make sure our file is executed. In game.cs or (scriptExec.cs for T3D), add the following line:
exec("./activate.cs");

And now your game has the 'activate' function.

3. Open your default.bind.cs:
TGE and TGEA: scriptsAndAssets/client/scripts/default.bind.cs
T3D: scripts/client/default.bind.cs

Somewhere near the top (or near your other key bindings), add the following:
moveMap.bind(keyboard,e,startActivate);             //Keyboard button E
moveMap.bind( joystick0, button4, startActivate);   //Playstation 2 style controllers, button X
moveMap.bind( gamepad, btn_x, startActivate);       //Xbox Controllers, button X
(You only need the ones you plan to use.)

And then anywhere in that same file (preferably under 'functions'), also add:
function startActivate(){
  if($startActivate){
    $startActivate=0;
    return;
  }
  else{
    $startActivate=1;
    activate();
  }
}

Which makes sure the code is only run once per button press. We could skip this part and just use 'bindCmd' above instead of just 'bind', but then we run into problems when remapping the buttons through the options menu within the game, and it also wouldn't be compatible with game controllers who, by nature, run functions every time the button is pressed, and again when released...

And voila!! You now have an 'activate' function that can be run any time you press the 'e' button on keyboard or 'x' on a controller!

One thing to make sure you do though!!

This code currently only looks for Static Shapes and Items. By changing or adding to the $TypeMasks, you can control what type of objects are checked for. In the activate.cs code, look for:
%ray = ContainerRayCast(%start,%end,$TypeMasks::StaticShapeObjectType | $TypeMasks::ItemObjectType);

IMPORTANT:
When the ray is actually cast, you NEED to have a dynamic field on each activatable object! In the code above, our ray will only tell us it found a door if the static shape we used for the door has the dynamic field:

isDoor = 1;

Then the ray will hit the door, see that %ray.isDoor, and thus run the 'door code'.

Any questions?

#1
04/07/2009 (11:59 am)
I actually figured it out!

Just added 2 lines:
if(%ray)
  {
     //ray collided with a staticshape. check to see if it is 'activatable'
     echo("Ray: "@ %ray);
     %ray = getWord(%ray, 0);  //This is the object my ray found
     if(%ray.pushBlock)
     {
        $PushObject = %ray;  //This makes the rest of my code work...
#3
04/07/2009 (12:21 pm)
Oh, the $PushObject is simply a StaticShape with an added field:

datablock staticShapeData( PushBlock )
{
   category = "StaticShape";

   shapeFile = "~/data/shapes/ALPHA/Cube_1m.dts";
   emap = true;
   allowPlayerStep = 1;
};

function makePushBlock(%xform)
{
   %newBlock = new staticShape() {
      canSaveDynamicFields = "1";
      Enabled = "1";
      dataBlock = "PushBlock";
      PushBlock = "1";     <== This makes the activate function recognize this as a pushblock
      scale = "2 2 2";    <== This makes the 1 meter block a much nicer size for a proper 'PushBlock' ala Tomb Raider style
   };
   %t = %xform;
   if(%t==0) %t = $player.getTransform();
   %newBlock.setTransform(%t);
   return %newBlock;
}
#4
01/13/2010 (4:16 pm)
What exactly does this code do? Make something happen? or does this just cast a ray of light when you look at something? Can this be used to make a sound play when a key is pressed while looking at an object?
#5
01/14/2010 (12:25 pm)
@Josiah,

That's some code I wrote quite a while back and it has been heavily modified since, but it does still work as is. The code basically casts an "imaginarey" line from the player's eye's to about 1.65 meters (%distance) in front of the player and if it collides with one of the defined $TypeMasks, then it can be coded to do something (anything)... Yes, like playing a sound when looking at a specific object.

I use it so the player walks around pressing the 'e' button and if the invisible ray hits things that we have programmed to be 'interactive', it will check that object for what to do and run the proper code. If the ray hits a door, it can open/close that door, check for keys, etc. If the ray hits a lever, it will activate the lever running whatever function that lever has assigned to it. (Open a door, run a specific function, etc).

The code is very versatile and can be used for a wide variety of things. To summarize, it's an invisible ray that looks for stuff right in front of the player and "activates" them.

(Note the line that says "if(%ray)". This means the ray hit an object that matches our $TypeMask search)
#6
01/14/2010 (9:32 pm)
Will it work in TGEA 1.8.1? And where do I place the code? the player.cs file? and how do I make it activate by a button like 'e' as you said? Sorry about all the questions.
#7
01/14/2010 (11:40 pm)
Yes, absolutely. :)
#8
01/15/2010 (1:05 am)
sorry but you didn't say where to put it or how to make a button activate the ray.
#9
01/15/2010 (12:03 pm)
Well, that's the easy part. :)

Put the code anywhere you want. I made a new file called 'activate.cs' and have it executed through game.cs using:

exec("./activate.cs");

Then in client/scripts/default.bind.cs, add:

moveMap.bind( keyboard, e, startActivate);

And lastly, a new function:
function startActivate()
{
  if($startActivate){
    $startActivate=0;
    return;
  }
  else{
    $startActivate=1;
    activate();
  }
}

This allows you to use the 'moveMap.bind' instead of 'moveMap.bindCmd'. The difference is, .bind works better with game controllers and with remapping the command to other buttons. If you don't care about remapping the command or using a game controller, you can leave out the 'startActivate' function and then use:

moveMap.bindCmd(keyboard,e,"","activate();");

Both methods ensures that the function is only run once per button press rather then once when pressed and again when released. :)
#10
01/15/2010 (12:40 pm)
I've completely re-written the entire first post to be more like a guide. I hope it helps as I did spend at least 20 minutes on it... If everyone likes this, I might write it up as a resource... The cool thing is, it works with TGE, TGEA, and T3D with NO changes between the 3! :D
#11
01/20/2010 (1:57 pm)
well thank you very much!
#12
01/20/2010 (5:56 pm)
I was wondering where I put the dynamic field "isDoor = 1;" code. can I put it in while in the world inspector? If so, then how? Cause I tried but it won't activate what I am try to.
#13
01/20/2010 (6:01 pm)
Through world editor, console, or by editing the object in your *.mis file.

Editor:

1. Click on the object
2. Scroll to bottom of the object's properties to "Dynamic Fields"
3. Add new Dynamic Field. Call it whatever you want to check for: isDoor (press Enter)
4. Set the value to 1 (press Enter)

Console:

1. %obj.isDoor = 1;
2. Make sure to replace %obj with the name or ID number of the object
#14
01/20/2010 (6:03 pm)
Oooh.. to test it out, maybe add an 'echo' statement to your 'activate.cs' file:

//  Is the %ray a door?
       if(%ray.isDoor)
       {
           //Our ray found a door, run code to check for locks, keys, etc, then open door as needed, message the player, etc.
            echo("Attempting to active a door!");
       }
#15
01/21/2010 (8:07 pm)
Wow, thanks so much. You are a great help. Also, thanks for the fast replies.
#16
01/25/2010 (9:25 pm)
ok I have implemented all of the code except for the $player = %player. It will not let me. When I do and try to start the game it wont load datablocks. So I need to know the exact position or something or maybe I don't have to. The console is giving me an error
scriptsAndAssets/server/scripts/activate.cs (45): Unable to find object: '' attempting to call function 'getEyeVector'
Do you know why this may be? This is what I tried as the code
// Is the %ray a Door?
       if(%ray.Door)
       {
          serverPlay3D(HealthUseSound,%obj.getTransform());
       }
I have a sound effect thing for the sound but it still doesn't work. Maybe I cannot call this sound this way or something. Can you help me?
#17
01/27/2010 (5:14 pm)
bump