Aprendiendo Rust - Kiwi Towers

Lecherito

Que coño es esto?

Pues mira despues de estar aburrido ya que el tema de los bancos ya lo he dejado finiquitado me he decidido a empezar de nuevo esta mierda (me compre el Element TD 2 en Steam y le he metido 20 horas esta semana, y para que enga;arnos, me ha entrado un mono de la hostia de empezar esto) asi que despues de 2 (1, 2) intentos fallidos (siempre abandonados), pues llega el tercero. Siempre ha salido mejor, pero la idea de todo era aprender mas que otra cosa. El primero fue Java, el segundo fue Kotlin y la tercera entrega de la saga es en Rust.

Se va a tratar de un juego de tipo Tower Defense donde podras crear tus propios mapas, con tu logica del juego sin saber absolutamente nada de programacion. Demasiado tiempo en el Warcraft 3 me llevaron a esta idea alla por 2012 y sigo pensando que podria ser muy guay, asi que alla que voy de nuevo.

Me encanta, como lo haras?

Pues creo que uno de los errores que he cometido en las dos primeras ediciones de este abandono es empezar por el editor. Siempre empezaba por la parte aburrida, la que no quiere hacer nadie (y con razon), y la que es muy poco vistosa por lo que me acababa desanimando por dos razones: 1, odio crear programitas con sus interfaces, ademas de ser pesimo y 2, al no poder probarlo me aburria muchisimo.

En esta edicion voy a hacer algunas cosas distintas:

  1. Llevar este diario actualizado (je)
  2. Empezar por la logica del juego, sin tan siquiera tener enemigos, o tenerlo todo hardcoded (por ejemplo un programa que me genere el mapa, pero sin editor)

Lenguaje

Pues hace tiempo que quiero aprender Rust. He hecho ya algun que otro pinito con este lenguaje pero me gustaria darle mas ca;a ya que en un futuro creo que me gustaria encontrar trabajo con este lenguaje (las palabras se las lleva el viento).

Rust y gamedev todavia esta en una edad muy temprana (https://arewegameyet.rs/), pero todo apunta a que tendra buena acogida y se estan desarrollando cosas bastante guays asi que vamos a darle una oportunidad. Si resulta que todo esto queda en la mierda pues me llevare mis conocimientos a otra parte.

Engine

bevy, despues de meditarlo un tiempo aunque este a medio hacer, voy a empezar con este engine. Tiene las cosas basicas y le faltan muchas otras pero esta en constante desarrollo. De ahi que vaya a empezar con la logica del juego y dejar todos los bloques de la interfaz para luego, cuando tenga que construir mi propia interfaz o usar algo que ya esta hecho.

Alguna otra libreria?
Supongo, pero todavia no tengo ni idea.

Plataformas

Juego - Android (No se si habra mas, tendria que investigar un poco mas, pero supongo que Steam tambien)
Editor - Pc (Windows/Linux/Mac)

FAQ

  1. Lo abandonaras? Sin duda.
  2. A ver, listo, y por que no lo abres en gamedev? Pues porque por ahora no va a ser un juego, va a ser una serie de modulos definiendo la logica que en muy poco se va a parecer a un juego.
  3. Estara el codigo en github? No, pero no tendre ningun problema en compartir lo que haga falta asi como responder preguntas
8
B

Te transmito mi energia para que lo termines <3

Fyn4r

Siempre es buen momento para verte intentar otro tower defense. Ánimo que te hará falta xd

1 1 respuesta
wasni

si necesitas alguien para testear me ofrezco voluntario :)

Leos

Estaré atentisimo a este diario, dos cosas que siempre he querido hacer, un tower defense y aprender rust!

Ánimos!

Lecherito

<3 por los animos

#3 no se en que quede la cosa, pero como poco voy a aprender Rust xDDDD.

Ayer estuve mirando un poco como funciona Bevy y su ECS (nunca he usado ECS) asi que estuve faileando un buen rato. Tengo que aprender mejor como funcionan los eventos para poder hacer spawn/kill/damage y estas mierdas pero ayer solo tenia panic!s por todos sitios lmao

Wei-Yu

intenté hacer yo un roguelike en rust hace año y medio y sólo te digo que eso de bevy creo que ni existía en las conversaciones sobre fw/libs que usar para gamedev

me miré cuatro o cinco libs/fw y al final lo mandé al carajo

