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.