基於EasyX和Raylib的堅持100秒

Raylib 播放音頻毫無壓力, 相比之下 EasyX 需要藉助 Windows API mciString 傳入播放相關的命令, 感覺風格不統一, 不夠優雅。

另一個問題是 clock()CLOCKS_PER_SEC 在 Linux 下結果和 windows 相差了10倍, 需要手動處理下。

EasyX

// 根據《C和C++遊戲趣味編程》第12章 堅持100秒 寫出

#include <graphics.h>
#include <conio.h> // _kbhit()
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include "EasyXPng.h"

// 引用 Windows Multimedia API
#pragma comment(lib, "Winmm.lib")

// global variables
#define WIDTH 560
#define HEIGHT 800

IMAGE im_bk;
IMAGE im_rocket;
IMAGE im_blowup;
IMAGE im_bullet;

void playMusicOnce(char filename[80])
{
    char cmdString[50] = { 0 };
    sprintf(cmdString, "open %s alias tmpmusic", filename);
    mciSendString("close tmpmusic", NULL, 0, NULL); // 先關閉前一次的音樂
    mciSendString(cmdString, NULL, 0, NULL); // 打開這次的音樂
    mciSendString("play tmpmusic", NULL, 0, NULL); // 僅播放一次
}

// 火箭
class Rocket
{
public:
	IMAGE im_rocket;
	IMAGE im_blowup;
	float x, y;
	float width;
	float height;
	int liveSecond; // 存活了多長時間
	int life; // 幾條命

	void draw()
	{
		if (life > 0)
		{
			putimagePng(x - width / 2, y - height / 2, &im_rocket);
		}
		else
		{
			putimagePng(x - width / 2, y - height / 2, &im_blowup);
		}

		// 窗口左上角顯示 life 個火箭圖標
		for (int i = 0; i < life; i++)
		{
            // 注: 如果去掉 ofs, 看起來結果應該一樣,但實際運行時子彈不會出現,推測是編譯器優化導致結果差異。。
            int ofs = 0;
			putimagePng(ofs + i * width * 0.9, ofs, &im_rocket);
		}

		char s[20] = { 0 };
		setbkmode(TRANSPARENT);
		sprintf(s, "%d秒", liveSecond);
		settextcolor(WHITE);
		settextstyle(40, 0, _T("黑體"));
		outtextxy(WIDTH * 0.85, 20, s);
	}

	void update(float mx, float my)
	{
		x = mx;
		y = my;
	}

	void updateWhenLifeLost()
	{
		if (life > 0)
		{
            playMusicOnce("explode.mp3");
			life--;
            if (life == 0)
            {
                mciSendString("close bkmusic", NULL, 0, NULL);
            }
		}
	}
};

// 子彈
class Bullet
{
public:
	IMAGE image;
	float x, y; // coordinate
	float vx, vy; // velocity
	float radius;

	void draw()
	{
		putimagePng(x - radius, y - radius, &image);
	}

	void update()
	{
		x += vx;
		y += vy;
		if (x <= 0 || x >= WIDTH)
		{
			vx = -vx;
		}
		if (y <= 0 || y >= HEIGHT)
		{
			vy = -vy;
		}
	}

	// 判斷子彈是否和火箭碰撞
	bool isCollideRocket(Rocket rocket) const
	{
		float distanec_x = fabs(rocket.x - x);
		float distance_y = fabs(rocket.y - y);
		if (distanec_x < rocket.width / 2 && distance_y < rocket.height / 2)
		{
			return true;
		}
		return false;
	}
};

// 飛碟
class SmartUFO : public Bullet
{
public:
	void updateVelForTarget(Rocket targetRocket)
	{
		float scalar = rand() / double(RAND_MAX) + 1;
		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;
		}
	}
};

int bulletNum = 0;
#define MaxBulletNum 20
Bullet bullet[MaxBulletNum];

Rocket rocket;

IMAGE im_UFO;
SmartUFO ufo;

