Touhou Danmakufu/Official Tutorial

From Touhou Wiki
Jump to: navigation, search


Note: This is an in-progress translation of the tutorial found here. It is meant to be worked through in order.

Touhou Danmakufu is based on the Touhou series of danmaku shooters. You can use it to create your own danmaku patterns, and even your own complete games. The coding language is based loosely off the C language, however it has been heavily simplified for the purpose of creating scripts easily. In this tutorial, we will explain how to create a simple Danmakufu script. This tutorial has been written specifically with the idea of teaching people with no prior programming experience.

Contents

Lesson 1: Our First Script

First, prior to making your own script, let's have a look at an existing Touhou Danmakufu script.

Installation

Installing is easy. First, go to the Touhou Danmakufu website. Then download the latest version of Touhou Danmakufu. (This should be the first link on the page.) Because it comes as a ZIP file, you can unzip and run it from wherever you want by opening the th_dnh.exe file in the main folder. If you have enabled "Hide extensions", it's possible that someone may have slipped a virus into the folder, so you should disable this feature if possible.

You can unpack the ZIP with various software. (The site mentions a program called "Vector", but the Windows ZIP utility should be fine.)

Settings

If you have a game pad, it's best if you set it up before starting to play with Touhou Danmakufu. Special settings such as this should be controlled with the pre-configure program "config.exe". Upon running config.exe, you should see this dialog.

The top half of the dialog displays the specifications for the environment you are running Danmakufu on. The bottom half displays the settings for the device (Windowed/Fullscreen, Window Size, Graphic Depth and Audio Source on the left) and the gamepad settings (the usual buttons for the Touhou games.)

Regular players of Touhou games will recognize this layout, however if the graphics become unstable, feel free to experiment with the right half of the dialog.

Setting the pad is easy, as this dialog shows. Simply highlight the button which you want to change, and press the button. They are, in order from top to bottom:

  • Shot
  • Bomb
  • Focus
  • Decide
  • Cancel
  • Pause
  • Event Skip

It is also possible to set the same button to different behaviour, for example you might want to select the Shot button to be the Decide button, or the Bomb button to be Cancel.

Playing the Game

th_dnh.exe is the main part of Touhou Danmaku. When you run the program, you will see a menu, the meanings of which will be explained below.

All Play all scripts within the main folder and subfolders.
Single Play one script within the main folder and subfolders.
Plural Play a continuous danmaku with more than one pattern. Only uses the plural scripts.
Stage Play a stage danmaku with enemies and bosses. Only uses the stage scripts.
Directory Play one script. Differs from "Single" by the choice of subdirectory.
Random Play one script randomly selected from the main folder and subfolders.
Exit Quits the program.

Apart from "Exit", the player chooses a character and then a script to play.

All, Single, Plural, Stage

Only scripts are listed. Select the desired script, then press the decision button (Z key) to choose. Press the decision button again to play. If you use no continues, it's possible to save the replay. Replays will be shown at the bottom of the screen when you select a script; you can choose to play them back.

Directory

Scripts are listed along with directories. Navigate through the directories until you find the script you want, then select it.

Random

Selects a script at random from all the scripts. You can save replays, but you can't replay them.

Updating

To update Touhou Danmakufu, first extract the new th_dnh.zip elsewhere. Then, simply move everything to the old folder. This will overwrite all the older files in the main directory.

Lesson 2: Let's Make One!

So, small talk aside, let's now make a simple danmaku pattern. In this second and later lessons, we'll provide a summary of what we've learned in that lesson.

Summary

  • Placing files in the script folder.
  • #TouhouDanmakufu recognized as the first line in a script.
  • script_enemy_main defines the behaviour of the enemy.

A script that does nothing

First, let's make a script that does nothing. Place the file in the "scripts" folder, or if you prefer, you can make a new folder. Anyway, if the script is in the "scripts" folder, there are no restrictions.

So, let's create a file called Test.txt. Touhou Danmakufu uses the .txt format, an ordinary text file. You can use any text editor (for example Notepad) to edit the following:

