在本系列文章中,我将使用 C 创建经典街机游戏太空入侵者的克隆,仅使用几个依赖项。 在这篇文章中,我将让游戏循环以固定的时间步长运行,添加玩家和外星人,最后添加精灵动画。
添加玩家和外星人群体
在添加玩家和外星人群之前,我们创建了两个数据聚合,即结构体,
struct Alien
{
size_t x, y;
uint8_t type;
};struct Player
{
size_t x, y;
size_t life;
};
玩家和外星人结构都有一个位置 x,y 以像素为单位从窗口的左下角给出。 在 Player 结构中,我们还包括玩家的生命数。 在经典的太空侵略者街机游戏中,只有三种不同的外星人类型。 我们在类型字段中对此进行编码。 我们还为所有游戏相关变量引入了一个结构体,
struct Game
{
size_t width, height;
size_t num_aliens;
Alien* aliens;
Player player;
};
这包括游戏的宽度和高度(以像素为单位)、玩家和外星人作为动态分配的数组。
正如我们之前所做的,我们为播放器添加一个精灵,编码为位图。
Sprite player_sprite;
player_sprite.width = 11;
player_sprite.height = 7;
player_sprite.data = new uint8_t[77]
{
0,0,0,0,0,1,0,0,0,0,0, // .....@.....
0,0,0,0,1,1,1,0,0,0,0, // ....@@@....
0,0,0,0,1,1,1,0,0,0,0, // ....@@@....
0,1,1,1,1,1,1,1,1,1,0, // .@@@@@@@@@.
1,1,1,1,1,1,1,1,1,1,1, // @@@@@@@@@@@
1,1,1,1,1,1,1,1,1,1,1, // @@@@@@@@@@@
1,1,1,1,1,1,1,1,1,1,1, // @@@@@@@@@@@
};
然后我们创建并初始化 Game 结构体,
Game game;
game.width = buffer_width;
game.height = buffer_height;
game.num_aliens = 55;
game.aliens = new Alien[game.num_aliens];game.player.x = 112 - 5;
game.player.y = 32;game.player.life = 3;
我们将外星人的数量设置为 55,就像在最初的街机游戏中一样,给玩家 3 条生命,并将他的位置设置在屏幕底部中心附近。 然后我们继续将外星人的位置初始化为合理的,
for(size_t yi = 0; yi < 5; yi)
{
for(size_t xi = 0; xi < 11; xi)
{
game.aliens[yi * 11 xi].x = 16 * xi 20;
game.aliens[yi * 11 xi].y = 17 * yi 128;
}
}
最后,在主循环中,我们绘制玩家和所有外星人,
for(size_t ai = 0; ai < game.num_aliens; ai)
{
const Alien& alien = game.aliens[ai];
buffer_draw_sprite(&buffer, alien_sprite,
alien.x, alien.y, rgb_to_uint32(128, 0, 0));
}buffer_draw_sprite(&buffer, player_sprite, game.player.x, game.player.y, rgb_to_uint32(128, 0, 0));
这是结果,
这已经开始看起来像太空入侵者了,但它是静态的!
精灵动画
为了让游戏更加动态,我们当然需要实现玩家输入,但我们还需要一些方法来为精灵设置动画。 视频游戏中精灵的动画是通过将精灵替换为一系列连续的精灵来实现的。 因此,我们创建了一个数据结构来保存有关精灵动画的各种信息。
struct SpriteAnimation
{
bool loop;
size_t num_frames;
size_t frame_duration;
size_t time;
Sprite** frames;
};
SpriteAnimation 结构体基本上是一个 Sprite 数组。 在这里,我们使用指针到指针类型来存储精灵,以便可以共享精灵。 如果想提高效率,我们可以将精灵打包成精灵表。 此外,我们还包含一个标志来告诉我们是否应该循环播放动画或只播放一次、连续帧之间的时间以及在当前动画实例中花费的时间。 给定动画的帧数和所需的持续时间,可以轻松计算连续帧之间的时间。 下面,我们为我们的外星人介绍一个额外的精灵,
Sprite alien_sprite1;
alien_sprite1.width = 11;
alien_sprite1.height = 8;
alien_sprite1.data = new uint8_t[88]
{
0,0,1,0,0,0,0,0,1,0,0, // ..@.....@..
1,0,0,1,0,0,0,1,0,0,1, // @..@...@..@
1,0,1,1,1,1,1,1,1,0,1, // @.@@@@@@@.@
1,1,1,0,1,1,1,0,1,1,1, // @@@.@@@.@@@
1,1,1,1,1,1,1,1,1,1,1, // @@@@@@@@@@@
0,1,1,1,1,1,1,1,1,1,0, // .@@@@@@@@@.
0,0,1,0,0,0,0,0,1,0,0, // ..@.....@..
0,1,0,0,0,0,0,0,0,1,0 // .@.......@.
};
并使用两个外星精灵创建一个两帧动画,
SpriteAnimation* alien_animation = new SpriteAnimation;alien_animation->loop = true;
alien_animation->num_frames = 2;
alien_animation->frame_duration = 10;
alien_animation->time = 0;alien_animation->frames = new Sprite*[2];
alien_animation->frames[0] = &alien_sprite0;
alien_animation->frames[1] = &alien_sprite1;
请注意,为了使事情更容易,我们测量游戏周期中的帧持续时间和时间,即循环迭代次数。 为了完成这项工作,我们需要固定帧速率。 对于像我们正在构建的游戏这样的简单游戏,我们可以使用垂直同步,这是一种视频卡更新与显示器刷新率同步的选项。 大多数现代显示器的刷新率为 60Hz,这意味着显示器每秒刷新 60 次。 开启 V-sync 将使我们的游戏帧率为 60,或屏幕刷新的整数倍。 不幸的是,这意味着游戏将在刷新率较高的显示器上运行得更快,例如 120Hz 或 240Hz 显示器。 要打开垂直同步,我们调用 GLFW 函数 glfwSwapInterval
glfwSwapInterval(1)
在每一帧结束时,我们通过推进时间来更新所有动画。 如果动画已经结束,我们要么删除它,要么将它的时间设置回 0,如果它是一个循环动画。
alien_animation->time;
if(alien_animation->time == alien_animation->num_frames * alien_animation->frame_duration)
{
if(alien_animation->loop) alien_animation->time = 0;
else
{
delete alien_animation;
alien_animation = nullptr;
}
}
请注意,我们目前只有一个动画需要更新。
在绘制外星人的主循环中,我们更新绘制循环以绘制适当的动画帧。 根据在动画中花费的时间和帧时长计算出合适的帧,
for(size_t ai = 0; ai < game.num_aliens; ai)
{
const Alien& alien = game.aliens[ai];
size_t current_frame = alien_animation->time / alien_animation->frame_duration;
const Sprite& sprite = *alien_animation->frames[current_frame];
buffer_draw_sprite(&buffer, sprite, alien.x, alien.y, rgb_to_uint32(128, 0, 0));
}
为了让事情更有趣,我们还添加了一些任意的玩家运动,通过引入一个控制玩家运动方向的变量,
int player_move_dir = 1;
并根据它在每一帧结束时更新玩家移动,
if(game.player.x player_sprite.width player_move_dir >= game.width - 1)
{
game.player.x = game.width - player_sprite.width - player_move_dir - 1;
player_move_dir *= -1;
}
else if((int)game.player.x player_move_dir <= 0)
{
game.player.x = 0;
player_move_dir *= -1;
}
else game.player.x = player_move_dir;
if 条件执行玩家精灵与游戏边界的基本碰撞检测,确保玩家停留在这些边界内。
在上面你可以看到结果的动画 gif。
结论
在这篇文章中,我们设置了一些结构来对游戏、玩家和外星人的数据进行逻辑分组。 更重要的是,我们为精灵动画奠定了基础。 对于这个简单的太空入侵者游戏,我们假设通过打开 V-sync 设置一个固定时钟。 这允许我们在游戏周期中而不是实时地制作精灵动画。
在使游戏可玩之前唯一剩下的就是对用户输入的处理。 用户输入可以来自各种来源,但我们将自己限制在键盘上。
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved