Danmakufu Basics Tutorial

From Danmakufu Wiki
Jump to: navigation, search

Return to Tutorials.

Basic Framework

All programming languages (to my knowledge) require some sort of framework for everything else to be stuck in and Danmakufu is no different. The following is the framework for Danmakufu. I encourage you to go ahead and create a folder in your script directory called tutorial and paste the code below in a new .txt file so that you have something to experiment with as you follow this tutorial. If the script ever gets screwed up and you have no idea how to fix it, just clear it and copy-paste it all over again.

#TouhouDanmakufu
#Title[Attack Name]
#Text[Attack Description]
#Image[.\img.png]
#BackGround[User(.\bg.png, 1, 1)]
#BGM[.\bgm.mp3]
#PlayLevel[Normal]
#Player[FREE]
#ScriptVersion[2]

script_enemy_main {
    @Initialize {
    }

    @MainLoop {
    }

    @DrawLoop {
    }

    @Finalize {
    }

    @BackGround {
    }
}

If you try running this script in Danmakufu, you'll notice that it does absolutely nothing. In fact, the boss should die as soon as it is created because there is no function setting its life value. For now, however, let's mess with some of the values in the framework to give you an idea as to what they actually do.

#TouhouDanmakufu

This tells Danmakufu that the file is a Danmakufu script. Specifically, it is telling Danmakufu that it is a single attack for a boss. If you delete this line, you'll see that the program will no longer have it on the list as a executable script, even if it's the most awesome attack in the world. However, not everything you create with this program will be a single attack. With #TouhouDanmakufu[Plural], you are telling Danmakufu that this script is a boss with multiple attacks. With #TouhouDanmakufu[Stage], you are telling it that the script is a stage. And with #TouhouDanmakufu[Player], you are telling it that the script is a playable character. For now, we will just be using #TouhouDanmakufu

#Title

Quite simply enough, this tells Danmakufu what to display as a name for this particular script when it is listed in the program as an executable script. If you put #Title[Lol], then it will be displayed as "Lol". If you put #Title[Awesome Attack of Doom], it will be displayed as "Awesome Attack of Doom". Go ahead and change the text in the brackets and check in the program to see what I'm talking about. (Be sure to save the .txt file every time you want to run the script so that Danmakufu runs the correct version. Otherwise, it will run the version from the last save and no changes will have appeared.)

#Text

This is similar to #Title. Instead of affecting the title, however, it affects the description of the script when the title is highlighted. Also, this directive is optional. You can delete this line and no description will show up.

#Image

This determines the preview image that is shown when the title is highlighted in Danmakufu. Danmakufu handles most of the common image types such as .jpg, .gif, and .png. To choose the image that you want displayed, you will need to know where the image is. For now, copy a small image into your tutorial folder you made earlier and rename it as "image.jpg" or whatever the extension is. Now, when you put #Image[.\image.jpg] (or whatever the extension is) and save the script, the image should show up when you select the script in Danmakufu. Examining the text, .\image.jpg, you may be able to tell that the period in the beginning means the directory that the script is currently in. If the image is not in the folder, but say in a folder labeled images which is in the tutorial folder, then you will need to put .\images\image.jpg in the brackets in order for Danmakufu to find the image. If the image is in the folder that the tutorial folder is in, then you use .\..\images\image.jpg where the double period means the directory that the current script's directory is located in.

This is also optional and can be deleted without affecting the executability of the script.

#BackGround

This is rarely ever used because @Background is much more flexible, but more on that later. #Background is used to either generate a tiled scrolling background with an image you provide, or a default background built into the system. First, the default backgrounds. To my knowledge there are only two, Default and IceMountain. If you replace the text in the brackets for this directive, you'll be able to see what they look like. If you would like to use an image to make a tiled background, you will need to use User(imagedirectory, x, y) where imagedirectory is the location of the image you want as explained in the last section, x is the speed at which the background will scroll from left to right, and y is the speed at which the background will scroll from top to bottom. Let's use the image that we used in #Image so assuming the image is still right next to the script file, the directory is .\image.jpg. Let's also put 1 for x and y. The directive should now look like this: #Background[User(.\image.jpg, 1, 1)]. If done correctly, when you run the script now, there should be a tile background made from the image that is scrolling diagonally. You can mess with the numbers for x and y to see how it affects the scrolling of the background and keep in mind that negatives and decimals are supported.

This is also optional and can be deleted without affecting the executability of the script.

#BGM

This tells Danmakufu what music to play. The music is selected in the same way as the images so let's bring in an mp3 into the tutorial folder. See if you can make Danmakufu run the music without me having to tell you exactly what to put in the brackets.

This is also optional and can be deleted without affecting the executability of the script.

#PlayLevel

PlayerLevel adds text to the right side of the screen. It's basically there for identifying whether or not the script will display "Lunatic" or "Easy" on the side of the screen. It's not really important, so most people leave it out, but if you plan on making several difficulties of your script, you can indicate a difficulty level with this function.

#Player

