render: create TileMap with a 'RelativeToTile' component to position entities along the grid
This commit is contained in:
parent
4afd518f45
commit
fa6511bff1
|
@ -861,6 +861,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"async-std",
|
||||
"lyra-engine",
|
||||
"rand",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
|
|
|
@ -9,6 +9,9 @@ pub use texture_atlas::*;
|
|||
mod animation_sheet;
|
||||
pub use animation_sheet::*;
|
||||
|
||||
mod tilemap;
|
||||
pub use tilemap::*;
|
||||
|
||||
/// How the sprite is positioned and rotated relative to its [`Transform`].
|
||||
///
|
||||
/// Default pivot is `Pivot::Center`, this makes it easier to rotate the sprites.
|
||||
|
|
|
@ -0,0 +1,215 @@
|
|||
use std::collections::VecDeque;
|
||||
|
||||
use glam::{UVec2, UVec3, Vec2, Vec3};
|
||||
use lyra_ecs::{
|
||||
query::{Entities, View},
|
||||
relation::ChildOf,
|
||||
Commands, Component, Entity, World,
|
||||
};
|
||||
use lyra_math::Transform;
|
||||
use lyra_reflect::Reflect;
|
||||
use lyra_resource::ResHandle;
|
||||
use lyra_scene::WorldTransform;
|
||||
use tracing::{debug, error};
|
||||
|
||||
use crate::{game::GameStages, plugin::Plugin};
|
||||
|
||||
use super::{AtlasSprite, TextureAtlas};
|
||||
|
||||
/// A position relative to a tile on a tilemap
|
||||
#[derive(Clone, Copy, Component, Reflect)]
|
||||
pub struct RelativeToTile {
|
||||
#[reflect(skip)] // TODO: impl reflect for Entity
|
||||
pub tilemap_entity: Entity,
|
||||
/// The position of the tile to spawn at.
|
||||
pub position: UVec2,
|
||||
pub z_level: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Component, Reflect)]
|
||||
pub struct Tile {
|
||||
/// The index in the atlas for the tile.
|
||||
pub atlas_index: u32,
|
||||
/// The tile position in the map.
|
||||
pub position: UVec2,
|
||||
pub z_level: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Component, Reflect)]
|
||||
struct TileInstance {
|
||||
tile: Tile,
|
||||
#[reflect(skip)] // TODO: impl reflect for Entity
|
||||
entity: Option<Entity>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Component, Reflect)]
|
||||
struct Layer {
|
||||
tiles: Vec<TileInstance>,
|
||||
level: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Component, Reflect)]
|
||||
pub struct TileMap {
|
||||
pub atlas: ResHandle<TextureAtlas>,
|
||||
/// The size of the map in tiles.
|
||||
///
|
||||
/// The Z-axis is used to specify the amount of layers in the map.
|
||||
pub size: UVec3,
|
||||
/// Dimensions of each tile.
|
||||
pub tile_size: UVec2,
|
||||
layers: Vec<Layer>,
|
||||
}
|
||||
|
||||
impl TileMap {
|
||||
pub fn new(
|
||||
atlas: ResHandle<TextureAtlas>,
|
||||
map_size: UVec2,
|
||||
layer_num: u32,
|
||||
tile_size: UVec2,
|
||||
) -> Self {
|
||||
let size = map_size.extend(layer_num);
|
||||
|
||||
let layer_size = map_size.element_product(); // x*y
|
||||
let mut layers = Vec::with_capacity(layer_num as _);
|
||||
for i in 0..layer_num {
|
||||
layers.push(Layer {
|
||||
tiles: Vec::with_capacity(layer_size as _),
|
||||
level: i,
|
||||
});
|
||||
}
|
||||
|
||||
Self {
|
||||
atlas,
|
||||
size,
|
||||
tile_size,
|
||||
layers,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_tile(&mut self, layer: u32, atlas_index: u32, x: u32, y: u32) {
|
||||
let l = &mut self.layers[layer as usize];
|
||||
l.tiles.push(TileInstance {
|
||||
tile: Tile {
|
||||
atlas_index,
|
||||
position: UVec2::new(x, y),
|
||||
z_level: 0,
|
||||
},
|
||||
entity: None,
|
||||
});
|
||||
}
|
||||
|
||||
/// Get the relative position of a tile
|
||||
pub fn position_of(&self, x: u32, y: u32) -> Vec2 {
|
||||
Vec2::new(x as _, y as _) * self.tile_size.as_vec2()
|
||||
}
|
||||
}
|
||||
|
||||
/// A system to update the tilemap when tiles are inserted/removed.
|
||||
pub fn system_tilemap_update(
|
||||
mut commands: Commands,
|
||||
view: View<(&mut TileMap, &Transform)>,
|
||||
) -> anyhow::Result<()> {
|
||||
for (mut map, pos) in view.into_iter() {
|
||||
let tile_size = map.tile_size;
|
||||
let atlas_handle = map.atlas.clone();
|
||||
let atlas = match atlas_handle.data_ref() {
|
||||
Some(a) => a,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
for layer in &mut map.layers {
|
||||
for tile in &mut layer.tiles {
|
||||
if tile.entity.is_none() {
|
||||
if let Some(frame) = atlas.frames.get(tile.tile.atlas_index as usize) {
|
||||
let sprite = AtlasSprite {
|
||||
atlas: atlas_handle.clone(),
|
||||
sprite: *frame,
|
||||
pivot: super::Pivot::TopLeft,
|
||||
};
|
||||
|
||||
let grid = tile.tile.position * tile_size;
|
||||
let sprite_pos = *pos
|
||||
+ Transform::from_xyz(
|
||||
grid.x as _,
|
||||
grid.y as _,
|
||||
tile.tile.z_level as _,
|
||||
);
|
||||
|
||||
tile.entity = Some(commands.spawn((sprite, sprite_pos, WorldTransform::default())));
|
||||
debug!("Spawned tile at ({}, {})", grid.x, grid.y);
|
||||
} else {
|
||||
error!(
|
||||
"Invalid atlas index '{}' for tile at pos '{:?}'",
|
||||
tile.tile.atlas_index, tile.tile.position
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn system_relative_tile_position_update(
|
||||
world: &mut World,
|
||||
view: View<(Entities, &RelativeToTile)>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut to_relate = VecDeque::new();
|
||||
|
||||
for (en, rel) in view.into_iter() {
|
||||
match world
|
||||
.view_one::<(&TileMap, &Transform)>(rel.tilemap_entity)
|
||||
.get()
|
||||
{
|
||||
Some((map, pos)) => {
|
||||
let layer = map.layers.last().unwrap();
|
||||
|
||||
if let Some(tile_en) = layer
|
||||
.tiles
|
||||
.iter()
|
||||
.find(|t| t.tile.position == rel.position)
|
||||
.and_then(|t| t.entity)
|
||||
{
|
||||
to_relate.push_back((en, tile_en, Transform::from_translation(Vec3::new(0.0, 0.0, rel.z_level as _))));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
error!(
|
||||
"Unknown tilemap for relative tile position of {:?}",
|
||||
rel.position
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while let Some((from, to, pos)) = to_relate.pop_front() {
|
||||
if world.view_one::<&WorldTransform>(from).get().is_none() {
|
||||
world.add_relation(from, ChildOf, to);
|
||||
world.insert(from, (pos, WorldTransform::default()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TileMapPlugin;
|
||||
|
||||
impl Plugin for TileMapPlugin {
|
||||
fn setup(&mut self, app: &mut crate::game::App) {
|
||||
// insert in postupdate to apply changes that other systems may have made to the tilemap
|
||||
app.add_system_to_stage(
|
||||
GameStages::PostUpdate,
|
||||
"tilemap_update",
|
||||
system_tilemap_update,
|
||||
&[],
|
||||
);
|
||||
app.add_system_to_stage(
|
||||
GameStages::PostUpdate,
|
||||
"relative_tile_position_update",
|
||||
system_relative_tile_position_update,
|
||||
&[],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -8,3 +8,4 @@ lyra-engine = { path = "../../", features = ["tracy"] }
|
|||
anyhow = "1.0.75"
|
||||
async-std = "1.12.0"
|
||||
tracing = "0.1.37"
|
||||
rand = "0.8.5"
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
use lyra_engine::{
|
||||
assets::{Image, ResourceManager},
|
||||
ecs::query::{Res, ResMut, View},
|
||||
ecs::{
|
||||
query::{Res, ResMut, View}, Commands, Component, Entity, World
|
||||
},
|
||||
game::App,
|
||||
gltf::Gltf,
|
||||
input::{
|
||||
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
||||
InputActionPlugin, KeyCode, LayoutId,
|
||||
},
|
||||
math::{self, Rect, Transform, URect, UVec2, Vec2, Vec3},
|
||||
math::{self, Rect, Transform, URect, UVec2, UVec3, Vec2, Vec3},
|
||||
render::light::directional::DirectionalLight,
|
||||
scene::{
|
||||
system_update_world_transforms, Camera2dBundle, CameraProjection, OrthographicProjection,
|
||||
|
@ -15,9 +17,16 @@ use lyra_engine::{
|
|||
ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD,
|
||||
ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
|
||||
},
|
||||
sprite::{self, AtlasAnimations, AtlasSprite, Pivot, Sprite, SpriteAnimation, TextureAtlas},
|
||||
sprite::{
|
||||
self, AtlasAnimations, AtlasSprite, Pivot, RelativeToTile, Sprite, SpriteAnimation,
|
||||
TextureAtlas, TileMap, TileMapPlugin,
|
||||
},
|
||||
DeltaTime,
|
||||
};
|
||||
use rand::{
|
||||
distributions::{Distribution, WeightedIndex},
|
||||
Rng,
|
||||
};
|
||||
use tracing::debug;
|
||||
|
||||
#[async_std::main]
|
||||
|
@ -95,42 +104,35 @@ async fn main() {
|
|||
a.with_plugin(lyra_engine::DefaultPlugins)
|
||||
.with_plugin(setup_scene_plugin)
|
||||
.with_plugin(action_handler_plugin)
|
||||
.with_plugin(TileMapPlugin)
|
||||
//.with_plugin(camera_debug_plugin)
|
||||
.with_plugin(TopDown2dCameraPlugin)
|
||||
.with_system(
|
||||
"system_update_world_transforms",
|
||||
"update_world_transforms",
|
||||
system_update_world_transforms,
|
||||
&[],
|
||||
).with_system(
|
||||
"spawn_egg",
|
||||
system_spawn_egg,
|
||||
&[],
|
||||
).with_system(
|
||||
"egg_location",
|
||||
system_egg_location,
|
||||
&[],
|
||||
);
|
||||
a.run();
|
||||
}
|
||||
|
||||
fn setup_scene_plugin(app: &mut App) {
|
||||
//app.add_resource(Timer(0.0));
|
||||
//app.with_system("sprite_change", sprite_change, &[]);
|
||||
app.with_system(
|
||||
/* app.with_system(
|
||||
"sprite_atlas_animation",
|
||||
sprite::system_sprite_atlas_animation,
|
||||
&[],
|
||||
);
|
||||
); */
|
||||
|
||||
let world = &mut app.world;
|
||||
let resman = world.get_resource_mut::<ResourceManager>().unwrap();
|
||||
|
||||
/* let camera_gltf = resman
|
||||
.request::<Gltf>("../assets/AntiqueCamera.glb")
|
||||
.unwrap();
|
||||
|
||||
camera_gltf.wait_recurse_dependencies_load();
|
||||
let camera_mesh = &camera_gltf.data_ref().unwrap().scenes[0];
|
||||
drop(resman);
|
||||
|
||||
world.spawn((
|
||||
camera_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(0.0, -5.0, -2.0),
|
||||
)); */
|
||||
|
||||
let cube_gltf = resman
|
||||
.request::<Gltf>("../assets/cube-texture-embedded.gltf")
|
||||
.unwrap();
|
||||
|
@ -138,33 +140,48 @@ fn setup_scene_plugin(app: &mut App) {
|
|||
cube_gltf.wait_recurse_dependencies_load().unwrap();
|
||||
let cube_mesh = &cube_gltf.data_ref().unwrap().scenes[0];
|
||||
|
||||
let image = resman.request::<Image>("../assets/Egg_item.png").unwrap();
|
||||
image.wait_recurse_dependencies_load().unwrap();
|
||||
|
||||
let soldier = resman
|
||||
.request::<Image>(
|
||||
"../assets/tiny_rpg_characters/Characters(100x100)/Soldier/Soldier/Soldier.png",
|
||||
)
|
||||
let grass_tileset = resman
|
||||
.request::<Image>("../assets/sprout_lands/Tilesets/Grass.png")
|
||||
.unwrap();
|
||||
soldier.wait_recurse_dependencies_load().unwrap();
|
||||
grass_tileset.wait_recurse_dependencies_load().unwrap();
|
||||
|
||||
let tile_size = UVec2::new(16, 16);
|
||||
let atlas = resman.store_new(TextureAtlas::from_grid(
|
||||
soldier,
|
||||
UVec2::new(9, 7),
|
||||
UVec2::new(100, 100),
|
||||
grass_tileset,
|
||||
UVec2::new(11, 7),
|
||||
tile_size,
|
||||
));
|
||||
let animations = AtlasAnimations::new(atlas, &[("soldier_run", 0.1, 9..=16)]);
|
||||
let run_anim = animations.get_active("soldier_run");
|
||||
let animations = resman.store_new(animations);
|
||||
let map_size = UVec2::new(32, 16);
|
||||
let mut tilemap = TileMap::new(atlas, map_size, 1, tile_size);
|
||||
|
||||
let textures = [
|
||||
12, // flat grass
|
||||
55, // tall grass
|
||||
56, // small two grass
|
||||
57, // small three grass
|
||||
58, // water puddle
|
||||
60, // three flower
|
||||
];
|
||||
let weights = [80, 15, 20, 20, 2, 10];
|
||||
|
||||
let dist = WeightedIndex::new(&weights).unwrap();
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for y in 0..map_size.y {
|
||||
for x in 0..map_size.x {
|
||||
let tex = textures[dist.sample(&mut rng)];
|
||||
tilemap.insert_tile(0, tex as _, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
drop(resman);
|
||||
|
||||
world.spawn((
|
||||
animations,
|
||||
run_anim,
|
||||
let en = world.spawn((
|
||||
tilemap,
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(0.0, 0.0, -10.0),
|
||||
));
|
||||
world.add_resource(GroundTileMap(en));
|
||||
|
||||
{
|
||||
let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0);
|
||||
|
@ -185,15 +202,70 @@ fn setup_scene_plugin(app: &mut App) {
|
|||
Camera2dBundle {
|
||||
projection: CameraProjection::Orthographic(OrthographicProjection {
|
||||
scale_mode: ScaleMode::Height(180.0),
|
||||
scale: 2.0,
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
Transform::from_xyz(0.0, 0.0, 0.0),
|
||||
Transform::from_xyz(
|
||||
(map_size.x * tile_size.x) as f32 * 0.5,
|
||||
(map_size.y * tile_size.y) as f32 * 0.5,
|
||||
0.0,
|
||||
),
|
||||
TopDown2dCamera {
|
||||
zoom_speed: Some(0.2),
|
||||
speed: 14.0,
|
||||
speed: 34.0,
|
||||
min_zoom: 10.0,
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
struct GroundTileMap(Entity);
|
||||
|
||||
#[derive(Component)]
|
||||
struct EggEntity;
|
||||
|
||||
fn system_egg_location(view: View<(&WorldTransform, &EggEntity)>) -> anyhow::Result<()> {
|
||||
for (pos, _) in view.into_iter() {
|
||||
println!("Found egg at world pos {:?}", **pos);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn system_spawn_egg(
|
||||
mut commands: Commands,
|
||||
inputs: Res<ActionHandler>,
|
||||
tile_map: Res<GroundTileMap>,
|
||||
resman: Res<ResourceManager>,
|
||||
) -> anyhow::Result<()> {
|
||||
let debug_state = inputs.get_action_state("Debug").unwrap();
|
||||
|
||||
if inputs.was_action_just_pressed("Debug").unwrap() {
|
||||
let egg = resman
|
||||
.request::<Image>("../assets/sprout_lands/Objects/Egg_item.png")
|
||||
.unwrap();
|
||||
|
||||
let x = rand::thread_rng().gen_range(0..32);
|
||||
let y = rand::thread_rng().gen_range(0..16);
|
||||
|
||||
let rtt = RelativeToTile {
|
||||
tilemap_entity: tile_map.0,
|
||||
position: UVec2::new(x, y),
|
||||
z_level: -9,
|
||||
};
|
||||
|
||||
commands.spawn((
|
||||
Sprite {
|
||||
texture: egg,
|
||||
color: Vec3::ONE,
|
||||
pivot: Pivot::TopLeft,
|
||||
},
|
||||
rtt,
|
||||
EggEntity
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
*
|
||||
!.gitignore
|
||||
!source.txt
|
|
@ -0,0 +1 @@
|
|||
https://cupnooble.itch.io/sprout-lands-asset-pack
|
Loading…
Reference in New Issue