Difference between revisions of "A to Z Scripting: Practical exercise: subtle teleportation"

From The DarkMod Wiki
Jump to navigationJump to search
(Created page with "== Practical exercise: subtly teleporting the player == So far we've seen the basics of scripting, including the basic composition of a script, how variables are made and used...")
 
m
Line 1: Line 1:
== Practical exercise: subtly teleporting the player ==
So far we've seen the basics of scripting, including the basic composition of a script, how variables are made and used, how to "get" information about entities in the map, all the ways to call a script, how to use the [[TDM Script Reference]] as well as how to set up your .script file. That would already be enough to try an early scripting exercise:
So far we've seen the basics of scripting, including the basic composition of a script, how variables are made and used, how to "get" information about entities in the map, all the ways to call a script, how to use the [[TDM Script Reference]] as well as how to set up your .script file. That would already be enough to try an early scripting exercise:


Line 5: Line 4:




=== Brainstorming ===
== Brainstorming ==
Before you start writing any lines of script, it's good to plan this out, maybe even writing down notes somewhere. The more you think of now, the fewer roadblocks there might be down the line:
Before you start writing any lines of script, it's good to plan this out, maybe even writing down notes somewhere. The more you think of now, the fewer roadblocks there might be down the line:
* 1) What will be teleported? Just the player, or maybe also moveables and even AIs?
* 1) What will be teleported? Just the player, or maybe also moveables and even AIs?
Line 30: Line 29:




=== The mapping ===
== The mapping ==
Next you will want to do the mapping in DR, as you'll need the entity names later on for the script. After the brainstorming, this is a setup that could work:
Next you will want to do the mapping in DR, as you'll need the entity names later on for the script. After the brainstorming, this is a setup that could work:


[[File:Comp_script_silent_tele.png]]
[[File:practical_exercise_teleportation2.png]]
 


These are the key features of the mapping setup:
These are the key features of the mapping setup:
* two trigger_multi brushes at opposite ends of the rooms. They're located such that the player only activates one on his path through the setup.
** each brush targets a callscriptfunction entity to call the teleportation script. Callscriptfunction entities are more advanced than trigger brushes, since they can tell the script which trigger brush was activated (see [[A to Z Scripting: Ways of calling a script]] > "callscriptfunction").
** each brush has a custom spawnarg called "direction" either with the value "forward" or "backward". The script looks up this spawnarg on the brush that called the script.
** Checking which brush called the script allows us to reuse a single script, rather than writing one script that teleports forward and one almost identical script that teleports backward.
* the two callscriptfunction entities are shown in the void for easier visibility, you'd place them inside the rooms or somewhere else that's sealed.
** the "foreach" "1" spawnarg is what allows them to pass on to the script which brush triggered them.
* the table in the centre of each room acts as the reference point for calculating the teleportation offset. For easier reference in the script, they've been given special names (table_1, table_2).
* the table in the centre of each room acts as the reference point for calculating the teleportation offset. For easier reference in the script, they've been given special names (table_1, table_2).
* there are 2 multi-use trigger brushes, arranged in such a way that when the player crosses the room he only activates one of them. One has a "call" spawnarg to call the script "teleport_forward", the other calls "teleport_backward".
* all 4 corridors are blind and L-shaped. The player enters the setup via the bottom left corridor and leaves via the top right corridor, so you'd attach the rest of your map to these 2 corridors.
* all 4 corridors are blind and L-shaped. The player enters the setup via the bottom left corridor and leaves via the top right corridor, so you'd attach the rest of your map to these 2 corridors.




=== The scripting ===
== The scripting ==
Now that all the planning and setup are out of the way, the scripting itself can begin. Start with thinking about what important components are involved: there will be 2 scripts (teleport_forward, teleport_backward) and one variable for storing the teleportation offset. Another variable will be needed to tell the script where to teleport the player to.
Now that all the planning and setup are out of the way, the scripting itself can begin. Start with thinking about what important components are involved: one script, and one variable for storing the teleportation offset.


