Author Topic: Another Danmakufu Tutorial?  (Read 49639 times)

Another Danmakufu Tutorial?
« on: April 23, 2009, 11:50:00 PM »
Since the archived forums aren't going to be up forever, all of our delicious reference scripts are being flushed down the toilet. So this leaves remarkably little information regarding how to script in Danmakufu, aside from the random script you may have saved from the old forums, and the three tutorials (two of which don't explain how to actually code attacks, just dress them up).

So, I've begun work on an "intermediate" Danmakufu tutorial, which basically picks up where Blargel's tutorial left off. It will include:
-Mediocre techniques on how to produce semi-complex patterns (looping bullet creation to make rings of bullets, incrementing variables in loops to produce patterns, spawning bullets in uniform circles around an origin using sin and cos, etc).
-Explanation of tasks and how to use them effectively
-Boss movement (because the basic tutorial did not include a boss movement section)
-How to use object bullet creation and tasks to make scripted indestructable bullets
-How to string scripts together to make a mini-stage/plural script

So this is all I can think of. Anything that I should add to the list, or perhaps take off? I already have the first section completed (mediocre bullet patterns), so if you'd like to personally help with the tutorial or see it in it's current state for whatever reason, my MSN is Nautth@hotmail.com
I welcome any and all assistance.

Stuffman

  • *
  • We're having a ball!
Re: Another Danmakufu Tutorial?
« Reply #1 on: April 23, 2009, 11:59:06 PM »
According to 7HS we'll find out this weekend if the DMF wiki is going to survive, these would be worthy additions to it.

Re: Another Danmakufu Tutorial?
« Reply #2 on: April 24, 2009, 12:12:14 AM »
What's going on this weekend? Also, nice Mod position.

Stuffman

  • *
  • We're having a ball!
Re: Another Danmakufu Tutorial?
« Reply #3 on: April 24, 2009, 12:53:58 AM »
Oh I asked 7HS about the wiki in IRC and he said he was going to fool around with it this weekend, that's all. So we'll find out if the migration will be any trouble or not.

Also uh yeah apparently they needed someone to mod for this board and they wanted someone with Danmakufu knowledge so I'm a mod now. I doubt I'll need to do much actual moderation, but if we need to shuffle stickies or whatever as we improve our resources, I've got the tools to do it. I suppose this weekend I'll go digging through the old boards and see if there's anything else useful I care to transfer.

My green name is more exciting than the actual powers.

Re: Another Danmakufu Tutorial?
« Reply #4 on: April 24, 2009, 01:06:42 AM »
"Fool around with it..."

Sounds enthralling.

The green name is pretty sexy, gotta say. But it doesn't appear green in the "online users" bar. You got ripped off, methinks.
« Last Edit: April 24, 2009, 01:14:06 AM by Naut »

Drake

  • *
Re: Another Danmakufu Tutorial?
« Reply #5 on: April 24, 2009, 02:17:49 AM »
The important parts of the Improvement thread.

+++++

Iv'e recently been working on functions and scripts to improve danamkufu but it hasnt been working like ive been wanting it, so il need help...Here's a few things on the agenda so far:

Better Laser Sources:

Danmakufu has a terrible way of making laser sources, Their too big on thin lasers and too small on big lasers (They turn CtC's beam graphic into a giant block). These sources also end up forming a giant blob when topped up on top of each other. What im thinking of is a script (Either enemy or shot, whichever does the job) that could:

-Be resized
-Choose color
-Have a delay and activated size to match the laser's
-Can -snap- onto a specific lasers for lasers that move
-Can flicker on smaller lasers
-Whatever would be convinient


Up-to-date Cutins

Dont you just envy the new cutin scripts on the newer touhou games? What im thinking for this one would be a drawing function in the boss that would activate at the start of regular spellcards, You would ignore the actual Cutin script. This drawing function would also be easely editable for those who want to expiriment with their own custom-cutin functions.


Templates for the default graphics and sounds

Alot of the graphics and sounds in danmakufu are pretty bland or just poorly drawn, and dont even get me started on the sound of ripping paper every time you kill an enemy. Luckely these can be changed from what CtC thought us, Im thinking of a few folders that could show you what the filenames of the Danmakufu graphics are and what to replace them with.


Cut-trough lasers

The hardest of my ideas so far, in the newer games when you bomb trough a laser it cuts in several pieces and every piece is properly resized, Im thinking of somehow making object lasers (or script if necesary) that would check for bombs, and resize into several different lasers based on which parts were touched...I think this one might actualy be imposible.

Anyways il wait to see what you guys have to suggest or even help me out on this, My msn is Samuel91119@hotmail.com

/////////////////////////////////////////////////////////////////////

#include won't work for drawing the cutins, I don't think.

Instead of calling a cutin, create a boolean upon loading. When a cutin is needed, change it to true. An 'if' statement inside the drawing loop activates. It should call a separate function that displays the graphic, moves it, resizes and all that junk based on what arguments the user calls.

Should be easy to do, in theory.

As for resizing lasers, only way to do that without ugliness is having a preloaded image with the laser graphics that you want at different widths. Lengths don't change a straight graphic much, so they should be fine as long as you don't have a stump laser. Yet again, the process would be the same as above, but they'd be objects instead. This means that you could still rotate and move it around easily. Very little resizing; the size argument indicates which image you use, and little discrepancies would be resized, and at certain widths the graphic would just change to a different one. If vertices could be used, they might help for a 3D effect as well.

With this, you could probably do the split-laser thing as well (should be easy enough to create a shitload of small copies of the original), but controlling every little laser would probably be hell.

Sounds that you can change by filename are

sePlayerShot01.wav   
seGraze.wav   
sePlayerCollision.wav (?)
sePlayerSpellCard.wav (?)
se1UP.wav   
seScore.wav   
seBomb_ReimuA.wav   
seExplode01.wav   
seBomb_ReimuB.wav
seUseSpellCard.wav (?)
seGetSpellCardBonus.wav (?)
seEnemyExplode01.wav   
seSuperNaturalBorder1.wav   
seSuperNaturalBorder2.wav

and

STG_Player01.png
STG_Player11.png
CutIn_PlayerChar01.png
CutIn_PlayerChar11.png
Select_Player01.png
Select_Player11.png
STG_Frame.png

/////////////////////////////////////////////////////////////////////

Has anyone checked to see how a laser graphic looks when you scale it down, rather than up? If it looks fine scaling down you could just use a high-definition shot graphic (like greater than 100 pixels) and stretch it however you want, and it wouldn't look nearly as bad as a tiny bullet blown up to Master Spark size.

/////////////////////////////////////////////////////////////////////

You'd think so. Most things are actually still possible, there just isn't enough direct code to simplistically get what you want accomplished. Requires an intense amount of dicking around. But I do agree, I'm not seeing how split lasers could be done. Active collision detection of a laser, then create two lasers on either side of the collision point... Or more? Sounds iffy to me. Can you even call upon non-circular collision detection? How does collision detetction of lasers even work (dozens of small circles, or just an elongated circle/oval)?

What Koishi did was create the laser source (the delay cloud thing) and the laser seperate of eachother, so instead of that really shitty spread thing you see when you make a wide laser, it looks something like this:
http://i641.photobucket.com/albums/uu134/Nautth/CtC-Stylebiglasers.png

/////////////////////////////////////////////////////////////////////

http://mp3splt.sourceforge.net/mp3splt_page/home.php

LoadMusic(track);
PlayMusic(track);

let min = 0;
let sec = 0;
let cen = 0;
    //current time, starts at 0'0"0
let loopmin = 3;
let loopsec = 42;
let loopcen = 58;
    //time to loop at

@MainLoop{

    cen++;    //every frame, cen will increase
    if (frame % 60 == 0){sec++;}    //every 60 frames, sec will increase
    if (frame == 3600){min++; frame = 0;}    //every 3600 frames (or 60 seconds), min will increase

    if (cen == loopcen && sec == loopsec && min == loopmin){    //if you're at the loop time
        LoadMusic(looptrack);
        PlayMusic(looptrack); //play the split track
        loopmin = 1;
        loopsec = 23;
        loopcen = 54;
        min = 0;
        sec = 0;
        cen = 0;
            //reset the current time and change the time to loop at
    }

}

/////////////////////////////////////////////////////////////////////

A good idea, Drake.  One suggestion, though:  It may be better to use the GetTime() function instead of counting frames.  If the game starts experiencing slowdown, your counter will start lagging behind, resulting in a late restart of the loop (it could happen the other way, too, if the game starts playing too fast for some reason).

This function gives you the time, in milliseconds, that the script has been running.  All you need to do is keep track of when you started the current loop, and check for when it should be restarted using this simple formula:
time_to_restart_at = time_loop_started + (minutes * 60000) + (seconds * 1000) + milliseconds

The just do a simple check to see if you should restart the loop:
if (GetTime() > time_to_restart_at) <restart the loop>;

This still won't be perfect, since it only gets called once per frame and has the possibility of not lining up perfectly with the music, but if you set it up right it should be good.



I've been making extensive use of this type of stuff in a new, secret project which I'll never finish cause I'm a lazy dumbassI've been a bit too busy to work on effectively.  Hopefully I'll find motivation to finish it up soon!


I should mention, though - I highly recommend against using GetTime() for anything related to the actual gameplay normally, since that will very likely break replays (since the timing could very well be different each time it is run).

A Colorful Calculating Creative and Cuddly Crafty Callipygous Clever Commander
- original art by Aiけん | ウサホリ -

Hat

  • Just an unassuming chapeau.
  • I will never be ready.
Re: Another Danmakufu Tutorial?
« Reply #6 on: April 24, 2009, 03:59:08 AM »
GetTime() could be used for looping, though, right? It wouldn't affect gameplay at all.

And what of character scripts? I could help with something like that... but to most vet programmers, here, they're really easy. >_> Not to me they aren't. XD
« Last Edit: April 24, 2009, 04:05:05 AM by Karfloozly »

Re: Another Danmakufu Tutorial?
« Reply #7 on: April 24, 2009, 04:55:43 AM »
Just go ahead and dump your shit here then Drake... Haha!

Nuclear Cheese

  • Relax and enjoy the danmaku.
    • My homepage
Re: Another Danmakufu Tutorial?
« Reply #8 on: April 24, 2009, 11:48:47 PM »
GetTime() could be used for looping, though, right? It wouldn't affect gameplay at all.

GetTime() is perfectly safe to use on anything that does not, in any way, affect how the game plays out.  Looping music (Drake's idea, with my suggestion of using GetTime() ) is a perfect example - it doesn't change anything that happens in the game.  Another example that would be okay is animating a character based on realtime (that is - only their sprite animation, NOT their movement/attack pattern/hitbox/etc).

There are (as I posted in the old forums) two main reasons why you shouldn't use GetTime() for any gameplay-related items.  First, it is practically guarenteed to break replays if something is based on real time instead of the framerate, since even a slight difference in playback speed when watching a replay could completely throw off the replay.  Second, depending on what, exactly, is dependant on GetTime(), significant slowdown (or speedup) could cause a normally balanced pattern to sway towards either rediculously easy or rediculously hard or even impossible.

If these are not a concern for your script, then you can go ahead and use GetTime() in ways that affect gameplay.  If you do, it would be nice to at least inform the player (in a readme or whatnot) that these anomalies might occur.  I highly recommend against it for serious scripts, though.


Naut - an intermediate tutorial would be a good thing to add to our collection.  I'd be glad to help as I can.  No MSN account, but you can post here or PM me and I should be able to reply within a reasonable amount of time (at least when work isn't sapping me dry of willpower :-\).
to quote Naut:
"I can see the background, there are too many safespots."
:V

Re: Another Danmakufu Tutorial?
« Reply #9 on: April 25, 2009, 12:24:58 AM »
Here's the first section. Opinions?


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.



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: April 25, 2009, 12:52:07 AM by Naut »

Stuffman

  • *
  • We're having a ball!
Re: Another Danmakufu Tutorial?
« Reply #10 on: April 25, 2009, 12:38:11 AM »
Oh cool, you even explained sin/cos in a clear way.

Not really a significant gripe but the angle shown in your image would actually be 300 or -60 degrees in Danmakufu, that might confuse some people.

Re: Another Danmakufu Tutorial?
« Reply #11 on: April 25, 2009, 12:39:36 AM »
Not really a significant gripe but the angle shown in your image would actually be 300 or -60 degrees in Danmakufu, that might confuse some people.

I actually just noticed that. Gonna redo the images to compensate for Danmakufu's needlessly different coordinate grid.

Aaaand fixed.
« Last Edit: April 25, 2009, 12:52:51 AM by Naut »

Hat

  • Just an unassuming chapeau.
  • I will never be ready.
Re: Another Danmakufu Tutorial?
« Reply #12 on: April 25, 2009, 01:04:39 AM »
Yeah, I would very much like to at least know the way the danmakufu coordinate grid works. I hate having to type up some polar absolute pattern, and then it shoots, like... ninety degrees to where I wanted it to go. >_>

I'll take the character script thing as a no, then.

Stuffman

  • *
  • We're having a ball!
Re: Another Danmakufu Tutorial?
« Reply #13 on: April 25, 2009, 01:21:47 AM »
The grid works pretty simple actually, starts at 0,0 in the upper left corner. Increase x to go right, increase y to go down.

Degrees start at 0, pointing to the right, going clockwise. So hard right is 0, down is 90, left is 180, up is 270.

And I was actually thinking I would try to tackle a tutorial on how to make a player script.

Re: Another Danmakufu Tutorial?
« Reply #14 on: April 25, 2009, 01:31:04 AM »
You're free to create character scripts if you want, and we'll help in any way we can.

The Danmakufu coordinate grid is the exact same as a cartesian plane, except the positive y direction points downwards. So the point (0, 0) is the top left of the gaming window, (448, 0) is the top right, (0, 480) is the bottom left and (448, 480) is the bottom right.

Note that your hitbox can only be +-32 pixels inside of this rectangle for the x directions and +-16 pixels for the y directions, so you can never actually touch the sides of the playing field.



And damn, Stuffman beat me to the explanation. Oh well.

And I was actually thinking I would try to tackle a tutorial on how to make a player script.

Please do this. It would be nice to know how to create proper player scripts.
« Last Edit: April 25, 2009, 01:37:58 AM by Naut »

Stuffman

  • *
  • We're having a ball!
Re: Another Danmakufu Tutorial?
« Reply #15 on: April 25, 2009, 01:34:30 AM »
Well diagrams are nice too!

Re: Another Danmakufu Tutorial?
« Reply #16 on: April 25, 2009, 01:38:18 AM »
Hells yeah diagrams!

Hat

  • Just an unassuming chapeau.
  • I will never be ready.
Re: Another Danmakufu Tutorial?
« Reply #17 on: April 25, 2009, 01:38:37 AM »
Well, I meant a tutorial, aheheh... well, I'll finish making a working character script, then we'll see. Yuka is still in progress. Character scripts are just very tough to describe. It's like trying to describe a level script, but from scratch.
« Last Edit: April 25, 2009, 01:41:24 AM by Karfloozly »

Re: Another Danmakufu Tutorial?
« Reply #18 on: April 25, 2009, 01:48:33 AM »
Our explanations aren't sufficient!? Well, tell us what don't you understand specifically, and we'll see what we can do! Some equation you're using not working properly? Can't get used to the clockwise degree system? Something else!? Ask us, and we'll explain!

Stuffman

  • *
  • We're having a ball!
Re: Another Danmakufu Tutorial?
« Reply #19 on: April 25, 2009, 01:58:00 AM »
Yeah dude, that's what we're getting paid for.

(We're getting paid, right?)

Re: Another Danmakufu Tutorial?
« Reply #20 on: April 25, 2009, 05:22:22 AM »
Well, if any of us is it's you, Mr. Green-name.

Iryan

  • Ph?nglui mglw?nafh
  • Cat R?lyeh wgah?nagl fhtagn.
Re: Another Danmakufu Tutorial?
« Reply #21 on: April 25, 2009, 04:59:46 PM »
I'll just try to contribute by saying something on tasks and object bullets.

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 behind "@Finalize", 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: May 30, 2009, 11:18:13 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."

Hat

  • Just an unassuming chapeau.
  • I will never be ready.
Re: Another Danmakufu Tutorial?
« Reply #22 on: April 25, 2009, 06:27:58 PM »
Yeah, the rapidbounce things are funny... XD When I first tried to make a bouncing bullet, I caked the sides of the screen with these little vibrating balls. I couldn't stop laughing.

D=< We really need our own Danmakufu forum again.
« Last Edit: April 25, 2009, 06:33:08 PM by Karfloozly »

Re: Another Danmakufu Tutorial?
« Reply #23 on: April 25, 2009, 07:22:39 PM »
To really make it run besides you 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.

I'm pretty sure this function will not take 60 frames, but will infact take one frame to complete, because while is repeatedly checked in the same frame for it's conditions. So really, this snippit of code will take one frame to execute, but whatever is inside of the while statement will happen 60 times.
The reason why in tasks that it would take 60 frames to complete is because when you have yield; inside the while loop, the task is suspended from continuing to check if the condition is true that frame, but the next frame it gets it's chance again.

Unless my understanding of while is incorrect?

Also! Your mirror bullet code could be better expressed with a counter:
Code: [Select]
task ReflectBullet(x, y, v, angle, graphic, delay){
let obj = Obj_Create(OBJ_SHOT);
let counter = 0;
Obj_SetPosition(obj, x, y);
Obj_SetAngle(obj, angle);
ObjShot_SetGraphic(obj, graphic);
ObjShot_SetDelay(obj, delay);
ObjShot_SetBombResist(obj, true);
Obj_SetSpeed(obj, v);

while(! Obj_BeDeleted(obj)){
if(counter==0){
if(Obj_GetX(obj)<=GetClipMinX){
Obj_SetAngle(obj, 180 - angle);
counter++;
}
}
if(counter==0){
if(Obj_GetX(obj)>=GetClipMaxX){
Obj_SetAngle(obj, 180 - angle);
counter++;
}
}
yield;
}
}

This way the bullet will check a counter and reflect only once because the counter increases once it's reflected, instead of vibrating on the sides of the walls like your current code.

May I include your essay in the intermediate tutorial?
« Last Edit: April 25, 2009, 07:35:05 PM by Naut »

Iryan

  • Ph?nglui mglw?nafh
  • Cat R?lyeh wgah?nagl fhtagn.
Re: Another Danmakufu Tutorial?
« Reply #24 on: April 25, 2009, 07:59:14 PM »
You are correct. It will probably loop only once. I am aware of this. That's why I say so directly afterwards when I explain the yield command.  ;D I didn't state it directly, though, and probably should've included something like "so, the corrected version of the above would be (insert same code with yield; in the while{} brackets).

On the second part, well...
Your edit has the disadvantage of the bullet bouncing only once in total, while my version will bounce any number of times until it hits the upper or lower border of the playing field.
If you fire such bullets at an angle almost equal to 0 or 180, you may, on the long term, have a wall of bullets amassing if you call the event many times. The same goes for bullets that bounce from the top and the bottom. To prevent this, one might add a counter and include an additional condition like (&&counter<3).
I used bouncing bullets in a script where a definite number of three bouncing bullets would stay in an inclosing laser cage while regularly spawning bullet rings. This problem couldn't arise. However, the bullets where trapped inside a single laser and only freed once the laser moved past the bullet, leaving it outside the cage. My version is there to circumvent this rapid bouncing that traps the bullet inside the mirror axis.

And if you want to add it to the tutorial, I'd be glad, though I think there are some typing errors that I have to fix. Also, I wanted to describe how to replicate Aya-Satori's final spellcard using object bullets, so it would be better to wait for a bit. Should happen in ~ 3 weeks at the latest.  8)
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: Another Danmakufu Tutorial?
« Reply #25 on: April 25, 2009, 08:25:06 PM »
Fair enough about the first part.

In regards to the mirror bullet code, the big problem is that if you set a rather high speed value, the position of the bullet can go long past GetClipMinX and GetClipMaxX, so setting it's position to +-0.1 pixels only corrects it for slow speeds. Extremely slow speeds as a matter fo fact... Like, less than or equal to 1. My code, though having the limitation of only being reflected once, can assume any realistically dodgable speed value and still work. Setting the if(counter<=num) to a higher value doesn't work unfortunately, for the same reason your +-0.1 pixel fix doesn't work; that is, the bullet can stay beyond GetClipMinX and GetClipMaxX for more than one frame if a high speed value is called. Perhaps osmosis of our two codes could solve both problems, but I don't really want to think about it right now.

Aside from a few spelling and grammatical errors, your explanation of tasks is quite nice. Things I would generally want to include would be:
-Why we say while(! Obj_BeDeleted(obj)){}
(While the object is currently in existance, do this... Or something to that effect)

-A brief explanation of function versus task
Why we use one over the other, limitations of functions, etc.

-A way to use tasks to accomplish other simple things, like ordered events.
For example, in a stage script, you use a long string of yield; commands and events to dictate how a stage would play out (if there are still enemies on the screen, yield;, etc.). A breif explanation on how to do this might flow well with this tutorial (not specifially how to code stage scripts, but other, simpler things).

Iryan

  • Ph?nglui mglw?nafh
  • Cat R?lyeh wgah?nagl fhtagn.
Re: Another Danmakufu Tutorial?
« Reply #26 on: April 25, 2009, 09:02:58 PM »
As long as you have a real reflection, the incoming speed shouldn't matter because, on a left/right reflection, the horizontal speed will be the same afterwards, only negative. The only problem that should be possible to arise is when the reflecting border is moving, in which case the bullet should be moved by at least the same amount of pixels the border moves per frame.

Use the task explanation as you want.  ;D
Your additions seem appropriate, though I don't exactly know the (dis-)advantages of tasks besides "tasks can make suspended and looping stuff" and "tasks take more time to type and may or may not go harder on the PC".
Also, I have no idea at all how stage scriptss work. All I've done are singles and plurals.
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."

Hat

  • Just an unassuming chapeau.
  • I will never be ready.
Re: Another Danmakufu Tutorial?
« Reply #27 on: April 25, 2009, 10:52:56 PM »
Okay, really now? The easiest way to circumvent the vibrating phenomenon would just be to include a timer. Suppose you had something like
Code: [Select]
while((GetX == GetClipMinX || GetX == GetClipMaxX) && timer <= 0){
  Insert All Your Reflection Junk Here
  timer = 10; //for example
  yield;
}
if(timer>0){timer--;}
Or something like that. You see what I'm saying, right? Make it so that it can only reflect once in a ten frame interval. (I'm still new to tasks, so I'm not 100% on the whole yield setup inside a 'while' loop)
« Last Edit: April 25, 2009, 10:54:58 PM by Karfloozly »

kdillon7323

Re: Another Danmakufu Tutorial?
« Reply #28 on: April 25, 2009, 11:12:13 PM »
Hi i'm kind of new to danmakufu and to the forum but I have been reading since blargels tutorial and I would like to ask a question.

I don't know how to work if I wanted to fire 2 different bullets and one at 60 frames and then the other at 120 and then it fires back and forth.  How would that script look like??
thank you in advance.

Hat

  • Just an unassuming chapeau.
  • I will never be ready.
Re: Another Danmakufu Tutorial?
« Reply #29 on: April 25, 2009, 11:50:43 PM »
Code: [Select]
#TouhouDanmakufu
#Title[Test Sign "Test"]
#Text[Test script]
#Player[FREE]
#ScriptVersion[2]

script_enemy_main {
    let ImgBoss = "script\img\ExRumia.png";

    let frame = -120;

    @Initialize {
        SetLife(2000);
        SetTimer(50);
        SetScore(1000000);

        SetMovePosition02(GetCenterX, GetClipMinY + 120, 120);
        CutIn(YOUMU, "Test Sign "\""Test"\", "", 0, 0, 0, 0);

        LoadGraphic(ImgBoss);
        SetTexture(ImgBoss);
        SetGraphicRect(0, 0, 64, 64);
    }

    @MainLoop {
        SetCollisionA(GetX, GetY, 32);
        SetCollisionB(GetX, GetY, 16);
        SetShotDirectionType(PLAYER);
        if(frame == 0) {
            CreateShot01(GetX + 15, GetY, 5, 0, RED01, 0);
        }
        if(frame == 60) {
           CreateShot01(GetX - 15, GetY, 5, 0, BLUE01, 0);
        }
        if(frame == 120) {
          frame = -1;
        }
        frame++;
    }

    @DrawLoop {
        DrawGraphic(GetX, GetY);
    }

    @Finalize {
        DeleteGraphic(ImgBoss);
    }
}
Comprende? At frame = 0, it fires one shot, then at frame = 60 it fires another shot... then 60 frames later, at frame = 120, it loops back (frame = -1 is just so that the frame++ doesn't accidentally skip over the first frame == 0 argument).

The whole SetShotDirectionType deal is to account for the fact that GetAngleToPlayer gets the angle to the player from the perspective of the enemy's sprite. Since I decided to be stupid and made the balls shoot a little to the right and left of Rumia, they'd actually graze you without ever hitting you. SetShotDirectionType sets the '0' of the coordinate system at the angle to the player relative to the BULLET, so that doesn't happen.
« Last Edit: April 25, 2009, 11:55:53 PM by Karfloozly »