Web Designer Pro Bundle - $500 of Site Templates, Stock Photos, Code, Graphics and more for only $20
Create a Racing Game Without a 3D Engine

Create a Racing Game Without a 3D Engine

Tutorial Details
  • Difficulty: Intermediate
  • Platform: Flash (Flash Player 9+)
  • Language: AS3
  • Software used: Flash Professional CS3+ (could be done with an image editor and any Flash compiler)
  • Estimated Completion Time: 45 minutes

This tutorial will give you an alternative to 3D for racing games in ActionScript 3. No external framework is required for this old-school style example.


Final Result Preview

Let’s take a look at the final result we will be working towards:


Step 1: Set up the FLA Document

Create a new Flash document set for ActionScript 3.0. I’ll be using dimensions of 480x320px, a frame rate of 30 FPS, and a light blue background. Save the file with a name of your choice.

Create a .FLA document

Step 2: Create a Document Class

Besides the FLA, we also need to create a document class. Create a new Actionscript file, and add this code:

package
{
	import flash.display.MovieClip;

	public class Main extends MovieClip
	{
		public function Main()
		{

		}
	}
}

Save this file in the same directory as our FLA. Name it Main.as.


Step 3: Link the Main Class with the FLA

In order to compile the code from the Main class, we need to link it with the FLA. On the Properties panel of the FLA, next to Class, enter the name of the document class, in this case, Main.

Link the Main class with the FLA

Then, save the changes on the FLA.


Step 4: Draw a Road Line

We need to start with a line to represent one segment of the road. Press R to select the Rectangle tool. In this example I’m going to make a gray rectangle for the road itself, two small red rectangles at each edge of the gray one, and green rectangles to fill the rest of the line. The green ones must be even wider than the stage, I’m making them 1500 pixels wide. The width of the road may vary to your needs, I’ll be using one of 245px wide. It’s not necessary for them to be very high, since we’ll be using several instances to draw the whole road on the screen. I’ll be making them 10px high.

Draw some rectangles next to each other for a road line

Step 5: Create a MovieClip for the Road Lines

Once you have drawn all the rectangles, select them all (Ctrl + A) and press F8 to create a Movie Clip out of those rectangles you just made. Name it "Road", make sure the the Registration Point is at the center, and select the "Export for ActionScript" checkbox.

Create a MovieClip out of the rectangles you drew.

You’ll end up with a Road MovieClip in the Library.

Road MovieClip in the Library.

It’s up to you if you want to draw each rectangle on different layers. I’m just going to put the gray one on a second layer. If you have any Road instance on the Stage, delete it. We’ll be adding the Road MovieClip by code later.


Step 6: Set up the Play Area

Let’s get back to the Main class. We’re going to use that Road MovieClip to generate the illusion of a racing track.

We’re going to determine the depth of the visible road, as well as dimensions of the play area. Also, in our class, all the Road instances that we add to the stage will be accessed from an Array. We’ll use another Array (zMap) to determine each line’s depth.

In this example, I’ll be setting a depth of 150 road lines in a 480×320 play area (it’s not necessary to be the same size of the stage, but since that’s all there’s going to be shown, I’ll use those numbers).

//Depth of the visible road
private const roadLines:int = 150;
//Dimensions of the play area.
private const resX:int = 480;
private const resY:int = 320;
//Line of the player's car.
private const noScaleLine:int = 8;
//All the road lines will be accessed from an Array.
private var zMap:Array = [];
private var lines:Array = [];
private var halfWidth:Number;
private var lineDepth:int;
private const widthStep:Number = 1;

Step 7: Display the Road by Code

We’ll be using all the previous variables and constants inside the Main function. We’ll be scaling each line according to their corresponding depth.