#TouhouDanmakufu
#Title [The title of the danmaku]
#Text [The description of the danmaku]
#ScriptVersion [2]
script_enemy_main { }

In the first line, #TouhouDanmakufu represents a single script file for Touhou Danmakufu. For example, if you remove the # or the entire line, the system will not recognize it as a Touhou Danmakufu script. In other words, if there is another error somewhere in the script then the system will still recognize it as a Touhou Danmakufu script (even though it won't actually load). Also, because it is a stand-alone danmaku script, it will appear in the Single list.

In the second line, #Title gives the system the text that lies inside the square brackets []. In this case, "The title of the danmaku" will be displayed.

In the third line, #Text is a description of the script that appears within the square brackets. The description appears at the lower left of the screen when a particular script is selected. In this case, "The description of the danmaku" will display. If you need to use new lines in the caption, you can simply press the Enter key and go to the next line. In other words, the square brackets act like a triple-quote in Python; whatever is in the brackets (spaces, tabs, newlines etc.) will be displayed.

The fourth line specifies the version of the script. There are only two versions as of this current point in time, so this line should always read #ScriptVersion [2].

The last line, script_enemy_main, describes the behaviour of the enemy. You could say it's the most important part of the Touhou Danmakufu script. But for now, let's just leave it at that; it does nothing.

So, to start the script, run Danmakufu and select the script "The name of the danmaku". You will see the game screen briefly, before going to "Clear", as there is no enemy left in the script.

Fixing the script

Is there a need to relaunch Danmakufu every time you modify the script? No, there's no need. If you have modified the script that's running, simply press Backspace and the script will run from the start.

If you modified a different script, you can just choose it from the regular list of scripts. However, if you change the #Title tag, you must exit the current script list by pressing the Cancel button. If you are in Directory mode, returning to the parent directory will suffice.

Summary

  • Placing files in the script folder.
  • #TouhouDanmakufu recognized as the first line in a script.
  • script_enemy_main defines the behaviour of the enemy.

Next, we'll create an enemy.

Lesson 3: Hello, Enemy!

As a continuation from last lesson, we'll see an enemy this time.

Summary

  • Making an enemy with position, strength, collision detection, and a graphic.
  • Using @Initialize, @MainLoop, @DrawLoop and @Finalize for initialization and processing, rendering and post-processing.

Set up the enemy

Elements necessary for shoot-em-up games are twofold: the "enemy" and the "bullets". Let's make the easiest one, the enemy.

The minimum information you need to create an enemy are "position", "strength", and "collision detection". Now, how can you specify this, and where? As I said last time, script_enemy_main describes the behaviour of the enemy. However, you can't just directly write this information within the curly brackets {} which go with it. One first has to use the proper zones, for example, @Initialize and @MainLoop:

#TouhouDanmakufu
#Title [A script with an enemy]
#Text [A script with only an enemy]
#ScriptVersion [2]
script_enemy_main { @Initialize { SetX(GetCenterX); SetY(GetClipMinY + 120); SetLife(2000); }
@MainLoop { SetCollisionA(GetX, GetY, 24); SetCollisionB(GetX, GetY, 24); }
}

Note that the new code for the script is written with the red text.

The written statement @Initialize will be executed upon first running the script. Omitting the detailed description, we have SetX which sets the x-coordinate of the enemy, SetY which sets the y-coordinate and SetLife which specifies the strength of the enemy. Here, we've set x to be equal to the middle of the play area, and y to be 120 pixels down from the top of the play area.

The statement @MainLoop will be run once per frame, every frame. Collision detection is specified at each frame. SetCollisionA specifies where the bullets can hit, and SetCollisionB specifies where the player character can hit. Collision detection is specified using the center position and the radius; here, we simply get the x- and y-coordinates of the enemy, and set a radius of 24 pixels. This should be enough for now, and there's no need really to set the collision area any differently.

Let's run the script. You'll notice a couple of things. When running, at first glance, there appears to be no enemies on the screen. However, when you shoot a bullet, the bullet hits the seemingly empty space. Did you see the health meter on top of the screen? It decreases when you hit that space. In short, we have an enemy, but we still lack a graphic to display the enemy.

