Compare commits

..

2 Commits

6 changed files with 161 additions and 25 deletions

19
camera/PersonalUI.gd Normal file
View File

@ -0,0 +1,19 @@
extends Control
@onready var level := Level.find_level(self)
const personal_msg_1 := "You're not it, run!"
const personal_msg_2 := "You're it, run!"
func get_player() -> Player:
return owner.get_parent()
func _ready():
level.state_enter_tagger_change.connect(tagger_change)
func tagger_change(next: Player):
if get_player() == next:
$Label.text = personal_msg_2
else:
$Label.text = personal_msg_1

View File

@ -1,6 +1,7 @@
[gd_scene load_steps=3 format=3 uid="uid://brgqf2ebuyhuy"] [gd_scene load_steps=4 format=3 uid="uid://brgqf2ebuyhuy"]
[ext_resource type="Script" path="res://camera/camera.gd" id="1_veqr4"] [ext_resource type="Script" path="res://camera/camera.gd" id="1_veqr4"]
[ext_resource type="Script" path="res://camera/PersonalUI.gd" id="2_u54jc"]
[sub_resource type="SphereShape3D" id="SphereShape3D_5lewf"] [sub_resource type="SphereShape3D" id="SphereShape3D_5lewf"]
@ -16,3 +17,26 @@ margin = 0.5
[node name="Camera3D" type="Camera3D" parent="SpringArm3D"] [node name="Camera3D" type="Camera3D" parent="SpringArm3D"]
unique_name_in_owner = true unique_name_in_owner = true
[node name="PersonalUI" type="Control" parent="."]
layout_mode = 3
anchors_preset = 12
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = -126.0
grow_horizontal = 2
grow_vertical = 0
size_flags_vertical = 8
script = ExtResource("2_u54jc")
[node name="Label" type="Label" parent="PersonalUI"]
layout_mode = 1
anchors_preset = 5
anchor_left = 0.5
anchor_right = 0.5
offset_left = -20.0
offset_right = 20.0
offset_bottom = 23.0
grow_horizontal = 2
theme_override_colors/font_color = Color(1, 0, 0, 1)

View File

@ -14,4 +14,6 @@ func spawn_player(owner_id: int):
return player return player
func despawn_player(owner_id: int): func despawn_player(owner_id: int):
get_node(spawn_path).get_node(str(owner_id)).queue_free() var node := get_node(spawn_path).get_node(str(owner_id))
node.queue_free()
await node.tree_exited

View File