public function Main()
{
    //Populate the zMap with the depth of the road lines
    for (var i:int = 0; i < roadLines; i++)
    {
        zMap.push(1 / (i - resY / 2));
    }
    //We want the line at the bottom to be in front of the rest,
    //so we'll add every line at the same position, bottom first.
    lineDepth = numChildren;
    for (i = 0; i < roadLines; i++)
    {
        var line = new Road();
        lines.push(line);
        addChildAt(line, lineDepth);
        line.x = resX / 2;
        line.y = resY - i;
    }
    //Scaling the road lines according to their position
    halfWidth = resX / 2;
    for (i = 0; i < roadLines; i++)
    {
        lines[i].scaleX = halfWidth / 60 - 1.2;
        halfWidth -= widthStep;
    }
}

If you Publish (Ctrl + Enter) the document at this point you’ll get a view of a straight road.

A straight road drawn by a series of the Road MovieClips we created previously.

You can play around with the scaling calculations to get different results. You might want a wider road or a longer view distance.


Step 8: Make a Second Road Graphic

Right now the road looks so flat that you wouldn’t be able to tell if we’re moving forward. We need at least two different styles of segment to distinguish how fast or how slow we’re moving.

Go to the Library panel and double-click the Road MovieClip to get back to the rectangles you drew. Now press F6 to insert a new Keyframe (if you have more than one layer you may want to insert a new Keyframe on every layer). Now, based on the first frame, you can change the colors of the rectangles or modify their design in some way. I’ll be changing their color and adding some lane lines to the second frame.

Change the colors or add lane lines on the second frame.

Step 9: Keep the Player Line from Scaling

We’re going to define a new variable in the Main class to maintain consistency on the player’s line (assuming there will be a car in the game, we’re going to keep the scaling to 1 on that line)

private var playerZ:Number;

Next, we’ll modify the Main function.


Step 10: Add Alternating Lines to the Road

This variable will be used in the Main function. Now the Road lines will be segmented, some will be displaying the second frame and the rest will be showing the first frame, enhancing the illusion of a racing track.

public function Main()
{
    for (var i:int = 0; i < roadLines; i++)
    {
        zMap.push(1 / (i - resY / 2));
    }

    playerZ = 100 / zMap[noScaleLine];
    for (i = 0; i < roadLines; i++)
    {
        zMap[i] *= playerZ;
    }

    lineDepth = numChildren;
    for (i = 0; i < roadLines; i++)
    {
        var line = new Road();
        lines.push(line);
        addChildAt(line, lineDepth);
        line.x = resX / 2;
        line.y = resY - i;
    }

    halfWidth = resX / 2;
    for (i = 0; i < roadLines; i++)
    {
        if (zMap[i] % 100 > 50)
            lines[i].gotoAndStop(1);
        else
            lines[i].gotoAndStop(2);
        lines[i].scaleX = halfWidth / 60 - 1.2;
        halfWidth -= widthStep;
    }
}

It might not be necessary to multiply by 100 to get the segments correctly, but these are the numbers I’ll be using in this example, you’re free to modify the numbers to your taste (and if you screw something up, you have this as a reference).

Add alternate lines to the road.

Step 11: Set up a Speed and an Offset

Let’s start making things move. We’re going to set a variable for speed. This will indicate the depth we’ll advance by frame. I’m going to start at a speed of 20, you may use any number you want.

We also need an indicator for the road segments, which will change according to the speed.

private var speed:int = 20;
private var texOffset:int = 100;

Step 12: Start Moving Forward

Before we can do anything with those variables, we need to import a new Event to this class. We could use either a Timer or an EnterFrame. In this example I’ll be using the EnterFrame Event.

import flash.events.Event;

Next, we’re going to cut the last conditional in the Main() function and move it to a new function we’re creating. This new function will be triggered by the EnterFrame Event, so we’ll get continuous movement on the road. Let’s call it race().