1 respuesta
Lecherito

#7 Bevy es de hace un par de meses: https://bevyengine.org/news/ hace a;o y medio estaba todo en pa;ales me parece a mi.

Saiko9

Muy interesante, te seguire de cerca.

La verdad es que yo estuve hace unos meses pensando en hacer lo mismo, estoy aprendiedo Rust y me hice en el primer confinamiento un prototipo de roguelike usando ECS en un motor llamado Amethyst.

Por muchos motivos como que el gamedev no es algo que tampoco me apasione si lo comparo con otros temas pues lo abandone y no se si algun dia lo retomare jajajjaa pero te deseo suerte y que crees algo bueno aunque nunca acabes :P.

Por cierto no esta mal puesto el foro? deberia de ir en desarrollo de videojuegos.

edit: no habia leido la explicacion a lo del foro, disculpa.

Wei-Yu

Cuando me metí al principio parecía bastante más maduro de lo que era, pero es algo que me lleva ocurriendo desde que me propuse darle un par de patadas a rust la primera vez; está muy verde y cambia todo mucho como para que me apetezca seguir intentando prestarle atención. Quizás es algo que ya cambió, porque de las cosas más gordas fue el tema del async y ya llovió desde entonces.

1 respuesta
Saiko9

#10 Si, te has respondido tu solo.

Desde rust 2018 y el tema del async se ha vuelto todo bastante mas estable (antes era inviable). En cuanto a gamedev y el mercado en general pues seguramente si no cambia nada tendremos que esperar unos añitos para empezar a verle dando duro.

Lecherito

Pues bueno, hoy he estado dandole un ratito y ahora me dispongo a abrir el Warcraft III para echar una footman frenzy, que he leido el hilo y desde ayer no vicio.

He estado mirando un poco como funciona Bevy, he mirado un poco a ver como iban los eventos, las resources y toda esto por lo que esto es mas o menos lo que tengo entendido:

  1. Resource: Entidades globales que se mantienen de esta manera desde el principio hasta el final del juego. Por ejemplo la puntuacion.
  2. System: Cosa que hace algo respecto a lo que existe/lo nuevo. Esto no lo entiendo del todo bien como funciona (me gustaria ver el codigo interno de Bevy), pero resulta que convierte una funcion con cualquier argumento y lo convierte en un sistema que se va a ejecutar cuando se tenga que ejecutar. Para mi por ahora es magia negra.
  3. Plugin: Esto es simplemente un conjunto de cosas que se ejecutan a la vez. Por ejemplo puedes hacer un plugin que cargue X cosa, o a;ada todos los malos al principio de la partida etc etc.
  4. Eventos y listeners: Esto todavia no lo entiendo del todo bien, aunque he conseguido enviar un evento cuando una torre se construye (por ahora se construye una al principio) pero es igual de magia negra que los argumentos del system() ya que es el sistema el que se encarga de ver los eventos y gestionarlos.

Este es el codigo que tengo por ahora, al principio supongo que ira todo muy lento dado que no tengo ni idea de demasiadas cosas (tambien junto que he puesto el plugin de vim en CLion lol).

use bevy::prelude::*;
use bevy::app::ScheduleRunnerPlugin;
use std::time::Duration;
use crate::GameEvent::{TowerDamaged, TowerPlaced};

fn main() {
    App::build()
        .add_event::<GameEvent>()
        .init_resource::<EventListenerState>()
        .add_startup_system(add_towers.system())
        .add_system(print_tower.system())
        .add_system(event_listener_system.system())
        .run();
}

#[derive(Debug)]
struct Tower;

#[derive(Debug)]
struct Damage(f32);

#[derive(Debug)]
struct HealthPoints(f32);

enum GameEvent {
    TowerPlaced,
    TowerDamaged,
}

#[derive(Default)]
struct EventListenerState {
    event_reader: EventReader<GameEvent>
}

fn print_tower(tower: &Tower) {
    println!("Tower: {:?}", tower);
}

fn add_towers(mut commands: Commands, mut events: ResMut<Events<GameEvent>>) {
    commands.spawn((Tower, Damage(100_f32), HealthPoints(100_f32)));
    events.send(TowerPlaced)
}

fn event_listener_system(
    mut state: ResMut<EventListenerState>,
    mut events: ResMut<Events<GameEvent>>
) {
    for event in state.event_reader.iter(&events) {
        match event {
            GameEvent::TowerDamaged => {
                println!("Tower has been damaged")
            }
            GameEvent::TowerPlaced => {
                println!("Tower has been placed")
            }
        }
    }
}

