Site Logo


Sparen's Danmakufu ph3 Tutorials Lesson 22 - Implementing 2D Backgrounds in Background Scripts

The video for this lesson is TalosMistake's Yuyuko. Enjoy the beautiful backgrounds, which you will soon learn to make (or well, the 2D ones).

Part 1: What will be Covered in this Lesson?

In this lesson, I will discuss Background Scripts as well as how to implement various types of 2D backgrounds by transforming their component 2D sprites (simple scrolling, rotating).

This lesson requires familiarity with 2D Sprites. If you are not familiar with 2D Sprites, please refer to Lesson 13.

Part 2: What are Background Scripts?

We've covered a lot of different script types now. System, Replay Save, Pause, End Scene... But now we'll touch on Background scripts. Like System scripts, you link to them in the Danmakufu header, and they run for the duration of a script. You can use the same background script for stages, plurals, and singles, and they generally work well.

Note that although you can *technically* dump whatever you want in a Background script, they are generally used to run tasks for stage/nonspell backgrounds and spell backgrounds. If you have any graphical effects that do not affect gameplay, like falling cherry petals or the like, the Background script is where you put them.

Part 3: How are Background Scripts structured?

We will begin our discussion of Background scripts by dissecting the default, located in default_system/Default_Background_IceMountain.txt. Naturally, similarly to System scripts, you can name your Background script however you want, as long as you link to it properly.

Inside the default Background script, there is a field, bSpell, which is controlled in @MainLoop. Contents of @MainLoop are below:

@MainLoop
{

	let objScene = GetEnemyBossSceneObjectID();
	if(objScene != ID_INVALID && ObjEnemyBossScene_GetInfo(objScene, INFO_IS_SPELL))
	{
		bSpell = true;
	}
	else
	{
		bSpell = false;
	}
	

	yield;
}

As you can see, we get the EnemyBossScene Object, and if it exists (i.e. on a boss), we check if there's a spell going on. If so, bSpell turns true, otherwise false. This is how we control the Spell Background.

In @Initialize, we will ignore all the camera and fog commands, as those are used for 3D Backgrounds. All standard Background scripts will have a task for the stage background (TNormalBackground in this case) and a task for the spell background (TSpellBackground). In this case, TNormalBackground is called three times - that is due to the way it is structured for the package example - usually one task is fine, depending on what you want to accomplish.

At the start of the task, we define all the paths to the textures we want to use, and then create the relevant render objects (2D sprites). It is highly recommended to use render priorities of 0.21 and, if necessary, 0.22 for effects that are continuously spawned and must render over other background components. I personally advise against using more than three render priority slots. Instead, in order to layer specific textures over others, keep in mind that the order in which you create 2D sprites dictates the order in which they are created in the background.

For the default background, for example, obj2 is created after obj1. Although they have the same render priority, obj2 will render above obj1.

After creating your textures, a standard loop begins. The default background tasks use the following to fade out the stage background:

	let frameInvisible = 0;
	...
		if(bSpell)
		{
			frameInvisible++;
			if(frameInvisible >= 60)
			{
				Obj_SetVisible(obj, false);
			}
		}
		else
		{
			frameInvisible = 0;
			Obj_SetVisible(obj, true);
		}

Essentially, after 60 frames, the object in the background turns invisible. If you have multiple objects in the background to turn off, you would set all of them to invisible or visible depending on the situation.

The spell background is similar, but has slightly different structure.

Part 4: How do I make a static 2D background?

Now that we have a basic understanding of how things work, let's begin with a static 2D background. To begin, recall that the playing field uses render priorities 20 to 80. For a refresher on render priorities, refer to Lesson 13. Additionally, recall that the standard playing field is 384x448 pixels.

With textures in Danmakufu, due to the underlying graphics engine (DirectX), it is highly recommended that you use 512x512 or similar multiples of 2 in the dimensions of your images. As an example, we will center a 512x512 image in the middle of the playing field.

task TSpellBackground {
    let path1 = GetCurrentScriptDirectory() ~ "img/sp1bg.png";

    let obj1 = ObjPrim_Create(OBJ_SPRITE_2D);
    ObjRender_SetBlendType(obj1, BLEND_ALPHA);
    Obj_SetRenderPriority(obj1, 0.21);
    ObjPrim_SetTexture(obj1, path1);
    ObjSprite2D_SetSourceRect(obj1, 0, 0, 512, 512);
    ObjSprite2D_SetDestRect(obj1, -256, -256, 256, 256);
    ObjRender_SetPosition(obj1, 384/2, 448/2, 1);

    let frame = 0;
    let alpha = 0;

    loop {
        if (bSpell) { //Transition from stage background
            alpha = 255;
        } else {
            alpha = 0; //If no longer spell
        }

        Obj_SetVisible(obj1, alpha > 0);

        frame++;
        yield;
    }
}