@ -9,6 +9,7 @@ enum GameState {
} }
var game_state: GameState = GameState.WAITING var game_state: GameState = GameState.WAITING
var safe_player: Player
var game_tagger: Player var game_tagger: Player
signal state_change(old: GameState, new: GameState) signal state_change(old: GameState, new: GameState)
@ -19,13 +20,18 @@ signal state_leave_tagger_ungrounded
signal state_enter_tagger_change(new_tagger: Player) signal state_enter_tagger_change(new_tagger: Player)
signal state_leave_tagger_change signal state_leave_tagger_change
const MIN_PLAYERS := 2 const MIN_PLAYERS := 3
const global_msg_1 := "Waiting for at least {min_players} players to join" const global_msg_1 := "Waiting for at least {min_players} players to join"
const global_msg_2 := "Game is starting soon, {tagger_username} is it!" const global_msg_2 := "Game is starting soon, {tagger_username} is it!"
const global_msg_3 := "{tagger_username} is it! Run!" const global_msg_3 := "{tagger_username} is it! Run!"
const global_msg_4 := "{old_tagger_username} caught {tagger_username}!" const global_msg_4 := "{old_tagger_username} caught {tagger_username}!"
const global_msg_5 := "{old_tagger_username} left. {tagger_username} is now it!" const global_msg_5 := "{old_tagger_username} left. {tagger_username} is now it!"
@rpc("any_peer", "reliable", "call_local")
func push_global_message(msg: String):
%GlobalMessage.text = msg
print(msg)
func set_tagger_grounded(v: bool): func set_tagger_grounded(v: bool):
if v and game_state == GameState.TAGGER_CHANGE: if v and game_state == GameState.TAGGER_CHANGE:
_change_state(GameState.TAGGER_GROUNDED) _change_state(GameState.TAGGER_GROUNDED)
@ -40,12 +46,14 @@ func set_tagger_grounded(v: bool):
func not_enough_players() -> bool: func not_enough_players() -> bool:
if %Players.get_children().size() < MIN_PLAYERS: if %Players.get_children().size() < MIN_PLAYERS:
game_tagger = null
_change_state(GameState.WAITING) _change_state(GameState.WAITING)
%GlobalMessage.text = global_msg_1.format({"min_players": MIN_PLAYERS}) push_global_message.rpc(global_msg_1.format({"min_players": MIN_PLAYERS}))
return true return true
return false return false
func change_to_random_tagger(): func change_to_random_tagger():
safe_player = null
game_tagger = %Players.get_children().pick_random() game_tagger = %Players.get_children().pick_random()
_change_state(GameState.TAGGER_CHANGE) _change_state(GameState.TAGGER_CHANGE)
@ -55,19 +63,32 @@ func tagger_left_game():
var old_tagger_username := game_tagger.username var old_tagger_username := game_tagger.username
change_to_random_tagger() change_to_random_tagger()
var tagger_username := game_tagger.username var tagger_username := game_tagger.username
%GlobalMessage.text = global_msg_5.format({ push_global_message.rpc(global_msg_5.format({
"old_tagger_username": old_tagger_username, "old_tagger_username": old_tagger_username,
"tagger_username": tagger_username}) "tagger_username": tagger_username}))
func set_tagger(who: Player): func set_tagger(who: Player):
if game_state == GameState.WAITING: safe_player = game_tagger
return
game_tagger.tree_exiting.disconnect(tagger_left_game)
who.tree_exiting.connect(tagger_left_game)
game_tagger = who game_tagger = who
_change_state(GameState.TAGGER_CHANGE) _change_state(GameState.TAGGER_CHANGE)
func _change_state(new_state: GameState): func _change_state(new_state: GameState):
var id := -1
if game_tagger != null:
id = game_tagger.get_multiplayer_authority()
var safe_player_id := -1
if safe_player != null:
safe_player_id = safe_player.get_multiplayer_authority()
_change_state_full.rpc(new_state, id, safe_player_id)
@rpc("any_peer", "reliable", "call_local")
func _change_state_full(new_state: GameState, new_tagger_id: int, new_safe_player_id: int):
game_tagger = null
safe_player = null
if new_tagger_id != -1:
game_tagger = find_player_by_id(new_tagger_id)
if new_safe_player_id != -1:
safe_player = find_player_by_id(new_safe_player_id)
if game_state == new_state: if game_state == new_state:
return return
if new_state == GameState.WAITING: if new_state == GameState.WAITING:
@ -89,22 +110,66 @@ func _change_state(new_state: GameState):
state_leave_tagger_ungrounded.emit() state_leave_tagger_ungrounded.emit()
if old_state == GameState.TAGGER_CHANGE: if old_state == GameState.TAGGER_CHANGE:
state_leave_tagger_change.emit() state_leave_tagger_change.emit()
func tagging_possible(_tagger: Player, tagee: Player) -> bool:
if game_state != GameState.TAGGER_GROUNDED and game_state != GameState.TAGGER_UNGROUNDED:
return false
return safe_player != tagee
func _ready(): func _ready():
# Hacky way to start level without going though multiplayer screens # Hacky way to start level without going though multiplayer screens
if get_parent() == get_tree().root: if get_parent() == get_tree().root:
%PlayerSpawner.spawn(multiplayer.get_unique_id()) %PlayerSpawner.spawn(multiplayer.get_unique_id())
func player_ground(v: bool, player: Player):
if game_tagger == player:
set_tagger_grounded(v)
func player_tag(who: Player, player: Player):
if not tagging_possible(player, who) or game_tagger != player:
return
var old_tagger_username := game_tagger.username
var tagger_username := player.username
push_global_message.rpc(global_msg_4.format({
"old_tagger_username": old_tagger_username,
"tagger_username": tagger_username}))
set_tagger(who)
func start_game():
change_to_random_tagger()
push_global_message.rpc(global_msg_3.format({"tagger_username" : game_tagger.username}))
# Called from the server # Called from the server
func server_add_player(id: int): func server_add_player(id: int):
%PlayerSpawner.spawn(id) %PlayerSpawner.spawn(id)
if not not_enough_players() and game_state == GameState.WAITING:
start_game()
func server_remove_player(id: int): func server_remove_player(id: int):
%PlayerSpawner.despawn_player(id) var player := find_player_by_id(id)
var tagger_left := (game_tagger == player)
await %PlayerSpawner.despawn_player(id)
if not_enough_players():
return
if tagger_left:
tagger_left_game()
func client_add_player(player: Player):
if not player.is_multiplayer_authority():
return
player.ground.connect(player_ground.bind(player))
player.tag.connect(player_tag.bind(player))
func client_remove_player(_node):
pass
static func find_level(node: Node) -> Level: static func find_level(node: Node) -> Level:
while not (node is Level): while not (node is Level):
node = node.get_parent() node = node.get_parent()
return node return node
func find_player_by_id(id: int) -> Player:
return %Players.get_node(str(id))

View File

