Author Topic: A solution to some Danmakufu Problems, only if it'd...  (Read 3770 times)

Aphid

A solution to some Danmakufu Problems, only if it'd...
« on: May 11, 2009, 10:56:22 PM »
Before this can work, there's just one little thing that needs to be overcome...

How to resolve a variable later on? ~~ I.e. we specify w = 5 * t inside some function. I don't want that w to be set in stone... but reevaluated each time it is called or on a special point, etc. I'll just call that the evaluate(); command but err... if anyone can make that command... haven't found anything on it yet.

Edit: To circumvent this otherwise you'd need to make a separate function for each bullet I'm afraid. And of course replace all 'vxt' using find&replace with the function you need, writing it out literally. Uhm.. there must be some way...

Sometimes, you want to make a bullet move in a peculiar fashion. These two tasks can do that as long as your bullet meets the following condition:

--> Its movement can be characterized by either:
x0, y0, vx0, vy0, vx(t), vy(t) functions... (note that the speeds for the first frame are just a necessity to set all the variables for the object). Just speed(time)-functions and starting position in the cartesian plane is what you need.

or:

x0, y0, vr0, va0, vr(t), va(t). So what you need are a starting point in the cartesian plane, and again speed(time) positions, just this time in danmakufu's inverted polar plane.

And your script meets the following conditions:

--> Somewhere in the initialization you declare let t = <insert any integer number here>,
--> There's a single 't++' in the main loop.

Make sure the three conditions are met or it won't work. So here's the code;

Code: [Select]
/* Define a function for a bullet controlled via Vx(t), Vy(t)- functions
Note: There must be a time counter 't' in the danmaku script for this to work. This means that your main

loop contains t++. That's about all. Just then specify vxt, vyt correctly. Now you can make bullets move

as weirdly as you want!
Denote a function of 't' as the speed in X, and Y */

task CreateShotF11(x0, y0, xv0, yv0, vxt, vyt, graphic, delay, duration) {

// Define bullet object

let obj = Obj_Create(OBJ_SHOT);

// Set Object initial settings

Obj_SetPosition (obj, x0, y0);
Obj_SetSpeed (obj, (xv0^2 + yv0^2)^0.5);
ObjShot_SetGraphic(obj, graphic);
ObjShot_SetDelay(obj, delay);
Obj_SetAngle (obj, atan2(yv0, xv0));
    ObjShot_SetBombResist (obj, false);
let TOJ = t

// Set the speed of the object equal to the functions.
        while(Obj_BeDeleted(obj)==false) {
             Evaluate(vxt);
             Evaluate(vyt);
Obj_SetSpeed (obj, (vxt^2 + vyt^2)^0.5);
Obj_SetAngle (obj, atan2(vyt, vxt);
if TOJ + duration > t {
Obj_Delete(obj);
}

yield;
}
}

/* The same thing, just with v(t) and r(t)-functions. Oh and just note that r(t) is in danmakufu's weird

coordinate system. */

task CreateShotF12(x0, y0, v0, r0, vt, rt, graphic, delay, duration) {

// Define bullet object

let obj = Obj_Create(OBJ_SHOT);

// Set Object initial settings

Obj_SetPosition (obj, x0, y0);
Obj_SetSpeed (obj, v0);
ObjShot_SetGraphic(obj, graphic);
ObjShot_SetDelay(obj, delay);
Obj_SetAngle (obj, r0);
    ObjShot_SetBombResist (obj, false);

// Set the speed of the object equal to the functions.
        while(Obj_BeDeleted(obj)==false) {
             Evaluate(vt);
             Evaluate(rt);
Obj_SetSpeed (obj, vt);
Obj_SetAngle (obj, rt);
yield;
if TOJ + duration > t {
Obj_Delete(obj);
}
}

}
What it does is fairly simple... say you wanted a bullet that would go in a circle with radius 110, and start going at 360 frames per round, but .... let's say go 10% faster each 100 frames? That's something we could parametrize easily.
Let's use the first function.
Then x(t) = 224 + 110 * cos(t * 1.1^(t/100)), y(t) = 240 + 110 * sin(t * 1.1 ^ (t / 100))
Now comes the more annoying part, because we've taken an example where you'd know x(t), y(t), but not the velocities. Thus derivate, but... first you transform the function to a regular polar plane (multiply everything within sines/cosines by 2 * pi, flip signs, divide by 360), then derivate, and then return to danmakufu's weird plane. On a positive accord, flipping signs usually can be skipped to save you some headache. Anyways, the results of all that are, using 3.1415 as an approximation of pi (why doesn't it know that number as some function?):

Code: [Select]
vx(t) = -1 * sin(2 * 3.1415 / 360 * t * 1.1 ^ (t/100)) * (2 * 3.1415 / 360 * 1.1 ^ (t/100) + 2 * 3.1415 / 36000 * t * log(1.1) * 1.1 ^ (t/100))
[i](apply chain & product rules)[/i]
vy(t) = cos(2 * 3.1415 / 360 * t * 1.1 ^ (t/100)) * (2 * 3.1415 / 360 * 1.1 ^ (t/100) + 2 * 3.1415 / 36000 * t * log(1.1) * 1.1 ^ (t/100))

Now the fun part is that we can set any number on the circle as x0, y0. then we'd put vx(0), vy(0) inside the correct places, and taking some easy x and y for once where the velocities are simply 0 and the starting, and obtain:

Code: [Select]
CreateShotF11(224, 334, -1 * (2 * 3.1415 / 360 ), 0, -1 * sin(2 * 3.1415 / 360 * t * 1.1 ^ (t/100)) * (2 * 3.1415 / 360 * 1.1 ^ (t/100) + 2 * 3.1415 / 36000 * t * log(1.1) * 1.1 ^ (t/100)), cos(2 * 3.1415 / 360 * t * 1.1 ^ (t/100)) * (2 * 3.1415 / 360 * 1.1 ^ (t/100) + 2 * 3.1415 / 36000 * t * log(1.1) * 1.1 ^ (t/100)), RED01, 0, 600);
This bullet will remain on the screen for 600 frames and rotate increasingly fast during that time. Furthermore, you could attach additional objects by using the Object_GetX and GetY functions! (Works just like CreateShotA, with the advantage being that you don't have to set the added bullets before the original one is fired, but the disadvantage of apparently more processing power needed.)

As an alternative solution... you can also replace the code for the speed/angle with just a Obj_setPosition, and keep the object's velocities at 0.

Now here's something funny. We did all that math, but... it's largely unnecessary actually. This is a version of the code for x(t), y(t)-functions!... It's less useable though for when these aren't possible to be created but the velocities are, AND it only works for round bullets, and a few other bullets that don't change angle depending on movement. Any bullets which are made in another fashion will not change their angles when moving if they normally would! So here it is:
Code: [Select]
task CreateShotF13(x0, y0, xt, yt, graphic, delay, duration) {

// Define bullet object

let obj = Obj_Create(OBJ_SHOT);

// Set Object initial settings

Obj_SetPosition (obj, x0, y0);
Obj_SetSpeed (obj, 0);
ObjShot_SetGraphic(obj, graphic);
ObjShot_SetDelay(obj, delay);
Obj_SetAngle (obj, 180);
    ObjShot_SetBombResist (obj, false);
let TOJ = t

// Set the position of the object equal to the functions.
        while(Obj_BeDeleted(obj)==false) {
             Evaluate(xt);
             Evaluate(yt);
Obj_SetPosition (obj, xt, yt);
Obj_SetAngle (obj, 180);
if TOJ + duration > t {
Obj_Delete(obj);
}

yield;
}
}

Note that you need smooth functions and that you don't want your functions to be too "jumpy" (that means no derivatives in the absolute sense greater than, say, 10), or, well, you'd get some very erratic behaviour.

Things to do:

The 13-version can be expanded for the directional bullets as well if you add in 10 variables to remember the x- and y-positions of the previous, say, 5 frames, sort of like every yield you would put in;

rem5=rem4.
(...)
rem1=rem0
rem0= current position

And then you'd do a tangent of these positions, take an average of the tangents if they're not of a (0,0)-diffrence, and get an approximation of the angle the bullet's moving at. Then of course you'd set the object's angle equal to this angle if not all the tangents have a (0,0)-diffrence, in which case you'd have to not set the angle at all. I haven't tried out this one idea yet, maybe the bullet won't 'wobble' as much with this as it does when you only take 1 frame. (Because GetX,Y functions return an integer it kind of wobbles back and forth really fast giving some blurrish effect...).

There's also a little problem with this one method and that's... what does the bullet do during the first 5 frames? Obviously it'll appear a bit weird so you'd have to specify a starting angle correctly for each bullet. Making things considerably more complex...
Heh I think I'll just stick with derivating my formulas if I want non-round bullets if you know what I mean  ::)
« Last Edit: May 11, 2009, 11:09:32 PM by Aphid »

Nuclear Cheese

  • Relax and enjoy the danmaku.
    • My homepage
Re: A solution to some Danmakufu Problems, only if it'd...
« Reply #1 on: May 12, 2009, 12:37:29 AM »
Before this can work, there's just one little thing that needs to be overcome...

How to resolve a variable later on? ~~ I.e. we specify w = 5 * t inside some function. I don't want that w to be set in stone... but reevaluated each time it is called or on a special point, etc. I'll just call that the evaluate(); command but err... if anyone can make that command... haven't found anything on it yet.

I don't have time to address all of your issues at the moment, but I think I see a pretty fundamental issue you may be having here.

"Variables" in programming (and scripting, like Danmakufu) are not like variables in mathematics.  You assign a value to them, and they retain that value until they are assigned a new one; in this way, you can think of them as a box that holds said value, and you can replace the value with a new one later one.

That said, mathematic equations like you posted above are not kept.  What that is actually called in programming is an "assignment" - that is, you're assigning a new value to the variable w.

Basically, when Danmakufu (or any programming/scripting language I know of) sees the line of code:
w = t * 5
... it doesn't see "w is five times t".

It sees the following, in order:
1) Get the value from t
2) Take that value, and multiply it by five
3) Assign the result to w

If you want to get the latest value of w based on an updated t, you need to re-assign the value of w by repeating the assignment statement, either later in your code or by reaching the same line again (such as with a loop).

This is how computers work in general - anything that thinks in terms of mathematics, with persisting equations and such (like you want, if I understand correctly) is against how a computer normally works.  But you can simulate this mathematical style of thinking (at least to a certain extent) by reassigning the variable just before you use it.

Or, if you're always basing the value on certain other variables, then what you want is a function, which takes certain parameters and returns a result.  For instance, your w above could be written as the following:
Code: [Select]
function w(t)
{
   return t * 5;
}

Of course, for something as simple as this, it may just be easier to in-line the multiplication wherever you're using it, rather than defining a function for it.  For instance, instead of saying:
CreateShot01(GetX(), GetY(), 2, w(t), RED01, 5);
... it may just be easier to use:
CreateShot01(GetX(), GetY(), 2, t*5, RED01, 5);

... but for more complex functions the shortcut will definitely make it more readable.


Oh, and one more quick thing I can help with ...

Edit: To circumvent this otherwise you'd need to make a separate function for each bullet I'm afraid. And of course replace all 'vxt' using find&replace with the function you need, writing it out literally. Uhm.. there must be some way...

Creating lots of variables named v1x, v2x, etc ... is a really bad practice (no offense, many people do this early on).  What you really want to use in situations like this is a data structure that holds several values.  Danmakufu has Arrays, which are quite useful in this regard.  I can explain in more detail if you'd like, but I'm short on time at the moment.


If I have time later, I'll pop back on and see if I can help you further.

I hope this helps!


EDIT:

I notice you're using a parametric function for determining the position of a bullet based on time.
Personally, I think it would be much easier if you just kept the bullet's velocities at zero and instead continually reassign the bullet's position.  The effect is the same, but it save you all of the hoops you're currently trying to jump through to get it to work.

Here's an example.

Code: [Select]
#TouhouDanmakufu
#Title[Orbit]
#Text[An example of orbiting object bullets using tasks]
#Player[FREE]
#ScriptVersion[2]

script_enemy_main
{
   let count = 0;
   let move_count = 0;

   let orbit_around_enemy = true; // if true, shots orbit around the enemy's position

   @Initialize
   {
      SetLife(700);
      LoadGraphic("script\img\ExRumia.png");
      SetGraphicRect(64, 1, 127, 64);
      SetMovePosition02(GetCenterX(), 120, 60);
      SetScore(150000);
      SetTimer(60);
      SetDamageRate(15, 8);
      SetInvincibility(150);
      CutIn(YOUMU, "Orbit", 0, 0, 0, 0, 0);

      SetShotDirectionType(ABSOLUTE);
   }

   @MainLoop
   {
      count++;
      if (count == 120) // spawn a new shot every two seconds by running an instance of the task
      {
         go;
         count = 0;
      }

      move_count++;
      if (move_count == 300) // Every five seconds, move the boss to where the player is
      {
         SetMovePosition01(GetPlayerX(), GetPlayerY(), 2);
         move_count = 0;
      }

      SetCollisionA(GetX(), GetY(), 32);
      SetCollisionB(GetX(), GetY(), 24);

      yield; // important - this allows the task to run as well
   }

   // A task to control each object bullet
   // This is horribly inefficient, but it allows an easier demonstration of the method.
   task go
   {
      let origin_x = GetCenterX(); // The point around which the bullet rotates
      let origin_y = GetCenterY();

      let radius = 100; // The radius of the orbit

      let current_angle = 0; // The current angle at which the bullet is positioned

      let obj = Obj_Create(OBJ_SHOT); // Initialize the object shot
      ObjShot_SetGraphic(obj, RED21);
      Obj_SetSpeed(obj, 0);

      Obj_SetAutoDelete(obj, false); // Set it so that the object won't be auto-deleted.
                                     // This helps if the orbit temporarily puts the bullets out of the frame.
                                     // Be careful when using this function, or you can "leak" objects.

      let t = 0; // Time, in frames

      let max_duration = 1200; // maximum duration for the bullet to exist (1200 frames - 20 seconds at normal speed)

      while (!Obj_BeDeleted(obj)) // ! is the symbol for boolean "not".  Its a simpler way of writing ".. == false"
      {
         current_angle = t * 1.1^(t/100); // reassign current_angle so it has the current angle to position the shot

         if (orbit_around_enemy)
         {
            // If we are orbiting around the boss's position, reassign the origin point
            origin_x = GetX();
            origin_y = GetY();
         }

         Obj_SetX(obj, origin_x + radius * cos(current_angle)); // update the shot's position based on the parametric formula
         Obj_SetY(obj, origin_y + radius * sin(current_angle));

         Obj_SetAngle(obj, current_angle + 90);  // Sets the bullet's angle perpendicular to the angle from the radius.
                                                 // This makes it appear to be moving forward as it circles around

         t++; // increment the timer

         if (t > max_duration) // Check if the bullet is done
         {
            Obj_Delete(obj);
         }

         yield; // important - this allows the main thread to run as well
      }
   }

   @DrawLoop
   {
      SetTexture("script\img\ExRumia.png");
      DrawGraphic(GetX(), GetY());
   }

   @Finalize
   {
      DeleteGraphic("script\img\ExRumia.png");
      let amnt = 15;
      if (GotSpellCardBonus)
      {
         amnt = 45;
      }
      let i = 0;
      while (i < amnt)
      {
         CreateItem(ITEM_SCORE, GetCenterX() + prand(-100, 100), 100 + prand(-60, 60));
         i++;
      }
   }
}

In the task named go, an object bullet is controlled by setting its position every frame.  It orbits around the boss, which moves around occasionally.

Note the variable orbit_around_enemy - it is only set once at the very beginning.  I use this as a "constant" - that is, a constant value that is named simply to make it easier to refer to.  This is pretty common programming practice, since it lets you describe what the value is for.
In this case, changing its value to false makes the bullets orbit around the center of the playing field, rather than follow the boss.

Also note my use of Obj_SetAngle() - this sets the object's angle so that it faces the direction it is moving.  This is not necessary, but it makes it look correct.  For a more complicated equation, you'll probably have to use a more complicated bit of math (including use of an arctangent function) to get the correct angle, but it seems you already know your way around the mathematics side of things pretty well, so you can probably figure that part out without much trouble.


Please keep in mind that using a task for each object shot like this is horribly inefficient, and will lead to massive slowdown with even a mildly dense attack (I've tried it - it's not pretty).  Instead, I recommend using an array to track the object bullets (as I mentioned above).  It's a bit more complicated that the code above, though.  I can write up some info on it if you'd like.


Once again, I hope this helps you out!
« Last Edit: May 12, 2009, 02:45:30 AM by Nuclear Cheese »
to quote Naut:
"I can see the background, there are too many safespots."
:V

Aphid

Re: A solution to some Danmakufu Problems, and it's solved.
« Reply #2 on: June 10, 2009, 09:21:58 PM »
This ought to be my slowest reply ever. Anyway, I should be a bit more rigid with terminology. But, I do understand how it works... you see, when you type in some formula using '=', a single variable on the left-hand-side, and some equation on the right-hand side not involving this variable, the computer automatically evaluates and assigns the found value to the variable. Here's the main problem. With 't', I mean time, as in the very frame the game is at. So basically, I do not want my variable to remain static, but depend in value upon the frame, I want to put a whole continuous thing into said box. A good example program that actually does that is Mathematica, there's several others that can do it too. But as you said, there's no delayed evaluation like in some other languages here apparently. (As an example of how it could work; instead of putting the evaluated formula at the time 't0' into said box the moment the variable is assigned (which is when we call our function), the whole formula we put into the function call will be put in a variable box as a 'string'. Then said string is to be 'converted' back into a formula and evaluated immediately, assigned to another variable, it's 'companion variable'. Then this variable holds the true value it should hold at 't0'. In addition, this conversion process happens every frame, so that the 'companion variable' changes every frame, thus I can have a bullet with speed 2 * 1.01^t, where t is time since launch in frames. Anyway, there's a way around it, it's just that you will have to call your function of time within the CreateShot itself.

