Simple Shoot-em-up Development


satacoy

Still Fresh
Joined
May 1, 2006
Messages
81
With both the Pandora and the Wiz on the (hopefully) near horizon, I've been inspired to try my hand at a bit of "next generation" handheld development. I thought it might be helpful or interesting to some if I keep a running log of what I'm doing.

I've done a bit of development on the GP2X, which culminated in BlastRiot. I entered BlastRiot in the GBAX'07 game competition, and enjoyed the entire experience. I've been kicking a few ideas around for another project but before I got too ambitious I thought I'd try to develop something simple for the Wiz and/or Pandora.

So, with that in mind I'm setting my sights on a simple Shoot-Em-Up consisting of:
  • A simple enclosing arena for the combatants. Maybe bigger than the screen and scrollable?
  • A player controlled combatant and a "Boss" enemy.
  • Some rudimentary AI for the Boss.
  • Simple collision detection.
  • Some particle effects (always my favorite) to liven things up.
  • Fast frantic action.
Some items I'll probably skip for this project include:
  • Sound or Music - I'll wait until I actually have a piece of hardware to figure this stuff out.
  • Scoring or the concept of lives or "Game Over". I just want to run around a shoot stuff.
Keep in mind that this won't be a complete game when finished, more of a proof of concept, and an excuse to learn some new technology.

Even though I envision this as a 2D project I'm planning on using OpenGL for a few reasons.
  • It'll allow me to rotate and zoom objects easily through hardware.
  • I'll get my feet wet in OpenGL, making the transition to 3D less painful.
  • From what I can gather, OpenGL is supported on both the Pandora and the Wiz. This may allow the project to be compiled easily for either platform. I'll restrict myself to OpenGL ES 1.1 in hopes that it's available on both.
SDL would also work, but OpenGL is inherently more interesting to me.

I plan on posting the source code as I go along with the hope that others can find use of it. If others want to contribute original artwork or ideas, I'm all ears.
 
Excellent, I also plan to learn some basic OGL|ES if only to get fast sprite drawing and graphic primitives :)

Looking forward to hearing more about this, good luck!
 
OK, the first thing to do is to get a development environment set up. My computer at home is a Windows XP box, so I'll do my initial development on it with the plans of cross-compiling the project once all the appropriate ARM tool chains become available. I'll try to keep all the OS specific code segregated out so that it's easy to swap out as necessary.

I'll be using C++, so the first thing I did was download the free Visual Studio 2008 Express edition from here:
http://www.microsoft.com/express/download/

It's a healthy download, over 90 megs if you leave out the Silverlight and Sql Server options.

Next I downloaded the PowerVR OpenGL ES 1.1 PC emulator from here:
http://www.imgtec.com/powervr/insider/sdkdownloads/index.asp
I did have to register to download it, but it was a fairly straightforward process. It's another healthy download, over 60 megs in all.

The PowerVR package comes with a lot of example source code. It includes both a set of Demos and a Training Course, which consists of several "getting started" type of programs.

To prove that I had everything I needed to code and build OpenGL applications, I decided a first good step would be to compile one of the demos.

To do this, I started Visual Studio, selected the "Open Project" on the start page, and browsed over to where the "EvilSkull" demo source resided. This was a bit tricky, as the layout of the PowerVR installation is a bit confusing. I found the project file (OGLESEvilSkull.sln) under the "Imagination Technologies\PowerVR SDK\OGLES-1.1_WINDOWS_PCEMULATION_2.03.23.1162\Demos\EvilSkull\OGLES\Build\WindowsPC" folder. Whew!

Upon opening the project, I was greeted with a Conversion Wizard. It appears that the project files for the PowerVR demos were created with an older version of Visual Studio. I clicked through the wizard, making no changes to the options, and things seemed to convert OK.

Next I selected "Build->Build Solution" from the menu. A whole bunch of warnings showed up at the bottom of the screen, but the project seemed to build OK.

To try to run the program, I hit F5. This is the same as selecting "Debug->Start Debugging" from the menu. I was instantly greeted with a "This Application has failed to start because libGLES_CM.dll was not found. Re-installing the application may fix this problem."