This tells the program what playable characters are allowed to be used in the script. To choose which characters are allowed, you must know where the script for the characters are. In most cases, they will be in the player folder of Danmakufu, like the Rumia playable character that is packaged with the program. To refer to that directory, you should use player\Rumia\Rumia.txt. As you may already know, there are also four playable chars already built into Danmakufu: ReimuA, ReimuB, MarisaA, and MarisaB. Obviously, there is no directories in which these are stored so instead, you should refer to them as REIMUA, REIMUB, MARISAA, and MARISAB. The caps are required. Also, you can kill two birds with one stone and refer to them as REIMU (to add both ReimuA and ReimuB to the playable characters list) and MARISA (to add both MarisaA and MarisaB to the playable characters list). Finally, if you just put FREE, all characters in the player folder as well as the four built in characters will be available for use. Here's an example where ReimuB, MarisaA, MarisaB, and Rumia is allowed: #Player[REIMUB, MARISA, player\Rumia\Rumia.txt].

This is optional and, if deleted, will default to FREE.

#ScriptVersion

I'm not sure what this does, but always have it and do not change it.

script_enemy_main

This indicates the beginning of the script that dictates the behavior of the enemy. Before anything else, you should declare variables and create the functions that you will be using most often. I'll teach you how to do that later.

@Initialize

This is where you should put stuff that should only be run once in the beginning of the script such as setting the boss's life, score, spellcard name, timer, etc. I'll teach you how to do that later.

@MainLoop

This is where you should put how you want the enemy to behave such as spawning bullets or moving around. I'll teach you how to do that later.

@DrawLoop

This is where you should put how you want things drawn. Please refer to Nuclear Cheese's Danmakufu Drawing Tutorial for more information on how to use this.

@Finalize

This is where you should put stuff that should happen only once at the end, such as dropping point items or deleting the boss's sprite.

@Background

This is where you control the behavior of the background. It replaces whatever commands you gave it in #Background if any and anything drawn in this is drawn behind anything drawn in @DrawLoop. Please refer to Nuclear Cheese's Danmakufu Drawing Tutorial for more information on how to use this.

Other notes

See all those braces (these things: "{" and "}")? Always make sure those are there. They tell the script when each section begins and ends. If you have an unequal amount or if they are placed inappropriately, Danmakufu will raise an error.

In fact, Danmakufu will probably raise an error more often than not when you are trying to create something. If you can't deal with figuring out what tiny little thing you did wrong every single time, you definitely should not be learning how to use this program. Or any programming language for that matter.


Variables and Control Statements

Variables and control statements are invaluable tools to creating anything in Danmakufu. If there were no such thing, than this program would be completely useless.

Variables

Variables store information for later use. They can be used to store numbers (e.g. 14, 2398.37, -583) , strings ("Hello, world!", "2957 is a number", ":V"), and booleans (true, false). To create a variable, all you have to do is put let test; where "test" is the name of the variable. This basically translates to "Create a variable called 'test.'" Make sure the semi-colon (";") is there because it signifies the end of a statement. When creating a variable, you can also give it a value immediately on the same line: let test = 500;. This basically translates to "Create a variable called 'test' that has an initial value of 500." Again, make sure the semi-colon is there because Danmakufu is very picky and will raise an error if it doesn't know where the end of a statement is. Make sure that when creating variables, you never give two variables the same name or an error will be raised and the script will be terminated when run.

To change the value of a variable, all you have to do is put test = 501;. This will mean "Set the variable called 'test' to equal 501." You can also adjust the value of the variable using math like this: test = 14*37-199/34+13;. Also, you can adjust the value of a variable based off of it's current value. For example, if the current value of test is 20 and you put test = test + 5;, then the value of test would become 25. There is a shorter way of writing that however. test += 5; means the exact same thing as test = test + 5;. Similarly, test -= 5; is the same as test = test - 5;, test *= 5; is the same as test = test * 5;, and test /= 5; is the same as test = test / 5;.

You can also use other variables when changing the value of a different variable. For example, if there were another variable called test2 with a value of 18, then the line test = test2 + 34; would cause test to assume the value of 52 while test2 would stay as 18.

Arrays are special types of variables that store multiple values under the same name. They are created in the same way, but are defined as an array when you set them in a specific way after the equal sign. Here is a line that creates an array: let array = [4, 1, 6, 3, 5, 2];. As you can see, it is now storing six values under the same name. To call a specific value, you put the array's name, open bracket, a number, close bracket. For example, if after creating the array, I were to write a line that said let something = array[4];, the newly created variable, something, would have a value of 5 because that was the 4th value in the array (the 1st value to us is the 0th value to the computer). Changing the value in an array is also similar. array[0] = 1; would cause the array to now be [1, 1, 6, 3, 5, 2].

The size of an array is constant so after making an array, you must make sure you never call something that goes past the limit of the array. For example, array[6] = 9; would cause an error to occur because there is no 6th value to change in the array that I had created. There are a few operations that work with arrays.

Arithmetic: The 1st element is operated on with the 1st element, the 2nd with the 2nd, etc. This only works if the arrays are the same length

