Player类
class Player
{
public:
Player()
{
loadimage(&ImgShadowP, _T("img/shadow_player.png"));
anim_left=new Animation (_T("img/player_left_%d.png"), 6, 45);
anim_right=new Animation (_T("img/player_right_%d.png"), 6, 45);
}
~Player()
{
delete anim_left;
delete anim_right;
}
void Input(const ExMessage& msg)
{
}
void Move()
{
}
void Draw(int delta)
{
}
private:
const int PlayerWidth = 80;
const int PlayerHeight = 80;
const int ShadowWidth = 32;
IMAGE ImgShadowP;
Animation* anim_left;
Animation* anim_right;
POINT player_pos = { 500,500 };
const int PLAYER_SPEED = 10;
bool is_move_up = 0;
bool is_move_down = 0;
bool is_move_left = 0;
bool is_move_right = 0;
};
我们建立Player类,将散落在外的变量放入私有变量。
在析构函数中对游戏背景及阴影初始化路径,并在析构函数中删除。
Input函数
存放输入按键,将这一部分复制过来
void Input(const ExMessage& msg)
{
if (msg.message == WM_KEYDOWN)
{
switch (msg.vkcode)
{
case VK_UP:
is_move_up = true;
break;
case VK_DOWN:
is_move_down = true;
break;
case VK_LEFT:
is_move_left = true;
break;
case VK_RIGHT:
is_move_right = true;
break;
}
}
else if (msg.message == WM_KEYUP)
{
switch (msg.vkcode)
{
case VK_UP:
is_move_up = false;
break;
case VK_DOWN:
is_move_down = false;
break;
case VK_LEFT:
is_move_left = false;
break;
case VK_RIGHT:
is_move_right = false;
break;
}
}
}
Move函数
将移动方式复制过来
void Move()
{
int dir_x = is_move_right - is_move_left;
int dir_y = is_move_down - is_move_up;
double len_dir = sqrt((dir_x * dir_x) + (dir_y * dir_y));
if (len_dir != 0)
{
double normalized_x = dir_x / len_dir;
double normalized_y = dir_y / len_dir;
player_pos.x += (int)(PLAYER_SPEED*normalized_x);
player_pos.y += (int)(PLAYER_SPEED*normalized_y);
}
if (player_pos.x < 0) player_pos.x = 0;
if (player_pos.y < 0) player_pos.y = 0;
if (player_pos.x + PlayerWidth > WindowWidth)player_pos.x = WindowWidth - PlayerWidth;
if (player_pos.y + PlayerHeight > WindowHeight)player_pos.y = WindowHeight - PlayerHeight;
}
其中的窗口范围边界依旧属于全局变量、派蒙和敌人都要引用所以不放入
#include <graphics.h>
#include<vector>
const int WindowWidth = 1280;
const int WindowHeight = 720;
Draw函数
将DrawPlayer内部函数复制过来,并直接将dir_x的表达式写进去。
void Draw(int delta)
{
int PosShadowX = player_pos.x + (PlayerWidth / 2 - ShadowWidth / 2);
int PosShadowY = player_pos.y + PlayerHeight - 8;
putimageA(PosShadowX, PosShadowY, &ImgShadowP);
static bool facing_left = false;
int dir_x = is_move_right - is_move_left;
if (dir_x < 0)facing_left = true;
else if (dir_x > 0)facing_left = false;
if (facing_left)
{
anim_left->Play(player_pos.x, player_pos.y, delta);
}
else
{
anim_right->Play(player_pos.x, player_pos.y, delta);
}
}
Enemy类
class Enemy
{
public:
Enemy()
{
loadimage(&ImgShadow, _T("img/shadow_enemy.png"));
anim_left = new Animation(_T("img/enemy_left_%d.png"), 6, 45);
anim_right = new Animation(_T("img/enemy_right_%d.png"), 6, 45);
}
void Hurt()
{
alive = false;
}
bool checkAlive()
{
return alive;
}
bool CheckBullet(const Bullet& bullet)
{
}
bool CheckPlayer(const Player& player)
{
}
void Move(const Player&player)
{
const POINT player_position = player.GetPosition();
int dir_x = player_position.x - position.x;
int dir_y = player_position.y - position.y;
double len_dir = sqrt((dir_x * dir_x) + (dir_y * dir_y));
if (len_dir != 0)
{
double normalized_x = dir_x / len_dir;
double normalized_y = dir_y / len_dir;
position.x += (int)(SPEED*normalized_x);
position.y += (int)(SPEED*normalized_y);
}
if (dir_x<0)
{
facing_left = true;
}
else if (dir_x > 0)
{
facing_left = false;
}
}
void Draw(int delta)
{
int PosShadowX = position.x + (EnemyWidth / 2 - ShadowWidth / 2);
int PosShadowY = position.y + EnemyHeight -35;
putimageA(PosShadowX, PosShadowY, &ImgShadow);
if (facing_left)
{
anim_left->Play(position.x, position.y, delta);
}
else
{
anim_right->Play(position.x, position.y, delta);
}
}
~Enemy()
{
delete anim_left;
delete anim_right;
}
private:
const int EnemyWidth = 80;
const int EnemyHeight = 80;
const int ShadowWidth = 48;
class Bullet;
IMAGE ImgShadow;
Animation* anim_left;
Animation* anim_right;
POINT position = { 0,0 };
const int SPEED = 2;
bool facing_left = false;
bool alive = true;
};
敌人与派蒙的运动方式类似,区别在于派蒙有默认点并且随着玩家的Input而改变坐标。
敌人则是初始位置随机、向着派蒙坐标移动、碰触派蒙游戏结束、被子弹碰到游戏结束。
构造函数析构函数
与Player类类似,只不过需要从边界外生成敌人,所以我们在创建一个随机边界生成敌人代码。
Enemy()
{
loadimage(&ImgShadow, _T("img/shadow_enemy.png"));
anim_left = new Animation(_T("img/enemy_left_%d.png"), 6, 45);
anim_right = new Animation(_T("img/enemy_right_%d.png"), 6, 45);
enum class SpawnEdge
{
Up, Down, Left, Right
};
SpawnEdge edge = (SpawnEdge)(rand() % 4);
switch (edge)
{
case SpawnEdge::Up:
position.x = rand() % WindowWidth;
position.y = -EnemyHeight;
break;
case SpawnEdge::Down:
position.x = rand() % WindowWidth;
position.y = WindowHeight;
break;
case SpawnEdge::Left:
position.x = -EnemyWidth;
position.y = rand() % WindowHeight;
break;
case SpawnEdge::Right:
position.x = WindowWidth;
position.y = rand() % WindowHeight;
break;
default:
break;
}
}
Move函数
派蒙的Move函数接收来自Input函数的数据,而Enemy需要接收Player的实时坐标,所以我们在Player类中创建获取坐标函数,其余与Player类似。
const POINT& GetPosition()const
{
return player_pos;
}
Draw函数
与Player一样,改一下Enemy阴影的相对位置即可。
Hurt函数
敌人的受伤机制,在这里可以写受子弹撞击一下掉一半血,也可一直致命或者任何掉血方式。
CheckBullet与CheckPlayer函数
敌人与子弹、敌人与玩家碰撞检测。bool变量,碰撞为true、未碰撞为false。
先写与玩家碰撞
知道玩家坐标与敌人坐标,检测敌人中心位置坐标是否落入玩家80x80像素内即可。
bool CheckPlayer(const Player& player)
{
POINT check_position = { position.x + EnemyWidth / 2,position.y + EnemyHeight/2 };
bool is_overlap_x = check_position.x >= player.GetPosition().x&&check_position.x <= player.GetPosition().x + EnemyWidth;
bool is_overlap_y = check_position.y >= player.GetPosition().y&&check_position.y <= player.GetPosition().y + EnemyHeight;
return is_overlap_x && is_overlap_y;
}
不去做敌人的正方形与派蒙的正方型碰撞检测是为什么呢?
我们可以正方形与正方形碰撞检测,但是两个方形都大于模型本身,容易出现碰撞箱碰触到了,但肉眼观察属于未碰触,与直觉不符。所以我们采取点对面的检测,当然你还可以将派蒙与敌人的碰撞箱缩小到模型体内。随便你。
Bullet类
class Bullet
{
public:
POINT position = { 0,0 };
public:
Bullet() {};
~Bullet() {};
void Draw()const
{
setlinecolor(RGB(255, 155, 50));
setlinecolor(RGB(255, 155, 50));
fillcircle(position.x, position.y, RADIUS);
}
private:
const int RADIUS = 10;
};
构造函数析构函数不重要。
我们需要子弹的坐标position,芸运用函数绘制半径为10的白色子弹。
对于两个变量,显然我们不希望改变子弹的大小只希望变换子弹的位置。所以将两个变量一个为私有一个为公有。
构造完毕Buttlet类我们编写CheckBullet函数
bool CheckBullet(const Bullet& bullet)
{
bool is_overlap_x = bullet.position.x >= position.x&&bullet.position.x <= position.x + EnemyWidth;
bool is_overlap_y = bullet.position.y >= position.y&&bullet.position.y <= position.y + EnemyHeight;
return is_overlap_x&& is_overlap_y;
}
记得将Buttlet类放置在Enemy类以上否则无法引用相关函数。(编译器从上向下扫描代码)
#include <graphics.h>
#include<vector>
const int WindowWidth = 1280;
const int WindowHeight = 720;
#pragma comment(lib,"MSIMG32.LIB")
inline void putimageA(int x, int y, IMAGE* img)
{
int w = img->getwidth();
int h = img->getheight();
AlphaBlend(GetImageHDC(NULL), x, y, w, h,
GetImageHDC(img), 0, 0, w, h, { AC_SRC_OVER,0,255,AC_SRC_ALPHA });
}
class Animation
{
public:
Animation(LPCTSTR path,int num,int interval)
{
interval_ms = interval;
TCHAR path_file[256];
for (size_t i = 0; i < num; i++)
{
_stprintf_s(path_file, path, i);
IMAGE* frame = new IMAGE();
loadimage(frame, path_file);
FrameList.push_back(frame);
}
}
~Animation()
{
for (size_t i = 0; i < FrameList.size(); i++)
{
delete FrameList[i];
}
}
void Play(int x, int y, int delta)
{
timer += delta;
if (timer >= interval_ms)
{
idx_frame = (idx_frame + 1) % FrameList.size();
timer = 0;
}
putimageA(x, y, FrameList[idx_frame]);
}
private:
int timer = 0;
int idx_frame = 0;
int interval_ms = 0;
std::vector<IMAGE*> FrameList;//动画帧序列
};
class Bullet
{
public:
POINT position = { 0,0 };
public:
Bullet() {};
~Bullet() {};
void Draw()const
{
setlinecolor(RGB(255, 155, 50));
setlinecolor(RGB(255, 155, 50));
fillcircle(position.x, position.y, RADIUS);
}
private:
const int RADIUS = 10;
};
class Player
{
public:
Player()
{
loadimage(&ImgShadowP, _T("img/shadow_player.png"));
anim_left=new Animation (_T("img/player_left_%d.png"), 6, 45);
anim_right=new Animation (_T("img/player_right_%d.png"), 6, 45);
}
~Player()
{
delete anim_left;
delete anim_right;
}
void Input(const ExMessage& msg)
{
if (msg.message == WM_KEYDOWN)
{
switch (msg.vkcode)
{
case VK_UP:
is_move_up = true;
break;
case VK_DOWN:
is_move_down = true;
break;
case VK_LEFT:
is_move_left = true;
break;
case VK_RIGHT:
is_move_right = true;
break;
}
}
else if (msg.message == WM_KEYUP)
{
switch (msg.vkcode)
{
case VK_UP:
is_move_up = false;
break;
case VK_DOWN:
is_move_down = false;
break;
case VK_LEFT:
is_move_left = false;
break;
case VK_RIGHT:
is_move_right = false;
break;
}
}
}
void Move()
{
int dir_x = is_move_right - is_move_left;
int dir_y = is_move_down - is_move_up;
double len_dir = sqrt((dir_x * dir_x) + (dir_y * dir_y));
if (len_dir != 0)
{
double normalized_x = dir_x / len_dir;
double normalized_y = dir_y / len_dir;
player_pos.x += (int)(PLAYER_SPEED*normalized_x);
player_pos.y += (int)(PLAYER_SPEED*normalized_y);
}
if (player_pos.x < 0) player_pos.x = 0;
if (player_pos.y < 0) player_pos.y = 0;
if (player_pos.x + PlayerWidth > WindowWidth)player_pos.x = WindowWidth - PlayerWidth;
if (player_pos.y + PlayerHeight > WindowHeight)player_pos.y = WindowHeight - PlayerHeight;
}
void Draw(int delta)
{
int PosShadowX = player_pos.x + (PlayerWidth / 2 - ShadowWidth / 2);
int PosShadowY = player_pos.y + PlayerHeight - 8;
putimageA(PosShadowX, PosShadowY, &ImgShadowP);
static bool facing_left = false;
int dir_x = is_move_right - is_move_left;
if (dir_x < 0)facing_left = true;
else if (dir_x > 0)facing_left = false;
if (facing_left)
{
anim_left->Play(player_pos.x, player_pos.y, delta);
}
else
{
anim_right->Play(player_pos.x, player_pos.y, delta);
}
}
const POINT& GetPosition()const
{
return player_pos;
}
public:
const int EnemyWidth = 80;
const int EnemyHeight = 80;
private:
const int PlayerWidth = 80;
const int PlayerHeight = 80;
const int ShadowWidth = 32;
IMAGE ImgShadowP;
Animation* anim_left;
Animation* anim_right;
POINT player_pos = { 500,500 };
const int PLAYER_SPEED = 10;
bool is_move_up = 0;
bool is_move_down = 0;
bool is_move_left = 0;
bool is_move_right = 0;
};
class Enemy
{
public:
Enemy()
{
loadimage(&ImgShadow, _T("img/shadow_enemy.png"));
anim_left = new Animation(_T("img/enemy_left_%d.png"), 6, 45);
anim_right = new Animation(_T("img/enemy_right_%d.png"), 6, 45);
enum class SpawnEdge
{
Up, Down, Left, Right
};
SpawnEdge edge = (SpawnEdge)(rand() % 4);
switch (edge)
{
case SpawnEdge::Up:
position.x = rand() % WindowWidth;
position.y = -EnemyHeight;
break;
case SpawnEdge::Down:
position.x = rand() % WindowWidth;
position.y = WindowHeight;
break;
case SpawnEdge::Left:
position.x = -EnemyWidth;
position.y = rand() % WindowHeight;
break;
case SpawnEdge::Right:
position.x = WindowWidth;
position.y = rand() % WindowHeight;
break;
default:
break;
}
}
void Hurt()
{
alive = false;
}
bool checkAlive()
{
return alive;
}
bool CheckBullet(const Bullet& bullet)
{
bool is_overlap_x = bullet.position.x >= position.x&&bullet.position.x <= position.x + EnemyWidth;
bool is_overlap_y = bullet.position.y >= position.y&&bullet.position.y <= position.y + EnemyHeight;
return is_overlap_x&& is_overlap_y;
}
bool CheckPlayer(const Player& player)
{
POINT check_position = { position.x + EnemyWidth / 2,position.y + EnemyHeight/2 };
bool is_overlap_x = check_position.x >= player.GetPosition().x&&check_position.x <= player.GetPosition().x + EnemyWidth;
bool is_overlap_y = check_position.y >= player.GetPosition().y&&check_position.y <= player.GetPosition().y + EnemyHeight;
return is_overlap_x && is_overlap_y;
}
void Move(const Player&player)
{
const POINT player_position = player.GetPosition();
int dir_x = player_position.x - position.x;
int dir_y = player_position.y - position.y;
double len_dir = sqrt((dir_x * dir_x) + (dir_y * dir_y));
if (len_dir != 0)
{
double normalized_x = dir_x / len_dir;
double normalized_y = dir_y / len_dir;
position.x += (int)(SPEED*normalized_x);
position.y += (int)(SPEED*normalized_y);
}
if (dir_x<0)
{
facing_left = true;
}
else if (dir_x > 0)
{
facing_left = false;
}
}
void Draw(int delta)
{
int PosShadowX = position.x + (EnemyWidth / 2 - ShadowWidth / 2);
int PosShadowY = position.y + EnemyHeight -35;
putimageA(PosShadowX, PosShadowY, &ImgShadow);
if (facing_left)
{
anim_left->Play(position.x, position.y, delta);
}
else
{
anim_right->Play(position.x, position.y, delta);
}
}
~Enemy()
{
delete anim_left;
delete anim_right;
}
private:
const int EnemyWidth = 80;
const int EnemyHeight = 80;
const int ShadowWidth = 48;
class Bullet;
IMAGE ImgShadow;
Animation* anim_left;
Animation* anim_right;
POINT position = { 0,0 };
const int SPEED = 2;
bool facing_left = false;
bool alive = true;
};
至此三个对象建立完毕,我们来处理main函数。
main
int main()
{
initgraph(1280, 720);
bool GameOver = false;
Player player;
ExMessage msg;
IMAGE ImgBackground;
loadimage(&ImgBackground, _T("img/background.png"));
BeginBatchDraw();
while (!GameOver)
{
DWORD StartTime = GetTickCount();
while (peekmessage(&msg))
{
player.Input(msg);
}
player.Move();
cleardevice();
putimage(0, 0, &ImgBackground);
player.Draw(1000/144);
FlushBatchDraw();
DWORD EndTime = GetTickCount();
DWORD DeleteTime = EndTime - StartTime;
if (DeleteTime <1000/144)
{
Sleep(1000 / 144 - DeleteTime);
}
}
EndBatchDraw();
}
在main函数中一些初始化被封装到类以内,事件循环中的一大段函数替换为Input函数、Move函数与Draw函数分别进行替换。
TryGenerateEnemy
我们来编写敌人出现函数放置到main外。
std::vector<Enemy*>enemy_list;
void TryGenerateEnemy(std::vector<Enemy*>& enemy_list)
{
const int INTERVAL = 100;
static int counter = 0;
if ((++counter) % INTERVAL == 0)
{
enemy_list.push_back(new Enemy());
}
}
屏幕依次出现敌人123456。。。且每个敌人都是指针,数据长度需要时实时变化,那我们就想到动态数组。
创建一个指针数组类型的动态数组,利用计数器每100向容器内推入一个数据。
UpdataBullets
void UpdataBullets(std::vector<Bullet>&bullet_list, const Player& player)
{
const double RadialSpeed = 0.0045;
const double TangentSpeed = 0.0045;
double radian_interval = 2 * 3.14159 / bullet_list.size();
POINT PlayerPosition = player.GetPosition();
double radius = 100 + 25 * sin(GetTickCount()*RadialSpeed);
for (size_t i = 0; i < bullet_list.size(); i++)
{
double radian = GetTickCount()*TangentSpeed + radian_interval * i;
bullet_list[i].position.x = PlayerPosition.x + player.EnemyWidth / 2 + (int)(radius*sin(radian));
bullet_list[i].position.y = PlayerPosition.y + player.EnemyHeight / 2 + (int)(radius*cos(radian));
}
}
子弹围绕着派蒙圆周运动,并附带一定的波动速度,让子弹效果更灵动。
此时main函数
int main()
{
initgraph(1280, 720);
bool GameOver = false;
Player player;
ExMessage msg;
IMAGE ImgBackground;
std::vector<Bullet>bullet_list(3);
loadimage(&ImgBackground, _T("img/background.png"));
BeginBatchDraw();
while (!GameOver)
{
DWORD StartTime = GetTickCount();
while (peekmessage(&msg))
{
player.Input(msg);
}
player.Move();
UpdataBullets(bullet_list, player);
TryGenerateEnemy(enemy_list);
for (Enemy*enemy : enemy_list)
enemy->Move(player);
for (Enemy* enemy:enemy_list)
{
if (enemy->CheckPlayer(player))
{
MessageBox(GetHWnd(), _T("666"), _T("游戏结束"), MB_OK);
GameOver = true;
break;
}
}
for (Enemy* enemy : enemy_list)
{
for (const Bullet&bullet : bullet_list)
{
if(enemy->CheckBullet(bullet))
{
enemy->Hurt();
}
}
}
for (size_t i = 0; i < enemy_list.size(); i++)
{
Enemy* enemy = enemy_list[i];
if (!enemy->checkAlive())
{
std::swap(enemy_list[i], enemy_list.back());
enemy_list.pop_back();
delete enemy;
}
}
cleardevice();
putimage(0, 0, &ImgBackground);
player.Draw(1000/144);
for (Enemy*enemy : enemy_list)
enemy->Draw(1000/144);
for (const Bullet& bullet : bullet_list)
bullet.Draw();
FlushBatchDraw();
DWORD EndTime = GetTickCount();
DWORD DeleteTime = EndTime - StartTime;
if (DeleteTime <1000/144)
{
Sleep(1000 / 144 - DeleteTime);
}
}
EndBatchDraw();
}
std::vector<Bullet>bullet_list(3);创建三个子弹
第一个for敌人向派蒙移动
第二个for检测是否碰撞玩家
第三个for检测碰撞子弹执行受伤函数
第四个for检测到碰触子弹标志位对敌人进行删除
由于敌人出现随机,被玩家击杀随机,可能第五个出现被第一个击杀,所以我们使用swap函数。
每次Hurt的敌人其与栈尾交换并推出。
完结撒花
至此所有的代码已经实现,有更好的想法可以在评论区交流,初次学习还是有很多bug。
一杯茶,一包瓜子,一个bug改一天。。。。。。。。