Author Topic: Danmakufu Intermediate Tutorial  (Read 36328 times)

Danmakufu Intermediate Tutorial
« on: June 23, 2009, 11:40:04 PM »
Danmakufu Intermediate Tutorial!

This tutorial assumes you've read and understand Blargel's Basic Tutorial, and have attempted to make patterns with it. If you haven't: what are you waiting for? Get to it!

In this tutorial, the following, among other things, will be explained:
  • How to create and spawn bullets in and around a uniform circle.
  • How to move the boss around the field.
  • How to play sound effects and music files.
  • How to use create and use tasks.
  • How to create indestructable bullets that react to their environment.
  • How to make familiars.
  • How to string together scripts to make a mini-boss.
  • How to make a stage script.
  • How to make an event script (when two characters talk to eachother).
This tutorial is mainly an index of various helpful posts around this forum, but all of this information is very useful for the intermediate Danmakufu coder.

Consider this a group effort between Iryan, Naut and Stuffman, even if the latter doesn't realize it.
« Last Edit: June 24, 2009, 09:35:06 PM by Naut »

Re: Danmakufu Intermediate Tutorial
« Reply #1 on: June 23, 2009, 11:40:35 PM »
Intermediate Bullet Patterns!

Since you've read Blargel's Basic tutorial, you should be familiar with all the CreateShot functions, as well as loop(){}. The latter you will likely be using often, as you can spawn a myriad of patterns by repeating a function while augmenting a value. Don't worry -- I'll show you what I mean. Note that anything after and on the same line as "//" in Danmakufu scripts is a comment, and will not be read by Danmakufu. Be sure to comment your work often so you don't forget what you're doing.

Code: [Select]
#TouhouDanmakufu
#Title[Spawning Bullets in a Circle]
#Text[Using loop to spawn bullets in a circular pattern.]
#Player[FREE]
#ScriptVersion[2]

script_enemy_main{


let imgExRumia="script\ExRumia\img\ExRumia.png";
let frame = 0;
let angle = 0;


@Initialize{
SetLife(1500);
SetTimer(60);
SetInvincibility(30);
LoadGraphic(imgExRumia); //Load the boss graphic.
SetMovePosition02(GetCenterX, GetCenterY - 100, 120); //Move the boss to the top center of the screen.
}

@MainLoop{
SetCollisionA(GetX, GetY, 32);
SetCollisionB(GetX, GetY, 16);
frame++;

if(frame==120){

loop(36){
CreateShot01(GetX, GetY, 3, angle, BLUE12, 0);
angle += 360/36;
}

angle = 0;
frame = 60;
}


}

@DrawLoop{
//All this foolishness pertains to drawing the boss. Ignore everything in @Drawloop unless you have read and understand Nuclear Cheese's Drawing Tutorial.
SetColor(255,255,255);
SetRenderState(ALPHA);
SetTexture(imgExRumia);
SetGraphicRect(64,1,127,64);
DrawGraphic(GetX,GetY);
}

@Finalize
{
DeleteGraphic(imgExRumia);
}
}

Begin by creating a text file with the former code in it, and run it. The main thing you should be looking at is in @Mainloop: the if(){} statement. You'll notice we loop CreateShot01 and angle. What loop does is it repeats everything inside it's braces however many times you've defined it to, in the same frame. In this case, we told Danmakufu to repeat everything inside loop 36 times, which means it will spawn 36 bullets on the boss' position, at an angle of angle, in the same frame. At the start of the script, we defined angle as 0, so all the bullets should fire at 0 degrees, or to the right. But if you run this script, you'll see that they form a perfect circle. Why? Well, that's because we set angle to be angle += 360/36 everytime the loop is run, so angle will hold a different value (in this case, 10 more than it used to) everytime we create a bullet. What this does is creates a perfect circle, because every bullet created will fire at ten degrees more than the last one, which will total to be 360 degrees because the loop is run 36 times (36x10=360).

The reason I put angle += 360/36; instead of angle += 10; was to show you that for any number that you loop(num), the angle you'll want to increment by in your loop will be angle += 360/num;. So if you want 24 bullets in your circle, the code would be:

Code: [Select]
if(frame==120){
loop(24){
CreateShot01(GetX, GetY, 3, angle, BLUE12, 0);
angle+=360/24;
}
angle = 0;
frame = 60;
}

We reset the value of angle after the loop to make sure it still holds a value that we can work with later in the script, but we don't really need to do this. Sometimes purposefully dividing by a different value than what was looped can create an interesting pattern, and you might not want angle to be reset afterwards.

A simple way to change this code would be to have the angular (4th) parameter of CreateShot01 be angle + GetAngleToPlayer, which would force one particle to always be shot towards the player.

Alright, you now know how to spawn bullets in a circle, and how to increment values in loop structures to create circular patterns. But what if you want the bullets to be spawned some distance away from the boss, but still in a circle? For that, we'll use the trigonometric functions sin and cos.

The sin of an angle is the y-coordinate of a point along a circle with a radius of one.
The cos of an angle is the x-coordinate of a point along a circle with a radius of one.



Where L = the radius of the circle, A = the angle from the origin, X = cos(A) and Y = sin(A), which form the point (cos(A), sin(A)).

Note the reversed y-coordinates, remember that Danmakufu has the positive y direction pointing downward.

So, if we want something to spawn at the point (cos60, sin60), where the boss is the origin of the circle, we say:

Code: [Select]
CreateShot01(GetX + cos(60), GetY + sin(60), 3, 60, BLUE12, 10);

But this code won't really do anything, because we're only spawning it a distance (radius) of 1 pixel away from the boss, which we won't see. So to spawn it with a distance of say, 100 pixels away from the boss, the code would be:

Code: [Select]
CreateShot01(GetX + 100*cos(60), GetY + 100*sin(60), 60, BLUE12, 10);

