02-敌人随机生成和索敌逻辑
1 前置知识
1.1 STL(Standard Template Library)

vector是一个能够根据元素数量动态增长的数组(动态数组)。
#include<vector>
std::vector<IMAGE *> frame_list;
2 动画Animation类实现
为了实现不同动画的加载和播放,我们利用Animation类来封装动画相关的数据和逻辑
2.1 动画载入
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);
frame_list.push_back(frame);
}
}
~Animation() {
for (size_t i = 0; i < frame_list.size(); i++) {
delete frame_list[i];
}
}
private:
int interval_ms = 0;
std::vector<IMAGE *> frame_list;
}
首先在类初始化的时候就进行图片序列的载入。
_stprintf_s 是微软 CRT(C 运行时库)中的安全格式化字符串函数,属于 sprintf 系列的 “安全版本”,且是宽字符 / 多字节通用版本(TCHAR 适配)。
2.2 动画播放
定义一个Animation类的成员方法Play()
void Play(int x, int y, int delta) {
timer += delta;
if (timer >= interval_ms) {
idx_frame = (idx_frame + 1) % frame_list.size();
timer = 0;
}
putimage_alpha(x, y, frame_list[idx_frame]);
}
这里利用计时器来实现动画播放。每次事件累加超出帧间隔时切换到下一帧。
2.3 调用类实现左右移动动画
首先定义角色向左和向右两个Animation类,类初始化时就会自动载入相关图片资源。
//6张序列帧,帧间隔为45毫秒
Animation anim_left_player(_T("..\\img\\player_left_%d.png"), 6, 45);
Animation anim_right_player(_T("..\\img\\player_right_%d.png"), 6, 45);
之后编写用于绘制Player的函数
DrawPlayer()void DrawPlayer(int delta, int dir_x)
{
static bool is_facing_left= false;
if(dir_x < 0) {
is_facing_left = true;
} else if(dir_x > 0) {
is_facing_left = false;
}
if (is_facing_left) {
anim_left_player.Play(player_pos.x, player_pos.y, delta);
} else {
anim_right_player.Play(player_pos.x, player_pos.y, delta);
}
}
函数中用bool变量is_facing_left来
在帧循环中的FlushBatchDraw();之前调用
//这里固定帧率为144Hz
DrawPlayer(1000/144, is_move_right-is_move_left);
2.4 角色阴影显示
定义全局常数变量
const int PLAYER_WIDTH = 80;
const int PLAYER_HEIGHT = 80;
const int SHADOW_WIDTH = 32;
const int SHADOW_HEIGHT = 20;
定义全局变量,用于存储阴影图片
IMAGE img_shadow;
在main函数中导入阴影的图片素材
loadimage(&img_shadow, _T("..\\img\\shadow_player.png"));
在
DrawPlayer()函数中,绘制player之前先绘制player的阴影。int pos_shadow_x = player_pos.x + PLAYER_WIDTH / 2 - SHADOW_WIDTH / 2;
int pos_shadow_y = player_pos.y + PLAYER_HEIGHT - 8;
putimage_alpha(pos_shadow_x, pos_shadow_y, &img_shadow);

