Site Logo


Sparen's Danmakufu ph3 Tutorials Extra Lesson 4 - Recollection: This Side Down

The video for this lesson (and all extra lessons) is a video of the spell we will be replicating. In this case, it is Seija's This Side Down, which is one of the cases where she flips the screen.

Part 1: What will be Covered in this Lesson?

In this lesson, I will discuss the 2D Camera and some applications, including screen flip and screenshake.

This lesson requires familiarity with 2D backgrounds. If you are not familiar with 2D Backgrounds, please refer to Lesson 22.

Part 2: What is the 2D Camera?

When you view the playing field in Danmakufu, it is a 384x448 pixel section of the screen using render priorities between 20 and 80, surrounded by the rest of the HUD to form a 640x480 pixel window. Within the playing field, in render priorities between 20 and 80, the display is controlled by the 2D Camera.

The 2D Camera is how the playing field is viewed. If the focus were to be moved to (0,0), it would appear as if the contents of the screen had shifted by 192 pixels to the left and 224 pixels up. Other transforms have their own various effects.

If we want to zoom in on a certain character and rotate the screen like in AJS's Cacophonous Cadre, we can do so. If we want to do a Seija spin, we can do so. That's the functionality that the 2D Camera Provides.

Part 3: What are the various 2D Camera functions?

The 2D Camera functions can be found here: 2D Camera Functions. For this portion of the tutorial, we shall focus on the code AJS uses in his Cacophonous Cadre to zoom in on a target on the screen.

task ScreenZoom(targX,targY){
	Set2DCameraFocusX(targX);
	Set2DCameraFocusY(targY);
	Set2DCameraAngleZ(30);
	Set2DCameraRatio(1.5);
	wait(30);
	Set2DCameraFocusX(targX);
	Set2DCameraFocusY(targY);
	Set2DCameraAngleZ(-30);
	Set2DCameraRatio(2);
	wait(30);
	Set2DCameraFocusX(targX);
	Set2DCameraFocusY(targY);
	Set2DCameraAngleZ(10);
	Set2DCameraRatio(2.5);
	
	...//Screenshake-related code

	wait(60);
	
	Set2DCameraAngleZ(0);
	Set2DCameraRatio(1);
	Set2DCameraFocusX(GetScreenWidth/2);
	Set2DCameraFocusY(GetScreenHeight/2);

}

The 2D Camera's default focus is the center of the playing field, at (384/2, 448/2) by default. It can be changed using Set2DCameraFocusX() and Set2DCameraFocusY(). You can use this to effectively move the 'playing field' wherever you want.

To rotate the view around the Z axis, use Set2DCameraAngleZ(). In the above example, the camera is rotated first to 30 degrees, then to -30, then to 10, and finally back to the default of 0.

To zoom in, use Set2DCameraRatio(), which defaults to a value of 1 (no zoom). In the above example, the camera zooms in 1.5x, then 2x, and finally 2.5x before reverting.

However, the 2D Camera Ratio can be set with Set2DCameraRatioX() and Set2DCameraRatioY() as well. Setting negative values for these will essentially flip them. We'll review this later on.

Part 4: How do I implement 2D Screenshake?

Screenshake is simple - for a set duration of time, shake the focus of the 2D camera around, and restore it at the end. In AJS's example, he did the following:

	Set2DCameraFocusX(GetScreenWidth/2);
	Set2DCameraFocusY(GetScreenHeight/2);

This restores the camera focus to the original location, which in his case is not (192, 244) since he has a different sized playing field.

Knowing what we want to do, there are two ways to go about the actual screenshake. One way is to use random offsets, and the other is to perform the screenshake in an organized fashion, where the screen shakes by a preset or math-controlled amount (for example, having the screenshake increase and then decrease using a sine wave).

For an example of an organized screen shake, let's review AJS's code once more.

task screenshake(shakeamount,length){
	let delay = 1;
	let origshakeamount = shakeamount;
	
	loop(length){
	Set2DCameraFocusX(GetScreenWidth/2+shakeamount);
	Set2DCameraFocusY(GetScreenHeight/2+shakeamount);
	wait(delay);
	Set2DCameraFocusX(GetScreenWidth/2-shakeamount);
	Set2DCameraFocusY(GetScreenHeight/2-shakeamount);
	wait(delay);
	Set2DCameraFocusX(GetScreenWidth/2+shakeamount);
	Set2DCameraFocusY(GetScreenHeight/2-shakeamount);
	wait(delay);
	Set2DCameraFocusX(GetScreenWidth/2-shakeamount);
	Set2DCameraFocusY(GetScreenHeight/2+shakeamount);
	wait(delay);
	shakeamount-=origshakeamount/length;}
	Set2DCameraFocusX(GetScreenWidth/2);
	Set2DCameraFocusY(GetScreenHeight/2);
}

In the above code, the screenshake task takes a pattern duration (length), as well as an amplitude (shakeamount). For length cycles, the code shifts the camera four different ways in an organized fashion, taking length*4 frames to execute. After this, the camera is restored.