Now we'll see some effects. A distance of 100 pixels away from the boss is pretty noticable. Just multiply sin and cos by a value, which will be your radius, to spawn particles that distance away from your origin, which in this case is the boss. So, to combine and summarize everything so far, I give you the following to place in the beginning script:

Code: [Select]
if(frame==120){
loop(36){
CreateShot01(GetX + 60*cos(angle), GetY + 60*sin(angle), 3, angle, BLUE12, 12);
angle += 360/36;
}
angle += 4;
frame = 112;
}

This code will continuously spawn bullets in a circle around the boss with a radius of 60 pixels. I set angle to be angle += 4; outside the loop to make the circle shift it's position 4 degrees everytime we spawn it, so it looks like the circle is spinning.

The format for spawning bullets in a uniform circle is:
[x, y] -> [originX + radius*cos(angle), originY + radius*sin(angle)]

Keep in mind that you don't just have to increment angle in a loop structure. You can increment any other variable, like radius, as well. Incrementing radius will make the circle look like it is expanding or contracting, or have other interesting effects if included inside the actual loop{}.



Using only the trigonometric functions, as well as our knowledge of loop structures and incrementing values, we can make some pretty nice patterns. Here's an example script using everything we've learned so far. Can you tell what's happening?

Code: [Select]
#TouhouDanmakufu
#Title[Border of Wave and Tutorial]
#Text[How to use incrementing values and loop structures to create complex danmaku.]
#Player[FREE]
#ScriptVersion[2]

script_enemy_main{


let imgExRumia="script\ExRumia\img\ExRumia.png";
let frame = 0;
let frame2 = 0;
let angle = 0;
let angleAcc = 0;
let radius = 0;


@Initialize{
SetLife(4000);
SetTimer(60);
SetInvincibility(30);
LoadGraphic(imgExRumia);
SetMovePosition02(GetCenterX, GetCenterY - 120, 120);
}

@MainLoop{
SetCollisionA(GetX, GetY, 32);
SetCollisionB(GetX, GetY, 16);
frame++;
frame2++;

if(frame==120){

loop(6){
CreateShot01(GetX + radius*cos(angle), GetY + radius*sin(angle), 3, angle, BLUE12, 12);
angle += 360/6;
}
angle += angleAcc;
angleAcc += 0.1;
frame = 119;
}

if(frame2>=-140 && frame2 <=110){
radius++;
}
if(frame2>=111 && frame2 <= 360){
radius--;
}
if(frame2==360){
frame2=-141;
}

}

@DrawLoop{
SetColor(255,255,255);
SetRenderState(ALPHA);
SetTexture(imgExRumia);
SetGraphicRect(64,1,127,64);
DrawGraphic(GetX,GetY);
}

@Finalize
{
DeleteGraphic(imgExRumia);
}
}

In this code, we have bullets spawn at six points around a uniform circle with a radius radius, which is being controlled by a second frame counter frame2. If frame2 is between -140 and 110, then the radius will increase, if it is between 111 and 360, the radius will decrease. The angle these bullets is being fired off at is always being increased by angleAcc, which is also increasing, so the circle will spin at an increasingly faster rate. Combining the oscillation of radius and the ever increasing rate of angle creates a pretty complex pattern, don'tcha think? Feel free to experiment by changing values, like changing angleAcc+=0.2 or radius+=0.5.
« Last Edit: June 30, 2009, 07:25:41 AM by Naut »

Re: Danmakufu Intermediate Tutorial
« Reply #2 on: June 23, 2009, 11:41:03 PM »
How to make the boss move!

I'll bet many of you have been waiting for this for quite a while... Well, it's incredibly easy and requires little explanation, as it's just like firing a bullet. Here are some boss movement functions for you to play around with:

SetMovePosition01(X-coordinate, Y-coordinate, velocity);
Tells Danmakufu to make the boss start moving towards the X and Y coordinates specified, with the velocity you declare. Typically we use SetMovePosition03 instead of this one, because this one isn't very fluid.

SetMovePosition02(X-coordinate, Y-coordinate, frames);
Tells Danmakufu to make the boss start moving towards the X and Y coordinates specified, taking "frames" long to get there. 60 frames is one second, 120 frames is two seconds, etc.

SetMovePosition03(X-coordinate, Y-coordinate, weight, velocity);
Tells Danmakufu to make the boss start moving towards the X and Y coordinates specified, with the velocity you declared. This is a special movement function which includes "weight", which tells Danmakufu to make the boss decelerate as it's reaching it's goal, making a more fluid motion. A higher weight value will make the deceleration more sudden.

SetMovePositionRandom01(X-distance, Y-distance, velocity, left-boundry, top-boundry, right-boundry, lower-boundry);
Tells Danmakufu to make the boss move around randomly inside a rectangle that you declare. The boss will move the X and Y distance you tell it to, with the velocity you declare, within the rectangle you designate by declaring it's left-most coordinate, upper-most coordinate, right-most coordinate and lower-most coordinate.

SetMovePositionHermite is complex as all blazes, so I'mma let Stuffman explain this:

Code: [Select]
SetMovePositionHermite(x,y,distance1,angle1,distance2,angle2,frames);
  • x/y: This is the destination point of the boss, obviously.
  • distance1: If you were to draw a line between the starting point and destination point, this value is how far the boss moves from that line. I'm not quite sure how this is calculated but as a rule of thumb, three times the length of the line gives you a circular movement arc.
  • angle1: This is the angle you are moving at when you begin the movement.
  • distance2: Same as above but for the second half of the movement.
  • angle2: This is the angle you are moving at when you finish the movement.
  • frames: This is how long it takes to complete the movement. In other words it sets the speed, just like SetMovePosition02.

As a sample from one of my spinner enemies in PoSR:
Code: [Select]
SetMovePositionHermite(GetX()+100,GetY(),300,90,300,270,40);The enemy comes onto the screen straight down and does a 180 to its right. The helmite part of its movement involves moving 100 pixels to the right, starting at 90 degrees (down) and ending at 270 degrees (up). The distance in both cases is 300 (3*100 pixel distance) for a round movement arc. It takes 40 frames to complete this movement.