Using the w=t*5 example... if 'w' is called at frame 100, it should return 500, and if it's called at frame 200, it should return 1000, etc. Of course, that means either w has to be reset to 't*5' every frame (thus you actually want this variable declaration in the main loop), or you need something else. Of course, a variable created within a task only exists within that particular task, so the w that is reset in the main loop will not carry over to the one set in stone in the task, so this will not solve the problem at all. Basically w needs to be evaluated like that in the task, i.e. the literal programming code 'w=t*5;' needs to be present within the task, and of course I want the right-hand part of the equation to be in the position of 'vxt' for example. So, basically, I just want the program to substitute the vxt within the task with some text that would also go as code. Basically vxt as a variable would be a string that is converted into programming code within the task itself. Because the string just contains some kind of math expression, the code it contains would be resolved when it would be applied as code. However, this happens only when it is converted to code within the task, i.e. it gets resolved inside the task. Thus, it gets applied a new value each time we call it, and that's the way of getting some kind of time-dependant behaviour. A much simpler way of doing this is to just manually replace 'vxt' in your programming code with the intended function and leave out 'vxt' and 'vyt' in the task's variables. But, you will need a new task for each function and some kind of naming convention if you intend to use many.

Code becomes;

Code: [Select]
 

 task CreateShotF11(x0, y0, xv0, yv0, graphic, delay, duration) {

// Define bullet object

let obj = Obj_Create(OBJ_SHOT);

// Set Object initial settings

Obj_SetPosition (obj, x0, y0);
Obj_SetSpeed (obj, (xv0^2 + yv0^2)^0.5);
ObjShot_SetGraphic(obj, graphic);
ObjShot_SetDelay(obj, delay);
Obj_SetAngle (obj, atan2(yv0, xv0));
    ObjShot_SetBombResist (obj, false);
let TOJ = t

// Set the speed of the object equal to the functions.
        while(Obj_BeDeleted(obj)==false) {
Obj_SetSpeed (obj, (vxt^2 + vyt^2)^0.5);
Obj_SetAngle (obj, atan2(vyt, vxt);
if TOJ + duration > t {
Obj_Delete(obj);
}

yield;
}
}

Of course, manually replace vxt, vyt with your formulae for these (which are to be only dependant on 't' and variables that are defined.). Now of course.... for several bullets, with functions vxt(n), etc. which can be written out neatly dependent on increasing 'n', you can potentially add in an ascent(i in 1..n+1) around the whole thing... Just add a variable 'whatever' to the task in front and adjust it in, ascent around 1..n+1 whenever you call the thing to make all 'n' bullets, in other words, it can be easily modified to create multiple bullets as long as they travel along the same guidelines save for simple transformations (anything not substantially changing the formula basically, additions, multiplications, etc.). Or, as a more efficient method, simply use arrays to the same effect... We could declare;

Code: [Select]

\\ in the initialization.
let obj=[0];

\\ in the main loop or task.
ascent(i in 1..n+1){
obj = obj ~ Obj_Create(OBJ_SHOT);
                         }

And of course, for each additional segment of lines in the task we will need to create an array. The whole code becomes (of course, insert the correct amount of 'zeroes' to first declare the array's size correctly);

Code: [Select]
 

 task CreateShotF11(x0, y0, xv0, yv0, graphic, delay, duration, n) {

// Define bullet object

ascent(i in 1..n+1){
obj = obj ~ Obj_Create(OBJ_SHOT);}

// Set Object initial settings

             ascent(i in 1..n+1){
Obj_SetPosition (obj[i], x0, y0);
Obj_SetSpeed (obj[i],  (xv0^2 + yv0^2)^0.5);
ObjShot_SetGraphic(obj[i], graphic);
ObjShot_SetDelay(obj[i], delay);
Obj_SetAngle (obj[i],  atan2(yv0, xv0));
    ObjShot_SetBombResist (obj[i],  false);}
let TOJ = t

// Set the speed of the object equal to the functions.
             ascent(i in 1..n+1){
             while(Obj_BeDeleted(obj[i])==false) {
Obj_SetSpeed (obj[i], (vxt^2 + vyt^2)^0.5);
Obj_SetAngle (obj[i], atan2(vyt, vxt);
if TOJ + duration > t {
Obj_Delete(obj);
}

}
yield;
}
}

Note that you need all x0, y0 to match up (all bullets starting in the same position) for this trick to work. If this is not the case, also remove x0, y0 from the task declaration variables and replace them with your intended arrays for the task, i.e. x0 becomes let x0 = ["x01", "x02", ...,], with x0n being the intended value of the x-starting position of the n-th bullet. The same goes for all the other variables you put into the task when you declare it, you can't have them dependant on 'i' without actually inserting the declaration into the task itself. Then again, you do gain the advantage of being more efficient, you need far less tasks if you can somehow create an array of bullets with only mildly different functions (say, a trigonometric mutation by a certain amount of degrees, for example creating a ring of bullets that all move in a lissajous pattern (would be funny to watch...)

*notice* vxt, vyt are NOT variables here, they're bits of things you should replace by your formula for them.

The main problem with using the second version (which DOES assign a bullet only a position and velocities of 0 at every frame), is that you have to determine the angle you want the bullet to be at, instead of the game doing that automatically*. Perhaps my example was too simple, but it can be quite a pain for some weird patterns. Say a Rose pattern (i.e. not bullets being in a rose pattern, but a bullet MOVING in a rose-pattern).

About the vx, vy versus the x,y versions; the angle the bullet should be facing has been rather simple in the problems we've seen so far so it's easy to include the angle ('t was included in one of the examples with an x, y version of the thing, basically). It's just that if you have some kind of very weird trajectory, it isn't always easy to come up with an angle(time)-function as well, and apply it each second (which gives you a total of three functions instead of two). Because I've been assuming the only work you need to do is deriving the functions and so on, the option of the vx-version is sometimes more alluring than the standard version. And of course, if you wanted a bullet with a certain speed instead of a certain path, that can be done like that more easily too. Integration is usually a very nasty thing when applied to random patterns, I don't like to do it nearly as much as the much easier version the other way. So, the answer to this bit;

Quote
I notice you're using a parametric function for determining the position of a bullet based on time.
Personally, I think it would be much easier if you just kept the bullet's velocities at zero and instead continually reassign the bullet's position.  The effect is the same, but it save you all of the hoops you're currently trying to jump through to get it to work.

Is that I actually did both. I saw that sometimes you can use just the bullet's position, that is when your angle(time) function, or your angle(position) function is rather simple (remove angle from the list of variables at the function's declaration, insert your angle(time) or angle(position) function instead of just 'angle' in all the places where it is mentioned). It's just that it needn't be a very simple function at all. It could be even more of a pain to obtain than the other things. For example;

Problem:

(i) Determine the angle(time)-function of a particle p, travelling in a pattern given by the polar coordinates

r = sin(2/5 * t)cos(2/5 * t)
angle = 0.5 * t^2

(ii) Transform both functions into the cartesian plane


That looks like a far more difficult problem than derivating both functions toward t, after transforming to cartesian coordinates. In fact, the latter would probably take one to five minutes, while that problem actually includes solving all those things (as you need the velocity vector to determine the angle, which is the tangent of the y-component divided by the x-component of said vector, and said vector is the combination of both unity vectors i and j times respectively both derivatives determined, but I'm digressing here. Anyway, my point is that sometimes one version is better, sometimes the other. Note that in the original post I concluded that for that particular problem it's possible to go with the x,y solution! 

Conclusion, the x,y solution can be used if:

(i) The bullet has an easy angle(time), angle(position) function, or does not need to be angled (round bullets).
(ii) The bullet has a specified x,y(t) equation set, or one that can be easily obtained through integration (don't do tricky integrals, just go with the vx, vy solution if you get one of them, they're not nice to me, and not nice to you.)

In practice, condition (i) is fairly commonly untrue, while (ii) is true most of the time. Both need to be true for the x,y method to be more efficient. By the way, here's the updated version for the x,y, the simpler version, you should be able to deduce the one with arrays as well  ;).

Code: [Select]
task CreateShotF13(x0, y0, graphic, delay, duration) {

// Define bullet object

let obj = Obj_Create(OBJ_SHOT);

// Set Object initial settings

Obj_SetPosition (obj, x0, y0);
Obj_SetSpeed (obj, 0);
ObjShot_SetGraphic(obj, graphic);
ObjShot_SetDelay(obj, delay);
Obj_SetAngle (obj, 180);
    ObjShot_SetBombResist (obj, false);
let TOJ = t

// Set the position of the object equal to the functions.
        while(Obj_BeDeleted(obj)==false) {
Obj_SetPosition (obj, xt, yt);
Obj_SetAngle (obj, 180);
if TOJ + duration > t {
Obj_Delete(obj);
}

yield;
}
}

Once again, replace xt, yt by your chosen/found formulae for movement...
You can also replace 180 by your angle function if you want one.

Quote from: Nuclear Cheese
CreateShot01(GetX(), GetY(), 2, w(t), RED01, 5);
... it may just be easier to use:
CreateShot01(GetX(), GetY(), 2, t*5, RED01, 5);

That won't really work, as the angle of the bullet won't change with time, it'll just become the time the function is called times five. Of course, you could just do that with angular velocity... However, what if you had something more complicated, like

w(t) = 0.1172t^6.4 - 2t^3 + arctan(5/8 * t - 7 + 3* t^0.5)

As your angle, as a function of t, with t=0 at the frame the bullet is launched? Surely you would need to adjust the angle frame-by-frame or each couple frames to maintain this part of the bullet's equation of motion. The derivative of this does not even approach a straight line, nor a set of a couple straight lines (so we could use a pair or trio of SetShotDataA). Each frame you would need to redetermine the angle as a function of the current time, in other words, put into the 'angle' variable box this function of time. In other words 'let angle = {insert function}' need be called every frame, so it need be present within the yield-loop in the task. Thus this formula need be present into the yield loop inside a task. So therefore, you need to manually put this formula into your task. (basically what I was hoping for was the possibility of having to have a way that you could put the formula somewhere else, like in the task's assigned modifiers as a string, but no luck I guess.)


« Last Edit: June 11, 2009, 03:16:10 PM by Aphid »

Nuclear Cheese

  • Relax and enjoy the danmaku.
    • My homepage
Re: A solution to some Danmakufu Problems, and it's solved.
« Reply #3 on: June 11, 2009, 01:11:48 AM »
I'll see what I can answer here ...

This ought to be my slowest reply ever. Anyway, I should be a bit more rigid with terminology. But, I do understand how it works... you see, when you type in some formula using '=', a single variable on the left-hand-side, and some equation on the right-hand side not involving this variable, the computer automatically evaluates and assigns the found value to the variable.

One thing to note: the right side of an equation can have the variable on the left side.  The following is perfectly valid code:

Code: [Select]
let a = 2;
a = a + 1;

After both lines are run, the variable a contains the value 3.

Here's the main problem. With 't', I mean time, as in the very frame the game is at. So basically, I do not want my variable to remain static, but depend in value upon the frame, I want to put a whole continuous thing into said box.

The simple way to keep t as a continuous frame counter is, at the beginning of each frame, increment t using code just like the code above.  If you don't use any yield statements in your code, its as simple as this:

Code: [Select]
let t = 0;

@MainLoop
{
   t = t + 1;
   // teh codez
}

When you use yield, it gets more complicated, since yield causes the code to wait there until the next frame.  You can still increment t after your yield statements as well, and it should work fine.

A good example program that actually does that is Mathematica, there's several others that can do it too. But as you said, there's no delayed evaluation like in some other languages here apparently. (As an example of how it could work; instead of putting the evaluated formula at the time 't0' into said box the moment the variable is assigned (which is when we call our function), the whole formula we put into the function call will be put in a variable box as a 'string'. Then said string is to be 'converted' back into a formula and evaluated immediately, assigned to another variable, it's 'companion variable'. Then this variable holds the true value it should hold at 't0'. In addition, this conversion process happens every frame, so that the 'companion variable' changes every frame, thus I can have a bullet with speed 2 * 1.01^t, where t is time since launch in frames. Anyway, there's a way around it, it's just that you will have to call your function of time within the CreateShot itself.

This is how every programming language actually works.  Not trying to sound elitist, but things like Mathematica (from what I google'd) aren't actual programming - rather its a layer above programming.  When you tell it to set, for example, x = sin(t^2), then every time x is updated it internally goes through the calculation based on t, and updates the displays/other calculations/etc wherever it needs to.  Of course, these usually go into a bit more depth with code that does things like solve sets of equations, but it all boils down to the program is reevaluating things whenever they're requested.

The main thing in Danmakufu that is different from what you're used to is that the language is lower-level, and does not handle this type of activity on its own; you need to tell it to do so when needed.

Using the w=t*5 example... if 'w' is called at frame 100, it should return 500, and if it's called at frame 200, it should return 1000, etc. Of course, that means either w has to be reset to 't*5' every frame (thus you actually want this variable declaration in the main loop), or you need something else. Of course, a variable created within a task only exists within that particular task, so the w that is reset in the main loop will not carry over to the one set in stone in the task, so this will not solve the problem at all. Basically w needs to be evaluated like that in the task, i.e. the literal programming code 'w=t*5;' needs to be present within the task, and of course I want the right-hand part of the equation to be in the position of 'vxt' for example. So, basically, I just want the program to substitute the vxt within the task with some text that would also go as code. Basically vxt as a variable would be a string that is converted into programming code within the task itself. Because the string just contains some kind of math expression, the code it contains would be resolved when it would be applied as code. However, this happens only when it is converted to code within the task, i.e. it gets resolved inside the task. Thus, it gets applied a new value each time we call it, and that's the way of getting some kind of time-dependant behaviour. A much simpler way of doing this is to just manually replace 'vxt' in your programming code with the intended function and leave out 'vxt' and 'vyt' in the task's variables. But, you will need a new task for each function and some kind of naming convention if you intend to use many.

Code becomes;

Code: [Select]
 

 task CreateShotF11(x0, y0, xv0, yv0, graphic, delay, duration) {

// Define bullet object

let obj = Obj_Create(OBJ_SHOT);

// Set Object initial settings

Obj_SetPosition (obj, x0, y0);
Obj_SetSpeed (obj, (xv0^2 + yv0^2)^0.5);
ObjShot_SetGraphic(obj, graphic);
ObjShot_SetDelay(obj, delay);
Obj_SetAngle (obj, atan2(yv0, xv0));
    ObjShot_SetBombResist (obj, false);
let TOJ = t

// Set the speed of the object equal to the functions.
        while(Obj_BeDeleted(obj)==false) {
Obj_SetSpeed (obj, (vxt^2 + vyt^2)^0.5);
Obj_SetAngle (obj, atan2(vyt, vxt);
if TOJ + duration > t {
Obj_Delete(obj);
}

yield;
}
}

Of course, manually replace vxt, vyt with your formulae for these (which are to be only dependant on 't' and variables that are defined.). Now of course.... for several bullets, with functions vxt(n), etc. which can be written out neatly dependent on increasing 'n', you can potentially add in an ascent(i in 1..n+1) around the whole thing... Just add a variable 'whatever' to the task in front and adjust it in, ascent around 1..n+1 whenever you call the thing to make all 'n' bullets, in other words, it can be easily modified to create multiple bullets as long as they travel along the same guidelines save for simple transformations (anything not substantially changing the formula basically, additions, multiplications, etc.). Or, as a more efficient method, simply use arrays to the same effect... We could declare;

Code: [Select]
let obj=[0,0,0,0,...,0];
ascent(i in 1..n+1){
let obj[i]=Obj_Create(OBJ_SHOT);
                         }

And of course, for each additional segment of lines in the task we will need to create an array. The whole code becomes (of course, insert the correct amount of 'zeroes' to first declare the array's size correctly);

Code: [Select]
 

 task CreateShotF11(x0, y0, xv0, yv0, graphic, delay, duration, n) {

// Define bullet object

[code]let obj=[0,0,0,0,...,0];
ascent(i in 1..n+1){
let obj[i]=Obj_Create(OBJ_SHOT);}

// Set Object initial settings

             ascent(i in 1..n+1){
Obj_SetPosition (obj[i], x0, y0);
Obj_SetSpeed (obj[i],  (xv0^2 + yv0^2)^0.5);
ObjShot_SetGraphic(obj[i], graphic);
ObjShot_SetDelay(obj[i], delay);
Obj_SetAngle (obj[i],  atan2(yv0, xv0));
    ObjShot_SetBombResist (obj[i],  false);}
let TOJ = t

// Set the speed of the object equal to the functions.
        while(Obj_BeDeleted(obj)==false) {
             ascent(i in 1..n+1){
Obj_SetSpeed (obj[i], (vxt^2 + vyt^2)^0.5);
Obj_SetAngle (obj[i], atan2(vyt, vxt);
if TOJ + duration > t {
Obj_Delete(obj);
}

}
yield;
}
}

An important thing to note with arrays:
Just like simple value variables, they can be reworked and reassigned freely.  Most importantly, you can add and remove elements to/from and array pretty easily:

Code: [Select]
let arr = [1, 2, 3];
let len = 0;


arr = arr ~ [4, 5]; // concatenates the two arrays, and reassigns the result back into arr
arr = erase(arr, 3); // removes the third element of arr, and reassigns the result back into arr
len = length(arr); // gets the length of the array, and assigns it into the variable len

Using these structures make it easier to create code that deals with dynamic amounts of bullets.  For instance, lets say you wanted to use your code to make structures with different amounts of bullets, but in the same pattern.  Using the structure I just demonstrated, you could use the same code for a pattern with 5 bullets as the pattern with 100 bullets.

Also, you should note that your code, as written above, will give an error, since you are trying to call Obj_BeDeleted() on an array, not an object ID.  The array code I gave can help with that, as well, since you can use it to check each individual element of the array and remove deleted ones from the array as applicable.


<tangentalthought>Hmm ... maybe I should do a tutorial on arrays in Danmakufu when I get a spare moment ...</tangentalthought>

That won't really work, as the angle of the bullet won't change with time, it'll just become the time the function is called times five. Of course, you could just do that with angular velocity... However, what if you had something more complicated, like

w(t) = 0.1172t^6.4 - 2t^3 + arctan(5/8 * t - 7 + 3* t^0.5)

As your angle, as a function of t, with t=0 at the frame the bullet is launched? Surely you would need to adjust the angle frame-by-frame or each couple frames to maintain this part of the bullet's equation of motion. The derivative of this does not even approach a straight line, nor a set of a couple straight lines (so we could use a pair or trio of SetShotDataA). Each frame you would need to redetermine the angle as a function of the current time, in other words, put into the 'angle' variable box this function of time. In other words 'let angle = {insert function}' need be called every frame, so it need be present within the yield-loop in the task. Thus this formula need be present into the yield loop inside a task. So therefore, you need to manually put this formula into your task. (basically what I was hoping for was the possibility of having to have a way that you could put the formula somewhere else, like in the task's assigned modifiers as a string, but no luck I guess.)

Whether you have the function in-line or called as w(t) will not change how the code works as I wrote it.  I think you're getting a bit confused ...

Take the below piece of code:

Code: [Select]
function w(t)
{
   return 5 * t + 2;
}

let a = 5;
let b = 0;

@MainLoop
{
   b = w(a);
}

When we hit the @MainLoop, we set b to hold the resulting value of calling the function w on the value stored in a.

In other words, the code jumps into the function w, assigning the local (only exists inside of w) variable t to the value that is currently stored in a (in this case, it'll be 5).  The function does the calculation at that point, and returns the result (in this case, 5*5+2 = 27).  Coming back to the line where the function was called, we get the result that, at that point in time, w(a) returns 27.  That value is then assigned into b.  If another piece of code changes a, then every time we return to that line the value of b will be updated accordingly, since the function's calculation is being re-run with the new value of a passed into it.

The code below will function in exactly the same way, save for the use of a function to calculate the result:

Code: [Select]
let a = 5;
let b = 0;

@MainLoop
{
   b = 5 * a + 2;
}

The same structure can be applied regardless of how simple or complex the formula you're using is.  The only thing that makes functions more appealing with more complex functions is that it shifts the calculation out of the particular line of code, making it look nicer (and less like a huge, jumbled mess).



Overall, I think you're still trying to apply thinking from mathematics to programming, which tends to not work out.  You have to think of the computer as a dumb machine, which can only follow the most basic of instructions (cause that's pretty much what it is).  Programming, essentially, is the art of knowing how to instruct a computer, at such a basic level, so that it gets the result you wanted.


I hope this helps!
to quote Naut:
"I can see the background, there are too many safespots."
:V

Aphid

Re: A solution to some Danmakufu Problems, only if it'd...
« Reply #4 on: June 11, 2009, 02:39:45 PM »
Ah, yes, of course, you can put statements such as t = 5t into a computer, it'll just increase the thing with a factor five. Anyway, the point of the code was to create a bullet that could dynamically depend on time. When using the standard bullet functions CreateShot01, etc. you assign angle, velocity, accelleration, etc. once. You can't change them after the bullet is fired. You can have a set of speeds/angles and change it a couple times, it just doesn't work terribly well for something complicated. But you can of course assign angle, etc. every frame with an object bullet.

The thing about concatenation helps though... could save a line from the array version.

And unfortunately, using t++ alone is not enough... it's in the @mainloop, of course. It's just that, when you enter something with t in it into a bullet's velocity, etc. that it will make this thing a static value. For example, say that I entered some formula of t, f(t) into my code as a bullet's velocity somewhere, and that f(0) = 4, f(1) = 6. Now when I make a bullet at time t=0, it will get a velocity of 4. And it stays that way. At frame 1, the bullet will not change its velocity to f(1). That's why I need the bullet to change velocity at every frame (both x and y, hence vx, vy). And that can be done by either:

a) Loop a SetShotDataA a couple hundred times (equal to the amount of time the bullet is on screen) and put in the command; like

Code: [Select]
CreateShotA(<ID>, GetX, GetY, 0);
ascent(q in 0..N){
SetShotDataA_XY(<ID>, q, vx(q+t), vy(q+t), d/dt * vx(q+t), d/dt * vy(q+t), vx(q+t)/|vx(q+t)| * 20, vy(q+t)/|vy(q+t)| * 20, <shot type>);}
SetShotKillTime(<ID>, N);
                         

N equals the amount of frames the bullet need be on the screen.
Replace 't' with 'q + t' in the formula and it should work. However, it isn't the most effective method of doing this, as you can see we need to call this thing a bunch of times. Also interesting, you could reduce computing time at the cost of accuracy by having q ascend in steps of 2, 3, or more. You just get more jagged movement. It's not possible with some functions though.
Example using this version, a large round bullet going in a lissajous pattern adjusted a bit downwards, something rather hard to accomplish with just the standard functions. Notice the pi over 180-term being there to adjust for the fact that the sines in danmakufu use degrees instead of radians;

Code: [Select]
CreateShotA(1, GetCenterX, GetCenterY, 0);
ascent(q in 0..480){
SetShotDataA_XY(1, q, sin(2*(t+q)), cos(t+q) + 0.4, 2 / 180 * 3.1415 * cos(2*(t+q)), -1 / 180 * 3.1415 * sin(t + q), (sin(2*(t+q)))/|(sin(2*(t+q)))| * 20, (cos(t+q))/|(cos(t+q))| * 20, RED03);}
SetShotKillTime(1, 480);

The main problem is the 480 loops required for just a single bullet, and I cannot have it interact either.

Quote
Also, you should note that your code, as written above, will give an error, since you are trying to call Obj_BeDeleted() on an array, not an object ID.  The array code I gave can help with that, as well, since you can use it to check each individual element of the array and remove deleted ones from the array as applicable.

Ah yes, that line should be reversed with the one above it. That'll check for the object deletion for each particular object. It includes the while-loop so each object will be treated separately as it's supposed to.
« Last Edit: June 11, 2009, 03:03:57 PM by Aphid »

Nuclear Cheese

  • Relax and enjoy the danmaku.
    • My homepage
Re: A solution to some Danmakufu Problems, only if it'd...
« Reply #5 on: June 12, 2009, 12:21:38 AM »
Ah, yes, of course, you can put statements such as t = 5t into a computer, it'll just increase the thing with a factor five. Anyway, the point of the code was to create a bullet that could dynamically depend on time. When using the standard bullet functions CreateShot01, etc. you assign angle, velocity, accelleration, etc. once. You can't change them after the bullet is fired. You can have a set of speeds/angles and change it a couple times, it just doesn't work terribly well for something complicated. But you can of course assign angle, etc. every frame with an object bullet.

The thing about concatenation helps though... could save a line from the array version.

And unfortunately, using t++ alone is not enough... it's in the @mainloop, of course. It's just that, when you enter something with t in it into a bullet's velocity, etc. that it will make this thing a static value. For example, say that I entered some formula of t, f(t) into my code as a bullet's velocity somewhere, and that f(0) = 4, f(1) = 6. Now when I make a bullet at time t=0, it will get a velocity of 4. And it stays that way. At frame 1, the bullet will not change its velocity to f(1). That's why I need the bullet to change velocity at every frame (both x and y, hence vx, vy). And that can be done by either:


You are right.  The function does not carry over.  Once you've assigned the value, it is fixed for that shot, if you're just using basic CreateShot01(); other forms can give some dynamics.  However, you need to reassign the shot's speed each frame if you want to do something like what you describe - object shots are probably the ideal candidate for this, as I mention below.

a) Loop a SetShotDataA a couple hundred times (equal to the amount of time the bullet is on screen) and put in the command; like

Code: [Select]
CreateShotA(<ID>, GetX, GetY, 0);
ascent(q in 0..N){
SetShotDataA_XY(<ID>, q, vx(q+t), vy(q+t), d/dt * vx(q+t), d/dt * vy(q+t), vx(q+t)/|vx(q+t)| * 20, vy(q+t)/|vy(q+t)| * 20, <shot type>);}
SetShotKillTime(<ID>, N);
                         

N equals the amount of frames the bullet need be on the screen.
Replace 't' with 'q + t' in the formula and it should work. However, it isn't the most effective method of doing this, as you can see we need to call this thing a bunch of times. Also interesting, you could reduce computing time at the cost of accuracy by having q ascend in steps of 2, 3, or more. You just get more jagged movement. It's not possible with some functions though.
Example using this version, a large round bullet going in a lissajous pattern adjusted a bit downwards, something rather hard to accomplish with just the standard functions. Notice the pi over 180-term being there to adjust for the fact that the sines in danmakufu use degrees instead of radians;

Code: [Select]
CreateShotA(1, GetCenterX, GetCenterY, 0);
ascent(q in 0..480){
SetShotDataA_XY(1, q, sin(2*(t+q)), cos(t+q) + 0.4, 2 / 180 * 3.1415 * cos(2*(t+q)), -1 / 180 * 3.1415 * sin(t + q), (sin(2*(t+q)))/|(sin(2*(t+q)))| * 20, (cos(t+q))/|(cos(t+q))| * 20, RED03);}
SetShotKillTime(1, 480);

The main problem is the 480 loops required for just a single bullet, and I cannot have it interact either.

The much simpler way to do this is simply to track each bullet as an Object shot (in an array, or a set of arrays if necessary), and simply update the information each frame while they exist.

While not exactly the same thing, the same basic structure is used in my spellcard Random Sign 「Entropy」 found in my thread.  Basically, you track each object shot as it lives, and update its information as necessary.

The difference would be that, whereas in my example the code randomly decides to spontaneously change all of a shot's information with no real pattern, what you're aiming to achieve would have the shot information being updated every frame based on your formulae.

Ah yes, that line should be reversed with the one above it. That'll check for the object deletion for each particular object. It includes the while-loop so each object will be treated separately as it's supposed to.

What I was pointing out is that, in your script with the array, obj is an array.  If you try to say Obj_BeDeleted(obj), you'll get an error, because of the type mismatch.  What you need to do is add a counter that counts through the list of objects in the array, and checks each:

Code: [Select]
let i = 0;
while (i < length(obj))
{
   if (Obj_BeDeleted(obj[i])) // check if element number i has been deleted
   {
      // stuff when it is deleted
   }
   else
   {
      // stuff when it is not deleted
   }
   i++; // Increment the loop counter.  Forget this and the code gets stuck in an infinite loop :V
};

Since obj is an array of object IDs, we need to pull each individual object ID out of the array and check it individually.  I use the variable i to index the array; the first index is always zero and the last index is always one lower than the total number of items in the array.
to quote Naut:
"I can see the background, there are too many safespots."
:V