Introduction to C + + - Implementation of "match man Parkour" game

Keywords: C++ Class easyx

reference resources

  1. Interesting programming of C and C + + Games by Tong Jing

Stick Run Mobile

The idea of the game is that players control matchmaker's running and jumping through the keyboard to avoid bats and reach the end. The game map is randomly generated. With the increase of the number of levels, the game becomes more and more difficult

Define Player class

class Player
{
public:
	IMAGE im_show;                               // Image to be displayed at the current time
	float x_left, y_bottom;                      // Lower left corner position
	float vx, vy;                                // speed
	float width, height;                         // Width and height of the picture

	void draw()
	{
		putimagePng(x_left, y_bottom - height, &im_show);
	}

	void initialize()
	{
		loadimage(&im_show, _T("standright.png"));
		width = im_show.getwidth();
		height = im_show.getheight();
		updateXY(WIDTH / 2, HEIGHT / 2);
		vx = 10;
		vy = 10;
	}

	void updateXY(float mx, float my)
	{
		x_left = mx;
		y_bottom = my;
	}
};

Define global variables, initialize and display:

IMAGE im_land;
IMAGE im_bk;
Player player;

void startup()
{
	player.initialize();
	loadimage(&im_land, _T("land.png"));
	loadimage(&im_bk, _T("landscape1.png"));

	initgraph(WIDTH, HEIGHT);
	BeginBatchDraw();
}

void show()
{
	putimage(-100, -100, &im_bk);
	putimage(WIDTH / 2, HEIGHT / 2, &im_land);
	player.draw();
	FlushBatchDraw();
}

Control character movement by pressing the key:

void updateWithInput()
{
	if (_kbhit())
	{
		char input = getch();
		if (input == 'A')
		{
			player.x_left -= player.vx;
		}
		if (input == 'D')
		{
			player.x_left += player.vx;
		}
		if (input == 'W')
		{
			player.y_bottom -= player.vy;
		}
		if (input == 'S')
		{
			player.y_bottom += player.vy;
		}
	}
}

Asynchronous input

Using the asynchronous input function GetAsyncKeyState(), you can identify the situation when two keys are pressed at the same time. Players can use A, S, D, W or left, right, up and down direction keys to control the movement of matchmaker:

void updateWithInput()
{
	if (_kbhit())
	{
		if (GetAsyncKeyState(VK_LEFT) || GetAsyncKeyState('A'))
		{
			player.x_left -= player.vx;
		}
		if (GetAsyncKeyState(VK_RIGHT) || GetAsyncKeyState('D'))
		{
			player.x_left += player.vx;
		}
		if (GetAsyncKeyState(VK_UP) || GetAsyncKeyState('W'))
		{
			player.y_bottom -= player.vy;
		}
		if (GetAsyncKeyState(VK_DOWN) || GetAsyncKeyState('S'))
		{
			player.y_bottom += player.vy;
		}
	}
}

Enumeration type state switching

Matchmaker can stand to the left, stand to the right, run to the left, run to the right, jump to the left, jump to the right and die:

Add a new member variable in the Player class to store the right standing image, left standing image and role status:

IMAGE im_standright;
IMAGE im_standleft;
PlayerStatus playerStatus;                   // current state

Initialize the new member variable in initialize():

loadimage(&im_standright, _T("standright.png"));
loadimage(&im_standleft, _T("standleft.png"));

playerStatus = standright;
im_show = im_standright;

In updateWithInput(), when the keyboard controls the character to move, the character's standing direction should also change:

if (GetAsyncKeyState(VK_LEFT) || GetAsyncKeyState('A'))
{
	player.x_left -= player.vx;
	player.playerStatus = standleft;
}
if (GetAsyncKeyState(VK_RIGHT) || GetAsyncKeyState('D'))
{
	player.x_left += player.vx;
	player.playerStatus = standright;
}

Add run animation

Add the member variable IMS in the Player class_ Runright stores 8 pictures of running to the right and initializes:

vector<IMAGE> ims_runright;
int animId;

void initialize()
{
	loadimage(&im_standright, _T("standright.png"));
	loadimage(&im_standleft, _T("standleft.png"));

	playerStatus = standright;
	im_show = im_standright;
	width = im_standright.getwidth();
	height = im_standright.getheight();

	TCHAR filename[80];
	for (int i = 0; i <= 7; i++)
	{
		swprintf_s(filename, _T("runright%d.png"), i);
		IMAGE im;
		loadimage(&im, filename);
		ims_runright.push_back(im);
	}

	animId = 0;

	updateXY(WIDTH / 2, HEIGHT / 2);
	vx = 10;
	vy = 10;
}

The new member function runRight() updates the coordinates involved in the character running to the right, and realizes the state switching and animation effect:

void runRight()
{
	x_left += vx;
	if (playerStatus != runright)
	{
		playerStatus = runright;
		animId = 0;
	}
	else
	{
		animId++;
		if (animId >= ims_runright.size())
		{
			animId = 0;
		}
	}
	im_show = ims_runright[animId];
}

Realize jump

Add a member variable in the Player class to store the pictures and gravitational acceleration jumping to the right and left, and initialize:

IMAGE im_jumpright;
IMAGE im_jumpleft;
float gravity;

void initialize()
{
	loadimage(&im_jumpright, _T("jumpright.png"));
	loadimage(&im_jumpleft, _T("jumpleft.png"));

	vx = 10;
	vy = 0;
	gravity = 3;
}

Add beginJump() to control the animation switching and give the character an upward initial speed:

void beginJump()
{
	if (playerStatus != jumpleft && playerStatus != jumpright)           // Unable to take off in the air
	{
		if (playerStatus == runleft || playerStatus == standleft)
		{
			im_show = im_jumpleft;
			playerStatus = jumpleft;
		}
		else if (playerStatus == runright || playerStatus == standright)
		{
			im_show = im_jumpright;
			playerStatus = jumpright;
		}
		vy = -30;
	}
}

A new member function updateYcoordinate() is added to automatically complete the free fall movement:

void updateYcoordinate()
{
	if (playerStatus == jumpleft || playerStatus == jumpright)
	{
		vy += gravity;
		y_bottom += vy;

		if (y_bottom > HEIGHT / 2)
		{
			y_bottom = HEIGHT / 2;                     // Make sure it falls right on the ground
			if (playerStatus == jumpleft)
			{
				playerStatus = standleft;
			}
			if (playerStatus == jumpright)
			{
				playerStatus = standright;
			}
		}
	}
}

Ground class

class Land
{
public:
	IMAGE im_land;
	float left_x, right_x, top_y;
	float land_width, land_height;

	void initialize()
	{
		loadimage(&im_land, _T("land.png"));
		land_width = im_land.getwidth();
		land_height = im_land.getheight();
		left_x = WIDTH / 2;
		right_x = left_x + land_width;
		top_y = HEIGHT / 2;
	}

	void draw()
	{
		putimage(left_x, top_y, &im_land);
	}
};

Scene class

The scene class includes background pictures and multiple ground objects

class Scene
{
public:
	IMAGE im_bk;
	vector<Land> lands;

	void draw()
	{
		putimage(-100, -100, &im_bk);
		for (int i = 0; i < lands.size(); i++)
		{
			lands[i].draw();
		}
	}

	void initialize()
	{
		loadimage(&im_bk, _T("landscape1.png"));
		for (int i = 1; i <= 6; i++)
		{
			Land land;
			land.initialize();
			land.left_x = i * land.land_width;
			land.top_y = (i - 1) / 6.0 * HEIGHT;
			lands.push_back(land);
		}
	}
};

collision detection

Generate some random ground:

void initialize()
{
	loadimage(&im_bk, _T("landscape1.png"));
	lands.clear();
	for (int i = 1; i < 10; i++)
	{
		Land land;
		land.initialize();
		land.left_x = i * land.land_width;
		land.right_x = land.left_x + land.land_width;
		land.top_y = HEIGHT / 2 + rand() % 2 * HEIGHT / 10;
		lands.push_back(land);
	}
}

Add the member function isOnLand() for the Player class to judge whether the character is on the ground:

int isOnLand(Land& land, float ySpeed)
{
	float x_right = x_left + width;
	if (ySpeed <= 0)
	{
		ySpeed = 0;
	}
	if (land.left_x - x_left <= width * 0.6 && x_right - land.right_x <= width * 0.6 && abs(y_bottom - land.top_y) <= 5 + ySpeed)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

Add the member function isNotAllLands() to judge whether the character is not on all the ground:

int isNotOnAllLands(vector<Land>& lands, float speed)
{
	for (int i = 0; i < lands.size(); i++)
	{
		if (isOnLand(lands[i], speed))
		{
			return 0;
		}
	}
	return 1;
}

Modify runRight() and runLeft(). If the character is not on any ground, enter the jumping state:

void runRight(Scene& scene)
{
	x_left += vx;
	if (isNotOnAllLands(scene.lands, vy))
	{
		im_show = im_jumpright;
		playerStatus = jumpright;
		return;
	}
}

void runLeft(Scene& scene)
{
	x_left -= vx;
	if (isNotOnAllLands(scene.lands, vy))
	{
		im_show = im_jumpleft;
		playerStatus = jumpright;
		return;
	}
}

Modify updateYcoordinate() to make the character touch the ground and then stop falling:

void updateYcoordinate(Scene& scene)
{
	if (playerStatus == jumpleft || playerStatus == jumpright)
	{
		vy += gravity;
		y_bottom += vy;
		for (int i = 0; i < scene.lands.size(); i++)
		{
			if (isOnLand(scene.lands[i], vy))
			{
				y_bottom = scene.lands[i].top_y;
				if (playerStatus == jumpleft)
				{
					playerStatus = standleft;
				}
				if (playerStatus == jumpright)
				{
					playerStatus = standright;
				}
				break;
			}
		}
	}
}

Realize relative motion

Keep the character's position unchanged, while the background and ground move relative:

class Land
{
	void draw(float px, float py)
	{
		putimage(left_x - px, top_y - py, &im_land);      // Draw the ground with an offset
	}
};

class Scene
{
	void draw(float px, float py)
	{
		putimage(-px / 20, -100 - py / 20, &im_bk);       // Draw the background with an offset to achieve the foreground and background
		for (int i = 0; i < lands.size(); i++)
		{
			lands[i].draw(px, py);
		}
	}
}

class Player
{
	void draw()
	{
		putimagePng(WIDTH / 2, HEIGHT / 2 - height, &im_show);  // Character position does not move
	}
}

Endless levels and victory judgment

In updateWithoutInput(), the character runs to the last ground. This level wins the game, and the level increases by 1 to enter the next level; The character falls to the bottom of the screen and the game fails. Restart this level:

void updateWithoutInput()
{
	player.updateYcoordinate(scene);

	int landSize = scene.lands.size();
	if (player.x_left > scene.lands[landSize - 1].left_x && abs(player.y_bottom - scene.lands[landSize - 1].top_y) <= 2) // Game victory
	{
		scene.lastlevel = scene.level;
		scene.level++;
		scene.initialize();
		player.initialize();
	}
	else if (player.y_bottom > 1.5 * HEIGHT)                   // The character falls to the bottom of the screen and the game fails
	{
		scene.lastlevel = scene.level;
		scene.initialize();
		player.initialize();
	}
}

With the increase of level, the generated map is larger and the ground is more discontinuous:

void initialize()
{
	loadimage(&im_bk, _T("landscape1.png"));
	if (lands.size() == 0)                             // By default, it starts from the first level
	{
		level = 1;
		lastlevel = 1;
	}

	if (lands.size() == 0 || level > lastlevel)        // If you do not upgrade, the scene is not regenerated
	{
		lands.clear();

		Land land1;                                    // The first level should be under the game character
		land1.initialize();
		lands.push_back(land1);

		for (int i = 1; i < 10 + level * 2; i++)
		{
			Land land2;
			land2.initialize();
			int r1 = randBetweenMinMax(1, 30);
			if (r1 > level)
			{
				land2.left_x = land1.left_x + land2.land_width;
			}
			else
			{
				land2.left_x = land1.left_x + 2 * land2.land_width;
			}

			int r3 = randBetweenMinMax(1, 20);
			if (r1 > level)
			{
				land2.top_y = land1.top_y;
			}
			else
			{
				int r3 = randBetweenMinMax(-1, 1);
				land2.top_y = WIDTH / 2 + HEIGHT / 10 * r3;
			}
			land2.right_x = land2.left_x + land2.land_width;
			lands.push_back(land2);
			land1 = land2;
		}
	}
}

Enemy human

class Enemy
{
public:
	IMAGE im_enemy;
	float x, y;
	float enemy_width, enemy_height;
	float x_min, x_max;                                   // Maximum and minimum x coordinates of enemy movement
	float vx;                                             // Velocity in x direction

	void initialize()
	{
		loadimage(&im_enemy, _T("bat.png"));
		enemy_width = im_enemy.getwidth();
		enemy_height = im_enemy.getheight();
	}

	void draw(float px, float py)
	{
		putimagePng(x - enemy_width / 2 - px, y - enemy_height / 2 - py, &im_enemy);
	}

	void update()
	{
		x += vx;
		if (x > x_max || x < x_min)
		{
			vx = -vx;
		}
	}
};

Add Enemy variable in the scene and initialize:

vector<Enemy> enemies;

void draw(float px, float py)
{
	putimage(-px / 20, -100 - py / 20, &im_bk);       // Draw the background with an offset to achieve the foreground and background
	for (int i = 0; i < lands.size(); i++)
	{
		lands[i].draw(px, py);
	}

	for (int i = 0; i < enemies.size(); i++)
	{
		enemies[i].draw(px, py);
	}
}

void initialize()
{
	if (lands.size() == 0 || level > lastlevel)        // If you do not upgrade, the scene is not regenerated
	{
		enemies.clear();
		int numEnemy = (level + 3) / 5;
		int idStep = lands.size() / (numEnemy + 1);       // Enemies are evenly distributed in the scene
		for (int j = 1; j <= numEnemy; j++)
		{
			Enemy enemy;
			enemy.initialize();
			int landId = j * idStep;
			enemy.x = lands[landId].left_x + lands[landId].land_width / 2;
			enemy.y = lands[landId].top_y - enemy.enemy_height;

			float movingRange = enemy.enemy_width * (3 + level / 15.0);

			enemy.x_min = enemy.x - movingRange;
			enemy.x_max = enemy.x + movingRange;
			enemy.vx = 2 + level / 10.0;

			enemies.push_back(enemy);
		}

	}
}

In the Player class, add the member function isCollideEnemy() to judge whether the character collides with the bat:

int isCollideEnemy(Enemy& enemy)
{
	float x_center = x_left + width / 2;
	float y_center = y_bottom - height / 2;
	if (abs(enemy.x - x_center) <= enemy.enemy_width * 0.5 && abs(enemy.y - y_center) <= enemy.enemy_height * 0.5)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

In updateWithoutInput(), make all enemies move horizontally. If they collide with characters, the game fails:

for (int i = 0; i < scene.enemies.size(); i++)
{
	scene.enemies[i].update();                              // Enemy automatically updates location
	if (player.isCollideEnemy(scene.enemies[i]))
	{
		scene.lastlevel = scene.level;
		scene.initialize();
		player.initialize();
	}
}

Add sound effects

Add background music in startup():

mciSendString(_T("open game_music.mp3 alias bkmusic"), NULL, 0, NULL);
mciSendString(_T("play bkmusic repeat"), NULL, 0, NULL);

Add victory sound:

PlayMusicOnce(_T("success.mp3"));

Failed to add sound effect:

PlayMusicOnce(_T("warning.mp3"));

Add Pentagram

Place a five pointed star at the end of each level:

IMAGE im_star;
void draw(float px, float py)
{
	// Place a five pointed star above the last ground
	putimagePng(lands[lands.size() - 1].left_x + im_star.getwidth() - px, lands[lands.size() - 1].top_y - im_star.getheight() - py, &im_star);
}

Complete code

#include <graphics.h>
#include <conio.h>
#include <time.h>
#include "EasyXPng.h"
#include "Timer.h"
#include <vector>
using namespace std;
#pragma comment(lib,"Winmm.lib")

#define WIDTH 800
#define HEIGHT 600

enum PlayerStatus
{
	standleft, standright, runleft, runright, jumpleft, jumpright, die
};

int randBetweenMinMax(int min, int max)
{
	int r = rand() % (max - min + 1) + min;
	return r;
}

void PlayMusicOnce(TCHAR fileName[80]) // Play music function once
{
	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 Land
{
public:
	IMAGE im_land;
	float left_x, right_x, top_y;
	float land_width, land_height;

	void initialize()
	{
		loadimage(&im_land, _T("land.png"));
		land_width = im_land.getwidth();
		land_height = im_land.getheight();
		left_x = WIDTH / 2;
		right_x = left_x + land_width;
		top_y = HEIGHT / 2;
	}

	void draw(float px, float py)
	{
		putimage(left_x - px, top_y - py, &im_land);      // Draw the ground with an offset
	}
};

class Enemy
{
public:
	IMAGE im_enemy;
	float x, y;
	float enemy_width, enemy_height;
	float x_min, x_max;                                   // Maximum and minimum x coordinates of enemy movement
	float vx;                                             // Velocity in x direction

	void initialize()
	{
		loadimage(&im_enemy, _T("bat.png"));
		enemy_width = im_enemy.getwidth();
		enemy_height = im_enemy.getheight();
	}

	void draw(float px, float py)
	{
		putimagePng(x - enemy_width / 2 - px, y - enemy_height / 2 - py, &im_enemy);
	}

	void update()
	{
		x += vx;
		if (x > x_max || x < x_min)
		{
			vx = -vx;
		}
	}
};

class Scene
{
public:
	IMAGE im_bk;
	IMAGE im_star;
	vector<Land> lands;
	vector<Enemy> enemies;
	int level;
	int lastlevel;

	void draw(float px, float py)
	{
		putimage(-px / 20, -100 - py / 20, &im_bk);       // Draw the background with an offset to achieve the foreground and background
		for (int i = 0; i < lands.size(); i++)
		{
			lands[i].draw(px, py);
		}

		for (int i = 0; i < enemies.size(); i++)
		{
			enemies[i].draw(px, py);
		}
		// Place a five pointed star above the last ground
		putimagePng(lands[lands.size() - 1].left_x + im_star.getwidth() - px, lands[lands.size() - 1].top_y - im_star.getheight() - py, &im_star);

		// Show which level this is
		TCHAR s[20]; // character string
		setbkmode(TRANSPARENT); // Text transparent display
		swprintf_s(s, _T("The first %d shut"), level); // Generate text string
		settextcolor(RGB(0, 50, 200));  // Set text color
		settextstyle(30, 0, _T("Blackbody")); // Set text size and font
		outtextxy(WIDTH * 0.45, 30, s); // Output text
	}

	void initialize()
	{
		TCHAR filename[80];
		int i = level % 9 + 1;
		swprintf_s(filename, _T("landscape%d.png"), i);
		loadimage(&im_bk, filename);
		loadimage(&im_star, _T("star.png"));

		if (lands.size() == 0)                             // By default, it starts from the first level
		{
			level = 1;
			lastlevel = 1;
		}

		if (lands.size() == 0 || level > lastlevel)        // If you do not upgrade, the scene is not regenerated
		{
			lands.clear();

			Land land1;                                    // The first level should be under the game character
			land1.initialize();
			lands.push_back(land1);

			for (int i = 1; i < 10 + level * 2; i++)
			{
				Land land2;
				land2.initialize();
				int r1 = randBetweenMinMax(1, 30);
				if (r1 > level)
				{
					land2.left_x = land1.left_x + land2.land_width;
				}
				else
				{
					land2.left_x = land1.left_x + 2 * land2.land_width;
				}

				int r3 = randBetweenMinMax(1, 20);
				if (r1 > level)
				{
					land2.top_y = land1.top_y;
				}
				else
				{
					int r3 = randBetweenMinMax(-1, 1);
					land2.top_y = WIDTH / 2 + HEIGHT / 10 * r3;
				}
				land2.right_x = land2.left_x + land2.land_width;
				lands.push_back(land2);
				land1 = land2;
			}

			enemies.clear();
			int numEnemy = (level + 3) / 5;
			int idStep = lands.size() / (numEnemy + 1);       // Enemies are evenly distributed in the scene
			for (int j = 1; j <= numEnemy; j++)
			{
				Enemy enemy;
				enemy.initialize();
				int landId = j * idStep;
				enemy.x = lands[landId].left_x + lands[landId].land_width / 2;
				enemy.y = lands[landId].top_y - enemy.enemy_height;

				float movingRange = enemy.enemy_width * (3 + level / 15.0);

				enemy.x_min = enemy.x - movingRange;
				enemy.x_max = enemy.x + movingRange;
				enemy.vx = 2 + level / 10.0;

				enemies.push_back(enemy);
			}

		}
	}
};

class Player
{
public:
	IMAGE im_show;                               // Image to be displayed at the current time
	IMAGE im_standright;
	IMAGE im_standleft;
	IMAGE im_jumpright;
	IMAGE im_jumpleft;
	vector<IMAGE> ims_runright;
	vector<IMAGE> ims_runleft;
	int animId;

	PlayerStatus playerStatus;                   // current state

	float x_left, y_bottom;                      // Lower left corner position
	float vx, vy;                                // speed
	float gravity;
	float width, height;                         // Width and height of the picture

	void draw()
	{
		putimagePng(WIDTH / 2, HEIGHT / 2 - height, &im_show);  // Character position does not move
	}

	void initialize()
	{
		ims_runleft.clear();
		ims_runright.clear();
		loadimage(&im_standright, _T("standright.png"));
		loadimage(&im_standleft, _T("standleft.png"));
		loadimage(&im_jumpright, _T("jumpright.png"));
		loadimage(&im_jumpleft, _T("jumpleft.png"));

		playerStatus = standright;
		im_show = im_standright;
		width = im_standright.getwidth();
		height = im_standright.getheight();

		TCHAR filename[80];
		for (int i = 0; i <= 7; i++)
		{
			swprintf_s(filename, _T("runright%d.png"), i);
			IMAGE im;
			loadimage(&im, filename);
			ims_runright.push_back(im);
		}
		for (int i = 0; i <= 7; i++)
		{
			swprintf_s(filename, _T("runleft%d.png"), i);
			IMAGE im;
			loadimage(&im, filename);
			ims_runleft.push_back(im);
		}

		animId = 0;

		updateXY(WIDTH / 2, HEIGHT / 2);
		vx = 10;
		vy = 0;
		gravity = 3;
	}

	void updateXY(float mx, float my)
	{
		x_left = mx;
		y_bottom = my;
	}

	void runRight(Scene& scene)
	{
		x_left += vx;
		if (isNotOnAllLands(scene.lands, vy))
		{
			im_show = im_jumpright;
			playerStatus = jumpright;
			return;
		}

		if (playerStatus == jumpleft || playerStatus == jumpright)    // If it is take-off state
		{
			im_show = im_jumpright;
		}
		else
		{
			if (playerStatus != runright)
			{
				playerStatus = runright;
				animId = 0;
			}
			else
			{
				animId++;
				if (animId >= ims_runright.size())
				{
					animId = 0;
				}
			}
			im_show = ims_runright[animId];
		}
	}

	void runLeft(Scene& scene)
	{
		x_left -= vx;
		if (isNotOnAllLands(scene.lands, vy))
		{
			im_show = im_jumpleft;
			playerStatus = jumpright;
			return;
		}

		if (playerStatus == jumpleft || playerStatus == jumpright)      // If it is take-off state
		{
			im_show = im_jumpleft;
		}
		else
		{
			if (playerStatus != runleft)
			{
				playerStatus = runleft;
				animId = 0;
			}
			else
			{
				animId++;
				if (animId >= ims_runleft.size())
				{
					animId = 0;
				}
			}
			im_show = ims_runleft[animId];
		}
	}

	void standStill()
	{
		if (playerStatus == runleft || playerStatus == standleft)
		{
			im_show = im_standleft;
		}
		else if (playerStatus == runright || playerStatus == standright)
		{
			im_show = im_standright;
		}
	}

	void beginJump()
	{
		if (playerStatus != jumpleft && playerStatus != jumpright)           // Unable to take off in the air
		{
			if (playerStatus == runleft || playerStatus == standleft)
			{
				im_show = im_jumpleft;
				playerStatus = jumpleft;
			}
			else if (playerStatus == runright || playerStatus == standright)
			{
				im_show = im_jumpright;
				playerStatus = jumpright;
			}
			vy = -30;
		}
	}

	int isCollideEnemy(Enemy& enemy)
	{
		float x_center = x_left + width / 2;
		float y_center = y_bottom - height / 2;
		if (abs(enemy.x - x_center) <= enemy.enemy_width * 0.5 && abs(enemy.y - y_center) <= enemy.enemy_height * 0.5)
		{
			return 1;
		}
		else
		{
			return 0;
		}
	}

	int isOnLand(Land& land, float ySpeed)
	{
		float x_right = x_left + width;
		if (ySpeed <= 0)
		{
			ySpeed = 0;
		}
		if (land.left_x - x_left <= width * 0.6 && x_right - land.right_x <= width * 0.6 && abs(y_bottom - land.top_y) <= 5 + ySpeed)
		{
			return 1;
		}
		else
		{
			return 0;
		}
	}

	int isNotOnAllLands(vector<Land>& lands, float speed)
	{
		for (int i = 0; i < lands.size(); i++)
		{
			if (isOnLand(lands[i], speed))
			{
				return 0;
			}
		}
		return 1;
	}

	void updateYcoordinate(Scene& scene)
	{
		if (playerStatus == jumpleft || playerStatus == jumpright)
		{
			vy += gravity;
			y_bottom += vy;
			for (int i = 0; i < scene.lands.size(); i++)
			{
				if (isOnLand(scene.lands[i], vy))
				{
					y_bottom = scene.lands[i].top_y;
					if (playerStatus == jumpleft)
					{
						playerStatus = standleft;
					}
					if (playerStatus == jumpright)
					{
						playerStatus = standright;
					}
					break;
				}
			}
		}
	}
};

Player player;
Scene scene;
Timer timer;

void startup()
{
	mciSendString(_T("open game_music.mp3 alias bkmusic"), NULL, 0, NULL);
	mciSendString(_T("play bkmusic repeat"), NULL, 0, NULL);

	srand(time(0));
	scene.initialize();
	player.initialize();
	initgraph(WIDTH, HEIGHT);
	BeginBatchDraw();
}

void show()
{
	scene.draw(player.x_left - WIDTH / 2, player.y_bottom - HEIGHT / 2);
	player.draw();
	FlushBatchDraw();
	timer.Sleep(50);
}

void updateWithoutInput()
{
	player.updateYcoordinate(scene);

	int landSize = scene.lands.size();
	if (player.x_left > scene.lands[landSize - 1].left_x && abs(player.y_bottom - scene.lands[landSize - 1].top_y) <= 2) // Game victory
	{
		PlayMusicOnce((TCHAR*)_T("success.mp3"));
		scene.lastlevel = scene.level;
		scene.level++;
		scene.initialize();
		player.initialize();
	}
	else if (player.y_bottom > 1.5 * HEIGHT)                   // The character falls to the bottom of the screen and the game fails
	{
		PlayMusicOnce((TCHAR*)_T("warning.mp3"));
		scene.lastlevel = scene.level;
		scene.initialize();
		player.initialize();
	}

	for (int i = 0; i < scene.enemies.size(); i++)
	{
		scene.enemies[i].update();                              // Enemy automatically updates location
		if (player.isCollideEnemy(scene.enemies[i]))
		{
			PlayMusicOnce((TCHAR*)_T("warning.mp3"));
			scene.lastlevel = scene.level;
			scene.initialize();
			player.initialize();
		}
	}
}

void updateWithInput()
{
	player.standStill(); // By default, the game character stands still to the left or right
	if (_kbhit())
	{
		if (GetAsyncKeyState(VK_RIGHT) || GetAsyncKeyState('D'))
		{
			player.runRight(scene);
		}
		else if (GetAsyncKeyState(VK_LEFT) || GetAsyncKeyState('A'))
		{
			player.runLeft(scene);
		}
		if (GetAsyncKeyState(VK_UP) || GetAsyncKeyState('W'))
		{
			player.beginJump();
		}
	}
}

int main()
{
	startup();
	while (1)
	{
		show();
		updateWithoutInput();
		updateWithInput();
	}
	return 0;
}

Posted by Christopher on Fri, 22 Oct 2021 22:50:41 -0700