// Written in the D Programming Language /** * The 21st lesson in the NeHe tutorial series. * Originally written by Jeff Molofee. * * Authors: Jeff Molofee * Olli Aalto */ module lesson21; import derelict.opengl.gl; import derelict.opengl.glu; import derelict.sdl.sdl; import derelict.sdl.mixer; import tango.stdc.stringz; import tango.math.Random; import tango.io.Stdout; import Int = tango.text.convert.Integer; /// The window title const char[] WINDOW_TITLE = "NeHe's Line Tutorial (D version)"; /// The main loop flag bool running; /// Our audio chunk Mix_Chunk* chunk; Mix_Music* music; /// Keeps Track Of Verticle Lines bool[11][11] vline; /// Keeps Track Of Horizontal Lines bool[11][11] hline; /// Done Filling In The Grid? bool filled; /// Is The Game Over? bool gameover; /// Antialiasing? bool anti = true; /// Enemy Delay int delay; /// Speed Adjustment For Really Slow Video Cards int adjust = 3; /// Player Lives int lives = 5; /// Internal Game Level int level = 1; /// Displayed Game Level int displayedLevel = 1; /// Game Stage int stage = 1; /// The width of the window int windowWidth = 640; /// The heigth of the window int windowHeigth = 480; /// The random number generator Random rand; /// Create a structure for our game objects struct GameObject { int fx, fy; // Fine Movement Position int x, y; // Current Player Position float spin = 0.0f; // Spin Direction } /// Player Information GameObject player; /// Enemy Information GameObject[] enemies; /// Hourglass Information GameObject hourGlass; /// Stepping Values For Slow Video Adjustment const steps = [1, 2, 4, 5, 10, 20]; /// Number of textures const NUM_TEXTURES = 2; /// Storage for textures GLuint[NUM_TEXTURES] textures; /// Base Display List For The Font GLuint base; /** * Module constructor. Here we load the GL, GLU, SDL and SDL Mixer shared libraries, * and the initialize SDL. */ static this() { DerelictGL.load(); DerelictGLU.load(); DerelictSDL.load(); DerelictSDLMixer.load(); if (SDL_Init(SDL_INIT_EVERYTHING) < 0) { throw new Exception("Failed to initialize SDL: " ~ getSDLError()); } // Enable key repeating if ((SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL))) { throw new Exception("Failed to set key repeat: " ~ getSDLError()); } rand = new Random(); } /** * Module destructor. SDL_Quit must be called somewhere, and as we initialized * it in the module constructor so the module destructor should be a suitable * place. */ static ~this() { // Clean up our font list glDeleteLists(base, 256); // Clean up our textures glDeleteTextures(NUM_TEXTURES, &textures[0]); // Stop playing the music Mix_HaltMusic(); // Free up the memory for the music Mix_FreeMusic(music); // Free up any memory for the sfx Mix_FreeChunk(chunk); // Close our audio device Mix_CloseAudio(); SDL_Quit(); } /** * The main function. This is where the fun begins. The first order of business * is the check the command line arguments if the user wanted to start in * fullscreen mode. Then the window is created and OpenGL is initialized with * basic settings. Finally the the function starts the main loop which will live * for the duration of the application. * * Params: * args = the command line arguments */ void main(char[][] args) { bool fullScreen = false; if (args.length > 1) { fullScreen = args[1] == "-fullscreen"; } // Open the sound device if (Mix_OpenAudio(22060, AUDIO_S16SYS, 2, 512) < 0) { throw new Exception("Unable to open audio: " ~ getMixError()); } createGLWindow(WINDOW_TITLE, windowWidth, windowHeigth, 16, fullScreen); initGL(); // reset our objects resetObjects(); // Load in the music music = Mix_LoadMUS("data/lktheme.mod"); // Start playing the music Mix_PlayMusic(music, -1); running = true; while (running) { processEvents(); logic(); drawGLScene(); SDL_GL_SwapBuffers(); SDL_Delay(10); } } /** * Process all the pending events. */ void processEvents() { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_VIDEORESIZE: break; case SDL_KEYDOWN: keyPressed(event.key.keysym.sym); break; case SDL_KEYUP: keyReleased(event.key.keysym.sym); break; case SDL_QUIT: running = false; break; default: break; } } } /** * Process a key released event. * * Params: * key = The released key */ void keyReleased(int key) { switch (key) { case SDLK_ESCAPE: running = false; break; case SDLK_SPACE: if (gameover) { gameover = false; // gameover becomes false filled = true; // filled becomes true level = 1; // Starting Level Is Set Back To One displayedLevel = 1; // Displayed Level Is Also Set To One stage = 1; // Game Stage Is Set To Zero lives = 5; // Lives Is Set To Five } break; case SDLK_a: anti = !anti; break; default: break; } } /** * Process a key pressed event. * * Params: * key = The pressed key */ void keyPressed(int key) { switch (key) { case SDLK_RIGHT: if ((player.x < 10) && (player.fx == player.x * 60) && (player.fy == player.y * 40)) { // Mark The Current Horizontal Border As Filled hline[player.x][player.y] = true; // Move The Player Right player.x++; } break; case SDLK_LEFT: if ((player.x > 0) && (player.fx == player.x * 60) && (player.fy == player.y * 40)) { // Move The Player Left player.x--; // Mark The Current Horizontal Border As Filled hline[player.x][player.y] = true; } break; case SDLK_UP: if ((player.y > 0) && (player.fx == player.x * 60) && (player.fy == player.y * 40)) { // Move The Player Up player.y--; // Mark The Current Verticle Border As Filled vline[player.x][player.y] = true; } break; case SDLK_DOWN: if ((player.y < 10) && (player.fx == player.x * 60) && (player.fy == player.y * 40)) { // Mark The Current Verticle Border As Filled vline[player.x][player.y] = true; // Move The Player Down player.y++; } break; default: break; } } void logic() { if (!gameover) { // Move the enemies foreach (inout enemy; enemies) { // Move The Enemy Right if ((enemy.x < player.x) && (enemy.fy == enemy.y * 40)) { enemy.x++; } // Move The Enemy Left if ((enemy.x > player.x) && (enemy.fy == enemy.y * 40)) { enemy.x--; } // Move The Enemy Down if ((enemy.y < player.y) && (enemy.fx == enemy.x * 60)) { enemy.y++; } // Move The Enemy Up if ((enemy.y > player.y) && (enemy.fx == enemy.x * 60)) { enemy.y--; } // Are Any Of The Enemies On Top Of The Player? if ((enemy.fx == player.fx) && (enemy.fy == player.fy)) { // Player Loses A Life lives--; // Are We Out Of Lives? if (lives == 0) { gameover = true; } // Play The Death Sound playSound("data/die.wav", 0); resetObjects(); } } // Should the enemies move? if (delay > (3 - level) && (hourGlass.fx != 2)) { // Reset The Delay Counter Back To Zero delay = 0; foreach (inout enemy; enemies) { // Is Fine Position On X Axis Lower Than Intended Position? if (enemy.fx < enemy.x * 60) { // Increase Fine Position On X Axis enemy.fx += steps[adjust]; // Spin Enemy Clockwise enemy.spin += steps[adjust]; } // Is Fine Position On X Axis Higher Than Intended Position? if (enemy.fx > enemy.x * 60) { // Decrease Fine Position On X Axis enemy.fx -= steps[adjust]; // Spin Enemy Counter Clockwise enemy.spin -= steps[adjust]; } // Is Fine Position On Y Axis Lower Than Intended Position? if (enemy.fy < enemy.y * 40) { // Increase Fine Position On Y Axis enemy.fy += steps[adjust]; // Spin Enemy Clockwise enemy.spin += steps[adjust]; } // Is Fine Position On Y Axis Higher Than Intended Position? if (enemy.fy > enemy.y * 40) { // Decrease Fine Position On Y Axis enemy.fy -= steps[adjust]; // Spin Enemy Counter Clockwise enemy.spin -= steps[adjust]; } } } // Move the player // Is Fine Position On X Axis Lower Than Intended Position? if (player.fx < player.x * 60) { // Increase The Fine X Position player.fx += steps[adjust]; } // Is Fine Position On X Axis Greater Than Intended Position? if (player.fx > player.x * 60) { // Decrease The Fine X Position player.fx -= steps[adjust]; } // Is Fine Position On Y Axis Lower Than Intended Position? if (player.fy < player.y * 40) { /* Increase The Fine Y Position */ player.fy += steps[adjust]; } // Is Fine Position On Y Axis Lower Than Intended Position? if (player.fy > player.y * 40) { // Decrease The Fine Y Position player.fy -= steps[adjust]; } } // Is The Grid Filled In? if (filled) { // Play The Level Complete Sound playSound("data/complete.wav", 0); // Increase The Stage stage++; // Is The Stage Higher Than 3? if (stage > 3) { stage = 1; // If So, Set The Stage To One level++; // Increase The Level displayedLevel++; // Increase The Displayed Level // Is The Level Greater Than 3? if (level > 3) { // Set The Level To 3 level = 3; // Give The Player A Free Life lives++; // Player Have More Than 5 Lives? if (lives > 5) { lives = 5; // Set Lives To Five } } } // Reset Player / Enemy Positions resetObjects(); // Loop Through The Grid X Coordinates for (int loop1 = 0; loop1 < 11; loop1++) { // Loop Through The Grid Y Coordinates for (int loop2 = 0; loop2 < 11; loop2++) { // If X Coordinate Is Less Than 10 if (loop1 < 10) { // Set Horizontal Value To false hline[loop1][loop2] = false; } // If Y Coordinate Is Less Than 10 if (loop2 < 10) { // Set Vertical Value To false vline[loop1][loop2] = false; } } } } // If The Player Hits The Hourglass While It's Being Displayed On The Screen if ((player.fx == hourGlass.x * 60) && (player.fy == hourGlass.y * 40) && (hourGlass.fx == 1)) { // Play Freeze Enemy Sound playSound("data/freeze.wav", -1); // Set The hourglass fx Variable To Two hourGlass.fx = 2; // Set The hourglass fy Variable To Zero hourGlass.fy = 0; } // Spin The Player Clockwise player.spin += 0.5f * steps[adjust]; // Is The spin Value Greater Than 360? if (player.spin > 360.0f) { player.spin -= 360; } // Spin The Hourglass Counter Clockwise hourGlass.spin -= 0.25f * steps[adjust]; // Is The spin Value Less Than 0? if (hourGlass.spin < 0.0f) { hourGlass.spin += 360.0f; } // Increase The hourglass fy Variable hourGlass.fy += steps[adjust]; // Is The hourglass fx Variable Equal To 0 And The fy Variable Greater Than 6000 Divided By The Current Level? if ((hourGlass.fx == 0) && (hourGlass.fy > 6000 / level)) { // Play The Hourglass Appears Sound playSound("data/hourglass.wav", 0); // Give The Hourglass A Random X Value hourGlass.x = rand.next() % 10 + 1; // Give The Hourglass A Random Y Value hourGlass.y = rand.next() % 11; // Set hourglass fx Variable To One (Hourglass Stage) hourGlass.fx = 1; // Set hourglass fy Variable To Zero (Counter) hourGlass.fy = 0; } // Is The hourglass fx Variable Equal To 1 And The fy Variable Greater Than 6000 Divided By The Current Level? if ((hourGlass.fx == 1) && (hourGlass.fy > 6000 / level)) { // Set fx To Zero (Hourglass Will Vanish) hourGlass.fx = 0; // Set fy to Zero (Counter Is Reset) hourGlass.fy = 0; } // Is The hourglass fx Variable Equal To 2 And The fy Variable Greater Than 500 Plus 500 Times The Current Level? if ((hourGlass.fx == 2) && (hourGlass.fy > 500 + (500 * level))) { // Kill The Freeze Sound playSound(null, 0); // Set hourglass fx Variable To Zero hourGlass.fx = 0; // Set hourglass fy Variable To Zero hourGlass.fy = 0; } delay++; } /** * Resets the player character and all the enemies. */ void resetObjects() { player.x = 0; // Reset Player X Position To Far Left Of The Screen player.y = 0; // Reset Player Y Position To The Top Of The Screen player.fx = 0; // Set Fine X Position To Match player.fy = 0; // Set Fine Y Position To Match // Create the enemies for the next level/stage enemies = new GameObject[stage * level]; // Loop Through All The Enemies foreach (inout enemy; enemies) { // A Random X Position enemy.x = 5 + rand.next() % 6; // A Random Y Position enemy.y = rand.next() % 11; // Set Fine X To Match enemy.fx = enemy.x * 60; // Set Fine Y To Match enemy.fy = enemy.y * 40; } } /** * Load the texture used in this tutorial. */ void loadGLTextures() { SDL_Surface* imageTexture; SDL_Surface* fontTexture; // Load The Bitmap, Check For Errors, If Bitmap's Not Found Quit if ((fontTexture = SDL_LoadBMP("data/Font.bmp")) !is null && (imageTexture = SDL_LoadBMP( "data/image.bmp")) !is null) { // Free the surface when exiting the scope scope(exit) SDL_FreeSurface(fontTexture); scope(exit) SDL_FreeSurface(imageTexture); // Create The Texture glGenTextures(2, &textures[0]); // Create Nearest Filtered Texture glBindTexture(GL_TEXTURE_2D, textures[0]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, 3, fontTexture.w, fontTexture.h, 0, GL_BGR, GL_UNSIGNED_BYTE, fontTexture.pixels); // Create Nearest Filtered Texture glBindTexture(GL_TEXTURE_2D, textures[1]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, 3, imageTexture.w, imageTexture.h, 0, GL_BGR, GL_UNSIGNED_BYTE, imageTexture.pixels); } } /** * Function to build our font list. */ void buildFont() { // Creating 256 Display List base = glGenLists(256); // Select Our Font Texture glBindTexture(GL_TEXTURE_2D, textures[0]); // Loop Through All 256 Lists for (int i = 0; i < 256; i++) { /* NOTE: * BMPs are stored with the top-leftmost pixel being the * last byte and the bottom-rightmost pixel being the first * byte. So an image that is displayed as * 1 0 * 0 0 * is represented data-wise like * 0 0 * 0 1 * And because SDL_LoadBMP loads the raw data without * translating to how it is thought of when viewed we need * to start at the bottom-right corner of the data and work * backwards to get everything properly. So the below code has * been modified to reflect this. Examine how this is done and * how the original tutorial is done to grasp the differences. * * As a side note BMPs are also stored as BGR instead of RGB * and that is why we load the texture using GL_BGR. It's * bass-ackwards I know but whattaya gonna do? */ // X Position Of Current Character float cx = 1 - cast(float) (i % 16) / 16.0f; // Y Position Of Current Character float cy = 1 - cast(float) (i / 16) / 16.0f; // Start Building A List glNewList(base + (255 - i), GL_COMPILE); { // Use A Quad For Each Character glBegin(GL_QUADS); { // Texture Coord (Bottom Left) glTexCoord2f(cx - 0.0625, cy); // Vertex Coord (Bottom Left) glVertex2i(0, 16); // Texture Coord (Bottom Right) glTexCoord2f(cx, cy); // Vertex Coord (Bottom Right) glVertex2i(16, 16); // Texture Coord (Top Right) glTexCoord2f(cx, cy - 0.0625f); // Vertex Coord (Top Right) glVertex2i(16, 0); // Texture Coord (Top Left) glTexCoord2f(cx - 0.0625f, cy - 0.0625f); // Vertex Coord (Top Left) glVertex2i(0, 0); } glEnd(); // Move To The Left Of The Character glTranslated(15, 0, 0); } glEndList(); } } /** * Resize and initialize the OpenGL window. */ void resizeGLScene(GLsizei width, GLsizei height) { if (height == 0) { height = 1; } // Reset The Current Viewport glViewport(0, 0, width, height); // Select The Projection Matrix glMatrixMode(GL_PROJECTION); // Reset The Projection Matrix glLoadIdentity(); // Set our ortho view glOrtho(0.0f, width, height, 0.0f, -1.0f, 1.0f); // Select The Modelview Matrix glMatrixMode(GL_MODELVIEW); // Reset The Modelview Matrix glLoadIdentity(); } /** * Initialize OpenGL. */ void initGL() { // Jump To Texture Loading Routine loadGLTextures(); // Build The Font buildFont(); // Enables Smooth Shading glShadeModel(GL_SMOOTH); // Black Background glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // Depth Buffer Setup glClearDepth(1.0f); // Enables Depth Testing glEnable(GL_DEPTH_TEST); // The Type Of Depth Test To Do glDepthFunc(GL_LEQUAL); // Really Nice Perspective Calculations glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Enable Blending glEnable(GL_BLEND); // Type Of Blending To Use glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } /** * This Is Where The Printing Happens. * * Params: * x = the x coordinate of the text * y = the y coordinate of the text * set = font set * string = the text to be printed */ void glPrint(GLint x, GLint y, int set, char[] string) { if (set > 1) { set = 1; } // Enable textures glEnable(GL_TEXTURE_2D); // Select Our Font Texture glBindTexture(GL_TEXTURE_2D, textures[0]); // Disables Depth Testing glDisable(GL_DEPTH_TEST); // Reset The Modelview Matrix glLoadIdentity(); // Position The Text (0,0 - Bottom Left) glTranslated(x, y, 0); // Choose The Font Set (0 or 1) glListBase(base - 32 + (128 * set)); // If Set 0 Is Being Used Enlarge Font if (set == 0) { // Enlarge Font Width And Height glScalef(1.5f, 2.0f, 1.0f); } // Write The Text To The Screen glCallLists(string.length, GL_UNSIGNED_BYTE, toStringz(string)); // Enables Depth Testing glEnable(GL_DEPTH_TEST); // Disable textures glDisable(GL_TEXTURE_2D); } /** * Here goes our drawing code */ void drawGLScene() { // These are to calculate our fps static GLint to = 0; static GLint frames = 0; // Clear The Screen And The Depth Buffer glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); // Set Color To Purple glColor3f(1.0f, 0.5f, 1.0f); // Write GRID CRAZY On The Screen glPrint(207, 24, 0, "GRID CRAZY"); // Set Color To Yellow glColor3f(1.0f, 1.0f, 0.0f); // Write Actual Level Stats glPrint(20, 20, 1, "Level: " ~ Int.toString(displayedLevel)); // Write Stage Stats glPrint(20, 40, 1, "Stage: " ~ Int.toString(stage)); // Is The Game Over? if (gameover) { // Pick A Random Color glColor3ub(rand.next() % 255, rand.next() % 255, rand.next() % 255); // Write GAME OVER To The Screen glPrint(472, 20, 1, "GAME OVER"); // Write PRESS SPACE To The Screen glPrint(456, 40, 1, "PRESS SPACE"); } // Loop Through Lives Minus Current Life for (int loop = 0; loop < lives - 1; loop++) { // Reset The View glLoadIdentity(); // Move To The Right Of Our Title Text glTranslatef(490 + (loop * 40.0f), 40.0f, 0.0f); // Rotate Counter Clockwise glRotatef(-player.spin, 0.0f, 0.0f, 1.0f); // Set Player Color To Light Green glColor3f(0.0f, 1.0f, 0.0f); // Start Drawing Our Player Using Lines glBegin(GL_LINES); glVertex2d(-5, -5); // Top Left Of Player glVertex2d(5, 5); // Bottom Right Of Player glVertex2d(5, -5); // Top Right Of Player glVertex2d(-5, 5); // Bottom Left Of Player glEnd(); // Rotate Counter Clockwise glRotatef(-player.spin * 0.5f, 0.0f, 0.0f, 1.0f); // Set Player Color To Dark Green glColor3f(0.0f, 0.75f, 0.0f); // Start Drawing Our Player Using Lines glBegin(GL_LINES); glVertex2d(-7, 0); // Left Center Of Player glVertex2d(7, 0); // Right Center Of Player glVertex2d(0, -7); // Top Center Of Player glVertex2d(0, 7); // Bottom Center Of Player glEnd(); } // Set Filled To True Before Testing filled = true; // Set Line Width For Cells To 2.0f glLineWidth(2.0f); // Disable Antialiasing glDisable(GL_LINE_SMOOTH); // Reset The Current Modelview Matrix glLoadIdentity(); // Loop From Left To Right for (int loop1 = 0; loop1 < 11; loop1++) { // Loop From Top To Bottom for (int loop2 = 0; loop2 < 11; loop2++) { // Set Line Color To Blue glColor3f(0.0f, 0.5f, 1.0f); // Has The Horizontal Line Been Traced if (hline[loop1][loop2]) { glColor3f(1.0f, 1.0f, 1.0f); } // Dont Draw To Far Right if (loop1 < 10) { // If A Horizontal Line Isn't Filled if (!hline[loop1][loop2]) { filled = false; } // Start Drawing Horizontal Cell Borders glBegin(GL_LINES); // Left Side Of Horizontal Line glVertex2d(20 + (loop1 * 60), 70 + (loop2 * 40)); // Right Side Of Horizontal Line glVertex2d(80 + (loop1 * 60), 70 + (loop2 * 40)); glEnd(); } // Set Line Color To Blue glColor3f(0.0f, 0.5f, 1.0f); // Has The Horizontal Line Been Traced if (vline[loop1][loop2]) { // If So, Set Line Color To White glColor3f(1.0f, 1.0f, 1.0f); } // Dont Draw To Far Down if (loop2 < 10) { // If A Verticle Line Isn't Filled if (!vline[loop1][loop2]) { filled = false; } // Start Drawing Verticle Cell Borders glBegin(GL_LINES); // Left Side Of Horizontal Line glVertex2d(20 + (loop1 * 60), 70 + (loop2 * 40)); // Right Side Of Horizontal Line glVertex2d(20 + (loop1 * 60), 110 + (loop2 * 40)); glEnd(); } // Enable Texture Mapping glEnable(GL_TEXTURE_2D); // Bright White Color glColor3f(1.0f, 1.0f, 1.0f); // Select The Tile Image glBindTexture(GL_TEXTURE_2D, textures[1]); // If In Bounds, Fill In Traced Boxes if ((loop1 < 10) && (loop2 < 10)) { // Are All Sides Of The Box Traced? if (hline[loop1][loop2] && hline[loop1][loop2 + 1] && vline[loop1][loop2] && vline[loop1 + 1][loop2]) { // Draw A Textured Quad glBegin(GL_QUADS); // Top Right glTexCoord2f(cast(float) (loop1 / 10.0f) + 0.1f, 1.0f - (cast(float) (loop2 / 10.0f))); glVertex2d(20 + (loop1 * 60) + 59, 70 + loop2 * 40 + 1); // Top Left glTexCoord2f(cast(float) (loop1 / 10.0f), 1.0f - (cast(float) (loop2 / 10.0f))); glVertex2d(20 + (loop1 * 60) + 1, 70 + loop2 * 40 + 1); // Bottom Left glTexCoord2f(cast(float) (loop1 / 10.0f), 1.0f - (cast(float) (loop2 / 10.0f) + 0.1f)); glVertex2d(20 + (loop1 * 60) + 1, (70 + loop2 * 40) + 39); // Bottom Right glTexCoord2f(cast(float) (loop1 / 10.0f) + 0.1f, 1.0f - (cast(float) (loop2 / 10.0f) + 0.1f)); glVertex2d(20 + (loop1 * 60) + 59, (70 + loop2 * 40) + 39); glEnd(); } } // Disable Texture Mapping glDisable(GL_TEXTURE_2D); } } // Set The Line Width To 1.0f glLineWidth(1.0f); // Is Anti true? if (anti) { glEnable(GL_LINE_SMOOTH); } // If fx = 1 Draw The Hourglass if (hourGlass.fx == 1) { // Reset The Modelview Matrix glLoadIdentity(); // Move To The Fine Hourglass Position glTranslatef(20.0f + (hourGlass.x * 60), 70.0f + (hourGlass.y * 40), 0.0f); // Rotate Clockwise glRotatef(hourGlass.spin, 0.0f, 0.0f, 1.0f); // Set Hourglass Color To Random Color glColor3ub(rand.next() % 255, rand.next() % 255, rand.next() % 255); // Start Drawing Our Hourglass Using Lines glBegin(GL_LINES); // Top Left Of Hourglass glVertex2d(-5, -5); // Bottom Right Of Hourglass glVertex2d(5, 5); // Top Right Of Hourglass glVertex2d(5, -5); // Bottom Left Of Hourglass glVertex2d(-5, 5); // Bottom Left Of Hourglass glVertex2d(-5, 5); // Bottom Right Of Hourglass glVertex2d(5, 5); // Top Left Of Hourglass glVertex2d(-5, -5); // Top Right Of Hourglass glVertex2d(5, -5); glEnd(); } // Reset The Modelview Matrix glLoadIdentity(); // Move To The Find Player Position glTranslatef(player.fx + 20.0f, player.fy + 70.0f, 0.0f); // Rotate Clockwise glRotatef(player.spin, 0.0f, 0.0f, 1.0f); // Set Player Color To Light Green glColor3f(0.0f, 1.0f, 0.0f); // Start Drawing Our Player Using Lines glBegin(GL_LINES); // Top Left Of Player glVertex2d(-5, -5); // Bottom Right Of Player glVertex2d(5, 5); // Top Right Of Player glVertex2d(5, -5); // Bottom Left Of Player glVertex2d(-5, 5); glEnd(); // Rotate Clockwise glRotatef(player.spin * 0.5f, 0.0f, 0.0f, 1.0f); // Set Player Color To Dark Green glColor3f(0.0f, 0.75f, 0.0f); // Start Drawing Our Player Using Lines glBegin(GL_LINES); // Left Center Of Player glVertex2d(-7, 0); // Right Center Of Player glVertex2d(7, 0); // Top Center Of Player glVertex2d(0, -7); // Bottom Center Of Player glVertex2d(0, 7); glEnd(); // Loop To Draw Enemies foreach (enemy; enemies) { // Reset The Modelview Matrix glLoadIdentity(); glTranslatef(enemy.fx + 20.0f, enemy.fy + 70.0f, 0.0f); // Make Enemy Body Pink glColor3f(1.0f, 0.5f, 0.5f); // Start Drawing Enemy glBegin(GL_LINES); // Top Point Of Body glVertex2d(0, -7); // Left Point Of Body glVertex2d(-7, 0); // Left Point Of Body glVertex2d(-7, 0); // Bottom Point Of Body glVertex2d(0, 7); // Bottom Point Of Body glVertex2d(0, 7); // Right Point Of Body glVertex2d(7, 0); // Right Point Of Body glVertex2d(7, 0); // Top Point Of Body glVertex2d(0, -7); glEnd(); // Rotate The Enemy Blade */ glRotatef(enemy.spin, 0.0f, 0.0f, 1.0f); // Make Enemy Blade Red */ glColor3f(1.0f, 0.0f, 0.0f); // Start Drawing Enemy Blade glBegin(GL_LINES); // Top Left Of Enemy glVertex2d(-7, -7); // Bottom Right Of Enemy glVertex2d(7, 7); // Bottom Left Of Enemy glVertex2d(-7, 7); // Top Right Of Enemy glVertex2d(7, -7); glEnd(); } // Gather our frames per second frames++; { GLint time = SDL_GetTicks(); if (time - to >= 5000) { GLfloat seconds = (time - to) / 1000.0; GLfloat fps = frames / seconds; Stdout(frames)(" frames in ")(seconds)(" seconds = ")(fps)(" FPS").newline; to = time; frames = 0; } } } /** * Play the given sound. * * Params: * sound = the sound to be played * repeat = the repeat count, -1 means infinite */ void playSound(char[] sound, int repeat) { if (sound is null) { Mix_HaltChannel(1); Mix_FreeChunk(chunk); chunk = null; return; } if (chunk !is null) { Mix_HaltChannel(1); Mix_FreeChunk(chunk); chunk = null; } chunk = Mix_LoadWAV(sound); if (chunk is null) { throw new Exception( "Failed to load sound: '" ~ sound ~ "': " ~ getMixError()); } Mix_PlayChannel(-1, chunk, repeat); } /** * Initializes and opens the SDL window. */ void createGLWindow(char[] title, int width, int height, int bits, bool fullScreen) { // Set the OpenGL attributes SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); // Set the window title SDL_WM_SetCaption(toStringz(title), null); // Note the SDL_DOUBLEBUF flag is not required to enable double // buffering when setting an OpenGL video mode. // Double buffering is enabled or disabled using the // SDL_GL_DOUBLEBUFFER attribute. (See above.) int mode = SDL_OPENGL; if (fullScreen) { mode |= SDL_FULLSCREEN; } // Now open a SDL OpenGL window with the given parameters if (SDL_SetVideoMode(width, height, bits, mode) is null) { throw new Exception("Failed to open OpenGL window: " ~ getSDLError()); } resizeGLScene(width, height); } /** * Get the SDL error as a D string. * * Returns: A D string containing the current SDL error. */ char[] getSDLError() { return fromStringz(SDL_GetError()); } /** * Get the SDL Mixer error as a D string. * * Returns: A D string containing the current SDL Mixer error. */ char[] getMixError() { return fromStringz(Mix_GetError()); }