提瓦特幸存者4
能帮到你的话,就给个赞吧 😘
#include <iostream>
#include <windows.h>
#include <string>
#include <graphics.h>
#include <vector>
#pragma comment(lib, "MSIMG32.LIB")
#pragma comment(lib, "Winmm.lib")
class Button {
private:
enum State { idle, hovered, pushed };
private:
IMAGE imgIdle, imgHovered, imgPushed; //资源
RECT region; //位置
State state = idle; //状态
public:
Button(const RECT& region, LPCTSTR pathIdle, LPCTSTR pathHovered, LPCTSTR pathPushed);
virtual ~Button() = 0; //纯虚函数 依然可以有函数体
public:
void processMessage(const ExMessage& msg);
void draw();
private:
//鼠标是否命中区域
bool checkCursorHit(int x, int y) {
return x >= region.left && x <= region.right && y >= region.top && y <= region.bottom;
}
virtual void onClick() = 0;
};
class StartButton :public Button {
public:
StartButton(RECT region, LPCTSTR pathIdle, LPCTSTR pathHovered, LPCTSTR pathPushed)
:Button(region, pathIdle, pathHovered, pathPushed) {};
virtual ~StartButton();
private:
virtual void onClick();
};
class QuitButton :public Button {
public:
QuitButton(RECT region, LPCTSTR pathIdle, LPCTSTR pathHovered, LPCTSTR pathPushed)
:Button(region, pathIdle, pathHovered, pathPushed) {};
virtual ~QuitButton();
private:
virtual void onClick();
};
class Atlas {
public:
std::vector<IMAGE*> images;
public:
Atlas(LPCTSTR path, int num);
~Atlas();
};
class Animation {
private:
int imgIndex = 0; //帧索引
int timer = 0; //计时器: 本张动画已经播放的时间
int frameInterval; //帧间隔ms: 两帧动画间的时间
Atlas* atlas;
public:
Animation(Atlas* atlas, int frameInterval); //LPCSTR 更通用的常字符指针
public:
//播放一张动画
void draw(int x, int y, int playTime); //playTime :本张动画播放的时间
};
class Bullet {
private:
const int radius = 10;
public:
POINT pos{ 0,0 };
public:
void draw() const {
//橙红色填充圆
setlinecolor(RGB(255, 155, 50));
setfillcolor(RGB(200, 75, 10));
fillcircle(pos.x, pos.y, radius);
}
};
class Player {
private:
POINT playerPos{ 500,500 }; //玩家位置
const int playerSpeed = 6; //移动速度
bool isLeft = false, isRight = false, //移动方向
isUp = false, isDown = false;
bool isFacingLeft = false; //面部朝向
const int shadowWidth = 32; //玩家阴影高度
public:
const int width = 80; //玩家高度
const int height = 80;
private:
IMAGE playerShadow;
Animation* animationPlayerLeft; //玩家动画
Animation* animationPlayerRight;
public:
Player();
~Player();
public:
void processMessage(const ExMessage& msg);
void move();
void draw(int frameInterval);
public:
int x() const { return playerPos.x; }
int y() const { return playerPos.y; }
};
class Enemy {
private:
POINT pos{ 0,0 }; //敌人位置
const int width = 80; //敌人高度
const int height = 80;
const int shadowWidth = 48; //敌人阴影高度
const int enemySpeed = 2; //移动速度
bool isLeft = false, isRight = false, //移动方向
isUp = false, isDown = false;
bool isFacingLeft = false; //面部朝向
bool isAlive = true; //怪物存活
private:
IMAGE enemyShadow;
Animation* animationEnemyLeft; //敌人动画
Animation* animationEnemyRight;
public:
Enemy();
~Enemy();
public:
void move(const Player& player);
bool checkBulletCollision(const Bullet& bullet) const;
bool checkPlayerCollision(const Player& player) const;
void draw(int frameInterval);
public:
void hurt() { isAlive = false; }
bool checkAlive() const { return isAlive; }
};
void putImageAlpha(int x, int y, IMAGE* img); //图像绘制(透明度)
void generateEnemy(std::vector<Enemy*>& enemys); //生成敌人
void updateBullets(std::vector<Bullet>& bullets, const Player& player); //更新子弹
void drawScore(const int& score);
Atlas* playerLeftAtlas;
Atlas* playerRightAtlas;
Atlas* enemyLeftAtlas;
Atlas* enemyRightAtlas;
const int windowWidth = 1280;
const int windowHeight = 720;
const int buttonWidth = 192;
const int buttonHeight = 75;
const int frameInterval = 1000 / 120;
bool running = true;
bool isStarted = false;
int main() {
initgraph(windowWidth, windowHeight);
//加载地图
IMAGE background;
loadimage(&background, _T("resources/img/background.png"));
//加载菜单
IMAGE menu;
loadimage(&menu, _T("resources/img/menu.png"));
//加载按钮
RECT startRegion, quitRegion;
startRegion.left = (windowWidth - buttonWidth) / 2;
startRegion.right = startRegion.left + buttonWidth;
startRegion.top = 430;
startRegion.bottom = startRegion.top + buttonHeight;
quitRegion.left = (windowWidth - buttonWidth) / 2;
quitRegion.right = quitRegion.left + buttonWidth;
quitRegion.top = 550;
quitRegion.bottom = quitRegion.top + buttonHeight;
StartButton startButton{ startRegion , _T("resources/img/ui_start_idle.png"),
_T("resources/img/ui_start_hovered.png"),_T("resources/img/ui_start_pushed.png") };
QuitButton quitButton{ quitRegion , _T("resources/img/ui_quit_idle.png"),
_T("resources/img/ui_quit_hovered.png"),_T("resources/img/ui_quit_pushed.png") };
//初始化资产
playerLeftAtlas = new Atlas(_T("resources/img/player_left_%d.png"), 6);
playerRightAtlas = new Atlas(_T("resources/img/player_right_%d.png"), 6);
enemyLeftAtlas = new Atlas(_T("resources/img/enemy_left_%d.png"), 6);
enemyRightAtlas = new Atlas(_T("resources/img/enemy_right_%d.png"), 6);
//加载mp3
//取 alias 为 bgm
mciSendString(_T("open resources/mus/bgm.mp3 alias bgm"), nullptr, 0, nullptr);
mciSendString(_T("open resources/mus/hit.wav alias hit"), nullptr, 0, nullptr);
//重复播放bgm 从0开始
mciSendString(_T("play bgm repeat from 0"), nullptr, 0, nullptr);
Player player;
std::vector<Bullet> bullets(3);
std::vector<Enemy*> enemys;
unsigned int score = 0;
ExMessage message;
BeginBatchDraw();
while (running) {
ULONGLONG startTime = GetTickCount64();
//读数据
peekmessage(&message);
//数据处理
//事件处理
if(isStarted)
player.processMessage(message);
else {
quitButton.processMessage(message);
startButton.processMessage(message);
}
//数据处理
if (isStarted) {
//更新玩家
player.move();
//更新子弹
updateBullets(bullets, player);
generateEnemy(enemys);
for (auto& enemy : enemys)
enemy->move(player);
//怪物和子弹碰撞检测
for (auto& enemy : enemys)
for (const auto& bullet : bullets)
if (enemy->checkBulletCollision(bullet)) {
score++;
enemy->hurt();
mciSendString(_T("play hit from 0"), nullptr, 0, nullptr);
}
//怪物和玩家碰撞检测
for (auto& enemy : enemys) {
if (enemy->checkPlayerCollision(player)) {
TCHAR text[64];
_stprintf_s(text, _T("最终得分: %d"), score);
MessageBox(GetHWnd(), text, _T("游戏结束"), MB_OK);
running = false;
break;
}
}
//移除以消失的怪物
for (auto& enemy : enemys) {
for (const auto& bullet : bullets) {
if (!enemy->checkAlive()) {
std::swap(enemy, enemys.back());
delete enemys.back();
enemys.pop_back();
}
}
}
}
//渲染
cleardevice();
if (isStarted) {
putimage(0, 0, &background);
player.draw(frameInterval);
for (const auto& bullet : bullets)
bullet.draw();
for (const auto& enemy : enemys)
enemy->draw(frameInterval);
drawScore(score);
}
else {
putimage(0, 0, &menu);
startButton.draw();
quitButton.draw();
}
FlushBatchDraw();
//120刷新
ULONGLONG executionTime = GetTickCount64() - startTime;
if (executionTime < frameInterval)
Sleep(frameInterval - executionTime);
}
EndBatchDraw();
//释放资产
delete playerLeftAtlas;
delete playerRightAtlas;
delete enemyLeftAtlas;
delete enemyRightAtlas;
}
Player::Player(){
loadimage(&playerShadow, _T("resources/img/shadow_player.png"));
animationPlayerLeft = new Animation(playerLeftAtlas, 45);
animationPlayerRight = new Animation(playerRightAtlas, 45);
}
Player::~Player(){
delete animationPlayerLeft;
delete animationPlayerRight;
}
void Player::processMessage(const ExMessage& msg){
//判断移动方向
if (msg.message == WM_KEYDOWN) {
switch (msg.vkcode) {
case VK_UP:
isUp = true;
break;
case VK_DOWN:
isDown = true;
break;
case VK_LEFT:
isLeft = true;
break;
case VK_RIGHT:
isRight = true;
break;
default:
break;
}
}
else if (msg.message == WM_KEYUP) {
switch (msg.vkcode) {
case VK_UP:
isUp = false;
break;
case VK_DOWN:
isDown = false;
break;
case VK_LEFT:
isLeft = false;
break;
case VK_RIGHT:
isRight = false;
break;
default:
break;
}
}
}
//计算移动信息
void Player::move(){
// x,y 代表 向量
int x = isRight - isLeft;
int y = isDown - isUp;
double modulus = sqrt(x * x + y * y); //向量的模
if (modulus) {
double vectorX = x / modulus;
double vectorY = y / modulus;
playerPos.x += int(playerSpeed * vectorX);
playerPos.y += int(playerSpeed * vectorY);
}
//校准
if (playerPos.x < 0) playerPos.x = 0;
if (playerPos.y < 0) playerPos.y = 0;
if (playerPos.x + width > windowWidth) playerPos.x = windowWidth - width;
if (playerPos.y + height > windowHeight) playerPos.y = windowHeight - height;
//修改面部朝向
//等于0时,指向原先面部朝向
if (x > 0)
isFacingLeft = false;
else if (x < 0)
isFacingLeft = true;
}
void Player::draw(int frameInterval){
//绘制阴影
int xShadow = playerPos.x + (width - shadowWidth) / 2;
int yShadow = playerPos.y + height - 8;
putImageAlpha(xShadow, yShadow, &playerShadow);
//绘制动画
if (isFacingLeft)
animationPlayerLeft->draw(playerPos.x, playerPos.y, frameInterval);
else
animationPlayerRight->draw(playerPos.x, playerPos.y, frameInterval);
}
Enemy::Enemy(){
loadimage(&enemyShadow, _T("resources/img/shadow_enemy.png"));
animationEnemyLeft = new Animation(enemyLeftAtlas, 45);
animationEnemyRight = new Animation(enemyRightAtlas, 45);
enum spawnEdge { up, down, left, right };
spawnEdge edge = spawnEdge(rand() % 4);
switch (edge){
case up:
pos.x = rand() % windowWidth;
pos.y = -height;
break;
case down:
pos.x = rand() % windowWidth;
pos.y = windowHeight;
break;
case left:
pos.x = -width ;
pos.y = rand() % windowHeight;
break;
case right:
pos.x = windowWidth;
pos.y = rand() % windowHeight;
break;
default:
break;
}
}
Enemy::~Enemy(){
delete animationEnemyLeft;
delete animationEnemyRight;
}
void Enemy::move(const Player& player){
//怪物向玩家移动
int x = player.x() - pos.x;
int y = player.y() - pos.y;
double modulus = sqrt(x * x + y * y); //向量的模
if (modulus) {
double vectorX = x / modulus;
double vectorY = y / modulus;
pos.x += int(enemySpeed * vectorX);
pos.y += int(enemySpeed * vectorY);
}
//修改面部朝向
if (x > 0)
isFacingLeft = false;
else if(x < 0)
isFacingLeft = true;
}
bool Enemy::checkBulletCollision(const Bullet& bullet) const{
//判断子弹 是否在 矩形内
bool isInX = bullet.pos.x >= pos.x && bullet.pos.x <= pos.x + width;
bool isInY = bullet.pos.y >= pos.y && bullet.pos.y <= pos.y + height;
return isInX && isInY;
}
bool Enemy::checkPlayerCollision(const Player& player) const{
//判断中心点 是否在 玩家矩形内
int centerX = pos.x + width / 2;
int centerY = pos.y + height / 2;
bool isInPlayerX = centerX >= player.x() && centerX <= player.x() + player.width;
bool isInPlayerY = centerY >= player.y() && centerY <= player.y() + player.height;
return isInPlayerX && isInPlayerY;
}
void Enemy::draw(int frameInterval){
//绘制阴影
int x = pos.x + (width - shadowWidth) / 2;
int y = pos.y + height - 35;
putImageAlpha(x, y, &enemyShadow);
//等于0时,指向原先的面部朝向
if (isFacingLeft)
animationEnemyLeft->draw(pos.x, pos.y, frameInterval);
else
animationEnemyRight->draw(pos.x, pos.y, frameInterval);
}
void putImageAlpha(int x, int y, IMAGE* img){
int w = img->getwidth();
int h = img->getheight();
/*
AlphaBlend: Windows GDI+ API,用于图像混合。
GetImageHDC(nullptr), x, y, w, h:
GetImageHDC(nullptr):获取屏幕
x, y, w, h: 屏幕的位置,作为目标区域。(左上角坐标为x,y,宽为w,高为h)
GetImageHDC(img), 0, 0, w, h:
GetImageHDC(img):获取图像
0, 0, w, h: 整个图像,作为源区域。
{ AC_SRC_OVER,0,255, AC_SRC_ALPHA }: 将源图像以透明的方式覆盖到目标图像上,透明度由源图像的Alpha通道控制。
AC_SRC_OVER: 源图像覆盖目标图像
0,255: 参数,此处无作用
AC_SRC_ALPHA: 指定源图像的Alpha通道覆盖
图像的Alpha通道: 是图像的透明度通道,存储着每个像素的透明度信息
*/
AlphaBlend(GetImageHDC(nullptr), x, y, w, h, GetImageHDC(img), 0, 0, w, h, { AC_SRC_OVER,0,255, AC_SRC_ALPHA });
}
void generateEnemy(std::vector<Enemy*>& enemys){
static const int interval = 25;
static int timer = 0;
if (timer % interval == 0) {
auto enemy = new Enemy;
enemys.push_back(enemy);
}
timer++;
timer %= interval;
}
void updateBullets(std::vector<Bullet>& bullets, const Player& player){
static const double radialCoefficient = 0.0045; //径系数
static const double tangentCoefficient = 0.009; //切系数
static const double Pi = 3.1415926;
//弧度制
double radianInterval = 2 * Pi / bullets.size(); //计算 子弹间隔
//半径: 随时间和径系数变化
double r = 100 + 25 * sin(GetTickCount() * radialCoefficient);
for (int i = 0; i < bullets.size(); i++) {
//角度: 随时间和切系数变化
double radian = GetTickCount() * tangentCoefficient + radianInterval * i;
bullets[i].pos.x = player.x() + player.width / 2 + int(r * cos(radian));
bullets[i].pos.y = player.y() + player.height / 2 + int(r * sin(radian));
}
}
void drawScore(const int& score){
static TCHAR text[64];
_stprintf_s(text, _T("当前玩家得分: %d"), score);
setbkmode(TRANSPARENT); //将背景设为透明
settextcolor(RGB(255, 85, 185));
outtextxy(10, 10, text);
}
Animation::Animation(Atlas* atlas, int frameInterval): atlas(atlas), frameInterval(frameInterval){
}
void Animation::draw(int x, int y, int playTime) {
if (timer > frameInterval) {
imgIndex = (imgIndex + 1) % atlas->images.size();
timer = 0;
}
//一张图片可以被多次绘制
putImageAlpha(x, y, atlas->images[imgIndex]);
timer += playTime;
}
Atlas::Atlas(LPCTSTR path, int num){
TCHAR tPath[256];
for (int i = 0; i < num; i++) {
_stprintf_s(tPath, path, i);
auto img = new IMAGE;
loadimage(img, tPath);
images.push_back(img);
}
}
Atlas::~Atlas(){
for (auto& img : images)
delete img;
}
Button::Button(const RECT& region, LPCTSTR pathIdle, LPCTSTR pathHovered, LPCTSTR pathPushed) :region(region) {
loadimage(&imgIdle, pathIdle);
loadimage(&imgHovered, pathHovered);
loadimage(&imgPushed, pathPushed);
}
Button::~Button() {}
//移动,左键按下,左键抬起
void Button::processMessage(const ExMessage& msg){
switch (msg.message){
case WM_MOUSEMOVE:
if (state == idle && checkCursorHit(msg.x, msg.y))
state = hovered;
else if (state == hovered && !checkCursorHit(msg.x, msg.y))
state = idle;
break;
case WM_LBUTTONDOWN:
if (state == hovered)
state = pushed;
break;
case WM_LBUTTONUP:
if (state == pushed)
onClick();
break;
default:
break;
}
}
void Button::draw(){
switch (state){
case idle:
putimage(region.left, region.top, &imgIdle);
break;
case hovered:
putimage(region.left, region.top, &imgHovered);
break;
case pushed:
putimage(region.left, region.top, &imgPushed);
break;
default:
break;
}
}
StartButton::~StartButton(){}
void StartButton::onClick(){
isStarted = true;
}
QuitButton::~QuitButton(){}
void QuitButton::onClick(){
running = false;
}