void startup()
{
    mciSendString(_T("open game_music.mp3 alias bkmusic"), NULL, 0, NULL); // 打開背景音樂
    mciSendString(_T("play bkmusic repeat"), NULL, 0, NULL); // 循環播放
	loadimage(&im_bk, "background.png");
	loadimage(&im_rocket, "rocket.png");
	loadimage(&im_blowup, "blowup.png");
	loadimage(&im_bullet, "bullet.png");
	loadimage(&im_UFO, "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.image = im_UFO;
	ufo.radius = im_UFO.getwidth() / 2;
	ufo.updateVelForTarget(rocket);

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

// 精確延時函數
void sleep(DWORD ms)
{
	static DWORD oldtime = GetTickCount();
	while (GetTickCount() - oldtime < ms)
	{
		Sleep(1);
	}
	oldtime = GetTickCount();
}

void show()
{
	putimage(0, 0, &im_bk); // background image
	for (int i = 0; i < bulletNum; i++)
	{
		bullet[i].draw();
	}
	rocket.draw();
	ufo.draw();
	FlushBatchDraw();
	sleep(10);
}

void updateWithoutInput()
{
	if (rocket.life <= 0)
	{
		return;
	}

	// 每隔2s,新生成一顆子彈
	static int lastSecond = 0;
	static int nowSecond = 0;
	static clock_t start = clock();
	clock_t now = clock();
	nowSecond = (int(now - start) / CLOCKS_PER_SEC);
	rocket.liveSecond = nowSecond;

	if (nowSecond == lastSecond + 1)
	{
		// 時間過了1秒, 更新飛碟速度
		ufo.updateVelForTarget(rocket);

		// 更新飛碟位置和速度
		ufo.update();

		// 如果飛碟和火箭相撞, 則火箭減命, 飛碟回到左上角初始位置
		if (ufo.isCollideRocket(rocket))
		{
			rocket.updateWhenLifeLost();
			ufo.x = 5;
			ufo.y = 5;
		}
	}

	if (bulletNum < MaxBulletNum && nowSecond == lastSecond + 2)
	{
		// printf("bulletNum = %d, MaxBulletNum = %d\n", 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].image = im_bullet;
		bullet[bulletNum].radius = im_bullet.getwidth() / 2;
		bulletNum++;

		lastSecond = nowSecond;
	}

	for (int i = 0; i < bulletNum; i++)
	{
		bullet[i].update();
		if (bullet[i].isCollideRocket(rocket))
		{
			rocket.updateWhenLifeLost();
			bullet[i].x = 5; // 當前子彈移開, 防止重複碰撞
			bullet[i].y = 5;
			break;
		}
	}
}

void updateWithInput()
{
	if (rocket.life <= 0)
	{
		return;
	}

	MOUSEMSG m;
	while (MouseHit())
	{
		m = GetMouseMsg();
		if (m.uMsg == WM_MOUSEMOVE)
		{
			rocket.update(m.x, m.y);
		}
	}
}

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

	closegraph();

	return 0;
}

基於 Raylib

// 根據《C和C++遊戲趣味編程》第12章 堅持100秒 寫出

#include "raylib.h"
#include <stdio.h>
#include <stdlib.h>
#include "raylib_helper.hpp"
#include <time.h>
#include <math.h>

// global variables
#define WIDTH 560
#define HEIGHT 800

Texture2D bk_texture;
Texture2D rocket_texture;
Texture2D blowup_texture;
Texture2D bullet_texture;

Music bk_music; // 背景音樂
Music ep_music; // 爆炸聲

void playMusicOnce(char filename[80])
{
    StopMusicStream(ep_music);
    ep_music.looping = false;
    PlayMusicStream(ep_music);
}

class Rocket
{
public:
	Texture2D rocket_texture;
	Texture2D blowup_texture;
	float x;
	float y;
	float width;
	float height;
	int liveSecond; // 存活了多長時間
	int life; // 幾條命

	void draw()
	{
		if (life > 0)
		{
			DrawTexture(rocket_texture, x - width / 2, y - height / 2, WHITE);
		}
		else
		{
			DrawTexture(blowup_texture, x - width / 2, y - height / 2, WHITE);
		}

		// 窗口左上角顯示 life 個火箭圖標
		for (int i = 0; i < life; i++)
		{
			int ofs = 0;
			DrawTexture(rocket_texture, ofs + i * width * 0.9, ofs, WHITE);
		}

		char s[20] = { 0 };
		sprintf(s, "%d s", liveSecond);
		DrawText(s, WIDTH * 0.85, 20, 40, WHITE);
	}

	void update(Vector2 pos)
	{
		x = pos.x;
		y = pos.y;
	}

	void updateWhenLifeLost()
	{
		if (life > 0)
		{
			life--;
            playMusicOnce("explode.mp3");

            if (life == 0)
            {
                StopMusicStream(bk_music);
            }
		}
	}
};

class Bullet
{
public:
	Texture2D texture;
	float x, y; // coordinate
	float vx, vy; // velocity
	float radius;

	void draw()
	{
		DrawTexture(texture, x - radius, y - radius, WHITE);
	}

	void update()
	{
		x += vx;
		y += vy;
		if (x <= 0 || x >= WIDTH)
		{
			vx = -vx;
		}
		if (y <= 0 || y >= HEIGHT)
		{
			vy = -vy;
		}
	}

	// 判斷子彈是否和火箭碰撞
	bool isCollideRocket(Rocket rocket) const
	{
		float distanec_x = fabs(rocket.x - x);
		float distance_y = fabs(rocket.y - y);
		if (distanec_x < rocket.width / 2 && distance_y < rocket.height / 2)
		{
			return true;
		}
		return false;
	}
};

// 飛碟
class SmartUFO : public Bullet
{
public:
	void updateVelForTarget(Rocket targetRocket)
	{
		float scalar = rand() / double(RAND_MAX) + 1;
		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;
		}
	}
};


