|
|
|
|
@ -1,5 +1,8 @@
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
// TODO:
|
|
|
|
|
// - When shape asset is updated, shape should update in real time
|
|
|
|
|
|
|
|
|
|
/// Create tetris game with camera that renders to subset of viewport
|
|
|
|
|
///
|
|
|
|
|
/// Focus on a single piece and making it really tight mechanically
|
|
|
|
|
@ -14,6 +17,7 @@ impl Plugin for BlocksPlugin {
|
|
|
|
|
.init_asset_loader::<ShapeAssetLoader>()
|
|
|
|
|
.add_systems(OnEnter(Loading(true)), load_assets.run_if(run_once))
|
|
|
|
|
.add_systems(OnEnter(GameState::Setup), (setup_camera, setup_blocks))
|
|
|
|
|
.add_systems(Update, updated_shape_asset.run_if(on_message::<AssetEvent<ShapeAsset>>))
|
|
|
|
|
.add_observer(add_shape);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -22,16 +26,64 @@ impl Plugin for BlocksPlugin {
|
|
|
|
|
/// Stores shape data in an asset file, likely toml
|
|
|
|
|
#[derive(Asset, TypePath, Debug, Deserialize)]
|
|
|
|
|
struct ShapeAsset {
|
|
|
|
|
layout: Vec<Vec<u8>>,
|
|
|
|
|
layout: ShapeLayout,
|
|
|
|
|
#[serde(default = "default_tint")]
|
|
|
|
|
tint: String,
|
|
|
|
|
#[serde(skip)]
|
|
|
|
|
mesh: Mesh2d,
|
|
|
|
|
#[serde(skip)]
|
|
|
|
|
material: MeshMaterial2d<ColorMaterial>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn default_tint() -> String {
|
|
|
|
|
"#aa00aa".into()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ShapeAsset {
|
|
|
|
|
fn into_bundle(&self) -> impl Bundle {
|
|
|
|
|
(self.mesh.clone(), self.material.clone())
|
|
|
|
|
fn as_bundle(&self) -> impl Bundle {
|
|
|
|
|
let [a, b, c, d] = self.layout.positions();
|
|
|
|
|
|
|
|
|
|
related!(ShapeBlocks[
|
|
|
|
|
(self.mesh.clone(), self.material.clone(), a),
|
|
|
|
|
(self.mesh.clone(), self.material.clone(), b),
|
|
|
|
|
(self.mesh.clone(), self.material.clone(), c),
|
|
|
|
|
(self.mesh.clone(), self.material.clone(), d),
|
|
|
|
|
])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Block positions relative to the shape's center
|
|
|
|
|
#[derive(Component, PartialEq, Debug)]
|
|
|
|
|
pub(crate) struct RelativePosition {
|
|
|
|
|
pub x: isize,
|
|
|
|
|
pub y: isize,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<(isize, isize)> for RelativePosition {
|
|
|
|
|
fn from((x, y): (isize, isize)) -> Self {
|
|
|
|
|
RelativePosition { x, y }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Layout for a given shape
|
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
|
pub(crate) struct ShapeLayout(pub Vec<Vec<u8>>);
|
|
|
|
|
|
|
|
|
|
impl ShapeLayout {
|
|
|
|
|
pub(crate) fn positions(&self) -> [RelativePosition;4] {
|
|
|
|
|
let mut c: Vec<RelativePosition> = Vec::with_capacity(4);
|
|
|
|
|
let center = {
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
for (y, nested) in self.0.iter().enumerate() {
|
|
|
|
|
for (x, val) in nested.iter().enumerate() {
|
|
|
|
|
if *val == 1 {
|
|
|
|
|
println!("{x}{y}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
todo!()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -43,7 +95,9 @@ enum ShapeAssetError {
|
|
|
|
|
#[error("Failed to read file {0}")]
|
|
|
|
|
Io(#[from] std::io::Error),
|
|
|
|
|
#[error("Failed to parse file {0}")]
|
|
|
|
|
Parse(#[from] toml::de::Error),
|
|
|
|
|
ParseFile(#[from] toml::de::Error),
|
|
|
|
|
#[error("Failed to parse tint {0}")]
|
|
|
|
|
ParseTint(#[from] HexColorError),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl AssetLoader for ShapeAssetLoader {
|
|
|
|
|
@ -58,29 +112,26 @@ impl AssetLoader for ShapeAssetLoader {
|
|
|
|
|
) -> Result<Self::Asset, Self::Error> {
|
|
|
|
|
let mut bytes = Vec::new();
|
|
|
|
|
reader.read_to_end(&mut bytes).await?;
|
|
|
|
|
// TODO: Create sub-assets for mesh and material
|
|
|
|
|
let parsed = toml::from_slice::<ShapeAsset>(bytes.as_slice())?;
|
|
|
|
|
let mesh = {
|
|
|
|
|
// https://docs.rs/bevy/latest/bevy/asset/struct.LoadContext.html#method.add_labeled_asset
|
|
|
|
|
let m: Mesh = Rectangle::new(100.0, 100.0).into();
|
|
|
|
|
let h: Handle<Mesh> = load_context.add_labeled_asset(format!("{}#mesh", load_context.asset_path()), m);
|
|
|
|
|
let h: Handle<Mesh> =
|
|
|
|
|
load_context.add_labeled_asset(format!("{}#mesh", load_context.asset_path()), m);
|
|
|
|
|
Mesh2d(h)
|
|
|
|
|
};
|
|
|
|
|
let material = {
|
|
|
|
|
let m = ColorMaterial {
|
|
|
|
|
color: PURPLE.into(),
|
|
|
|
|
..default()
|
|
|
|
|
};
|
|
|
|
|
let h: Handle<ColorMaterial> = load_context.add_labeled_asset(format!("{}#material", load_context.asset_path()), m);
|
|
|
|
|
let color = Srgba::hex(parsed.tint.clone())?.into();
|
|
|
|
|
let m = ColorMaterial { color, ..default() };
|
|
|
|
|
let h: Handle<ColorMaterial> = load_context
|
|
|
|
|
.add_labeled_asset(format!("{}#material", load_context.asset_path()), m);
|
|
|
|
|
MeshMaterial2d(h)
|
|
|
|
|
};
|
|
|
|
|
let parsed = toml::from_slice::<ShapeAsset>(bytes.as_slice())?;
|
|
|
|
|
Ok(
|
|
|
|
|
ShapeAsset {
|
|
|
|
|
mesh,
|
|
|
|
|
material,
|
|
|
|
|
..parsed
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
Ok(ShapeAsset {
|
|
|
|
|
mesh,
|
|
|
|
|
material,
|
|
|
|
|
..parsed
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn extensions(&self) -> &[&str] {
|
|
|
|
|
@ -117,6 +168,16 @@ fn setup_blocks(
|
|
|
|
|
checklist.spawn_shape = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Blocks <- Shape Relationship
|
|
|
|
|
#[derive(Component)]
|
|
|
|
|
#[relationship(relationship_target = ShapeBlocks)]
|
|
|
|
|
struct ShapeBlock(Entity);
|
|
|
|
|
|
|
|
|
|
/// Shape -> Blocks Relationship
|
|
|
|
|
#[derive(Component)]
|
|
|
|
|
#[relationship_target(relationship = ShapeBlock)]
|
|
|
|
|
struct ShapeBlocks(Vec<Entity>);
|
|
|
|
|
|
|
|
|
|
/// Event handler for transforming a handle component into a thing
|
|
|
|
|
fn add_shape(
|
|
|
|
|
event: On<Add, AssetComponent<ShapeAsset>>,
|
|
|
|
|
@ -126,5 +187,25 @@ fn add_shape(
|
|
|
|
|
) {
|
|
|
|
|
let asset_component = query.get(event.entity).unwrap();
|
|
|
|
|
let shape = shapes.get(asset_component.handle.id()).unwrap();
|
|
|
|
|
commands.entity(event.entity).insert(shape.into_bundle());
|
|
|
|
|
|
|
|
|
|
commands.entity(event.entity).insert(shape.as_bundle());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn updated_shape_asset(
|
|
|
|
|
mut messages: MessageReader<AssetEvent<ShapeAsset>>,
|
|
|
|
|
query: Query<&AssetComponent<ShapeAsset>>,
|
|
|
|
|
) {
|
|
|
|
|
messages.read().for_each(|asset_event| {
|
|
|
|
|
match asset_event {
|
|
|
|
|
AssetEvent::Added { id } => debug!("Asset added: {id:?}"),
|
|
|
|
|
AssetEvent::Modified { id } => query.iter().filter(|AssetComponent { handle }| {
|
|
|
|
|
handle.id() == *id
|
|
|
|
|
}).for_each(|ac| {
|
|
|
|
|
warn!("TODO: Update {ac:?}");
|
|
|
|
|
}),
|
|
|
|
|
AssetEvent::Removed { id } => warn!("Asset removed: {id:?}"),
|
|
|
|
|
AssetEvent::Unused { id } => warn!("Asset is unused: {id:?}"),
|
|
|
|
|
AssetEvent::LoadedWithDependencies { id } => debug!("Asset lodaed: {id:?}"),
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|