自学内容网 自学内容网

提瓦特幸存者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;
}


原文地址:https://blog.csdn.net/qq_42863961/article/details/143622185

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!