• Advertisement
Sign in to follow this  
  • entries
  • comments
  • views

About this blog

Shiny things? Awesome. Things that are not shiny? Possibly awesome.

Entries in this blog


Source + installer (needs .NET Framework 2.0).

AI is moderately stupid, it will go for an obvious win or block yours but that's about it.

Summary of victory checking, since someone was asking about it:

For each player 'p':

bool won = false;
for (int i = 0; i < 3; i++)
// Column
if (gameBoard == p && gameBoard[i + 3] == p && gameBoard[i + 6] == p)
won = true;

// Row
if (gameBoard[i * 3] == p && gameBoard[i * 3 + 1] == p && gameBoard[i * 3 + 2] == p)
won = true;

// Diagonal
if (gameBoard[4] == p) // center
if (gameBoard[0] == p && gameBoard[8] == p)
won = true;
if (gameBoard[2] == p && gameBoard[6] == p)
won = true;

Nothing fancy, just brute force.

Yeah, I was bored. Yeah, I'll get back to the asteroids thing.

Download C# source and ClickOnce installer
Needs .NET Framework 2.0

Tidied up a bit, looks a little nicer now. I've set it to open near the mouse, without going off-screen or covering the taskbar, and close on deactivation, so it's more like an overgrown balloon tip than a complete window.

Note: The noon/midnight lines should be fairly accurate (I would guess within an hour, as long as I haven't mucked up anything), but only compared to the actual position of the sun, NOT local time. Daylight savings/summer time, for example, throws things off substantially - the sun doesn't suddenly rise and set an hour earlier/later. The 6am/6pm lines (between the light and dark regions), however, are not intended to match the actual sunrise/sunset, they're just a rough guess (and again, they won't match local time). The day/night regions should actually be shaped something like this.

Yes, more Asteroids stuff is coming, as soon as I get a chance to work on it.

Download C# source code and ClickOnce installer - requires .NET Framework 2.0

Designing the Asteroid class
Most of the movement code is the same for the player and the asteroids. The asteroids' movement is somewhat simpler since they have some initial random velocity, but never accelerate, but this can be achieved simply by leaving the Acceleration property as 0.

It is therefore useful to keep the movement code in a common base class, GameObject and have the Player class simply set the acceleration and turning rate to the desired values, then ask the base to calculate the updated position. We can also extend the GameField object that was used before to track the size of the playing area to also track the current list of objects. This lets all of the update and drawing operations be performed at once with a single call.

Consider a theoretical simple game with the following types of object:
  • Player, with animated sprite with different images for walking, shooting, etc.
  • Monster, with animated sprite
  • Obstacle, with either a single sprite image or an animated sprite
  • Various powerups, with a single sprite image each

Although there are four types of object, there are only two types of sprite - static, with a single image, and animated, with many images - and some objects may even have the same behaviour, but different types of sprite. Also, there is no need to duplicate the sprite objects - only one copy of each bitmap needs to be stored, rather than one per object.

It is much easier to create a seperate inheritance hierarchy with a Sprite base class, and give the game objects an instance of Sprite, than to build the drawing into every object type. Maintenance also benefits, since drawing code for each type of sprite is kept in one place, and when a new type of sprite is added, every type of object gains use of it. This is even more important in a complex 3D game, where there may be a mesh representing the object's appearance, a simpler mesh used for collision detection, as well as geometry (possibly not a mesh) and associated information such as mass of each component used for the physics engine. Merging all of them into one class would create a useless clutter.

The Asteroids2 sample provides a Sprite base class, as well as two implementations. One simply draws a polygon; the other contains a collection of individual Sprites. The Player class uses this to seperate the ship and its exhaust, so it can disable the exhaust sprite when the engine is not in use (rather than keeping two seperate sprites, one for engine on and one for engine off).

Asteroid generation
The algorithm used here is fairly simple: generate a certain number of points spaced evenly around a circle. Then, set the radius at each point to a random value.

for (int i = 0; i < numberOfVertices; i++)
float angle = 2 * pi * (float)i / numberOfVertices;
float radius = random between minRadius, maxRadius;

Vertex = Vector2(radius * cos(angle), radius * sin(angle));

The number of vertices and the exact overall size of the asteroid can both be varied as well. Velocity and, if the asteroid needs to spin, the rate of rotation should also be random. angle could also be varied randomly, possibly by applying a random offset.

