Compare commits

..

3 Commits

Author SHA1 Message Date
Mark c20bd5d7a4
Minor renames 2024-01-02 19:14:13 -08:00
Mark 0e38fdb21e
Added particle foundation 2024-01-02 19:11:18 -08:00
Mark e5a96621a4
Added blaster particle 2024-01-02 18:54:34 -08:00
25 changed files with 315 additions and 52 deletions

20
Cargo.lock generated
View File

@ -580,11 +580,11 @@ dependencies = [
"anyhow", "anyhow",
"cgmath", "cgmath",
"crossbeam", "crossbeam",
"galactica-behavior",
"galactica-constants", "galactica-constants",
"galactica-content", "galactica-content",
"galactica-gameobject", "galactica-gameobject",
"galactica-render", "galactica-render",
"galactica-shipbehavior",
"galactica-ui", "galactica-ui",
"galactica-world", "galactica-world",
"image", "image",
@ -597,6 +597,15 @@ dependencies = [
"winit", "winit",
] ]
[[package]]
name = "galactica-behavior"
version = "0.0.0"
dependencies = [
"cgmath",
"galactica-content",
"galactica-world",
]
[[package]] [[package]]
name = "galactica-constants" name = "galactica-constants"
version = "0.0.0" version = "0.0.0"
@ -642,15 +651,6 @@ dependencies = [
"winit", "winit",
] ]
[[package]]
name = "galactica-shipbehavior"
version = "0.0.0"
dependencies = [
"cgmath",
"galactica-content",
"galactica-world",
]
[[package]] [[package]]
name = "galactica-ui" name = "galactica-ui"
version = "0.0.0" version = "0.0.0"

View File

@ -33,7 +33,7 @@ members = [
"crates/render", "crates/render",
"crates/constants", "crates/constants",
"crates/world", "crates/world",
"crates/shipbehavior", "crates/behavior",
"crates/gameobject", "crates/gameobject",
"crates/ui", "crates/ui",
] ]
@ -45,7 +45,7 @@ galactica-content = { path = "crates/content" }
galactica-render = { path = "crates/render" } galactica-render = { path = "crates/render" }
galactica-constants = { path = "crates/constants" } galactica-constants = { path = "crates/constants" }
galactica-world = { path = "crates/world" } galactica-world = { path = "crates/world" }
galactica-shipbehavior = { path = "crates/shipbehavior" } galactica-behavior = { path = "crates/behavior" }
galactica-gameobject = { path = "crates/gameobject" } galactica-gameobject = { path = "crates/gameobject" }
galactica-ui = { path = "crates/ui" } galactica-ui = { path = "crates/ui" }

View File

@ -1,8 +1,9 @@
## Specific Jobs ## Specific Jobs
- Finish particles
- Projectile colliders & particles
- UI: health, shield, fuel, heat, energy bars - UI: health, shield, fuel, heat, energy bars
- UI: text arranger - UI: text arranger
- Sound system - Sound system
- Particles, impact effects
- Debris on ship death - Debris on ship death
---------------------------------- ----------------------------------

2
assets

@ -1 +1 @@
Subproject commit b53acd48a98987914ceb196af18ba679a222fa10 Subproject commit 6e4b7022a963a3357ba5f578bdd91064ae9f6b3e

View File

@ -33,3 +33,6 @@ path = "ui/radarframe.png"
[texture."ui::centerarrow"] [texture."ui::centerarrow"]
path = "ui/center-arrow.png" path = "ui/center-arrow.png"
[texture."particle::blaster"]
path = "particle/blaster-01.png"

View File

@ -1,5 +1,5 @@
[package] [package]
name = "galactica-shipbehavior" name = "galactica-behavior"
version = "0.0.0" version = "0.0.0"
edition = "2021" edition = "2021"

View File

@ -50,5 +50,8 @@ pub const OBJECT_SPRITE_INSTANCE_LIMIT: u64 = 500;
/// We can draw at most this many ui sprites on the screen. /// We can draw at most this many ui sprites on the screen.
pub const UI_SPRITE_INSTANCE_LIMIT: u64 = 100; pub const UI_SPRITE_INSTANCE_LIMIT: u64 = 100;
/// The size of our circular particle buffer. When we create particles, the oldest ones are replaced.
pub const PARTICLE_SPRITE_INSTANCE_LIMIT: u64 = 1000;
/// Must be small enough to fit in an i32 /// Must be small enough to fit in an i32
pub const STARFIELD_SPRITE_INSTANCE_LIMIT: u64 = STARFIELD_COUNT * 24; pub const STARFIELD_SPRITE_INSTANCE_LIMIT: u64 = STARFIELD_COUNT * 24;

View File

@ -56,7 +56,6 @@ impl crate::Build for Texture {
index: ct.textures.len(), index: ct.textures.len(),
aspect: dim.0 as f32 / dim.1 as f32, aspect: dim.0 as f32 / dim.1 as f32,
}; };
ct.texture_index.insert(texture_name.clone(), h);
if texture_name == ct.starfield_texture_name { if texture_name == ct.starfield_texture_name {
if ct.starfield_handle.is_none() { if ct.starfield_handle.is_none() {
@ -67,9 +66,10 @@ impl crate::Build for Texture {
} }
} }
ct.texture_index.insert(texture_name.clone(), h);
ct.textures.push(Self { ct.textures.push(Self {
name: texture_name, name: texture_name,
path: path, path,
handle: h, handle: h,
}); });
} }

View File

@ -3,7 +3,7 @@ struct InstanceInput {
@location(3) transform_matrix_1: vec4<f32>, @location(3) transform_matrix_1: vec4<f32>,
@location(4) transform_matrix_2: vec4<f32>, @location(4) transform_matrix_2: vec4<f32>,
@location(5) transform_matrix_3: vec4<f32>, @location(5) transform_matrix_3: vec4<f32>,
@location(6) texture_idx: u32, @location(6) texture_index: u32,
}; };
struct VertexInput { struct VertexInput {
@ -14,7 +14,7 @@ struct VertexInput {
struct VertexOutput { struct VertexOutput {
@builtin(position) position: vec4<f32>, @builtin(position) position: vec4<f32>,
@location(0) texture_coords: vec2<f32>, @location(0) texture_coords: vec2<f32>,
@location(1) index: u32, @location(1) texture_index: u32,
} }
@ -29,6 +29,7 @@ struct GlobalUniform {
starfield_texture: vec2<u32>, starfield_texture: vec2<u32>,
starfield_tile_size: vec2<f32>, starfield_tile_size: vec2<f32>,
starfield_size_limits: vec2<f32>, starfield_size_limits: vec2<f32>,
current_time: vec2<f32>,
}; };
@ -56,7 +57,7 @@ fn vertex_main(
var out: VertexOutput; var out: VertexOutput;
out.position = transform * vec4<f32>(vertex.position, 1.0); out.position = transform * vec4<f32>(vertex.position, 1.0);
out.texture_coords = vertex.texture_coords; out.texture_coords = vertex.texture_coords;
out.index = instance.texture_idx; out.texture_index = instance.texture_index;
return out; return out;
} }
@ -64,7 +65,7 @@ fn vertex_main(
@fragment @fragment
fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> { fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
return textureSampleLevel( return textureSampleLevel(
texture_array[in.index], texture_array[in.texture_index],
sampler_array[0], sampler_array[0],
in.texture_coords, in.texture_coords,
0.0 0.0

View File

@ -0,0 +1,85 @@
struct InstanceInput {
@location(2) position: vec3<f32>,
@location(3) size: f32,
@location(4) expires: f32,
@location(5) texture_index: u32,
};
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) texture_coords: vec2<f32>,
}
struct VertexOutput {
@builtin(position) position: vec4<f32>,
@location(0) texture_coords: vec2<f32>,
@location(1) texture_index: u32,
}
@group(1) @binding(0)
var<uniform> global: GlobalUniform;
struct GlobalUniform {
camera_position: vec2<f32>,
camera_zoom: vec2<f32>,
camera_zoom_limits: vec2<f32>,
window_size: vec2<f32>,
window_aspect: vec2<f32>,
starfield_texture: vec2<u32>,
starfield_tile_size: vec2<f32>,
starfield_size_limits: vec2<f32>,
current_time: vec2<f32>,
};
@group(0) @binding(0)
var texture_array: binding_array<texture_2d<f32>>;
@group(0) @binding(1)
var sampler_array: binding_array<sampler>;
@vertex
fn vertex_main(
vertex: VertexInput,
instance: InstanceInput,
) -> VertexOutput {
var out: VertexOutput;
out.texture_coords = vertex.texture_coords;
out.texture_index = instance.texture_index;
if instance.expires < global.current_time.x {
out.position = vec4<f32>(2.0, 2.0, 0.0, 1.0);
return out;
}
var scale: f32 = instance.size / global.camera_zoom.x;
var pos: vec2<f32> = vec2(vertex.position.x, vertex.position.y);
pos = pos * vec2<f32>(
1.0 * scale / global.window_aspect.x,
scale
);
var ipos: vec2<f32> = vec2(instance.position.x, instance.position.y) - global.camera_position;
pos = pos + vec2<f32>(
ipos.x / (global.camera_zoom.x/2.0) / global.window_aspect.x,
ipos.y / (global.camera_zoom.x/2.0)
);
out.position = vec4<f32>(pos, 0.0, 1.0) * instance.position.z;
return out;
}
@fragment
fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
return textureSampleLevel(
texture_array[in.texture_index],
sampler_array[0],
in.texture_coords,
0.0
).rgba;
}

View File

@ -26,6 +26,7 @@ struct GlobalUniform {
starfield_texture: vec2<u32>, starfield_texture: vec2<u32>,
starfield_tile_size: vec2<f32>, starfield_tile_size: vec2<f32>,
starfield_size_limits: vec2<f32>, starfield_size_limits: vec2<f32>,
current_time: vec2<f32>,
}; };

View File

@ -4,7 +4,7 @@ struct InstanceInput {
@location(4) transform_matrix_2: vec4<f32>, @location(4) transform_matrix_2: vec4<f32>,
@location(5) transform_matrix_3: vec4<f32>, @location(5) transform_matrix_3: vec4<f32>,
@location(6) color_transform: vec4<f32>, @location(6) color_transform: vec4<f32>,
@location(7) texture_idx: u32, @location(7) texture_index: u32,
}; };
struct VertexInput { struct VertexInput {
@ -15,7 +15,7 @@ struct VertexInput {
struct VertexOutput { struct VertexOutput {
@builtin(position) position: vec4<f32>, @builtin(position) position: vec4<f32>,
@location(0) texture_coords: vec2<f32>, @location(0) texture_coords: vec2<f32>,
@location(1) index: u32, @location(1) texture_index: u32,
@location(2) color_transform: vec4<f32>, @location(2) color_transform: vec4<f32>,
} }
@ -31,6 +31,7 @@ struct GlobalUniform {
starfield_texture: vec2<u32>, starfield_texture: vec2<u32>,
starfield_tile_size: vec2<f32>, starfield_tile_size: vec2<f32>,
starfield_size_limits: vec2<f32>, starfield_size_limits: vec2<f32>,
current_time: vec2<f32>,
}; };
@ -58,7 +59,7 @@ fn vertex_main(
var out: VertexOutput; var out: VertexOutput;
out.position = transform * vec4<f32>(vertex.position, 1.0); out.position = transform * vec4<f32>(vertex.position, 1.0);
out.texture_coords = vertex.texture_coords; out.texture_coords = vertex.texture_coords;
out.index = instance.texture_idx; out.texture_index = instance.texture_index;
out.color_transform = instance.color_transform; out.color_transform = instance.color_transform;
return out; return out;
} }
@ -67,7 +68,7 @@ fn vertex_main(
@fragment @fragment
fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> { fn fragment_main(in: VertexOutput) -> @location(0) vec4<f32> {
return textureSampleLevel( return textureSampleLevel(
texture_array[in.index], texture_array[in.texture_index],
sampler_array[0], sampler_array[0],
in.texture_coords, in.texture_coords,
0.0 0.0

View File

@ -30,7 +30,7 @@ pub struct GlobalDataContent {
/// Size ratio of window, in physical pixels /// Size ratio of window, in physical pixels
pub window_size: [f32; 2], pub window_size: [f32; 2],
// Aspect ration of window /// Aspect ratio of window
/// Second component is ignored. /// Second component is ignored.
pub window_aspect: [f32; 2], pub window_aspect: [f32; 2],
@ -42,8 +42,12 @@ pub struct GlobalDataContent {
/// Second component is ignored. /// Second component is ignored.
pub starfield_tile_size: [f32; 2], pub starfield_tile_size: [f32; 2],
// Min and max starfield star size, in game units /// Min and max starfield star size, in game units
pub starfield_size_limits: [f32; 2], pub starfield_size_limits: [f32; 2],
/// Current game time, in seconds.
/// Second component is ignored.
pub current_time: [f32; 2],
} }
impl GlobalDataContent { impl GlobalDataContent {
@ -70,7 +74,7 @@ impl GlobalData {
}, },
count: None, count: None,
}], }],
label: Some("camera_bind_group_layout"), label: Some("globaldata bind group layout"),
}); });
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
@ -79,7 +83,7 @@ impl GlobalData {
binding: 0, binding: 0,
resource: buffer.as_entire_binding(), resource: buffer.as_entire_binding(),
}], }],
label: Some("camera_bind_group"), label: Some("globaldata bind group"),
}); });
return Self { return Self {

View File

@ -2,7 +2,7 @@ use anyhow::Result;
use bytemuck; use bytemuck;
use cgmath::{Deg, EuclideanSpace, Matrix4, Point2, Vector3}; use cgmath::{Deg, EuclideanSpace, Matrix4, Point2, Vector3};
use galactica_constants; use galactica_constants;
use std::{iter, rc::Rc}; use std::{iter, rc::Rc, time::Instant};
use wgpu; use wgpu;
use winit::{self, dpi::LogicalSize, window::Window}; use winit::{self, dpi::LogicalSize, window::Window};
@ -10,13 +10,13 @@ use crate::{
content, content,
globaldata::{GlobalData, GlobalDataContent}, globaldata::{GlobalData, GlobalDataContent},
pipeline::PipelineBuilder, pipeline::PipelineBuilder,
sprite::ObjectSubSprite, sprite::{ObjectSubSprite, ParticleBuilder},
starfield::Starfield, starfield::Starfield,
texturearray::TextureArray, texturearray::TextureArray,
vertexbuffer::{ vertexbuffer::{
consts::{SPRITE_INDICES, SPRITE_VERTICES}, consts::{SPRITE_INDICES, SPRITE_VERTICES},
types::{ObjectInstance, StarfieldInstance, TexturedVertex, UiInstance}, types::{ObjectInstance, ParticleInstance, StarfieldInstance, TexturedVertex, UiInstance},
VertexBuffer, BufferObject, VertexBuffer,
}, },
ObjectSprite, UiSprite, OPENGL_TO_WGPU_MATRIX, ObjectSprite, UiSprite, OPENGL_TO_WGPU_MATRIX,
}; };
@ -39,6 +39,7 @@ pub struct GPUState {
object_pipeline: wgpu::RenderPipeline, object_pipeline: wgpu::RenderPipeline,
starfield_pipeline: wgpu::RenderPipeline, starfield_pipeline: wgpu::RenderPipeline,
particle_pipeline: wgpu::RenderPipeline,
ui_pipeline: wgpu::RenderPipeline, ui_pipeline: wgpu::RenderPipeline,
starfield: Starfield, starfield: Starfield,
@ -51,6 +52,12 @@ struct VertexBuffers {
object: Rc<VertexBuffer>, object: Rc<VertexBuffer>,
starfield: Rc<VertexBuffer>, starfield: Rc<VertexBuffer>,
ui: Rc<VertexBuffer>, ui: Rc<VertexBuffer>,
/// The index of the next particle slot we'll write to.
/// This must cycle to 0 whenever it exceeds the size
/// of the particle instance array.
particle_array_head: u64,
particle: Rc<VertexBuffer>,
} }
impl GPUState { impl GPUState {
@ -120,7 +127,7 @@ impl GPUState {
let vertex_buffers = VertexBuffers { let vertex_buffers = VertexBuffers {
object: Rc::new(VertexBuffer::new::<TexturedVertex, ObjectInstance>( object: Rc::new(VertexBuffer::new::<TexturedVertex, ObjectInstance>(
"objecte", "object",
&device, &device,
Some(SPRITE_VERTICES), Some(SPRITE_VERTICES),
Some(SPRITE_INDICES), Some(SPRITE_INDICES),
@ -142,6 +149,15 @@ impl GPUState {
Some(SPRITE_INDICES), Some(SPRITE_INDICES),
galactica_constants::UI_SPRITE_INSTANCE_LIMIT, galactica_constants::UI_SPRITE_INSTANCE_LIMIT,
)), )),
particle_array_head: 0,
particle: Rc::new(VertexBuffer::new::<TexturedVertex, ParticleInstance>(
"particle",
&device,
Some(SPRITE_VERTICES),
Some(SPRITE_INDICES),
galactica_constants::PARTICLE_SPRITE_INSTANCE_LIMIT,
)),
}; };
// Load uniforms // Load uniforms
@ -191,6 +207,18 @@ impl GPUState {
.set_bind_group_layouts(bind_group_layouts) .set_bind_group_layouts(bind_group_layouts)
.build(); .build();
let particle_pipeline = PipelineBuilder::new("particle", &device)
.set_shader(include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/shaders/",
"particle.wgsl"
)))
.set_format(config.format)
.set_triangle(true)
.set_vertex_buffer(&vertex_buffers.particle)
.set_bind_group_layouts(bind_group_layouts)
.build();
let mut starfield = Starfield::new(); let mut starfield = Starfield::new();
starfield.regenerate(); starfield.regenerate();
@ -207,6 +235,7 @@ impl GPUState {
object_pipeline, object_pipeline,
starfield_pipeline, starfield_pipeline,
ui_pipeline, ui_pipeline,
particle_pipeline,
starfield, starfield,
texture_array, texture_array,
@ -500,8 +529,12 @@ impl GPUState {
&mut self, &mut self,
camera_pos: Point2<f32>, camera_pos: Point2<f32>,
camera_zoom: f32, camera_zoom: f32,
// TODO: clean this up, pass one struct
object_sprites: &Vec<ObjectSprite>, object_sprites: &Vec<ObjectSprite>,
ui_sprites: &Vec<UiSprite>, ui_sprites: &Vec<UiSprite>,
new_particles: &mut Vec<ParticleBuilder>,
start_instant: Instant,
) -> Result<(), wgpu::SurfaceError> { ) -> Result<(), wgpu::SurfaceError> {
let output = self.surface.get_current_texture()?; let output = self.surface.get_current_texture()?;
let view = output let view = output
@ -535,6 +568,9 @@ impl GPUState {
timestamp_writes: None, timestamp_writes: None,
}); });
// TODO: handle overflow
let time_now = start_instant.elapsed().as_secs_f32();
// Update global values // Update global values
self.queue.write_buffer( self.queue.write_buffer(
&self.global_data.buffer, &self.global_data.buffer,
@ -554,9 +590,31 @@ impl GPUState {
galactica_constants::STARFIELD_SIZE_MIN, galactica_constants::STARFIELD_SIZE_MIN,
galactica_constants::STARFIELD_SIZE_MAX, galactica_constants::STARFIELD_SIZE_MAX,
], ],
current_time: [time_now, 0.0],
}]), }]),
); );
for i in new_particles.iter() {
let texture = self.texture_array.get_texture(i.texture);
self.queue.write_buffer(
&self.vertex_buffers.particle.instances,
ParticleInstance::SIZE * self.vertex_buffers.particle_array_head,
bytemuck::cast_slice(&[ParticleInstance {
position: [i.pos.x, i.pos.y, 1.0],
size: i.size,
texture_index: texture.index,
expires: time_now + i.lifetime,
}]),
);
self.vertex_buffers.particle_array_head += 1;
if self.vertex_buffers.particle_array_head
== galactica_constants::PARTICLE_SPRITE_INSTANCE_LIMIT
{
self.vertex_buffers.particle_array_head = 0;
}
}
new_particles.clear();
// Create sprite instances // Create sprite instances
let (n_object, n_ui) = let (n_object, n_ui) =
self.update_sprite_instances(camera_zoom, camera_pos, object_sprites, ui_sprites); self.update_sprite_instances(camera_zoom, camera_pos, object_sprites, ui_sprites);
@ -580,6 +638,15 @@ impl GPUState {
render_pass.set_pipeline(&self.object_pipeline); render_pass.set_pipeline(&self.object_pipeline);
render_pass.draw_indexed(0..SPRITE_INDICES.len() as u32, 0, 0..n_object as _); render_pass.draw_indexed(0..SPRITE_INDICES.len() as u32, 0, 0..n_object as _);
// Particle pipeline
self.vertex_buffers.particle.set_in_pass(&mut render_pass);
render_pass.set_pipeline(&self.particle_pipeline);
render_pass.draw_indexed(
0..SPRITE_INDICES.len() as u32,
0,
0..galactica_constants::PARTICLE_SPRITE_INSTANCE_LIMIT as _,
);
// Ui pipeline // Ui pipeline
self.vertex_buffers.ui.set_in_pass(&mut render_pass); self.vertex_buffers.ui.set_in_pass(&mut render_pass);
render_pass.set_pipeline(&self.ui_pipeline); render_pass.set_pipeline(&self.ui_pipeline);

View File

@ -17,7 +17,7 @@ mod vertexbuffer;
use galactica_content as content; use galactica_content as content;
pub use gpustate::GPUState; pub use gpustate::GPUState;
pub use sprite::{AnchoredUiPosition, ObjectSprite, ObjectSubSprite, UiSprite}; pub use sprite::{AnchoredUiPosition, ObjectSprite, ObjectSubSprite, ParticleBuilder, UiSprite};
use cgmath::Matrix4; use cgmath::Matrix4;

View File

@ -1,6 +1,23 @@
use crate::content; use crate::content;
use cgmath::{Deg, Point2, Point3}; use cgmath::{Deg, Point2, Point3};
/// Instructions to create a new particle
pub struct ParticleBuilder {
/// The texture to use for this particle
pub texture: content::TextureHandle,
// TODO: rotation, velocity
/// This object's center, in world coordinates.
pub pos: Point3<f32>,
/// This particle's lifetime, in seconds
pub lifetime: f32,
/// The size of this sprite,
/// given as height in world units.
pub size: f32,
}
/// The location of a UI element, in one of a few /// The location of a UI element, in one of a few
/// possible coordinate systems. /// possible coordinate systems.
/// ///

View File

@ -195,3 +195,60 @@ impl BufferObject for UiInstance {
} }
} }
} }
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct ParticleInstance {
/// World position of this particle
pub position: [f32; 3],
// TODO: docs: particle frames must all have the same size
// TODO: is transparency trimmed? That's not ideal for animated particles!
/// The height of this particle, in world units
pub size: f32,
// TODO: rotation, velocity vector
// TODO: animated sprites
// TODO: texture aspect ratio
/// The time, in seconds, at which this particle expires.
/// Time is kept by a variable in the global uniform.
pub expires: f32,
/// What texture to use for this particle
pub texture_index: u32,
}
impl BufferObject for ParticleInstance {
fn layout() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: Self::SIZE,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &[
// Position
wgpu::VertexAttribute {
offset: 0,
shader_location: 2,
format: wgpu::VertexFormat::Float32x3,
},
// Size
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
shader_location: 3,
format: wgpu::VertexFormat::Float32,
},
// Expires
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
shader_location: 4,
format: wgpu::VertexFormat::Float32,
},
// Texture
wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress,
shader_location: 5,
format: wgpu::VertexFormat::Uint32,
},
],
}
}
}

View File

@ -1,4 +1,4 @@
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Rad, Vector2}; use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Point3, Rad, Vector2};
use crossbeam::channel::Receiver; use crossbeam::channel::Receiver;
use nalgebra::vector; use nalgebra::vector;
use rand::Rng; use rand::Rng;
@ -12,7 +12,7 @@ use std::{collections::HashMap, f32::consts::PI};
use crate::{objects, objects::ProjectileWorldObject, util, wrapper::Wrapper, ShipPhysicsHandle}; use crate::{objects, objects::ProjectileWorldObject, util, wrapper::Wrapper, ShipPhysicsHandle};
use galactica_content as content; use galactica_content as content;
use galactica_gameobject as object; use galactica_gameobject as object;
use galactica_render::ObjectSprite; use galactica_render::{ObjectSprite, ParticleBuilder};
/// Keeps track of all objects in the world that we can interact with. /// Keeps track of all objects in the world that we can interact with.
/// Also wraps our physics engine /// Also wraps our physics engine
@ -92,7 +92,7 @@ impl<'a> World {
.linvel(vector![vel.x, vel.y]) .linvel(vector![vel.x, vel.y])
.build(); .build();
let collider = ColliderBuilder::ball(5.0) let collider = ColliderBuilder::ball(1.0)
.sensor(true) .sensor(true)
.active_events(ActiveEvents::COLLISION_EVENTS) .active_events(ActiveEvents::COLLISION_EVENTS)
.build(); .build();
@ -169,7 +169,7 @@ impl<'a> World {
} }
/// Step this physics system by `t` seconds /// Step this physics system by `t` seconds
pub fn step(&mut self, t: f32, ct: &content::Content) { pub fn step(&mut self, t: f32, ct: &content::Content, particles: &mut Vec<ParticleBuilder>) {
// Run ship updates // Run ship updates
// TODO: maybe reorganize projectile creation? // TODO: maybe reorganize projectile creation?
let mut projectiles = Vec::new(); let mut projectiles = Vec::new();
@ -214,6 +214,18 @@ impl<'a> World {
if let Some(s) = self.ships.get_mut(b) { if let Some(s) = self.ships.get_mut(b) {
let hit = s.ship.handle_projectile_collision(ct, &p.projectile); let hit = s.ship.handle_projectile_collision(ct, &p.projectile);
if hit { if hit {
let r = self.get_rigid_body(p.rigid_body);
let pos = util::rigidbody_position(r);
particles.push(ParticleBuilder {
texture: ct.get_texture_handle("particle::blaster"),
pos: Point3 {
x: pos.x,
y: pos.y,
z: 1.0, // TODO:remove z coordinate
},
lifetime: 0.1,
size: 10.0,
});
self.remove_projectile(*a); self.remove_projectile(*a);
} }
} }

View File

@ -5,10 +5,10 @@ use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, Virt
use crate::camera::Camera; use crate::camera::Camera;
use crate::{content, inputstatus::InputStatus}; use crate::{content, inputstatus::InputStatus};
use galactica_behavior::{behavior, ShipBehavior};
use galactica_constants; use galactica_constants;
use galactica_gameobject as object; use galactica_gameobject as object;
use galactica_render::{ObjectSprite, UiSprite}; use galactica_render::{ObjectSprite, ParticleBuilder, UiSprite};
use galactica_shipbehavior::{behavior, ShipBehavior};
use galactica_ui as ui; use galactica_ui as ui;
use galactica_world::{util, ShipPhysicsHandle, World}; use galactica_world::{util, ShipPhysicsHandle, World};
@ -21,10 +21,14 @@ pub struct Game {
paused: bool, paused: bool,
pub time_scale: f32, pub time_scale: f32,
physics: World, world: World,
shipbehaviors: Vec<Box<dyn ShipBehavior>>, shipbehaviors: Vec<Box<dyn ShipBehavior>>,
playerbehavior: behavior::Player, playerbehavior: behavior::Player,
content: content::Content, content: content::Content,
pub start_instant: Instant,
// TODO: clean this up
pub new_particles: Vec<ParticleBuilder>,
} }
impl Game { impl Game {
@ -85,6 +89,7 @@ impl Game {
last_update: Instant::now(), last_update: Instant::now(),
input: InputStatus::new(), input: InputStatus::new(),
player: h1, player: h1,
start_instant: Instant::now(),
camera: Camera { camera: Camera {
pos: (0.0, 0.0).into(), pos: (0.0, 0.0).into(),
@ -95,10 +100,11 @@ impl Game {
paused: false, paused: false,
time_scale: 1.0, time_scale: 1.0,
physics, world: physics,
shipbehaviors, shipbehaviors,
content: ct, content: ct,
playerbehavior: behavior::Player::new(h1), playerbehavior: behavior::Player::new(h1),
new_particles: Vec::new(),
} }
} }
@ -131,19 +137,19 @@ impl Game {
self.playerbehavior.key_right = self.input.key_right; self.playerbehavior.key_right = self.input.key_right;
self.playerbehavior.key_left = self.input.key_left; self.playerbehavior.key_left = self.input.key_left;
self.playerbehavior self.playerbehavior
.update_controls(&mut self.physics, &self.content); .update_controls(&mut self.world, &self.content);
self.shipbehaviors.retain_mut(|b| { self.shipbehaviors.retain_mut(|b| {
// Remove shipbehaviors of destroyed ships // Remove shipbehaviors of destroyed ships
if self.physics.get_ship_mut(&b.get_handle()).is_none() { if self.world.get_ship_mut(&b.get_handle()).is_none() {
false false
} else { } else {
b.update_controls(&mut self.physics, &self.content); b.update_controls(&mut self.world, &self.content);
true true
} }
}); });
self.physics.step(t, &self.content); self.world.step(t, &self.content, &mut self.new_particles);
if self.input.v_scroll != 0.0 { if self.input.v_scroll != 0.0 {
self.camera.zoom = (self.camera.zoom + self.input.v_scroll) self.camera.zoom = (self.camera.zoom + self.input.v_scroll)
@ -153,11 +159,11 @@ impl Game {
// TODO: Camera physics // TODO: Camera physics
let r = self let r = self
.physics .world
.get_ship_mut(&self.player) .get_ship_mut(&self.player)
.unwrap() .unwrap()
.physics_handle; .physics_handle;
let r = self.physics.get_rigid_body(r.0); // TODO: r.0 shouldn't be public let r = self.world.get_rigid_body(r.0); // TODO: r.0 shouldn't be public
let ship_pos = util::rigidbody_position(r); let ship_pos = util::rigidbody_position(r);
self.camera.pos = ship_pos; self.camera.pos = ship_pos;
self.last_update = Instant::now(); self.last_update = Instant::now();
@ -167,7 +173,7 @@ impl Game {
let mut sprites: Vec<ObjectSprite> = Vec::new(); let mut sprites: Vec<ObjectSprite> = Vec::new();
sprites.append(&mut self.system.get_sprites()); sprites.append(&mut self.system.get_sprites());
sprites.extend(self.physics.get_ship_sprites(&self.content)); sprites.extend(self.world.get_ship_sprites(&self.content));
// Make sure sprites are drawn in the correct order // Make sure sprites are drawn in the correct order
// (note the reversed a, b in the comparator) // (note the reversed a, b in the comparator)
@ -177,7 +183,7 @@ impl Game {
sprites.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z)); sprites.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z));
// Don't waste time sorting these, they should always be on top. // Don't waste time sorting these, they should always be on top.
sprites.extend(self.physics.get_projectile_sprites()); sprites.extend(self.world.get_projectile_sprites());
return sprites; return sprites;
} }
@ -186,7 +192,7 @@ impl Game {
return ui::build_radar( return ui::build_radar(
&self.content, &self.content,
&self.player, &self.player,
&self.physics, &self.world,
&self.system, &self.system,
self.camera.zoom, self.camera.zoom,
self.camera.aspect, self.camera.aspect,

View File

@ -37,6 +37,11 @@ fn main() -> Result<()> {
game.camera.zoom, game.camera.zoom,
&game.get_object_sprites(), &game.get_object_sprites(),
&game.get_ui_sprites(), &game.get_ui_sprites(),
// TODO: clean this up, single game data struct
// Game in another crate?
// Shipbehavior needs game state too...
&mut game.new_particles,
game.start_instant,
) { ) {
Ok(_) => {} Ok(_) => {}
Err(wgpu::SurfaceError::Lost) => gpu.resize(), Err(wgpu::SurfaceError::Lost) => gpu.resize(),