Lo siguiente, seria:

  1. Spawnear 2 torres, 2 bichos y que las torres disparen a los bichos cada segundo o algo asi. Y el juego se termine cuando se maten todos los bichos. Asi que espero llegar a algo asi este fin de semana.
  2. A;adirle oleadas y que el juego termine cuando se terminen las oleadas
  3. Y no se que mas, pero ya vere xddd

Asi que bueno, excepto la magia negra y el ECS, me esta gustando como funciona el engine.

1 1 respuesta
JuAn4k4

#12 He estado leyendo por encima, y creo que component & system funcionan de tal forma que son:

componentes: Propiedades de las entidades.
system: Funcionalidades de las entidades según sus propiedades.

De forma que en conjunción, puedes hacer lo que te de la gana de forma modular.

Tus torres tendrán componentes: position, dps, range
Y habrá una system que haga el damage: por cada entity con dps/pos/range hará daño cada X a la unidad más cercana/en rango.

Por ejemplo.

O las unidades, tendran velocidad y positicion (components) + movimiento (system).

Los systems te dejan tener queries/etc y se ejecutan según los parámetros que tengan ( como un pub-sub) parece con magia negra para ejecutarlo (reflection? no conozco mucho rust)

La verdad es que está muy bien para modular, aunque para un proyecto grande puede ser jodido mantenerlo ordenado si hay muchas entidades con muchos componentes. Al final imagino que acabarías metiendo un componente por tipo de entidad, royo Tower/Enemy/etc..

Me ha parecido muy buena la doc https://bevyengine.org/news/introducing-bevy/

Ojala tuviera tiempo, me haría el juego de lucha de hormigas que quería hacer hace tiempo.

1 respuesta
Lecherito

#13 Hostias, me habia ido al book directamente y no habia visto ese "Introducing Bevy" lol

Si, mas o menos lo habia entendido asi (componentes y sistemas), pero la cosa es que me parece raro crear un componente por cada propiedad. Por ejemplo si tengo una torre con: da;o, velocidad de ataque, bala, velocidad de la bala, critico, probabilidad de critico, slow, probabilidad de slow, porcentaje slow, stun, probabilidad de stun y un largo etcetera meter un struct (single) para cada una tiene pinta de ser un pifostio en vez de crear una struc con todas las propiedades y luego hacer spawn de eso. Esto lo estuve intentando pero se ve que tiene que implementar alguna cosa rar del engine y todavia no me ha quedado del todo claro.

Lo del pub-sub y la magia negra si me he dado cuenta, pero no es para nada reflection dado que es un compilado a maquina, no bytecode asi que ahi no hay reflection que valga, sigo aprendiendo y esas cosas aunque hoy no me he puesto nada con ello (dia largo en el trabajo). Si me quedan ganas me pondre ahora un ratito pero me esta gustando lo que estoy viendo por el momento.

1 respuesta
JuAn4k4

#14 Te permite hacer cosas molonas modelando proyectiles y projectileHit, splashes, etc, modularizando todo. Pero si puede llegar a ser un overkill para cosas simples.

La verdad es que estaba escondida la doc

Lecherito

Pues he estado jugando otro rato con el engine y me va gustando lo que veo, hiper personalizable y empiezo a entender un poco mas sobre ECS. He aprendido que existen los bundles para no tener que crear objetos mega enormes cuando haces spawn si no que spawneas la Tower y de ahi puedes sacar lo que quieras. Asi que por ahora no creo que hagan falta tantas struct como Damage ya que seguramente pueda ir como propiedad, ese es el siguiente experimento que quiero hacer.

Consegui enviar 2 eventos de que una torre le quita vida a un mob que hay vivo (2 veces), y se ve en el output como de verdad pone que le queda menos vida que con la que empezo.

Trying to damage a mob
Tower: Tower { damage: Damage(100.0), hp: HealthPoints(1000.0), bullet: Bullet { speed: Speed(100) } }
Event received: TowerPlaced(1v0)
Event received: MobSpawned(2v0)
Event received: TowerDamaged(0v0, 1v0)
Tower has damaged a mob! HealthPoints(1000.0) for Damage(100.0)
Event received: TowerDamaged(0v0, 1v0)
Tower has damaged a mob! HealthPoints(900.0) for Damage(100.0)

