From 79f223cb21fbcc43f521fd08ab57720bb1a21758 Mon Sep 17 00:00:00 2001 From: Sofia Date: Mon, 20 Jul 2026 01:46:51 +0300 Subject: [PATCH] Add proper shooting effect --- godot/assets/particle.png | Bin 0 -> 4344 bytes godot/assets/particle.png.import | 41 ++++++++++++++ godot/scenes/map0.tscn | 6 +- godot/scenes/player/local_player.tscn | 8 +-- godot/scenes/player/raygun.tscn | 8 ++- godot/scenes/player/raygun_bullet.tscn | 40 +++++++++++++ godot/scenes/player/remote_player.tscn | 6 +- rust/src/game_manager.rs | 6 +- rust/src/net/network_manager.rs | 23 +++++--- rust/src/player.rs | 74 +++++++++++++++++++------ rust/src/weapon.rs | 54 +++++++++++++++++- 11 files changed, 226 insertions(+), 40 deletions(-) create mode 100644 godot/assets/particle.png create mode 100644 godot/assets/particle.png.import create mode 100644 godot/scenes/player/raygun_bullet.tscn diff --git a/godot/assets/particle.png b/godot/assets/particle.png new file mode 100644 index 0000000000000000000000000000000000000000..e217c17c6478ac12832eaa4484353416703de4b5 GIT binary patch literal 4344 zcmVz@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy24YJ`L;wH)0002_L%V+f000SaNLh0L04^f{04^f|c%?sf00007 zbV*G`2kHkC6gU?IKmr!R9( z&(6bCM0ee?GSd_Z0w7KNO&^jPga)pk5TZZ<0tEulXsu2BjUW5(dg3|NNbL{^kPw=v zjgEjI0Pt0VBqR}-l4gE4K~1vt-x&b@zn=0`19AAG(Ev0M4Z`N{1&}R|27LSksQk?k zG)c-|Lxcu|31G0b@sA9EGd1F%EfK?F^>6$GW(1l|`QL)0PT>D1gZM| zeuJQxE+E%F9!&u9C(QiofOZg5&_!Vg*a&X8aRrbXj6cn>`n4eJzku?u)cXPAmG6D( zQnH|e0p@obgk4}GK)CGNo%nS2-zA_Rz9d{ECcr^wRHs-V_-L~{({3R^(!{TDhy5qO zZin;|@Fo)g=mqfwVh8bpc!1igBLLh9Z2`x|%7Dko5xGnNB=Kl>5O|T;Ax=;oCy`NCJ3)RgT{s?DtPPF#pAnf1e?!Zpya2>Ny{srL$>J4It z@N(_*?Gn)LzY8aTwy?oEy)DU_JV-kX*-6?z`oDtwpzEC*h>x_~)6XJ4$kYTd2@GjY z^84E2&jSGE1X(BlhID{@LwZSkk=RAOD83CuIvfxjGT3bngi8TJ%jrE{XJLVvpHE%@ zx;qUnh!unmrE{2Z`pflHDp0AXo&e5Y4uGVzHNLC10c-+X3gnBv(4F8L#7ojn2kHQ@ zQ`%A*Te6}#2y{CP2jCryDPWRH`@>G6r{A}<)iWobKO%27x%lN=G+KOX>bul={}$1~ z>hDfx2yaj?l1H^Vfd_;aftLfr1Jn!Pmb!hX6}l}F4JJR$eZMkS#R4ZmK^%U(I^ZDW zdA3|+w1fcg1JDu9FaeB+>;ZDf3UmKnDSRUXR;?et5bl6BC}f9x!QB7wW9aT+A#@~I zyMTj=KETom4nn1BUA)h60vK?=-w6))f0nBJ2(LI3z#RQkPH= zl*tjfK?p}sgc9QlslgaFe0ZFII>cQ<@qP*tXK-i7`G!AHS`%P4%sytR(d;xr?6}nSUuVheIV}iI!NpwkA6_On0{}jLjK&M~( z1kgw{fMYI9B$=)m9Z;LwrSpbauAZ=f5m97#t5(j-DD6N_Um;Ki z&V5RI`=S+|8PFsgqz;o6wzQ%nEv&SEU3itlk+!%wK&Xp<V4Tf*&+3iuFBXRQ2vtu7-0#q3ED}R!GY~od|=$3 zBR=1;aKPOC4TdBc7~gsPOXN-~MFtSJGy`h@a00rbFU}kIcw2|nDU3N2;7BJ5W8`rM z#9l;7tvt#0%@PRhZAMI`?t<7wLhSAyD^QgU)B*5t>oRE~V2z-32k^zp7HfpX2%}sk zT(69hJD{{Pxx>%x#JVK>gJc&(YlhpX=2K&;~$cdY%;lfn8)Jfjc(~5FgEF#%!>vz8 zx<;e5M>CkJmrvkU3Q{Z?{Urb>g5ry`@t_)FM0rKftPGI3f_2>nU>C6y*dG1=m0%bX z%nuGomdQ3q?DQy^#_+;;12n+db2LBbAyb<(>CW7|*A3LL2cgU(&?vZ*edz}KV>}U^ zktQQbs6U9dU-iT-5_h9XQUlTK*61kIT_6bVB3?9RywVbztsn`+1@QvZXi_wCO8d$L z17x{BM?kQ$lAi<@D3;r9*QvRZuq`V6xdbd!cw1Ht!p3D{@HQjdj&59Fxif4j*v6!~ zgQ6Y4o8lqhMJ`XFEfaLvTkt%^Qd4F5#caw6uF3+~W&hN`WPBq$wYjz&eQkDt* zIRm)FxwL%Rn;=GWQZ?rC6WN(bdH7A#9Dp?_afT4C{nrB=TUvXezvE})b5E*o_jE~2 z%E$lUW2SQZORV3np$nc5#H}N$_fPrc?h~#qkbf0j7CtN!GU>Dhbs3^`cMhKl(aJia)=&pBay zt`*tOWC&}l#OHJ)gBBkoKaJib=DBXvstjOjQ-ec#)ME^=d((WQZ~!vxB@}POK^Ny?2mapaQYz zoKG;Ye;N?oStRny0IAG7Kl$xJmA6rEsN5N8{ar0THEvKnn+fra)#@3Gvk;bP< zuI=wVMCZy|-`2F)nO5%al`b$KT@VNBb#AZE@5|URtOxM$2mJWT`7+)UCw(0T78S0G znG4KrMn*gh2Pgrd168v-r}s$7^ip6sv-57O8UV8`;_CEs$MXkHw?+0mPrnDH?D^eD zDOw}kZgS?Va%K@CHR++-SmpZ-HF%%sb1q-Q%F2lQF{$r0q|D#~J(hT<3-NwU?LWWQ1KTSRDn0v9G? zM4jj!F*4TR(ibuXTrBI`TKu9G4>)h{(}9)P$$DMZfW&0Tr*wflDV~tdOokI&c7tTH z!LK(~2D==(%VCvD9cVlU{;>qeHZ`YQ|IBI%lzQ$KIjrGjkJ1|eaxolHWHr4h$JYJF z|7{?4N_+HsP>X~#^Ro%@^eT)_5htrGO7p8SO+Cg)&k5fLDD;I7Io#mDpEus0u9RlG#) z@%_5QMn;}KMpcU_uR(`q9HZ?2=!$k=F%6SOJn<(RAi7fbCxnk&;*Fe?Avrdzzz>C5R5FOS}kAjY*z1KfN#ov4S>0U6~scPZ>WRj0jg8mFmAu zV9&_4booaHl?)Cn`b~b?fgQB66NFvVMLdnxPom^OZ8>jB9GBPwjh;Z9-Vwm%6Q&=F zM<9#~Gi~|Gzy8C~7#pWFdj_1{h6b*{R5Ac9Z$Qk_3rn(?e9aK2_g&ZAGtJoU=iqZD zH$cjWtr0$8a^%@SvC1?2;`GXl&bUb*B03S=;JV<#f* ztcaC@;aOt(a#`l$#oMezd<6$^S>r;OWKaLDi@pR63LhzpGpzASZC{=yWos;WAnBxo z)8&Bt+<4qspi9u^)~2&OzX0CHLxa~U!i@Twkw>qnxt^(VODE`&VlgaP3mzkzE)!BW zi&Gal8K4t%5xBr)gfZwN!1$5Qb%jZGRu)iK`_7igHy0#Gc9%l!_uyl-wc8vhyicDe zVBTFsYR{ff&t#})!UPG8U)E$9O=1O*RF!2Z;9~B0}fl538IuNTR1gTDYmSCvp;5mPQCq)?)>9x*cX& zQq>ZxRbE<(GD?x(-U;}r0N5xA`?@zWK7mETFa^GBl~VK=DpR8fCGjRpevXZ0!n+$x z5u~N=$UOvC+E<6rsZxj-06su=L8n1lCkX2YfBymU{4?PD={32O!fmed>9pGQqqTRX z?g-+KY=s=vPRmwj+3*iPOD-MUhDuy1qMO#%qSR{7XX^YTT_#LXXNjht-5CAhq9ob6 zH%DTd*LJ56$Hi6@t+tLeIc9N(b-%zCW0N-qSPw6?NIWb#j96tBT3~-j?gt*Ar!AfU zFG*)>Kfgi#Q>(IMJ2fY2p$~`|;+FHOjf`;m9xcDG^|m#@@I;B_r7?Gk*sOhcjEtP+ z#NzUc6!mq{P)X#E%8J(7FRjo2^s+QtJEbh<0D~4WBRzj#Tb-Dg|`{Mqe?-}SnT8I68_YA2PplTHkvBuceb|}_eV_M3Q zthBF@h8HDb%z(6sAzkANvjj&=75aRQ2vqXB|9<@Y@$bhkef$>#YXiz9oH%9x0000 bool { if let Some(player) = self.find_player_mut(player_id) { - player.try_shoot(transform, look_up, deal_damage) + player.try_shoot(to, transform, look_up, deal_damage) } else { false } @@ -432,6 +433,7 @@ impl Player { pub fn try_shoot( &mut self, + to: Vector3, transform: NetPlayerTransform, look_up: f32, deal_damage: bool, @@ -441,7 +443,7 @@ impl Player { .dyn_bind_mut() .apply_rotation(transform.rotation.into()); character.dyn_bind_mut().apply_look_up(look_up); - character.dyn_bind_mut().try_shoot(true) + character.dyn_bind_mut().try_shoot(to, true) } else { false } diff --git a/rust/src/net/network_manager.rs b/rust/src/net/network_manager.rs index 6ac4b9c..1fbaded 100644 --- a/rust/src/net/network_manager.rs +++ b/rust/src/net/network_manager.rs @@ -227,10 +227,15 @@ impl INode for NetworkManager { game.bind_mut().handle_jump(player_id); } } - Package::Shoot(player_id, transform, look_up) => { + Package::Shoot(player_id, to, transform, look_up) => { if let Some(game) = &mut Game::singleton() { - game.bind_mut() - .handle_shoot(player_id, transform, look_up, false); + game.bind_mut().handle_shoot( + player_id, + to.into(), + transform, + look_up, + false, + ); } } _ => {} @@ -384,7 +389,7 @@ impl INode for NetworkManager { } } } - Package::Shoot(_, transform, look_up) => { + Package::Shoot(_, to, transform, look_up) => { if let Some(game) = &mut Game::singleton() { let player = game .bind() @@ -392,10 +397,14 @@ impl INode for NetworkManager { .cloned(); if let Some(player) = player { if game.bind_mut().handle_shoot( - player.id, transform, look_up, true, + player.id, + to.into(), + transform, + look_up, + true, ) { peer.broadcast_reliable(Package::Shoot( - player.id, transform, look_up, + player.id, to, transform, look_up, )); } } @@ -531,7 +540,7 @@ pub enum Package { Sync(HashMap), SelfSync(SyncPackage), Jump(u16), - Shoot(u16, NetPlayerTransform, f32), + Shoot(u16, NetVector3, NetPlayerTransform, f32), } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/rust/src/player.rs b/rust/src/player.rs index a39afdb..2dc01dd 100644 --- a/rust/src/player.rs +++ b/rust/src/player.rs @@ -1,9 +1,9 @@ -use std::f32::consts::PI; +use std::{f32::consts::PI, ops::Neg}; use godot::{ classes::{ AudioStreamPlayer3D, Camera3D, CharacterBody3D, ICharacterBody3D, Input, InputEvent, - InputEventMouseMotion, input::MouseMode, + InputEventMouseMotion, PhysicsRayQueryParameters3D, input::MouseMode, }, obj::WithBaseField, prelude::*, @@ -43,7 +43,7 @@ pub trait IPlayer { fn get_look_up(&self) -> f32; fn get_transform(&self) -> NetPlayerTransform; fn jump(&mut self); - fn try_shoot(&mut self, deal_damage: bool) -> bool; + fn try_shoot(&mut self, to: Vector3, deal_damage: bool) -> bool; } pub trait PlayerMovement { @@ -154,10 +154,10 @@ impl IPlayer for LocalPlayer { fn apply_look_up(&mut self, _look: f32) {} - fn try_shoot(&mut self, deal_damage: bool) -> bool { + fn try_shoot(&mut self, to: Vector3, deal_damage: bool) -> bool { if self.shoot_cd <= 0. { if let Some(weapon) = &mut self.weapon { - weapon.bind_mut().shoot(Vector3::ZERO, deal_damage); + weapon.bind_mut().shoot(to, deal_damage); } self.shoot_cd = 0.2; true @@ -219,18 +219,56 @@ impl ICharacterBody3D for LocalPlayer { fn input(&mut self, event: Gd) { if self.locked { if event.is_action_pressed("shoot") { - if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer { - match peer { - PeerKind::Client(peer, addr) => { - peer.send_reliable(addr, Shoot(0, self.get_transform(), self.look_up)); + let pos = if let Some(camera) = &self.camera { + let forward = camera.get_global_basis().col_c().neg(); + let ray = PhysicsRayQueryParameters3D::create( + camera.get_global_position(), + camera.get_global_position() + forward * 10000., + ); + if let Some(mut ray) = ray { + ray.set_collision_mask(0b110); + ray.set_hit_back_faces(false); + ray.set_hit_from_inside(false); + ray.set_collide_with_bodies(true); + if let Some(world) = self.base().get_world_3d() + && let Some(mut space) = world.get_direct_space_state() + { + let results = space.intersect_ray(&ray); + if let Some(position) = results.get("position") { + Some(Vector3::from_variant(&position)) + } else { + Some(camera.get_global_position() + forward * 100.) + } + } else { + None } - PeerKind::Server(peer, _) => { - if self.try_shoot(true) { - peer.broadcast_reliable(Shoot( - 0, - self.get_transform(), - self.look_up, - )); + } else { + None + } + } else { + None + }; + + godot_print!("{:?}", pos); + + if let Some(pos) = pos { + if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer { + match peer { + PeerKind::Client(peer, addr) => { + peer.send_reliable( + addr, + Shoot(0, pos.into(), self.get_transform(), self.look_up), + ); + } + PeerKind::Server(peer, _) => { + if self.try_shoot(pos, true) { + peer.broadcast_reliable(Shoot( + 0, + pos.into(), + self.get_transform(), + self.look_up, + )); + } } } } @@ -343,10 +381,10 @@ impl IPlayer for RemotePlayer { self.look_up = look; } - fn try_shoot(&mut self, deal_damage: bool) -> bool { + fn try_shoot(&mut self, to: Vector3, deal_damage: bool) -> bool { if self.shoot_cd <= 0. { if let Some(weapon) = &mut self.weapon { - weapon.bind_mut().shoot(Vector3::ZERO, deal_damage); + weapon.bind_mut().shoot(to, deal_damage); } self.shoot_cd = 0.2; true diff --git a/rust/src/weapon.rs b/rust/src/weapon.rs index 8359126..14676b9 100644 --- a/rust/src/weapon.rs +++ b/rust/src/weapon.rs @@ -1,10 +1,17 @@ -use godot::{classes::AudioStreamPlayer3D, prelude::*}; +use godot::{ + classes::{AudioStreamPlayer3D, GpuParticles3D, PhysicsRayQueryParameters3D}, + prelude::*, +}; #[derive(GodotClass)] #[class(base=Node3D, init)] pub struct Weapon { #[export] sound_emitter: Option>, + #[export] + bullet_prefab: Option>, + #[export] + bullet_source: Option>, base: Base, } @@ -14,5 +21,50 @@ impl Weapon { if let Some(emitter) = &mut self.sound_emitter { emitter.play(); } + + if let Some(prefab) = &self.bullet_prefab { + let mut bullet = prefab.instantiate_as::(); + if let Some(mut parent) = self.base().find_parent("map") { + parent.add_child(&bullet); + if let Some(source) = &self.bullet_source { + bullet.set_global_position(source.get_global_position()); + } + } + bullet.bind_mut().target = Some(to); + } + } +} + +#[derive(GodotClass)] +#[class(base=Node3D, init)] +pub struct Bullet { + #[export] + pub emitter: Option>, + + pub target: Option, + + alive_for: f64, + + base: Base, +} + +#[godot_api] +impl INode3D for Bullet { + fn ready(&mut self) { + if let Some(emitter) = &mut self.emitter { + emitter.set_emitting(true); + } + } + + fn process(&mut self, delta: f64) { + self.alive_for += delta; + if self.alive_for > 0.05 { + let prev_pos = self.base().get_global_position(); + + if let Some(target) = self.target { + let new_pos = prev_pos.lerp(target, 0.5); + self.base_mut().set_position(new_pos); + } + } } }