[2, 4, 7] + [1, 5, 2] = [3, 9, 9]
[2, 4, 7] - [1, 5, 2] = [1, -1, 5]
[2, 4, 7] * [1, 5, 2] = [2, 20, 14]
[2, 4, 7] / [1, 5, 2] = [2, 0.8, 3.5]

You can also combine arrays or add values.

[2, 4, 7] ~ [1, 5, 2] = [2, 4, 7, 1, 5, 2]
[2, 4, 7] ~ [1, 5] = [2, 4, 7, 1, 5]
[2, 4, 7] ~ [1] = [2, 4, 7, 1]

Or delete values.

erase([2, 4, 7], 0) = [4, 7]
erase([2, 4, 7], 1) = [2, 7]
erase([2, 4, 7], 2) = [2, 4]

Control Statements

Control Statements direct the flow of script. It determines when things should happen such as when bullets should be fired, when the boss should move, or when a special effect should be created. Without these, everything would happen once per frame, or 60 times a second, regardless of any conditions.

There are many different types of control statements that help you direct the flow but before we talk about them, you will first need to know a few boolean operators, or comparisons:

==: This means "is equal to". 12==12 is true. 28==2 is false. 8==14 is false.
!=: This means "is not equal to". 12!=12 is false. 28!=2 is true. 8!=14 is true.
>: This means "is greater than". 12>12 is false. 28>2 is true. 8>14 is false.
<: This means "is less than". 12<12 is false. 28<2 is false. 8<14 is true.
>=: This means "is greater than or equal to". 12>=12 is true. 28>=2 is true. 8>=14 is false.
<=: This means "is less than or equal to". 12<=12 is true. 28<=2 is false. 8<=14 is true.

&&: This means "and". If two true statements are joined with && as a single statement, then that statement is true. Otherwise, it is false.
||: This means "or". If two false statements are joined with || as a single statement, then that statement is false. Otherwise, it is true.

And now for the actual control statements. The most common one is the if statement, which is often followed up with else though it depends on what you are trying to accomplish. The if statement works the same way as the word "if" in real life. For example, the English sentence, "If you sleep, you dream" means that you will dream if and only if you are sleeping. Likewise, the Danmakufu statement, if(PowerLevel > 9000){VegetaScreams = "It's over nine thousand!";} means that the variable VegetaScreams will store the string, "It's over nine thousand!" if and only if the variable PowerLevel is greater than 9000.

Basically, the structure of an if statement is if(requirements){stuff happens}. However, it's actually much better to organize it like this:

if(requirements){
   something happens;
   something happens;
   something happens;
}

This way, if multiple things are happening as in this instance, it is much easier to see what is going on. Note that if the requirements are not fulfilled, then Danmakufu will skip over the stuff that happens in between the braces, which should make sense. If you put else{stuff happens} after the closing brace of an if statement, it will skip to that area instead.

if(requirements){
   something happens;
   something happens;
   something happens;
}else{
   something else happens;
   something else happens;
   something else happens;
}

So in this case, if the requirements are fulfilled, something will happen three times. If the requirements are not fulfilled, something else will happen three times. Note that in this set up, something and something else cannot happen at the same time. It is either one or the other.

There is a variation of the if statement called alternative. It is used when there are more than two outcomes you want. Here is an example:

alternative(variable)
case(1){
   x+=5;
}
case(2){
   x-=5;
}
case(3){
   x+=2;
}
others{
   x=0;
}

What this snippet of code basically means is "Look at the variable called 'variable'. If it's equal to 1, then add 5 to the variable 'x'. If it's equal to 2, then subtract 5 from the variable 'x'. If it's equal to 3, then add 2 to the variable 'x'. If it's anything else, then make the variable 'x' store the value of 0." As you can see, alternative is basically just a shorthand way of writing a looooooooooong if statement.

Another common control statement is loop. This is much easier to understand and use than if and else. Suppose you wanted something to occur multiple times, but you didn't want to take up space by writing the same thing over and over again. This is where the loop statement comes in. The structure of loop goes like this:

loop(20){
   something happens;
}

The number in the parentheses tell Danmakufu how many times to run what's in the braces. In this set up, something will happen 20 times. loop is also known as time. If you do not put a number in, then you will create an infinite loop and will have to end task Danmakufu unless break; is used. break immediately exits out to just past the next close braces ("}").

while statements are rather useful as well, but they may sometimes cause infinite loops that will force you to end task Danmakufu.

while(variable<10){
    something happens;
}

In this example, something will continue to happen until the variable variable is no longer less than 10. If it's already not less than 10, then something will not happen when the program reaches this part of the script. Note that the program will not move past this part until variable no longer is less than 10 so in this case, an infinite loop has been created. To counteract this, you can put something in the braces that changes the value of variable in a way that will eventually cause it to become greater than or equal to 10. break can also be used to exit out of the infinite loop.

Finally, there are the ascent and descent statements which are similar to loop. The difference is, they involve a variable as well that increases or decreases by 1 every loop until it reaches the limit that you set. The variable you use for this statement does not have to be created with let.