The variables should be available to both scripts, so they should be defined at the top (making them visible to all scripts below them) and outside of a script (so they're not specific to only one script).
void teleport_script(entity ent_target, entity ent_brush, entity ent_callscriptfunction) //receive up to 3 entities as input from the callscriptfunction entity. Only ent_brush is used in this script
vector teleport_offset; //the vector for moving between room 1 and room 2
vector teleport_destination; //the position the player should be teleported to
 
On to the first teleportation script. Much of the work lies in calculating the variables (the offset and then the destination).
void teleport_forward()
  {
  {
  teleport_offset = $table_2.getOrigin() - $table_1.getOrigin(); //calculate the vector to get to room 2 from room 1, using the tables as reference points
  vector offset = $table_2.getOrigin() - $table_1.getOrigin(); //calculate the vector to get to room 2 from room 1, using the tables as reference points
teleport_destination = $player1.getOrigin() + teleport_offset; //modify the player's current position with the "teleport_offset" in order to get the destination
   
   
  $player1.setOrigin(teleport_destination); //perform the teleportation to the position stored as "teleport_destination"
if (ent_brush.getKey("direction") == "backwards") offset = -offset; //check the "direction" spawnarg on the brush that called the script. If "backwards", make the offset become negative
  $player1.setOrigin($player1.getOrigin() + offset); //teleport the player by taking his current position and adding the teleportation offset to it
  }
  }


For teleporting back, the first script can be copied. The only change needed is to subtract the teleport_offset from the player's origin, rather than adding it.
void teleport_backward()
{
teleport_offset = $table_2.getOrigin() - $table_1.getOrigin(); //calculate the vector to get to room 2 from room 1, using the tables as reference points
teleport_destination = $player1.getOrigin() - teleport_offset; //modify the player's current position with the teleportation offset in order to get the destination
$player1.setOrigin(teleport_destination); //perform the teleportation
}
   
   
The setup is now done. If everything is done right, the player would now teleport seamlessly between the 2 rooms without ever noticing. You could place some kind of unique item in one of the rooms (i.e. a big torch) to demonstrate this more visibly.
The setup is now done. If everything is done right, the player would now teleport seamlessly between the 2 rooms without ever noticing. You could place some kind of unique item in one of the rooms (i.e. a big torch) to demonstrate this more visibly.




Notes:
 
* A lot of the work in this case went into the planning and mapping phases, while the scripting itself was quite straightforward (possibly thanks in part to the advance planning). Planning setups like this should become easier the more you've done with scripting in your maps.
== Looking back ==
* The variable "teleport_offset" is always the same, so it's not ideal to recalculate it every time a teleportation script is called (even though get() events are very lightweight). Alternatively you could calculate it a single time in void main() or in a 3rd script that gets called after the map starts. The downside would be that your scripts are more spread out. With the above approach, everything is compact and in one place.
 
A faster and simpler method would've been to just make one script for each brush: one for teleporting forwards, and one for teleporting backwards. However, these scripts would've basically been duplicates except for a + or - sign. It's best to get into the habit of writing a piece of code only once and using variables in order to change how it plays out.
 
 
Further comments:
* A lot of the work in this case went into the planning and mapping phases, while the scripting itself was quite short (possibly thanks in part to the advance planning). Planning setups like this should become easier the more you've done with scripting in your maps.
* Technically one callscriptfunction entity would've been enough, rather than one for each trigger brush. However, this might look a bit messy if the 2 rooms are far apart. It looks neater in my opinion if you don't have target lines crisscrossing the map.
* Since there are 2 callscriptfunction entities, you could also have stored the "direction" spawnargs on them instead of on the brushes.





Revision as of 12:41, 23 December 2020

So far we've seen the basics of scripting, including the basic composition of a script, how variables are made and used, how to "get" information about entities in the map, all the ways to call a script, how to use the TDM Script Reference as well as how to set up your .script file. That would already be enough to try an early scripting exercise:

Say you were making some kind of wizard's tower, or had 2 separate areas that should look like they're physically connected: you'd want to teleport the player between 2 rooms without him realising it. That means both rooms should look identical, and the player should be teleported to the exactly same position in the other room.


Brainstorming

Before you start writing any lines of script, it's good to plan this out, maybe even writing down notes somewhere. The more you think of now, the fewer roadblocks there might be down the line:

  • 1) What will be teleported? Just the player, or maybe also moveables and even AIs?
  • 2) How will the script know where the player/etc. should be teleported?
  • 3) How should the rooms be made? Should AIs have access? Should there be extinguishable lamps? Where should the trigger brushes be?


1) What will be teleported? Just the player, or maybe also moveables and even AIs?

Since this is a basic script, it's better to keep things simple, so only the player will be teleported for now. Moveables would be more more advanced, since they would require some way of detecting them (i.e. with a room-filling trigger_touch brush) and a way of running the teleportation script on every one of them. AIs most likely shouldn't be teleported since it would disrupt their pathfinding.


