Skip to content

Commit 0c1ecab

Browse files
committed
Add sprite animation using texture array
Texture arrays are limited 128 on mid 2014 macbook pro running mac os catalina. This means sprite animations are limited to 128 which might not be enough for complex characters. This feature may have to be re-implemented using a texture atlas in the future due to this limitation.
1 parent 5abd565 commit 0c1ecab

File tree

13 files changed

+250
-54
lines changed

13 files changed

+250
-54
lines changed

assets/adventurer_idle.png

4.91 KB
Loading

assets/adventurer_walk1.png

5.69 KB
Loading

assets/adventurer_walk2.png

4.82 KB
Loading

shaders/shader.frag

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
#version 450
22

33
layout(location = 0) in vec2 v_TexCoord;
4+
layout(location = 1) flat in uint v_tex_id;
5+
46
layout(location = 0) out vec4 o_Target;
57

6-
layout(set = 1, binding = 0) uniform texture2D t_Color;
8+
layout(set = 1, binding = 0) uniform texture2D t_Color[128];
79
layout(set = 1, binding = 1) uniform sampler s_Color;
810

911
void main() {
10-
vec4 texel = texture(sampler2D(t_Color, s_Color), v_TexCoord);
12+
vec4 texel = texture(sampler2D(t_Color[v_tex_id], s_Color), v_TexCoord);
1113
if(texel.a < 0.5) {
1214
discard;
1315
}

shaders/shader.vert

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ layout(location=2) in vec4 model_matrix_0;
66
layout(location=3) in vec4 model_matrix_1;
77
layout(location=4) in vec4 model_matrix_2;
88
layout(location=5) in vec4 model_matrix_3;
9+
layout(location=6) in uint frame_id;
910

1011
layout(location=0) out vec2 v_tex_coords;
12+
layout(location=1) flat out uint v_tex_id;
13+
1114

1215
layout(set = 0, binding = 0) uniform Uniforms {
1316
mat4 ortho;
@@ -24,6 +27,7 @@ void main() {
2427
);
2528

2629
v_tex_coords = a_tex_coords;
30+
v_tex_id = frame_id;
2731

2832
vec4 centre = vec4(vec3(0.0), 1.0);
2933

src/app.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ impl App {
4444
.await
4545
.unwrap();
4646

47-
let optional_features = wgpu::Features::empty();
47+
let optional_features = wgpu::Features::empty()
48+
| wgpu::Features::SAMPLED_TEXTURE_BINDING_ARRAY
49+
| wgpu::Features::SAMPLED_TEXTURE_ARRAY_DYNAMIC_INDEXING;
4850
let required_features = wgpu::Features::empty();
4951
let adapter_features = adapter.features();
5052
assert!(
@@ -53,15 +55,18 @@ impl App {
5355
required_features - adapter_features
5456
);
5557

56-
let needed_limits = wgpu::Limits::default();
58+
let limits = wgpu::Limits {
59+
max_sampled_textures_per_shader_stage: 1024,
60+
..Default::default()
61+
};
5762

5863
let trace_dir = std::env::var("WGPU_TRACE");
5964
let (device, queue) = adapter
6065
.request_device(
6166
&wgpu::DeviceDescriptor {
6267
label: wgpu::Label::None,
6368
features: (optional_features & adapter_features) | required_features,
64-
limits: needed_limits,
69+
limits,
6570
},
6671
trace_dir.ok().as_ref().map(std::path::Path::new),
6772
)

src/bin/game.rs

Lines changed: 78 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![allow(clippy::single_match)]
2+
#![feature(or_patterns)]
13
extern crate erlking;
24

35
use cgmath::{Deg, Quaternion, Rotation3, Vector3};
@@ -7,7 +9,7 @@ use erlking::{
79
};
810
use glam::Vec3;
911
use hecs::World;
10-
use std::time::Duration;
12+
use std::time::{Duration, Instant};
1113
use winit::{
1214
event::{ElementState, VirtualKeyCode},
1315
event_loop::EventLoop,
@@ -16,6 +18,32 @@ use winit::{
1618
#[derive(Clone, Copy)]
1719
struct MoveSpeed(f32);
1820

21+
enum PlayerState {
22+
Idle,
23+
Walk(Instant),
24+
}
25+
26+
impl PlayerState {
27+
pub fn animation_state(&self, now: Instant) -> u32 {
28+
match self {
29+
Self::Idle => 0,
30+
Self::Walk(start) => {
31+
let animation = vec![(2, 0.2), (1, 0.0)];
32+
let dt = now - *start;
33+
let dt = dt.as_secs_f32() % 0.4;
34+
let mut frame = 0;
35+
for (f, time) in animation {
36+
if dt > time {
37+
frame = f;
38+
break;
39+
}
40+
}
41+
frame
42+
}
43+
}
44+
}
45+
}
46+
1947
fn main() {
2048
let event_loop = EventLoop::new();
2149
let app = futures::executor::block_on(App::new("parallax-demo", &event_loop));
@@ -31,7 +59,11 @@ fn main() {
3159
)),
3260
Scale(1),
3361
KeyboardInput(None),
34-
Sprite("player".to_string()),
62+
Sprite {
63+
id: "player".to_string(),
64+
frame_id: 0,
65+
},
66+
PlayerState::Idle,
3567
movespeed,
3668
);
3769

@@ -55,7 +87,10 @@ fn main() {
5587
Deg(0.0),
5688
)),
5789
Scale(1),
58-
Sprite("apple".to_string()),
90+
Sprite {
91+
id: "apple".to_string(),
92+
frame_id: 0,
93+
},
5994
);
6095

6196
let ashberry = (
@@ -65,7 +100,10 @@ fn main() {
65100
Deg(0.0),
66101
)),
67102
Scale(1),
68-
Sprite("ashberry".to_string()),
103+
Sprite {
104+
id: "ashberry".to_string(),
105+
frame_id: 0,
106+
},
69107
);
70108

71109
let baobab = (
@@ -75,7 +113,10 @@ fn main() {
75113
Deg(0.0),
76114
)),
77115
Scale(1),
78-
Sprite("baobab".to_string()),
116+
Sprite {
117+
id: "baobab".to_string(),
118+
frame_id: 0,
119+
},
79120
);
80121

81122
let beech = (
@@ -85,8 +126,12 @@ fn main() {
85126
Deg(0.0),
86127
)),
87128
Scale(1),
88-
Sprite("beech".to_string()),
129+
Sprite {
130+
id: "beech".to_string(),
131+
frame_id: 0,
132+
},
89133
);
134+
90135
parallax_demo.spawn_entity(player);
91136
parallax_demo.spawn_entity(apple);
92137
parallax_demo.spawn_entity(ashberry);
@@ -95,14 +140,16 @@ fn main() {
95140
parallax_demo.spawn_entity(camera);
96141

97142
parallax_demo.add_system(&move_player);
143+
parallax_demo.add_system(&move_camera);
144+
parallax_demo.add_system(&update_animation_state);
98145

99146
app.run(event_loop, parallax_demo);
100147
}
101148