2.5 角色速度归一化
在DrawPlayer()之前,我们把速度的大小进行归一化
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);
}
2.6 限制角色移动范围
定义窗口大小
const int WINDOW_WIDTH = 1280;
const int WINDOW_HEIGHT = 720;
在更新玩player位置坐标之后,将其限制在屏幕范围内
if(player_pos.x < 0)
player_pos.x = 0;
if(player_pos.y < 0 )
player_pos.y = 0;
if(player_pos.x + PLAYER_WIDTH > WINDOW_WIDTH)
player_pos.x = WINDOW_WIDTH - PLAYER_WIDTH;
if(player_pos.y + PLAYER_HEIGHT > WINDOW_HEIGHT)
player_pos.y = WINDOW_HEIGHT - PLAYER_HEIGHT;
3 Player类实现
class Player
{
public:
Player()
{
//使用loadimage加载图片
//使用new语法初始化向左向右的动画对象
}
~Player()
{
//使用delete语法删除动画对象
}
void ProcessEvent(const ExMessage& msg)
{
//根据msg.message和msg.vkcode,使用两个嵌套的switch语句来更新运动状态
}
void Move()
{
//根据运动状态更新player的位置
}
void Draw(int delta)
{
//根据运动状态和player的位置进行绘制
}
private:
//动画指针、阴影图片、角色位置、运动状态等成员变量
//还有一些角色图片尺寸等常数
};
将之前写的代码进行修改填充到这个Player类中,之后帧循环中只使用下述代码即可:
while (peekmessage(&msg)) {
player.ProcessEvent(msg);
}
player.Move();
//这里固定帧率为144Hz
player.Draw(1000 / 144);
4 Bullet类实现
class Bullet
{
public:
POINT position = {0, 0};
public:
Bullet() = default;
~Bullet() = default;
void Draw() const
{
setlinecolor(RGB(255, 155, 50));
setfillcolor(RGB(200, 75, 10));
fillcircle(position.x, position.y, RADIUS);
}
private:
const int RADIUS = 10;
}
5 Enemy类实现
class Enemy
{
public:
Enemy()
{
loadimage(&img_shadow, _T("..\\img\\shadow_enemy.png"));
//6张序列帧,帧间隔为45毫秒
anim_left = new Animation(_T("..\\img\\enemy_left_%d.png"), 6, 45);
anim_right = new Animation(_T("..\\img\\enemy_right_%d.png"), 6, 45);
}
~Enemy()
{
delete anim_left;
delete anim_right;
}
private:
const int SPEED = 2;
const int FRAME_WIDTH = 80;
const int FRAME_HEIGHT = 80;
const int SHADOW_WIDTH = 48;
private:
IMAGE img_shadow;
Animation *anim_left;
Animation *anim_right;
POINT position = {0, 0};
bool facing_left = false;
};
在Enemy对象初始化时,要随机给他设置一个在屏幕之外的位置
//敌人生成边界
enum class SpawnEdge
{
UP = 0,
Down,
Left,
Right
};
SpawnEdge edge = (SpawnEdge)(rand() % 4);
switch(edge)
{
case SpawnEdge::UP:
position.x = rand() % WINDOW_WIDTH;
position.y = -FRAME_HEIGHT;
break;
case SpawnEdge::Down:
position.x = rand() % WINDOW_WIDTH;
position.y = WINDOW_HEIGHT;
break;
case SpawnEdge::Left:
position.x = -FRAME_WIDTH;
position.y = rand() % WINDOW_HEIGHT;
break;
case SpawnEdge::Right:
position.x = WINDOW_WIDTH;
position.y = rand() % WINDOW_HEIGHT;
break;
default:
break;
}
之后定义用于检测碰撞的函数
bool CheckBulletCollision(const Bullet& bullet)
{
return false;
}
bool CheckPlayerCollision(const Player& player)
{
return false;
}
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;
if(dir_x>0)
facing_left = false;
}
void Draw(int delta)
{
int pos_shadow_x = position.x + FRAME_WIDTH / 2 - SHADOW_WIDTH / 2;
int pos_shadow_y = position.y + FRAME_HEIGHT - 30;
putimage_alpha(pos_shadow_x, pos_shadow_y, &img_shadow);
if (facing_left) {
anim_left->Play(position.x, position.y, delta);
} else {
anim_right->Play(position.x, position.y, delta);
}
}
由于敌人数量是动态的,所以我们使用vector容器来存储Enemy的对象指针
std::vector<Enemy *> enemy_list;
编写函数
TryGenerateEnemy用于产生新的敌人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());
}
}
在帧循环中调用这个函数,并遍历每个Enemy对象,让它们向着玩家移动
TryGenerateEnemy(enemy_list);
for(Enemy* enemy : enemy_list) {
enemy->Move(player);
}
绘制时,也要遍历所有Enemy对象,分别调用它们的Draw方法
for(Enemy* enemy : enemy_list) {
enemy->Draw(1000 / 144);
}
评论
如果你已登录 GitHub,就可以直接在这里评论。