That should be enough for practical use, but you can get some really wacky movement if you play around with it. It's actually rather easy to use once you understand the arguments. The distance arguments are the only ones I don't fully understand.

You would use these functions just like calling a bullet in @MainLoop:
Code: [Select]
frame++;

if(frame==120){
SetMovePosition03(GetCenterX, GetCenterY, 10, 3);
}
Tells the boss, at frame 120, to move towards the center of the playing field with a moderate velocity and decelerate as it's reaching its goal.





How to add sound effects and music!

For now, your scripts may be fun to play and/or look pretty. However, an easy way to enrich your scripts is to have shots, movements, pattern changes and other happenings be accompanied by sound effects and even controllable music files. Music and sound effects are fairly simple, just play them the same frame you want the sound effect to be heard.

However, you'll need to load it first, preferably in @Initialize, but you can load it anywhere before it's played. To load a sound effect, you simply type:

LoadSE(sound file);

And play it using the function:

PlaySE(sound file);

So in the middle of a script, it could look something like this:

Code: [Select]
script_enemy_main{

let shot = GetCurrentScriptDirectory~"shot.wav";
let frame = 0;

 @Initialize{
   LoadSE(shot);
  }

  @MainLoop{
   frame++;

   if(frame==60){
     CreateShot01(GetX, GetY, 3, GetAngleToPlayer, RED01, 0);
     PlaySE(shot);
     frame = 0;
   }

  }

  @Finalize{
   DeleteSE(shot);
  }
}

Basically, this will play the shot sound the same frame that the RED01 and thus make it sound as if the shot actually has a sound effect. Something notable is that if you shoot more than one shot a frame, you need only play the shot sound once, as otherwise it will waste your computer's resources and possibly cause people to FPS spike. However, if you fire two shots on different frames, you'll want to play the shot sound on each frame. There is no reason to play the same shot sound more than once per frame. Don't do it.

You may have noticed the function DeleteSE in @Finalize inside the previous script. This is another sound function, this unloads the sound effect from memory to free up space for other loadable things for the next spellcard or script. Basically, if you loaded something in that script, you'll want to unload it in @Finalize. There is an exception to this, in stage scripts particularly, but I'll go over that in a bit.

A few other notable sound functions, this time for music files. You may be wondering, "I already know how to play music using #BGM, why do these function exist?" Well, what if you wanted to play certian music at a certain time, instead of autoplaying at the beginning of your script and automatically looping? Well, we've got functions for you:

PlayMusic(music file);

LoadMusic(music file);

DeleteMusic(music file);

These all do the exact same thing as their SE counterparts, except for larger files. This way, you can control when the music starts and stops playing in any script. Particularly useful for stage scripts.

Alright, as I mentioned before, the is an exception to the "anything loaded you want to unload in the same script" rule. Well, sort of. For spawning enemies or familiars in stage scripts or spell cards, sometimes you'll want the sound effect to play when they shoot bullets. For these scripts, it is not necessary to load or delete the sound effect before and after you use it, since you've likely loaded it in the other script (it will just find the memory slot and pull it from there). As a matter of fact, if you delete a sound effect in an enemy script (when the enemy dies), you'll no longer be able to use that sound until you load it again, which means the sound effect will just not play whenever you call it -- until LoadSE appears again. Not related to sound effects, but it should be noted that this same thing occurs for graphics too. If you load a graphic and unload it in an enemy script, then the graphic will stop being displayed for all enemies until you load it again. Just something to be aware of.
« Last Edit: June 24, 2009, 09:33:55 PM by Naut »

Re: Danmakufu Intermediate Tutorial
« Reply #3 on: June 23, 2009, 11:42:23 PM »
How to use Tasks!

With the CreateShotA command, you can already program rather complex bullet movements. However, what if you want to create indestructable bullets? What if you want your bullet to react to stuff that happens after you fired it, for example, to bounce off of the border of the playing field? That is when you will need object bullets.


To program an object bullet, you have to create a task.


Tasks are basically smaller scripts that run alongside the regular MainLoop. The task script is usually inserted after the @Finalize loop before the closing bracket of script_enemy_main and can later be called like a regular function. To script a task, the following code is used:
Code: [Select]
task NameOfTheTask(put, variables, here) {
        stuff happens;
        }
So far, it looks much like a script for a regular function, and it pretty much is. To really make it run besides your main loop, you have to include a while statement. That way, the task works as long as this condition is met. Example:
Code: [Select]
task Testtask(a, b, c) {
         let timer=60;
         while(timer>0) {
               stuff happens;
               timer--;
              }
        }
For a duration of 60 frames, every frame stuff will happen. After that, the "while" condition is no longer met, and the task expires.

But, because tasks run separate from the MainLoop, there is a very important command:
Code: [Select]
yield;The yield command says the program to stop the task it is working on and to look if there is other code to perform. This includes the MainLoop. That means you have to insert the yield; command at the end of the MainLoop and inside the recurring part of your task. Otherwise, danmakufu will proceed to ignore everything else and keep focused on the MainLoop (or the task).
That means the above code is actually wrong as it will do the "stuff" 60 times, but all during the same frame. The corrected version of that code would be:
Code: [Select]
task Testtask(a, b, c) {
         let timer=60;
         while(timer>0) {
               stuff happens;
               timer--;
               yield;
              }
        }

The yield command can also be called repeatedly to suspend the task. For each yield inserted, danmakufu will run the MainLoop once before the stuff after the yields proceeds. This means that by inserting the code:
Code: [Select]
      loop(n){ yield; } into the task, the following code will suspend for n frames.

Tasks performed by danmakufu are independant from each other.
This means that you can call the task multiple times while it is still running. These don't affect each other and are performed side by side. It also means that, like with regular loops, variables and arrays created in a task don't exist outside the task.