Alternatively, a set of points with random positions (in either cartesian or polar coordinates) could be generated, and the resulting array sorted by polar angle.

Next time: Bullets, collision detection and scoring.

Download C# source code and ClickOnce installer - requires .NET Framework 2.0

The first step is to get the ship moving around the screen properly. For now, there are no asteroids, and the ship is just a line.

Although the example game is written in C#, the math and logic will be very similar in most languages, and are far more significant than the details of the implementation.

The code excerpts below use the Vector2 class included in the source. It provides basic operations such as addition and multiplication through overloaded operators. You can just store the components seperately if you want, and perform individual operations on them (ax = bx + cx; ay = by + cy; rather than a = b + c;). However, this tends to complicate the code unnecessarily, and is much harder to manage, since you have twice as many variables (worse, in 3D, three times as many).

Movement in Asteroids
  • The game uses a basic Newtonian physics system. The ship has a position, velocity, and orientation.
  • The direction the ship faces is independent of the direction it's moving in. If you turn the ship, it will
    continue to move in the same direction.
  • If you release the thrust key, the ship should continue to move for some time (inertia)

Turning the ship
We need to be able to handle the user pressing two keys at once. This often happens by accident in fast gameplay - the right key is pressed enough to contact before the left is fully released, or the user starts to switch from left to right, presses the left key, then changes their mind before releasing the right key. Therefore, we need to keep track of whether each button is pressed, rather than simply set the direction whenever a key is pressed or released.
Some input systems simply specify whether every key is up or down. In this case, the ship can be allowed to stop turning if both keys are pressed at once. It is usually preferrable to continue in the direction of whichever key was pressed last, however. This is fairly easy to do with event-based input (such as Windows Forms or messages in MFC or plain Win32).

bool keyLeft, keyRight;

// When the left arrow is pressed:
keyLeft = true;
TurnDirection = left;

// When the left arrow is released:
keyLeft = false;
if (keyRight)
TurnDirection = right;
TurnDirection = 0;

// On update:
float deltaTime; // time in seconds since last update
if (TurnDirection == left)
direction -= deltaTime * turning speed in radians per second;
else ...

For games that allow both forwards and backwards movement, the up/down keys can be handled similarly.

Updating the position

Vector2 position, velocity, acceleration;

calculate acceleration based on what direction the ship is pointing, and whether the user is pressing the thrust button

position = position + velocity * deltaTime + acceleration * (deltaTime * deltaTime / 2);
velocity = velocity + acceleration * deltaTime;

This comes from basic physics. The last position term tends to make the results more stable if the framerate varies. It still won't be perfect - the ship's movement will be slightly different at different update rates - but it's about as good as it gets without resorting to far more complicated integrators.

Finding the acceleration
Given an angle in radians representing the ship's direction (direction), we can find the acceleration from thrust with:

acceleration.x = amount * cos(direction);
acceleration.y = amount * sin(direction);

// Drag
acceleration = acceleration - (small number) * velocity;

The trigonometric functions always use radians. Also, with more complex physics involving rotating objects, you will frequently find you'll have to either use radians or add factors of pi to formulas. Of course, it's a lot easier to understand an angle shown in degrees, but the user never gets to see the angle here, except indirectly through the appearance of the ship. Even if you must display the angle to the user or allow them to enter it, it's easier to store things as radians internally and convert on entry/display than to perform the conversion over and over in every calculation.

We also subtract a small amount of the current velocity to provide drag. This will gradually bring the ship to a stop if all of the controls are released. Under thrust, it will also cause the ship's direction of motion to tend to turn towards the direction it's facing - the sideways component of the velocity is soaked up by drag, while the forward component is continuously replaced by the engines.

Restricting the top speed
Some possible approaches:
  1. Allow the ship to continue to accelerate indefinitely. This can cause problems with collision detection - if the ship moves fast enough, it may pass through objects. At high speeds it may also be difficult for the player to see how the ship is moving so they can slow it down.
  2. Set the multiplier used for drag to the desired top speed divided by the acceleration. This is the easiest, but gives the programmer less control over the ship's movement.
  3. Set a speed cutoff. The sample does this by reducing acceleration whenever the next velocity update (velocity = velocity + acceleration * deltaTime) would cause the ship to reach an excessive speed.