2) How will the script know where the player should be teleported?

To maintain the illusion, we want the player to be teleported to the exact same position in the other room. That means we can't teleport him to a fixed position, but will instead have to get his current position and modify it with an offset.

Say the 2nd room was 1024 units off to the right compared to the first unit. In that case, you can simply add or subtract 1024 from the player's origin on the x axis to move him between the 2 rooms. But if you ever moved these rooms, you'd have to manually update this number.

Therefore it's better to automatically calculate the offset as a variable. All you need is to take the position of some kind of reference point in both rooms, such as the origin of a piece of furniture, and find the difference. This vector will always take you to the corresponding point in the other room.


3) How should the rooms be made? Should AIs have access? Should there be extinguishable lamps? Where should the trigger brushes be?

This is more of a mapping question. We want there to be as few differences as possible between the 2 rooms, so any non-static entities like AIs, loot, moveables or extinguishable lamps shouldn't be found in or near these rooms (unless you want to put in the extra work of synchronising the 2 rooms' copies via script). AIs should have no way of pathing into these rooms.

The next mapping question is layout. Corridors leading to/from the rooms could be L-shaped to minimise what the player can see when the transition happens (to reduce how much you have to keep identical). You will also need to place the trigger brushes so that the player doesn't teleport and immediately stumble into the brush that teleports him back where he came from.


The mapping

Next you will want to do the mapping in DR, as you'll need the entity names later on for the script. After the brainstorming, this is a setup that could work:

Practical exercise teleportation2.png


These are the key features of the mapping setup:

  • two trigger_multi brushes at opposite ends of the rooms. They're located such that the player only activates one on his path through the setup.
    • each brush targets a callscriptfunction entity to call the teleportation script. Callscriptfunction entities are more advanced than trigger brushes, since they can tell the script which trigger brush was activated (see A to Z Scripting: Ways of calling a script > "callscriptfunction").
    • each brush has a custom spawnarg called "direction" either with the value "forward" or "backward". The script looks up this spawnarg on the brush that called the script.
    • Checking which brush called the script allows us to reuse a single script, rather than writing one script that teleports forward and one almost identical script that teleports backward.
  • the two callscriptfunction entities are shown in the void for easier visibility, you'd place them inside the rooms or somewhere else that's sealed.
    • the "foreach" "1" spawnarg is what allows them to pass on to the script which brush triggered them.
  • the table in the centre of each room acts as the reference point for calculating the teleportation offset. For easier reference in the script, they've been given special names (table_1, table_2).
  • all 4 corridors are blind and L-shaped. The player enters the setup via the bottom left corridor and leaves via the top right corridor, so you'd attach the rest of your map to these 2 corridors.


The scripting

Now that all the planning and setup are out of the way, the scripting itself can begin. Start with thinking about what important components are involved: one script, and one variable for storing the teleportation offset.

void teleport_script(entity ent_target, entity ent_brush, entity ent_callscriptfunction)	//receive up to 3 entities as input from the callscriptfunction entity. Only ent_brush is used in this script
{
	vector offset = $table_2.getOrigin() - $table_1.getOrigin();		//calculate the vector to get to room 2 from room 1, using the tables as reference points

	if (ent_brush.getKey("direction") == "backwards") offset = -offset;	//check the "direction" spawnarg on the brush that called the script. If "backwards", make the offset become negative


	$player1.setOrigin($player1.getOrigin() + offset);			//teleport the player by taking his current position and adding the teleportation offset to it
}


The setup is now done. If everything is done right, the player would now teleport seamlessly between the 2 rooms without ever noticing. You could place some kind of unique item in one of the rooms (i.e. a big torch) to demonstrate this more visibly.


Looking back

A faster and simpler method would've been to just make one script for each brush: one for teleporting forwards, and one for teleporting backwards. However, these scripts would've basically been duplicates except for a + or - sign. It's best to get into the habit of writing a piece of code only once and using variables in order to change how it plays out.


Further comments:

  • A lot of the work in this case went into the planning and mapping phases, while the scripting itself was quite short (possibly thanks in part to the advance planning). Planning setups like this should become easier the more you've done with scripting in your maps.
  • Technically one callscriptfunction entity would've been enough, rather than one for each trigger brush. However, this might look a bit messy if the 2 rooms are far apart. It looks neater in my opinion if you don't have target lines crisscrossing the map.
  • Since there are 2 callscriptfunction entities, you could also have stored the "direction" spawnargs on them instead of on the brushes.


Next / previous article