Site Logo


Sparen's Danmakufu ph3 Tutorials Extra Lesson 1 - Recollection: Eternal Meek

The video for this lesson (and all extra lessons) is a video of the spell we will be replicating. In this case, it is, as you have probably realized by now, Eternal Meek, Sakuya's signature spell.

Part 1: What will be Covered in this Lesson?

In this lesson, I will be explaining the usage of rand in greater detail, how to recreate rand_int, how to index arrays using rand, and will show, step by step, the components of Eternal Meek and how we can replicate it.

Part 2: How do I use rand?

We've used rand before. You give rand two numbers - these are your bounds. rand() will generate a floating point number between these (it will, for practical purposes, never be an integer). Note that regardless of whether or not the bounds are included, it is nearly impossible to get exactly those bounds, so always use rand() assuming that the result will have a multitude of numbers after a decimal point.

Part 3: What Functions Can I Use to Truncate Floats?

We will be creating a function called rand_int shortly, but before we do this, we must discuss the various built-in ways to truncate floating point numbers (anything with a decimal point) to integers in Touhou Danmakufu ph3. Our main reference will be Math Functions.

The first function we will look at is round(). This function will round the decimal to the nearest integer using standard rules. Anything .5 or greater will round up, anything less than .5 will round down.

round(0.006);     //0
round(3.500);     //4
round(4.566);     //5
round(6.246);     //6
round(-1.836);    //-2
round(-9.496);    //-9

The second function we will look at is truncate(). This function will slice off the decimal place, leaving the integer component only. It can also be referred to as trunc()

truncate(0.006);     //0
truncate(3.500);     //3
trunc(4.566);        //4
trunc(6.246);        //6
truncate(-1.836);    //-1
round(-9.496);       //-9

The third function we will look at is ceil(), short for ceiling. This function will always round up. Think of a number line - ceil will return the next integer to the right.

ceil(0.006);     //1
ceil(3.500);     //4
ceil(4.566);     //5
ceil(6.246);     //7
ceil(-1.836);    //-1
ceil(-9.496);    //-9

The last function we will look at is floor(), which is similar to ceiling. This function will always round down. Think of a number line - ceil will return the next integer to the left.

floor(0.006);     //0
floor(3.500);     //3
floor(4.566);     //4
floor(6.246);     //6
floor(-1.836);    //-2
floor(-9.496);    //-10

Now that we have these, we will create rand_int().

Part 4: How do I Write My Own rand_int?

The function of rand_int() is to return a random integer in between two bounds, inclusive. To do this, which of the four functions should we use?

Of the four, trunc, ceil, and floor all exclude one or both of the bounds. Therefore, the only option here is to use round(). So how exactly would we go about making this function?

Let's start with the skeleton. We have a function that takes in two integer parameters and returns an integer. So let's write an empty function.

function rand_int(min, max){

}

Of course, nothing will happen if we run this function, and using it assuming it will return something will result in an error. So we need the contents of the function.

Our aim is to use round with the two bounds to obtain a random integer between the two bounds, inclusive. And of course... we can use rand! A completed rand_int() is presented below.

function rand_int(min, max){
  return round(rand(min, max))
}

This works by first running rand(). This obtains a floating point value between the two bounds, and then runs round() on it, turning it into an integer.

Note that using round(rand(min, max)) results in a bias against the first and last numbers. Alternate and perhaps more effective ways to implement rand_int include truncate(rand(min, max + 1)), etc.

Of course, what are the uses of rand_int?

Part 5: What are the Uses of rand_int?

rand_int is used whenever you need a random integer. This often happens when you want a specific graphic in a range - you can use rand_int(min, max) to obtain a random integer between min and max inclusive.

But let's say you don't want the one in the middle. For example, you want 30-36, but not 33. How could you do this? Well, rand_int provides a solution. Remember that you index arrays starting from 0 and then incrementing from there. So we can do the following!

let graphic = [30, 31, 32, 34, 35, 36][rand_int(0, 5)];

The above code will create an array with [30, 31, 32, 34, 35, 36], and will then index that array using the [rand_int(0, 5)]. The rand_int will spit out an integer between 0 (corresponding to 30) and 5 (corresponding to 36). This is a nifty trick that you may consider using. (Credits to Infinite Ultima Wave for teaching me that this exists)

Quiz: rand_int

1) Let's say we run rand_int(0, 50). Which of the following is not a possible result?

A. 50
B. 4.5
C. 30
D. 0
E. 12

2) What is a possible output of trunc(absolute(rand(-60, 60)));

A. -30
B. 60
C. 59
D. 0.5

Part 6: How do I Replicate Eternal Meek?

So it's finally time for the main show - Eternal Meek! We will begin with an analysis of the spellcard.

The first thing to note is that this 13 second spell has Sakuya stationary in one location. This means that there will be no movement at all during the attacking. The next thing to note is that the bullets are all spawned from directly on top of her position with a high delay. This will inform the player to beware of faster bullets, and indeed, the bullet speeds are fast.

Based on how often the SFX plays, it is safe to assume that there is a bullet every frame, or a bullet every two frames. This allows us to formulate our MainTask, which we will call once at the end of @Initialize. For the purposes of our replica, we will have one bullet per frame.

task MainTask{
    ObjMove_SetDestAtFrame(objBoss, GetCenterX(), 60, 60)
    wait(120);
    while(ObjEnemy_GetInfo(objBoss, INFO_LIFE) > 0){
	//We will be inserting the bullet spawning here.
	yield;
    }
}

In the above code, I have set her position to GetCenterX(), 60. This may not be the actual position, but it should do for now. I have also put in a 60 frame delay from the end of the movement to the start of the actual bullet spawning. Now, for the actual bullet spawning.

Each bullet has the same graphic and has the same starting position, but has a different angle and speed. Therefore, we will use rand(0,360) for the angle and rand(3,6) for the speed. For all practical purposes, speeds of 2 and below are slow, 2-4 are moderate, and anything over 4 usually requires a delay laser or for the speed of the bullet to be obvious. In the case of Eternal Meek, the large delay clouds and the occurrence of the spell late in the game help justify the presence of bullets with such high speeds.

Now that we have our arguments prepared, we can fill in our code.

task MainTask{
    ObjMove_SetDestAtFrame(objBoss, GetCenterX(), 60, 60)
    wait(120);
    while(ObjEnemy_GetInfo(objBoss, INFO_LIFE) > 0){
	CreateShotA1(ObjMove_GetX(objBoss), ObjMove_GetY(objBoss), rand(3, 6), rand(0, 360), DS_BALL_S_BLUE, 20);
	yield;
    }
}

And there we have it - Eternal Meek, the easiest spellcard to replicate in all of Touhou.

Sources and External Resources

N/A