public function Main()
{
    for (var i:int = 0; i < roadLines; i++)
    {
        zMap.push(1 / (i - resY / 2));
    }

    playerZ = 100 / zMap[noScaleLine];
    for (i = 0; i < roadLines; i++)
    {
        zMap[i] *= playerZ;
    }

    lineDepth = numChildren;
    for (i = 0; i < roadLines; i++)
    {
        var line = new Road();
        lines.push(line);
        addChildAt(line, lineDepth);
        line.x = resX / 2;
        line.y = resY - i;
    }

    halfWidth = resX / 2;
    for (i = 0; i < roadLines; i++)
    {
        lines[i].scaleX = halfWidth / 60 - 1.2;
        halfWidth -= widthStep;
    }

    addEventListener(Event.ENTER_FRAME, race);
}

Step 13: Define a Race Function

Now let’s bring back the conditional that was cut off to the new function so we get movement. The texOffset will point the position of the road to keep an accurate illusion of movement.

private function race(event:Event):void
{
    for (var i:int = 0; i < roadLines; i++)
    {
        if ((zMap[i] + texOffset) % 100 > 50)
            lines[i].gotoAndStop(1);
        else
            lines[i].gotoAndStop(2);
    }
    texOffset = texOffset + speed;
    while (texOffset >= 100)
    {
        texOffset -= 100;
    }
}

If you Publish this now, you should be getting an animated road.


Step 14: Steering

Perpetually straight roads are boring and there are thousands of ways to make a perspective going only forward. Now let’s add some new variables to take care of the curves while in motion.

In this example I’ll be alternating curves to the right with straight sections. The road ahead will be stored in the nextStretch variable. Also, we’ll be moving the lines’ x position at the curves.

private var rx:Number; //Each line's x position
private var dx:Number; //Curve amount per segment
private var ddx:Number = 0.02; //Curve amount per line
private var segmentY:int = roadLines;
private var nextStretch = "Straight";

Step 15: Add Curves to the Road

The rx variable will store the x position of each line, so we’ll want it to start at the center and take the curves from there. Also, ddx controls the sharpness of the curves. In this example I’ll have it at 0.02; you might want to vary its value between curves. This is how the new race() function will look:

private function race(event:Event):void
{
    rx = resX / 2;
    dx = 0;
    for (var i:int = 0; i < roadLines; i++)
    {
        if ((zMap[i] + texOffset) % 100 > 50)
            lines[i].gotoAndStop(1);
        else
            lines[i].gotoAndStop(2);
        lines[i].x = rx;

        if (nextStretch == "Straight")
        {
            if (i >= segmentY)
                dx += ddx;
            else
                dx -= ddx / 64; //Reverts smoothly from a curve to a straight part.
        }
        else if (nextStretch == "Curved")
        {
            if (i <= segmentY)
                dx += ddx;
            else
                dx -= ddx / 64;
        }
        rx += dx;
    }
    texOffset = texOffset + speed;
    while (texOffset >= 100)
    {
        texOffset -= 100;
    }
    segmentY -= 1;
    while (segmentY < 0)
    {
        segmentY += roadLines;
        if (nextStretch == "Curved")
            nextStretch = "Straight";
        else
            nextStretch = "Curved";
    }
}

This time we won’t be touching the Main function. If you Publish it now you should be getting something like this:

Curves.

You might want to change the Curve value for Left and Right, and change the steering values. At this point you should already be able to add a car to the scene and control the speed manually.


Step 16: Hills, Slopes

Remember the rectangles for the road are more than 1 pixel high? That might help us stretch the road view in case we want hills in our game.

There’s a method for making hills that’s very similar to making curves. There might be lots of different methods, but this is the one I’ll be using here. For simplicity, I’ll be recycling as much of the code we already have and just add a few lines for this new effect. As usual, if you don’t like the results you may modify the values at will.

We just made variables for the x positions of the road lines, now let’s make those for the y positions as well.

private var ry:Number;
private var dy:Number;
private var ddy:Number = 0.01; //A little less steep than the curves.