Yeah right. It sounds more like a path issue. The demo tried to run, but could not locate the libGLES_CM.dll. I poked around the PowerVR installation directories a bit, and eventually found the missing DLL located in the \Imagination Technologies\PowerVR SDK\OGLES-1.1_WINDOWS_PCEMULATION_2.03.23.1162\Builds\OGLES\WindowsPC\Lib folder. I copied the DLL into the same folder that I'd found the project file in, and hit F5 again.

This time the demo started up in all its glory.

This seemed to indicate that everything was set up OK. For fun I opened up the OGLESEvilSkull.cpp file and made a change to some of the jaw rotation angles. After hitting F5 again to re-run the demo, I was greeted with a slightly more grotesque image:

skulldemo.jpg


Next go-around I'll try to get a simple application running that opens my own OpenGL application.
 
In this installment, I'll try to get a simple application running that just opens a OpenGL ES friendly window, waits for a quit command, then cleans things up. This turns out to be a lot of effort for hardly any payoff. A simple black filled window isn't too impressive. Hopefully we'll do something a bit more exciting in the next posting. A large portion of this post involves setting up Visual Studio properly for this project. Unfortunately this is pretty boring stuff.

To figure this stuff out I took a look at tutorials on the web, and I read through a few of of the Training Courses that came with the PowerVR PC emulator. I also dug out my "OpenGL Programming Guide" that I bought about 14 years ago, but never really read. I am amazed how relevant the OpenGL book still is, even though it covered Release 1 of OpenGL and is 15 years old. I've placed an order for a more current version of the book, but for the time being I think the older version will work.

The tutorials I found the most useful were the Zeus OpenGL ES tutorials located at http://www.zeuscmd.com/tutorials/opengles/index.php Even though they used the GLUT|ES libraries, they were generally useful.

The training courses that came with PowerVR installation were also useful. Fairly early in the training course the tutorials start using a "PowerVRShell" set of classes to abstract the OS out.

Both the GLUT|ES and PowerVRShell code added a lot of stuff I wasn't interested in. I decided to use neither, and come up with some simpler OGLES start up code. I did use the other resources heavily to determine how to do this.

In order to simplify path issues, I created a new "tutorials" folder, and "lib" and "include\GLES" folders under this. I then copied the contents of the "Imagination Technologies\PowerVR SDK\OGLES-1.1_WINDOWS_PCEMULATION_2.03.23.1162\Builds\OGLES\Include\GLES" folder into the new include\GLES folder, and the contents of "Imagination Technologies\PowerVR SDK\OGLES-1.1_WINDOWS_PCEMULATION_2.03.23.1162\Builds\OGLES\Windows\Include\GLES" to this folder too.

I did a similar trick for the lib folder, copying the contents of "Imagination Technologies\PowerVR SDK\OGLES-1.1_WINDOWS_PCEMULATION_2.03.23.1162\Builds\OGLES\WindowsPC\Lib" into the lib folder. This would hopefully simplify things down the road.

Next I started up Visual Studio and selected "Create Project" from the Start Page. I selected "Win32" from the Project Type list, and "Win32 Project" from the template list. I named the project "tutorial2", and made sure the "Location" field pointed to my new "tutorials" folder. After hitting "OK", I selected the "Application Settings" option on the left side of the wizard, and then "Windows application" under "Application type", and "Empty project" under the "Additional options" group. I clicked "Finish" to actually create the project.

Next I needed to point the project at both the include files and the library files. To do this, I right-clicked on the "tutorial2" project in the solution explorer, and selected "Properties" from the menu. Since I wanted these settings to work for all project configurations, I selected "All Configurations" from the "Configuration" options. Under the "Configuration Properties/C++/General", I set the "Additional Include Directories" to "..\..\include". This will allow me to reference the OpenGL|ES include files in my project easily.

I did a similar thing for the library files. In this case I had to select "Configuration Properties/Linker/General" item, then set the "Additional Library Directories" to "..\..\lib". This would allow Visual Studio to find the libraries that I needed to use. I also had to specify which of the two OPenGL|ES libraries to use. I selected "Configuration Properties/Linker/Input", and set "Additional Dependencies" to libgles_cl.lib. This would point the project toward the fixed point version of the OpenGL|ES libraries, which is the version I want to use due to the assumed slow floating point capabilities of the Wiz.