Note that we set the Source and Dest rects in such a way that the image is centered at the specified position. You can use SetDestCenter, but for backgrounds, where it is common to manipulate images, using DestRect is preferred so that you can easily scale the image. If you would like to tile the image, you can manipulate the source and dest rects to scale the image to whatever size and shape you want.

In the above example, note that the image is only visible when alpha > 0. This is used for transitioning between the stage and spell background tasks.

If we want to add more background images, simply add the new objects. You may want to fiddle with alpha values and blend types if you want to stack multiple images on top of one another to create interesting effects.

To provide another example, let's say we have a 32x32 image that we would like to tile across the screen. Using the code above, it would tile across the entire screen without any changes to the code.

Part 5: How do I make a scrolling 2D background?

Now we would like to scroll images. To do this, we will manipulate the Source Rects of the image. Since Source Rects wrap, we can take advantage of that property to essentially provide the image of a scrolling image without actually moving the image. Essentially, what we're doing is the following:

The aqua box on the left texture shows the source rects. The aqua box on the right shows the rendered image given the source rect on the left texture. As you can see, by moving the source rect, we can provide the impression that the image is scrolling in the opposite direction.

To put this example into practice, let's observe the following:

task TSpellBackground {
    let path1 = GetCurrentScriptDirectory() ~ "img/sp1bg.png";
    let path2 = GetCurrentScriptDirectory() ~ "img/bgfog.png";

    let obj1 = ObjPrim_Create(OBJ_SPRITE_2D);
    ObjRender_SetBlendType(obj1, BLEND_ALPHA);
    Obj_SetRenderPriority(obj1, 0.21);
    ObjPrim_SetTexture(obj1, path1);
    ObjSprite2D_SetSourceRect(obj1, 0, 0, 512, 512);
    ObjSprite2D_SetDestRect(obj1, -256, -256, 256, 256);
    ObjRender_SetPosition(obj1, 384/2, 448/2, 1);

    let obj2 = ObjPrim_Create(OBJ_SPRITE_2D);
    ObjRender_SetBlendType(obj2, BLEND_ALPHA);
    Obj_SetRenderPriority(obj2, 0.21);
    ObjPrim_SetTexture(obj2, path2);
    ObjSprite2D_SetSourceRect(obj2, 0, 0, 512, 512);
    ObjSprite2D_SetDestRect(obj2, -256, -256, 256, 256);
    ObjRender_SetPosition(obj2, 384/2, 448/2, 1);

    let frame = 0;
    let alpha = 0;

    loop {
        if (bSpell) { //Transition from stage background
            alpha = 255;
        } else {
            alpha = 0; //If no longer spell
        }

        Obj_SetVisible(obj1, alpha > 0);
        Obj_SetVisible(obj2, alpha > 0);
        ObjSprite2D_SetSourceRect(obj2, 0, 0 + (frame*2) % 512, 512, 512 + (frame*2) % 512);

        frame++;
        yield;
    }
}

This is the same code as above, except that we now have bgfog as obj2. It is a seamless texture, which means that if it were to wrap, you wouldn't notice the edges of the image. You can also use tileable textures.

In the loop, we edit the source rect. Note the (frame*2) % 512 - this is what's scrolling it. As the frame increases, the numbers rise, going up to 512. As a result, the source rect gently moves downwards, and after 256 frames, it resets to its original position without any noticeable teleportation (the number you mod by, in this case 512, must be a multiple of the image dimensions).

And that's one way to scroll 2D images. You do the same for the source rects of 3D images.

Part 6: How do I make a rotating 2D background?

For rotation, instead of changing source rects, we will instead take advantage of ObjRender_SetAngleZ().

What this function does is rotate a 2D image on the cartesian x-y plane. In other words, it's rotating around the z-axis. Similarly, ObjRender_SetAngleX() and ObjRender_SetAngleY() rotate the image around the X and Y axes, on the y-z and x-z planes respectively.

Usage is simple. See the following example:

task TSpellBackground {
    let path1 = GetCurrentScriptDirectory() ~ "img/sp1bg.png";

    let obj1 = ObjPrim_Create(OBJ_SPRITE_2D);
    ObjRender_SetBlendType(obj1, BLEND_ALPHA);
    Obj_SetRenderPriority(obj1, 0.21);
    ObjPrim_SetTexture(obj1, path1);
    ObjSprite2D_SetSourceRect(obj1, 0, 0, 512, 512);
    ObjSprite2D_SetDestRect(obj1, -256, -256, 256, 256);
    ObjRender_SetPosition(obj1, 384/2, 448/2, 1);

    let obj2 = ObjPrim_Create(OBJ_SPRITE_2D);
    ObjRender_SetBlendType(obj2, BLEND_ALPHA);
    Obj_SetRenderPriority(obj2, 0.21);
    ObjPrim_SetTexture(obj2, path1);
    ObjSprite2D_SetSourceRect(obj2, 0, 0, 512, 512);
    ObjSprite2D_SetDestRect(obj2, -256, -256, 256, 256);
    ObjRender_SetPosition(obj2, 384/2, 448/2, 1);

    let frame = 0;
    let alpha = 0;

    loop {
        if (bSpell) { //Transition from stage background
            alpha = 255;
        } else {
            alpha = 0; //If no longer spell
        }

        Obj_SetVisible(obj1, alpha > 0);
        ObjRender_SetAngleZ(obj1, frame);

        Obj_SetVisible(obj2, alpha > 0);
        ObjRender_SetAngleZ(obj2, -frame);

        frame++;
        yield;
    }
}

In this example, we have two of the same image, rotating around their centers (their position, as set in ObjRender_SetPosition()) with a change of frame degrees. In other words, in 6 seconds, they will make a complete rotation. Usually, it is recommended to have two images rotating in opposite directions to give a feel of balance to your background.

EXERCISE: In a background script, try out rotating images around different points on the screen. What arrangements are visually appealing? What spin frequencies are distracting?
EXERCISE: In a background script, try to rotate images using Border of Wave and Particle style angle incrementation, using a different variable for holding the rotation angle of the images and adding sin(frame*n) to this new variable every frame.

Part 7: How do I layer textures with varying Alpha and Blend Types?

We've finally reached blend types and layering. First and foremost, please do feel free to utilize multiple render priorities - for some backgrounds, it provides immense flexibility.

Anyways, let's say you have two images, each of which have alpha of 255 for every pixel. In Danmakufu, you set the alpha of each to 128, and naively render them on top of each other with BLEND_ALPHA (the default). What do you see? Well, a mess... that's rather dark. What you're essentially doing is adding the rgb of the initial bottom layer (black) with half the rgb of one image and half the rgb of the other. It's not necessarily going to look good.

In more complex backgrounds, you will want to play with different images rendered on top of others, with scrolling effects and rotating objects (as well as scaling objects too, perhaps) being used to enhance the experience. It's very important to consider how much of each image to show - for a fog layer, you may want a low alpha, for example, even if the source image is already mostly transparent. Rendering at 255 alpha is going to be exceptionally rare, unless it's a base background image upon which other things are placed.

Another thing to keep in mind is contrast. You will want good contrast between the background and the bullets. Usually, since bullets tend to be bright, this means that the spell background must be dark. The only exceptions are cases where the background is very bright and the bullets are very dark, such as with Tsurubami Senri and Tsubakura Enraku's attacks in the Len'en Project. However, this is definitely not the norm.

If you start out with a bright base background, you may therefore be tempted to dump a large number of dark images on top. However, you have blend types to spice things up. In general, ADD_RGB and MULTIPLY are big no-nos for spell backgrounds, as ADD_RGB is not flexible (no matter what alpha you assign to it, it always renders the same way) and MULTIPLY can be unintuitive to control. Also, whatever you do, do NOT use INV_DESTRGB. We don't need more of that eye-scarring invert, especially in a background.

The most useful blend types will be BLEND_ADD_ARGB and BLEND_SUBTRACT. ADD_ARGB provides shininess in a way that can be easily controlled by changing the alpha value of the render object. Subtractive blend is a rather easy way to darken an image - using a white fog background to cast a black fog over everything else, for example. Do be aware that if you choose to change the color of an image with Danmakufu's ObjRender_SetColor(), subtractive blend will not handle those colors the same way as with ALPHA or ADD.

Quiz: Background Scripts

1) Sanae is making a scrolling background with two components - one that should move in the bottom right direction and one that should move in the bottom left direction. However, instead of moving in the bottom right and bottom left directions, they're moving in the top left and top right directions. Why is this?

A. The destination rects are not properly implemented.
B. The initial position of the render objects is incorrect.
C. In her source rects, she is adding frame % 512 instead of subtracting.

2) Hina is making a spinning background with six components, each of which spins at a different rate. Each component spins faster in a linear fashion, with the first component spinning at x degrees/second, the second at 2x, and so on and so forth. She wants all of them to line up once again in exactly 360 frames. What is n?

A. frame
B. frame / 6
C. frame ^ 0.5

Summary

  • Background Scripts are run the same way as System scripts
  • Rendering images in Background Scripts is the same as rendering images anywhere else in Danmakufu
  • When scrolling via SetSourceRect, adding frames scrolls in the opposite direction

Sources and External Resources

N/A