Step 17: Downhill, Uphill

For simplicity, in this example I’m going to use the same straight segments for both a straight an uphill effect, and the curves for both a curve and a downhill effect.

private function race(event:Event):void
{
    rx = resX / 2;
    ry = resY;
    dx = 0;
    dy = 0;
    for (var i:int = 0; i < roadLines; i++)
    {
        if ((zMap[i] + texOffset) % 100 > 50)
            lines[i].gotoAndStop(1);
        else
            lines[i].gotoAndStop(2);
        lines[i].x = rx;
        lines[i].y = ry;

        if (nextStretch == "Straight")
        {
            if (i >= segmentY)
            {
                dx += ddx;
                dy -= ddy;
            }
            else
            {
                dx -= ddx / 64;
                dy += ddy;
            }
        }
        else if (nextStretch == "Curved")
        {
            if (i <= segmentY)
            {
                dx += ddx;
                dy -= ddy;
            }
            else
            {
                dx -= ddx / 64;
                dy += ddy;
            }
        }
        rx += dx;
        ry += dy - 1;
    }
    texOffset = texOffset + speed;
    while (texOffset >= 100)
    {
        texOffset -= 100;
    }
    segmentY -= 1;
    while (segmentY < 0)
    {
        segmentY += roadLines;
        if (nextStretch == "Curved")
            nextStretch = "Straight";
        else
            nextStretch = "Curved";
    }
}

In your game you should separate the curves from the hills and make two different algorithms, but this example shows how similar they can be.

Hills.

Step 18: Enhance the Aesthetics of the Road

Old-school games couldn’t take advantage of Flash, but we can. Something as simple as adding a gradient to the road lines will make a nice difference. If you want to, you may use any filters and textures you like, but in this example I’m just adding some simple gradients, so let’s get back to the Road MovieClip.

On frame 1, select the gray rectangle, then go to the Color panel and choose Linear Gradient from the drop-down menu, then choose Reflect color as Flow, so the gradient will continue back and forth from the first to the last color. I’m not telling you to choose the same colors as I do, but I’ll be using #666666 and #999999 here. If you need to rotate the gradient, press F to switch to the Gradient Transform Tool, that will let you move, rotate, and resize your gradient. In this case I’m moving the gradient to a quarter of the rectangle, and resizing it to half the size of the rectangle, so the center will be lighter and the edges will be darker. I use a similar size for the green part, so it’ll change from dark green (#006600) to light green (#009900) continuously.

Add gradients for a nicer texture.

Now go to frame 2 and make new gradients with different colors. For the gray rectangle, I kept the lighter color and only changed the darker color to #777777. On the green part, I changed the size of the gradient to try to avoid a checkerboard look, and the change of colors was very subtle (#007700 and #008800).

A subtle change of colors from frame 1 to frame 2.

Maybe now you’ll want to add a nice background at the horizon, or some graphic for the sky.


Conclusion

Whether you’re short on resources for 3D frameworks or you just want to go old-school, now you have a simple example of how to make an illusion of depth for a racing game. Now it’s up to you if it’ll be a motorcycle Grand Prix, or a street race on a highway full of traffic, or maybe something unrelated to racing.

I hope you’ve found this tutorial useful. Thanks for reading!

Daniel Ramirez is Dallenad on Activeden
Add Comment