Wrapping around the edge of the screen
If the X or Y coordinate exceeds the size of the playing area, we can use the modulus (%) operator - new X = old X % width. This won't work for negative values, however. We could just add width to the coordinate until it's greater than 0, but this is somewhat inefficient, and could cause the app to freeze if the position is somehow corrupted. new X = width - (-old X % width) produces the proper result without looping.
Notice, however, that this can cause the ship to disappear from one side while the back is still partially visible. Moving asteroids will also have this problem. This could be solved by moving the edge of the game field slightly off-screen, or by drawing up to 4 copies of the ship when it's near the border. In the latter case, collisions with asteroids near the border must also be handled carefully. In the former, the border will probably need to change size dynamically based on the object's size.

Next time: Proper graphics and asteroids.

Download C# source and ClickOnce installer
Needs .NET Framework 2.0

Double-click to open the fancy world clock/map:

The yellow line is noon, blue is midnight. The bright/dark areas are split at 6:00 AM and 6:00 PM. It's not 100% accurate given the peculiarities of time zones (and of course the sunset/sunrise times are rough guesses at best, especially near the poles) but if one of the lines is over England, then that's the GMT/UTC time. (Although, again, not necessarily the local time due to daylight savings)

I was planning on putting in a list with local time for cities around the world, but it turns out to be rather annoying. You can get the local time zone information, and Windows does have a list of time zones somewhere (check the Date and Time control panel), but you can't easily get at that list. (And, of course, you would still need a list of cities with lat/lon and time zone)

Download C# source and ClickOnce installer
Needs .NET Framework 2.0

Double-click to open the fancy world clock/map:

The yellow line is noon, blue is midnight. The bright/dark areas are split at 6:00 AM and 6:00 PM. It's not 100% accurate given the peculiarities of time zones (and of course the sunset/sunrise times are rough guesses at best, especially near the poles) but if one of the lines is over England, then that's the GMT/UTC time. (Although, again, not necessarily the local time due to daylight savings)

Download C# source and ClickOnce installer
Needs .NET Framework 2.0

Quick one* - makes a taskbar notification area (system tray) icon that shows the day of the month. If you hover the mouse cursor over it, it displays the full date.

Every 10 seconds it checks to see if the date has changed - if so, it redraws the image and updates it. It just draws the text to a bitmap and makes an icon out of it, which is pretty easy in .NET. I have seen it done by creating an icon for every single day, but, well, yuck. And that stops you from being able to change the colours and fonts or otherwise customize it without going to a lot of trouble. Right now it uses the colours from an active caption, and the default GUI font shrunk down to an appropriate size (I did try making the background transparent, but that creates some annoying drawing issues.) It's also pretty simple to add other information, such as the day of the week or month (although it clutters it somewhat) - shrink the font used for the day, create a smaller font for the new data, and shuffle things around a bit.

Next up: Good old-fashioned nice and simple Asteroids in C#. Tomorrow. Unless I forget, or it turns out to be boring, or I get distracted by something shiny. Shiny... like coins... oooh....

* Started this morning, spent most of the time mucking with the fonts to make it less ugly. Yes, I know it's still ugly.
Download the RenderMonkey workshop
HDR source image not included, it's too big - you can download several from Paul Debevec's site, then convert them to DDS with the texture tool in the DX SDK

Creating the shader
This sort of effect is easiest to do as a post-process. You only need to do the calculations once per pixel. So, we start off by rendering our OMFG HDRTASTIC SCENE (TM) to a texture.

float3 clrIn = tex2D(Texture0, texCoord).rgb;

Now to adjust the saturation. Real cameras (and eyes!) don't just take in red light, and make red, etc.; each pigment responds to different frequencies of light based on a nice smooth response curve. We can fake this by simply multiplying the input colour's R, G, and B components by constants and adding them together for each output channel. To make things easier, instead of specifying 3 complete colours, we're going to just use two constants, A and B, to specify how quickly the response falls off from the 'center' frequency. If A and B are 0, the output will be the same as the input; if they are 1, the output will be greyscale.
float3 responseR = float3( 1.0, b, a);
float3 responseG = float3( b, 1.0, b);
float3 responseB = float3( a, b, 1.0);

clr.r = dot(clrIn, responseR);
clr.g = dot(clrIn, responseG);
clr.b = dot(clrIn, responseB);