So, now that you know something about tasks, how does one make object bullets?


First, you have to assign an ID for your bullet. As the ID is assigned inside the task and thus doesn't exist outside of it, you can practically use anything. The most common way is to call it "obj". You assign the ID like this:
Code: [Select]
      let obj=Obj_Create(OBJ_SHOT);This creates an object that is classified as an object bullet and can be referred to by the name "obj". It has, however, no properties as of now - they all have to be assigned with a separate command. The most important are:
Code: [Select]
    Obj_SetPosition(obj, x coordinate, y coordinate);
    Obj_SetAngle(obj, angle);
    Obj_SetSpeed(obj, speed);
    ObjShot_SetGraphic(obj, graphic);
    ObjShot_SetDelay  (obj, delay);
    ObjShot_SetBombResist (obj, true or false);
In order, they set the position, angle, speed, bullet graphic, delay before changing from vapor to damaging bullets, and whether the bullet is immune to bullets and death explosions (true) or can be bombed like a regular bullet (false).
These commands should be called right after the declaration of your object bullet. The can, however, come in handy to regularly change the property of your bullet. For example, there is no simple command to give the bullet a specified angular velocity. To give it one, you have to call Obj_SetAngle every loop.

Speaking of which, how do you define your looping action for a bullet?
Aside from some advanced shenanigans, the looping part should be called every frame, as long as the bullet still exists, right? In danmakufu terms, this is expressed as follows:
Code: [Select]
        while(Obj_BeDeleted(obj)==false) {
               stuff happens;
               yield;
        }
The stuff happens once every frame, as long as the statement "the object has been deleted" is a false statement. Or, to put it easier, it happens once every frame as long as the bullet still exists.

So now we have the basic structure to create an object bullet:
Code: [Select]
task Bullet(x, y, v, angle) {
      let obj=Obj_Create(OBJ_SHOT);

      Obj_SetPosition(obj, x, y);
      Obj_SetAngle(obj, angle);
      Obj_SetSpeed(obj, v);
      ObjShot_SetGraphic(obj, RED01);
      ObjShot_SetDelay  (obj, 0);
      ObjShot_SetBombResist (obj, true);

      while(Obj_BeDeleted(obj)==false) {
              yield;
       }
}
Put this after (not inside) "@Finalize{ }", before ending script_enemy_main{}, and you can use "Bullet(x, y, v, angle);" just like a regular function to create a normal indestructable bullet.


Now, let's say we want the bullet to react to something. For the bullt to react to something, we must be able to check whether or not the conditions of reaction are given. For this, we have the information gathering commands, such as:
Code: [Select]
Obj_GetX(obj);
Obj_GetY(obj);
Obj_GetAngle(obj);
Obj_GetSpeed(obj);
I'm pretty sure you can guess what these do.  :P

Now, let's say that we want a bullet that reflects from the left and right border of the playing field. To do this, we specify in the "while" part that, if the bullet is too far on the left or right, it's angle shall be adjusted like with a real light reflection - incoming angle equals leaving angle. Because of how angles are coded, this requires some trickery:


Code: [Select]
task Bullet(x, y, v, angle) {
      let obj=Obj_Create(OBJ_SHOT);

      Obj_SetPosition(obj, x, y);
      Obj_SetAngle(obj, angle);
      Obj_SetSpeed(obj, v);
      ObjShot_SetGraphic(obj, RED01);
      ObjShot_SetDelay  (obj, 0);
      ObjShot_SetBombResist (obj, true);

      while(Obj_BeDeleted(obj)==false) {

              if(Obj_GetX(obj)<GetClipMinX) {
                     Obj_SetAngle(obj, 180 - Obj_GetAngle(obj) );
                     Obj_SetX(obj,  Obj_GetX(obj) + 0.1);
              }

              if(Obj_GetX(obj)>GetClipMaxX) {
                     Obj_SetAngle(obj, 180 - Obj_GetAngle(obj) );
                     Obj_SetX(obj,  Obj_GetX(obj) - 0.1);
              }

              yield;
       }
}
This code creates an indestructable bullet that checks each frame wheter it is right or left from the actual playing field. If that is the case, it will adjust the angle to mimic a real reflection, then move the bullet a little towards the center of the field to prevent it from rapid bouncing along the border.


             /\ 270°
<---- 180°           0° --->
             \/ 90°

I just noticed that I lack the proper english vocabulary to describe why the reflected angle is calculated as it is.  :-\
It would be nice if someone else could elaborate. You know, someone who has a great deal of knowledge about angles and danmakufu and who also likes to explain stuff to others.


Nevertheless, now you can fire your very own indestructable-bouncing-of-the-sides-bullets, just with a little "Bullet(x, y, v, angle);"!
Just don't forget to insert a yield command in your MainLoop, or the bullet won't bounce!
« Last Edit: March 27, 2010, 06:55:42 PM by Naut »

Re: Danmakufu Intermediate Tutorial
« Reply #4 on: June 23, 2009, 11:44:10 PM »
How to use Tasks part 2: "Great Whirlwind"



So, you are familiar with this spellcard and maybe even captured it. But how does it work?
Well, each seperate cyclone is a cluster of several bullets with the same starting point and angular velocity, but with different starting angles and speeds. The same angular velocity ensures that every bullet of the cluster will return to the center at the same time, then the cluster will unfold again, dependent on the different angles and speeds of the individual bullets.
So far, this would be easy to replicate with a CreateShotA, right? There is a little problem, however: the cyclones move downward, and still, they manage to stick to their circling motion. That is quite tougher to program.

There are actually two ways to do this; one involves CreateShotA_XY and trigonometric functions, the other involves object bullets, but since I'm here to showcase how to have fun with object bullets, we'll use these.