Discussion 28 Comments

  1. Dave Stewart says:

    Awesome little tutorial! I might have a crack at that if I get a spare couple of hours. I bet you could have loads of fun making it a bit more OO, and working out how to do forks, scenery, other cars, maps, etc.

    • Author

      I’m currently making a game of my own, and I’m still figuring out an approach for all the details needed after having the road. A scenery will be mandatory to have, forks might be optional but nice to have. The hardest part and the reason why I wrote the tutorial has been making the road itself. Glad you liked it!

  2. Cor van Dooren says:

    Very nice!
    I would love to learn how to extend this so I can build something like a city and make the car make every kind of turn up to a U-turn.
    So please extend this tutorial!!!

    Best regards
    Cor

    • Author

      A U-shaped road might be possible, even a loop, but if you want a whole city, that’s a bit trickier… Let me finish my game and see if I come up with something.

  3. Ian Yates says:
    Staff

    I love this effect! Reminds me of happy days spent staring at OutRun and Road Rash II..

  4. Amazing effect, Ramirez! I loved your simple approach to the “3D problem”. Definitely bookmarked for future references :)

  5. 3rddesign says:

    I love this tutorial. I’ve been a fan of flash but I only do simple flash projects. This one is very difficult but I would love to do this as a challenge. I am really excited to make one.

  6. Greetings. Nice throw back to the old Grand Prix race game I used to play as a kid. As much as I would like to use this idea for a game, I was looking to use it for something a little simpler such as an automotive advertising banner.

    I’m not the most efficient Flash designer when it comes to coding animation and still rely a lot on timeline and wondered if you might have any suggestions how I could use this method of classes and external .as combined with timeline animation?

    I would like to be able to start or stop and maybe control hills or curves with AS3 code on timeline where I could create a 15 second animation along with the road. I noticed what I built on the timeline to sit on the road went behind the it instead. Can I adjust layer height in code somewhere so I can add pic to bg and build on top of the road?

    Thanks for the tutorial and appreciate any input you might be able to offer for creating this. I like that it so lightweight (perfect for banners) and just trying to come up with something new for our auto advertisers ;)

    • Author

      Greetings to you. This tutorial is a simplified version of what should be done. Normally I wouldn’t put the road directly in the Main class, but in a Movie Clip of its own, so anything I’d want to display on the road could be just another Movie Clip in front of it.

      In code, the layer and depth order are determined by the index of each object’s children. If we only have the road in a Movie Clip, the bottom line (closest) is the child at position 149, and the top line (farthest) is the child at position 0, so mc.getChildAt(149) will give you the first line, mc.swapChildren(child1, child2) will let you exchange positions between 2 Display Objects contained in mc.

      The speed is fixed in this example, if you want to accelerate or brake you’ll need to write a different method for controlling the speed.

      Hope that helps.

      • Thanks for the follow up reply. I think I understand what you are you saying. I will have to experiment a bit. Not so much worried about the speed, just need to call it to a stop for end of 15 seconds for animation limit or maybe mid animation maybe. If I put it in a containing movie clip… that might help me create around it and control it I think.

        Already wondering how this will would look with a slight parallax background effect added. ;)

  7. Chris N says:

    Great tutorial and thanks for posting.

    I’m a newbie at this, and have a question on your code. Why are you using the height of the play area to figure out the depth of your road lines? It would seem to me that the two really don’t have anything to do with each other. I’m specifically looking at the use of ‘resY’ in your following bit of code. Thanks!

    //Populate the zMap with the depth of the road lines
    for (var i:int = 0; i < roadLines; i++)
    {
    zMap.push(1 / (i – resY / 2));
    }

    • Author

      I’m using the center of the play area as the vanishing point. Since I’m not using the actual Z axis, the maximum draw distance will equal half of the play area.

  8. harilalkm says:

    hello,

    This is a great tutorial.. but i do have some doubts..

    I need to extend the visible area of the road .. and change the size of the screen .. when i changed the road lines it’s not showingcorectly..

    and one more doubt how can i calculate distance travelled?

    • Author

      The numbers I used are not to be precisely followed. If you scale a line too much it’ll start growing instead of shrinking, so you could try to reduce the scaling rate. This way you’ll be able to increase the view distance.

      If you want to calculate the distance traveled, maybe you could add up the speed after every frame. But I wouldn’t know how to convert pixels to meters…

  9. Juan Vallejo says:

    I change de curve Steering code to make possible turn left, stay on curve more than one segmentY cicle and change form turn left to turn rigth and opposive without pass by straight path.

    here are the changes:

    // Vars
    private var ddx:Number = 0.02; //Curve amount per line (Now it’s the maximum amount per curve)
    private var cdx:Number = 0; //Current curve amount per line
    private var pdx:Number = 0; //Previous curve amount per line

    private var segmentTypes:Array = ["Straight", "CurvedRight", "CurvedLeft"]; // The types of segments
    //End Vars

    And here is the new race function:

    private function race(event:Event):void
    {
    rx = resX / 2;
    dx = 0;

    if (nextStretch == “Straight”)
    {
    cdx = 0;
    }
    else if (nextStretch == “CurvedRight”)
    {
    cdx = ddx;
    }
    else if (nextStretch == “CurvedLeft”)
    {
    cdx = -ddx;
    }

    for (var i:int = 0; i 50)
    {
    lines[i].gotoAndStop(1);
    lines[i].wall.gotoAndStop(1);
    }
    else
    {
    lines[i].gotoAndStop(2);
    lines[i].wall.gotoAndStop(2);
    }
    lines[i].x = rx;

    if (i >= segmentY)
    {
    dx += cdx;
    }
    else
    {
    dx += pdx; //Reverts smoothly from a curve to a straight part.
    }

    rx += dx;
    }
    texOffset = texOffset + speed;
    if (texOffset >= 100)
    {
    texOffset -= 100;
    }
    segmentY -= 1;
    if (segmentY < 0)
    {
    segmentY += roadLines;
    pdx = cdx;
    nextStretch = segmentTypes[int(Math.random() * segmentTypes.length)];
    }
    }

    You could make the same with slopes, making.

    You also can change the ddx value to make next curve more "curved", to have more than types of curves.

    I hope this code should be usefull!!!

    • Phil says:

      Your left/right code works brilliantly. Thank you for sharing it. You say that the same thing can be done for slopes as well – would you mind sharing that too? I’ve had a bit of a play, but can’t get it to work properly.

      Thanks.

  10. Juan Vallejo says:

    I can’t figured out how to make simulated car phisics. I need to make a car go away from a curve similar to a real car taking a curve. Any ideas?

  11. Phil says:

    This looks like a great tutorial, but I’m a bit stuck at Step 10. I’m actually trying to port this to another language, and am using 2 sprites to represent the two different road graphics.

    Because I can draw sprites at a partiuclar depth, I’m using that property in an array instead of zMap, but I don’t really get the whole playerZ bit.

    When I try to add alternating lines to my road, each line is just 1 pixel wide. I obviously need to do some scaling of the lines in the Y direction, and offset the position of each line so they’re thin at the horizon and thick nearer the player.

    Is there a chance you could explain that bit in ‘dunce language’ for me please? Maybe with a bit of psuedo-code to help me along?

  12. Paul Wright says:

    How are we going to lean the bike left or right as it turns through corners? My aproach was to put the whole thing in a container movieclip and then rotate that, but flash likes to use anything other than the centre of a clip as the rotation point…mmm any ideas? Excellent tutorial – thanks very much.

  13. pablo says:

    Cool tutorial , but i have a little problem. I want to add some items that scroll with the track, for a little car to grab. But i havent figured out a way to make the items go with te same position with the track.

  14. Pierre says:

    The only thing I don’t understand about this code is on line 30 of Main1.as. Where does numChildren come from? It is not declared or set anywhere. Is it an object, asset name, or variable?

  15. Chimera says:

    I really like your tutorial it was done well.
    Do you have a tutorial on finishing this game, with car, scoreboard, and AI?

    Thank you

Add a Comment

To add a code snippet to your comment, please wrap your code like so: <pre name="code" class="html">YOUR CODE</pre>. You can replace the class name with "js," "css," "sql," or "php." If there are any "<" or ">" within your code, please search and replace them with: &lt; and &gt; respectively.