El cogido queda asi, es bastante engorroso por ahora ya que es el fruto de muchos tests y aprendizaje pero creo que el core empiezo a entenderlo mucho mas.

use crate::GameEvent::{TowerPlaced, MobSpawned, TowerDamaged};


use bevy::prelude::*;


fn main() {
    App::build()
        .add_event::<GameEvent>()
        .init_resource::<EventListenerState>()
        .add_startup_system(add_towers.system())
        .add_startup_system(add_mobs.system())
        .add_system(damage_mob.system())
        .add_system(print_tower.system())
        .add_system(event_listener_system.system())
        .run();
}

#[derive(Debug, Bundle)]
struct Tower {
    damage: Damage,
    hp: HealthPoints,
    bullet: Bullet,
}

#[derive(Debug, Bundle)]
struct Bullet {
    speed: Speed
}

#[derive(Debug)]
struct Damage(f32);

#[derive(Debug)]
struct Speed(i32);

#[derive(Debug)]
struct HealthPoints(f32);

#[derive(Debug, Bundle)]
struct Mob {
    damage: Damage,
    hp: HealthPoints,
    ms: Speed,
}

#[derive(Debug)]
enum GameEvent {
    TowerPlaced(Entity),
    TowerDamaged(Entity, Entity),
    MobSpawned(Entity),
}

#[derive(Default)]
struct EventListenerState {
    event_reader: EventReader<GameEvent>,
}

fn print_tower(tower: &Tower) {
    println!("Tower: {:?}", tower);
}

fn damage_mob(
    mut events: ResMut<Events<GameEvent>>,
) {
    println!("Trying to damage a mob");
    events.send(TowerDamaged(Entity::new(0), Entity::new(1)));
    events.send(TowerDamaged(Entity::new(0), Entity::new(1)));
}

fn add_mobs(mut commands: Commands, mut events: ResMut<Events<GameEvent>>) {
    let entity1 = Entity::new(2);
    let mob = Mob {
        hp: HealthPoints(1000_f32),
        damage: Damage(100_f32),
        ms: Speed(100),
    };
    commands.spawn((mob, entity1));
    events.send(MobSpawned(entity1));
}

fn add_towers(mut commands: Commands, mut events: ResMut<Events<GameEvent>>) {
    let entity1 = Entity::new(1);
    let tower = Tower {
        damage: Damage(100_f32),
        hp: HealthPoints(1000_f32),
        bullet: Bullet {
            speed: Speed(100)
        }
    };
    let tower = (tower, entity1);
    commands.spawn(tower);
    events.send(TowerPlaced(entity1));
}

fn event_listener_system(
    mut state: ResMut<EventListenerState>,
    events: ResMut<Events<GameEvent>>,
    mut towers: Query<(Entity, &Tower)>,
    mut mobs: Query<(Entity, &mut Mob)>,
) {
    for event in state.event_reader.iter(&events) {
        println!("Event received: {:?}", &event);
        match event {
            GameEvent::TowerDamaged(_from, _to) => {
                let mut tower_query = towers.entity(*_from);
                let mut tower_query = tower_query.unwrap();
                let (_, tower) = tower_query.get().unwrap();
                let mut mob_query = mobs.entity(*_to).unwrap();
                let (_, mut mob) = mob_query.get().unwrap();
                println!("Tower has damaged a mob! {:?} for {:?}", &mob.hp, &tower.damage);
                mob.hp.0 -= &tower.damage.0;
            },
            GameEvent::TowerPlaced(tower_entity) => {
                if let Ok(mut query) = towers.entity(*tower_entity) {
                    if let Some((_entity, tower)) = query.get() {
                        println!(
                            "Tower has been placed with {:?} damage and {:?} hp",
                            &tower.damage, &tower.hp
                        );
                    }
                }
            }
            _ => {}
        }
    }
}

Para lo siguiente

  1. Me gustaria hacer un system por cada tipo de eventos y que no sea el mismo metodo para todos los eventos.
  2. Ver si un evento puede ser ejecutado por 2 sistemas diferentes.
  3. Como funciona el testing?
1
Lecherito

Lo siguiente esta siendo modular un poco las cosas para que no este todo en el mismo sitio y poder exponer una API o algo asi. Por ahora solo tengo un modulo que se llama model con las structs que se convertiran luego en entities.

