Site Logo


Sparen's Danmakufu ph3 Tutorials Lesson 20 - Manipulating Pause and End Scene Files

The video for this lesson is CK Crash's Sanae for the RaNGE 10 contest - it had a super awesome pause menu, and it's an amazing script you should all play.

Part 1: What will be Covered in this Lesson?

In this lesson, I will discuss Pause and End Scene scripts, which are called on demand upon pause or end of a script, respectfully.

This lesson requires knowledge of render and text objects. For reference, please see Lesson 13 and Lesson 17 for 2D Sprites and Text, respectively. You must read Lesson 19 first before starting with this tutorial, as that tutorial has information that will not be duplicated here.

Part 2: What are Pause and End Scene Scripts?

So we've seen System scripts, and the gist is that it's a script run at the start of a script that continues running until the script ends. Pause and End Scenes are different but have some similarities. Pause scripts are run automatically on EV_PAUSE_ENTER (when you hit ESC, which is the default key for Pause in Danmakufu). When you pause the game, Danmakufu automatically handles the actual pausing of the game - all the Pause script does is give the player a menu of what to do - for example, resume, retry, or return to title. And that's all.

End Scene scripts are similar to Pause Scripts, both codewise and in how they are triggered. End Scene scripts are automatically triggered when a script finishes executing. Usually the menu the player is presented with has replay save, retry, and return to title as its options, though there may be more depending on how the scripter rolls.

In the previous lesson, we went over how to set custom Pause, EndScene, and ReplaySave scripts, and of course, you will want to follow those procedures. For the purposes of this tutorial and for every script you make, you should ALWAYS call your own Pause and End Scene scripts (i.e. you must include your own Pause and End Scene scripts within your game and should never rely on default_system's defaults) from your System file from here on out. By this point in the tutorial, you are no longer considered a beginner, and you should not be using the default Pause and End Scene scripts. Of course, the main reason is that they're in Japanese, and at the very least, you want to have an English version (or whatever language your target audience speaks) packaged with your script.

Note that with Packages, there will be a different method for handling Pause and End Scenes, but we'll get there when we get there.

Anyways, we'll be working with the defaults, so copy the default pause and end scene scripts from default_system into your system folder or preferred directory, and link them with your System script so that your scripts will hook into them. We're going to take them apart.

Part 3: What is inside a Pause or End Scene Script?

When you open up Default_Pause.txt, you are greeted by a wall of code in two main tasks, TBackground and TMain, both of which are called in @Initialize. SetAutoDeleteObject(true); should be left alone - it will make sure that all the objects you create in the pause (or end scene) script will delete when you close the script.

Anyways, the meat of the task is in TMenu. As for TBackground, it's responsible for that fancy effect above the playing field but below the text. More specifically, it takes a screenshot of the screen with a render target, and then does some fancy vertex stuff to produce the effect. We won't look into this now.

task TMenu
{
	let selectIndex = 0;//選択位置
	task TMenuItem(let index, let mx, let my, let text)
	{
	    // Will be discussed later
	}

	//メニュー配置
	let mx = 48;
	let my = 32;
	let texts = ["再開", "再生終了", "最初からやり直す"];
	var countMenu = length(texts);
	ascent(var iText in 0 .. countMenu)
	{
		TMenuItem(iText, mx, my, texts[iText]);
		my += 32;
	}

	//キー状態がリセットされるまで待機
	while(GetVirtualKeyState(VK_PAUSE) != KEY_FREE){yield;}
...

Anyways, let's take a look at TMenu. First, you have selectIndex, which is your counter. It will determine which option the player is currently selecting (if there are three choices, it will have values of 0, 1, or 2). We also have a task TMenuItem that creates the text objects. Don't worry about this just yet - we'll go over this in a moment. We then have two variables - mx and my. There is also an array called texts. All three of these get fed into TMenuItem. So we can deduce that mx is the x coordinate from where the text objects for our options start, y is their y coordinate, and texts holds the values to show. Specifically, for the Pause Menu they are Cancel, End, and Retry. For the End Scene they are Save Replay, End, and Retry. Please take the time to turn these into English (and remove all other Japanese from the script while you're at it).

We ascent through all possible options and create the text objects, then wait for the player to stop holding the escape key (Pause) or the Z key (End Scene). This is to make sure the pause scene doesn't spontaneously end (in the Pause script's case) and makes sure that the first option in the menu isn't automatically selected if the player happens to be holding the Z key when the End Scene begins execution.

//Pause Script
...
	//決定
	if(GetVirtualKeyState(VK_OK) == KEY_PULL)
	{
		let listResult = [RESULT_CANCEL, RESULT_END, RESULT_RETRY];
		SetScriptResult(listResult[selectIndex]);
		CloseScript(GetOwnScriptID());
		return;
	}

	//キャンセル
	if(GetVirtualKeyState(VK_CANCEL) == KEY_PULL || GetVirtualKeyState(VK_PAUSE) == KEY_PULL)
	{
		SetScriptResult(RESULT_CANCEL);
		CloseScript(GetOwnScriptID());
		return;
	}
...

But what about the menu? We have an infinite loop with a bunch of things inside. First, we have a check against VK_OK, which is the virtual key for OK (defaults to Z). If we release the key (KEY_PULL is the releasing of the key), then we have a list of outcomes corresponding to the text objects. Our counter selectIndex determines which one is saved. The script is closed and the loop is broken.

And if we cancel (Pause menu only, VK_CANCEL is the virtual key for CANCEL, defaults to X), we save RESULT_CANCEL and close the pause script, returning to the game. The Pause script also allows you to exit via Esc as well.

In regards to keys, note that when you push down, that triggers KEY_PUSH, and releasing the key is KEY_PULL. There's also KEY_HOLD (holding down the key, see below) and KEY_FREE (doing nothing with the key).

