1use cuneus::compute::*;
4use cuneus::prelude::*;
5use winit::event::ElementState;
6
7#[repr(C)]
8#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
9struct BlockGameParams {
10 game_state: i32,
12 score: u32,
13 current_block: u32,
14 total_blocks: u32,
15
16 block_x: f32,
17 block_y: f32,
18 block_z: f32,
19
20 block_width: f32,
21 block_height: f32,
22 block_depth: f32,
23
24 movement_speed: f32,
25 movement_range: f32,
26 drop_triggered: i32,
27
28 camera_height: f32,
29 camera_angle: f32,
30 camera_scale: f32,
31
32 perfect_placement: i32,
34 game_over: i32,
35
36 _padding: [f32; 2],
37}
38
39impl Default for BlockGameParams {
40 fn default() -> Self {
41 Self {
42 game_state: 0,
43 score: 0,
44 current_block: 0,
45 total_blocks: 1,
46
47 block_x: 0.0,
48 block_y: 1.0,
49 block_z: 0.0,
50
51 block_width: 3.0,
52 block_height: 0.6,
53 block_depth: 3.0,
54
55 movement_speed: 2.0,
56 movement_range: 2.5,
57 drop_triggered: 0,
58
59 camera_height: 0.0,
60 camera_angle: 0.0,
61 camera_scale: 65.0,
62
63 perfect_placement: 0,
64 game_over: 0,
65
66 _padding: [0.0; 2],
67 }
68 }
69}
70
71impl UniformProvider for BlockGameParams {
72 fn as_bytes(&self) -> &[u8] {
73 bytemuck::bytes_of(self)
74 }
75}
76
77struct BlockTowerGame {
78 base: RenderKit,
79 compute_shader: ComputeShader,
80 last_mouse_click: bool,
81 game_params: BlockGameParams,
82}
83
84impl ShaderManager for BlockTowerGame {
85 fn init(core: &Core) -> Self {
86 let base = RenderKit::new(core);
87
88 let config = ComputeShader::builder()
90 .with_entry_point("main")
91 .with_mouse()
92 .with_fonts()
93 .with_audio(1024) .with_workgroup_size([8, 8, 1])
95 .with_texture_format(COMPUTE_TEXTURE_FORMAT_RGBA16)
96 .with_label("Block Tower Game Unified")
97 .build();
98
99 let compute_shader = cuneus::compute_shader!(core, "shaders/blockgame.wgsl", config);
100
101
102 Self {
103 base,
104 compute_shader,
105 last_mouse_click: false,
106 game_params: BlockGameParams::default(),
107 }
108 }
109
110 fn update(&mut self, core: &Core) {
111 let current_time = self.base.controls.get_time(&self.base.start_time);
112 let delta = 1.0 / 60.0;
113 self.compute_shader
114 .set_time(current_time, delta, &core.queue);
115 self.compute_shader
116 .update_mouse_uniform(&self.base.mouse_tracker.uniform, &core.queue);
117
118 self.update_camera_in_shader(&core.queue);
119 let mouse_buttons = self.base.mouse_tracker.uniform.buttons[0];
120 let mouse_pressed = mouse_buttons & 1 != 0;
121 self.last_mouse_click = mouse_pressed;
122 }
123
124 fn resize(&mut self, core: &Core) {
125 self.base.default_resize(core, &mut self.compute_shader);
126 }
127
128 fn render(&mut self, core: &Core) -> Result<(), cuneus::SurfaceError> {
129 let mut frame = self.base.begin_frame(core)?;
130 let _controls_request = self
131 .base
132 .controls
133 .get_ui_request(&self.base.start_time, &core.size, self.base.fps_tracker.fps());
134
135 let full_output = if self.base.key_handler.show_ui {
136 self.base.render_ui(core, |ctx| {
137 RenderKit::apply_default_style(ctx);
138 egui::Window::new("Block Tower")
139 .collapsible(true)
140 .resizable(true)
141 .default_width(220.0)
142 .show(ctx, |ui| {
143 egui::CollapsingHeader::new("Camera")
144 .default_open(true)
145 .show(ui, |ui| {
146 ui.add(
147 egui::Slider::new(
148 &mut self.game_params.camera_height,
149 0.0..=20.0,
150 )
151 .text("Height"),
152 );
153 ui.add(
154 egui::Slider::new(
155 &mut self.game_params.camera_angle,
156 -3.14159..=3.14159,
157 )
158 .text("Angle"),
159 );
160 ui.add(
161 egui::Slider::new(
162 &mut self.game_params.camera_scale,
163 20.0..=200.0,
164 )
165 .text("Scale"),
166 );
167
168 ui.separator();
169 ui.label("Controls:");
170 ui.label("Q/E: Move up/down");
171 ui.label("W/S: Rotate left/right");
172
173 ui.separator();
174 ui.label("Scale presets:");
175 ui.horizontal(|ui| {
176 if ui.button("1080p").clicked() {
177 self.game_params.camera_scale = 50.0;
178 }
179 if ui.button("1440p").clicked() {
180 self.game_params.camera_scale = 65.0;
181 }
182 if ui.button("4K").clicked() {
183 self.game_params.camera_scale = 100.0;
184 }
185 });
186
187 if ui.button("Reset Camera").clicked() {
188 self.game_params.camera_height = 8.0;
189 self.game_params.camera_angle = 0.0;
190 self.game_params.camera_scale = 65.0;
191 }
192 });
193 });
194 })
195 } else {
196 self.base.render_ui(core, |_ctx| {})
197 };
198
199
200 self.compute_shader.dispatch(&mut frame.encoder, core);
201
202 self.base.renderer.render_to_view(&mut frame.encoder, &frame.view, &self.compute_shader.get_output_texture().bind_group);
203
204 self.base.end_frame(core, frame, full_output);
205 Ok(())
206 }
207
208 fn handle_input(&mut self, core: &Core, event: &WindowEvent) -> bool {
209 let ui_handled = self
210 .base
211 .egui_state
212 .on_window_event(core.window(), event)
213 .consumed;
214
215 if self.base.handle_mouse_input(core, event, ui_handled) {
216 return true;
217 }
218
219 if let WindowEvent::KeyboardInput { event, .. } = event {
220 if let winit::keyboard::PhysicalKey::Code(key_code) = event.physical_key {
221 if event.state == ElementState::Pressed {
222 let camera_speed = 0.5;
223
224 match key_code {
225 winit::keyboard::KeyCode::KeyQ => {
226 self.game_params.camera_height += camera_speed;
227 return true;
228 }
229 winit::keyboard::KeyCode::KeyE => {
230 self.game_params.camera_height -= camera_speed;
231 return true;
232 }
233 winit::keyboard::KeyCode::KeyW => {
234 self.game_params.camera_angle += 0.1;
235 return true;
236 }
237 winit::keyboard::KeyCode::KeyS => {
238 self.game_params.camera_angle -= 0.1;
239 return true;
240 }
241 _ => {}
242 }
243 }
244 }
245 return self
246 .base
247 .key_handler
248 .handle_keyboard_input(core.window(), event);
249 }
250
251 false
252 }
253}
254
255impl BlockTowerGame {
256 fn update_camera_in_shader(&self, queue: &wgpu::Queue) {
257 if let Some(audio_buffer) = self.compute_shader.get_audio_buffer() {
258 let camera_data = [
259 self.game_params.camera_height,
260 self.game_params.camera_angle,
261 self.game_params.camera_scale,
262 ];
263
264 let camera_data_bytes = bytemuck::cast_slice(&camera_data);
265 let offset = 5 * std::mem::size_of::<f32>();
266
267 queue.write_buffer(audio_buffer, offset as u64, camera_data_bytes);
268 }
269 }
270}
271
272fn main() -> Result<(), Box<dyn std::error::Error>> {
273 env_logger::init();
274 cuneus::gst::init()?;
275
276 let (app, event_loop) = ShaderApp::new("Block Tower Game", 600, 800);
277
278 app.run(event_loop, BlockTowerGame::init)
279}