godot-rust(gdext)2D游戏之旅【pong】 - 4

7 阅读2分钟

简介

本文以Make Pong Game in Godot | gameidea为蓝本撰写。如果你对rust、godot等内容比较生疏或感到疑惑,可以先阅读godot-rust入门文档

本文搭建在godot-rust(gdext)创建项目的基础之上,如果你对该部分内容感到生疏,可以先阅读这部分内容,为接下来的操作做好准备。出于连贯性考虑,本文将省略一些基础性操作,这些操作可以在godot-rust(gdext)你的第一个2D游戏这个系列中看到,可以从这个系列中了解相关内容。

注意,本文使用的godot版本为4.6.2,godot-rust(gdext)版本为0.5.1,相关的版本变化可能会导致一些内容失效

本文所有操作均在Windows11环境下进行。

本文工程资源和相关素材可以在这里(pong-lab)可以获取。

正文

创建UI

// ui.rs
use std::collections::HashMap;

use godot::{
    classes::{Button, CanvasLayer, ICanvasLayer, Label},
    prelude::*,
};

use crate::state::{State, Stateful};

#[derive(GodotClass)]
#[class(init, base=CanvasLayer)]
pub struct UI {
    base: Base<CanvasLayer>,

    state: UIState,

    #[init(val = Self::contents())]
    contents: HashMap<UIState, Content>,

    #[init(node = "Label")]
    label: OnReady<Gd<Label>>,

    #[init(node = "Button")]
    button: OnReady<Gd<Button>>,
}

#[godot_api]
impl ICanvasLayer for UI {
    fn ready(&mut self) {
        self.button
            .signals()
            .button_up()
            .connect_other(&*self, Self::on_button_up);

        self.on_state_enter();
    }
}

#[godot_api]
impl UI {
    #[signal]
    pub fn start_game();

    #[signal]
    pub fn resume_game();

    fn contents() -> HashMap<UIState, Content> {
        // 使用Model思路将内容部分和展示部分解耦
        let mut map = HashMap::new();
        map.insert(UIState::Title, ("Pong", "Play").into());
        map.insert(UIState::Paused, ("Paused", "Resume").into());
        map.insert(UIState::Victory, ("Win", "Replay").into());
        map.insert(UIState::GameOver, ("Game Over", "Replay").into());

        map
    }

    fn on_button_up(&mut self) {
        match self.state { // 根据不同状态复用按钮,提供不同信号发射
            UIState::Paused => self.signals().resume_game().emit(),
            _ => self.signals().start_game().emit(),
        }
    }
}

struct Content {
    label: &'static str,
    button: &'static str,
}
impl From<(&'static str, &'static str)> for Content {
    fn from(value: (&'static str, &'static str)) -> Self {
        Self {
            label: value.0,
            button: value.1,
        }
    }
}

#[derive(PartialEq, Eq, Default, Clone, Copy, Hash)]
pub enum UIState {
    #[default]
    Title,
    Paused,
    Victory,
    GameOver,
}
impl State for UIState {}

impl Stateful for UI {
    type S = UIState;

    fn on_state_enter(&mut self) {
        if let Some(content) = self.contents.get(&self.state) {
            // 进入状态即加载其应展示数据
            self.label.set_text(content.label);
            self.button.set_text(content.button);
        }
    }

    fn on_state_exit(&mut self) {}

    fn set_state(&mut self, new_state: Self::S) {
        self.state = new_state;
    }

    fn state(&self) -> Self::S {
        self.state
    }
}

UI只提供UI功能和UI界面信号发送,我们不把控制逻辑放入UI。

UI只有两个元素,一个Lable,一个Button,编译rust后,我们在godot创建UI场景。

UI场景

选中Label

  • Text属性中可以填写一些文字来用于编辑展示,这里填写的时“Label”,这些文字在运行中将会被替换掉。
  • 将Label顶部居中后,稍微再往下调整20

调整Label位置

  • 检查器 > Control > Theme Override > Font Sizes > Font Sizes : 80
  • 检查器 > Control > Theme Override > Styles > Normal : StyleBoxFlat
  • 选中刚创建的StyleBoxFlat,为其设置一个背景颜色 : Hex : 000000b4,即半透明的黑色背景

设置Label背景

Label类似,选中ButtonText属性可填示意内容,这里填写的是“Button”,与上述相同的位置设置Font Size80,锚点设置为居中

参考

  1. godot-rust(gdext)创建项目 - 掘金
  2. godot - Rust
  3. Make Pong Game in Godot | gameidea
  4. Pong with GDScript Demo - Godot Asset Library