#define MaxBulletNum 20
Bullet bullet[MaxBulletNum];
int bulletNum = 0;

Rocket rocket;

Texture2D ufo_texture;
SmartUFO ufo;

void startup()
{
    SetConfigFlags(FLAG_MSAA_4X_HINT);  // NOTE: Try to enable MSAA 4X
	InitWindow(WIDTH, HEIGHT, "persist 100 seconds");
    SetTargetFPS(60);

    InitAudioDevice();                  // Initialize audio device

    //bk_music = LoadMusicStream("game_music.mp3");
    char* music_path = "game_music.mp3";
    bk_music = LoadMusicStream(music_path);
    bk_music.looping = true;
    PlayMusicStream(bk_music);

    ep_music = LoadMusicStream("explode.mp3");

	bk_texture = LoadTexture("background.png");
	rocket_texture = LoadTexture("rocket.png");
	blowup_texture = LoadTexture("blowup.png");
	bullet_texture = LoadTexture("bullet.png");
	ufo_texture = LoadTexture("ufo.png");

	rocket.rocket_texture = rocket_texture;
	rocket.blowup_texture = blowup_texture;
	rocket.width = rocket_texture.width;
	rocket.height = rocket_texture.height;
	rocket.life = 5;
	rocket.x = WIDTH / 2;
	rocket.y = HEIGHT / 2;

	ufo.x = WIDTH / 2;
	ufo.y = 10;
	ufo.texture = ufo_texture;
	ufo.radius = ufo_texture.width / 2;
	ufo.updateVelForTarget(rocket);
}

void show()
{
	BeginDrawing();
	{
	    DrawTexture(bk_texture, WIDTH/2 - bk_texture.width/2, HEIGHT/2 - bk_texture.height/2, WHITE);
		for (int i = 0; i < bulletNum; i++)
		{
			bullet[i].draw();
		}
		rocket.draw();
		ufo.draw();
	}
	EndDrawing();
}

void updateWithoutInput()
{
	if (rocket.life <= 0)
	{
		return;
	}

	// 每隔2s,新生成一顆子彈
	static int lastSecond = 0;
	static int nowSecond = 0;
	static clock_t start = clock();
	clock_t now = clock();
    int unit = CLOCKS_PER_SEC;
#if __linux__
    unit /= 10;
#endif
	nowSecond = (int(now - start) / unit);
    //printf("start = %d, now = %d, CLOCKS_PER_SEC = %d\n", (int)start, (int)now, CLOCKS_PER_SEC);K
	rocket.liveSecond = nowSecond;

	if (nowSecond == lastSecond + 1)
	{
		// 時間過了1秒, 更新飛碟速度
		ufo.updateVelForTarget(rocket);

		// 更新飛碟位置和速度
		ufo.update();

		// 如果飛碟和火箭相撞, 則火箭減命, 飛碟回到左上角初始位置
		if (ufo.isCollideRocket(rocket))
		{
			rocket.updateWhenLifeLost();
			ufo.x = 5;
			ufo.y = 5;
		}
	}

	if (bulletNum < MaxBulletNum && nowSecond == lastSecond + 2)
	{
		lastSecond = nowSecond;
		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].texture = bullet_texture;
		bullet[bulletNum].radius = bullet_texture.width / 2;
		bulletNum++;
	}

	for (int i = 0; i < bulletNum; i++)
	{
		bullet[i].update();
		if (bullet[i].isCollideRocket(rocket))
		{
			rocket.updateWhenLifeLost();
			bullet[i].x = 5; // 當前子彈移開, 防止重複碰撞
			bullet[i].y = 5;
			break;
		}
	}
}

void updateWithInput()
{
	if (rocket.life <= 0)
	{
		return;
	}

	Vector2 pos = GetMousePosition();
	rocket.update(pos);
}

int main()
{
    startup();

    while (!WindowShouldClose())
    {
        UpdateMusicStream(bk_music);
        UpdateMusicStream(ep_music);
    	updateWithoutInput();
    	updateWithInput();
        show();
    }

    UnloadTexture(bk_texture);
    UnloadTexture(rocket_texture);
    UnloadTexture(blowup_texture);
    UnloadTexture(bullet_texture);

    CloseWindow();

	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章