After looking through the training course named "01_Initialization", I realized it was very similar to what I wanted to do. It does most of it's work in one ugly procedure. I wanted to move most of the OS specific stuff out to a class that could be easily changed once I knew more about the ARM GL|ES implementation.

To do this I created a GLControl class. This class definition looks like this:

CODE

class GLControl
{
public:
GLControl(void);
~GLControl(void);

// Initialize the OpenGL system, creating an appropriate window
bool Init(HINSTANCE hInstance);
// Close the OpenGL system, cleaning up any related resources
void Term(void);
// Display the scene, and do any appropriate OS related per-frame work necessary.
void Pump(void);

private:
EGLDisplay display;
EGLSurface surface;
HWND hWnd;
HDC hDC;

bool CheckError(char* pszLocation);

};



The three interesting procedures are Init, Term, and Pump.

Init: This opens an OS specific window, and does any OpenGL initialization necessary.
Term: This closes any OS specific windows opened in Init, and does any other resource cleanup.
Pump: Called once per frame, displays the currently rendered scene, and does any OS work necessary. For Windows, this is involves keeping messages flowing.

Again, this code was heavily inspired by the code in the 01_Initizliation training course that came with the PowerVR installation. My hope is that once I have an actual Wiz in my hands, and an appropriate tool chain set up, I can just rework the Windows specific portions of this class, and leave the rest of the application alone.

Similarly, I created an Input class. The role of this class would be to store any input the user gave, so that the main application could use it in a non-OS specific way. This class is a singleton, so it's easy to access from anywhere in the project. It's pretty straightforward for now, only containing an attribute named "escape" which indicates whether or not the escape key has been pressed. We'll use this to determine when the user wants to exit.

I next added a main.cpp file to the project. This is the main entry point to the program. It initializes OpenGL using the GLControl class, clears the screen, and waits for the user to hit Escape, or close the window.

CODE

GLControl *gl = new GLControl();

if (gl->Init(hInstance)) {

glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);

while (Input::GetInstance()->escape == false) {
gl->Pump();
}
}

gl->Term();
return 0;



To get this to run properly, you'll have to copy the libgles_cl.dll file to the proper project directory. Hitting F5 runs the project, and the beautiful empty black window shows up properly.

That was a lot of work without a bunch to show. Next time we'll try to get a few sprites on the screen, so we'll at least have something to look at.

The source for this amazing tutorial can be found here:

http://www.blastriot.com/tutorials/tutorial2.zip
 
Thanks again satacoy! We are aware of the work needed to write such explicit tutorials, and your work is greatly appreciated! :)
 
Michoko said:
Thanks again satacoy! We are aware of the work needed to write such explicit tutorials, and your work is greatly appreciated! :)
Yeah, I'm a little surprised by the amount of work required to post these. On the brighter side this process is forcing me to think about the code quite a bit more than usual, and should result in better quality stuff. At least I hope.
 
Last edited by a moderator:
On to Tutorial3. Now that we have the framework laid for initializing and closing an OpenGL ES application, let's see if we can get some moving graphics on the screen.

For the enemy, I drew up something inspired a bit by the Mancubus from Doom2:

boss-large.gif


He'll shoot rockets from each arm:

rocket-large.gif


Obviously I'm not artist. If someone wants to touch these up, or donate something else, feel free. No copyrighted work though! I'm also in dire need of a top view of the player. I enlarged the images by 3x, just so they're easier to see here. In reality the boss is 32x32 pixels, the rocket is 16 x 16.

The next trick is figuring out how to load them from disk. BMP files are a fairly simple file format, so I poked around on the web and Microsoft's Bitmap documentation. I eventually got a LoadBMP procedure working well, at least for 24 bit non-compressed BMP files. The major stumbling block here was that Visual Studio wanted to pack my structures on 4 byte boundaries. I didn't realize this, and really scratched my head for a bit trying to figure out why they would not load. Eventually I figured it out and set the structure packing option to use 2 byte boundaries instead, and all was well. This setting is buried under the Configuration Properties->C/C++->Code Generation->Struct Member Alignment option.


