在本系列文章中,我将使用 C 创建经典街机游戏太空入侵者的克隆,仅使用几个依赖项。 在这篇文章中,我将添加对来自键盘的玩家输入和射弹发射的处理。
为 GLFW 实现键回调
GLFW 使用回调来传递重要事件,就像我们实现的用于捕获错误的回调一样。 我们需要实现一个适当的回调函数来捕获输入事件也就不足为奇了。 回调应具有以下签名,
typedef void(*GLFWkeyfun)(GLFWwindow*, int, int, int, int)
它是通过调用 GLFW 函数 glfwSetKeycallback 来设置的。
让我们首先实现一个简单的键回调来捕捉 Esc 键的按下。 当我们检测到该键被按下时,我们将退出游戏。 这样我们也可以快速测试我们的回调。 为此,我们添加一个全局变量,
bool game_running = false;
我们在主循环之前设置为true。 此外,在主循环中,我们检查变量是否仍然为真,
while (!glfwWindowShouldClose(window) && game_running)
如果不是,则退出循环并终止游戏。 最后,我们实现按键回调,
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods){
switch(key){
case GLFW_KEY_ESCAPE:
if(action == GLFW_PRESS) game_running = false;
break;
default:
break;
}
}
这里的 mods 表示是否按下了任何键修饰符,例如 Shift、Ctrl 等。scancode 是该键的系统特定代码,我们不使用它。 最后,在初始化窗口时,我们设置 GLFW 回调,
glfwSetKeyCallback(window, key_callback);
如果一切都正确完成,您应该可以按 Esc 退出游戏。
添加玩家移动
为了让事情变得更有趣,我们将使用键盘上的左右箭头键添加玩家移动。 我们首先添加一个全局变量来指示运动的方向,
int move_dir = 0;
为此,我们为右箭头指定值 1,为左箭头指定值 -1。 如果其中一个键被按下,它的值被添加到 move_dir,而如果它被释放,它被减去。 如果例如 两个键都被按下,move_dir = 0。我们通过在键回调中添加两个额外的开关案例来实现这个逻辑,
case GLFW_KEY_RIGHT:
if(action == GLFW_PRESS) move_dir = 1;
else if(action == GLFW_RELEASE) move_dir -= 1;
break;
case GLFW_KEY_LEFT:
if(action == GLFW_PRESS) move_dir -= 1;
else if(action == GLFW_RELEASE) move_dir = 1;
break;
在主循环中,绘制完游戏后,我们添加了一些根据按键输入更新玩家位置的逻辑,
int player_move_dir = 2 * move_dir;if(player_move_dir != 0)
{
if(game.player.x player_sprite.width player_move_dir >= game.width)
{
game.player.x = game.width - player_sprite.width;
}
else if((int)game.player.x player_move_dir <= 0)
{
game.player.x = 0;
}
else game.player.x = player_move_dir;
}
在内部 if 语句中,我们首先检查玩家靠近边界的情况。 在这种情况下,我们限制玩家精灵的移动,使其保持在游戏边界内。
添加弹丸射击
添加射弹/子弹的射击需要更多的参与。 和上一节一样,我们添加一个全局变量来指示触发按钮是否被按下,
bool fire_pressed = 0;
并通过在键回调中再添加一个 switch case 将触发绑定到 Space,
case GLFW_KEY_SPACE:
if(action == GLFW_RELEASE) fire_pressed = true;
break;
有不同的方法来实现射弹的发射。 我最初认为,游戏中只能出现一个弹丸。 在这里,我选择在玩家每次释放按钮时发射弹丸。 然后我们为射弹添加一个新结构,就像我们为 Player、Aliens 等所做的那样。
struct Bullet
{
size_t x, y;
int dir;
};
其中 dir 的符号表示弹丸的行进方向,即它是否向上行进,朝向外星人 ( ),或向下行进,朝向玩家 (-)。 我希望你能原谅我在代码中将射弹称为“子弹”,但那是我最初使用的。 我们定义了游戏可以显示的最大可能射弹,并将它们添加到 Game 结构中,
#define GAME_MAX_BULLETS 128
struct Game
{
size_t width, height;
size_t num_aliens;
size_t num_bullets;
Alien* aliens;
Player player;
Bullet bullets[GAME_MAX_BULLETS];
};
在游戏初始化时,我们设置子弹的数量,game.num_bullets = 0,并为弹丸添加一个精灵,
Sprite bullet_sprite;
bullet_sprite.width = 1;
bullet_sprite.height = 3;
bullet_sprite.data = new uint8_t[3]
{
1, // @
1, // @
1 // @
};
如您所见,我们目前绘制为一条小的垂直线。 在主循环中,我们绘制射弹的方式与绘制外星人的方式类似,
for(size_t bi = 0; bi < game.num_bullets; bi)
{
const Bullet& bullet = game.bullets[bi];
const Sprite& sprite = bullet_sprite;
buffer_draw_sprite(&buffer, sprite, bullet.x, bullet.y, rgb_to_uint32(128, 0, 0));
}
绘制后,我们通过添加 dir 来更新弹丸位置,并使用一种常用技术移除所有移出游戏区域的弹丸,我们用数组中的最后一个元素覆盖要删除的元素,
for(size_t bi = 0; bi < game.num_bullets;)
{
game.bullets[bi].y = game.bullets[bi].dir;
if(game.bullets[bi].y >= game.height ||
game.bullets[bi].y < bullet_sprite.height)
{
game.bullets[bi] = game.bullets[game.num_bullets - 1];
--game.num_bullets;
continue;
} bi;
}
最后,玩家发射弹丸在主循环结束时处理,
if(fire_pressed && game.num_bullets < GAME_MAX_BULLETS)
{
game.bullets[game.num_bullets].x = game.player.x player_sprite.width / 2;
game.bullets[game.num_bullets].y = game.player.y player_sprite.height;
game.bullets[game.num_bullets].dir = 2;
game.num_bullets;
}
fire_pressed = false;
我们将弹丸方向 dir 设置为 2
我们需要更多的外星人入侵
目前射弹是非交互式的,它们只是飞过外星人群并进入深渊! 如果我们想阻止外星人入侵,我们最好让射弹不那么脆弱。 但在此之前,让我们先添加更多类型的外星人。 Space Invaders中有3种外星人类型,此外,我们将通过增加一种外星人类型,死亡外星人来实现外星人死亡。 我们使用枚举在代码中实现这一点,
enum AlienType: uint8_t
{
ALIEN_DEAD = 0,
ALIEN_TYPE_A = 1,
ALIEN_TYPE_B = 2,
ALIEN_TYPE_C = 3
};
并在 Alien 结构中添加一个标志来指示外星人类型。 然后我们将外星精灵存储为一个由外星类型和动画帧索引索引的数组。
Sprite alien_sprites[6];alien_sprites[0].width = 8;
alien_sprites[0].height = 8;
alien_sprites[0].data = new uint8_t[64]
{
0,0,0,1,1,0,0,0, // ...@@...
0,0,1,1,1,1,0,0, // ..@@@@..
0,1,1,1,1,1,1,0, // .@@@@@@.
1,1,0,1,1,0,1,1, // @@.@@.@@
1,1,1,1,1,1,1,1, // @@@@@@@@
0,1,0,1,1,0,1,0, // .@.@@.@.
1,0,0,0,0,0,0,1, // @......@
0,1,0,0,0,0,1,0 // .@....@.
};alien_sprites[1].width = 8;
alien_sprites[1].height = 8;
alien_sprites[1].data = new uint8_t[64]
{
0,0,0,1,1,0,0,0, // ...@@...
0,0,1,1,1,1,0,0, // ..@@@@..
0,1,1,1,1,1,1,0, // .@@@@@@.
1,1,0,1,1,0,1,1, // @@.@@.@@
1,1,1,1,1,1,1,1, // @@@@@@@@
0,0,1,0,0,1,0,0, // ..@..@..
0,1,0,1,1,0,1,0, // .@.@@.@.
1,0,1,0,0,1,0,1 // @.@..@.@
};alien_sprites[2].width = 11;
alien_sprites[2].height = 8;
alien_sprites[2].data = new uint8_t[88]
{
0,0,1,0,0,0,0,0,1,0,0, // ..@.....@..
0,0,0,1,0,0,0,1,0,0,0, // ...@...@...
0,0,1,1,1,1,1,1,1,0,0, // ..@@@@@@@..
0,1,1,0,1,1,1,0,1,1,0, // .@@.@@@.@@.
1,1,1,1,1,1,1,1,1,1,1, // @@@@@@@@@@@
1,0,1,1,1,1,1,1,1,0,1, // @.@@@@@@@.@
1,0,1,0,0,0,0,0,1,0,1, // @.@.....@.@
0,0,0,1,1,0,1,1,0,0,0 // ...@@.@@...
};alien_sprites[3].width = 11;
alien_sprites[3].height = 8;
alien_sprites[3].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 // .@.......@.
};alien_sprites[4].width = 12;
alien_sprites[4].height = 8;
alien_sprites[4].data = new uint8_t[96]
{
0,0,0,0,1,1,1,1,0,0,0,0, // ....@@@@....
0,1,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,0,0,1,1,0,0,1,1,1, // @@@..@@..@@@
1,1,1,1,1,1,1,1,1,1,1,1, // @@@@@@@@@@@@
0,0,0,1,1,0,0,1,1,0,0,0, // ...@@..@@...
0,0,1,1,0,1,1,0,1,1,0,0, // ..@@.@@.@@..
1,1,0,0,0,0,0,0,0,0,1,1 // @@........@@
};
alien_sprites[5].width = 12;
alien_sprites[5].height = 8;
alien_sprites[5].data = new uint8_t[96]
{
0,0,0,0,1,1,1,1,0,0,0,0, // ....@@@@....
0,1,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,0,0,1,1,0,0,1,1,1, // @@@..@@..@@@
1,1,1,1,1,1,1,1,1,1,1,1, // @@@@@@@@@@@@
0,0,1,1,1,0,0,1,1,1,0,0, // ..@@@..@@@..
0,1,1,0,0,1,1,0,0,1,1,0, // .@@..@@..@@.
0,0,1,1,0,0,0,0,1,1,0,0 // ..@@....@@..
};Sprite alien_death_sprite;
alien_death_sprite.width = 13;
alien_death_sprite.height = 7;
alien_death_sprite.data = new uint8_t[91]
{
0,1,0,0,1,0,0,0,1,0,0,1,0, // .@..@...@..@.
0,0,1,0,0,1,0,1,0,0,1,0,0, // ..@..@.@..@..
0,0,0,1,0,0,0,0,0,1,0,0,0, // ...@.....@...
1,1,0,0,0,0,0,0,0,0,0,1,1, // @@.........@@
0,0,0,1,0,0,0,0,0,1,0,0,0, // ...@.....@...
0,0,1,0,0,1,0,1,0,0,1,0,0, // ..@..@.@..@..
0,1,0,0,1,0,0,0,1,0,0,1,0 // .@..@...@..@.
};
请注意,我们还添加了死亡精灵。 为了跟踪外星人的死亡情况,我们创建了一系列死亡计数器,
uint8_t* death_counters = new uint8_t[game.num_aliens];
for(size_t i = 0; i < game.num_aliens; i)
{
death_counters[i] = 10;
}
在每一帧,如果外星人死亡,我们会减少死亡计数器,当计数器达到 0 时,将外星人从游戏中“移除”。绘制外星人时,我们现在需要检查死亡计数器是否大于 0, 否则我们不必画外星人。 这样,死亡精灵会显示 10 帧。
for(size_t ai = 0; ai < game.num_aliens; ai)
{
if(!death_counters[ai]) continue; const Alien& alien = game.aliens[ai];
if(alien.type == ALIEN_DEAD)
{
buffer_draw_sprite(&buffer, alien_death_sprite, alien.x, alien.y, rgb_to_uint32(128, 0, 0));
}
else
{
const SpriteAnimation& animation = alien_animation[alien.type - 1];
size_t current_frame = animation.time / animation.frame_duration;
const Sprite& sprite = *animation.frames[current_frame];
buffer_draw_sprite(&buffer, sprite, alien.x, alien.y, rgb_to_uint32(128, 0, 0));
}
}
请注意,我们进一步更新了绘图循环,以根据外星人的类型绘制适当的外星精灵。 死亡计数器在更新射弹之前循环更新,
for(size_t ai = 0; ai < game.num_aliens; ai)
{
const Alien& alien = game.aliens[ai];
if(alien.type == ALIEN_DEAD && death_counters[ai])
{
--death_counters[ai];
}
}
酷男不看爆炸
一切终于到位,以实现 Space Invaders 中最令人满意的元素; 炸毁外星人! 为了实现这一点,我们必须在更新位置时检查子弹是否击中了活着的外星人,
for(size_t ai = 0; ai < game.num_aliens; ai)
{
const Alien& alien = game.aliens[ai];
if(alien.type == ALIEN_DEAD) continue; const SpriteAnimation& animation = alien_animation[alien.type - 1];
size_t current_frame = animation.time / animation.frame_duration;
const Sprite& alien_sprite = *animation.frames[current_frame];
bool overlap = sprite_overlap_check(
bullet_sprite, game.bullets[bi].x, game.bullets[bi].y,
alien_sprite, alien.x, alien.y
);
if(overlap)
{
game.aliens[ai].type = ALIEN_DEAD;
// NOTE: Hack to recenter death sprite
game.aliens[ai].x -= (alien_death_sprite.width - alien_sprite.width)/2;
game.bullets[bi] = game.bullets[game.num_bullets - 1];
--game.num_bullets;
continue;
}
}
我们遍历所有外星人并使用函数 sprite_overlap_check 来检查两个精灵是否重叠。 我们通过简单地检查精灵矩形是否重叠来做到这一点,
bool sprite_overlap_check(
const Sprite& sp_a, size_t x_a, size_t y_a,
const Sprite& sp_b, size_t x_b, size_t y_b
)
{
if(x_a < x_b sp_b.width && x_a sp_a.width > x_b &&
y_a < y_b sp_b.height && y_a sp_a.height > y_b)
{
return true;
} return false;
}
如果子弹精灵与外星精灵重叠,我们将子弹从游戏中移除,并将外星人类型更改为 ALIEN_DEAD。 请注意,由于我们没有正确处理游戏中的精灵居中,因此我们使用了一种随意的方式来重新居中死亡精灵。
如果你编译这篇文章的代码,你应该可以炸掉一些外星人!
结论
在这篇文章中,设置制作游戏的最重要部分,即游戏; 交互性。 使用 GLFW,我们绑定了用于移动玩家和发射弹丸的键。 尽管游戏还处于非常不完整的状态,但我们已经可以享受向外星人发射子弹的乐趣了。 享受这个过程在游戏开发中非常重要,也是激发新想法实验的源泉。 您会看到,我们能够轻松地用低级编程语言设置游戏原型。 更重要的是,我们可以重用这些代码来为其他 2D 游戏创建原型。
在下一篇文章中,我们将创建必要的工具来在屏幕上绘制基本文本,我们将跟踪并绘制玩家的得分。
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved