通过SDL2/Rust实现含有动画的Timer程序

· Technology

#rust

主要是用 Rust clone sowon - Starting Soon Timer for Tsoding Streams 用来学习的项目。

Sowon Rust

看 C 版的代码,没搞过 SDL2,看不懂的 API,就看 C 文档,看 Rust 中 sdl2 binding 的文档 。

API 设计形式可能一样,有需要包装类,用动态指针更好的管理内存。

SDL2 Rust Doc
SDL2 Doc

主要实现思虑是,使用包含数字的图片加载成 sdl 中的 texture,然后每次根据窗口的大小计算需要画这些数字的位置。

fn initial_pen(
    window: &Window,
    pen_x: &mut i32,
    pen_y: &mut i32,
    user_scale: f32,
    fit_scale: &mut f32,
) {
    let (w, h) = window.size();
 
    let text_aspect_ratio = TEXT_WIDTH as f64 / TEXT_HEIGHT as f64;
    let window_aspect_radio = w as f64 / h as f64;
    *fit_scale = if text_aspect_ratio > window_aspect_radio {
        w as f32 / TEXT_WIDTH as f32
    } else {
        h as f32 / TEXT_HEIGHT as f32
    };
 
    let effective_digit_width = (CHAR_WIDTH as f32 * user_scale * *fit_scale).floor() as i32;
    let effective_digit_height = (CHAR_HEIGHT as f32 * user_scale * *fit_scale).floor() as i32;
    *pen_x = w as i32 / 2 - effective_digit_width * CHARS_COUNT as i32 / 2;
    *pen_y = h as i32 / 2 - effective_digit_height / 2;
}

怎么实现数字抖动的?其实就是在取 texture 图像的时候,每次根据抖动频率来进行特定的偏移,然后取模保整处于一个循环中。

渲染,可以看到 src_rect 会根据 wiggle_index 来确定 texture 中的要取的高度位置。

fn render_digit_at(
    renderer: &mut WindowCanvas,
    texture: &Texture,
    digit_index: usize,
    wiggle_index: usize,
    pen_x: &mut i32,
    pen_y: &mut i32,
    user_scale: f32,
    fit_scale: f32,
) {
    let effective_digit_width = (CHAR_WIDTH as f32 * user_scale * fit_scale).floor() as i32;
    let effective_digit_height = (CHAR_HEIGHT as f32 * user_scale * fit_scale).floor() as i32;
    let src_rect = Rect::new(
        (digit_index * CHAR_WIDTH as usize) as i32,
        (wiggle_index * CHAR_HEIGHT as usize) as i32,
        SPRITE_CHAR_WIDTH,
        SPRITE_CHAR_HEIGHT,
    );
 
    let dst_rect = Rect::new(
        *pen_x,
        *pen_y,
        effective_digit_width as u32,
        effective_digit_height as u32,
    );
 
    renderer.copy(texture, src_rect, dst_rect).unwrap();
    *pen_x += effective_digit_width;
}
 
 
 

抖动频率控制

        if wiggle_cooldown <= 0.0 {
            wiggle_index += 1;
            wiggle_cooldown = WIGGLE_DURATION;
        }
        wiggle_cooldown -= fps_dt.dt;

还有个比较有意思的是帧率管理,在 fps 为 60 时,代表一秒中要迭代渲染 60 次画面。

通过 frame_end() 确保每帧不会超出设定的 fps_cap的每帧时隔时间,通过帧延迟来控制渲染速度,防止帧率过高。

这样就确保无论实际帧率如何,游戏或动画中的移动、物理效果等都能按照相同的速率运行。这解决了帧率不稳定可能导致的动画速度变化问题。

然后我的们控制的时间是秒为单位的,所以每次渲染加等于 dt 就行。

struct FpsDeltaTime {
    pub frame_delay: u32, // Frame delay in milliseconds
    pub dt: f32,          // Delta time in seconds
    pub last_time: u64,
    timer_subsystem: TimerSubsystem,
}
 
impl FpsDeltaTime {
    pub fn new(fps_cap: u32, timer_subsystem: TimerSubsystem) -> Self {
        Self {
            last_time: timer_subsystem.performance_counter(),
            timer_subsystem,
            frame_delay: 1000 / fps_cap,
            dt: 0.0,
        }
    }
 
    pub fn frame_start(&mut self) {
        let now = self.timer_subsystem.performance_counter();
        let elapsed = now - self.last_time;
        self.dt = elapsed as f32 / self.timer_subsystem.performance_frequency() as f32;
        self.last_time = now;
    }
 
    pub fn frame_end(&self) {
        let now = self.timer_subsystem.performance_counter();
        let elapsed = now - self.last_time;
        let cap_frame_end = (((elapsed as f32) * 1000.0)
            / (self.timer_subsystem.performance_frequency() as f32))
            as u32;
 
        if cap_frame_end < self.frame_delay {
            self.timer_subsystem.delay(self.frame_delay - cap_frame_end);
        }
    }
}
 

Comments (0)

    Send comment

    Markdown supported. Please keep comments clean.