Let's start out with the basic framework from the previous post, with a different bullet sprite:
Code: [Select]
task Bullet(x, y, v, angle) {
      let obj=Obj_Create(OBJ_SHOT);

      Obj_SetPosition(obj, x, y);
      Obj_SetAngle(obj, angle);
      Obj_SetSpeed(obj, v);
      ObjShot_SetGraphic(obj, BLUE21);
      ObjShot_SetDelay  (obj, 0);
      ObjShot_SetBombResist (obj, true);

      while(Obj_BeDeleted(obj)==false) {
              yield;
       }
}
We said that the bullets have an angular velocity, so we'll work in a command that adjusts the angle of the bullet by a fixed amount every frame:
Code: [Select]
      Obj_SetAngle(obj, Obj_GetAngle(obj) + 2);
Now we can create an object bullet that will move in a circle. Firing a ring of these bullets will get you a pulsating circle with a radius of
360*(velocity of the bullets) / (2*Pi).
Using an angular veloctiy of 2 means that the bullet will have moved a full circle after 360/2 frames. That is exactly three seconds after being fired.

The bullets still won't move downwards, though. To accomplish that, we have to add a command that moves the bullet downward every frame, independently from the regular movement direction and speed. To simulate this movement, we can simply set the position of the bullet to a place the has a distance of, say, one pixel, every frame. A command that does this looks like this:
Code: [Select]
      Obj_SetPosition(Obj_GetX(obj), Obj_GetY(obj) + 1);
The bullet is moved straight down by one pixel every frame. Of course, if we wanted, we could make the whole cyclone move in a specific pattern itself, like waving to left and right while moving to the bottom of the screen ~~~~ , adjusting the x coordinate every second dependent on the value of a trigonometric function. You can do that as practice, if you like.  ;D

So, inserting both of these commands into the bullet task, we get:

Code: [Select]
task Bullet(x, y, v, angle) {
      let obj=Obj_Create(OBJ_SHOT);

      Obj_SetPosition(obj, x, y);
      Obj_SetAngle(obj, angle);
      Obj_SetSpeed(obj, v);
      ObjShot_SetGraphic(obj, BLUE21);
      ObjShot_SetDelay  (obj, 0);
      ObjShot_SetBombResist (obj, true);

      while(Obj_BeDeleted(obj)==false) {
              Obj_SetAngle(obj, Obj_GetAngle(obj) + 2);
              Obj_SetPosition(obj, Obj_GetX(obj), Obj_GetY(obj) + 1);
              yield;
       }
}

This task creates a single bullet that spirals down the screen. To craft a cyclone, you have to fire a ring of these bullets. Several rings with different bullet speeds, actually. For this, we can define a function:

Code: [Select]
function cyclone(x, y){
        ascent(i in 0..10){
                ascent(k in 0..6){
                        Bullet(x, y, 0.5*(1+k), i*40);
                }   
        }
}
This will fire 6 rings of 9 stormbullets each. The rings have an individual bullet speed of 0.5, 1, 1.5,  2, 2.5 and 3, and thus different radii. Calling This function for a specified place will call down a whirlwind that folds and unfolds indefinitely as it carves it's way to the bottom of the screen.

Now, to emulate the spellcard, define the task after @finalize, define the function in the mainscript and then call the function with your main loop in regular intervals.
Again, do not forget the yield; in the MainLoop!
« Last Edit: September 12, 2010, 12:50:40 AM by Naut »

Re: Danmakufu Intermediate Tutorial
« Reply #5 on: June 23, 2009, 11:44:59 PM »
How to make a boss fight!

A Plural file is a .txt file that allows you to string together multiple scripts to make a "boss fight". Essentially it operates by playing your scripts in the order you define in the file. It also manages how the boss' life bar will be broken up into sections. Plural files look like this:

Code: [Select]
#TouhouDanmakufu[Plural]
#Title[ExRumia Boss Stage]
#Text[ExRumia boss fight, including regular attacks and spell-cards.]
#Image[.\img\ExRumia(?????u?~?b?h?i?C?g?????@???G?v).png]
#BackGround[Default]
#Player[FREE]
#ScriptVersion[2]

#ScriptPathData
#ScriptPath[.\ExRumia01.txt]
#ScriptPath[.\ExRumiaSpell01.txt]
#ScriptPath[.\ExRumiaSpell02.txt]
#ScriptNextStep
#ScriptPath[.\ExRumia02.txt]
#ScriptPath[.\ExRumiaSpell03.txt]
#ScriptPath[.\ExRumiaSpell05.txt]
#ScriptNextStep
#ScriptPath[.\ExRumiaSpell04.txt]

#EndScriptPathData

Alrighty, the first little bit should be self-explanatory. Declare that this is a TouhouDanmakufu script, indicate that it is a plural script on the same line, set a title for the script, set some descriptive text, set an image to appear when you're selecting the script from Danmakufu's menu, set the background, set what player characters may be used, then indicate it's script version two.

Now, onto some of these new parameters.

#ScriptPathData starts the script path data (no wai). Be sure to declare this at the beginning of this string, it tells Danmakufu that the following is data for your plural script.

#ScriptPath[] tells Danmakufu to load and play the script you indicate in it's braces. It's relative to this file, so if your scripts are located in a subfolder, be sure to show that with [.\Sub Folder Name\script.txt]

#ScriptNextStep tells Danmakfu how to arange the life bars at the top of the screen. Everytime you call this, a new life bar is broken up and created, so all the scripts inbetween #ScriptNextStep will appear on the same lifebar. Test it out to see what I mean.

#EndScriptPathData does exactly what it says. Declare this at the end of your plural script.

And that's about it. Playing this script will tell Danmakufu to play all the scripts you've indicated inside this file, in decending order. So in the example, ExRumia01.txt will be played first, then when that script ends, ExRumiaSpell01.txt will be played, then ExRumiaSpell02.txt will be played, then a new lifebar will be created, then we'll move onto ExRumia02.txt, etc.

Re: Danmakufu Intermediate Tutorial
« Reply #6 on: June 23, 2009, 11:46:56 PM »
How to make Familiars!