ascent(i in 0..5) {
   array[i] = i;
}
descent(i in 0..5) {
   array[i] = i;
}

In these two cases, the variable used is i and the numbers go from 0 to 5. Those two snippets of code are equivalent to the following two snippets of code respectively.

array[0] = 0;
array[1] = 1;
array[2] = 2;
array[3] = 3;
array[4] = 4;
array[4] = 4;
array[3] = 3;
array[2] = 2;
array[1] = 1;
array[0] = 0;

Other notes: When creating variables in control statements, something happens to where the variable can be used.

let variable1 = 0;
loop(5){
   let variable2 = variable1;
   variable2 += 10;
}
variable1 = variable2;

In this example, an error will be raised. Why? It has to do with the scope in which variables can be used. In the loop, variable1 is called, something perfectly fine, but once the loop is exited, variable2 is called, something that is not allowed. This is because every time Danmakufu reaches the end of the loop, variable2 ceases to exist. This also explains why 5 variables of the same name can seemingly exist (which is actually not really the case). Basically, variables in the inside cannot be accessed by things outside, but variables in the outside can be accessed by things inside.


Initializing a Boss Fight

Now, we finally get to the part where you learn how to make the boss do something. Well, sorta. You get to learn how to set a boss's life and stuff like that. But hey! That's important too!

The following script has a bunch of stuff in it's @Initialize, most of which will probably never be actually in it at the same time in an actual script and a few variables to store a couple values to make information easier to keep track of. It also has some things in the @DrawLoop and has @Background just omitted completely (@Background is actually optional) which you should learn from Nuclear Cheese's Tutorial sometime in the near future.

#TouhouDanmakufu
#Title[Boss Initialize]
#Text[Boss Initialization with almost everything you could possibly need stuck in @Initialize]
#Player[FREE]
#ScriptVersion[2]

script_enemy_main {
    let BossImage = "script\img\ExRumia.png";
    let BossCutIn = "script\img\ExRumia.png";
    @Initialize {
        LoadGraphic(BossImage);
        SetLife(100);
        SetDamageRate(10, 10);
        SetTimer(50);
        SetInvincibility(30);
        CutIn(YOUMU, "Spellcard Name", BossCutIn, 0, 0, 200, 600);
        SetScore(500000);
        SetEnemyMarker(true);
        SetDurableSpellCard;
        LastSpell;
        Concentration01(60);
        Concentration02(60);
        MagicCircle(false);
        SetEffectForZeroLife(180, 100, 1);
    }

    @MainLoop {
    }

    @DrawLoop {
        SetTexture(BossImage);
        DrawGraphic(GetX, GetY);
       
    }

    @Finalize {
    }
}

Quite a bit of stuff that can go into @Initialize and I just showed some of the more common ones. If you really wanted to, you could also make the boss move to a specific spot when it is spawned, shoot some bullets or lasers, or spawn familiars when it starts up. For now, let's stick with what I stuck in there right now.

LoadGraphic

This function loads the image that is referred to by the directory path in the parentheses, getting it ready to be drawn in the @DrawLoop. More about this in Nuclear Cheese's Tutorial.

SetLife

The cool thing about Danmakufu is that all the functions are named very well. If you take a look at the function name, you should be able to tell, in general, what it does. Accordingly, SetLife, quite obviously, sets the life of the boss. All you have to do is put a numerical value in the parentheses. The higher the number, the longer the boss will stay alive. If you don't set a life in an attack, the boss will automatically die when it spawns. Never set the value in the parentheses as a variable. The script will work, but you will have issues later when stringing attacks together to make a boss.

SetDamageRate

This function determines what percentage of the player's firepower actually gets converted to damage. The first number is the percentage that a shot's damage will deal while the second is the percentage that a bomb's damage will deal. For example, as it is right now, SetDamageRate(10, 10);, if a player's shot normally deals 10 damage per hit and a player's bomb normally deals 100 damage, the shot will actually only deal 1 damage to the boss and the bomb will only deal 10 damage. If this function is not called, it will default as SetDamageRate(100, 100);.

SetTimer

Another somewhat obvious one, SetTimer sets the timer in seconds for the attack to run out. If you don't know, the timer is in the top right corner right below the life bar of the boss when you run the script. If you don't set a timer, the timer will stick at 99 and the attack will not run out until the boss's life hits 0. Also, you can set the timer to be higher than 99, but the timer will display 99 until it can finally correctly display a two digit number.

SetInvincibility

This function determines how much invincibility time the boss will have in frames (1 second = 60 frames). Usually it's called in order to prevent bombs that finished off a previous attack from damaging the next life bar. If you don't set this function, it defaults to 0 frames of invincibility.

CutIn