102-
fn move_player(world: &World, dt: Duration) {
103-
let mut q = world.query::<(&KeyboardInput, &mut Position, &MoveSpeed)>();
149+
fn move_player(world: &World, dt: Duration, instant: Instant) {
150+
let mut q = world.query::<(&KeyboardInput, &mut Position, &MoveSpeed, &mut PlayerState)>();
104151

105-
for (_, (key, pos, speed)) in q.iter() {
152+
for (_, (key, pos, speed, state)) in q.iter() {
106153
if let Some(input) = key.0 {
107154
let dx = Vector3::new(speed.0 * dt.as_secs_f32(), 0.0, 0.0);
108155
match input {
@@ -111,19 +158,32 @@ fn move_player(world: &World, dt: Duration) {
111158
virtual_keycode: Some(VirtualKeyCode::Left),
112159
..
113160
} => {
161+
match state {
162+
PlayerState::Idle => *state = PlayerState::Walk(instant),
163+
_ => (),
164+
}
114165
pos.0 -= dx;
115166
}
116167
winit::event::KeyboardInput {
117168
state: ElementState::Pressed,
118169
virtual_keycode: Some(VirtualKeyCode::Right),
119170
..
120171
} => {
172+
match state {
173+
PlayerState::Idle => *state = PlayerState::Walk(instant),
174+
_ => (),
175+
}
121176
pos.0 += dx;
122177
}
123-
_ => (),
178+
_ => *state = PlayerState::Idle,
124179
}
180+
} else {
181+
*state = PlayerState::Idle;
125182
}
126183
}
184+
}
185+
186+
fn move_camera(world: &World, dt: Duration, _instant: Instant) {
127187
let mut q = world.query::<(
128188
&ActiveCamera,
129189
&mut ParallaxCamera,
@@ -153,3 +213,11 @@ fn move_player(world: &World, dt: Duration) {
153213
}
154214
}
155215
}
216+
217+
fn update_animation_state(world: &World, _dt: Duration, instant: Instant) {
218+
let mut q = world.query::<(&PlayerState, &mut Sprite)>();
219+
220+
for (_, (state, sprite)) in q.iter() {
221+
sprite.frame_id = state.animation_state(instant);
222+
}
223+
}

src/gpu_primitives.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,14 @@ pub struct Instance {
4040
pub position: cgmath::Vector3<f32>,
4141
pub rotation: cgmath::Quaternion<f32>,
4242
pub scale: f32,
43+
pub frame_id: u32,
4344
}
4445

4546
#[repr(C)]
4647
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
4748
pub struct InstanceRaw {
4849
model: [[f32; 4]; 4],
50+
frame_id: u32,
4951
}
5052

5153
impl From<Instance> for InstanceRaw {
@@ -55,6 +57,7 @@ impl From<Instance> for InstanceRaw {
5557
* cgmath::Matrix4::from(from.rotation)
5658
* cgmath::Matrix4::from_scale(from.scale))
5759
.into(),
60+
frame_id: from.frame_id,
5861
}
5962
}
6063
}
@@ -86,6 +89,11 @@ impl InstanceRaw {
8689
shader_location: 5,
8790
format: wgpu::VertexFormat::Float4,
8891
},
92+
wgpu::VertexAttribute {
93+
offset: mem::size_of::<[f32; 16]>() as wgpu::BufferAddress,
94+
shader_location: 6,
95+
format: wgpu::VertexFormat::Uint,
96+
},
8997
],
9098
}
9199
}

