fantasy-craft 0.0.2

A 2D / 2.5D game engine built on top of Macroquad and Hecs for the Foxvoid Ecosystem
Documentation
use std::sync::Arc;
use macroquad::math::Vec2;

use crate::core::plugins::Plugin;
use crate::prelude::{GameState, Stage, System};
use crate::scene::scene_loader::ComponentLoader;
use crate::{graphics::sprites::Spritesheet, prelude::Context};
use crate::physics::components::Transform;

pub struct AnimationKeyFrame {
    pub column: u32,
    pub row: u32
}

impl AnimationKeyFrame {
    pub fn new(column: u32, row: u32) -> Self {
        Self { column, row }
    }
}

pub struct Animation {
    pub spritesheet: Arc<Spritesheet>,
    pub frames: Vec<AnimationKeyFrame>,
    pub current_index: f32,
    pub speed: f32,
    pub flip: bool
}

impl Animation {
    pub fn new(spritesheet: Arc<Spritesheet>, frames: Vec<AnimationKeyFrame>, speed: f32, flip: bool) -> Self {
        Self {
            spritesheet,
            frames,
            current_index: 0.0,
            speed,
            flip
        }
    }

    pub fn update(&mut self, dt: f32) {
        self.current_index += self.speed * dt;
        let num_frames = self.frames.len() as f32;

        if self.current_index >= num_frames {
            self.current_index = self.current_index.rem_euclid(num_frames);
        }
    }

    pub fn draw(&self, x: f32, y: f32, scale: Vec2) {
        let frame_index = self.current_index.floor() as usize;
        let key_frame = self.frames.get(frame_index).expect("AnimationKeyFrame index out of bounds");
        self.spritesheet.draw_sprite(key_frame.column, key_frame.row, x, y, scale, self.flip);
    }
}

#[derive(Debug)]
pub struct AnimationComponent(pub String);

pub struct AnimationComponentLoader;

impl ComponentLoader for AnimationComponentLoader {
    fn load(&self, ctx: &mut Context, entity: hecs::Entity, data: &serde_json::Value) {
        let animation_name: String = serde_json::from_value(data.clone())
            .unwrap_or_default();

        let component = AnimationComponent(animation_name);

        ctx.world.insert_one(entity, component).expect("Failed to insert AnimationComponent");
    }
}

pub fn update_animations(ctx: &mut Context) {
    let dt = ctx.dt().clone();

    for (_, animation_comp) in ctx.world.query::<&AnimationComponent>().iter() {
        if let Some(animation) = ctx.asset_server.get_animation_mut(&animation_comp.0) {
            animation.update(dt);
        }
    }
}

pub fn animation_render_system(ctx: &mut Context) {
    for (_, (animation_comp, transform)) in ctx.world.query::<(&AnimationComponent, &Transform)>().iter() {
        if let Some(animation) = ctx.asset_server.get_animation_mut(&animation_comp.0) {
            animation.draw(transform.position.x, transform.position.y, transform.scale);
        }
    }
}

pub struct AnimationPlugin;

impl Plugin for AnimationPlugin {
    fn build(&self, app: &mut crate::prelude::App) {
        app.scene_loader
            .register("AnimationComponent", Box::new(AnimationComponentLoader));

        app
            .add_system(Stage::Update, System::new(
                update_animations,
                vec![GameState::Playing]
            ))
            .add_system(Stage::Render, System::new(
                animation_render_system,
                vec![GameState::Playing, GameState::Menu]
            ));
    }
}