Moved ship behaviors to world

master
Mark 2024-01-09 11:36:39 -08:00
parent 8ec3ece500
commit 85da817ed6
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
14 changed files with 185 additions and 245 deletions

11
Cargo.lock generated
View File

@ -579,7 +579,6 @@ version = "0.0.0"
dependencies = [
"anyhow",
"cgmath",
"galactica-behavior",
"galactica-constants",
"galactica-content",
"galactica-gameobject",
@ -590,15 +589,6 @@ dependencies = [
"winit",
]
[[package]]
name = "galactica-behavior"
version = "0.0.0"
dependencies = [
"cgmath",
"galactica-content",
"galactica-world",
]
[[package]]
name = "galactica-constants"
version = "0.0.0"
@ -647,6 +637,7 @@ dependencies = [
"cgmath",
"galactica-constants",
"galactica-content",
"galactica-gameobject",
"galactica-packer",
"galactica-world",
"image",

View File

@ -46,7 +46,6 @@ galactica-constants = { path = "crates/constants" }
galactica-content = { path = "crates/content" }
galactica-render = { path = "crates/render" }
galactica-world = { path = "crates/world" }
galactica-behavior = { path = "crates/behavior" }
galactica-gameobject = { path = "crates/gameobject" }
galactica-packer = { path = "crates/packer" }
galactica = { path = "crates/galactica" }

View File

@ -1,22 +0,0 @@
[package]
name = "galactica-behavior"
description = "AI behaviors for Galactica"
categories = { workspace = true }
keywords = { workspace = true }
version = { workspace = true }
rust-version = { workspace = true }
authors = { workspace = true }
edition = { workspace = true }
homepage = { workspace = true }
repository = { workspace = true }
license = { workspace = true }
documentation = { workspace = true }
readme = { workspace = true }
[lints]
workspace = true
[dependencies]
galactica-content = { workspace = true }
galactica-world = { workspace = true }
cgmath = { workspace = true }

View File

@ -1,23 +0,0 @@
use crate::ShipBehavior;
use galactica_content as content;
use galactica_world::{ShipPhysicsHandle, World};
/// Dummy ship behavior.
/// Does nothing.
pub struct Dummy {
_handle: ShipPhysicsHandle,
}
impl Dummy {
/// Create a new ship controller
pub fn new(handle: ShipPhysicsHandle) -> Self {
Self { _handle: handle }
}
}
impl ShipBehavior for Dummy {
fn update_controls(&mut self, _physics: &mut World, _content: &content::Content) {}
fn get_handle(&self) -> ShipPhysicsHandle {
return self._handle;
}
}

View File

@ -1,9 +0,0 @@
//! Various implementations of [`crate::ShipBehavior`]
mod dummy;
mod player;
mod point;
pub use dummy::Dummy;
pub use player::Player;
pub use point::Point;

View File

@ -1,48 +0,0 @@
use crate::ShipBehavior;
use galactica_content as content;
use galactica_world::{ShipPhysicsHandle, World};
/// Player ship behavior.
/// Controls a ship using controller input
pub struct Player {
handle: ShipPhysicsHandle,
/// Turn left
pub key_left: bool,
/// Turn right
pub key_right: bool,
/// Fire guns
pub key_guns: bool,
/// Foward thrust
pub key_thrust: bool,
}
impl Player {
/// Make a new ship controller
pub fn new(handle: ShipPhysicsHandle) -> Self {
Self {
handle,
key_left: false,
key_right: false,
key_guns: false,
key_thrust: false,
}
}
}
impl ShipBehavior for Player {
fn update_controls(&mut self, physics: &mut World, _content: &content::Content) {
let s = physics.get_ship_mut(&self.handle).unwrap();
s.controls.left = self.key_left;
s.controls.right = self.key_right;
s.controls.guns = self.key_guns;
s.controls.thrust = self.key_thrust;
}
fn get_handle(&self) -> ShipPhysicsHandle {
return self.handle;
}
}

View File

@ -1,21 +0,0 @@
#![warn(missing_docs)]
//! Computer-controlled ship behaviors
pub mod behavior;
use galactica_content as content;
use galactica_world::{ShipPhysicsHandle, World};
/// Main behavior trait. Any struct that implements this
/// may be used to control a ship.
pub trait ShipBehavior
where
Self: Send,
{
/// Update a ship's controls based on world state
fn update_controls(&mut self, physics: &mut World, content: &content::Content);
/// Get the ship this behavior is attached to
fn get_handle(&self) -> ShipPhysicsHandle;
}

View File

@ -25,7 +25,6 @@ galactica-content = { workspace = true }
galactica-render = { workspace = true }
galactica-constants = { workspace = true }
galactica-world = { workspace = true }
galactica-behavior = { workspace = true }
galactica-gameobject = { workspace = true }
winit = { workspace = true }

View File

@ -0,0 +1,18 @@
//! Various implementations of [`crate::ShipBehavior`]
mod null;
//mod point;
pub use null::*;
//pub use point::Point;
use crate::{objects::ShipControls, StepResources};
/// Main behavior trait. Any struct that implements this
/// may be used to control a ship.
pub trait ShipBehavior {
/// Update a ship's controls based on world state.
/// This method does not return anything, it modifies
/// the ship's controls in-place.
fn update_controls(&mut self, res: &StepResources) -> ShipControls;
}

View File

@ -0,0 +1,19 @@
use super::ShipBehavior;
use crate::{objects::ShipControls, StepResources};
/// The Null behaviors is assigned to objects that are not controlled by the computer.
/// Most notably, the player's ship has a Null behavior.
pub struct Null {}
impl Null {
/// Create a new ship controller
pub fn new() -> Self {
Self {}
}
}
impl ShipBehavior for Null {
fn update_controls(&mut self, _res: &StepResources) -> ShipControls {
ShipControls::new()
}
}

View File

@ -1,24 +1,30 @@
use cgmath::{Deg, InnerSpace};
use galactica_gameobject::GameData;
use crate::ShipBehavior;
use galactica_content as content;
use galactica_world::{util, ShipPhysicsHandle, World};
use crate::World;
use super::ShipBehavior;
/// "Point" ship behavior.
/// Point and shoot towards the nearest enemy.
pub struct Point {
handle: ShipPhysicsHandle,
}
pub struct Point {}
impl Point {
/// Create a new ship controller
pub fn new(handle: ShipPhysicsHandle) -> Self {
Self { handle }
pub fn new() -> Self {
Self {}
}
}
impl ShipBehavior for Point {
fn update_controls(&mut self, physics: &mut World, content: &content::Content) {
fn update_controls(
&mut self,
physics: &mut World,
content: &content::Content,
data: &GameData,
) {
// Turn off all controls
let s = physics.get_ship_mut(&self.handle).unwrap();
s.controls.left = false;
@ -27,20 +33,22 @@ impl ShipBehavior for Point {
s.controls.thrust = false;
let (my_s, my_r) = physics.get_ship_body(self.handle).unwrap();
let my_data = data.get_ship(my_s.data_handle).unwrap();
let my_position = util::rigidbody_position(my_r);
let my_rotation = util::rigidbody_rotation(my_r);
let my_angvel = my_r.angvel();
let my_faction = content.get_faction(my_s.ship.faction);
let my_faction = content.get_faction(my_data.get_faction());
// Iterate all possible targets
let mut it = physics
.iter_ship_body()
.filter(
|(s, _)| match my_faction.relationships.get(&s.ship.faction).unwrap() {
.filter(|(s, _)| {
let data = data.get_ship(s.data_handle).unwrap();
match my_faction.relationships.get(&data.get_faction()).unwrap() {
content::Relationship::Hostile => true,
_ => false,
},
)
}
})
.map(|(_, r)| r);
// Find the closest target
@ -75,8 +83,4 @@ impl ShipBehavior for Point {
s.controls.guns = true;
s.controls.thrust = false;
}
fn get_handle(&self) -> ShipPhysicsHandle {
return self.handle;
}
}

View File

@ -3,102 +3,14 @@
//! This module keeps track of the visible world.
//! Ships, projectiles, collisions, etc.
pub mod behavior;
pub mod objects;
mod particlebuilder;
mod stepresources;
pub mod util;
mod world;
mod wrapper;
use cgmath::{Matrix2, Point2, Rad, Vector2};
use galactica_content as content;
use rand::Rng;
pub use particlebuilder::*;
pub use stepresources::*;
pub use world::World;
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
/// A lightweight handle for a specific ship in our physics system
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct ShipPhysicsHandle(pub RigidBodyHandle, ColliderHandle);
/// Instructions to create a new particle
pub struct ParticleBuilder {
/// The sprite to use for this particle
pub sprite: content::SpriteHandle,
/// This object's center, in world coordinates.
pub pos: Point2<f32>,
/// This particle's velocity, in world coordinates
pub velocity: Vector2<f32>,
/// This particle's angle
pub angle: Rad<f32>,
/// This particle's angular velocity (rad/sec)
pub angvel: Rad<f32>,
/// This particle's lifetime, in seconds
pub lifetime: f32,
/// The size of this particle,
/// given as height in world units.
pub size: f32,
/// Fade this particle out over this many seconds as it expires
pub fade: f32,
}
impl ParticleBuilder {
/// Create a ParticleBuilder from an Effect
pub fn from_content(
effect: &content::Effect,
pos: Point2<f32>,
parent_angle: Rad<f32>,
parent_velocity: Vector2<f32>,
target_velocity: Vector2<f32>,
) -> Self {
let mut rng = rand::thread_rng();
let velocity = {
let a =
rng.gen_range(-effect.velocity_scale_parent_rng..=effect.velocity_scale_parent_rng);
let b =
rng.gen_range(-effect.velocity_scale_target_rng..=effect.velocity_scale_target_rng);
let velocity = ((effect.velocity_scale_parent + a) * parent_velocity)
+ ((effect.velocity_scale_target + b) * target_velocity);
Matrix2::from_angle(Rad(
rng.gen_range(-effect.direction_rng..=effect.direction_rng)
)) * velocity
};
// Rad has odd behavior when its angle is zero, so we need extra checks here
let angvel = if effect.angvel_rng == 0.0 {
effect.angvel
} else {
Rad(effect.angvel.0 + rng.gen_range(-effect.angvel_rng..=effect.angvel_rng))
};
let angle = if effect.angle_rng == 0.0 {
parent_angle + effect.angle
} else {
parent_angle + effect.angle + Rad(rng.gen_range(-effect.angle_rng..=effect.angle_rng))
};
ParticleBuilder {
sprite: effect.sprite,
pos,
velocity,
angle,
angvel,
lifetime: 0f32
.max(effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng)),
// Make sure size isn't negative. This check should be on EVERY rng!
size: 0f32.max(effect.size + rng.gen_range(-effect.size_rng..=effect.size_rng)),
fade: 0f32.max(effect.fade + rng.gen_range(-effect.fade_rng..=effect.fade_rng)),
}
}
}

View File

@ -0,0 +1,88 @@
use cgmath::{Matrix2, Point2, Rad, Vector2};
use galactica_content as content;
use rand::Rng;
/// Instructions to create a new particle
#[derive(Debug)]
pub struct ParticleBuilder {
/// The sprite to use for this particle
pub sprite: content::SpriteHandle,
/// This object's center, in world coordinates.
pub pos: Point2<f32>,
/// This particle's velocity, in world coordinates
pub velocity: Vector2<f32>,
/// This particle's angle
pub angle: Rad<f32>,
/// This particle's angular velocity (rad/sec)
pub angvel: Rad<f32>,
/// This particle's lifetime, in seconds
pub lifetime: f32,
/// The size of this particle,
/// given as height in world units.
pub size: f32,
/// Fade this particle out over this many seconds as it expires
pub fade: f32,
}
impl ParticleBuilder {
/// Create a ParticleBuilder from an Effect
pub fn from_content(
effect: &content::Effect,
pos: Point2<f32>,
parent_angle: Rad<f32>,
parent_velocity: Vector2<f32>,
target_velocity: Vector2<f32>,
) -> Self {
let mut rng = rand::thread_rng();
let velocity = {
let a =
rng.gen_range(-effect.velocity_scale_parent_rng..=effect.velocity_scale_parent_rng);
let b =
rng.gen_range(-effect.velocity_scale_target_rng..=effect.velocity_scale_target_rng);
let velocity = ((effect.velocity_scale_parent + a) * parent_velocity)
+ ((effect.velocity_scale_target + b) * target_velocity);
Matrix2::from_angle(Rad(
rng.gen_range(-effect.direction_rng..=effect.direction_rng)
)) * velocity
};
// Rad has odd behavior when its angle is zero, so we need extra checks here
let angvel = if effect.angvel_rng == 0.0 {
effect.angvel
} else {
Rad(effect.angvel.0 + rng.gen_range(-effect.angvel_rng..=effect.angvel_rng))
};
let angle = if effect.angle_rng == 0.0 {
parent_angle + effect.angle
} else {
parent_angle + effect.angle + Rad(rng.gen_range(-effect.angle_rng..=effect.angle_rng))
};
ParticleBuilder {
sprite: effect.sprite,
pos,
velocity,
angle,
angvel,
lifetime: 0f32
.max(effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng)),
// Make sure size isn't negative. This check should be on EVERY rng!
size: 0f32.max(effect.size + rng.gen_range(-effect.size_rng..=effect.size_rng)),
fade: 0f32.max(effect.fade + rng.gen_range(-effect.fade_rng..=effect.fade_rng)),
}
}
}

View File

@ -0,0 +1,33 @@
use crate::{objects::ShipControls, ParticleBuilder};
use cgmath::Point2;
use galactica_content::Content;
use galactica_gameobject::{GameData, GameShipHandle};
/// External resources we need to compute time steps
#[derive(Debug)]
pub struct StepResources<'a> {
/// Game content
pub ct: &'a Content,
/// Game data
pub dt: &'a mut GameData,
/// Length of time step
pub t: f32,
/// Particles to create
pub particles: &'a mut Vec<ParticleBuilder>,
/// Player inputs
pub player_controls: ShipControls,
/// The ship that the player controls
pub player: GameShipHandle,
}
/// Return values after computing time steps
#[derive(Debug)]
pub struct StepOutput {
/// The player's position in world coordinates
pub player_position: Point2<f32>,
}