src/lib.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ use crate::{
88
};
99
use cgmath::{Quaternion, Vector3};
1010
use hecs::{DynamicBundle, Entity, World};
11-
use std::{collections::HashMap, time::Duration};
11+
use std::{
12+
collections::HashMap,
13+
time::{Duration, Instant},
14+
};
1215
use winit::event::WindowEvent;
1316

14-
mod app;
17+
pub mod app;
1518
pub mod camera;
1619
mod gpu_primitives;
1720
mod renderer;
@@ -25,13 +28,16 @@ pub use app::App;
2528
pub struct Position(pub Vector3<f32>);
2629
pub struct Rotation(pub Quaternion<f32>);
2730
pub struct Scale(pub u8);
28-
pub struct Sprite(pub String);
31+
pub struct Sprite {
32+
pub id: String,
33+
pub frame_id: u32,
34+
}
2935
pub struct KeyboardInput(pub Option<winit::event::KeyboardInput>);
3036

3137
pub struct Game<'a> {
3238
world: World,
3339
timer: Timer,
34-
systems: Vec<&'a dyn Fn(&World, Duration)>,
40+
systems: Vec<&'a dyn Fn(&World, Duration, Instant)>,
3541
}
3642

3743
impl<'a> Game<'a> {
@@ -45,35 +51,36 @@ impl<'a> Game<'a> {
4551
fn run(&mut self) -> Scene {
4652
self.timer.tick();
4753
for system in self.systems.iter() {
48-
system(&self.world, self.timer.elapsed())
54+
system(&self.world, self.timer.elapsed(), self.timer.now())
4955
}
5056
self.build_scene()
5157
}
5258
pub fn spawn_entity(&mut self, components: impl DynamicBundle) -> Entity {
5359
self.world.spawn(components)
5460
}
55-
pub fn add_system(&mut self, system: &'a dyn Fn(&World, Duration)) {
61+
pub fn add_system(&mut self, system: &'a dyn Fn(&World, Duration, Instant)) {
5662
self.systems.push(system)
5763
}
5864
fn build_scene(&mut self) -> Scene {
5965
let mut sprites: HashMap<String, Vec<InstanceRaw>> = HashMap::default();
6066

61-
for (_, (pos, rot, scale, sprite_id)) in
62-
&mut self
63-
.world
64-
.query::<(&Position, &Rotation, &Scale, &Sprite)>()
67+
for (_, (pos, rot, scale, sprite)) in &mut self
68+
.world
69+
.query::<(&Position, &Rotation, &Scale, &Sprite)>()
6570
{
6671
let instance_raw = InstanceRaw::from(Instance {
6772
position: pos.0,
6873
rotation: rot.0,
6974
scale: scale.0 as f32,
75+
frame_id: sprite.frame_id,
7076
});
71-
if let Some(instances) = sprites.get(&sprite_id.0) {
77+
if let Some(instances) = sprites.get(&sprite.id) {
78+
// TODO: try and remove these clones
7279
let mut new = instances.clone();
7380
new.push(instance_raw);
74-
sprites.insert(sprite_id.0.clone(), new);
81+
sprites.insert(sprite.id.clone(), new);
7582
} else {
76-
sprites.insert(sprite_id.0.clone(), vec![instance_raw]);
83+
sprites.insert(sprite.id.clone(), vec![instance_raw]);
7784
}
7885
}
7986

0 commit comments

Comments
 (0)