Difference between revisions of "Hotspot Scripting"
m (→Overview) |
m (→Overview) |
||
Line 20: | Line 20: | ||
* Several game event messages are only passed to level scripts by default - '''TODO''': Add section below describing how to get around this by a hotspot registering itself level events | * Several game event messages are only passed to level scripts by default - '''TODO''': Add section below describing how to get around this by a hotspot registering itself level events | ||
* If you want to make GUI menus ([[Level_Script_External_Code#Dear_ImGui_Functions|Dear ImGui]], or [[Level_Script_External_Code#IMGUI_UI_Functions|Phoenix IMGUI]]), then you need a level script - '''TODO''': Add a page for describing debug drawing functions and HUD image functions, that will show how to get around this in a hotspot | * If you want to make GUI menus ([[Level_Script_External_Code#Dear_ImGui_Functions|Dear ImGui]], or [[Level_Script_External_Code#IMGUI_UI_Functions|Phoenix IMGUI]]), then you need a level script - '''TODO''': Add a page for describing debug drawing functions and HUD image functions, that will show how to get around this in a hotspot | ||
− | * If you want to do more complicated file access, [[Level_Script_External_Code#File_Access_Functions|the functions you need only work in Level Scripts]] | + | * If you want to do more complicated file access (such as parsing custom files), [[Level_Script_External_Code#File_Access_Functions|the functions you need only work in Level Scripts]] |
Hotspots have to placed inside a level in order to work. You can get around this by using a mod level hook script to spawn a hotspot inside a given set of levels, or spawn it inside all levels. | Hotspots have to placed inside a level in order to work. You can get around this by using a mod level hook script to spawn a hotspot inside a given set of levels, or spawn it inside all levels. |
Revision as of 05:03, 9 November 2017
Contents
- 1 Overview
- 2 Documentation
- 3 How to create a hotspot
- 4 How to add a hotspot to the spawner menu
- 5 Hotspot script hook functions
- 5.1 GetTypeString function
- 5.2 Init function
- 5.3 SetParameters function
- 5.4 SetEnabled function
- 5.5 Reset function
- 5.6 Dispose function
- 5.7 Update function
- 5.8 HandleEvent function
- 5.9 HandleEventItem function
- 5.10 ReceiveMessage function
- 5.11 PreDraw function
- 5.12 Draw function
- 5.13 DrawEditor function
- 6 Example hotspots
Overview
Hotspots are the most versatile game object in the Phoenix engine.
It is highly recommended to use hotspots for most of your game logic, when you can help it. They will only affect levels that they have been placed into. Level creators can manually place them inside their levels, and they can mix and match which hotspots they choose to place.
Hotspots can handle either local behavior (when a charater or item is inside the hotspot), or global behavior (since they have script functions that run on all updates, as long as they're placed inside the level). Hotspots can also be programmed to interact with each other.
Hotspots scripts can choose to remain invisible while playing, or can be made to be visible. They can even have fancy drawing, so they look like something that fits into the game, rather than some strange editor object. They can also be set up to draw differently while inside the editor.
The Overgrowth main campaign and Lugaru campaign both use hotspots extensively:
- The Overgrowth story checkpoint and goal system uses invisible checkpoint hotspots
- The Lugaru and Overgrowth campaigns both use them to control music
- Kill boxes use hotspots which listen to "enter" events to kill characters that end up inside them
- Lava and water are both set up using hotspots, and are drawn with special shaders
Occasionally a hotspot is not the right tool for the job:
- Several game event messages are only passed to level scripts by default - TODO: Add section below describing how to get around this by a hotspot registering itself level events
- If you want to make GUI menus (Dear ImGui, or Phoenix IMGUI), then you need a level script - TODO: Add a page for describing debug drawing functions and HUD image functions, that will show how to get around this in a hotspot
- If you want to do more complicated file access (such as parsing custom files), the functions you need only work in Level Scripts
Hotspots have to placed inside a level in order to work. You can get around this by using a mod level hook script to spawn a hotspot inside a given set of levels, or spawn it inside all levels.
Documentation
Callable functions and data available inside hotspot scripts
For the most part, a hotspot script can call all the same code as a level script. There is some code level scripts can call that hotspots cannot - see the links below for more info.
In addition, a hotspot gets an instances of itself (a variable defined as Hotspot@ hotspot;
), so it can call functions on the Hotspot
interface, get its own id, etc.
A list of functions, data, etc, is created automatically whenever you run the game, in a file on your hard drive. This lists what external code you get "for free" and can call from inside a hotspot script. (Some of this code in this list is not usable from a hotspot script - see the links below for more info).
- Windows:
My Documents\Wolfire\Overgrowth\aslevel_docs.h
- Mac:
~/Library/Application Support/Overgrowth/aslevel_docs.h
- Linux:
~/.local/share/Overgrowth/aslevel_docs.h
This documentation will change with each version of the game, so keep checking back on this aslevel_docs.h
file to see the most up to date information.
There are also wiki pages which have more detailed documentation many of these functions. These pages have a danger of going out of date, but give more detailed documentation on some of the code:
- These functions are unique to hotspot scripts - Beware, some of this code is only available to level scripts! Read the docs carefully!
- These functions are shared with other script types
Hotspot instances in other scripts
Hotspots are exposed to other scripts as instances of the Hotspot
class.
Hotspot object instances are like other objects in the game. They support everything that is supported by Object
, and also have their own special set of functions, properties, etc.
If you have a handle to a Hotspot@
object in angelscript, you can convert it to an Object@
by calling ReadObjectFromID(hotspot.GetID());
.
How to create a hotspot
Hotspots require only a few files to work: The hotspot definition (.xml), and the hotspot angelscript file (.as).
Hotspot XML file
Here's the format for the hotspot XML file.
<?xml version="2.0" ?> <Type>generic</Type> <Hotspot> <BillboardColorMap>Data/UI/spawner/thumbs/Hotspot/empty.png</BillboardColorMap> <Script>hotspots/your_mod_name/your_hotspot_script.as</Script> </Hotspot>
-
BillboardColorMap
is the relative path (from the base directory) to an image file. This image will be visible inside the hotspot's bounding box while you have the editor active.
-
Script
is the relative path (rooted at theData/Scripts
directory) to a script fie. This is the script that will provide the script logic to the hotspot. See the Hotspot script hook functions section below to see what functions you should make inside your script.
Remember to place your hotspot XML file and hotspot script file in the correct directories, so they don't conflict with other mods. See the File path conventions section on this page.
How to set a default scale for the hotspot
TODO: Document how to save a transform for a hotspot (scale, mostly)
Short version: You select the object, and choose Edit -> Save Selection
File path conventions
Be careful to place your hotspot XML files and hotspot script files in a place where they won't conflict with other mods. The easiest way to do this is add a your_mod_name directory to the path, right before the file.
Data/Mods/<your_mod_name>/Objects/Hotspots/<your_mod_name>/my_hotspot.xml
Data/Mods/<your_mod_name>/Scripts/hotspots/<your_mod_name>/my_hotspot.as
<- Note, lowercase "Script/hotspots"
Short version:
You must add it in a mod.
You can add as many hotspots to the spawner menu as you want in a single mod, you just need to follow the below instructions for each, and add multiple <Item>
tags to your mod.xml file.
In your mod.xml file, add this xml tag:
<Item category="My Mods Items" title="Some Item To Spawn" path="Data/Objects/example_item_pack/mod_item_example.xml" thumbnail="Data/UI/example_item_pack/thumbs/mod_item_example.png" />
-
category
is the top level category where the item will show up, in the Load menu. -
title
is the name of the item, as it will show up in the spawner menu. -
path
is the path to the object XML that will get spawned. See the How to create a hotspot section for which XML file to target (either the hotspot XML itself, or a version you saved off that has the modified scale): http://wiki.wolfire.com/index.php/Hotspots#How_to_create_a_hotspot -
thumbnail
is the image that will be used for a tooltip when you hover over your item in the spawner menu.
See GameInstallDir/Data/ExampleMods/mod_xml_specification.txt for full information.
Hotspot script hook functions
All functions inside a hotspot script are optional, but will be called if they are present.
GetTypeString function
string GetTypeString() { return "MyHotspotType"; }
Optional
GetTypeString
is called when a different script invokes hotspot_instance.GetTypeString()
This is useful for identifying specific hotspots by type in a different script.
You can find hotspots with this type from a different script using code like this:
int num_hotspots = GetNumHotspots(); for(int i=0; i < num_hotspots; ++i) { Hotspot@ hotspot = ReadHotspot(i); if(hotspot.GetTypeString() == "SomeCustomHotspotType") { // ...
This is useful when you want to send messages to the hotspot, to control or move it, to delete it, etc
Init function
void Init()
Optional
Init
is Called when the hotspot is first loaded, upon level load.
It is also called when an undo/redo is fired in the editor, to reload script variables from that point in history.
Be careful, this may be called before some objects or script params are present in the level.
It is most useful for setting initial values for file-scope angelscript state.
SetParameters function
void SetParameters()
Optional
SetParameters
is called when the hotspot is first loaded, and whenever the script parameter values are changed via the GUI.
It is also called when an undo/redo is fired in the editor, to reload script params from that point in history.
This function allows the hotspot script to work with script parameter values. You can use it to define, change, verify, protect (undo writes), or locally cache script parameter values.
You can also use this to work with game objects that you use hotspot script params to control.
SetEnabled function
Note: This function won't be available until Beta 6, but is already present in the Steam internal_testing build, which is available to everyone. I don't recommend you release a mod that uses this function until at least Beta 6. (-Merlyn)
void SetEnabled(bool val)
Optional
SetEnabled
is called when SetEnabled gets called on the hotspot object by another script.
This function allows the hotspot to enable or disable any objects that should be associated with the hotspot (objects that are used like children of the hotspot, such as PlaceholderObject
instances).
This function can also be used to internally set variables so that its functions have no effect. The engine will generally not call the hotspot if it is not enabled (except when it gets re-enabled), but other scripts could theoretically call functions on the hotspot using hotspot_instance.Execute("some code");
.
Reset function
void Reset()
Optional
Reset
is called whenever the level is reset. This does not happen when a level is first loaded, or when undo/redo are fired in the editor.
This happens when the player hits the "L" debug key to manually reset the level.
This also happens upon player respawn (but this is up to the level script, and modded level scripts might not do that).
Dispose function
void Dispose()
Optional
Dispose
is called when the hotspot is destroyed, either by manually deleting it, or when the level is unloaded.
WARNING: Do not DeleteObjectID for other game objects from this function! If you try to, then the game will likely crash on level unload when calling Dispose()
on the object you deleted. Also don't try to QueueDeleteObjectID()
through Dispose, or it will really mess with undo/redo state.
Used to free any non-game-object resources that you have a handle to that are owned by this hotspot.
One example is Debug Draw objects with _persistent lifetime.
Update function
void Update()
Optional
Update
is called regularly by the engine so you can perform work.
It may be useful to do initialization once in this function, if you need objects or level script params to be present.
Be careful to keep this function short, sweet, and fast. Update
gets called 120 times a second as of the time of this writing.
HandleEvent function
void HandleEvent(string event, MovementObject @mo)
Optional
HandleEvent
is triggered when a movement object (usually a character) interacts with this hotspot.
For the most part, this is event = "enter" or event = "exit", when a character enters or leaves the hotspot.
I cannot find any other Hotspot + MovementObject events at this time, but there may be more in the future. (-Merlyn)
HandleEventItem function
void HandleEventItem(string event, ItemObject @obj)
Optional
HandleEventItem
is triggered when an item object interacts with this hotspot.
For the most part, this is event = "enter" or event = "exit", when an item enters or leaves the hotspot.
I cannot find any other Hotspot + ItemObject events at this time, but there may be more in the future. (-Merlyn)
ReceiveMessage function
void ReceiveMessage(string)
Optional
ReceiveMessage
is called when messages have been sent to this hotspot by the engine, level, or objects in levels.
Objects in levels (such as characters or hotspots) can send this using hotspot.SendMessage("some message string");<code>.
Parameters can be sent by separating them with spaces, and putting quotes around parameters that might contain spaces, then using the <code>TokenIterator object.
If you're setting up the code to send a message to a hotspot, you might want to choose to only send messages to a hotspot with a specific custom type. See the GetTypeString
function above for an example of how to do this filtering: http://wiki.wolfire.com/index.php?title=Hotspots#GetTypeString_function
PreDraw function
void PreDraw(float curr_game_time)
Optional
PreDraw
serves as a before-update function for script-defined drawing.
This lets you correct properties of the object before draw is called, so movement, etc, doesn't lag behind by a frame.
Be careful to keep this function short, sweet, and fast. PreDraw
gets called on every frame as of this writing.
Draw function
void Draw()
Optional
Draw
serves as an update function for script-defined drawing. This is called both while in editor, and while not in the editor.
Be careful to keep this function short, sweet, and fast. Draw
gets called on every frame as of this writing.
DrawEditor function
void DrawEditor()
Optional
DrawEditor
serves as an update function for drawing. This is called only while the editor is active.
Be careful to keep this function short, sweet, and fast. DrawEditor
may get called on every frame.
Example hotspots
TODO: Update this to something more useful/complete. Multiple examples? Demo editor drawing, and in-game drawing. Demo using enter/exit events, and hotspots that don't use them.
TODO: Demo using a level script to insert a hotspot.
TODO: Demo using the hotspot function that lets you register for receiving level events
//Variables can be created outside any function, these will be available to all following functions. float force; int friendly; void Init() { //Inside the init function you can put something that only needs to happen once. Print("Initializing hotspot\n"); } //Inside the SetParameters function you can set declare variables that the user can change. By selecting the hotspot and press Ctrl + U the parameters will be available to the user. void SetParameters() { params.AddString("Upward force", "5.0"); //You can either assign the parameter to a variable straight away or fetch it when it is used. force = params.GetFloat("Upward force"); //Sliders can be usefull for the user to quickly change parameters. params.AddFloatSlider("Recovery Time", 1.0f, "min:0.0,max:50.0"); //You can add checkboxes for simple on/off functions. params.AddIntCheckbox("Friendly", false); friendly = (params.GetInt("Friendly") == 1); } //You can check for a character or an item to enter or exit the hotspot. void HandleEvent(string event, MovementObject @mo){ //Here we check if a character enters the hotspot. if(event == "enter"){ OnEnterChar(mo); } //The function "OnExitChar()" will be called if a character exits teh hotspot else if(event == "exit"){ OnExitChar(mo); } } //The same technique can be used for an item. Note: There is a difference between an ItemObject and an Object. An ItemObject is affected by gravity, such as weapons and collectables. An Object is static for example a hexcrete or joshua tree. void HandleEventItem(string event, ItemObject @obj){ if(event == "enter"){ OnEnterItem(obj); } if(event == "exit"){ OnExitItem(obj); } } void OnEnterItem(ItemObject @obj) { if(obj.GetType() == _collectable){ Print("A " + obj.GetType() + " item has entered the hotspot\n") } //An item can just like a character have it's properties changed via a hotspot. vec3 vel(0.0f,15.5f,0.0f); obj.SetLinearVelocity(vel); } void OnExitItem(ItemObject @obj) { } void OnEnterChar(MovementObject @mo) { //To check if the character is the user you can check if it is a controller MovementObject. if(mo.controlled){ Print("A user has entered the hotspot\n") //If you choose to retrieve the parameter as needed you can request by name. mo.velocity.y = params.GetFloat("Upward force"); } else{ Print("An NPC has entered the hotspot\n"); } //You can create effects with MakeParticle.The first parameter is the particle file to load. The second parameter is the position in the world to spawn this particle. The last parameter is the direction the particle is moving. The higher the values the faster it will move. MakeParticle("Data/Particles/fire.xml", mo.position,vec3(0.0f,15.0f,0.0f)); //Certain functions are only accessible if you go through the Execute() function. These can contain multiple functions and even parameters. mo.Execute("SetOnGround(false);DropWeapon();recovery_time = "+params.GetFloat("Recovery time")+";"); //The level itself can also receive various function level.SendMessage("loadlevel \"/Data/Levels/sea_cliffs.xml"); //To put some text on the screen in stead of the console you can use. level.SendMessage("displaytext Test Message"); //And to clear it. level.SendMessage("cleartext"); //You can also reset the level. level.SendMessage("reset"); //If you want to put an image onto the screen level.SendMessage("displayhud /Data/Textures/twisted_paths.png"); //And to remove this again use level.SendMessage("clearhud"); //To play a sound you use the PlaySound function. The second parameter to specify the location is optional. Make sure the audio file mono or else the positional effect won't work. PlaySound("Data/Sounds/fall_swoosh.wav", mo.position); //To get a random value you can use RangedRandomFloat. This will be usefull for example with particle effects. float direction = RangedRandomFloat(-100.0f,100.0f); //To spawn objects you can use the CreateObject function. This can be an Object, ItemObject or Character. int objectid = CreateObject("Data/Objects/rock.xml"); //The function return an id number which you can use to get the Object. Now the Object can be manipulated. Object@ objectid = ReadObjectFromID(obj_id); //If the object is character use the MovementObject type. MovementObject@ character = ReadCharacterID(objectid); //If the object is a plant use the EnvObject type. EnvObject@ plant = ReadEnvObjectID(objectid); //If the object is weapon or collectable use the ItemObject type. ItemObject@ weapon = ReadItemID(objectid); //The hotspot is also an object inside the game. You can also move and resize it. Object@ this_hotspot = ReadObjectFromID(hotspot.GetID()); } void OnExitChar(MovementObject @mo) { } //The Update() function will continue to execute as long as the hotspot is loaded. It does not need to be triggered. void Update() { }