Map Changing Tutorial by NeoLogiX

From Spherical
Jump to: navigation, search

The most common activity in an RPG is walking on a map. A map usually contains places to visit so the game needs to be able to "warp" the player from one map (usually an overworld map) to another (usually a town or dungeon), or vice versa. This tutorial offers two practical, usable methods to scripting a change of map.

Simple Method

var mapfile = "overworld.rmp";
ChangeMap(mapfile);

Sphere has a built-in function ChangeMap that handles all the behind-the-scenes work of switching from one map to another. It requires the map engine to be running, otherwise Sphere will abort with an error telling you that the map engine isn't running. If you don't care about any special visual effects taking place during a map change, all you need to do is call ChangeMap with the file name of the new map.

Triggering

The most common way to activate a change-of-map within a Sphere map is to put a Trigger entity on the same layer as the expected input person and add the map change code to it. In actuality, as long as the map engine is running ChangeMap can be called at any time.


If you don't need anything fancier than this you can simply end here and use plain ChangeMap whenever you need to warp.

Advanced Method

This tutorial exists because Sphere users often want to see visual effects during a map change. Commonly known as a "transition" or "screen transition," this visual effect adds some "juice" to your game by making a gradual map change look more fantastical or even "realistic" than a sudden swap; random blocks or pixels fading in or out looks more like a fantasy teleportation, a fade in our out looks more like going into a dark place from a light place or coming out of that dark place to a much lighter one, and so on.

A convenient way to manage the transitions needed for a map change (or for that matter any screen transition in general) is by using a general purpose transition manager that allows for configuration. Certain functionalities can be split out to be independent of the transition manager so that they can be used at any time. We shall now create a transition manager piece by piece.

Animation Speed

Robert Penner presented "a collection of swappable functions that add flavor to motion." He called them "easing functions" because they "ease" an animation. This is the essence of tweening and can be a very powerful tool for timing an animation.

An easing function takes a time (as a raw change in time since the start of the animation), beginning value, final change in value (i.e. how far from the beginning value we'll end up), and duration of time the animation should take. We will define easing functions independent of the transition manager so that we can use any of them anywhere; for this transition manager, easing functions will control the speed with which an animation takes place.

Linear Ease

A linear ease is the easing function version of normal linear interpolation.

function linear(t, b, c, d) { return c*t/d + b; }

Other easing functions can be written to create more natural-looking animations or to use with some less common screen transitions. Visit the links at the bottom for more info.

Animation Effect

A screen transition requires an effect to use. For this tutorial, we will take one second (1000 milliseconds) to fade between two maps; the old map fades into the new one.

Effects functions in this transition manager will take the before-image, the after-image, the start time, the length of time for the animation, the current time, and an easing function to transform the time into an alpha value for the old image.

Fade Effect

function fade(a, b, ts, ms, tn, ease) {
	var alpha = ease(tn-ts, 255, -255, ms);
	if (alpha>255) alpha = 255;
	else if (alpha<0) alpha = 0;
	b.blit(0,0);
	a.blitMask(0,0, CreateColor(255,255,255, alpha|0));
}

There are a lot of parameters, but the above function simply fades the old image on top of the new image. Once tn reaches the value ts+ms (i.e. the end of the animation), it will render the old image completely transparent, showing the new image unobstructed.

The Transition Manager

We will now create the actual transition manager that will initialize a transition, perform the transition over a given amount of time, then prepare the new image for use once the transition completes. Transition takes the before-image, the after-image, the effect function to use, the easing function to use to smooth the animation, the amount of time (in milliseconds) to perform the transition, and an extra object to hold data specific to the given instance of the transition.

function Transition(before, after, effect, easing, ms, data) {
	if ('init' in effect) effect.init(data);
	var start = GetTime(), end = start+ms, time;
	while ((time=GetTime())<end) {
		effect(before, after, start, ms, time, easing);
		FlipScreen();
	}
	after.blit(0,0);
}

As you'll see at the beginning of the function, Transition will initialize the given effect with the extra data; the fade effect function above, for example, can be modified to take a color for tinting the old image:

/**** add the following AFTER defining the fade function ****/
fade.prototype.init = function(data) {
	this.color = (data && data.color) || CreateColor(255,255,255,255);
};

...but for it to take effect, the above fade function has to be modified to actually use it:

/**** modified fade function; keep this before defining its init() method ****/
function fade(a, b, ts, ms, tn, ease) {
	var h = 'color' in this?1:0;
	var c = CreateColor(
		h?this.color.red:255,
		h?this.color.green:255,
		h?this.color.blue:255,
		h?this.color.alpha:255
	);
	c.alpha = ease(tn-ts, c.alpha, -c.alpha, ms)|0;
	b.blit(0,0);
	a.blitMask(0,0, c);
}

So! To recap, we've created an easing function to control animation speed, a fade animation effect that overlays the old image semi-transparently on top of the new image, and a transition manager that will automate the work. Now, we need to trigger the animation.

Triggering

For this tutorial, a map change with screen transition follows the same conditions as a regular map change: it can be activated at any time as long as the map engine is running and the most common method to activate it is to put the required code in a Trigger entity on the map. Below is sample code that can be used for changing maps with the fade effect (the modified one that allows adding a custom tint) and linear easing we defined earlier:

var SW = GetScreenWidth(), SH = GetScreenHeight();
var before = GrabImage(0,0, SW,SH), ms = 1000;
ChangeMap("newmap.rmp");
RenderMap();
var after = GrabImage(0,0, SW,SH);
Transition(before, after, fade, linear, ms, {'color':CreateColor(255,255,255,255)});

The above block of code performs the following:

  1. For convenience, we put the screen width and height into the variables SW and SH for less typing.
  2. We then define the before-image before as a capture of the entire screen and set the duration of the animation to 1000 milliseconds.
  3. Next we actually change the map. We assume that the new map has a usable entry point defined, but maybe we might want to extend the map change to allow us to set a custom position; we'll do that later.
  4. We call RenderMap to make sure that the new map is put into the backbuffer so we can capture it into the after-image after.
  5. Finally, we call our transition manager with the given variables.

Notes

Keep in mind the transition manager and the associated code defined in this tutorial is (mostly) extremely simplistic. The concept of initializing a given effect is an example of possible additions to its functionality and for more advanced effects like "mosaic" or "piecemeal blackout" this initialization step is essential. The sample fade transition also uses only two images and fixes the screen coordinates for them at (0,0); a possible edit that can be made would allow use of different coordinates and/or more than just two images.

In describing triggering the map change, you may have decided upon reading that you'd like to allow the ability to set an entity's map coordinate different from a given map's existing entry point, possibly because the map is meant to have multiple entrances. To accomplish this you would set the coordinate after the map is changed but before the temporary display of the new map (i.e. between the ChangeMap and RenderMap calls). This should be a simple exercise for the scripter and adds a bit more juice to the final map change effect.

Another possible modification that can be made is separating the transition's rendering and updating and attaching them to a render script and an update script, respectively, instead of running the whole transition as a blocking function. This is an advanced exercise that should be performed once you feel more comfortable with render and update scripting.

See also

In closing

If there are any questions, you can ask at the forums or the Talk. Enjoy transitioning your maps! --Apollolux (talk) 06:13, 8 March 2014 (UTC)