1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
/*
* librePvZ: game logic implementation.
* Copyright (c) 2022 Ruifeng Xie
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
//! Kinematics for projectiles and other movable entities.
//!
//! All the movable entities live in a right-handed pseudo-3D world. The XY plane lies on the
//! ground, with the X axis points right, and the Y axis points up. The Z axis is for height, and
//! coincides with Y axis on the viewport. This coordinate system has the following benefits:
//! - it easily translates to the viewport coordinate (`x <- x`, `y <- y + z`);
//! - it avoids false collision (it distinguishes between "at the back" and "high in the sky");
//! - it allows objects to correctly drop their shadows;
//!
//! All entities have a planar collide box in the 2D world. During collision detection, the collide
//! boxes are laid out parallel to the XZ plane, and then pushed along the Y axis to form a cuboid.
use bevy::prelude::*;
use bevy::transform::TransformSystem;
use crate::animation::transform::Transform2D;
/// Kinematics plugin for all movable entities.
#[derive(Default, Copy, Clone)]
#[allow(missing_debug_implementations)]
pub struct KinematicsPlugin;
impl Plugin for KinematicsPlugin {
fn build(&self, app: &mut App) {
app
.configure_sets(PostUpdate, (
KineticsSystem::Acceleration,
KineticsSystem::Movement,
KineticsSystem::CoordinateTranslation
.before(TransformSystem::TransformPropagate),
).chain())
.add_systems(Update, coordinate_translation_system.in_set(KineticsSystem::CoordinateTranslation))
.add_systems(Update, movement_system.in_set(KineticsSystem::Movement))
.add_systems(Update, acceleration_system.in_set(KineticsSystem::Acceleration));
}
}
/// System label for kinetics systems.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, SystemSet)]
pub enum KineticsSystem {
/// Update velocity from acceleration.
Acceleration,
/// Update position from velocity.
Movement,
/// Coordinate translation from pseudo 3D to 2D.
CoordinateTranslation,
}
/// Position.
#[derive(Debug, Copy, Clone, Component)]
pub struct Position(pub Vec3);
/// Coordinate translation from pseudo 3D to 2D.
#[allow(clippy::type_complexity)]
pub fn coordinate_translation_system(mut objects: Query<(&Position, &mut Transform2D), Changed<Position>>) {
for (pos, mut trans) in objects.iter_mut() {
trans.translation.x = pos.0.x;
trans.translation.y = pos.0.y + pos.0.z;
}
}
/// Velocity (in length per second).
#[derive(Debug, Copy, Clone, Component)]
pub struct Velocity(pub Vec3);
/// Update position from velocity.
pub fn movement_system(time: Res<Time>, mut objects: Query<(&Velocity, &mut Position)>) {
for (vel, mut pos) in objects.iter_mut() {
pos.0 += time.delta_seconds() * vel.0;
}
}
/// Acceleration (in length per square second).
#[derive(Debug, Copy, Clone, Component)]
pub struct Acceleration(pub Vec3);
/// Update velocity from acceleration.
pub fn acceleration_system(time: Res<Time>, mut objects: Query<(&Acceleration, &mut Velocity)>) {
for (acc, mut vel) in objects.iter_mut() {
vel.0 += time.delta_seconds() * acc.0;
}
}