From 0b8091b352048024d9c9ab7f00d88ebdfa50a200 Mon Sep 17 00:00:00 2001 From: Sebastian Benjamin Date: Tue, 25 Feb 2025 20:11:05 -0800 Subject: [PATCH] Server-based collision and player death --- client/danmaku!/Board.tscn | 1 + client/danmaku!/ScalableSprite2D.gd | 6 ++++ client/danmaku!/network_manager.gd | 25 ++++++++++++---- client/danmaku!/player.gd | 33 ++++++++++++++++++++- client/danmaku!/player.tscn | 12 ++++++-- client/test-bullet.png | Bin 743 -> 1255 bytes client/test-collision.png | Bin 0 -> 1095 bytes ffi-wrapper/src/ffi.rs | 2 +- server/main.go | 43 ++++++++++++++++++---------- 9 files changed, 96 insertions(+), 26 deletions(-) create mode 100644 client/danmaku!/ScalableSprite2D.gd create mode 100644 client/test-collision.png diff --git a/client/danmaku!/Board.tscn b/client/danmaku!/Board.tscn index 20a1659..dd95b89 100644 --- a/client/danmaku!/Board.tscn +++ b/client/danmaku!/Board.tscn @@ -30,6 +30,7 @@ unique_name_in_owner = true scale = Vector2(6.75, 6.75) [node name="Player" parent="World" instance=ExtResource("1_22cjd")] +unique_name_in_owner = true [node name="NetworkManager" type="Node2D" parent="World" node_paths=PackedStringArray("player")] script = ExtResource("2_b2dol") diff --git a/client/danmaku!/ScalableSprite2D.gd b/client/danmaku!/ScalableSprite2D.gd new file mode 100644 index 0000000..5eff03d --- /dev/null +++ b/client/danmaku!/ScalableSprite2D.gd @@ -0,0 +1,6 @@ +class_name ScalableSprite2D +extends Sprite2D + +func scale_sprite(world_space_multiple: float): + var scale_factor = (world_space_multiple * Globals.SERVER_SIZE.x) / self.texture.get_width() + self.scale = Vector2(scale_factor, scale_factor) diff --git a/client/danmaku!/network_manager.gd b/client/danmaku!/network_manager.gd index caa9214..cc21624 100644 --- a/client/danmaku!/network_manager.gd +++ b/client/danmaku!/network_manager.gd @@ -39,12 +39,21 @@ func _on_match_state(p_state : NakamaRTAPI.MatchData): match p_state.op_code: 2: var data = JSON.parse_string(p_state.data) - print(data) - + # Set player position given server bounds-checking if data["forcePlayerPos"]: - player.position = Vector2(float(data["playerPos"]["x"]), float(data["playerPos"]["y"])) - + player.set_position_data( + Vector2( + float(data["playerPos"]["x"]), + float(data["playerPos"]["y"]) + ), + float(data["playerPos"]["radius_multiplier"]) + ) + + if int(data["deathTimer"]) > 0: + print("server says u died") + %Player.kill() + # Spawn new bullets for b in data["newBullets"]: var bullet = DanmakuBullet.new() @@ -53,12 +62,16 @@ func _on_match_state(p_state : NakamaRTAPI.MatchData): int(b["tick"]), b["x"], b["y"], - b["radius"], + b["radius_multiplier"] * Globals.SERVER_SIZE.x, b["vel_x"], b["vel_y"]) bullet.texture = load("res://test-bullet.png") bullet.position = bullet.get_current_pos(int(b["tick"])) - bullet.scale = Vector2(0.2, 0.2) + + # Reimplemented from ScalableSprite2D here atm + var scale_ratio = ((b["radius_multiplier"] * 2) * Globals.SERVER_SIZE.x) / bullet.texture.get_width() + bullet.scale = Vector2(scale_ratio, scale_ratio) + add_child(bullet) bullets.append(bullet) predicted_tick = int(b["tick"]) diff --git a/client/danmaku!/player.gd b/client/danmaku!/player.gd index e78d73d..2e13b85 100644 --- a/client/danmaku!/player.gd +++ b/client/danmaku!/player.gd @@ -3,7 +3,18 @@ extends Node2D @export var speed = 80 var velocity := Vector2.ZERO +var collision: DanmakuCircle = DanmakuCircle.new() +# This is temporary, it should be defined per-sprite when I get to the skin system +const PLAYER_BODY_WIDTH_MULTIPLIER = 0.18 + +# Temp +var flash_timer = 0.0 +var flashing = false + +func _ready() -> void: + $BodySprite.scale_sprite(PLAYER_BODY_WIDTH_MULTIPLIER) + func get_input(): if Input.is_action_pressed("Slow Mode"): speed = 30 @@ -13,10 +24,30 @@ func get_input(): velocity = Input.get_vector("Left", "Right", "Up", "Down") * speed func _physics_process(delta: float): + # Temp + if flashing: + flash_timer -= delta + $BodySprite.modulate = Color(1, 1, 1, 1) + flashing = false + get_input() # Bounds checking var attempted_position := position + (velocity * delta) attempted_position = attempted_position.clamp(Vector2(0, 0), Globals.SERVER_SIZE) - position = attempted_position + set_position_data(attempted_position, null) + +func set_position_data(pos: Vector2, hurtcircle_scale_multiplier): + position = pos + collision.set_position(pos.x, pos.y) + + if hurtcircle_scale_multiplier: + collision.set_radius(Globals.SERVER_SIZE.x*hurtcircle_scale_multiplier) + $HurtcircleSprite.scale_sprite(hurtcircle_scale_multiplier*2) + +func kill(): + # Temp + $BodySprite.modulate = Color(1, 0, 0, 1) + flash_timer = 0.5 + flashing = true diff --git a/client/danmaku!/player.tscn b/client/danmaku!/player.tscn index 1cdb5a6..e49a7e2 100644 --- a/client/danmaku!/player.tscn +++ b/client/danmaku!/player.tscn @@ -1,11 +1,17 @@ -[gd_scene load_steps=3 format=3 uid="uid://cd3tqt7hr5pqs"] +[gd_scene load_steps=5 format=3 uid="uid://cd3tqt7hr5pqs"] [ext_resource type="Script" path="res://danmaku!/player.gd" id="1_r7xhp"] [ext_resource type="Texture2D" uid="uid://bs3fntlmlqpt2" path="res://icon.svg" id="2_04s0l"] +[ext_resource type="Texture2D" uid="uid://c3deywcu4du2b" path="res://test-collision.png" id="3_gf44i"] +[ext_resource type="Script" path="res://danmaku!/ScalableSprite2D.gd" id="3_u0x7w"] [node name="Player" type="Node2D"] script = ExtResource("1_r7xhp") -[node name="Sprite2D" type="Sprite2D" parent="."] -scale = Vector2(0.09, 0.09) +[node name="BodySprite" type="Sprite2D" parent="."] texture = ExtResource("2_04s0l") +script = ExtResource("3_u0x7w") + +[node name="HurtcircleSprite" type="Sprite2D" parent="."] +texture = ExtResource("3_gf44i") +script = ExtResource("3_u0x7w") diff --git a/client/test-bullet.png b/client/test-bullet.png index 6e2028c84844441bf238f0eefbecdd4eea7db9f8..5f44ab345a851171b0fe1b45b27ca7064230bfcb 100644 GIT binary patch delta 1250 zcmV<81ReY51?LGNiBL{Q4GJ0x0000DNk~Le0000$0000$2nGNE0IF$m-jN{}e*~FH zL_t(|0qvVjh#XZEh40L0%tQr6AsPcBF$NJ)HwuE%C>T_5Axc1Tl~I%tbmc-^+=aMs zWgvkNlT~mL6c8k1jr{Akv z@8_OZ@2|S5rfe19W;>~7f5EEef5>;BV4lLsFDRTNCxe?k$m#1MN$!r=2AQ_I*=!`y z3G9a>_EM&)Qn|XB+)Vk`LxmgQ+wd+pN-R+L!|+e|4?OFgkZ5tNvnF(1vmS?Ol{L;GIjGje?=(q>IG|s z&;#X5DHQ4fZl;QE?$&f4;SJsRS_j|azz1C?zH+mD!#Xrn15n_l6nHH>2ycbsjL&iI z3HXDXogU=qutQ)w1-%1r98};KoR^RCRXz`dX70XlX7-lZVwpa{v#s!GyJu-k zCqeJr&|Gb1_rv|Lj+jH#`|wBat_;~ZK-8gmAaa#8;f6u@(5|GXT-Z5;! zc9l-l6weE6we-~;(kQLcoDPAl#P5Mu z$XIhoqqItM+5l&D&@d#L!w_hg_9eNY0sdYxH71s54h?XwF|wD0CYop5>{)Eu_TIE~ zt`V_wEw=hop7Uoxe-oM^41p$SjcS}UK(tvJWSsPX23_o^)JYGBHcNwylOE8Ziyf6Z z=>gGZX^>G5KYvBS;J7;j(g_S-sI_USEwEpy$BazU0;xR32k~o zB7GD%!$E>JzwmU+>}R6F#c*R1tNzcINO%JtbhA@!A_3_uGQki+H+zlvbK$t+;V$$% zAJdS|0g;pXe*mH`=&L2K0km9v0;~MsRK80?-{n0!36gFZUgzjzSmjq`PvQU5@ED9D zodkQ&w)rHb)!7+p>9`PXgH^)2IJ*mevh0Va%zQVqKVBl#9kKaEE;~}U zD4k6@uVX&ioywIa8$bug<~IQbe{@j6%7j*l%y>a7EOY{~`A9x{1iw0}OGMdP5{yl1Wa`Y;v1dBvB^No8(>+MFPD|E|SRm5}R*?C=#+R&CU^GGiF0> zvjWW{j;xC-R{>*>ehQ8HfZ5BsUvB+OV&`L%FOJPWQc#4zkP>&hw6+5?e;2#8()uBt zjN`Ti%_QeEn|}Z%8Xm_OVhQN*DqrqK;P$+}>1rgoQ~efg2rj=qy8Er86YIv8Fe)|= zxB_|=R<4PGCdP;HY~dVUE}qB1$x#u5Pmg4Mu05eAXP~D*Pi~*Z-1Ha@?H|SRoilkz zS6Y4St|Ym0KPM+aGAp|Ff3SU#fx&@(50ak~eS|CE@xQUSoLTNJy~^ae0^dsdtEZfR zkzIr6qfhv{wjt;39|8_vkZp<9xHq8;9GV1vz7uV0-#v*fT2Ia>!!6Job>Nf@S8t9g zZLNg_6$cLb`U~Lw{dxrEu7vUPk^C#Ay~a4V{gD#@w*9L}DCf4{-lQ>OtAtd*8+ z)sl0%d;BMFRHzM%cE_7r0AIJ9bmufManiMhP{g$b@V QU;qFB07*qoM6N<$f&@}gt^fc4 diff --git a/client/test-collision.png b/client/test-collision.png new file mode 100644 index 0000000000000000000000000000000000000000..de205095cd792ddc8a6e51ce6db79b7bc9cd2328 GIT binary patch literal 1095 zcmV-N1i1T&P)UMI6RcQ?;f>6orcR5*qPfLT@b;l7j_%D8-9j z^b{6)segp$;GsPT;zy>nj49^r~ z+8}MEAbm%Yb`e#PQ}89Y3S(P3Hoq~9WlUqMp>J%fFi{ooG29O);UV}e947wY;%o3R z{0W|d_hIRnCE^mi2V+}tQ2C9m2DOxqWg;rzZ#XDJvDY!S8r0GkVh6#KM44|!dW&Px zAk~+UJ_~sZtKseD(aT(gpZdojG0ny1;3XK_N@eF)qgvHGFucQ+*5LzKwxQT~YE-M5 zd%}^s8~p!!@O^j~#@;d%DIIEj0eWjZ0k1(TF&a-%!ux#_#tOHy( zyaw$FcRLyKq2d+TfU);lRr2dik9z$NoHjm#zrv~*iq)w%J+_`;+s6P`R0rV8u&&UX z9$Tx&Pyx;cuCX7&x+yhc=`QUrp142!tdK~wFR9imy9`}Gy6R94P?*T4`Zo@xeYKx(-@caqP z(BL!nE9R=_*K_MDv8S_YNb4i>9L9ifOsNVo&nX(;(#W9#_H0Q_<0SJOD&TuOe*>Kn z>JB~XRnIMiS7Tq3QuWcjmwr9AUX5k%$4GbtZ^6Fy(on8S(4$^6gj>K9Um4$ozj;zq z=uMBU*W>LzliVQTqi{RiKJt4rVK2wJY>~NwmoMPw@E7>MaGG4b29&D&UoVAd9|Q6k z>a|V8RJ$JZQi%K@*q-SzCq6Yfbvy}Q zhgHHk#GZi{pwBtmf?Lnmp}Q*bwLEI)YE-M52Zm{`Gza}L!l)U}>rtax)!Y-^Zh`i~ zZY}fqz+gYZHTXOnl}%MQ(DG$d*9yn;aOhk9mI>k}WJlV3E0lF^Y&EE*bj%WQ2`Zp0 z!}&PIR)bplLRbyA25Mt_f@Ow|e*sU!ysu@@o>l+= N002ovPDHLkV1h+(4}$;z literal 0 HcmV?d00001 diff --git a/ffi-wrapper/src/ffi.rs b/ffi-wrapper/src/ffi.rs index 96773e7..a6dfb2e 100644 --- a/ffi-wrapper/src/ffi.rs +++ b/ffi-wrapper/src/ffi.rs @@ -47,7 +47,7 @@ pub extern "C" fn destroy_bullet(bullet: *mut Bullet) { } #[no_mangle] -pub extern "C" fn bullet_collides_with(tick: i64, bullet: *mut Bullet, c: *const Circle) -> bool { +pub extern "C" fn bullet_collides_with(bullet: *mut Bullet, tick: i64, c: *const Circle) -> bool { if bullet.is_null() || c.is_null() { return false; } diff --git a/server/main.go b/server/main.go index dc172db..cd2526b 100644 --- a/server/main.go +++ b/server/main.go @@ -32,9 +32,11 @@ const ( ) const ( - STAGE_WIDTH float64 = 90.0 - STAGE_HEIGHT float64 = 160.0 - BULLET_KILL_BUFFER_WIDTH float64 = 16.0 + STAGE_WIDTH float64 = 90.0 + STAGE_HEIGHT float64 = 160.0 + BULLET_KILL_BUFFER_WIDTH float64 = 16.0 + PLAYER_COL_RADIUS_MULTIPLIER float64 = 0.04 + PLAYER_DEATH_TIMER_MAX int = 180 ) // Interface for registering match handlers @@ -58,6 +60,7 @@ type GameTickUpdate struct { PlayerPos map[string]interface{} `json:"playerPos"` NewBullets []map[string]interface{} `json:"newBullets"` ForcePlayerPos bool `json:"forcePlayerPos"` + DeathTimer int `json:"deathTimer"` } type PresenceState struct { // present time! hahahahahahahah! @@ -111,7 +114,7 @@ func (m *BattleRoyaleMatch) MatchJoin(ctx context.Context, logger runtime.Logger lobbyState.presences[presences[i].GetSessionId()] = &PresenceState{ presence: presences[i], stageState: PlayerStageState{ - col: C.new_circle(C.double(STAGE_WIDTH*0.5), C.double(STAGE_HEIGHT-STAGE_HEIGHT*0.1), C.double(STAGE_WIDTH*0.09)), + col: C.new_circle(C.double(STAGE_WIDTH*0.5), C.double(STAGE_HEIGHT-STAGE_HEIGHT*0.1), C.double(STAGE_WIDTH*PLAYER_COL_RADIUS_MULTIPLIER)), bullets: []*C.Bullet{}, updatePlayerPos: true, health: 3, @@ -229,13 +232,21 @@ func (m *BattleRoyaleMatch) MatchLoop(ctx context.Context, logger runtime.Logger return false }) + // Check if the player collided with a bullet and kill them if so + deathTimer := 0 + if slices.ContainsFunc(v.stageState.bullets, func(b *C.Bullet) bool { + return bool(C.bullet_collides_with(b, C.int64_t(tick), v.stageState.col)) + }) { + deathTimer = PLAYER_DEATH_TIMER_MAX + } + var newBulletsToBroadcast = []map[string]interface{}{} // Test bullet spawning if tick%10 == 0 { velx := (rand.Float64() * STAGE_WIDTH) / float64(lobbyState.tickRate) vely := (rand.Float64() * STAGE_WIDTH) / float64(lobbyState.tickRate) - radius := 0.03 + radius_multiplier := 0.03 + rand.Float64()*(0.1-0.03) vel_x_sign := 2*rand.Intn(2) - 1 vel_y_sign := 2*rand.Intn(2) - 1 @@ -244,7 +255,7 @@ func (m *BattleRoyaleMatch) MatchLoop(ctx context.Context, logger runtime.Logger C.int64_t(tick), C.double(STAGE_WIDTH*rand.Float64()), C.double(STAGE_HEIGHT*rand.Float64()), - C.double(radius), + C.double(radius_multiplier*STAGE_WIDTH), C.double(float64(vel_x_sign)*velx), C.double(float64(vel_y_sign)*vely), ) @@ -255,13 +266,13 @@ func (m *BattleRoyaleMatch) MatchLoop(ctx context.Context, logger runtime.Logger C.bullet_get_current_pos(bullet, C.int64_t(tick), &x, &y) bulletData := map[string]interface{}{ - "class": BULLET_LINEAR, - "tick": tick, - "x": float64(x), - "y": float64(y), - "radius": float64(radius), - "vel_x": float64(vel_x_sign) * velx, - "vel_y": float64(vel_y_sign) * vely, + "class": BULLET_LINEAR, + "tick": tick, + "x": float64(x), + "y": float64(y), + "radius_multiplier": float64(radius_multiplier), + "vel_x": float64(vel_x_sign) * velx, + "vel_y": float64(vel_y_sign) * vely, } newBulletsToBroadcast = append(newBulletsToBroadcast, bulletData) @@ -270,11 +281,13 @@ func (m *BattleRoyaleMatch) MatchLoop(ctx context.Context, logger runtime.Logger var tickData = GameTickUpdate{ Tick: tick, PlayerPos: map[string]interface{}{ - "x": v.stageState.col.x, - "y": v.stageState.col.y, + "x": v.stageState.col.x, + "y": v.stageState.col.y, + "radius_multiplier": PLAYER_COL_RADIUS_MULTIPLIER, }, NewBullets: newBulletsToBroadcast, ForcePlayerPos: v.stageState.updatePlayerPos, + DeathTimer: deathTimer, } v.stageState.updatePlayerPos = false