I put my BMP file loading routine in a new class named TextureManager. A simplified view of the class header looks like this:

CODE

class TextureManager
{
public:

// Get singleton instance
static TextureManager *GetInstance(void);

// Returns a raw BRTexture structure for the given symbolic texture.
RawTexture *GetRawTexture(int tex);

// Returns the GL texture number for the given symbolic texture
GLuint GetGLTexture(int tex);

// Loads all textures, converts them to raw RGB and OpenGL textures.
bool LoadTextures();

// Frees up any memory allocated.
void FreeTextures();

// Loads simple 24bit uncompressed BMP file.
RawTexture *LoadBMP(const char *filename);
};



This is another singleton class, allowing easy access to the one instance of it from anywhere in the program. Typically LoadTextures will be called at program startup. This will load all the defined BMP files as raw textures, and also convert them to OpenGL textures. This is a fairly straightforward process involving telling OpenGL the width and height of the texture, the format of the texture, and pointing it to the pixel data. In our case this is done via:

CODE

// Generate a GL texture based off of the pixel data from our RGB texture
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, textures->w,
textures->h, 0, GL_RGB, GL_UNSIGNED_BYTE,
textures->pixels);



We also have tell OpenGL what algorithm to use to enlarge or shrink the texture. You can use either GL_NEAREST or GL_LINEAR mode. I'm using GL_NEAREST for now, it looks the most retro.

CODE

glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);



Now that we can load and access our textures, let's get them on the screen! For now I'm plopping everything down in the main loop of the program. We'll get more organized in the next installment. Our modified initialization looks like this

CODE

GLControl *gl = new GLControl();

if (gl->Init(hInstance)) {

// Load in our textures
if (TextureManager::GetInstance()->LoadTextures()) {

// Set our background color
glClearColorx(0, 0, 0, 0);

// Place the matrix mode into Projection mode.
glMatrixMode(GL_PROJECTION);

// Set up our view
glLoadIdentity();
glOrthox(f2x(0), f2x(320), f2x(0), f2x(240), f2x(-1), f2x(1));



Here we initialize OpenGL, load the textures, and set our background color. We next set up a few of OpenGL's matrix modes. Since we're doing simple 2D operations we can effectively ignore a few of the normal matrix set ups, and just focus on the projection and model view matrixes.

The projection matrix tells OpenGL how much of the world we want to see. This can be thought of as setting the zoom and lens on a camera. Since we're not drawing any 3D objects, we can use an Orthogonal projection (things don't get smaller the farther away they are). I set up the orthogonal projection to show the world from coordinates 0, 0 all the way to 320, 240. I could have picked other coordinates, but this will allow me to correlate directly to the Wiz's 320 X 240 screen easily.

Note that if you wanted to zoom in or out in your game you could change these values as the program ran to accomplish this. Also note that this 320 X 240 is stretched appropriately to fit the window that we set up in the last tutorial. The window that GLControl currently opens a 640 X 480 window. This effectively causes everything drawn to be stretched 2x in both the x and y directions. When run on the Wiz, we'd want the window to be 320 X 240, but that's a little hard to see on a high resolution monitor. Note that this technique would allow easy resizing of a game from the Wiz's 320 X 240 resolution to the Pandora's 800 by 480. You'd realistically want it to actually be 640 X 480 on the Pandora to maintain the aspect ratio.

CODE

// Get ready for our model manipulations
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();



Here we switch the matrix mode to the model view. This is where we manipulate the objects we want to draw on the screen. We default it to the Identity Matrix, which is the basic state we want to start with.

To draw a texture on the screen, we draw a rectangle that has the texture applied to it. Previously in the code I'd defined three different rectangles, one for the enemy, one for the rocket, and one for the arena. I could have used one square and resized it as appropriate, but that got a little confusing.

I first draw the arena:

CODE

// Load the rectangle texture coords
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FIXED, 0, texCoords);

// Draw the arena
// Send the arena rectangle
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FIXED, 0, rectangleFull);

// Select arena texture
glBindTexture(GL_TEXTURE_2D, TextureManager::GetInstance()->GetGLTexture(T_ARENA));
glPushMatrix();
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glPopMatrix();



To do this, I first tell OpenGL that I want the rectangle I'm about to draw to be completely covered by the arena texture. Next I send OpenGL the coordinates for a rectangle that covers the entire screen. Finally I select the arena texture and tell OpenGL to draw the rectangle.

I do very similar steps for drawing the rocket and the enemy. The only difference is I move the rocket and enemy toward the middle of the screen, and then rotate them a bit according to a few rotation variables that are being constantly updated.

CODE

// Select the boss texture
glBindTexture(GL_TEXTURE_2D, TextureManager::GetInstance()->GetGLTexture(T_BOSS));
glPushMatrix();
glTranslatex(f2x(160), f2x(120), f2x(0));
glRotatex(r2 % f2x(360), 0, 0, f2x(1));
glScalex(f2x(3), f2x(3), 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glPopMatrix();



You'll notice that I push and then pop the current matrix before and after each object. These leaves the model matrix in a known state, and ready to be used with the next object to position and draw. Again, in order to see the enemy and missile, I draw them 3 times their actual size. Once the game starts really moving, I'll leave them at their original sizes.

If we run the program, we get something that looks like this:

tutorial3.jpg


There are some obvious transparency issues, but at least we have rotating 2d sprites! Next time we'll see if we can fix the transparency problem.

A video of the tutorial in action is here:

http://www.youtube.com/watch?v=oR_ahrWyxm0

Next time I hope to make the textures transparent, and start setting up the game code to handle drawing game objects in a general manner.
 
Last edited by a moderator:
Load textures with alpha channel (8R8G8B8A) is like
CODE
glTexImage2D(GL_TEXTURE_2D,0,4,Width,Height,0,GL_RGBA,GL_UNSIGNED_BYTE,Texture_Buffer);


In frame initialization one string required
CODE
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);


and draw after
CODE
glEnable(GL_BLEND);


I hope that all will help a bit.
 
quasist said:
Load textures with alpha channel (8R8G8B8A) is like
CODE
glTexImage2D(GL_TEXTURE_2D,0,4,Width,Height,0,GL_RGBA,GL_UNSIGNED_BYTE,Texture_Buffer);

Don't use 4 as the internalformat (3rd parameter), it's not supported in OpenGL ES and deprecated in OpenGL. Use GL_RGBA instead.

QUOTE
In frame initialization one string required
CODE
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

Once you use bilinear filtering or partial transparency it may be worthwhile to use premultiplied alpha (Here are some good reasons why).
 
Last edited by a moderator:
Thanks quasist and Xmas for the pointers, both sets were useful.

In order to add transparency to the sprites, we need to add an alpha channel to our textures. The alpha channel is another 8 bit value that goes along with our R, G, and B bytes we're already using to specify the color of each pixel. This means that each pixel will be defined by 32 bits, instead of 24 bits.

Alpha values can be used for a variety of tasks, but we'll be using them to describe how transparent each pixel is. A value of 0 means that the pixel is completely transparent, a value of 255 means it's completely opaque. A value of 128 would be 50% transparent.

Some graphics formats (such as TGA and PNG) support storing the alpha data directly in the file. Instead of figuring out how to read a more complicated file format in, I'll use the BMP file loading we got working last time, and use it to add alpha values to the textures.

In order to do this for our current textures, after reading the texture into memory I'll assign an alpha value of 255 for any non-black pixel, and assign a 0 alpha value to any pixel that is completely black. This technique doesn't allow for semi-transparent pixels, but for the task at hand it should work.

I've added a new method to the TextureManager class to convert a raw RGB texture to an RGBA texture. It looks like this:

CODE

// Converts a RawTexture into an OpenGL texture with R, G, B, and A components
bool TextureManager::ConvertToOGL_RGBA(RawTexture *tex, GLuint oglTex) {
// Tell OpenGL which texture we're setting up.
glBindTexture(GL_TEXTURE_2D, oglTex);

// Convert the RawTexture's RGB components into an RGBA set.
unsigned char *tempPixels = new unsigned char[tex->w * tex->h * 4];

for (int i = 0; i < tex->w * tex->h; i++) {
tempPixels[i * 4 + 0] = tex->pixels[i * 3 + 0];
tempPixels[i * 4 + 1] = tex->pixels[i * 3 + 1];
tempPixels[i * 4 + 2] = tex->pixels[i * 3 + 2];
if (tex->pixels[i * 3 + 0] == 0 && tex->pixels[i * 3 + 1] == 0 && tex->pixels[i * 3 + 2] == 0)
tempPixels[i * 4 + 3] = 0;
else
tempPixels[i * 4 + 3] = 255;
}

// Generate a GL texture based off of the pixel data from our RGB texture
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex->w,
tex->h, 0, GL_RGBA, GL_UNSIGNED_BYTE,
tempPixels);

delete [] tempPixels;

// Set the filter rules for both magification and shrinking.
// GL_NEAREST gives a pixely old-school look,
// GL_LINEAR gives a smoother result
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

return true;
}



It's pretty straightforward, expanding the 3 byte RGB format to a 4 byte RGBA format, and setting the alpha byte depending on whether the pixel is completely black or not.

In our OpenGL set up I need to tell the OpenGL to enable blending, and also define which blending function we'll use.

The classic blending function is as quasist states, which looks like:

CODE

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);



This was the method I was using before reading Xmas's post. It basically calculates the output of each pixel by using the formula on each color channel (Red, Green, and Blue):

Color = Source Color * Source Factor + Destination Color * Destination Factor

Given our blending function, this expands to

Color = Source Color * Source Alpha + Destination Color * (1 - Source Alpha)

So, say we have a black background, and we're drawing a texture who's first pixel is which, and has an alpha factor of 128.

The Red component calculation looks like:

Red = 255 * .5 + 0 * (1 - .5) = 127

The Blue and Green calculations look the same, since we're dealing with white and black. These three calculations result in a medium gray pixel with RGB values of 127, 127, 127. This is what we'd expect, since we're drawing pure white over pure black, and the white texture is 50% transparent.

Trying to follow the article that Xmas posted, this technique causes problems in certain conditions. It specifically gives examples of smoke and soot trails, which is one effect I'd like to use. So, following the article's advice, I changed my blending function to:

CODE

glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);



In our current example this won't make any visible difference, and I don't have to change my texture manipulation code since I'm currently only dealing with fully opaque or fully transparent texture pixels (texels). Once I get into simulating smoke I suspect I'll have to re-read the article, and make a few changes.

Which the current code set, the sprites are being drawn correctly:

tutorial4.jpg


Here's the updated video:

http://www.youtube.com/watch?v=CI6Qg5ukv1Y
 
Last edited by a moderator:
I hope that you will make ground of the level affected by the explosions and blood from dead monsters (like in Crimson Land)

As I remember that Quake3 used some glSubTexImage2D (i'm not sure if that function name is correct) to update only a small region of texture. That is not to update whole ground texture.

I'm looking forward to see great bloodbath game :)

Could you please make a video of 100+ sprites??? :)
 
quasist said:
I'm looking forward to see great bloodbath game :)
Soon the streets will flow with the blood of the non-believers!
 
Last edited by a moderator:
Sphinxter said:
Post any code yet?
Oops, I forgot to post the last couple of code sets for the tutorials. I've zipped up the last two, and put them here:

Tutorial3 Source
Tutorial4 Source

Both of those require the same lib and include set up as Tutorial2 used.

After quasist's post, I started wondering how many items I could get on the screen before my computer started bogging down. I realize this in no way reflects how many I could get moving on a Pandora or Wiz, but it still sounded like a fun thing to try.

http://www.youtube.com/watch?v=-OoSp9R_ank

That's 400 boss characters and 400 rockets on screen, each independently rotating. Once I reached 400 my frame rate dropped to 54 fps or so, from a rock solid 60 fps before.
 
Last edited by a moderator:
This looks like fantastic work. I'm going to have a good read through once I get some more time on my hands. I've yet to try anything 3d related. My last brush against 3d was in 1991 when I used 3D Construction Kit to build my parents house :D .

Anyway I'll have a good look through your tutorials soon.
 
Next I'll add some smoke for the rockets. Here's the end result:

tutorial5.jpg


And here's the video:

http://www.youtube.com/watch?v=5qRsQCpIM6o

I added and changed quite a bit of code to get this done. A lot of this code is framework type of stuff, there's very little new that's OpenGL specific. Since the previous tutorials covered rotation, scaling, transparency, and translation, there's not a lot of remaining OpenGL to cover that I'll be using in this game.

That's OK, I enjoy framework type of stuff. Again, this resulted in quite a bit of code. I'll cover it at a high level, and let you dig into the code if you're interested in the details. I tried to comment it well, if you run into specific questions, let me know.

Thinking about the objects I'll want in the game, I came up with the following list:
  • Boss
  • Rocket
  • Player
  • Bullet
All these objects will have a location on the screen, and be facing a particular direction. Each one will be updated each frame, and drawn each frame. All will use a texture of some sort as well. I'll need some way of managing a group or collection of these objects.

I created a few new classes to help fulfill these needs:
GameObject: The base class for all the above classes. Has a location and an angle. Also keeps track of which type of object it is, so that other objects can look at it and make decisions based off of its type. Has several methods for accessing the above information, and states that any instantiated objects must support Draw() and Update() functions.

TexturedObject: Derives from the above class, but contains transparency and texture information. Handles drawing of the object at it's current location, angle, scale, and transparency

Rocket: Derived from TexturedObject. I added some dummy code to the Update() function so that a rocket will bounce from left to right. The Rocket class also contains a particle system that emits smoke.

In terms of special effects, I can see the need for:
  • Smoke Trails
  • Explosions
  • Blood splatter?
All these can be implemented with particle systems. For this installment I implemented a smoke trail particle system using two classes:

ParticleSystem: Responsible for emitting particles at an appropriate rate from a given location. This derives from GameObject, re-using it's location and update/draw functionality.

Particle: This is a TexturedObject with an appropriate smoke like texture. Currently when a Particle is created it decides its attributes, which are currently hard-coded to act like smoke. I suspect I'll make this more flexible down the road.

In order to keep track of the rockets and the resulting particles, I created a class named ObjectManager. This class is responsible for updating and drawing of a collection of objects. For this demo I use two copies of this class, one to track the rockets, and another to track the particles they create. I used two so that the smoke particles would be separated cleanly from the other game objects. This will make collision detection easier and faster down the road. In general it makes sense to keep the "special effects" separate from the game objects that the player can interact with.

Getting the smoke particles to look good did require a bit of exploration of the premultiplied alpha concept. I found that my previous method of generating sprites wouldn't work for a good smoke particle. Not only did they need transparent areas around the particle, the visible area of the particle had to be semi-transparent as well.

In order to get this to work, I added a new function to the TextureManager class.

CODE

PreMultiplyAlpha(RawTexture *color, RawTexture *alpha, TextureID tex)



This takes two raw textures, one representing the color attributes of the texture, and the other representing the alpha components. The function then multiplies the alpha and color values, and adds the alpha component to the RGB triplet.

In the case of smoke a smoke particle, I sent in a 16 X 16 pixel pure white texture, and a 16 X 16 greyscale texture representing the opacity of the white texture. After playing around a bit with this it produced a good looking smoke particle.

Once the particle looked right, I played around with the attributes of the ParticleSystem and Particle classes quite a bit. One problem with particle systems in general is it's easy to blow a bunch of time endlessly tweaking their parameters.

For this particle system, I arrived at a system that emits a new smoke particle each frame. Each particle has a life duration, in this case I used 60 frames (or about 1 second). I add a random amount to this, making some particles live a bit longer, some die off quicker. This makes things look more natural and random.

Each particle will drift a bit from it's point of emission. I generate random x and y deltas to do this that get added to the particles location each frame. Likewise I make each particle rotate and grow a bit, again randomizing this behavior. As the particle decays, it becomes more transparent until it finally dies and is removed from the special effects object manager.

Each rocket would have roughly 60 smoke particles on the screen at any given time. This looks great on a PC, once I can run this on a Wiz or Pandora I may have to scale this back to not add too much processing load.

That does it for this installment. The source code can be found here.
 
Last edited by a moderator:
Back
Top