Por ejemplo, el common.rs

use bevy::prelude::*;

#[derive(Debug, Bundle)]
pub struct Bullet {
    pub speed: Speed
}

pub type Speed = i32;
pub type HealthPoints = f32;
pub type Damage = f32;

Y el tower.rs

use bevy::prelude::*;

use crate::model::common::{Bullet, Damage, HealthPoints};

#[derive(Debug, Bundle)]
pub struct Tower {
    pub damage: Damage,
    pub hp: HealthPoints,
    pub bullet: Bullet,
}

Haciendo los type alias puedo mantener el tipo y si luego se ha de cambiar solamente los numeros hardcoded que haya (si es que hay, no deberia; bueno, quiza los tests), pero no creo que se tuviese que cambiar nada mas.

Tambien he investigado un poco 1 y seria algo dificil ya que se usa con un enum. Y para pasar datos se necesita como minimo pattern matching, asi que se puede pero tiene pinta de ser un co;azo.

Sobre 2, no parece ser posible, lo ejecuta el primer system que lo pilla aunque todavia me queda hacer mas pruebas sobre esto.

3 ni me acordaba xdddd

D

Voy a leer en algun momento, aunque Rust solo hago cosas de networking for fun seguro que aprendo algo nuevo por aqui.

Lecherito

Vale, 2 si que es posible. Estaba usando uno de sus ejemplos que usa ResMut<EventListenerState> que en verdad por ahora no hace falta ya que no necesito guardar ningun tipo de estado.

Cambiandolo de eso a: Local<EventReader<GameEvent>> funciona estupendamente y ahora puedo recibir el mismo evento en dos diferentes system.

Tower: Tower { damage: 100.0, hp: 1000.0, bullet: Bullet { speed: 100 } }
Tower2 has damaged a mob! 1000.0 for 100.0
Tower2 has damaged a mob! 900.0 for 100.0
Tower has damaged a mob! 800.0 for 100.0
Tower has damaged a mob! 700.0 for 100.0

El 2 es el system que esta antes en la caeda de sistemas y se ve que al mob le baja la vida hasta 0.

Esto tiene una pinta de puta madre porque puedes crear un sistema (o conjunto de sistemas, que lo llaman plugin) que tenga todo lo relacionado con los mobs y en este caso seria algo del tipo: Query<(&Mob)>, for mob in query.iter() { if mob.health_points <= 0 { despawn; send_mob_death_event(); } }. Y nadie mas tiene que saber sobre eso.

Sabiendo esto, el 1 se hace casi que mas facil dado que simplemente puedes hacer un filter con lo que aunque el filter quedaria raro en un principio seria easy.

1 respuesta
AikonCWD

A ver, listo, y por que no lo abres en gamedev?

1 respuesta
HeXaN

#20 Porque está programando de verdad.

3 2 respuestas
JuAn4k4

Viendo como funciona el ECS, me extraña que no haya librerias open-source con componentes/systems y demás como base de juegos. No se, royo muros, movimientos, colisiones, proyectiles, armas, etc para poder montar diferentes tipos de juegos "uniendo piezas".

1 respuesta
AikonCWD

#21 La respuesta correcta era: Pues porque por ahora no va a ser un juego, va a ser una serie de modulos definiendo la logica que en muy poco se va a parecer a un juego.

Fyn4r

#21 yo iba más por "nadie va a entender de lo que habla"

1 1 respuesta
Lecherito

#22 La cosa es que no me gustan ese tipo de cosas, en verdad se que nunca lo voy a terminar dado que en el fondo se que lo hago por cacharrear y aprender cosas nuevas y no para hacerme rico con esta mierda. Me gusta usar alguna base pero que no me lo den todo hecho, asi he aprendido de mates/geometria y otras muchas cosas.

Me mola demasiado personalizar y hacerme mis propias mierdas (si no, puedes mirar el hilo de los bancos, los templates, el workspace)... joder si estaba pensando en hacerme mi propio sistema de building.

#24 Dudo que nadie se entere de todas maneras, pero como que me ayuda a centrarme en lo que hago. Y si de paso le llega a ayudar a alguien, pues mejor.

1 1 respuesta
JuAn4k4