Once we have this result, we can multiply the result by another colour to give the whole scene a slight tint (but don't go overboard - your entire scene probably does not need to be bright pink). After that, we do something fancy to get the exposure + gamma in a nice range (exp is used here), and output the final result.

clr = 1 - exp(-exposure * tint.rgb * clr);

return float4(clr.r, clr.g, clr.b, 1);

HDR results
First, the original HDR input image with A=B=0, tint=1,1,1, and exposure = 1: (tonemapped so we can see it on an LDR monitor, but basically, this is what the original looks like)

Setting A=0.4 and B=0.2 takes away some of the colour by mixing the red, green, and blue values together. Setting the tint to 1,0.8,0.7 or so gives a nice sepia look. Also, if we adjust the values somewhat and give it a bluish tint, we can get a night-time effect (although the overly bright sky gives it away).

On the other hand, if we use negative values (here, A=-0.1 and B =-0.2), the colours become slightly richer (bump the exposure up slightly to compensate for the decreased brightness). However, exaggerating the effect can give unpleasant results - if you want a major increase in colour saturation, you will probably need to resort to a more sophisticated technique.

LDR results
But wait, you cry! Unlike most, I know that "HDR" is not another name for "massively overdone bloom", and I've realized that my game looks just fine without it! Well, guess what - it works just as well with ordinary images, as long as you don't try to push it too far. We only need to slightly change the end of our shader:

clr = exposure * tint.rgb * clr;

return float4(clr.r, clr.g, clr.b, 1);

Top: Original screenshot (from CounterStrike: Source); sepia; Bottom: increased saturation; night.

These images have more extreme settings as well as some gamma adjustment.
...it works.

Simple filter run on the height values - basically, points higher up than their neighbours come out brighter, and lower-down points have more light blocked. Nice and soft and subtle, as outdoor shadows should be.

...so I made a per-vertex one to play with. It works out looking just as nice for a detailed model, and means one less texture - the data can just be stuffed into the vertex colour. Interestingly, this approach would work just fine on a card that doesn't even support multitexturing. Lighting is a whole lot easier at runtime when you precalculate as much as possible.

Ruby was too slow so I chopped her head off... the rest is in the closet over there.

The basic approach:

// first build a coordinate system with Z as the normal
// make X anything perpendicular to Z, then Y = Z cross X
CVector3 origin, X, Y, Z;

// now shoot the rays
unsigned hit, total;

for (unsigned u = 0; u < samples; u++)
float phi = u * pi / samples;
float c = cos(phi);

for (unsigned v = 0; v < samples; v++)
float theta = 2.0f * pi * v / samples;

float x = c * cos(theta);
float y = c * sin(theta);
float z = sin(phi);


const CVector3 dir = x * X + y * Y + z * Z;

// Test ray against mesh
// Note highly scientific fudge factor
if (test mesh against ray(origin + dir * 0.001f, dir))

// Store result
float f = 1.0f - float(hit) / float(total);
vertex colour = CColour4(f, f, f, 1.0f);

This one's easy - blur the skybox image for less of a shiny polished metal effect. I tried using multiple samples in the shader, but that's slow and doesn't look all that good. Turns out that just blurring the individual cubemap images works fine. There should be errors around the edges of each image but it's blurry so you don't notice.

The Earth image is from the Celestia Motherlode

Ambient occlusion

Lighting using a precalculated cubemap

For every point in the cubemap, the colour is calculated by finding the lighting on a surface whose normal points from the center of the cube to that point. Here it's calculated from a few directional sources (the sun and a reddish planet). It's also possible to calculate the diffuse lighting cube using a cubemap image of the scene.
Only one texture lookup into the precalculated cubemap is needed at runtime. The result is very fast calculation of an unlimited number of light sources (in the case of a source cubemap, effectively you have one tiny light for every pixel).

Ambient occlusion

Left: Diffuse cubemap only. Notice that there are no shadows - the engine ports are far too bright on the inside.
Right: Diffuse cubemap mixed with ambient occlusion texture, calculated with ATi's NormalMapper. This texture describes how much of each point on the object's view of the surrounding scene is obstructed. This gives simple self-shadowing.

(click to see the occlusion texture map)
In this example, light is coming evenly from every direction. Decent soft shadows are achieved with no runtime cost. The downside is that this only works with static models - the models can be moved around, turned, etc., but they cannot change shape (for example, bending at a joint). There are some artifacts visible; using more samples per texel helps but takes longer to generate.

A more sophisticated approach such as PRT allows taking the direction of incoming light into consideration (as is, the shadows are only correct for uniform light)
Sign in to follow this  
  • Advertisement