Seeing the enemy

#TouhouDanmakufu
#Title [A script with an enemy]
#Text [A script with only an enemy]
#ScriptVersion [2]
script_enemy_main { @Initialize { SetX(GetCenterX); SetY(GetClipMinY + 120); SetLife(2000);
LoadGraphic("script\img\ExRumia.png"); SetTexture("script\img\ExRumia.png"); SetGraphicRect(0, 0, 63, 63); }
@MainLoop { SetCollisionA(GetX, GetY, 24); SetCollisionB(GetX, GetY, 24); }
@DrawLoop { DrawGraphic(GetX, GetY); }
@Finalize { DeleteGraphic("script\img\ExRumia.png"); }
}

We have a number of new features. First, the graphics processing every frame goes inside the @DrawLoop statement. To render the graphic, we use the DrawGraphic() function, which takes the x- and y-coordinates of a position to draw an image. Touhou Danmakufu puts the center of the graphic at that position.

But that's not enough to draw a graphic. How do we draw a graphic? That's where SetTexture() comes into the picture. Starting from the folder which th_dnh.exe is located, we specify the location for a graphic file. We've just used the sample image which comes with Danmakufu, which is ExRumia.png, found in the img folder in the script folder.

Because the location has a lot of characters in it, we need to wrap it in double quotes "" - this is because it is a string. We use strings to distinguish between text and items with text labels. You'll notice that for example the #Title tag doesn't use strings but rather just a text item. Just think of it as an exceptional circumstance; most strings need to be wrapped in double quotes.

Also, SetTexture() by itself is useless. We need to tell the system which part of the image to draw, and this is where SetGraphicRect() comes in. This crops the image to the rectangle with the upper-left point the first two coordinates (in this case (0,0)), and the lower-right point the last two coordinates (in this case (63,63)). So, we have a square of size 64x64 pixels.

You might think that's all we need to do. However, SetTexture() needs to know that the graphic exists first before it can import it. So we can't actually load graphics using SetTexture().

This is why we need a function called LoadGraphic(). Because there is no need to run this many times, we simply put it in the @Initialize statement. For much the same reason, we need to use DeleteGraphic() in the @Finalize statement.

I'll spare you most of the intricate details; this is all you need to know for now.

Let's run the script. It's easy to see that EX Rumia has now appeared on screen; and you can shoot her, and you can kill her.

Summary

  • Making an enemy with position, strength, collision detection, and a graphic.
  • Using @Initialize, @MainLoop, @DrawLoop and @Finalize for initialization and processing, rendering and post-processing.

Next time, we will make a simple danmaku pattern.

Lesson 4: Hello, Bullets!

This time we're finally going to make the enemy shoot bullets.

Summary

  • Shooting bullets in @MainLoop
  • Creating bullets using CreateShot01 to shoot at you.

Firing bullets

A shooter has two necessary elements: the "enemy", and the "bullet". Let's make one, out of these "bullets".

The minimum necessary information in order to use the CreateShot01 function:

  • the x-position
  • the y-position
  • the number of bullets
  • the speed of the bullets
  • the type of bullet
  • shoot delay

The problem is, where do we put the function? We shouldn't put it in @Initialize, because we probably want this to happen more than once. I tend to put my shots in @MainLoop. Let's do it.

#TouhouDanmakufu
#Title [A script with an enemy]
#Text [A script with only an enemy]
#ScriptVersion [2]
script_enemy_main { @Initialize { SetX(GetCenterX); SetY(GetClipMinY + 120); SetLife(2000);
LoadGraphic("script\img\ExRumia.png"); SetTexture("script\img\ExRumia.png"); SetGraphicRect(0, 0, 63, 63); }
@MainLoop { SetCollisionA(GetX, GetY, 24); SetCollisionB(GetX, GetY, 24);
CreateShot01(GetX, GetY, 1, 90, WHITE01, 0); }
@DrawLoop { DrawGraphic(GetX, GetY); }
@Finalize { DeleteGraphic("script\img\ExRumia.png"); } }

