Site Logo


Sparen's Danmakufu ph3 Tutorials Extra Lesson 2 - Recollection: Moonlight Ray

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, Moonlight Ray.

Part 1: What will be Covered in this Lesson?

In this lesson, I will be explaining how to recreate Moonlight Ray, with explanations on how to create and change the angle of straight lasers in Touhou Danmakufu ph3. The end goal of this lesson is for you to better understand how to change the angle of a straight laser.

Part 2: How do I Create Moving Straight Lasers?

In Lesson 9, I discussed (briefly), changing the angle of a laser using non-directional lasers. In this scenario, we will not be doing something as complicated as that, so it should be easier to grasp what we will doing.

Recall from Lesson 9 that we can manipulate the angle of a straight laser using ObjStLaser_SetAngle(). Our goal is to create two lasers, one to the right and one to the left of Rumia, that change angle at a set speed as they enclose the player.

Therefore, given a set initial angle, we can use a while loop to gradually change the angle of the player. In this case, the right laser goes from approximately -15 degrees clockwise to approximately -75 degrees, and the left laser goes from approximately 165 degrees counterclockwise to approximately -105 degrees.

We will try to use one while loop to control both lasers, since their behavior is similar.

Let us create a task fireLaser that, when called, will create a laser given a parameter passed into the task, in this case a direction - 1 for the right laser and -1 for the left laser. Let's begin with the object itself.

    task fireLaser(dir){
        //As a reminder, I am splitting the lines just so that they fit in the browser window.
        //You do not need to split the lines in your code.
        let obj = CreateStraightLaserA1(ObjMove_GetX(objBoss), ObjMove_GetY(objBoss), 
                270 + 105*dir, 512, 36, 240, DS_BEAM_BLUE, 30);
    }

So far, all we have is a task that creates a single laser. Note the angle - 270 + 105*dir. dir will be -1 or 1, resulting in an initial angle of either 375 or 165, depending on which laser we want.

Now we want to move the laser towards 90 degrees. To accomplish this, we will use an ascent loop and yield.

Using a normal loop, we will first need to observe that we want the laser to move a total of 60 degrees. This will be accomplished over approximately 120 frames. Therefore, we will do the following:

    task fireLaser(dir){
        let obj = CreateStraightLaserA1(ObjMove_GetX(objBoss), ObjMove_GetY(objBoss), 
                270 + 105*dir, 512, 36, 120, DS_BEAM_BLUE, 30);
        ascent(i in 0..120){
            ObjStLaser_SetAngle(obj, 270 + 105*dir + 60/120*i * dir);
            yield;
	}
    }

In the code above, the laser will, over the course of 120 frames, set its angle to the original angle (270 + 105*dir) + a new angle, which is calculated using 60/120*i*dir. The 60/120 is how many degrees to move per frame so that after 120 frames, the laser has moved by 60 degrees. The local variable i, which goes from 0 to 120, will multiply itself against this 60/120 to determine the total number of degrees that the laser needs to be displaced from its original angle. And finally, the dir, like in the initial angle, acts to make sure that the right and left lasers act properly.

The right laser will move from 15 (375) degrees to 75, and the left one will move from 165 to 105, both over the course of 120 frames. Note that this is not an exact replica of the actual spell and that laser size, delay, and other features are different.

CHECKPOINT: How is the dir parameter used to determine the initial angle and angle increments of the right and left lasers?

Part 3: How do I Replicate Moonlight Ray?

At this point, we have most of the script done. There are two main components left - the movement and the rings. I will be leaving the movement to you, the scripter, but will cover the rings below.

The first thing to do is to watch the spell in action, and it is then that you will most likely realize that the rings are spawned every 30 or so frames, and are aimed at the player. As for the number of bullets, I will use 48 because I really can't tell how many there are. The graphics are EoSD dots, perhaps blue.

So, all we really need to do for the rings is spawn a ring of 48 bullets aimed at the player every 30 frames.

    task fireRing{
        let angleT = GetAngleToPlayer(objBoss);
        loop(48){
            CreateShotA1(ObjMove_GetX(objBoss), ObjMove_GetY(objBoss), 3, angleT, DS_BALL_SS_BLUE, 10);
            angleT += 360/48;
        }
    }

And that's that! Now, to put everything together!

Below I will post a portion of my sample code. Please note that this is in my own style (it is not tasking style), and that the movement is not necessarily accurate.

    @MainLoop{
        ObjEnemy_SetIntersectionCircleToShot(objBoss, ObjMove_GetX(objBoss), ObjMove_GetY(objBoss), 32); 
        ObjEnemy_SetIntersectionCircleToPlayer(objBoss, ObjMove_GetX(objBoss), ObjMove_GetY(objBoss), 24);
        if(count == 0){movement;}

        if(count % 30 == 0 && count >= 0){
            fireRing;
        }

        if(count % 360 == 90 && count >= 0){
            fireLaser(1);
            fireLaser(-1);
        }

        count++;
        yield;
    }

    task fireRing{
        let angleT = GetAngleToPlayer(objBoss);
        loop(48){
            CreateShotA1(ObjMove_GetX(objBoss), ObjMove_GetY(objBoss), 3, angleT, DS_BALL_SS_BLUE, 10);
            angleT += 360/48;
        }
    }

    task fireLaser(dir){
        let obj = CreateStraightLaserA1(ObjMove_GetX(objBoss), ObjMove_GetY(objBoss), 
                270 + 105*dir, 512, 36, 120, DS_BEAM_BLUE, 30);
        ascent(i in 0..120){
            ObjStLaser_SetAngle(obj, 270 + 105*dir + 60/120*i * dir);
            yield;
        }
    }

    task movement{
        while(ObjEnemy_GetInfo(objBoss, INFO_LIFE) > 0){
            ObjMove_SetDestAtFrame(objBoss, rand(GetCenterX + 90, GetCenterX - 90), rand(GetCenterY - 90, GetCenterY - 150), 60);
            wait(360);
        }
    }

And that's that! I hope you learned something about using direction multipliers. If you want to see more examples, you can always refer to Lesson 10, and I will be using direction multipliers in the future as well.

Sources and External Resources

N/A