#25 Me parece lo más correcto para aprender la verdad. Era solo por comentar, yo creo que estos sistemas tendrán mayor/menor acogida por la base que den ya hecha, y un sistema como el ECS que han montado da mucho pie a esto mismo.
Si alguien se lanza a hacer un OS de componentes para Bevy, si no lo acaba absorbiendo Bevy, empezarían a hacerlo ellos mismos, les daría mucho tirón. Y el resto irían detrás. Teniendo en cuenta la cantidad de devs indies que hay en la comunidad. No se, yo lo veo.

¿Que tal compila ? Que cuando toco Rust se pone mi macbook a despegar que parece que vaya a llegar a la Luna.

1 respuesta
Lecherito

#26 El primer compilado creo que siempre va a ser un infierno y parece que va a despegar (ten en cuenta que esta compilando practicamente todo desde 0, todas las dependencias). Luego cuando estas desarrollando de normal los tiempos de compilado son irrisorios (en torno a 2 segundos?).

Y creo que he visto una forma estupenda de hacer las cosas y tener las propiedades en una torreta y los componentes a la vez.

1
Lecherito

Pues si, he visto una forma mejor (aunque requiere algo de sincronizacion entre el modelo y el bundle), que se trata de tener el modelo con sus tipos normales (aunque sean alias). Teniendo una implementacion de la struct que se puedan crear nuevas torres y algo que lo convierte a bundle parece lo optimo.

use bevy::prelude::*;

use crate::model::common::{Bullet, Damage, HealthPoints, Speed};
use bevy::ecs::DynamicBundle;

#[derive(Debug, Bundle)]
pub struct Tower {
    pub damage: Damage,
    pub hp: HealthPoints,
    pub bullet: Bullet,
    pub attack_speed: Speed,
}

pub mod components {

    #[derive(Debug)]
    pub struct AttackSpeed;

    #[derive(Debug)]
    pub struct Damage;

}

impl Tower {
    pub fn as_bundle(self,) -> impl DynamicBundle {
        (Entity::new(1), self, components::AttackSpeed, components::Damage)
    }

    pub fn new(
        damage: Damage,
        hp: HealthPoints,
        bullet: Bullet,
        attack_speed: i32,
    ) -> Self {
        Tower {
            damage,
            hp,
            bullet,
            attack_speed,
        }
    }
}

Luego para hacer spawn (en los components), es easy!

    let tower = Tower::new(100_f32, 1000_f32, Bullet {
        speed: 100
    }, 100);
    let tower = tower.as_bundle();
    commands.spawn(tower);

De esta manera me deja hacer Queries por los componentes que a mi me venga en gana y actualizar lo que se necesite:

fn print_damage(mut attacking_towers: Query<(&Tower, &AttackSpeed)>) {
    for (tower, attack_speed) in attacking_towers.iter().iter() {
        println!("Damage: {:?}", tower.damage);
    }
}

No solo eso si no que Bevy tiene bastantes tipos de queries, por ejemplo las Or, Query<(Or<&Tower, &Mob>, &AttackSpeed)>, esto me daria un iterador de tuplas que contiene en cada tupla (tower, attack_speed) o (mob, attack_speed), asi que la flexibilidad que le veo ahora mismo es inmensa.

