Touhou Wiki
Register
Advertisement


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.

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[In some countries its Y]) 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 speed of the bullets
  • the launch angle
  • 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

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
02 02
03 03
04 04
05 05
11 11
12 12
21 21
22 22
23 23
31 31
32 32

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[]

Lesson 7: That way? This way?[]

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[]


Advertisement