移动玩家

如果我们不能移动玩家,那就不能算是游戏,对吧?在本节中,我们将学习如何获取输入事件。

输入事件

让玩家移动的第一步是开始监听输入事件。如果我们快速查看 ggez 输入示例,可以看到我们可以使用 is_key_pressed 检查某个键是否被按下。

让我们从一个非常基本的输入系统实现开始,在这里我们只是检查某个键是否被按下并打印到控制台。


#![allow(unused)]
fn main() {
#[allow(dead_code)]
fn run_input_print(_world: &World, context: &mut Context) {
    if context.keyboard.is_key_pressed(KeyCode::Up) {
        println!("UP");
    }
    if context.keyboard.is_key_pressed(KeyCode::Down) {
        println!("DOWN");
    }
    if context.keyboard.is_key_pressed(KeyCode::Left) {
        println!("LEFT");
    }
    if context.keyboard.is_key_pressed(KeyCode::Right) {
        println!("RIGHT");
    }
}
}

然后,我们将这段代码添加到 Game 的 event::EventHandler 实现块中:


#![allow(unused)]
fn main() {
impl event::EventHandler<ggez::GameError> for Game {
    fn update(&mut self, context: &mut Context) -> GameResult {
        // Run input system
        {
            run_input(&self.world, context);
        }

        Ok(())
    }

    fn draw(&mut self, context: &mut Context) -> GameResult {
        // Render game entities
        {
            run_rendering(&self.world, context);
        }

        Ok(())
    }
}
}

如果我们运行它,应该会在控制台中看到打印的行。

LEFT
LEFT
RIGHT
UP
DOWN
LEFT

输入系统

现在让我们实现最终的输入系统。

我们已经有了一种方法来检查某个键是否被按下,现在我们需要实现移动玩家的逻辑。我们希望实现的逻辑是:

  • 如果按下 UP 键,我们将玩家在 y 轴上向上移动一个位置
  • 如果按下 DOWN 键,我们将玩家在 y 轴上向下移动一个位置
  • 如果按下 LEFT 键,我们将玩家在 x 轴上向左移动一个位置
  • 如果按下 RIGHT 键,我们将玩家在 x 轴上向右移动一个位置

#![allow(unused)]
fn main() {
#[allow(dead_code)]
fn input_system_duplicate(world: &World, context: &mut Context) {
    for (_, (position, _player)) in world.query::<(&mut Position, &Player)>().iter() {
        if context.keyboard.is_key_pressed(KeyCode::Up) {
            position.y -= 1;
        }
        if context.keyboard.is_key_pressed(KeyCode::Down) {
            position.y += 1;
        }
        if context.keyboard.is_key_pressed(KeyCode::Left) {
            position.x -= 1;
        }
        if context.keyboard.is_key_pressed(KeyCode::Right) {
            position.x += 1;
        }
    }
}
}

输入系统非常简单,它获取所有玩家和位置(我们应该只有一个玩家,但这段代码不需要关心这一点,它理论上可以工作于我们希望用相同输入控制多个玩家的情况)。然后,对于每个玩家和位置组合,它将获取第一个按下的键并从输入队列中移除它。接着,它会计算所需的变换——例如,如果按下上键,我们希望向上移动一个格子,依此类推,并应用这个位置更新。

非常酷!这就是它的效果。注意我们可以穿过墙和箱子。在下一节中,当我们添加可移动组件时,我们会修复这个问题。

移动玩家

但你可能注意到一个问题,单次按键会触发多次移动。让我们在下一节中修复这个问题。

处理多次按键

问题在于我们在一秒内多次调用输入系统,这意味着按住一个键一秒钟会触发多次移动。作为玩家,这不是一个很好的体验,因为你无法很好地控制移动,很容易陷入一种情况——箱子卡在墙边,无法拉回。

我们有哪些选项来解决这个问题?我们可以记住上一个帧是否按下了键,如果是,我们跳过它。这需要存储上一帧的状态,并在当前帧中与之比较以决定是否移动,这完全可行。幸运的是,ggez 在他们的键盘 API 中添加了这个功能,你可以调用 is_key_just_pressed,它会自动检查当前状态。让我们试试,它看起来像这样:


#![allow(unused)]
fn main() {
fn run_input(world: &World, context: &mut Context) {
    for (_, (position, _player)) in world.query::<(&mut Position, &Player)>().iter() {
        if context.keyboard.is_key_just_pressed(KeyCode::Up) {
            position.y -= 1;
        }
        if context.keyboard.is_key_just_pressed(KeyCode::Down) {
            position.y += 1;
        }
        if context.keyboard.is_key_just_pressed(KeyCode::Left) {
            position.x -= 1;
        }
        if context.keyboard.is_key_just_pressed(KeyCode::Right) {
            position.x += 1;
        }
    }
}
}

现在一切都按预期工作了!

CODELINK: 你可以在 这里 查看本示例的完整代码。