Compare commits

..

No commits in common. "33c96a93a5aa3b2659b27c586f5c0f05cc95368b" and "7854245a4b48e84fade0f3ea036467ad4907e990" have entirely different histories.

15 changed files with 308 additions and 414 deletions

14
TODO.md
View File

@ -1,10 +1,10 @@
## Specific Jobs ## Specific Jobs
- Start documenting
- Check for handle leaks - Check for handle leaks
- Rename and crtl-f comments - Rename and crtl-f comments
- gameobject and world - gameobject and world
- behavior and personality - behavior and personality
- ship (content) / ship (data) / ship (world) - ship (content) / ship (data) / ship (world)
- Fix ship controllers
- Don't allocate each frame - Don't allocate each frame
- UI: text arranger - UI: text arranger
@ -122,7 +122,7 @@
- Non-removable outfits - Non-removable outfits
- Space-converting outfits - Space-converting outfits
- Damage struct and debuffs - Damage struct and debuffs
- -
## Camera ## Camera
- Shake/wobble on heavy hits? - Shake/wobble on heavy hits?
@ -146,7 +146,7 @@
## Write and Document ## Write and Document
- Parallax - Parallax
- Starfield & tiling - Starfield & tiling
- How the game is drawn - How the game is drawn
- transforming game coordinates to screen - transforming game coordinates to screen
- how angles work - how angles work
- Computation flow - Computation flow
@ -176,4 +176,4 @@
- Lose some outfits, lose ship? Real risk for going out! (HK does this well) - Lose some outfits, lose ship? Real risk for going out! (HK does this well)
- Damage to ship subsystems - Damage to ship subsystems
- Soft and highly armored ship points - Soft and highly armored ship points
- Repair your own ship - Repair your own ship

View File

@ -104,7 +104,7 @@ pub struct Object {
/// This object's position, in game coordinates, /// This object's position, in game coordinates,
/// relative to the system's center (0, 0). /// relative to the system's center (0, 0).
pub pos: Point3<f32>, pub position: Point3<f32>,
/// This object's sprite's angle. /// This object's sprite's angle.
pub angle: Rad<f32>, pub angle: Rad<f32>,
@ -200,14 +200,13 @@ impl crate::Build for System {
objects.push(Object { objects.push(Object {
sprite: handle, sprite: handle,
pos: resolve_position(&system.object, &obj, cycle_detector) position: resolve_position(&system.object, &obj, cycle_detector)
.with_context(|| format!("In object {:#?}", label))?, .with_context(|| format!("In object {:#?}", label))?,
size: obj.size, size: obj.size,
angle: Deg(obj.angle.unwrap_or(0.0)).into(), angle: Deg(obj.angle.unwrap_or(0.0)).into(),
}); });
} }
objects.sort_by(|a, b| a.pos.z.total_cmp(&b.pos.z));
content.systems.push(Self { content.systems.push(Self {
name: system_name, name: system_name,
objects, objects,

View File

@ -45,29 +45,23 @@ impl Game {
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 1 })); s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 1 }));
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 2 })); s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 2 }));
let a = gamedata.create_ship( gamedata.create_ship(
&ct, &ct,
content::ShipHandle { index: 0 }, content::ShipHandle { index: 0 },
content::FactionHandle { index: 1 }, content::FactionHandle { index: 1 },
ShipPersonality::Dummy, ShipPersonality::Dummy,
&content::SystemHandle { index: 0 }, &content::SystemHandle { index: 0 },
); );
let s = gamedata.get_ship_mut(a).unwrap();
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 0 }));
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 1 }));
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 2 }));
let a = gamedata.create_ship( let a = gamedata.create_ship(
&ct, &ct,
content::ShipHandle { index: 0 }, content::ShipHandle { index: 0 },
content::FactionHandle { index: 0 }, content::FactionHandle { index: 1 },
ShipPersonality::Point, ShipPersonality::Point,
&content::SystemHandle { index: 0 }, &content::SystemHandle { index: 0 },
); );
let s = gamedata.get_ship_mut(a).unwrap(); let s = gamedata.get_ship_mut(a).unwrap();
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 0 })); s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 0 }));
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 1 }));
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 2 }));
let physics = World::new(&ct, &gamedata, content::SystemHandle { index: 0 }); let physics = World::new(&ct, &gamedata, content::SystemHandle { index: 0 });
@ -157,11 +151,10 @@ impl Game {
camera_zoom: self.camera.zoom, camera_zoom: self.camera.zoom,
current_time: self.start_instant.elapsed().as_secs_f32(), current_time: self.start_instant.elapsed().as_secs_f32(),
content: &self.content, content: &self.content,
world: &self.world, // TODO: maybe system should be stored here? world: &self.world,
particles: &mut self.new_particles, particles: &mut self.new_particles,
player_data: self.player, player_data: self.player,
data: &self.gamedata, data: &self.gamedata,
current_system: content::SystemHandle { index: 0 },
} }
} }
} }

View File

@ -7,6 +7,8 @@
mod gamedata; mod gamedata;
mod handles; mod handles;
pub mod ship; pub mod ship;
mod system;
pub use gamedata::*; pub use gamedata::*;
pub use handles::*; pub use handles::*;
pub use system::{System, SystemObject};

View File

@ -56,9 +56,7 @@ impl Ship {
/// Add an outfit to this ship /// Add an outfit to this ship
pub fn add_outfit(&mut self, o: &content::Outfit) -> super::OutfitAddResult { pub fn add_outfit(&mut self, o: &content::Outfit) -> super::OutfitAddResult {
let r = self.outfits.add(o); self.outfits.add(o)
self.shields = self.outfits.get_shield_strength();
return r;
} }
/// Remove an outfit from this ship /// Remove an outfit from this ship

View File

@ -0,0 +1,37 @@
use cgmath::{Point3, Rad};
use galactica_content as content;
// TODO: rework
pub struct System {
pub name: String,
pub bodies: Vec<SystemObject>,
}
impl System {
pub fn new(ct: &content::Content, handle: content::SystemHandle) -> Self {
let sys = ct.get_system(handle);
let mut s = System {
name: sys.name.clone(),
bodies: Vec::new(),
};
for o in &sys.objects {
s.bodies.push(SystemObject {
pos: o.position,
sprite: o.sprite,
size: o.size,
angle: o.angle,
});
}
return s;
}
}
pub struct SystemObject {
pub sprite: content::SpriteHandle,
pub pos: Point3<f32>,
pub size: f32,
pub angle: Rad<f32>,
}

View File

@ -19,7 +19,7 @@ impl GPUState {
let radar_size = 300.0; let radar_size = 300.0;
let hide_range = 0.85; let hide_range = 0.85;
let shrink_distance = 20.0; let shrink_distance = 20.0;
let system_object_scale = 1.0 / 600.0; //let system_object_scale = 1.0 / 600.0;
let ship_scale = 1.0 / 10.0; let ship_scale = 1.0 / 10.0;
let player_world_object = state.world.get_ship(state.player_data).unwrap(); let player_world_object = state.world.get_ship(state.player_data).unwrap();
@ -29,7 +29,7 @@ impl GPUState {
.unwrap(); .unwrap();
let player_position = util::rigidbody_position(player_body); let player_position = util::rigidbody_position(player_body);
let planet_sprite = state.content.get_sprite_handle("ui::planetblip"); //let planet_sprite = state.content.get_sprite_handle("ui::planetblip");
let ship_sprite = state.content.get_sprite_handle("ui::shipblip"); let ship_sprite = state.content.get_sprite_handle("ui::shipblip");
let arrow_sprite = state.content.get_sprite_handle("ui::centerarrow"); let arrow_sprite = state.content.get_sprite_handle("ui::centerarrow");
@ -54,9 +54,9 @@ impl GPUState {
); );
self.vertex_buffers.ui_counter += 1; self.vertex_buffers.ui_counter += 1;
/*
// Draw system objects // Draw system objects
let system = state.content.get_system(state.current_system); for o in &system.bodies {
for o in &system.objects {
let size = (o.size / o.pos.z) / (radar_range * system_object_scale); let size = (o.size / o.pos.z) / (radar_range * system_object_scale);
let p = Point2 { let p = Point2 {
x: o.pos.x, x: o.pos.x,
@ -72,35 +72,24 @@ impl GPUState {
// Don't draw super tiny sprites, they flicker // Don't draw super tiny sprites, they flicker
continue; continue;
} }
out.push(UiSprite {
// Enforce buffer limit sprite: planet_sprite,
if self.vertex_buffers.ui_counter as u64 pos: AnchoredUiPosition::NwC(
> galactica_constants::UI_SPRITE_INSTANCE_LIMIT Point2 {
{
// TODO: no panic, handle this better.
panic!("UI limit exceeded!")
}
// Push this object's instance
self.queue.write_buffer(
&self.vertex_buffers.ui.instances,
UiInstance::SIZE * self.vertex_buffers.ui_counter,
bytemuck::cast_slice(&[UiInstance {
anchor: PositionAnchor::NwC.to_int(),
position: (Point2 {
x: radar_size / 2.0 + 10.0, x: radar_size / 2.0 + 10.0,
y: radar_size / -2.0 - 10.0, y: radar_size / -2.0 - 10.0,
} + (d * (radar_size / 2.0))) } + (d * (radar_size / 2.0)),
.into(), ),
angle: o.angle.0, dimensions: Point2 {
size, x: planet_sprite.aspect,
color: [0.5, 0.5, 0.5, 1.0], y: 1.0,
sprite_index: planet_sprite.get_index(), } * size,
}]), angle: o.angle,
); color: Some([0.5, 0.5, 0.5, 1.0]),
self.vertex_buffers.ui_counter += 1; });
} }
} }
*/
// Draw ships // Draw ships
for (s, r) in state.world.iter_ship_body() { for (s, r) in state.world.iter_ship_body() {
@ -134,7 +123,6 @@ impl GPUState {
} + (d * (radar_size / 2.0)); } + (d * (radar_size / 2.0));
// Enforce buffer limit // Enforce buffer limit
// TODO: cleaner solution. don't do this everywhere.
if self.vertex_buffers.ui_counter as u64 if self.vertex_buffers.ui_counter as u64
> galactica_constants::UI_SPRITE_INSTANCE_LIMIT > galactica_constants::UI_SPRITE_INSTANCE_LIMIT
{ {

View File

@ -364,9 +364,13 @@ impl GPUState {
// Order matters, it determines what is drawn on top. // Order matters, it determines what is drawn on top.
// The order inside ships and projectiles doesn't matter, // The order inside ships and projectiles doesn't matter,
// but ships should always be under projectiles. // but ships should always be under projectiles.
self.world_push_system(state, (clip_ne, clip_sw)); for s in state.world.iter_ships() {
self.world_push_ship(state, (clip_ne, clip_sw)); self.world_push_ship(state, (clip_ne, clip_sw), &s);
self.world_push_projectile(state, (clip_ne, clip_sw)); }
for p in state.world.iter_projectiles() {
self.world_push_projectile(state, (clip_ne, clip_sw), &p);
}
self.hud_add_radar(state); self.hud_add_radar(state);
self.hud_add_status(state); self.hud_add_status(state);

View File

@ -2,7 +2,10 @@
use bytemuck; use bytemuck;
use cgmath::{EuclideanSpace, InnerSpace, Point2, Vector2}; use cgmath::{EuclideanSpace, InnerSpace, Point2, Vector2};
use galactica_world::util; use galactica_world::{
objects::{ProjectileWorldObject, ShipWorldObject},
util,
};
use crate::{ use crate::{
globaluniform::ObjectData, globaluniform::ObjectData,
@ -16,115 +19,114 @@ impl GPUState {
state: &RenderState, state: &RenderState,
// NE and SW corners of screen // NE and SW corners of screen
screen_clip: (Point2<f32>, Point2<f32>), screen_clip: (Point2<f32>, Point2<f32>),
s: &ShipWorldObject,
) { ) {
for s in state.world.iter_ships() { let r = state.world.get_rigid_body(s.rigid_body).unwrap();
let r = state.world.get_rigid_body(s.rigid_body).unwrap(); let ship_pos = util::rigidbody_position(&r);
let ship_pos = util::rigidbody_position(&r); let ship_rot = util::rigidbody_rotation(r);
let ship_rot = util::rigidbody_rotation(r); let ship_ang = -ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }); // TODO: inconsistent angles. Fix!
let ship_ang = -ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }); // TODO: inconsistent angles. Fix! let ship_cnt = state.content.get_ship(s.data_handle.content_handle());
let ship_cnt = state.content.get_ship(s.data_handle.content_handle());
// Position adjusted for parallax // Position adjusted for parallax
// TODO: adjust parallax for zoom? // TODO: adjust parallax for zoom?
// 1.0 is z-coordinate, which is constant for ships // 1.0 is z-coordinate, which is constant for ships
let pos: Point2<f32> = (ship_pos - state.camera_pos.to_vec()) / 1.0; let pos: Point2<f32> = (ship_pos - state.camera_pos.to_vec()) / 1.0;
// Game dimensions of this sprite post-scale. // Game dimensions of this sprite post-scale.
// Post-scale width or height, whichever is larger. // Post-scale width or height, whichever is larger.
// This is in game units. // This is in game units.
// //
// We take the maximum to account for rotated sprites. // We take the maximum to account for rotated sprites.
let m = (ship_cnt.size / 1.0) * ship_cnt.sprite.aspect.max(1.0); let m = (ship_cnt.size / 1.0) * ship_cnt.sprite.aspect.max(1.0);
// Don't draw sprites that are off the screen // Don't draw sprites that are off the screen
if pos.x < screen_clip.0.x - m if pos.x < screen_clip.0.x - m
|| pos.y > screen_clip.0.y + m || pos.y > screen_clip.0.y + m
|| pos.x > screen_clip.1.x + m || pos.x > screen_clip.1.x + m
|| pos.y < screen_clip.1.y - m || pos.y < screen_clip.1.y - m
{ {
continue; return;
} }
let idx = self.vertex_buffers.object_counter; let idx = self.vertex_buffers.object_counter;
// Write this object's location data // Write this object's location data
self.queue.write_buffer( self.queue.write_buffer(
&self.global_uniform.object_buffer, &self.global_uniform.object_buffer,
ObjectData::SIZE * idx as u64, ObjectData::SIZE * idx as u64,
bytemuck::cast_slice(&[ObjectData { bytemuck::cast_slice(&[ObjectData {
xpos: ship_pos.x, xpos: ship_pos.x,
ypos: ship_pos.y, ypos: ship_pos.y,
zpos: 1.0, zpos: 1.0,
angle: ship_ang.0, angle: ship_ang.0,
size: ship_cnt.size, size: ship_cnt.size,
parent: 0, parent: 0,
is_child: 0, is_child: 0,
_padding: Default::default(), _padding: Default::default(),
}]), }]),
); );
// Enforce buffer limit // Enforce buffer limit
if self.vertex_buffers.object_counter as u64 if self.vertex_buffers.object_counter as u64
> galactica_constants::OBJECT_SPRITE_INSTANCE_LIMIT > galactica_constants::OBJECT_SPRITE_INSTANCE_LIMIT
{ {
// TODO: no panic, handle this better. // TODO: no panic, handle this better.
panic!("Sprite limit exceeded!") panic!("Sprite limit exceeded!")
} }
// Push this object's instance // Push this object's instance
self.queue.write_buffer( self.queue.write_buffer(
&self.vertex_buffers.object.instances, &self.vertex_buffers.object.instances,
ObjectInstance::SIZE * self.vertex_buffers.object_counter, ObjectInstance::SIZE * self.vertex_buffers.object_counter,
bytemuck::cast_slice(&[ObjectInstance { bytemuck::cast_slice(&[ObjectInstance {
sprite_index: ship_cnt.sprite.get_index(), sprite_index: ship_cnt.sprite.get_index(),
object_index: idx as u32, object_index: idx as u32,
}]), }]),
); );
self.vertex_buffers.object_counter += 1; self.vertex_buffers.object_counter += 1;
// This will be None if this ship is dead. // This will be None if this ship is dead.
// (physics object stays around to complete the death animation) // (physics object stays around to complete the death animation)
// If that is the case, we're done, no flares to draw anyway! // If that is the case, we're done, no flares to draw anyway!
let ship = match state.data.get_ship(s.data_handle) { let ship = match state.data.get_ship(s.data_handle) {
None => continue, None => return,
Some(s) => s, Some(s) => s,
}; };
let flare = ship.get_outfits().get_flare_sprite(state.content); let flare = ship.get_outfits().get_flare_sprite(state.content);
if s.get_controls().thrust && flare.is_some() { if s.get_controls().thrust && flare.is_some() {
for engine_point in &ship_cnt.engines { for engine_point in &ship_cnt.engines {
self.queue.write_buffer( self.queue.write_buffer(
&self.global_uniform.object_buffer, &self.global_uniform.object_buffer,
ObjectData::SIZE * self.vertex_buffers.object_counter as u64, ObjectData::SIZE * self.vertex_buffers.object_counter as u64,
bytemuck::cast_slice(&[ObjectData { bytemuck::cast_slice(&[ObjectData {
xpos: engine_point.pos.x, xpos: engine_point.pos.x,
ypos: engine_point.pos.y - engine_point.size / 2.0, ypos: engine_point.pos.y - engine_point.size / 2.0,
zpos: 1.0, zpos: 1.0,
angle: 0.0, angle: 0.0,
size: engine_point.size, size: engine_point.size,
parent: idx as u32, parent: idx as u32,
is_child: 1, is_child: 1,
_padding: Default::default(), _padding: Default::default(),
}]), }]),
); );
// Enforce buffer limit // Enforce buffer limit
if self.vertex_buffers.object_counter as u64 if self.vertex_buffers.object_counter as u64
> galactica_constants::OBJECT_SPRITE_INSTANCE_LIMIT > galactica_constants::OBJECT_SPRITE_INSTANCE_LIMIT
{ {
// TODO: no panic, handle this better. // TODO: no panic, handle this better.
panic!("Sprite limit exceeded!") panic!("Sprite limit exceeded!")
}
self.queue.write_buffer(
&self.vertex_buffers.object.instances,
ObjectInstance::SIZE * self.vertex_buffers.object_counter,
bytemuck::cast_slice(&[ObjectInstance {
sprite_index: flare.unwrap().get_index(),
object_index: self.vertex_buffers.object_counter as u32,
}]),
);
self.vertex_buffers.object_counter += 1;
} }
self.queue.write_buffer(
&self.vertex_buffers.object.instances,
ObjectInstance::SIZE * self.vertex_buffers.object_counter,
bytemuck::cast_slice(&[ObjectInstance {
sprite_index: flare.unwrap().get_index(),
object_index: self.vertex_buffers.object_counter as u32,
}]),
);
self.vertex_buffers.object_counter += 1;
} }
} }
} }
@ -134,140 +136,69 @@ impl GPUState {
state: &RenderState, state: &RenderState,
// NE and SW corners of screen // NE and SW corners of screen
screen_clip: (Point2<f32>, Point2<f32>), screen_clip: (Point2<f32>, Point2<f32>),
p: &ProjectileWorldObject,
) { ) {
for p in state.world.iter_projectiles() { let r = state.world.get_rigid_body(p.rigid_body).unwrap();
let r = state.world.get_rigid_body(p.rigid_body).unwrap(); let proj_pos = util::rigidbody_position(&r);
let proj_pos = util::rigidbody_position(&r); let proj_rot = util::rigidbody_rotation(r);
let proj_rot = util::rigidbody_rotation(r); let proj_ang = -proj_rot.angle(Vector2 { x: 1.0, y: 0.0 });
let proj_ang = -proj_rot.angle(Vector2 { x: 1.0, y: 0.0 }); let proj_cnt = &p.content; // TODO: don't clone this?
let proj_cnt = &p.content; // TODO: don't clone this?
// Position adjusted for parallax // Position adjusted for parallax
// TODO: adjust parallax for zoom? // TODO: adjust parallax for zoom?
// 1.0 is z-coordinate, which is constant for ships // 1.0 is z-coordinate, which is constant for ships
let pos: Point2<f32> = (proj_pos - state.camera_pos.to_vec()) / 1.0; let pos: Point2<f32> = (proj_pos - state.camera_pos.to_vec()) / 1.0;
// Game dimensions of this sprite post-scale. // Game dimensions of this sprite post-scale.
// Post-scale width or height, whichever is larger. // Post-scale width or height, whichever is larger.
// This is in game units. // This is in game units.
// //
// We take the maximum to account for rotated sprites. // We take the maximum to account for rotated sprites.
let m = (proj_cnt.size / 1.0) * proj_cnt.sprite.aspect.max(1.0); let m = (proj_cnt.size / 1.0) * proj_cnt.sprite.aspect.max(1.0);
// Don't draw sprites that are off the screen // Don't draw sprites that are off the screen
if pos.x < screen_clip.0.x - m if pos.x < screen_clip.0.x - m
|| pos.y > screen_clip.0.y + m || pos.y > screen_clip.0.y + m
|| pos.x > screen_clip.1.x + m || pos.x > screen_clip.1.x + m
|| pos.y < screen_clip.1.y - m || pos.y < screen_clip.1.y - m
{ {
continue; return;
}
let idx = self.vertex_buffers.object_counter;
// Write this object's location data
self.queue.write_buffer(
&self.global_uniform.object_buffer,
ObjectData::SIZE * idx as u64,
bytemuck::cast_slice(&[ObjectData {
xpos: proj_pos.x,
ypos: proj_pos.y,
zpos: 1.0,
angle: proj_ang.0,
size: 0f32.max(proj_cnt.size + p.size_rng),
parent: 0,
is_child: 0,
_padding: Default::default(),
}]),
);
// Enforce buffer limit
if self.vertex_buffers.object_counter as u64
> galactica_constants::OBJECT_SPRITE_INSTANCE_LIMIT
{
// TODO: no panic, handle this better.
panic!("Sprite limit exceeded!")
}
// Push this object's instance
self.queue.write_buffer(
&self.vertex_buffers.object.instances,
ObjectInstance::SIZE * self.vertex_buffers.object_counter,
bytemuck::cast_slice(&[ObjectInstance {
sprite_index: proj_cnt.sprite.get_index(),
object_index: idx as u32,
}]),
);
self.vertex_buffers.object_counter += 1;
} }
}
pub(super) fn world_push_system( let idx = self.vertex_buffers.object_counter;
&mut self, // Write this object's location data
state: &RenderState, self.queue.write_buffer(
// NE and SW corners of screen &self.global_uniform.object_buffer,
screen_clip: (Point2<f32>, Point2<f32>), ObjectData::SIZE * idx as u64,
) { bytemuck::cast_slice(&[ObjectData {
let system = state.content.get_system(state.current_system); xpos: proj_pos.x,
ypos: proj_pos.y,
zpos: 1.0,
angle: proj_ang.0,
size: 0f32.max(proj_cnt.size + p.size_rng),
parent: 0,
is_child: 0,
_padding: Default::default(),
}]),
);
for o in &system.objects { // Enforce buffer limit
// Position adjusted for parallax if self.vertex_buffers.object_counter as u64
let pos: Point2<f32> = (Point2 { > galactica_constants::OBJECT_SPRITE_INSTANCE_LIMIT
x: o.pos.x, {
y: o.pos.y, // TODO: no panic, handle this better.
} - state.camera_pos.to_vec()) panic!("Sprite limit exceeded!")
/ o.pos.z;
// Game dimensions of this sprite post-scale.
// Post-scale width or height, whichever is larger.
// This is in game units.
//
// We take the maximum to account for rotated sprites.
let m = (o.size / o.pos.z) * o.sprite.aspect.max(1.0);
// Don't draw sprites that are off the screen
if pos.x < screen_clip.0.x - m
|| pos.y > screen_clip.0.y + m
|| pos.x > screen_clip.1.x + m
|| pos.y < screen_clip.1.y - m
{
continue;
}
let idx = self.vertex_buffers.object_counter;
// Write this object's location data
self.queue.write_buffer(
&self.global_uniform.object_buffer,
ObjectData::SIZE * idx as u64,
bytemuck::cast_slice(&[ObjectData {
xpos: o.pos.x,
ypos: o.pos.y,
zpos: o.pos.z,
angle: o.angle.0,
size: o.size,
parent: 0,
is_child: 0,
_padding: Default::default(),
}]),
);
// Enforce buffer limit
if self.vertex_buffers.object_counter as u64
> galactica_constants::OBJECT_SPRITE_INSTANCE_LIMIT
{
// TODO: no panic, handle this better.
panic!("Sprite limit exceeded!")
}
// Push this object's instance
self.queue.write_buffer(
&self.vertex_buffers.object.instances,
ObjectInstance::SIZE * self.vertex_buffers.object_counter,
bytemuck::cast_slice(&[ObjectInstance {
sprite_index: o.sprite.get_index(),
object_index: idx as u32,
}]),
);
self.vertex_buffers.object_counter += 1;
} }
// Push this object's instance
self.queue.write_buffer(
&self.vertex_buffers.object.instances,
ObjectInstance::SIZE * self.vertex_buffers.object_counter,
bytemuck::cast_slice(&[ObjectInstance {
sprite_index: proj_cnt.sprite.get_index(),
object_index: idx as u32,
}]),
);
self.vertex_buffers.object_counter += 1;
} }
} }

View File

@ -1,5 +1,5 @@
use cgmath::Point2; use cgmath::Point2;
use galactica_content::{Content, SystemHandle}; use galactica_content::Content;
use galactica_gameobject::{GameData, GameShipHandle}; use galactica_gameobject::{GameData, GameShipHandle};
use galactica_world::{ParticleBuilder, World}; use galactica_world::{ParticleBuilder, World};
@ -11,9 +11,6 @@ pub struct RenderState<'a> {
/// Player ship data /// Player ship data
pub player_data: GameShipHandle, pub player_data: GameShipHandle,
/// The system we're currently in
pub current_system: SystemHandle,
/// Height of screen, in world units /// Height of screen, in world units
pub camera_zoom: f32, pub camera_zoom: f32,

View File

@ -1,19 +1,12 @@
//! Various implementations of [`crate::ShipBehavior`] //! Various implementations of [`crate::ShipBehavior`]
mod null; mod null;
mod point; //mod point;
use std::collections::HashMap;
use galactica_gameobject::GameShipHandle;
pub use null::*; pub use null::*;
pub use point::Point; //pub use point::Point;
use rapier2d::dynamics::{RigidBodyHandle, RigidBodySet};
use crate::{ use crate::{objects::ShipControls, StepResources};
objects::{ShipControls, ShipWorldObject},
StepResources,
};
/// Main behavior trait. Any struct that implements this /// Main behavior trait. Any struct that implements this
/// may be used to control a ship. /// may be used to control a ship.
@ -21,12 +14,5 @@ pub trait ShipBehavior {
/// Update a ship's controls based on world state. /// Update a ship's controls based on world state.
/// This method does not return anything, it modifies /// This method does not return anything, it modifies
/// the ship's controls in-place. /// the ship's controls in-place.
fn update_controls( fn update_controls(&mut self, res: &StepResources) -> ShipControls;
&mut self,
res: &StepResources,
rigid_bodies: &RigidBodySet,
ships: &HashMap<GameShipHandle, ShipWorldObject>,
this_ship: RigidBodyHandle,
this_data: GameShipHandle,
) -> ShipControls;
} }