@ -20,6 +20,7 @@ size = Vector2(100, 100)
[sub_resource type="BoxShape3D" id="BoxShape3D_m3lo5"] [sub_resource type="BoxShape3D" id="BoxShape3D_m3lo5"]
[sub_resource type="SceneReplicationConfig" id="SceneReplicationConfig_285vp"]
[sub_resource type="BoxShape3D" id="BoxShape3D_kefm0"] [sub_resource type="BoxShape3D" id="BoxShape3D_kefm0"]
size = Vector3(6.87, 1, 1) size = Vector3(6.87, 1, 1)
@ -95,6 +96,20 @@ shape = SubResource("BoxShape3D_m3lo5")
[node name="StaticBody3D" type="StaticBody3D" parent="." index="9"] [node name="StaticBody3D" type="StaticBody3D" parent="." index="9"]
[node name="GlobalMessage" type="Label" parent="SharedUI" index="0"]
unique_name_in_owner = true
layout_mode = 2
offset_right = 1152.0
offset_bottom = 23.0
theme_override_colors/font_color = Color(1, 0, 0, 1)
text = "Waiting for at least 2 players to join"
horizontal_alignment = 1
[node name="MultiplayerSynchronizer" type="MultiplayerSynchronizer" parent="." index="10"]
replication_config = SubResource("SceneReplicationConfig_285vp")
[connection signal="despawned" from="PlayerSpawner" to="." method="client_remove_player"]
[connection signal="spawned" from="PlayerSpawner" to="." method="client_add_player"]
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D" index="0"] [node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D" index="0"]
transform = Transform3D(1.5, 0, 0, 0, 3.2, 0, 0, 0, 10, 5.01694, 11.1202, -5.176) transform = Transform3D(1.5, 0, 0, 0, 3.2, 0, 0, 0, 10, 5.01694, 11.1202, -5.176)
shape = SubResource("BoxShape3D_kefm0") shape = SubResource("BoxShape3D_kefm0")

View File

@ -12,12 +12,23 @@ var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")
# Set by camera child, switch to signals if becomes too spaghetti # Set by camera child, switch to signals if becomes too spaghetti
var movement_dir: Vector2 = Vector2.ZERO var movement_dir: Vector2 = Vector2.ZERO
# Signals fire continously every frame they're true
signal tag(player: Player)
signal ground(v: bool)
func _process(_delta):
ground.emit(is_on_ground())
var player := get_first_bumper()
if player != null:
tag.emit(player)
# Using signals to maintain a list of currently colliding players because it simplifies # Using signals to maintain a list of currently colliding players because it simplifies
# Memory management substantially since nodes are not GC'd # Memory management substantially since nodes are not GC'd
# Connected nodes are the colliding nodes # Connected nodes are the colliding nodes
signal is_bumping(src: Player) signal check_bumping(src: Player)
var _is_grounded := false var _is_grounded := false
signal is_grounded signal check_grounded
func _ready(): func _ready():
if username == "": if username == "":
@ -25,7 +36,7 @@ func _ready():
func is_on_ground() -> bool: func is_on_ground() -> bool:
_is_grounded = false _is_grounded = false
is_grounded.emit(self) check_grounded.emit(self)
var v := _is_grounded var v := _is_grounded
_is_grounded = false _is_grounded = false
return v return v
@ -55,7 +66,7 @@ func _bump_check(src: Player):
func get_first_bumper() -> Player: func get_first_bumper() -> Player:
_first_bumper = null _first_bumper = null
is_bumping.emit(self) check_bumping.emit(self)
var player := _first_bumper var player := _first_bumper
_first_bumper = null _first_bumper = null
return player return player
@ -64,26 +75,26 @@ func _on_tag_detection_area_entered(area):
if not (area.get_parent() is Player): if not (area.get_parent() is Player):
return return
var _other_player: Player = area.get_parent() var _other_player: Player = area.get_parent()
if is_bumping.is_connected(_bump_check): if check_bumping.is_connected(_bump_check):
return return
is_bumping.connect(_bump_check) check_bumping.connect(_bump_check)
func _on_tag_detection_area_exited(area): func _on_tag_detection_area_exited(area):
if not (area.get_parent() is Player): if not (area.get_parent() is Player):
return return
var _other_player: Player = area.get_parent() var _other_player: Player = area.get_parent()
if not is_bumping.is_connected(_bump_check): if not check_bumping.is_connected(_bump_check):
return return
is_bumping.disconnect(_bump_check) check_bumping.disconnect(_bump_check)
func _on_tag_detection_body_entered(body): func _on_tag_detection_body_entered(body):
if "grounded" in body: if "grounded" in body:
if not is_grounded.is_connected(body.grounded): if not check_grounded.is_connected(body.grounded):
is_grounded.connect(body.grounded) check_grounded.connect(body.grounded)
func _on_tag_detection_body_exited(body): func _on_tag_detection_body_exited(body):
if "grounded" in body: if "grounded" in body:
if is_grounded.is_connected(body.grounded): if check_grounded.is_connected(body.grounded):
is_grounded.disconnect(body.grounded) check_grounded.disconnect(body.grounded)