Best way to make familiars: Objects. Objects are made using tasks, which you should be familiar with after reading Iryan's task portion of this tutorial. The main difficulty you'll have with objects is getting the damn image to appear correctly, which can be accomplished by following Nuclear Cheese's Object Effect Drawing Tutorial. Or, if you prefer, you could skim through Stuffman's Player Script Tutorial, which explains how to make Sakuya's "options", which are basically familiars. Your best bet is to go through the player tutorial though, since it gives a basic outline of how to shoot with them as well (just remember you're not using "player shots").

The second way is the way I mostly use, and probably the easiest (though slightly messy). I traditionally make familiars using seperate enemy scripts, which are very easy to make and summon.

Just make a text file, and fill it with:
Code: [Select]
script_enemy_main {
@Initialize{
    SetLife(10);
    etc.
 }
@MainLoop{
    [include no collion A or B so it can't be shot or collided with, include it if you want it to be shot]
    [attack functions, blah blah, like you would any script]
 }
@DrawLoop{
    [draw your familiar as you would any boss]
 }
@Finalize{
 }
}

And to spawn it, anywhere in your main script you write:
CreateEnemyFromFile(script path of your familiar/enemy, x-coordinate, y-coordinate, velocity (can be defined in the script), angle (also can be defined in script), user-defined argument);

For the user defined argument, any value or text string you put there can be transfered to the enemy script by saying "GetArgument" in the enemy script. So if I said "2" as the last parameter of my CreateEnemyFromFile, in the enemy script I could say:
v = GetArgument;
And "v" would assume a value of 2 for the script.

Object are more flexable in terms of being able to share the same global variables with the main boss, as well as being able to get the coordinates of the boss, but Enemy Scripts are much easier to handle and are probably much easier to code advanced movements with. Choose whichever you fancy, but keep in mind that spawning familiars as enemies can have unexpected results (some player script homing shots may target it instead of the boss, etc).

Re: Danmakufu Intermediate Tutorial
« Reply #7 on: June 23, 2009, 11:47:27 PM »
How to make Stages!

Start with your usual crap in any danmakufu script, except this time you're specifying that this is a stage script:

Code: [Select]
#TouhouDanmakufu[Stage]
#Title[Stage script tutorial]
#Text[How to make stages in Danmakufu]
#Image[]
#Background[]
#BGM[]
#Player[FREE]
#ScriptVersion[2]

You should know what everything here means, just make sure you've specified "Stage" after the first line.

Alrighty, onto how to script full-length stages. It's not very difficult actually, we just need to understand a few things beforehand. We actually don't even need to understand how tasks work, we just need to know how to use them. I won't bother explaining tasks and yield;, all of that is explained in Iryan's section of this tutorial. It's not necessary to know for this, but it certainly helps.

So the bulk of the code will go inside "script_stage_main{}". This is how it plays out:

Code: [Select]
script_stage_main{

  @Initialize{

  }
 @MainLoop{
   yield;
  }
  @Background{

  }
  @Finalize{

  }
}

Nothing unfamiliar. How this stage will play out will not be written in @MainLoop, but instead inside of a task. This way we can dictate exactly when things will occur by waiting a certain amount of frames before doing something else -- here's how we do that: We'll make a task just after script_stage_main and just before @Initialize, and call the name of that task in @initialize, like so:

Code: [Select]
script_stage_main{

 task stage{

 }

  @Initialize{
 stage;
  }
 @MainLoop{
   yield;
  }
  @Background{

  }
  @Finalize{

  }
}

This tells Danmakufu to run "stage" once at the very start of the script. So this way, we begin running our stage task as soon as the script starts. Let's declare some helper functions to make timing things in the stage task a little easier. We'll put these just before task stage{} so we can see them before anything else.

Code: [Select]
script_stage_main{

 function Wait(let frames){
   loop(frames){yield;}
 }
 function WaitForZeroEnemy{
  while(GetEnemyNum != 0){yield;}
 }
 task stage{

 }

  @Initialize{
 stage;
  }
 @MainLoop{
   yield;
  }
  @Background{

  }
  @Finalize{

  }
}

Alright, we've made two functions. The first is called Wait, and requires us to declare a parameter everytime we call it. What we've made it do is pause the script for however many frames we tell it to. So saying Wait(60); will tell Danmakufu to stop reading anything beyond it for 60 frames (1 second). The second function is called WaitForZeroEnemy, and doesn't require us to declare any parameters. It tells Danmakufu "when there are enemies on the screen (when the enemy count does not equal zero), wait". This is useful if you want the player to kill all the enemies you've spawned before continuing any further with your stage.

These functions will actually have use if we start putting things between them! So, let's get to spawning some enemies. The way we spawn an enemy is simple:

CreateEnemyFromFile(path of enemy script, x-coordinate, y-coordinate, velocity (if you didn't declare it in the script), angle (if you didn't declare it in the script), user-defined arguement);

These 6 parameters are simple: The first is the pathname of your enemy script, the second and third are the x and y coordinates of where you want your enemy to spawn, the fourth parameter is the velocity you want the enemy to have when you spawn it (you can set it to 0 and just define the velocity in the enemy script if you'd like), the fifth is the angle you want the enemy to move towards (again, can be defined in the script), and the last is a user-defined arguement. Basically, any value you want to pass on to the enemy script you put into the last paramet. You get this information by saying "GetArgument" in the enemy script. So, if I said 2 for the last parameter in CreateEnemyFromFile, in the enemy script I could say:

v = GetArgument;

And then the variable v would assume a value of 2 for that enemy script. Useful if you want to change bullet angles or something but still use the same enemy.

Alright, I've done alot of explaining about enemy scripts, yet you don't even know how to create them. Well, good knews! They're exactly  the same as what you've been creating all along, except with even less information! In an enemy script, you can neglect anything beginning with "#" (like #TouhouDanmakufu, #Title, etc), and just get straight to script_enemy_main. In that, it's just the same shit, but a different pile. You include your @Initialize, @MainLoop, @DrawLoop and @Finalize just like a boss script. Load graphics, spawn bullets, set life, all of it you do exactly the same. For enemies, you won't want them to have too much health, so you can kill them quickly. Typically have them spawn a really basic flurry of bullets, and just continue off in one direction (they will be destroyed upon leaving the game screen).

So all that information is nice, but how do we throw it all together into a stage? Let's take a look:

Code: [Select]
#TouhouDanmakufu[Stage]
#Title[Stage script tutorial]
#Text[How to make stages in Danmakufu]
#Image[]
#Background[]
#BGM[]
#Player[FREE]
#ScriptVersion[2]

script_stage_main{

 function Wait(let frames){
   loop(frames){yield;}
 }
 function WaitForZeroEnemy{
  while(GetEnemyNum != 0){yield;}
 }
 task stage{
  Wait(120);
  CreateEnemyFromFile(GetCurrentScriptDirectory~"enemy.txt", GetCenterX, GetCenterY, 0, 0, 0);
  CreateEnemyFromFile(GetCurrentScriptDirectory~"enemy.txt", GetCenterX, GetCenterY, 0, 0, 0);
  WaitForZeroEnemy;
  CreateEnemyFromFile(GetCurrentScriptDirectory~"enemy2.txt", GetCenterX, GetCenterY, 0, 0, 0);
  WaitForZeroEnemy;
  CreateEnemyBossFromFile(GetCurrentScriptDirectory~"cirno.txt", 0, 0, 0, 0, 0);
  WaitForZeroEnemy;
  Wait(60);
  Clear;
 }

  @Initialize{
 stage;
  }
 @MainLoop{
   yield;
  }
  @Background{

  }
  @Finalize{

  }
}

Let's go through how this stage will play out. First, the stage will wait two seconds before anything happens. Then, it will spawn two enemies at the center of the screen whose behavior I've defined in "enemy.txt", which is located in the same script directory as my stage file. The stage script will then wait until the player kills those two enemies before spawning a different enemy, "enemy2.txt". The script will again wait for the player to kill the enemy, then it will create a boss. The CreateEnemyBossFromFile function is exactly the same as CreateEnemyFromFile function, except you can summon plural files as well, and the enemy will be treated as a boss (will have an enemy marker, can use spellcards, etc). The stage script will then wait for you to defeat the boss (can be a multi-spellcard plural file), then end the stage with the "Clear" function.

Make sure you include "yield;" in your @MainLoop for the stage file, this allows all the other yield; commands to work correctly.

And that's it. This stage is incredibly small, since most stages spawn hundreds of different kinds of enemies, which require seperate enmy files or really complex GetArgument statements. The coding is long and tiring, but it looks freakin' sweet when you finish one off. Good luck!

Re: Danmakufu Intermediate Tutorial
« Reply #8 on: June 23, 2009, 11:47:58 PM »
How to make Event scripts!

Alrighty, for event scripts, you're gonna have to go beyond "script_enemy_main{}" inside of a spellcard and write the following:

Code: [Select]
script_event name{


@Initialize{
  [Load all your graphics and foolishness here]
 }

@MainLoop{
 SetChar(LEFT, [player graphic]);                 //Set the player's character on the left side, with the graphic you've told it to display.
 SetGraphicRect(LEFT, [left-side], [top-side], [right-side], [bottom-side]);           //The region you're displaying of the graphic for the player character, just like SetGraphicRect.
 MoveChar(LEFT, BACK)                             //Move the player's character into the background, to show she is not speaking.
 SetChar(RIGHT, [enemy's grahic]);                //Set the boss' picture on the right side of the screen.
 SetGraphicRect(RIGHT, 0, 0, 200, 350);           //Set the boundry of the picture you want displayed.
 MoveChar(RIGHT, FRONT);                          //Move the boss' image to the front to show that she is speaking.
 TextOutA("The text you want the character to speak goes here");           //Self explanatory. Danmakufu will not pass this function until a certain amount of time has passed, or the player clicks the shot button.
 MoveChar(RIGHT, BACK):                           //Move the boss to the background, then...
 MoveChar(LEFT, FRONT);                           //Move the player forward, to show that she will now speak.
 TextOutA("More words here");                     //What the player will be speaking.
 End;           //This ends the event.
 }

@Finalize{
  [delete all your graphics here]
 }
}

Keep in mind that anything after "//" in Danmakufu is a comment and will not be processed, so I can explain the functions as they appear in the code.

To initialize the event, call in your script:

CreateEventFromScript("name");

Which will tell Danmakufu to look in the current file for an event script called "name", then run it.

For your TextOutA commands, there are two special character combinations that will tell Danmakufu to do different things. The first is /n, which will tell Danmakufu to write everything proceeding it on a new line. The second is /c[COLOUR], which tells Danmakufu to write everything proceeding it in the designated colour. Colours available are the standard bullet colours: RED, GREEN, BLUE, YELLOW, PURPLE, AQUA, and WHITE.

A few other text options are available, including TextOutB, which has two parameters:

TextOutB(time text is displayed in frames, "text you want to be displayed");

As you could guess, this is the same thing as TextOutA except is displayed for a certain amount of frames. Think about the midboss text for the Extra stage of any Touhou game. The text can't be skipped and is displayed for a certain amount of time, which you designate in the first parameter. As per TextOutA, /n and /c[COLOUR] are available.

Select is another text function for event scripts, and is used for choosing two different options. Think stage five Imperishable Night (go versus either Eirin or Kaguya by choosing path A or path B). There are two parameters for Select:

Select("Option number 1", "Option number 2");

The function will then output a value based on which option the player chose, either 1 or 2. So to use this function in conjunction with different things happening, you could say:

if(Select("Stage 1", "Stage 2")==1){
   [code for "Stage 1"]
   }else{
   [code for "Stage 2"]
}

And that's about all you need to know for event scripts. Happy talkin'!
« Last Edit: June 23, 2009, 11:59:42 PM by Naut »

Re: Danmakufu Intermediate Tutorial
« Reply #9 on: June 23, 2009, 11:50:20 PM »
So, anything else need explaining?

Stuffman

  • *
  • We're having a ball!
Re: Danmakufu Intermediate Tutorial
« Reply #10 on: June 24, 2009, 12:02:35 AM »
I dunno but it's a pretty nice compilation of various information. I've added it to the index.

Re: Danmakufu Intermediate Tutorial
« Reply #11 on: June 24, 2009, 12:06:01 AM »
Well, it was everywhere, so instead of having to search every nook and cranny for a post on how to do something, here's an index of most intermediate concepts (and some advanced ones too, I suppose). This took a little longer than expected when I lost it on my old computer, and until recently, haven't been able to work on it because I hadn't the means to transfer things over (plus there were other things I needed from the old computer, so I wanted to do it all at once). Oh well, it's pretty much done for now, better late than never eh. Hope some of these white names I've seen around get some use out of it.

Iryan

  • Ph?nglui mglw?nafh
  • Cat R?lyeh wgah?nagl fhtagn.
Re: Danmakufu Intermediate Tutorial
« Reply #12 on: June 24, 2009, 06:16:00 PM »
Awesome Sauce!

I guess the only adidtional thing required is to put the explanation for how to use sounds somewhere in there. Yeah, I know it is really only one command, but an important one, I think.

Something like...

_________________

For now, your scripts may be fun to play and/or look pretty. However, an easy way to enrich your scripts is to have shots, movements, pattern changes and other happenings be accompanied by sound effects.

To accomplish this, the first thing you have to do is to get a hold of fitting sound effects to use, of course. To do this, just download some files other people have posted here.
If you have your sound files, you can either put them inside your script folder or create a seperate folder inside it. For the sake of ease, let's call the folder "sounds".

Now, all you have to put the following command inside your MainLoop or inside a task at the point where the sound is supposed to be heard:

Code: [Select]
PlaySE(----Path of the sound file---);
I the sound file is inside the folder "sounds" and if the folder "sounds" is in the same folder as the script, it would look like this:

Code: [Select]
PlaySE(GetCurrentScriptDirectory~"sounds\Shot1.wav");
______________________
Something like that.

Edit: fixed some errors. I need sleep...
« Last Edit: June 24, 2009, 06:56:51 PM by Iryan »
Old Danmakufu stuff can be found here!

"As the size of an explosion increases, the numbers of social situations it is incapable of solving approaches zero."

Re: Danmakufu Intermediate Tutorial
« Reply #13 on: June 24, 2009, 09:34:40 PM »
Added a sound and music tutorial, thanks for the suggestion. Too many times do I see people play the same sound effect 20 times a frame....

Iryan

  • Ph?nglui mglw?nafh
  • Cat R?lyeh wgah?nagl fhtagn.
Re: Danmakufu Intermediate Tutorial
« Reply #14 on: June 24, 2009, 11:33:25 PM »
Actually you don't need to load the sound file first. If you PlaySE a sound file that is not loaded, danmakufu will load it and play it. The loading is only really neccessary for larger sound files and when playing on really slow computers.

I never loaded a sound effect beforehand in my scripts an they work just fine and without lags.
Old Danmakufu stuff can be found here!

"As the size of an explosion increases, the numbers of social situations it is incapable of solving approaches zero."

Re: Danmakufu Intermediate Tutorial
« Reply #15 on: June 24, 2009, 11:49:04 PM »
To avoid frustration on shitty computers, I figure I'll leave the "load it first" command in the tutorial. It's for the better anyway.

Fujiwara no Mokou

  • Hourai Incarnate
  • Oh, so this trial of guts is for ME?
    • Profile
Re: Danmakufu Intermediate Tutorial
« Reply #16 on: June 27, 2009, 12:35:15 AM »
Thank you. This tutorial was very helpful.

Delpolo

Re: Danmakufu Intermediate Tutorial
« Reply #17 on: June 30, 2009, 06:29:41 AM »
I'd like to help by giving this picture I made to resume the "bullets in a circle around the boss" images and text.


(Click for a double-size version.)

It also has both the "angle=atan2(y, x)" and the pythagorean formula, the degrees for the four main directions, as well as showing the inversed y-axis.

I know this isn't much, but...

Re: Danmakufu Intermediate Tutorial
« Reply #18 on: June 30, 2009, 07:26:41 AM »
Thanks, edited with the use of your image.

CK Crash

  • boozer
Re: Danmakufu Intermediate Tutorial
« Reply #19 on: July 07, 2009, 08:49:39 PM »
Just wanted to say that you can spawn an enemy from the same script as the boss script using CreateEnemyFromScript, just start the enemy script after the last closing bracket (where the boss script ends). Instead of a file name, just put the string for the enemy's name. This is typically how I do familiars, since it keeps everything in one file :P

For an example, you can check non-spell 4 in my Chen script (too lazy to link)

Helepolis

  • Charisma!
  • *
  • O-ojousama!?
Re: Danmakufu Intermediate Tutorial
« Reply #20 on: July 07, 2009, 09:09:28 PM »
Seperate script files for familiars are useful if you want to call them multiple times in different forms without messing up your boss script as it requires it to be small and tidy. Or using it for stages.

Defining familiars in boss scripts gives a huge bulk of text and backtracking is really annoying sometimes.

CK Crash

  • boozer
Re: Danmakufu Intermediate Tutorial
« Reply #21 on: July 08, 2009, 07:20:03 AM »
Well, I'm just saying that you CAN do it, not that you HAVE to do it. If you're using the same familiar repeatedly, then obviously the separate script route would be more efficient. It's just that the tutorial made it seem like you can only make them in separate scripts. And the amount of added clutter is only slightly worse than adding a object bullet related task.