Touhou Danmakufu/Nuclear Cheese's Drawing Tutorial
- Return to Touhou Danmakufu
Download them at: www.stephenware.com/th_dnh/bg_ex.zip
- 1 Section 0: Before You Try This
- 2 Section 1: The Basics
- 3 Section 2: Simple Effects
- 4 Section 3: Section 3: Intro to 3D
- 5 Section 4: Rotation in 3D
- 6 Section 5: The 3D Depth Buffer and Fog
- 7 Section O: Outro (for now)
Section 0: Before You Try This
This tutorial assumes you have a basic familiarity with danmakufu scripting in general. If you don't understand that yet, this tutorial won't really help you. Sorry. I recommend looking at the Touhou Danmakufu section. There's also a danmakufu-specific wiki.
Section 1: The Basics
Okay, first off, download the sample code: www.stephenware.com/th_dnh/bg_ex.zip
Extract it somewhere inside of your danmakufu scripts directory.
For this section, we will be looking at the script file bg1.txt
First off - if you've worked with danmakufu code before, you've probably seen some of the drawing functions. In fact, if you've made a spellcard script which has an enemy character, you've already used some of what I'm going to show here. This section introduces the simple commands to draw 2D graphics in danmakufu.
LoadGraphic ( <the_image> ) - This function will load an image file into memory, and get it ready for danmakufu to use. This should only be called once for each image you want to load. <the_image> can either be a string indicating the path of the file, or a string variable holding that path. Important note: if danmakufu can't find the file you give it, there will not be any warning or error, and any graphic you try to draw with that image will just not appear.
SetTexture ( <the_image> ) - This will set the given image file as the currently active one. This means that it will be the one that will be drawn when you instruct danmakufu to draw, until you change it with another call to SetTexture.
SetGraphicRect ( <left-side x> , <top-side y> , <right-side x> , <bottom-side y> ) - This will set the region of the selected image file to draw. To draw all of an image file that is M pixels wide by N pixels tall, use SetGraphicRect(0, 0, M, N). The coordinates of the region selected is known as "texture coordinates."
IMPORTANT NOTE on texture coordinates:
- The left and top side coordinates are the pixel you want to start with, with (0, 0) being the upper-left corner. The right and bottom side coordinates are one past the coordinates of the last pixel in the image you want to use.
- Think of it this way. The left and top coordinates give a >= (greater than or equal) limitation, while the right and bottom coordinates give a < (less than) limitation.
DrawGraphic ( <x> , <y> ) - This draws the currently selected image to the screen at the given coordinates. These coordinates are the same as the coordinates used for positioning the player, enemy, and bullets. Important note: the coordinates you pass this function give where the CENTER of the image will be drawn.
DrawText ( <text> , <x> , <y>, <size>, <alpha> ) - This will draw text on the screen. <text> can be just about any basic value, including both strings and numbers. The coordinates passed here indicate the top-left of where the text is output. <size> determines how big the font will be, by giving the height of the text. <alpha> is used for alpha-blending, which will be discussed later in Section 2.
DeleteGraphic ( <the_image> ) - This will remove an image file from danmakufu's memory. This is usually called in your script's @Finalize, to clear up the memory that was used by the graphic.
Using These Commands
To draw a graphic, the following must be done:
1) In your script's @Initialize, you must load the image file using LoadGraphic(). 2) In your script's @DrawLoop or @BackGround, you must do the following: 2a) Set the current image to the image file you want using SetTexture() 2b) Set the texture coordinates (the area of the image file to use) with SetGraphicsRect() 2c) Draw the graphic, using DrawGraphic()
To draw text, simply call the DrawText() function in either @DrawLoop or @BackGround
Important: anything that you draw is cleared the next frame, so things must be drawn every frame for them to continuously appear on the screen. This isn't much of an issue, though, since @BackGround and @DrawLoop are both run every frame anyway.
- Whenever using an image file, setting a pixel to pure black (RGB 0 0 0) will result in that pixel being transparent when drawn in danmakufu. If you want a black color, use a close RGB value (for example, RGB 1 1 1).
- I might be missing something, but as far as I can tell @BackGround only works in stage scripts, not in enemy scripts. This sort-of makes sense, anyways.
- As you might notice in the script file, I used a variable named iamgefile to store the path to the image I was using. This is a good idea, and makes it easier to reference graphic files generally.
- DrawText() can be useful for quite a few things. When I'm having some trouble getting a script to work correctly, I sometimes use DrawText() to output some key variables to make sure they're taking on the right values. You can also use this function to make a simple menu in your script.
Section 2: Simple Effects
Here, I'll show you how to do a few simple effects to add to the rendering we did in Section 1. For this section, we will be looking at the script file bg2.txt
To tile a graphic means to display multiple copies of it next to each other, basically. Tiling a graphic is actually really simple in danmakufu.
When passing arguments to the SetGraphicRect() command, if you set an argument outside of the range of the image file's coordinates, danmakufu will automatically wrap around the image and tile it for you!
For example, if we have an M by N image, and we want to tile it twice horizontally, we could say: SetGraphicRect(0, 0, 2*M, N); and danmakufu would automatically tile two copies of the texture horizontally.
Rotation in 2D
To rotate a graphic, we use this command:
SetGraphicAngle ( <rotation about x> , <rotation about y> , <rotation about z> );
However, since we're working in 2D graphics, we will always set <rotation about x> and <rotation about y> to zero. Set <rotation about z> to the angle you want to rotate the graphic, in degrees.
Just like the other "Set" functions, this needs to be called before you call DrawGraphic().
Scaling, or changing the size of a graphic, is pretty simple. Just use:
SetGraphicScale ( <x-scale> , <y-scale> );
This will make the drawn graphic <x-scale> times as wide, and <y-scale> times as tall.
Just like the others, SetGraphicScale() comes before you call DrawGraphic().
Alpha blending is a technique which gives objects a transparent appearance. To use alpha blending, use the following command:
SetAlpha ( <alpha value> );
<alpha value> is a value between zero and 255, and indicates how opaque the drawn object is. At 255, it is fully solid, which at zero, it is completely see-through. Giving values in-between yields a partially see-through graphic being drawn.
Again, SetAlpha() must come before the DrawGraphic() you want it to have an affect.
The Importance of Drawing Order
When drawing things like we are doing currently, the order in which things are drawn is very important.
To make it simple, think of each object we draw as a separate layer we put on top of the previous things that have been drawn. If the new object overlaps an older one, the new one will be seen over the older one.
Danmakufu's drawing algorithms work the same way here. Every item you draw will obscure anything else that is under it (more or less depending on the current alpha value, of course).
The Updated Draw Function Order
With what we know now, here's the full way to draw a graphic:
1) In your script's @Initialize, you must load the image file using LoadGraphic() 2) In your script's @DrawLoop or @BackGround, you must do the following: 2a) Set the current image to the image file you want using SetTexture() 2b) Set the texture coordinates (the area of the image file to use) with SetGraphicsRect() 2c) Set the alpha value with SetAlpha() 2d) Set the rotation with SetGraphicAngle() 2e) Set the scaling with SetGraphicScale() 2f) Draw the graphic, using DrawGraphic()
- Of course, you do not need to set every different value for every different thing you draw. For example, in the sample code I only call SetTexture() once and then use it to draw two instances of that graphic on screen.
- In a similar vein, ALWAYS set, at the beginning of @DrawLoop or @BackGround, any value you change later. You'll notice that I call SetAlpha(255); at the start of @BackGround. Normally, this would be unnecessary, since that is the default value. But, since I later call SetAlpha(160);, I need to clear that value (which is left over from the last run-through of @Background), or it will apply to the first image as well.
Section 3: Section 3: Intro to 3D
Here, we start to look at danmakufu's 3D capabilities. For this section, we will be looking at the script file bg3.txt
3D Coordinates in Danmakufu
When learning to draw in 3D, the first thing to learn is, of course, the coordinate system.
Danmakufu's coordinate system can be pretty simply described:
- the Y axis is vertical, with positive pointing upward
- the X and Z axes make the horizontal plane.
The Camera Analogy
When working with 3D graphics, it helps to think of the view as a camera, looking at a certain point in space. To set up the camera for 3D drawing in danmakufu, use these functions:
SetViewTo ( <x> , <y> , <z> ) - Sets the point in space which the camera is looking at. This, as you can see, is given in simple three-space coordinates.
SetViewFrom ( <distance> , <horizontal_angle> , <vertical_angle> ) - Sets the point at which the camera itself will be positioned. This is a bit trickier than the SetViewTo() function, though.
- <distance> indicates how far away from the target point the camera will be.
- <horizontal_angle> sets the angle the camera will be at relative to the target point on the horizontal plane. I usually use 90, which makes the x axis left and right on the screen, with right being positive.
- <vertical_angle> sets the angle the camera will be at relative to the target point vertically. Positive values move it above the target point, looking downward.
Drawing in 3D
When it comes to actually drawing a graphic in 3D with danmakufu, we use this function:
DrawGraphic3D ( <x> , <y> , <z> );
This works much like its 2D counterpart, DrawGraphic(), except that the graphic is given a three-space coordinate.
Important Note: The graphic will be drawn as a FLAT image in three-space. You can rotate it around to change its angle (covered in Section 4). To create a 3D object, you need to draw multiple graphics at different positions and angles (seen briefly in Section 5).
The Basic 3D Drawing Order
This works much like the drawing order for 2D graphics:
1) In your script's @Initialize, you must load the image file using LoadGraphic(). 2) In your script's @DrawLoop or @BackGround, you must do the following: 2a) Set the current image to the image file you want using SetTexture() 2b) Set the texture coordinates (the area of the image file to use) with SetGraphicsRect() 2c) Set the alpha value with SetAlpha() 2d) Set the rotation with SetGraphicAngle() ... this will be covered in detail in Section 4. 2e) Set the scaling with SetGraphicScale() 2f) Draw the graphic, using DrawGraphic3D()
- If you run the sample program, you'll notice that the image is drawn upside-down. This has to do with the Y axis being up-positive in 3D, while in 2D it was generally taken as down-positive. We can fix this with what is shown in Section 4.
- To be honest, I haven't tried using 3D drawing in a @DrawLoop function. It might only work in @BackGround.
Section 4: Rotation in 3D
This is where things get fun. And by 'fun', I mean please don't crawl into the fetal position and start crying; it distracts the other students. ^_^;;
For this section, we will be looking at the script file bg4.txt
How Rotation Works in 3D
In 2D, we gave a single rotation value, which rotated an image around its center.
In 3D, however, its not quite that simple. To give an angle of rotation in 3D, we need to provide three angles to danmakufu. Each one of these represents rotating the image around one of the cardinal axes (X, Y, Z).
--> Be sure to run the example script for this section. It will give you insight on how the rotation in Danmakufu works.
Important Note: From my testing, it seems that Danmakufu applies the rotation in the following order:
- first it rotates about the Z axis
- second about the X axis
- finally about the Y axis.
Rotating in 3D
To rotate a 3D graphic in danmakufu, we use the SetGraphicAngle() function, just like we did in Section 2 to rotate 2D images. The main difference this time is that we can supply a value in all three fields, for the rotation angles around each of the axes.
- Take some time and try to absorb how rotation in danmakufu works. It took me a while to figure this much out, so don't feel bad if you don't get it immediately at first.
- Observe how each image rotates in the sample script. Especially notice the one in the lower right, which shows danmakufu applying each rotation in order.
Section 5: The 3D Depth Buffer and Fog
This section deals with a couple functions that can make things easier to render and even look a bit cooler. For this section, we will be looking at the script file bg5.txt
The Depth Buffer
The depth buffer is a special thing used in 3D graphics. What it does is, when it renders a 3D graphic, it also notes at each pixel how far away from the camera the graphic is at that pixel. When a subsequent graphic is drawn at the same pixel, the renderer will only draw it if it is closer to the camera than the old graphic at that pixel.
In simpler terms, this makes sure things are drawn in front of each other correctly.
To enable the use of the depth, buffer, you need to call these two functions:
WriteZBuffer ( <enable> )
UseZBuffer ( <enable> )
For both, set <enable> to true to enable them, or false to disable them.
WriteZBuffer() enables or disables writing to the Z (depth) buffer, while UseZBuffer() enables or disables using the depth buffer test when drawing over another graphic. Generally, you'd have them both on or both off, but there are some effects that could require using them otherwise.
These functions will generally be set first thing in your drawing function.
Fog is actually a very simple effect. Basically, as things get further away from the camera, they start to fade into a pre-set color.
To set up Fog, use the following:
SetFog ( <start_distance> , <end_distance> , <r> , <g> , <b> );
<start_distance> sets the distance from the camera at which the fog will start to take effect, while <end_distance> determines the end of the fog range; anything beyond <end_distance> will be rendered purely in the fog's color.
Of course, <r> <g> <b> are the color values for the fog.
Usually you would put the SetFog() function at the beginning of the drawing function, like with the depth buffer functions.
The New 3D Draw Function Order
1) In your script's @Initialize, you must load the image file using LoadGraphic(). 2) At the beginning of your @DrawLoop or @BackGround: 2a) Enable or disable the depth buffer, with WriteZBuffer() and UseZBuffer() 2b) Set up the fog, with SetFog() 3) For each graphic you want to draw: in your script's @DrawLoop or @BackGround, you must do the following: 3a) Set the current image to the image file you want using SetTexture() 3b) Set the texture coordinates (the area of the image file to use) with SetGraphicsRect() 3c) Set the alpha value with SetAlpha() 3d) Set the rotation with SetGraphicAngle() 3e) Set the scaling with SetGraphicScale() 3f) Draw the graphic, using DrawGraphic3D()
- Unlike most "Set" functions, the fog setting is cleared each time, so you need to call SetFog() every time.
- In the sample script, you have a box drawn, while the camera rotates around it. There are a couple things to notice:
- First: If you look at the script, you'll notice I never draw the bottom of the box. This is because it is never seen; not drawing a part that is never seen will save some processing power.
- Second: Near the top of the file, notice the variable depth_buffer_enable. If you change its value to false and then save and run the script, you'll see what happens when you don't use a depth buffer.
Section O: Outro (for now)
Well, I hope this clears some things up about the danmakufu drawing functions. Feel free to comment and such.
I might come up with some more topics to add to this, but for now its pretty much complete.
And Blargel - STOP STARING AT ME! >_<!