This function displays the name of the attack as well as a cut in of the boss that you have to provide. This is the most complicated function you've seen thus far with 7 parameters to fill out. Let's go in order. The first parameter is the style in which you want Danmakufu to display the cut in. YOUMU refers to Touhou Youyoumu, Perfect Cherry Blossom, while KOUMA refers to Touhou Koumakyou, Embodiment of Scarlet Devil. At the time of this writing, those two are the only available options for the first parameter. The second parameter is the spellcard name in quotes. Something like Power Sign "Chaotic Spin" or something like that. However, you may notice that if you try to put quotes inside the quotes, it will confuse Danmakufu (and therefore raise an error). To unconfuse it (and confuse yourself), write it like this: CutIn(YOUMU, "Power Sign "\""Chaotic Spin"\", BossCutIn, 0, 0, 200, 600);.

The last five parameters are what this function is really supposed to be for (I usually just use it for the spellcard name). The first of the last five is the directory of the image you want to use for the cut in, in this case stored by the variable BossCutIn. You should know how to point to the correct image by now. The last four determine the four sides of the image (left, top, right, and bottom in order) that you referred to in the last parameter. Generally, if your cut-in image is just the image you want as opposed to a sprite sheet, the first two numbers (left and top) will be 0, the third number (right) will be the width of the image in pixels, and the last number (bottom) will be the height.

SetScore

This function sets the base spellcard bonus that is rewarded upon a spellcard's defeat with no bombs or deaths. If the function is not called, then a bonus will not be rewarded regardless of how well the player does.

SetEnemyMarker

This function determines whether the red enemy indicator at the bottom of the screen will show up or not. It will show up if the value in the parentheses is true, and will not show up if the value is false. If the function is not called, then the enemy indicator may or may not show up depending on if SetScore was called. If SetScore was called, then the indicator will show up. Otherwise, it will not.

SetDurableSpellCard

This function, when called will cause the spellcard bonus to be collected even if the timer ran out and the player did not deplete the boss's health bar. If it isn't called, then a timed out spellcard will not give a spellcard bonus. This does not have an effect on the spellcard bonus in regard to deaths or bombs.

LastSpell

This function, when called will disable bombs for this particular attack. If it is not called, bombs will be allowed to be used. Also, when the player is hit, the attack will end immediately and the player will not lose a life.

Concentration01

This function creates a special effect over the boss that makes it look like it's charging energy. The value in the parentheses determines how long the boss will charge.

Concentration02

This function works exactly like the above function but the special effect is that of charging icy energy (i.e. snowflakes).

MagicCircle

This function determines if the rotating magic circle is drawn. If true, it will be drawn. If false, it will not be. If this function is omitted, it will default to true.

SetEffectForZeroLife

This function determines the effect that happens when the boss's life reaches 0. The first parameter determines how many frames the boss and the effect will linger. The second determines the intensity of the motion blur of everything being drawn with 255 being maximum intensity. And the last value determines how much slow down will occur as the boss dies. The higher the number, the more the slow down. 0 is no slowdown and 1 is half speed. If this function is not called, then no extra effect will occur.

Other Notes

Hopefully by now, you realize how the built in functions of Danmakufu work. There's the name of the function (which is case sensitive by the way), usually some parentheses, and some values in the parentheses that determine the specifics of the function. By now you can probably look in the Touhou wiki's Danmakufu section and understand what is meant by the list of functions.

Firing and Controlling Bullets

Okay, now for the part of the tutorial that you probably wanted to get to much earlier (or skipped to without reading anything else). Anyway, first of all, make a .txt file with the following code in it:

#TouhouDanmakufu
#Title[how do I shot bullets?]
#Text[lol i dunno]
#Player[FREE]
#ScriptVersion[2]

script_enemy_main {
    let frame = 0;
    @Initialize {
        SetLife(1000);
        SetEnemyMarker(true);
    }

    @MainLoop {
        SetCollisionA(GetX, GetY, 32);
        SetCollisionB(GetX, GetY, 24);
        if(frame==60){
            CreateShot01(GetX, GetY, 3, GetAngleToPlayer, RED01, 10);
            frame = 0;
        }
        frame++;
    }

    @DrawLoop {       
    }

    @Finalize {
    }
}

This is a very simple script to make the boss, which now has a hitbox to be shot at, to fire a standard red bullet at the player every second. Let's examine the flow of the program as it reads the script.

First, it creates a variable called frame and stores the value of 0 in it. It then goes into @Initialize where the boss's life is set to 1000, and the enemy indicator is enabled. Finally, it goes into the @MainLoop which will continue to run once per frame. For the first 60 run-throughs, nothing will happen except the commands SetCollisionA(GetX, GetY, 32);, SetCollisionB(GetX, GetY, 24);, and frame++;. SetCollisionA sets the boss's collision to player bullets so that the player can deal damage. The first parameter is the x-coordinate of the center of the collision circle, the second the y-coordinate, and the third the radius of the collision circle. As you may have noticed, GetX and GetY were used in place of numbers for the first two parameters. These are functions that find the current x-coordinate and y-coordinate, respectively, of the boss. SetCollisionB is used the same way as SetCollisionsA, but the type of collision it detects is boss to player character collision, which would cause the player to lose a life. Finally, frame++ is equivalent to frame += 1 or frame = frame + 1. As you can see, after the first 60 runs through the @MainLoop the variable, frame, will finally equal 60 and the flow of the script will go into the if statement for the first time.

In the if statement, the function CreateShot01 is called, which is set to fire a red bullet aimed directly at the player. This is the most basic command for firing a bullet and it, along with many others, will be explained later on in this section. After firing the shot, the variable frame is set to 0 again, causing the @MainLoop to do basically nothing again for 60 frames before the flow enters if statement once again. This will continue infinitely until the player depletes the enemy's life.

Hopefully, now you know how control statements and variables will help you control what happens when and how often. With just this simple set up, the timing of all the bullets you want shot can be controlled with the frame variable and one or more control statements.


Bullets Graphics

In the CreateShot01 function that I called in the above example, the parameter RED01 tells Danmakufu to use a small red bullet for that shot. A full list of all the bullet graphics built into the Danmakufu as well as example graphics can be found in the Touhou wiki here. Go ahead and experiment with them by replacing RED01 with other values such as GREEN05 or WHITE22.

Bullet Control

Danmakufu gives you many different ways to fire bullets which vary in the amount of control you have over the bullet as well as how much processing power the bullet requires. Let's go in order of most basic and least control to most complex and most control.

CreateShot01

This is the easiest function to use. The parameters that it asks for, in order, are starting x-coordinate, starting y-coordinate, speed, angle, graphic, and delay. In the x/y coordinate plane of Danmakufu, (0, 0) is the top left corner. The greater the x coordinate, the farther right the point goes and the greater the y coordinate, the farther down the point goes. Speed is measured in pixels per frame and can be a decimal and negative. For angle, 0 is straight right while 90 is straight down. In the example above, I used GetAngleToPlayer, a function that gets the current angle from the boss to the player. Note that it does NOT get the current angle from the spawning point of the bullet to the player so the bullet will only aim at the player if it is spawned directly on top of the boss (and thus at the point (GetX, GetY)). Graphics have already been explained in the previous section. And finally delay is the amount time in frames that the bullet is postponed before being fired. Before it is fired, its position will be displayed as a glow of the same color of the bullet. For a better idea of what I mean, change the value in the example to something much greater or much lower to see a difference.

CreateShot11

This is about as easy to use as CreateShot01. The only difference is that to choose direction, it doesn't use angle and velocity as its 3rd and 4th parameters. Instead it uses two velocities, horizontal and vertical, as the 3rd and 4th parameters. A positive horizontal velocity will cause to bullet to move right while a negative horizontal velocity will cause the bullet to move left. A positive vertical velocity will cause the bullet to move downward while a negative vertical velocity will cause it to move upward.

CreateShot02

This is similar to CreateShot01 except that it has two extra parameters: acceleration and max/min velocity. The order of parameters is x-coordinate, y-coordinate, starting velocity, angle, acceleration, max/min velocity, graphics, and delay. acceleration is measured in pixels per frame per frame and can be negative or positive, decimal or integer. If the acceleration is positive and the starting velocity is already greater than the max velocity, then the bullet will automatically start moving at the max velocity. Same for if the acceleration is negative and the starting velocity is less than the min velocity.

CreateShot12

This is similar to CreateShot02 except that is has four extra parameters: x-acceleration, y-acceleration, max/min x velocity, and max/min y velocity. The order of parameters is x-coordinate, y-coordinate, starting x velocity, starting y velocity, x acceleration, y acceleration, max/min x velocity, max/min y velocity, graphics, and delay.

CreateShotA

This is much more complicated than any of the previous bullet creation functions. This one actually needs to be coupled with two to five other functions in order for it to work. The related functions are SetShotDataA, SetShotDataA_XY, SetShotKillTime, FireShot, and AddShot. First I'm going to explain what each of these functions do and then show examples on how they work in conjunction to control a bullet.

CreateShotA initializes one of these types of bullets and makes you give the bullet a number identity for you to refer to it later in the other functions. It also asks for the starting position and delay time for the bullet. For example, CreateShotA(1, GetX, GetY, 10); would create a bullet with an ID number of 1, starting at point (GetX, GetY), with a delay of 10.

SetShotDataA gives detailed instructions to the bullet on how to behave. Here, after identifying which shot you want to control via the ID number, you can control speed, angle, angular velocity, acceleration, max/min speed, and graphic. Angular velocity is a new parameter unique to CreateShotA bullets that changes the angle of the bullet every frame by the value indicated. The cool thing about these shots is that SetShotDataA also has one more parameter that determines the time after shooting that the settings for speed, angle, angular velocity, etc. actually affect the bullet. This means that if you call this function once to set the behavior at frame 0, you can call it again to set the behavior at frame 60, or 83, or any other frame after 0. There is no limit to how many times SetShotDataA can be called for one bullet either, which gives you incredible control over the bullet. For now, as a simple example, SetShotDataA(1, 0, 5, GetAngleToPlayer, 0.1, -0.08, 2, RED01); would tell the bullet with the ID number 1 to, at the time 0 frames after being shot (immediately), to go at a speed of 5 pixels per frame, straight at the player initially but turning 0.1 degrees every frame, decelerating at 0.08 pixels per frame per frame until it reaches a speed of 2 pixels per frame, with the bullet graphic of a small red bullet. If you would like to see an example with multiple SetShotDataA functions, then scroll down a bit.

SetShotDataA_XY works the same way as normal SetShotDataA except that instead of controlling angle directly, you control it with x- and y-velocities and accelerations, much like CreateShot12. For example, SetShotDataA_XY(1, 30, 1, -2.5, 0, 0.1, 1, 5, RED01); tells the bullet with ID 1 to, at 30 frames after being shot, move with a horizontal velocity of 1 to the right and with a vertical velocity of 2.5 upwards, with no horizontal acceleration and a vertical acceleration downward with a max speed of 5 pixels per frame, and with a bullet graphic of a small red bullet.

SetShotKillTime tells the bullet when to delete itself. If it isn't called, the bullet will delete itself after moving out of the playing field like a normal bullet. If it doesn't have this function called AND doesn't move off the playing field, it will never be deleted. This function is easy to use, as it only has two parameters: bullet ID and time. For example, SetShotKillTime(1, 120); tells the bullet with ID of 1 to get deleted after 120 frames.

FireShot tells the bullet to actually be fired after all the all the settings are configured. Yes, that means that you define the behavior of the bullet before it actually ever enters the playing field so all SetShotDataA functions and the SetShotKillTime function if desired must be called before FireShot is called. As an example, FireShot(1); fires the bullet with ID of 1 and all functions referring to that bullet will no longer work. In fact, now that the bullet has been fired, the ID of 1 is freed up and CreateShotA(1, GetX, GetY, 10); can be called again without raising an error.

AddShot is similar to FireShot in that the bullet it refers to must be configured before it is added and the ID is freed up after it is added. The difference is that AddShot is used to fire a shot from another CreateShotA. The shot must be added to the target bullet before that bullet is fired as well, which makes this feature somewhat confusing to some people. One way to think of this is that you're building a bullet rather than controlling it frame by frame. Anyway, for the sake of this example, pretend I had created a bullet with an ID of 2 and configured it already. AddShot(60, 1, 2, 0); would cause the bullet with ID of 1 to fire the bullet with ID of 2 from its current position 60 frames after bullet ID 1 had been fired. Note that the extra parameter is completely useless at the moment and only has use when the firing shot is actually a laser.

And now, for an example that uses all of these functions to create some relatively complicated movement:

CreateShotA(1, GetX, GetY, 10);
SetShotDataA(1, 0, 5, 0, 2.2, 0, 5, RED03);
SetShotDataA(1, 60, 5, 132, 0, 0.1, 8, RED03);
ascent(i in 1..60){
    CreateShotA(2, 0, 0, 30);
    SetShotDataA_XY(2, 0, rand(-1, 1), rand(-1, -4), 0, 0.1, 0, 3, RED01);
    AddShot(i*2, 1, 2, 0);
}
FireShot(1);

If you place this code in the if statement in the beginning of this section of the tutorial, you can see that the boss will fire a bubble shot that curves initially but levels off and head into a corner, leaving a trail of falling bullets along they way. Using the knowledge of everything I taught you so far, can you figure out how each part of this snippet of code corresponds to the behavior of the bullets?

Other Notes

There are such things even more complicated than ShotA bullets which are literally manipulated frame by frame and require a lot of processing power for the computer. However, objects cover such a wide range of features in Danmakufu that it deserves its own tutorial, which may or may not be written by me later on. For now, with these tools and a bit of creativity, you should be able to make good bullet patterns already.

Firing and Controlling Lasers

So by now, you should know how the functions work in Danmakufu as well as what I mean by certain terms. Hopefully this will save time when I try to explain things...

* Blargel is just talking to himself.

Lasers are fired quite similarly to bullets. Except for the more complicated ones, they behave almost exactly like elongated bullets. They even use the same bullet graphics by stretching them to fit the lengths and widths that you specify. There are four types of laser available in Danmakufu: CreateLaser01, CreateLaserA, CreateLaserB, and CreateLaserC. That's right: there's only one type of laser that is relatively simple to control.

CreateLaser01

This creates a laser that shoots at a specified angle at a specified speed. Once the laser exits the screen, it is deleted. It's like the lasers in Cirno's second non-spellcard attack in Embodiment of Scarlet Devil. The parameters for it are starting x-coordinate, starting y-coordinate, speed, angle, length, width, graphic, and delay. 8 parameters total. CreateLaser01(GetX, GetY, 2, GetAngleToPlayer, 200, 16, RED01, 20); would create a laser that starts at the boss's position, moves at 2 pixels per frame at the player, is 200 pixels in length and 16 pixels in width, is drawn with the small red bullet graphic, and is delayed for 20 frames.

CreateLaserA

Now for the fun complicated lasers. This laser is the type that, when delayed, has a thin line that shows where the laser will show up. It has no speed and instead covers the whole line. Think of Keine's Last Spell in Imperishable Night for an example. If you understood how CreateShotA worked, then this should be easy to understand as well. CreateLaserA is controlled by SetLaserDataA, SetShotKillTime, FireShot, and AddShot.

CreateLaserA has 7 parameters: ID number, starting x-coordinate, starting y-coordinate, length, width, graphic, and delay. For example, if you write CreateLaserA(1, GetX, GetY, 400, 20, YELLOW01, 60); you will initialize a laser with an ID of 1, whose base is positioned at the boss's position, with a length of 400 pixels and width of 20 pixels, with bullet graphic of a small yellow bullet, and a delay of 60 frames.

SetLaserDataA also has 7 parameters: ID number, time after creation to affect the laser, firing angle, firing angular velocity, change in length, moving speed of the base, and moving angle of the base. For example, SetLaserDataA(1, 0, 0, 1, -1, 2, 180); will cause the laser with ID of 1 to, 0 frames after its creation (immediately), aim straight right but turn at 1 degree per frame, shrink by one pixel per frame, and move to the left at 2 pixels per frame. Note that the second parameter measures frames from creation instead of frames from firing. This means that if you create a laser with a delay of 60, and tell it with this function to move, it will actually start moving even while it is delayed.

The other three functions have already been explained in the previous section and stringing them all together as a whole has also already been demonstrated there too. Therefore, I'm going to move on to CreateLaserB.

CreateLaserB

This laser works almost the same as CreateLaserA, but the position of the base is controlled a little differently. The position of the base is determined by the position of the boss, so in a way, they are installed on the boss. When delayed, has a thin line that shows where the laser will show up. Think of Flandre's Laevantein in Embodiment of Scarlet Devil for an example. CreateLaserB is controlled by SetLaserDataB, SetShotKillTime, FireShot, and AddShot.

CreateLaserB has five parameters: ID number, length, width, graphic, and delay. CreateLaserB(2, 300, 32, RED01, 120); will initialize a laser with an ID of 2, a length of 300, a width of 32, a graphic of a small red bullet, and a delay of 120 frames (2 seconds).

SetLaserDataB has nine parameters: ID number, time after creation to modify laser, change in length, distance from the boss to the laser base, change in distance from the boss to the laser base, angle from the boss to the laser base, change in angle from the boss to the laser base, firing angle, and change in firing angle. As a useful example (for once), this will cause the laser to rotate around the boss in a circle with radius of 100, pointing outwards at all times: SetLaserDataB(0, 0, 0, 100, 0, 0, 2, 0, 2);. This makes the laser start pointing to the right and rotate clockwise around the boss, keeping a constant length and distance from the boss. Keep in mind that even if the boss moves, the laser will move with it to continue circling her.

Again, please check the section of ShotA bullets to learn about the other three functions.

CreateLaserC

These lasers are usually seen only in Perfect Cherry Blossom and Undefined Fantastic Object, namely against Letty Whiterock, Merlin Prismriver, or Shou Toramaru. I'm talking about those annoying curving lasers... or at least they're annoying when Merlin or Shou uses them. These lasers are very heavy on the processor so use them sparingly (i.e. don't have fifty of them out at once). However, if you've mastered controlling ShotAs and LaserAs, this will be really easy for you to use. The parameters for CreateLaserC are exactly the same as those of CreateLaserA and the parameters for SetLaserDataC are exactly the same as those of SetShotDataA. In other words, LaserCs are pretty much ShotAs with a tail following them.

I don't think I need to explain them because they are exactly the same. Just the names are different. If I'm wrong and someone does need explaining, then just PM me or contact me in some other way. I'll edit in some explanations.

AddShot with Lasers

As mentioned in the previous section about ShotA bullets, AddShot has a fourth parameter that determines how far down from the laser base the added bullet should spawn from. An example of why this might be used can be seen from Flandre's Laevantein attack, which spawns bullets at equal intervals along the bullet as it moves. Here's a snippet of code as an example of how this will be used.

CreateLaserA(0, GetX, GetY, 300, 20, YELLOW01,0);
SetLaserDataA(0, 0, 0, 2, 0, 0, 0);
SetShotKillTime(0, 90);
ascent(i in 0..30){
    ascent(j in 0..20){
        CreateShotA(1, 0, 0, 10);
        SetShotDataA(1, 0, 0, 90, 0, 0.1, 3, YELLOW11);
        AddShot(i*3, 0, 1, j*15);
    }
}
FireShot(0);

If you run this part of a script in an actual script, it should create a laser that turns and spawns bullets from it at equal intervals. You'll notice, though, that though I set the bullets to shoot at 90 degrees, or straight down, they are instead being fired 90 degrees to the laser. Keep this in mind when adding bullets to lasers. Lasers can also be added to lasers with the same result.

Other Notes

Again, there are such things as object lasers but those are too advanced for a "Basic Tutorial". I'll leave it to someone else to explain them... or maybe me when I get time again.


Need more help? Go to tutorials.