A Space Trading Sim is born
3D Game programming for beginners
Episode 1: The real world and your flat screen
Written by Kevin R. (August, 2007)
Welcome everyone to the first tutorials in this series which is aimed at beginners to 3D programming and maybe to game programming. I know many people (at least 90%) will skip this introduction so I will keep it short. All the code in this text is written for FreeBasic. I will not use any external package or library such as DirectX or OpenGL... That was shocking... No DirectX or OpenGL, so how we gonna produce some 3d graphics??? Simple... with our own FBgfx.
Why would I ignore DirectX or OpenGL and don't use them??? Every game produced nowadays uses them. I can program applications using OpenGL and DirectX, so that's the problem neither. The answer is simple and summarised with one word: understanding.
Understanding the concepts which are working within 3D engines is very important and can make your life much easier. When you write applications with OpenGL and use matrix equation, rotation and shading commands, these commands are black boxes. You know they produce nice graphics, but how they do it is unknown. The big problems arrive when you get a nasty runtime error somewhere in the rendering of your flashy games. Good understanding is important. Besides that, itís fun to look if your routine is faster than DirectX or OpenGL.
What we are going to do?
In this series Iím planning to do a space trading sim like Elite or some more up-to-date Freelancer. This type of game can easily be separated in little pieces for a tutorial in order that at the end of the series we got an entire game ready and running.
This episode I will explain the conversion from 3D to a 2D screen, the base of an 3d engine. Not very difficult and for some maybe way to easy, but look at the top of the text:
3D game programming for BEGINNERS, so I will start at lowest level possible. Later on in the series I will get to more difficult problems.
Alright, what do we need in random order:
2D and 3D worlds
Alright, what is the difference between 2D and 3D worlds? Or in gamer language, what is the difference between Super Mario and Quake? The answer is again simple: a coordinate. In a 3D world we define a point by 3 coordinates respectively (x, y, z). In 2D we use 2 coordinates (x, y). So we've to find a way to convert a 3D coordinate to a 2D coordinate.
Before we can do that, we have to define the 3D coordinate system. I'm using a right handed system:
z-axis into the screen, y up the screen and x positive to the right. This is called a right handed system because when you point your thumb of your right hand to the screen, your four fingers point to the direction of positive rotation. This sounds like hocus-pocus right know, I know... But I will explain it later in a tutorial on rotations. For now just assume z into the screen, y upwards and x to the right, OK?
Well, that's done. Now take a good look at the screen in front of you. It is flat. How we are going to simulate a 3D effect on this screen? The screen has only x and y coordinates, so we need a conversion which is depending on the z-coordinate. Could it be so easy...? Well actually it is... Look at it this way: a point is defined as (x,y,z). We can multiply this point by a constant.
constant * (x, y, z)
What value must this constant have in order to make the formula depending on z but staying in a 2D plane? Again simple:
constant = 1 / z because (1 / z) * (x, y, z) = (x / z, y / z, 1)
As you see x and y are variable but the z coordinate always equals 1 thus for all z only x and y can vary, but the plane remains the same.
So we are working currently at the plane z = 1. If we want to change this plane, to make it further away or closer by we simply change the constant a little:
constant = b / z
Where b can be every value. With b you can adjust the perspective. Usually values like 128, 256 or 512 are used for b, but I encourage you to experiment with this value to achieve a desired effect or view. From here on b is called the scale.
The transformation can now be done with:
x2d = x3d * Scale / z3d + (screen width / 2) y2d = y3d * Scale / z3d + (screen height / 2)
First one note to the transformation formulas. The first runtime error which can occur and is sometimes hard to find is the division by zero: If z3d equals zero we get and error so be careful. Second note is the screen height and screen width used in the formulas. It's common in 3D engines to use the middle point of the screen as the origin. So if you are using a 640 x 480 resolution the origin lies at (320, 240) which is (0,0,z) in 3D.
I know that the information above looks simple at a first glance, but I bet (and I was one too) that you have to think hard to understand the concept. Therefore I present to you all: Some source code!!!
' Screen dimensions of mode 13 Const SCRN_X = 320 Const SCRN_Y = 200 Type Point3D x As Integer y As Integer z As Integer End Type Type Point2D x As Integer y As Integer End Type Dim p3d As Point3D ' 3D point Dim p2d As Point2D ' 2D point Dim Scale As Integer ' Perspective variable ' Define perspective Scale = 256 ' Define starting coordinates p3d.x = 1 p3d.y = 1 p3d.z = 256 ' For sake of simplicity i use screen 13 (320 x 200) ' Change for other resolution. (don't forget the constants) Screen 13 Do While (p2d.x < SCRN_X) and (p2d.x > -1) and (p2d.y < SCRN_Y) and (p2d.y > -1) ' Convert 3d to 2d p2d.x = p3d.x * Scale / p3d.z + (SCRN_X / 2) p2d.y = p3d.y * Scale / p3d.z + (SCRN_Y / 2) ' Put pixel PSet (p2d.x, p2d.y), 40 ' reduce z and check if z > 0 (no division by zero) p3d.z = p3d.z - 1 If p3d.z = 0 Then p3d.z = -1 Loop Do Loop Until InKey <> ""
Well I think the code above speaks for itself. First two types are created for 2d points and 3d points. Then the perspective is determined as 256. I like this value, but feel free to choose your own. The loop contains only a z decrement and a conversion from 3D to 2D. This code is the base of the 3D starfield which I present to you below. This source is not optimised and if you like you can do that by yourself (a good exercise). Like I said before, this tutorial is for beginners and optimising is a subject that I may treat in one of the last tutorials.
' =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= ' 3D Starfield ' ' The Flipside ' ' =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Declare Sub GenerateStar(index As Integer) Const MAX_STARS = 500 Const SCRN_WIDTH = 320 Const SCRN_HEIGHT = 200 Const SPEED = 0.015 ' Adjust for different speed Type Point3D x As Integer y As Integer z As Integer End Type Type Point2D x As Integer y As Integer End Type Dim shared Star(0 To (MAX_STARS - 1)) As Point3D Dim p2d(0 To (MAX_STARS - 1)) As Point2D Dim i As Integer Dim Scale As Integer Dim OldTime As double Dim TimeDummy As double ' Initialize starfield and variables Randomize Timer ' Random seed For i = 0 To (MAX_STARS - 1) GenerateStar(i) ' Create stars!!! Next Scale = 256 ' Perspective ' =-=-=-=-=-= Main Loop =-=-=-=-=-= Screen 13 ' Screen mode 13, change if you like but don't forget the constants! Do While Inkey <> Chr(27) ' Loop until esc is pressed For i = 0 To (MAX_STARS - 1) ' Delete old star PSet (p2d(i).x, p2d(i).y), 0 ' draw a black pixel on old position ' Transformation from 3D to 2D ' Origin lies in the middle of the screen p2d(i).x = star(i).x * Scale / star(i).z + (SCRN_WIDTH / 2) p2d(i).y = star(i).y * Scale / star(i).z + (SCRN_HEIGHT / 2) ' Draw star PSet (p2d(i).x, p2d(i).y), 15 ' reduce z and check if z > 0 (no division by zero) star(i).z = star(i).z - 1 If Star(i).z <= 0 Then ' star has passed us, lets create a new one GenerateStar(i) EndIf Next ' Some timing code to reduce the speed of the starfield ' Waiting time = SPEED seconds OldTime = Timer Do TimeDummy = Timer SLEEP 1 ' Does nothing but wait. (Old QB way of timing im always using) ' I know there are better ways, but this works Loop Until (TimeDummy - OldTime) >= SPEED Loop Sub GenerateStar(index As Integer) ' The origin of the screen lies at the middle of the screen. ' So in case of screen 13 the possible screen coordinates are: ' -160 to 160, -100 to 100 ' This procedure divides the stars array in four and assign every ' screen quadrant with it. Sounds complicated? ' Look at it this way: ' The first IF puts one fourth of MAX_STARS in the box constaining ' the coordinates (0, 0), (100, 160) ' Then the ElseIf puts stars in the box containing the coordinates ' (0, 0), (-160, 100) ' And so on! Get it already??? ' If not change the signs before the (SCRN_WIDTH / 2) or (SCRN_HEIGTH / 2) ' and see what happens! If index < (MAX_STARS / 4) THEN Star(index).x = Rnd(1) * (SCRN_WIDTH / 2) + 1 Star(index).y = Rnd(1) * (SCRN_HEIGHT / 2) + 1 ElseIf (index > (MAX_STARS / 4)) And (index < (MAX_STARS / 2)) Then Star(index).x = Rnd(1) * -(SCRN_WIDTH / 2) + 1 Star(index).y = Rnd(1) * (SCRN_HEIGHT / 2) + 1 ElseIf (index > (MAX_STARS / 2)) And (index < (3 * MAX_STARS / 4)) Then Star(index).x = Rnd(1) * (SCRN_WIDTH / 2) + 1 Star(index).y = Rnd(1) * -(SCRN_HEIGHT / 2) + 1 ElseIf (index > (3 * MAX_STARS / 4)) And (index < MAX_STARS) Then Star(index).x = Rnd(1) * -(SCRN_WIDTH / 2) + 1 Star(index).y = Rnd(1) * -(SCRN_HEIGHT / 2) + 1 EndIf Star(index).z = Rnd(1) * 256 End Sub
Hope you liked this tutorial. I did while writing. I know that not much of true game code and game concepts are explained in this tutorial. That's something I leave to later tutorials. First Iím gonna explain some basic techniques and after that the game elements are introduced. However, don't be disappointed. This will be soon enough.
Some tips for experimenting:
- Use color differences to create spacy effects.
- Change the code in order to create rain or snow (the y coordinate is decreased) and use a nice blue color. You could even change the colorcode of the drops which are further away (z-coordinate) to a darker color. This looks very cool!
- Use a circle command instead of pset and calculate the radius with the distance to the viewer (or in easier language, from the z coordinate). You get some planets you fly by!
- Next tutorial we are going to do something more interesting, namely rotations of points. Very interesting but a little heavy in the math. I'll introduce you to the biggest friends of a 3D coder; mr. Vector and mrs. Matrix.
Have fun and see you next time,
Proofreading and minor changes by Lachie Dazdarian - firstname.lastname@example.org