It's clear to see that CreateShot01 has easily defined parameters. The x and y positions are just that, measured as before. Speed ranges from 1 pixel per frame.

Launch angle is easy to see from Figure 1 below, for example, 0 degrees for right, and 90 degrees for down. Naturally, using multiples of 360 was too much water under the bridge, so we just use limits of ±180 degrees.

Figure 1. Angle directions

The type of bullet specifies a couple of things, like the colour, and the type of bullet. Colours are RED, ORANGE, YELLOW, GREEN, AQUA, BLUE, PURPLE and WHITE. The numbers in Table 1 show the form of the bullet.

Table 1
01 01.png
02 02.png
03 03.png
04 04.png
05 05.png
11 11.png
12 12.png
21 21.png
22 22.png
23 23.png
31 31.png
32 32.png

CreateShot01's "Delay Frames" is the number of frames it takes for the bullet to actually be fired. We've set it to 0 here, so that means the bullet will be fired immediately.

However, if we try to run the program, it will be obvious that there is something wrong. One bullet is fired every frame. So, there are bullets being fired at a constant density. To prevent this, we'll need to fire one every few frames. However, in order to do this, we still need to know a couple more things. I'll explain this step by step in the following few lessons.

Summary

  • Shooting bullets in @MainLoop
  • Creating bullets using CreateShot01 to shoot at you.

Next time, we'll make some more modifications to this test script, and look closer at the syntax.

Lesson 5: Using Variables

This time, I'll be talking about variables.

Summary

  • Variables can store numeric values and text
  • Creating variables with the let command
  • A semicolon is required

Occasions for Variables

In the last programs, we passed the path (the file location) "script\img\ExRumia.png" directly into the functions LoadGraphic, DrawGraphic and DeleteGraphic. So what happens if I change the location of the file? Does it not break my program? Yes. We need to change every path to the new path. However, changing things in three places is always too much. You may forget to change one, or have a typing error in one. What are we going to do about this?

We can easily avoid this problem. We'll use something called a variable. A variable is a kind of container that can store any value. This means that we can save the path in the variable, and pass that to LoadGraphic, DrawGraphic and DeleteGraphic instead of the entire path.

#TouhouDanmakufu
#Title [A script with an enemy]
#Text [A script with only an enemy]
#ScriptVersion [2]
script_enemy_main { let imgBoss = "script\img\ExRumia.png"; @Initialize { SetX(GetCenterX); SetY(GetClipMinY + 120); SetLife(2000);
LoadGraphic(imgBoss); SetTexture(imgBoss); SetGraphicRect(0, 0, 63, 63); }
@MainLoop { SetCollisionA(GetX, GetY, 24); SetCollisionB(GetX, GetY, 24);
CreateShot01(GetX, GetY, 1, 90, WHITE01, 0); }
@DrawLoop { DrawGraphic(GetX, GetY); }
@Finalize { DeleteGraphic(imgBoss); } }

We've actually declared a variable. I'll stress it again:

let imgBoss = "script\img\ExRumia.png";

The variable here is imgBoss. To declare a variable, we use the let command. We want to declare path variables (like this one) in initialization time. To initialize, we write what we want the variable to be after the = sign.

When you declare a variable like this, you can use the saved value later. For example, we passed imgBoss to LoadGraphic, DrawGraphic and DeleteGraphic. Now, imgBoss is just the same thing that I saved in it; the string. But now if we want to change the path we only need to change it in one place. That's the important thing.

Variable Declaration

In general, to declare a variable, we'll write something like this:

let <variable name> [= value];

The bit in square brackets [ ] is optional in certain circumstances. In order for Danmakufu to know when the variable ends, we use a ; symbol to tell it.

Another thing: a variable cannot be used without a declaration. That is, if there's no let statement for the variable, you'll get an error. So, if you misspelled a variable name, you're likely to get an error for a variable that doesn't exist. The important thing is when you get an error, you should understand your mistakes. Fix your errors, and run as many times as possible, and you'll do just fine.

Summary

  • Variables can store numeric values and text
  • Creating variables with the let command
  • A semicolon is required

Next time, we'll talk about variables some more.

Lesson 6: Changing Variables

