-
Notifications
You must be signed in to change notification settings - Fork 0
A Crash Course on Cutscenes (FAQ)
What follows are many frequently asked questions about Emerald scripting and various tricks about making cutscenes.
In mapping for a gen 3 pokemon game, you will see that every tile has an "elevation" or a "Z" coordinate. This somewhat-misnomer of a term indicates how objects standing on the tile will act, more, and interact with one another. It has nothing specifically to do with height. For more in-depth information, see the tutorial on triple layer metatiles.
For basic mapping, what you need to know is that elevation 3 is the default, elevation 4 is for walking over tiles on the top layer, and elevation 0 connects between them. Objects on different elevations cannot interact and can walk through one another. Attempting to walk from one elevation to another without a 0 in between results in bumping into a wall.
You have to change the elevation of the NPC to be standing on the bridge. Porymap sets it to 3 by default.
Normally, when obects are moving around the map, the game sets the object's elevation to be the same as the tile it is standing on. However, bridges (elevation 15) work by not changing the elevation of objects walking over them. This means that objects that enter the tile from elevation 3 ("walking under the bridge") will stay at elevation 3, and those that enter on elevation 4 ("walking on the bridge") will stay at elevation 4.
Because porymap by default sets object's elevation to 3, you simply have to change it to 4 to ensure your NPC which starts on the bridge will stay on the bridge.
I put an NPC on a door and the player can just walk through it still! What gives?! The NPC is supposed to stop the player from entering!
In order for warps to work, a warp point needs to be put on a tile that has a metatile behavior that the game considers a warp behavior. Animated doors are special warp behaviors because they work a little differently from normal warps.
An animated door's warp is triggered by the player bumping into the collision of the warp door. This could just be normal collision set on the tile, but bumping into a different elevation or an NPC also counts as collision to activate the door.
The only way to disable a warp by script is to change the metatile to one that does not have a warp behavior on it. If you want to disable a warp in the editor, all you need to do is move the warp object off the tile and onto a metatile that doesn't have the warp behavior.
The warp out of a south-facing cave requires that the tile above it be the warp tile, usually using the MB_SOUTH_ARROW_WARP behavior. These tiles are just like house doorway carpets, except the warp tile likely looks exactly like another ground tile in the tileset.
Why isn't my cutscene triggering when I warp into a map? The player is stepping on the trigger for it!
Triggers and warps are only processed when the player themselves walk onto the tile and not if the game places or applymovements them there.
In order to run a cutscene upon first entering a map, put the script into the FRAME_TABLE map script table. These scripts are evaluated once per frame while the player has control, and so will run as soon as the map fades up right before the player gains control. Be sure to set the variable you check inside the script you've run, or the script will run again immediately after it finishes and the game is softlocked.
Note: DO NOT attempt to run a cutscene from any map script except for the FRAME_TABLE, as the rest of the map scripts are run in a special "immediate script context" (RunScriptImmediately) which is designed to run in a single frame. If you attempt to wait* in any of those map scripts, your game will lock up on a black screen, as the immediate script context does not return until the script is finished and the script will never finish if it is waiting for something.
As above, warp tiles do not work unless it is the player that activates them, so to transition to another map in a cutscene, you need to use a warp command. When you use the warp command, it should be immediately followed by a waitstate command, which pauses the script until the warp is finished.
After you call the warp command, the game will begin fading out the screen to warp to another map. When the game warps, the script will be cut off whereever it is as the script context is reset and cleaned out upon warping to a new map. It's safest to assume that anything after a warp command may not run, and calling waitstate after a warp command ensures that nothing will.
So to continue the cutscene in a new map, you will need to set up a new script upon entering the new map. See the above answer about using the FRAME_TABLE for this. Right before you warp in the previous map's script, you should set one of the permenant variables to something which you check in the next map to continue the cutscene.
When you move event objects around with applymovement or setobjectxy, you are changing the position of the spawned object. When the object gets too far off-screen, the game will despawn the object and will respawn it at the point where the object template is in the map.
If you want an object to stick in a new place, you can use setobjectxyperm, which modifies the object template for the current map. Or, if you've already moved the object in a cutscene, you can use copyobjectxytoperm, which copies a spawned objects current position to its template. When you've done either of these, your object will stay where you left it for as long as the map is loaded.
There's a set of object graphics that are variable. They are OBJ_EVENT_GFX_VAR_0 to OBJ_EVENT_GFX_VAR_F. The graphic they're set to depends on what graphic id is set to VAR_OBJ_GFX_ID_0 to VAR_OBJ_GFX_ID_F respectfully. The vanilla game uses these for the player's rival, setting the graphic to the rival version of the opposite gender player character.
If you wish to change the graphic mid-cutscene, you need to first copyobjectxytoperm to set the object's template position to the current position of the object. Then set the corresponding VAR_OBJ_GFX_ID_* to the graphic you want. Then you need to removeobject to despawn the object and addobject to respawn it right after, and it will respawn with the new graphic.
Alternately, you can just use two different objects. The vanilla game uses two different objects for the rival when they get on and off their bike on Route 120.
The applymovement command does not itself move an object: rather it sets up a task that runs in the background which will apply forced movement to the object over a period of time. The waitmovement command pauses script execution until that movement is finished.
It is possible to use applymovement on multiple objects before calling any wait* commands. If you want an object to walk around while a dialog box it up, you can call applymovement before a msgbox command (or on either side of a message command). Note that applymovment will silently fail if an object already has a movement script running on it, so make sure you waitmovement on the object before applying another movement.
Note 1: waitmovement 0 waits on the last object that applymovement was called on. If you want to wait for multiple objects, call waitmovement on each object individually. The waitmovement command will continue immediately if the object has already stopped moving or no movement script has been put on the object.
Note 2: The forced movement task that actually moves the objects is destroyed only when release is called. If you find your object isn't moving/turning, you might be missing a waitmovement before your release. If you forget to call release, you may find objects moving after the script is done.
Make a script in the ON_WARP_INTO_MAP script table which calls hideobjectat OBJ_EVENT_ID_PLAYER, 0. (The second parameter is ignored for the player object; vanilla scripts put a wide variety of map ids in there, but all of them are ignored.)
You can use special SpawnCameraObject to spawn a new invisible object that the camera will then follow. You can move this object around with OBJ_EVENT_ID_CAMERA as the Local ID, and you can move the player around without the camera using the usual OBJ_EVENT_ID_PLAYER. You can then remove the camera object and make it follow the player again with special RemoveCameraObject.
Note 1: When you call RemoveCameraObject, the camera will continue to follow the player at the offset from the player it is currently positioned at. So if you're not centered on the player when you remove the camera object, the player will be offset for as long as the player remains on the map. (A warp to another map will reset the camera position.) This is a deliberate feature used in the vanilla Rayquaza cutscene, where the camera pans over to the fighting legendaries, and then returns to where it left off after the cutaway, and pans back to the player.
Note 2: Even without use of the CameraObject specials, you can end up with this offset camera position if you call setobjectxy on the player. This is why the player is up in the top corner of the screen after the tutorial battle in vanilla, and why it corrects itself after the game warps the player to the lab.
Note 3: You cannot just teleport the camera elsewhere on the map with setobjectxy OBJ_EVENT_ID_CAMERA as that will break the background tiling. Teleporting the camera like this requires use of the otherwise unused native function MoveCameraAndRedrawMap in field_camera.c.
Event objects in the overworld are defined as "event object templates". When you make a scene in porymap, you are defining an event object template. Those templates don't "spawn" into proper event objects until the camera gets close to them.
When the camera is moving (usually because the player is walking and the camera follows the player), the game loops through the list of templates and spawns objects within 2 tiles of the edge of the screen, and despawns objects that are outside of that 2 tile border. (More specifically, it checks both the current location of the object as well as the initial location, so event objects that walk off screen of their own accord don't just pop back into existence again.)
If you attempt to do applymovement or setobjectxy to an object that is not spawned, the command will silently fail and the script will continue regardless. If you need a character to walk in from off-screen, it's best to first call addobject to force the object to spawn before calling the above two commands.
Remember that when the camera moves, the game will reevaluate all objects for if they should be spawned, so if you've forced-spawned an object that has its hide flag set, when the camera moves, the game will despawn that object. The opposite is also true (using removeobject to force-despawn an object that does not have a hide flag).
Note: in vanilla, removeobject sets an object's hide flag while addobject does not clear the flag.
The vanilla game has this very rarely used feature where a trigger can have a "variable to check" of 0. This is a special case where the script attached to the trigger will run in the immediate script context (RunScriptImmediately). It's used in vanilla for the Route111_EventScript_SunTrigger script, which does stuff with the weather and sets a couple variables, but does not start any cutscenes. The benefit of this is that the script will run without interrupting the player's movement at all, whereas a script that runs normally may cause a small amount of hitching as the player's controls are momentarily locked while the script runs.
As a script run like this runs in the immediate context, it runs in a single frame and will lock up the game if it calls any wait* commands. It also shouldn't call lock, as that also causes the script to wait for overworld movement to finish. If not used carefully, it is possible to softlock your game, but it is a nice tool to have should you wish to do stuff in the background based on where the player is.