Si, el iter().iter() queda feo, pero es porque estoy usando una version mas antigua de bevy (la que estan en git tiene cambios importantes en ECS) y uno de esos cambios es que Query es un iter normal (https://github.com/bevyengine/bevy/pull/741) asi que es algo que va a cambiar en la siguiente release. Ademas de que si no se hace pronto, cambiare de release a la version de git (que es alo que se puede hacer de forma nativa en Rust).

Bueno, pues creo que por ahora tengo un buen entendimiento de como funciona el engine con ECS y sus eventos como para seguir refactorizar lo que tengo hasta ahora de esta manera y hacer el prototipo de una torre que tiene velocidad de ataque y ataca a todos los mobs que ve en cada frame.

Cosas para investigar:

  1. Ver como funcionan los timer para en vez de hacer una cosa cada frame, hacer una cosa cada X tiempo (se que existe, pero no me he molestado todavia)
  2. Sigo teniendo que mirar los test lul
Lecherito

Pues sigo investigando, en este caso he estado mirando cosas sobre 1.

Para tener timers era necesario primero tener una aplicacion que no se parara nada mas ejecutarse una vez (la verdad es que esto tiene que molar para los unit test tambien) asi que le he dado una vuelta a eso y resulta que hay un plugin que te hace eso. El plugin es ScheduleRunnerPlugin y lo que pasa es que hay una cosa que se llama runner que se puede hacer desde la app.set_runner y es lo que hace el loop infinito actualizando y esas cosas. No solo eso si no que ademas tiene eventos de exit por si alguna vez quieres salir de la aplicacion, la verdad es que me ha sorprendido para bien.

Asi que me he propuesto experimentar con plugins, le he a;adido el ScheduleRunnerPlugin y he creado mi AttackPlugin (que por ahora no mira si el mob esta en rango), pero una implementacion muy simple seria:

pub struct AttackPlugin;

impl Plugin for AttackPlugin {
    fn build(&self, app: &mut AppBuilder) {
        app.add_system(attack_mobs.system());
    }

    fn name(&self) -> &str {
        return "AttackPlugin";
    }
}

Y el metodo para atacar:

fn attack_mobs(
    mut towers: Query<(&Tower, &Attack)>,
    mut mobs: Query<(&mut Mob)>
) {
    for (tower, _) in towers.iter() {
        for (mut mob) in mobs.iter_mut() {
            if true {
                mob.hp -= &tower.attack.damage;
                println!("Now the mob has {} hp.", mob.hp);
            }
        }
    }
}

Luego en verdad el &Attack no haria ni falta, pero quiza hay torres que no atacan (bufos?) o algo asi, y te las puedes saltar.

Con esto ataca en cada frame (y hostias que velocidad):

Tower: Tower { hp: 1000.0, attack: Attack { damage: 100.0, bullet: Bullet { speed: 100 }, attack_speed: 100, range: 0 }, critical: None, slow: None, stun: None }
Damage: 100.0
Now the mob has -97700 hp.
Tower: Tower { hp: 1000.0, attack: Attack { damage: 100.0, bullet: Bullet { speed: 100 }, attack_speed: 100, range: 0 }, critical: None, slow: None, stun: None }
Damage: 100.0
Now the mob has -97800 hp.
Tower: Tower { hp: 1000.0, attack: Attack { damage: 100.0, bullet: Bullet { speed: 100 }, attack_speed: 100, range: 0 }, critical: None, slow: None, stun: None }
Damage: 100.0

Ademas he estado jugando con cosas como el critico/stun y serian super sencillos de implementar.

Cosas para la siguiente:

  1. Sigo teniendo que ver como funcionan los timer
  2. Y los tests pues ahi estan tambien xD
Lecherito

Pues hoy he quitado un monton de mierda de todas las pruebas que he estado haciendo durante estos dias mientras aprendia sobre Bevy y ya he conseguido usar bien los timers y ya tengo a 2 torres atacando a un mob (aunque este no muere todavia). Tambien le he configurado loggers asi puedo saber tiempos y esas cosas para comprobar mejor lo de los timers xDD

Este es el system que ataca, es de lo mas sencillo y lo unico que hay que comprobar es si el timer ha terminado, los plugins que vienen ya preconfigurados se encargan (ya que son componentes) de hacer el tick para cada componente del tipo Timer. Como se ve, es lo mas sencillo de la historia y la unica linea con chicha es la de mob.hp

fn attack_mobs(
    mut towers: Query<(&Tower, &mut Timer)>,
    mut mobs: Query<(&mut Mob)>
) {
    for (tower, mut timer) in towers.iter_mut() {
        if timer.just_finished {
            for (mut mob) in mobs.iter_mut().take(1) {
                // Handle stuff like critical strike, stun, slow
                mob.hp -= &tower.attack.damage;
                info!("Now the mob has {} hp.", mob.hp);
            }
        }
    }
}

Por lo demas, me he creado un plugin propio donde a;ado unas cuantas torres y mobs ya que asi puedo intercambiar los plugins y hacerlos como si fueran mapas. Es super comodo y se intercambian con una simple linea de codigo. Asi que por ahora todo queda bastante guay y modularizado. Creo que lo siguiente si deberia ser ya los tests xDDD

El main queda asi:

fn main() {
    log4rs::init_file("config/log4rs.yaml", Default::default()).unwrap();

    info!("Initializing");

    App::build()
        .add_plugin(TypeRegistryPlugin::default())
        .add_plugin(CorePlugin::default())
        .add_plugin(ScheduleRunnerPlugin::default())
        .add_plugin(SimpleMapPlugin)
        .add_plugin(AttackPlugin)
        .run();
}
1