This time, I want to talk about changing the contents of a variable.

Summary

  • Variables can be assigned a different value later.
  • We can use += to change the value of the variable.

Changing Values

This time, let's finally change the launch angle for each frame of the bullets. It will become a little danmaku-ish. To make this change, we'll make it so that CreateShot01 isn't a constant. The way it is now, I've hard-coded 90 in for the value, so we can't alter it from this value.

Thus, the active variable. Variables can be left until the last moment to initialise; basically, as the name suggests, you can change the content held within. In other words, keeping the variable portion of the launch angle means we can change it every frame.

Let's get real.

#TouhouDanmakufu
#Title [Test Script]
#Text [Test Script]
#ScriptVersion [2]
script_enemy_main { let imgBoss = "script\img\ExRumia.png"; let angle = 90; @Initialize { SetX(GetCenterX); SetY(GetClipMinY + 120); SetLife(2000);
LoadGraphic(imgBoss); SetTexture(imgBoss); SetGraphicRect(0, 0, 63, 63); }
@MainLoop { SetCollisionA(GetX, GetY, 24); SetCollisionB(GetX, GetY, 24);
CreateShot01(GetX, GetY, 1, angle, WHITE01, 0); angle = angle + 10; }
@DrawLoop { DrawGraphic(GetX, GetY); }
@Finalize { DeleteGraphic(imgBoss); } }

So declaring the variable angle is a part, and I don't think passing that to CreateShot01 is a problem. Let's review, just in case. First of all, angle is declared and initialized:

let angle = 90;

This declares the variable's name as angle, and the value to 90. Then we pass it to CreateShot01:

CreateShot01(GetX, GetY, 1, angle, WHITE01, 0);

If you remember, the fourth argument specifies the launch angle. Here, when I write angle, it really means the value stored in angle, which we pass to CreateShot01. That way, when angle changes value, the value passed to CreateShot01 does too.

Lastly, the part that changes the variable:

angle = angle + 10;

Mathematically, this expression is very problematic. However, this isn't math, and the meaning of = is different. Here, it doesn't mean that the left side and right side are equal -- it means the value on the right side is assigned to the variable on the left. That is, the new value of angle is assigned to 10 more than its original value.

If you like, we can just write an expression which literally means "increased by 10":

angle += 10;

By using +=, we add 10 to the value already stored in the variable. Of course, we can also use -= too, if we want.

To sum up, when we run the script, we change the launch angle by 10 degrees per frame, from the position of the boss. Please check it by running the script -- there should be 36 bullets fired in one circle.

Summary

  • Variables can be assigned a different value later.
  • We can use += to change the value of the variable.

Next time, let's change the script, under certain conditions.

#TouhouDanmakufu
#Title [Test Script]
#Text [Test Script]
#ScriptVersion [2]
script_enemy_main { let imgBoss = "script\img\ExRumia.png"; let angle = 90; @Initialize { SetX(GetCenterX); SetY(GetClipMinY + 120); SetLife(2000);
LoadGraphic(imgBoss); SetTexture(imgBoss); SetGraphicRect(0, 0, 63, 63); }
@MainLoop { SetCollisionA(GetX, GetY, 24); SetCollisionB(GetX, GetY, 24);
CreateShot01(GetX, GetY, 1, angle, WHITE01, 0); angle = angle + 10; }
@DrawLoop { DrawGraphic(GetX, GetY); }
@Finalize { DeleteGraphic(imgBoss); } }

Lesson 7: That way? This way? Which way?

Summary

  • if and else can be used to modify the processing conditions.
  • When comparing for equality, == is used.

So let's go.

Running every few frames

Last time, we made our script a bit danmaku-ish. The problem is, we're firing a bullet every frame. We can do something about this though, and make sure that some frames aren't firing bullets. To accomplish this (firing one frame, and not firing the next), we need to know what to do. Like the firing angle, whether or not we want to process something can be changed, too.

In this way, we choose our process, and whether or not to do it, and it's a common process in making a danmaku. Things with such treatment are called conditionals. As the name suggests, it does not run under certain conditions.

But what can I do to change how I fire the bullets? Briefly, the process may be as follows:

1. Provide a variable to count the number of frames. 2. Increase the variable by one each frame. 3. When the variable reaches a certain number, we fire, and reset the variable to 0.

Let's get real.

#TouhouDanmakufu
#Title [Test Script]
#Text [Test Script]
#ScriptVersion [2]
script_enemy_main { let imgBoss = "script\img\ExRumia.png"; let angle = 90; let frame = 0; @Initialize { SetX(GetCenterX); SetY(GetClipMinY + 120); SetLife(2000);
LoadGraphic(imgBoss); SetTexture(imgBoss); SetGraphicRect(0, 0, 63, 63); }
@MainLoop { SetCollisionA(GetX, GetY, 24); SetCollisionB(GetX, GetY, 24);
frame++; if (frame == 5) { CreateShot01(GetX, GetY, 1, angle, WHITE01, 0); angle += 2; frame = 0; } }
@DrawLoop { DrawGraphic(GetX, GetY); }
@Finalize { DeleteGraphic(imgBoss); } }

This part requires a little added emphasis. Since we've changed the firing rate to one every 5 frames, we changed the angle by 2 degrees each time.

There are two notable things in the above code. The first one is the way we increased frame above:

frame++;

Using our previous knowledge, this is simply the same as

frame += 1;

But these things do the same, by a special operator called ++, which is more common. Of course, if we're reducing a variable, we'll use -- instead.

The most important thing is the part between the new braces:

if (frame == 5) { ... }

This is called a conditional branch. To perform a conditional branch, we use if, followed by ( ... ), which executes the bit inside the {} only if it is true.

We'll look at the case here,

frame == 5.

Whether or not frame is equal to 5 will determine whether we run the process or not. We already know that = is the operator for assignment, so the mathematical "is equal to", in Danmakufu language, is ==. So, to determine whether something is equal or not, use ==.

In conclusion, the bit inside {} will be executed every 5 frames. In other words, we're not firing one bullet per frame anymore.

Conditional Grammar

This is the way a conditional expression works:

if (<expression>) {
    <actions that are performed when <expression> is true>
} [ else {
    <actions that are performed when <expression> is false>
} ]

The common mathematical terms true and false are used here; "true" means the expression is correct, and "false" means it is wrong. In the example above, if the treatment for the condition is true only in some cases, you may want to do something if it's false. This is where we use else. Otherwise, it's optional.

However, if you want more conditional branches in the else, we can write:

if (<expression1>) {
    <actions that are performed when <expression1> is true>
} else if (<expression2>) {
    <when <expression1> is false, actions that are performed when <expression2> is true>
} else {
    <when both <expression1> and <expression2> are false>
}

Summary

  • if and else can be used to modify the processing conditions.
  • When comparing for equality, == is used.

Next time, I want to look at processing things similarly.

Lesson 8: Möbius Strip

Lesson 9: Automations

Lesson 10: Automated Computations

Lesson 11: Cutting the Gordian Knot

Lesson 12: One Hundred Metre Dash

Lesson 13: Tadpole to Frog

Lesson 14: Human Realm Sword "Delusion of Enlightenment -Easy-" (1)

Lesson 15: Human Realm Sword "Delusion of Enlightenment -Easy-" (2)

Lesson 16: Human Realm Sword "Delusion of Enlightenment -Easy-" (3)

Lesson 17: Human Realm Sword "Delusion of Enlightenment -Easy-" (4)

Lesson 18: Human Realm Sword "Delusion of Enlightenment -Easy-" (5)

Lesson 19: Wave Sign "Mind Shaker" Easy (1)

Lesson 20: Wave Sign "Mind Shaker" Easy (2)

Lesson 21: Wave Sign "Mind Shaker" Easy (3)

Lesson 22: Wave Sign "Mind Shaker" Easy (4)

Lesson 23: Ambition Sign "Buretsu Crisis" (1)

Lesson 24: Ambition Sign "Buretsu Crisis" (2)

Lesson 25: Ambition Sign "Buretsu Crisis" (3)

Lesson 26: Plural Linking