And now we have the good part - navigating the menu.

Part 4: How Do Menus in Danmakufu Work?

	//メニュー選択処理
	let frameKeyHold = 0;//キー押しっぱなしフレーム数
	loop
	{
...
		//カーソル移動
		if(GetVirtualKeyState(VK_UP) == KEY_PUSH)
		{
			selectIndex--;
		}
		else if(GetVirtualKeyState(VK_DOWN) == KEY_PUSH)
		{
			selectIndex++;
		}
		else if(GetVirtualKeyState(VK_UP) == KEY_HOLD)
		{
			frameKeyHold++;
			if(frameKeyHold == 30 || (frameKeyHold > 30 && (frameKeyHold % 10 == 0)))
			{
				selectIndex--;
			}
		}
		else if(GetVirtualKeyState(VK_DOWN) == KEY_HOLD)
		{
			frameKeyHold++;
			if(frameKeyHold == 30 || (frameKeyHold > 30 && (frameKeyHold % 10 == 0)))
			{
				selectIndex++;
			}
		}
		else
		{
			frameKeyHold = 0;
		}

		if(selectIndex < 0) 
		{
			selectIndex = countMenu - 1;
		}
		else
		{
			selectIndex %= countMenu;
		}

		yield;
	}

Now, it definitely looks like a mess. There's a variable outside the loop called frameKeyHold that will be used when we hold down keys (so that after a certain number of frames, the selection will change). Note that if we have KEY_PUSH (the act of pushing a key), all it does is move our index up or down. However, if we hold down the keys, notice that frameKeyHold goes up, and if it hits 30 frames (and any 10 frames afterwards), the index will move. So if you press the key and release within 30 frames, selectIndex will change by one unit. If you press the key and hold it for 35 frames before releasing, it will change by two units. So we have a 30 frame wait to make sure that the player doesn't accidentally move too far, and once that's passed, it changes every 10 frames to allow for fast movement within the menu. If neither up nor down is being held, then the frameKeyHold resets.

After this, our menu handles when the select index is out of bounds. If it's less than 0, wrap it to the last option. If it's greater than the last option, mod it and wrap it to where it needs to go.

And that's the menu!

Part 5: What are the Results from a Pause or End Scene Script?

Anyways, let's discuss that listResult and the SetScriptResult stuff a little bit. Earlier I just brushed right past it, but there's actually quite a bit of stuff here.

RESULT_CANCEL is a constant that means 'cancel the pause script and resume the ongoing script'. In a Pause script, it resumes the script (i.e. unpauses)

RESULT_END is a constant that means 'quit'. It will quit the script (if Pause) and return to the title menu (or however the package handles it if you use a package).

RESULT_RETRY is a constant that will restart the current script. It will reset things such as Points, Graze, Lives, and Bombs, but does NOT reset CommonData. And of course, if you use a package, the way it's handled may be a little different.

RESULT_SAVE_REPLAY will trigger the replay save scene.

Regardless of which one is being used, they are saved using SetScriptResult and Danmakufu handles the results.

Part 6: How Do the Pause and End Scene Text Objects Work?

Anyways, we've already covered text objects, so this should be self-explanatory, but let's take a look anyways.

	task TMenuItem(let index, let mx, let my, let text)
	{
		function CreateTextObject(let mx, let my, let text)
		{
			let obj = ObjText_Create();
			ObjText_SetText(obj, text);
			ObjText_SetFontSize(obj, 20);
			ObjText_SetFontBold(obj, true);
			ObjText_SetFontColorTop(obj, 128, 128, 255);
			ObjText_SetFontColorBottom(obj, 64, 64, 255);
			ObjText_SetFontBorderType(obj, BORDER_FULL);
			ObjText_SetFontBorderColor(obj,255, 255, 255);
			ObjText_SetFontBorderWidth(obj, 2);
			Obj_SetRenderPriorityI(obj, 10);
			ObjRender_SetX(obj, mx);
			ObjRender_SetY(obj, my);
			return obj;
		}

		let objText = CreateTextObject(mx, my, text);
		let objSelect = CreateTextObject(mx, my, text);
		ObjRender_SetBlendType(objSelect, BLEND_ADD_RGB);
		loop
		{
			Obj_SetVisible(objSelect, index == selectIndex);
			yield;
		}
	}

We have a task surrounding a function. This is a common procedure to be able to manipulate the object returned by the function. So we create two objects - objText and objSelect, of which objSelect is ADD rendered. Now, this is ugly and there are far better options than this that you may want to consider. The loop states that objSelect should only be visible when we have selected this particular menu option.

The text object is pretty simple, and you are free to customize or do away with this arrangement entirely. Changing colors is one simple step up, as is using a specific font.

More complex methods exist, but the most important thing is to be able to distinguish which option has been selected, so you'll want to keep that loop and do something with the index == selectIndex expression *unless you use a cursor image to mark which option has been selected, in which case the cursor will position itself based off of the current value of selectIndex). Feel free to experiment and get something nice. Or use images, which is also nice.

Pause and End Scenes are an important part of a game, and can be used to great effect. Use a good aesthetic, and make your Pause and End Scenes fit in nicely with the rest of your game. It'll pay off.

Quiz: Pause and End Scene Scripts

1) Which final constant is sent as a script result signaling 'return to title'?

A. RESULT_RETRY
B. RESULT_CANCEL
C. RESULT_END

2) How long would you hold the down arrow key (VK_DOWN) in order to move two options down?

A. 30
B. 40
C. 50

Summary

  • Pause scripts are run when the game is paused
  • End scene scripts are run when the game is over or the player has died
  • Menus work by checking for virtual key input
  • Pause and End Scenes return their script result using existing constants such as RESULT_END

Sources and External Resources

N/A