跳转至

02-敌人随机生成和索敌逻辑

1 前置知识

1.1 STL(Standard Template Library)

Pasted image 20260218162025.png
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);


Pasted image 20260219204658.png

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,就可以直接在这里评论。