View File

@ -1,13 +1,5 @@
use std::collections::HashMap;
use galactica_gameobject::GameShipHandle;
use rapier2d::dynamics::{RigidBodyHandle, RigidBodySet};
use super::ShipBehavior; use super::ShipBehavior;
use crate::{ use crate::{objects::ShipControls, StepResources};
objects::{ShipControls, ShipWorldObject},
StepResources,
};
/// The Null behaviors is assigned to objects that are not controlled by the computer. /// The Null behaviors is assigned to objects that are not controlled by the computer.
/// Most notably, the player's ship has a Null behavior. /// Most notably, the player's ship has a Null behavior.
@ -21,14 +13,7 @@ impl Null {
} }
impl ShipBehavior for Null { impl ShipBehavior for Null {
fn update_controls( fn update_controls(&mut self, _res: &StepResources) -> ShipControls {
&mut self,
_res: &StepResources,
_rigid_bodies: &RigidBodySet,
_ships: &HashMap<GameShipHandle, ShipWorldObject>,
_this_ship: RigidBodyHandle,
_this_data: GameShipHandle,
) -> ShipControls {
ShipControls::new() ShipControls::new()
} }
} }

View File

@ -1,13 +1,9 @@
use cgmath::{Deg, InnerSpace}; use cgmath::{Deg, InnerSpace};
use rapier2d::dynamics::{RigidBodyHandle, RigidBodySet}; use galactica_gameobject::GameData;
use std::collections::HashMap;
use crate::{
objects::{ShipControls, ShipWorldObject},
util, StepResources,
};
use galactica_content as content; use galactica_content as content;
use galactica_gameobject::GameShipHandle;
use crate::World;
use super::ShipBehavior; use super::ShipBehavior;
@ -25,47 +21,43 @@ impl Point {
impl ShipBehavior for Point { impl ShipBehavior for Point {
fn update_controls( fn update_controls(
&mut self, &mut self,
res: &StepResources, physics: &mut World,
rigid_bodies: &RigidBodySet, content: &content::Content,
ships: &HashMap<GameShipHandle, ShipWorldObject>, data: &GameData,
this_ship: RigidBodyHandle, ) {
this_data: GameShipHandle, // Turn off all controls
) -> ShipControls { let s = physics.get_ship_mut(&self.handle).unwrap();
let mut controls = ShipControls::new(); s.controls.left = false;
s.controls.right = false;
s.controls.guns = false;
s.controls.thrust = false;
let this_rigidbody = rigid_bodies.get(this_ship).unwrap(); let (my_s, my_r) = physics.get_ship_body(self.handle).unwrap();
let my_data = res.dt.get_ship(this_data).unwrap(); let my_data = data.get_ship(my_s.data_handle).unwrap();
let my_position = util::rigidbody_position(this_rigidbody); let my_position = util::rigidbody_position(my_r);
let my_rotation = util::rigidbody_rotation(this_rigidbody); let my_rotation = util::rigidbody_rotation(my_r);
let my_angvel = this_rigidbody.angvel(); let my_angvel = my_r.angvel();
let my_faction = res.ct.get_faction(my_data.get_faction()); let my_faction = content.get_faction(my_data.get_faction());
// Iterate all possible targets // Iterate all possible targets
let mut hostile_ships = ships let mut it = physics
.values() .iter_ship_body()
.filter(|s| { .filter(|(s, _)| {
let data = res.dt.get_ship(s.data_handle); let data = data.get_ship(s.data_handle).unwrap();
if let Some(data) = data { match my_faction.relationships.get(&data.get_faction()).unwrap() {
match my_faction.relationships.get(&data.get_faction()).unwrap() { content::Relationship::Hostile => true,
content::Relationship::Hostile => true, _ => false,
_ => false,
}
} else {
// This check is necessary---we don't want to target (or panic on)
// ships that don't have data (and are thus playing their collapse animation)
false
} }
}) })
.map(|s| rigid_bodies.get(s.rigid_body).unwrap()); .map(|(_, r)| r);
// Find the closest target // Find the closest target
let mut closest_enemy_position = match hostile_ships.next() { let mut closest_enemy_position = match it.next() {
Some(c) => util::rigidbody_position(c), Some(c) => util::rigidbody_position(c),
None => return controls, // Do nothing if no targets are available None => return, // Do nothing if no targets are available
}; };
let mut d = (my_position - closest_enemy_position).magnitude(); let mut d = (my_position - closest_enemy_position).magnitude();
for r in hostile_ships { for r in it {
let p = util::rigidbody_position(r); let p = util::rigidbody_position(r);
let new_d = (my_position - p).magnitude(); let new_d = (my_position - p).magnitude();
if new_d < d { if new_d < d {
@ -78,13 +70,17 @@ impl ShipBehavior for Point {
.angle(closest_enemy_position - my_position) .angle(closest_enemy_position - my_position)
.into(); .into();
let s = physics.get_ship_mut(&self.handle).unwrap();
s.controls.left = false;
s.controls.right = false;
if angle_delta < Deg(0.0) && my_angvel > -0.3 { if angle_delta < Deg(0.0) && my_angvel > -0.3 {
controls.right = true; s.controls.right = true;
} else if angle_delta > Deg(0.0) && my_angvel < 0.3 { } else if angle_delta > Deg(0.0) && my_angvel < 0.3 {
controls.left = true; s.controls.left = true;
} }
controls.guns = true; s.controls.guns = true;
return controls; s.controls.thrust = false;
} }
} }

View File

@ -1,6 +1,7 @@
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2, Zero}; use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2, Zero};
use content::{FactionHandle, ShipHandle}; use content::{FactionHandle, ShipHandle};
use nalgebra::{point, vector}; use nalgebra::{point, vector};
use object::GameShipHandle; use object::GameShipHandle;
use rand::{rngs::ThreadRng, Rng}; use rand::{rngs::ThreadRng, Rng};
use rapier2d::{ use rapier2d::{
@ -8,7 +9,7 @@ use rapier2d::{
geometry::{Collider, ColliderHandle}, geometry::{Collider, ColliderHandle},
}; };
use crate::{util, ParticleBuilder, StepResources}; use crate::{behavior::ShipBehavior, util, ParticleBuilder, StepResources};
use galactica_content as content; use galactica_content as content;
use galactica_gameobject as object; use galactica_gameobject as object;
@ -40,7 +41,6 @@ impl ShipControls {
} }
} }
#[derive(Debug)]
struct ShipCollapseSequence { struct ShipCollapseSequence {
total_length: f32, total_length: f32,
time_elapsed: f32, time_elapsed: f32,
@ -181,7 +181,6 @@ impl ShipCollapseSequence {
} }
/// A ship instance in the physics system /// A ship instance in the physics system
#[derive(Debug)]
pub struct ShipWorldObject { pub struct ShipWorldObject {
/// This ship's physics handle /// This ship's physics handle
pub rigid_body: RigidBodyHandle, pub rigid_body: RigidBodyHandle,
@ -195,6 +194,9 @@ pub struct ShipWorldObject {
/// This ship's controls /// This ship's controls
pub(crate) controls: ShipControls, pub(crate) controls: ShipControls,
/// This ship's behavior
behavior: Box<dyn ShipBehavior>,
/// This ship's collapse sequence /// This ship's collapse sequence
collapse_sequence: ShipCollapseSequence, collapse_sequence: ShipCollapseSequence,
@ -207,9 +209,10 @@ pub struct ShipWorldObject {
impl ShipWorldObject { impl ShipWorldObject {
/// Make a new ship /// Make a new ship
pub(crate) fn new( pub fn new(
ct: &content::Content, ct: &content::Content,
data_handle: GameShipHandle, data_handle: GameShipHandle,
behavior: Box<dyn ShipBehavior>,
faction: FactionHandle, faction: FactionHandle,
rigid_body: RigidBodyHandle, rigid_body: RigidBodyHandle,
collider: ColliderHandle, collider: ColliderHandle,
@ -219,12 +222,18 @@ impl ShipWorldObject {
rigid_body, rigid_body,
collider, collider,
data_handle, data_handle,
behavior,
controls: ShipControls::new(), controls: ShipControls::new(),
faction, faction,
collapse_sequence: ShipCollapseSequence::new(ship_content.collapse.length), collapse_sequence: ShipCollapseSequence::new(ship_content.collapse.length),
} }
} }
/// Compute this ship's controls using its behavior
pub fn update_controls(&mut self, res: &StepResources) {
self.controls = self.behavior.update_controls(res);
}
/// If this is true, remove this ship from the physics system. /// If this is true, remove this ship from the physics system.
pub fn should_be_removed(&self) -> bool { pub fn should_be_removed(&self) -> bool {
self.collapse_sequence.is_done() self.collapse_sequence.is_done()

View File

@ -12,8 +12,7 @@ use rapier2d::{
use std::{collections::HashMap, f32::consts::PI}; use std::{collections::HashMap, f32::consts::PI};
use crate::{ use crate::{
behavior::{self, ShipBehavior}, behavior, objects,
objects,
objects::{ProjectileWorldObject, ShipWorldObject}, objects::{ProjectileWorldObject, ShipWorldObject},
util, util,
wrapper::Wrapper, wrapper::Wrapper,
@ -30,7 +29,6 @@ pub struct World {
wrapper: Wrapper, wrapper: Wrapper,
projectiles: HashMap<ColliderHandle, objects::ProjectileWorldObject>, projectiles: HashMap<ColliderHandle, objects::ProjectileWorldObject>,
ships: HashMap<GameShipHandle, objects::ShipWorldObject>, ships: HashMap<GameShipHandle, objects::ShipWorldObject>,
ship_behaviors: HashMap<GameShipHandle, Box<dyn ShipBehavior>>,
collider_ship_table: HashMap<ColliderHandle, GameShipHandle>, collider_ship_table: HashMap<ColliderHandle, GameShipHandle>,
collision_handler: ChannelEventCollector, collision_handler: ChannelEventCollector,
@ -81,7 +79,6 @@ impl<'a> World {
true, true,
); );
let h = self.collider_ship_table.remove(&s.collider).unwrap(); let h = self.collider_ship_table.remove(&s.collider).unwrap();
self.ship_behaviors.remove(&h);
self.ships.remove(&h); self.ships.remove(&h);
} }
@ -180,7 +177,6 @@ impl World {
wrapper: Wrapper::new(), wrapper: Wrapper::new(),
projectiles: HashMap::new(), projectiles: HashMap::new(),
ships: HashMap::new(), ships: HashMap::new(),
ship_behaviors: HashMap::new(),
collider_ship_table: HashMap::new(), collider_ship_table: HashMap::new(),
collision_handler: ChannelEventCollector::new(collision_send, contact_force_send), collision_handler: ChannelEventCollector::new(collision_send, contact_force_send),
collision_queue, collision_queue,
@ -231,11 +227,16 @@ impl World {
); );
self.collider_ship_table.insert(c, ship.get_handle()); self.collider_ship_table.insert(c, ship.get_handle());
self.ship_behaviors
.insert(ship.get_handle(), Box::new(behavior::Point::new()));
self.ships.insert( self.ships.insert(
ship.get_handle(), ship.get_handle(),
objects::ShipWorldObject::new(ct, ship.get_handle(), ship.get_faction(), r, c), objects::ShipWorldObject::new(
ct,
ship.get_handle(),
Box::new(behavior::Null::new()),
ship.get_faction(),
r,
c,
),
); );
} }
@ -244,57 +245,25 @@ impl World {
/// - applies ship controls /// - applies ship controls
/// - creates projectiles /// - creates projectiles
fn step_ships(&mut self, res: &mut StepResources) { fn step_ships(&mut self, res: &mut StepResources) {
// We can't apply these right away since self is borrowed
// by the iterator
// TODO: don't allocate!
let mut projectiles = Vec::new(); let mut projectiles = Vec::new();
let mut to_remove = Vec::new(); let mut to_remove = Vec::new();
for (_, handle) in &self.collider_ship_table { for (_, ship_object) in &mut self.ships {
// Borrow immutably for now...
// (required to compute controls)
let ship_object = self.ships.get(handle).unwrap();
if ship_object.should_be_removed() { if ship_object.should_be_removed() {
to_remove.push(ship_object.collider); to_remove.push(ship_object.collider);
continue; continue;
} }
// Short-circuit continue if this ship isn't in game data let rigid_body = &mut self.wrapper.rigid_body_set[ship_object.rigid_body];
// (which means it's playing a collapse sequence) let collider = &mut self.wrapper.collider_set[ship_object.collider];
if res.dt.get_ship(*handle).is_none() {
let ship_object = self.ships.get_mut(handle).unwrap();
ship_object.step(
res,
&mut self.wrapper.rigid_body_set[ship_object.rigid_body],
&mut self.wrapper.collider_set[ship_object.collider],
);
continue;
}
// Compute new controls
let controls;
if ship_object.data_handle == res.player { if ship_object.data_handle == res.player {
controls = res.player_controls.clone(); ship_object.controls = res.player_controls.clone();
} else { } else {
let b = self.ship_behaviors.get_mut(handle).unwrap(); ship_object.update_controls(&res);
controls = b.update_controls(
&res,
&self.wrapper.rigid_body_set,
&self.ships,
ship_object.rigid_body,
*handle,
);
} }
// Now re-borrow mutably to apply changes // TODO: unified step info struct
let ship_object = self.ships.get_mut(handle).unwrap(); ship_object.step(res, rigid_body, collider);
ship_object.controls = controls;
ship_object.step(
res,
&mut self.wrapper.rigid_body_set[ship_object.rigid_body],
&mut self.wrapper.collider_set[ship_object.collider],
);
// If we're firing, try to fire each gun
if ship_object.controls.guns { if ship_object.controls.guns {
let ship_data = res.dt.get_ship_mut(ship_object.data_handle).unwrap(); let ship_data = res.dt.get_ship_mut(ship_object.data_handle).unwrap();