This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| en:projects:racing_game [2010/06/13 18:21] – mikk.leini | en:projects:racing_game [2020/07/20 09:00] (current) – external edit 127.0.0.1 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| ====== Racing game ====== | ====== Racing game ====== | ||
| - | <code c> | + | Platform: |
| - | // | + | |
| - | // | + | |
| - | // Racing game | + | |
| - | // | + | |
| - | // This is a small 2D top-view racing game for ARM-CAN kit. | + | |
| - | // | + | |
| - | // Copyright (c) 2009 Mikk Leini | + | |
| - | // | + | |
| - | // | + | |
| - | #include < | + | [{{ : |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | #include " | + | |
| - | #include " | + | |
| - | #include " | + | |
| - | #include " | + | |
| - | // | + | This racing game is a demonstration of using ARM-CAN UserInterface module to display 2D graphics on the LCD and use the joystick. The game itself is very simple – user has to drive the car on the track as fast as possible and there are no opponents nor obstacles on the track. Steering and driving is accomplished by moving the joystick. The program is made of several C source files which each take care of different tasks. |
| - | // | + | |
| - | // Global variables. | + | |
| - | // | + | |
| - | // | + | |
| - | tCar g_pCar; | + | |
| - | // | + | ===== Data types ===== |
| - | // | + | |
| - | // Car driving by controls. | + | There are structure data types to hold information about 2D points, track sections and the car. All of them are declared in " |
| - | // | + | |
| - | // | + | <code c> |
| - | void DoCarDriving(tCar *pCar) | + | typedef struct |
| { | { | ||
| - | long lVelocity; | + | short sX; |
| - | long lSteering; | + | short sY; |
| - | tBoolean bOnTrack; | + | } |
| + | tPoint; | ||
| - | // | + | typedef struct |
| - | // Check if car is on track. | + | { |
| - | // | + | |
| - | bOnTrack = TrackPointOnTrack(& | + | unsigned short usRadius; |
| + | } | ||
| + | tTrackSection; | ||
| - | // | + | typedef struct |
| - | // Get joystick position. | + | { |
| - | // | + | short sRadius; |
| - | JoystickGetPosition(& | + | |
| + | } | ||
| + | tPolarPoint; | ||
| - | // | + | typedef struct |
| - | // Do acceleration/ | + | { |
| - | // | + | |
| - | if(lVelocity | + | long lSteering; |
| - | { | + | |
| - | pCar-> | + | |
| - | } | + | |
| - | else if(lVelocity < pCar-> | + | |
| - | { | + | |
| - | pCar-> | + | } |
| - | } | + | tCar; |
| + | </ | ||
| - | // | + | ===== Trigonometric functions ===== |
| - | // Limit velocity off the track. | + | |
| - | // | + | |
| - | if(!bOnTrack) | + | |
| - | { | + | |
| - | if(pCar-> | + | |
| - | { | + | |
| - | pCar-> | + | |
| - | } | + | |
| - | if(pCar-> | + | |
| - | { | + | |
| - | pCar-> | + | |
| - | } | + | |
| - | } | + | |
| - | + | ||
| - | // | + | |
| - | // Handle steering. | + | |
| - | // | + | |
| - | pCar-> | + | |
| - | // | + | StellarisWare has a function to get the sine of different angles, but there is a need for cosine and arctangent functions. These functions are written in the source file named " |
| - | // Calculate car orientation. | + | |
| - | // | + | |
| - | pCar-> | + | |
| - | (pCar-> | + | |
| - | 0x4000; | + | |
| - | // | + | <code c> |
| - | // Calculate car position. | + | long cosine(unsigned long ulAngle); |
| - | // | + | unsigned long arctangent(unsigned long ulRise); |
| - | pCar-> | + | unsigned long arctangent2(signed long y, signed long x); |
| - | pCar-> | + | </code> |
| - | // | + | ===== Polygon drawing ===== |
| - | // Car on finish line? | + | |
| - | // | + | |
| - | if(TrackPointOnFinishLine(& | + | |
| - | { | + | |
| - | if(!pCar-> | + | |
| - | { | + | |
| - | pCar-> | + | |
| - | ClockReset(); | + | |
| - | } | + | |
| - | } | + | |
| - | else | + | |
| - | { | + | |
| - | pCar-> | + | |
| - | } | + | |
| - | // | + | As there are no polygon drawing functions in StellarisWare Graphics Library it is implemented in " |
| - | // Calculate time. | + | |
| - | // | + | |
| - | pCar-> | + | |
| - | } | + | |
| - | // | + | <code c> |
| - | // | + | void GrPolyDraw(const tContext |
| - | // Car preparing. | + | |
| - | // | + | void GrPolyFill(const tContext |
| - | //***************************************************************************** | + | unsigned long ulNumPoints); |
| - | void PrepareCar(void) | + | </code> |
| - | { | + | |
| - | g_pCar.pPosition.sX | + | ===== Rendering ===== |
| - | g_pCar.pPosition.sY = 0; | + | |
| - | g_pCar.ulAngle = 0x40000000; | + | All the graphics of the game are first rendered on the off-screen buffer and then written to the LCD. This method avoids flickering. The off-screen buffer is initialized in the // |
| - | g_pCar.ulColor = ClrBlue; | + | |
| - | } | + | " |
| + | |||
| + | <code c> | ||
| + | void RendererInit(void); | ||
| + | void RendererSetViewPoint(tPoint | ||
| + | void RendererRender(void); | ||
| + | void RendererWorldToScreenPoint(tPoint *pWorldPoint, | ||
| + | </ | ||
| + | |||
| + | ===== Rendering track ===== | ||
| + | |||
| + | The track is defined by the series of points which form a closed loop, so it is like a polygon. The points are specified in virtual centimeters and for each point, which define the end of the track section, there is also a width/2 (radius) of the track. Here is a short example of track data: | ||
| + | |||
| + | <code c> | ||
| + | #define NUM_TRACK_SECTIONS 10 | ||
| - | // | + | tTrackSection g_pSection[NUM_TRACK_SECTIONS] = |
| - | // | + | |
| - | // Main function. | + | |
| - | // | + | |
| - | // | + | |
| - | int main(void) | + | |
| { | { | ||
| - | // | + | { 0, 1000, 400 }, |
| - | // Set the clocking to run from the PLL at 50MHz | + | { 0, 1200, 400 }, |
| - | // | + | { 0, 3000, 400 }, |
| - | SysCtlClockSet(SYSCTL_SYSDIV_4 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | | + | { 1000, 6000, 400 }, |
| - | SYSCTL_XTAL_8MHZ); | + | { 6000, 6000, 400 }, |
| + | { 7000, 5000, 400 }, | ||
| + | { 7000, 0, 400 }, | ||
| + | { 6000, | ||
| + | { 1000, | ||
| + | { | ||
| + | }; | ||
| + | </ | ||
| - | // | + | [{{ : |
| - | // Device general configuration. | + | |
| - | // | + | |
| - | DeviceConfigure(); | + | |
| - | // | + | To get the edges of the track, |
| - | // Initialize buttons. | + | |
| - | // | + | |
| - | ButtonsInit(BUTTON_ONBOARD | BUTTON_UIBOARD_UP | BUTTON_UIBOARD_DOWN); | + | |
| - | // | + | But there are problems - these rectangles are not connected like a racing track might be. Too understand it, take a look at the blue and green rectangle on the picture. Firstly, there is a gap on the outer side of corner, secondly the rectangles intersect in the inner corner. Intersection point of two rectangles is solved in //GetLinesIntersectionPoint// function. To speed up drawing the track, it is converted from sections to two polygons which define inner and outer edges of the track. So the intersection point replaces corner points of intersecting rectangles and gap is filled by simply drawing a line between two consecutive edges. |
| - | // Initialize joystick. | + | |
| - | // | + | |
| - | JoystickInit(); | + | |
| - | // | + | The rendering of the track in //TrackRender// function is rather trivial. As the track is defined by inner and outer edge, polygon drawing functions are used to first fill the area inside the outer edge and then inside the inner edge. |
| - | // Initialize RGB LED. | + | |
| - | // | + | |
| - | RGBLEDInit(); | + | |
| - | // | + | Because it is not an offroad racing game, some collision detection is needed to check if the car is on the road or not. This check is achieved with the help of //PointInPolygon// function which contains an clever algorithm proposed by Bob Stein. The collision detection function |
| - | // Initialize renderer. | + | |
| - | // | + | |
| - | RendererInit(); | + | |
| - | + | ||
| - | // | + | |
| - | // Enable processor interrupts. | + | |
| - | // | + | |
| - | IntMasterEnable(); | + | |
| - | + | ||
| - | // | + | |
| - | // Generate track. | + | |
| - | // | + | |
| - | TrackGenerate(); | + | |
| - | // | + | <code c> |
| - | // Prepare car. | + | void TrackGenerate(void); |
| - | // | + | void TrackRender(tContext *pContext); |
| - | PrepareCar(); | + | tBoolean GetLinesIntersectionPoint(tPoint *pA1, tPoint *pA2, tPoint *pB1, |
| + | | ||
| + | tBoolean PointInPolygon(tPoint pPolygonPoints[], | ||
| + | | ||
| + | tBoolean TrackPointOnTrack(tPoint *pPoint); | ||
| + | tBoolean TrackPointOnFinishLine(tPoint *pPoint); | ||
| + | </ | ||
| - | // | + | ===== Rendering |
| - | // Loop forever. | + | |
| - | // | + | |
| - | while (true) | + | |
| - | { | + | |
| - | // | + | |
| - | // Handle user input. | + | |
| - | // | + | |
| - | DoCarDriving(& | + | |
| - | + | ||
| - | // | + | |
| - | // Set viewpoint to the car position. | + | |
| - | // | + | |
| - | RendererSetViewPoint(& | + | |
| - | // | + | The car in the game is also made up of polygons. It has a rectangular body and roof polygon, plus two circles as headlights. As the viewpoint do not rotate, the car has to rotate to give the right visual effect. Rotating means trigometric functions and it is most easily achieved by converting polar coordinates to Cartesian coordinates. That is why the points of the car are defined by radius and angle as seen below. |
| - | // Render screen. | + | |
| - | // | + | <code c> |
| - | RendererRender(); | + | tPolarPoint pBodyPoints[4]; |
| - | } | + | |
| - | } | + | |
| + | | ||
| + | |||
| + | pBodyPoints[1].sRadius = 200; | ||
| + | pBodyPoints[1].ulAngle = DEGREES_TO_FIX32(30); | ||
| + | |||
| + | | ||
| + | pBodyPoints[2].ulAngle = DEGREES_TO_FIX32(150); | ||
| + | |||
| + | pBodyPoints[3].sRadius = 200; | ||
| + | pBodyPoints[3].ulAngle = DEGREES_TO_FIX32(210); | ||
| </ | </ | ||
| + | |||
| + | All the points of the body parts are converted to x, y coordinates in // | ||
| + | |||
| + | <code c> | ||
| + | void CalculateScreenPoint(tPoint *pScreenPoint, | ||
| + | | ||
| + | void CarRender(tContext *pContext, tCar *pCar); | ||
| + | </ | ||
| + | |||
| + | ===== Main program ===== | ||
| + | |||
| + | [{{ : | ||
| + | |||
| + | The main function resides in " | ||
| + | |||
| + | <code c> | ||
| + | int main(void); | ||
| + | void DoCarDriving(tCar *pCar); | ||
| + | void PrepareCar(void); | ||
| + | </ | ||
| + | |||
| + | ===== Download ===== | ||
| + | |||
| + | * {{: | ||
| + | |||