For a random shake approach, we can do the following:

/* *************************************************************************
*ScreenShakeA1(int, double) -- Screenshake the 2D camera for a given time with a given intensity
*Param: shaketime (int) - Time to shake for
*Param: intensity (double) - Maximum change in position
************************************************************************* */
task ScreenShakeA1(shaketime, intensity){
    while(shaketime > 0){
        Set2DCameraFocusX(GetStgFrameWidth/2 + rand(-intensity, intensity));
        Set2DCameraFocusY(GetStgFrameHeight/2 + rand(-intensity, intensity));
        shaketime--;
        yield;
    }
    Set2DCameraFocusX(GetStgFrameWidth/2);
    Set2DCameraFocusY(GetStgFrameHeight/2);
    yield;
}

In this scenario, we have our time and intensity as parameters. We log the original position of the 2D camera, and then, for shaketime frames, move the camera's focus from the center using the intensity as an upper bound on each the X and Y deviations. At the end, we restore the original camera position.

It will be left as an exercise to create a sine-based screenshake (see below Exercise and Quiz).

EXERCISE: Using the sine function, create a screenshake task ScreenShakeSine(x, n) that, over x frames, oscillates from 0 intensity to n and then back to 0.

Part 5: How do I replicate Seija's This Side Down?

It's finally time to do some Seija replication! First and foremost, the 2D background must be perfectly centered around the horizontal center line (default to 448/2 = 224). Otherwise when we perform the flip, it will be unbalanced and look weird due to there being more of the image above the center line than below.

In the spell, Seija flips the screen, then, from the top, spawns bullets in a -1..2 wave with a 30 degree offset, straight downwards. The bullet spawn pattern is random in the y aspect but preset in the x.

Let us begin with the screen flip. From above we know that Set2DCameraRatioY() can be used to flip the screen. Therefore, if we want the screen to flip, taking one full second, we can do the following:

task MainTask(){
    ascent(i in -30..31){
        Set2DCameraRatioY(1/30*-i);
        yield;
    }
}

The above starts with i at -30, AKA RatioX = 1. It then increases to 30, when the RatioX = -1. It does this over 60 frames.

After this, the bullets are fired. As stated before, the pattern is a wave from the bottom of the screen. Except that the bottom is still the top - just because the camera has changed doesn't mean that the actual coordinates you use in Danmakufu change. Code below.

task MainTask(){
    ascent(i in -30..31){
        Set2DCameraRatioY(1/30*-i);
        yield;
    }
    while(ObjEnemy_GetInfo(objBoss, INFO_LIFE) > 0) {
        let x = GetCenterX() + 192 * cos(count * 3);
        let y = rand(0, 96);
        ascent(i in -1..2) {
            CreateShotA1(x, y, 2, 90 + 30*i, DS_FIRE_RED, 12);
        }
        wait(8);
    }
}

As you can see, while the boss is alive we get our x and y positions, and fire the wave. Our y position is between 0 (the top of the screen) and 96. Since the screen has been flipped vertically, though it appears that the bullets are spawning on the bottom half and are moving up, the reality is that they're spawning in the top, moving down, and are simply flipped across the horizontal center axis of the screen.

Part 6: How do I make the 2D Camera rotate?

To close the lesson, we will have a brief discussion about replicating Seija's last boss spell, where the screen rotates around the center of the screen.

As stated earlier, the camera focus defaults to the center, and Set2DCameraAngleZ() can be used to set the rotational angle of the 2D Camera.

Using this, one way to rotate the screen (background and all) is to adjust the angle of the 2D Camera over time using a while loop. We can make a task that handles this:

task RotateScreen(change, time, dir) {
    loop(time) {
        Set2DCameraAngleZ(Get2DCameraAngleZ() + change/time * dir);
        yield;
    }
}

In this scenario, change is the number of degrees to rotate, time is how long it takes to perform the rotation (in frames), and dir is the direction of rotation.

Quiz: 2D Camera Functions

1) Seiga, having been forgotten after Seija came into existence, wants to cash in on Seija's popularity. With vengeance, she plans to create a combination script utilizing both her and Seija's abilities. However, she is befuddled when upon capturing a spell in the middle of the screenshake animation, the screen does not return to its original position. Which function can be used to reset the 2D Camera?

A. SetCameraRadius()
B. Set2DCameraRatio()
C. Reset2DCamera()

2) In an inspirational burst of sadism, Seija decides to have the screen flip indefinitely while rotating. Write a task below to handle this.

Hit 'Show' to show possible answers.

One possible answer is below.

    task Koishi {
        let count = 0;
        while(ObjEnemy_GetInfo(objBoss, INFO_LIFE) > 0) {
            Set2DCameraRatioX(cos(count));
            Set2DCameraAngleZ(count/2);
            count++;
            yield;
        }
    }

Sources and External Resources

N/A