reference resources
- Interesting programming of C and C + + Games by Tong Jing
Stick to the 100 second game
The main idea of the game is that players use the mouse to control rockets to avoid a UFO and more and more rebound bullets
Background display
Use the prepared background.png picture to display the background picture in the picture:
#include <graphics.h> #include <conio.h> #define WIDTH 560 #define HEIGHT 800 int main() { IMAGE im_bk; // Define image objects loadimage(&im_bk, _T("background.png")); initgraph(WIDTH, HEIGHT); putimage(0, 0, &im_bk); // Show background picture _getch(); return 0; }
Display rocket
Like the background display, define im first_ Rocket image object, and then import rocket image rocket.png. However, the rocket image has a transparent channel. If it is drawn with putimage(), the transparent part will be displayed in black, so import the EasyXPng.h header file to solve the problem:
bullet
Define the bullet class, add an IMAGE image object to the class, then define a draw() member function to display bullets, and define an update() member function to update the position and speed of bullets:
class Bullet { public: IMAGE im_bullet; float x, y; // Bullet coordinates float vx, vy; // Bullet speed float radius; // Bullet radius void draw() { putimagePng(x - radius, y - radius, &im_bullet); } void update() { x += vx; y += vy; if (x <= 0 || x >= WIDTH) { vx = -vx; } if (y <= 0 || y >= HEIGHT) { vy = -vy; } } }; IMAGE im_bk, im_bullet; // Define image objects Bullet bullet; void startup() { loadimage(&im_bk, _T("background.png")); loadimage(&im_bullet, _T("bullet.png")); bullet.x = WIDTH / 2; bullet.y = HEIGHT / 2; bullet.vx = 2; bullet.vy = 2; bullet.im_bullet = im_bullet; bullet.radius = im_bullet.getwidth() / 2; // Set the radius of the bullet to half the width of its picture initgraph(WIDTH, HEIGHT); BeginBatchDraw(); } void show() { putimage(0, 0, &im_bk); // Display background bullet.draw(); // Show bullets FlushBatchDraw(); // Batch drawing Sleep(10); } void updateWithoutInput() { bullet.update(); } int main() { startup(); while (1) { show(); updateWithoutInput(); } return 0; }
Add a bullet every 2 seconds
Define the array bullet [] to store existing bullets, initialize a new bullet randomly every 2 seconds, update the speed and position of existing bullets, and display:
Bullet bullet[MaxBulletNum]; int bulletNum = 0; void updateWithoutInput() { static int lastSecond = 0; // Record how many seconds the previous program ran static int nowSecond = 0; // Record how many seconds the current program has run static clock_t start = clock(); // Record the first running time clock_t now = clock(); // Get the current moment nowSecond = (int(now - start) / CLOCKS_PER_SEC); if (nowSecond == lastSecond + 2) { lastSecond = nowSecond; if (bulletNum < MaxBulletNum) { bullet[bulletNum].x = WIDTH / 2; bullet[bulletNum].y = 10; float angle = (rand() / double(RAND_MAX) - 0.5) * 0.9 * PI; float scalar = 2 * rand() / double(RAND_MAX) + 2; bullet[bulletNum].vx = scalar * sin(angle); bullet[bulletNum].vy = scalar * cos(angle); bullet[bulletNum].im_bullet = im_bullet; bullet[bulletNum].radius = im_bullet.getwidth() / 2; } bulletNum++; } for (int i = 0; i < bulletNum; i++) { bullet[i].update(); } }
rocket
Define the rocket class, similar to the bullet class:
class Rocket { public: IMAGE im_rocket; float x, y; // Rocket coordinates float width, height; // Width and height of rocket picture void draw() { putimagePng(x - width / 2, y - height / 2, &im_rocket); } void update(float mx, float my) { x = mx; y = my; } };
Initialize the rocket in startup():
loadimage(&im_rocket, _T("rocket.png")); rocket.im_rocket = im_rocket; rocket.width = im_rocket.getwidth(); rocket.height = im_rocket.getheight();
In updateWithInput(), when the mouse moves, the position of the rocket is updated:
void updateWithInput() { ExMessage e; while (peekmessage(&e)) { if (e.message == WM_MOUSEMOVE) { rocket.update(e.x, e.y); } } }
Collision judgment and rocket explosion
Add the member function isCollideRocket() in the Bullet class:
int isCollideRocket(Rocket rocket) { float distance_x = fabs(rocket.x - x); float distance_y = fabs(rocket.y - y); if (distance_x < rocket.width / 2 && distance_y < rocket.height / 2) { return 1; } else { return 0; } }
Add a rocket explosion image object in the rocket class and modify the draw() that displays the rocket:
IMAGE im_blowup; int islive; void draw() { if (islive > 0) { putimagePng(x - width / 2, y - height / 2, &im_rocket); } else { putimagePng(x - width / 2, y - height / 2, &im_blowup); } }
During initialization, the islive of the rocket is set to 1, and all bullets are traversed in updateWithoutInput(). If any bullet collides with the rocket, the islive of the rocket is set to 0:
for (int i = 0; i < bulletNum; i++) { bullet[i].update(); if (bullet[i].isCollideRocket(rocket)) { rocket.islive = 0; bullet[i].x = 5; // Move the current bullet away to prevent repeated collision bullet[i].y = 5; break; } }
Adhere to the display of time and multiple lives
Add the member variable liveSecond in the Rocket class, record the number of seconds the Rocket survived, and draw it in draw():
int liveSecond; void draw() { if (islive > 0) { putimagePng(x - width / 2, y - height / 2, &im_rocket); } else { putimagePng(x - width / 2, y - height / 2, &im_blowup); } TCHAR s[20]; setbkmode(TRANSPARENT); swprintf_s(s, _T("%d second"), liveSecond); settextcolor(WHITE); settextstyle(40, 0, _T("Blackbody")); outtextxy(WIDTH * 0.85, 20, s); }
Then, add the member variable life to record the life of the rocket, add the member function updateWhenLifeLoss() to handle the operation when the life of the rocket decreases, and display the life of the rocket in the upper left corner of the implementation window in draw():
int life; void updateWithLifeLost() { life--; } void draw() { for (int i = 0; i < life; i++) // A picture of life rocket is displayed in the upper left corner { putimagePng(i * width * 0.9, 0, &im_rocket); } TCHAR s[20]; // Just above the window shows how many seconds it lasted setbkmode(TRANSPARENT); swprintf_s(s, _T("%d second"), liveSecond); settextcolor(WHITE); settextstyle(40, 0, _T("Blackbody")); outtextxy(WIDTH * 0.85, 20, s); if (life > 0) { putimagePng(x - width / 2, y - height / 2, &im_rocket); } else { putimagePng(x - width / 2, y - height / 2, &im_blowup); } }
Add sound effects
Import winmm.lib library to support windows multimedia programming:
#pragma comment(lib, "Winmm.lib")
background music
Add code in startup() and play the game repeatedly_ music.mp3:
mciSendString(_T("open game_music.mp3 alias bkmusic"), NULL, 0, NULL); // Turn on background music mciSendString(_T("play bkmusic repeat"), NULL, 0, NULL); // Loop Playback
Explosion sound
Define the function to play music once:
void PlayMusicOnce(TCHAR fileName[80]) { TCHAR cmdString1[50]; swprintf_s(cmdString1, _T("open %s alias tmpmusic"), fileName); // Generate command string mciSendString(_T("close tmpmusic"), NULL, 0, NULL); // Turn off the previous music first mciSendString(cmdString1, NULL, 0, NULL); // Turn on the music mciSendString(_T("play tmpmusic"), NULL, 0, NULL); // Play only once }
In the updateWhenLifeLost() member function of Rocket() class, we call:
void updateWithLifeLost() { PlayMusicOnce((TCHAR*)_T("explode.mp3")); life--; }
Add smart UFO class
Define a new class SmartUFO, which inherits from bullet class:
class SmartUFO : public Bullet { public: void updateVelforTarget(Rocket targetRocket) { float scalar = 1 * rand() / double(RAND_MAX) + 1; // Let the speed of the flying saucer aim at the target rocket if (targetRocket.x > x) { vx = scalar; } else if (targetRocket.x < x) { vx = -scalar; } if (targetRocket.y > y) { vy = scalar; } else if (targetRocket.y < y) { vy = -scalar; } } };
Initialize UFO in startup():
ufo.x = WIDTH / 2; ufo.y = 10; ufo.im_bullet = im_UFO; ufo.radius = im_UFO.getwidth() / 2; ufo.updateVelforTarget(rocket);
In updateWithoutInput(), set the ufo speed according to the rocket position every 1 second:
if (nowSecond == lastSecond + 1) { ufo.updateVelforTarget(rocket); }
Judge whether the flying saucer collides with the rocket. If it collides, the rocket will lose its life:
ufo.update(); if (ufo.isCollideRocket(rocket)) { rocket.updateWithLifeLost(); ufo.x = 5; ufo.y = 5; }
Complete code
#include <graphics.h> #include <conio.h> #include <time.h> #include "EasyXPng.h" #define WIDTH 560 #define HEIGHT 800 #define MaxBulletNum 200 #pragma comment(lib, "Winmm.lib") void sleep(DWORD ms) // Exact delay function { static DWORD oldtime = GetTickCount(); while (GetTickCount() - oldtime < ms) { Sleep(1); } oldtime = GetTickCount(); } void PlayMusicOnce(TCHAR fileName[80]) { TCHAR cmdString1[50]; swprintf_s(cmdString1, _T("open %s alias tmpmusic"), fileName); // Generate command string mciSendString(_T("close tmpmusic"), NULL, 0, NULL); // Turn off the previous music first mciSendString(cmdString1, NULL, 0, NULL); // Turn on the music mciSendString(_T("play tmpmusic"), NULL, 0, NULL); // Play only once } class Rocket { public: IMAGE im_rocket; IMAGE im_blowup; float x, y; // Rocket coordinates float width, height; // Width and height of rocket picture int liveSecond; int life; void draw() { for (int i = 0; i < life; i++) // A picture of life rocket is displayed in the upper left corner { putimagePng(i * width * 0.9, 0, &im_rocket); } TCHAR s[20]; // Just above the window shows how many seconds it lasted setbkmode(TRANSPARENT); swprintf_s(s, _T("%d second"), liveSecond); settextcolor(WHITE); settextstyle(40, 0, _T("Blackbody")); outtextxy(WIDTH * 0.85, 20, s); if (life > 0) { putimagePng(x - width / 2, y - height / 2, &im_rocket); } else { putimagePng(x - width / 2, y - height / 2, &im_blowup); } } void update(float mx, float my) { x = mx; y = my; } void updateWithLifeLost() { PlayMusicOnce((TCHAR*)_T("explode.mp3")); life--; } }; class Bullet { public: IMAGE im_bullet; float x, y; // Bullet coordinates float vx, vy; // Bullet speed float radius; // Bullet radius void draw() { putimagePng(x - radius, y - radius, &im_bullet); } void update() { x += vx; y += vy; if (x <= 0 || x >= WIDTH) { vx = -vx; } if (y <= 0 || y >= HEIGHT) { vy = -vy; } } int isCollideRocket(Rocket rocket) { float distance_x = fabs(rocket.x - x); float distance_y = fabs(rocket.y - y); if (distance_x < rocket.width / 2 && distance_y < rocket.height / 2) { return 1; } else { return 0; } } }; class SmartUFO : public Bullet { public: void updateVelforTarget(Rocket targetRocket) { float scalar = 1 * rand() / double(RAND_MAX) + 1; // Let the speed of the flying saucer aim at the target rocket if (targetRocket.x > x) { vx = scalar; } else if (targetRocket.x < x) { vx = -scalar; } if (targetRocket.y > y) { vy = scalar; } else if (targetRocket.y < y) { vy = -scalar; } } }; IMAGE im_bk, im_bullet, im_rocket, im_blowup, im_UFO; // Define image objects Bullet bullet[MaxBulletNum]; Rocket rocket; SmartUFO ufo; int bulletNum = 0; void startup() { srand(time(0)); loadimage(&im_bk, _T("background.png")); loadimage(&im_bullet, _T("bullet.png")); loadimage(&im_rocket, _T("rocket.png")); loadimage(&im_blowup, _T("blowup.png")); loadimage(&im_UFO, _T("ufo.png")); rocket.im_rocket = im_rocket; rocket.im_blowup = im_blowup; rocket.width = im_rocket.getwidth(); rocket.height = im_rocket.getheight(); rocket.life = 5; ufo.x = WIDTH / 2; ufo.y = 10; ufo.im_bullet = im_UFO; ufo.radius = im_UFO.getwidth() / 2; ufo.updateVelforTarget(rocket); initgraph(WIDTH, HEIGHT); BeginBatchDraw(); mciSendString(_T("open game_music.mp3 alias bkmusic"), NULL, 0, NULL); // Turn on background music mciSendString(_T("play bkmusic repeat"), NULL, 0, NULL); // Loop Playback } void show() { putimage(0, 0, &im_bk); // Display background for (int i = 0; i < bulletNum; i++) { bullet[i].draw(); // Show bullets } rocket.draw(); ufo.draw(); FlushBatchDraw(); // Batch drawing sleep(10); } void updateWithoutInput() { if (rocket.life <= 0) { return; } static int lastSecond = 0; // Record how many seconds the previous program ran static int nowSecond = 0; // Record how many seconds the current program has run static clock_t start = clock(); // Record the first running time clock_t now = clock(); // Get the current moment nowSecond = (int(now - start) / CLOCKS_PER_SEC); rocket.liveSecond = nowSecond; if (nowSecond == lastSecond + 2) { lastSecond = nowSecond; if (bulletNum < MaxBulletNum) { bullet[bulletNum].x = WIDTH / 2; bullet[bulletNum].y = 10; float angle = (rand() / double(RAND_MAX) - 0.5) * 0.9 * PI; float scalar = 2 * rand() / double(RAND_MAX) + 2; bullet[bulletNum].vx = scalar * sin(angle); bullet[bulletNum].vy = scalar * cos(angle); bullet[bulletNum].im_bullet = im_bullet; bullet[bulletNum].radius = im_bullet.getwidth() / 2; } bulletNum++; } if (nowSecond == lastSecond + 1) { ufo.updateVelforTarget(rocket); } for (int i = 0; i < bulletNum; i++) { bullet[i].update(); if (bullet[i].isCollideRocket(rocket)) { rocket.updateWithLifeLost(); bullet[i].x = 5; // Move the current bullet away to prevent repeated collision bullet[i].y = 5; break; } } ufo.update(); if (ufo.isCollideRocket(rocket)) { rocket.updateWithLifeLost(); ufo.x = 5; ufo.y = 5; } } void updateWithInput() { if (rocket.life <= 0) { return; } ExMessage e; while (peekmessage(&e)) { if (e.message == WM_MOUSEMOVE) { rocket.update(e.x, e.y); } } } int main() { startup(); while (1) { show(); updateWithoutInput(); updateWithInput(); } return 0; }