mirror of
https://github.com/Steffo99/nanogolf.git
synced 2024-11-21 15:44:21 +00:00
Everything works!
This commit is contained in:
parent
c252fe13fc
commit
ff9336eecd
15 changed files with 239 additions and 73 deletions
|
@ -23,6 +23,12 @@ window/stretch/mode="canvas_items"
|
||||||
window/stretch/aspect="expand"
|
window/stretch/aspect="expand"
|
||||||
window/handheld/orientation=1
|
window/handheld/orientation=1
|
||||||
|
|
||||||
|
[layer_names]
|
||||||
|
|
||||||
|
2d_physics/layer_1="Default"
|
||||||
|
2d_physics/layer_2="Players"
|
||||||
|
2d_physics/layer_3="Walls"
|
||||||
|
|
||||||
[physics]
|
[physics]
|
||||||
|
|
||||||
common/physics_ticks_per_second=300
|
common/physics_ticks_per_second=300
|
||||||
|
|
|
@ -7,4 +7,4 @@ class_name FollowCamera
|
||||||
|
|
||||||
func _physics_process(_delta):
|
func _physics_process(_delta):
|
||||||
if target != null:
|
if target != null:
|
||||||
position = target.position
|
position = target.global_position
|
||||||
|
|
|
@ -2,31 +2,58 @@ extends CharacterBody2D
|
||||||
class_name GolfBall
|
class_name GolfBall
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@export_category("References")
|
||||||
|
|
||||||
|
## The [PuttController] that this node should poll.
|
||||||
|
@export var putt_controller: PuttController
|
||||||
|
|
||||||
|
## The [HoleController] that this node should poll.
|
||||||
|
@export var hole_controller: HoleController
|
||||||
|
|
||||||
|
## The [AudioStreamPlayer2D] that this node should play when entering the hole.
|
||||||
|
@export var hole_sound: AudioStreamPlayer2D
|
||||||
|
|
||||||
|
## The [Label] where the name of this player should be displayed.
|
||||||
|
@export var player_label: Label
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@export_category("Physics")
|
||||||
|
|
||||||
## Dynamic friction subtracted from the body's velocity on each physics step.
|
## Dynamic friction subtracted from the body's velocity on each physics step.
|
||||||
@export var physics_friction: float
|
@export var physics_friction: float
|
||||||
|
|
||||||
## The maximum number of bounces that the collision algorithm will consider in a single physics step.
|
## The maximum number of bounces that the collision algorithm will consider in a single physics step.
|
||||||
@export var physics_max_bounces: float
|
@export var physics_max_bounces: float
|
||||||
|
|
||||||
## A multiplier applied to the body's velocity every time it collides with something.
|
## A multiplier applied to the body's velocity every time it collides with something.
|
||||||
@export var physics_bounce_coefficient: float
|
@export var physics_bounce_coefficient: float
|
||||||
|
|
||||||
## The scene to instantiate to play the collision sound
|
## The scene to instantiate to play the collision sound
|
||||||
@export var collision_sound: PackedScene
|
@export var collision_sound: PackedScene
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@export_category("Sounds")
|
||||||
|
|
||||||
## Curve mapping relative putt power to putt sound volume.
|
## Curve mapping relative putt power to putt sound volume.
|
||||||
@export var collision_volume_db: Curve
|
@export var collision_volume_db: Curve
|
||||||
|
|
||||||
## The velocity at which the maximum volume of [member collision_volume_db] is played.
|
## The velocity at which the maximum volume of [member collision_volume_db] is played.
|
||||||
@export var collision_volume_max_velocity: float
|
@export var collision_volume_max_velocity: float
|
||||||
|
|
||||||
|
|
||||||
## Emitted when the ball enters the hole.
|
## Emitted when the ball enters the hole.
|
||||||
signal entered_hole(strokes: int)
|
signal entered_hole(strokes: int)
|
||||||
|
|
||||||
|
|
||||||
## How many strokes have been performed so far.
|
## How many strokes have been performed so far.
|
||||||
var strokes: int = 0
|
var strokes: int = 0
|
||||||
|
|
||||||
## Whether the ball has entered the hole.
|
## Whether the ball has entered the hole.
|
||||||
var in_hole: bool = false
|
var in_hole: bool = false
|
||||||
|
|
||||||
@onready var putt_controller: PuttController = $"PuttController"
|
|
||||||
@onready var hole_controller: HoleController = $"HoleController"
|
|
||||||
@onready var hole_sound: AudioStreamPlayer2D = $"HoleSound"
|
|
||||||
@onready var player_label: Label = $"PlayerLabel"
|
|
||||||
|
|
||||||
|
|
||||||
## The name of the player represented by this scene.
|
## The name of the player represented by this scene.
|
||||||
var player_name: String = "Player":
|
var player_name: String = "Player":
|
||||||
|
@ -88,25 +115,36 @@ func do_movement(delta: float) -> void:
|
||||||
velocity *= physics_bounce_coefficient
|
velocity *= physics_bounce_coefficient
|
||||||
|
|
||||||
|
|
||||||
func apply_friction(delta) -> void:
|
## Reduce [field velocity] by [field physics_friction], taking the [param delta] into account.
|
||||||
|
func apply_friction(delta: float) -> void:
|
||||||
var new_velocity_length = max(0.0, velocity.length() - physics_friction * delta)
|
var new_velocity_length = max(0.0, velocity.length() - physics_friction * delta)
|
||||||
velocity = velocity.normalized() * new_velocity_length
|
velocity = velocity.normalized() * new_velocity_length
|
||||||
|
|
||||||
|
|
||||||
|
## Return whether this object can be considered stopped or not.
|
||||||
func check_has_stopped() -> bool:
|
func check_has_stopped() -> bool:
|
||||||
return velocity.length() == 0.0
|
return velocity.length() == 0.0
|
||||||
|
|
||||||
|
## Return whether this object will enter the hole on this frame or not.
|
||||||
func check_has_entered_hole() -> bool:
|
func check_has_entered_hole() -> bool:
|
||||||
return check_has_stopped() and hole_controller.over_hole
|
return check_has_stopped() and hole_controller.over_hole
|
||||||
|
|
||||||
|
|
||||||
func enter_hole() -> void:
|
@rpc("authority", "call_local", "reliable")
|
||||||
|
func rpc_sync_enter_hole():
|
||||||
in_hole = true
|
in_hole = true
|
||||||
visible = false
|
visible = false
|
||||||
hole_sound.play()
|
hole_sound.play()
|
||||||
entered_hole.emit(strokes)
|
entered_hole.emit(strokes)
|
||||||
print("[GolfBall] Entered hole in: ", strokes)
|
Log.peer(self, "Entered hole in: %d" % strokes)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME: What happens on the server?
|
||||||
|
## Push this object's [field global_position] to all other clients.
|
||||||
|
@rpc("authority", "call_remote", "unreliable_ordered")
|
||||||
|
func rpc_sync_global_position(nposition: Vector2):
|
||||||
|
global_position = nposition
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
@ -114,10 +152,12 @@ func _ready() -> void:
|
||||||
|
|
||||||
|
|
||||||
func _physics_process(delta) -> void:
|
func _physics_process(delta) -> void:
|
||||||
if not in_hole:
|
if is_multiplayer_authority():
|
||||||
do_movement(delta)
|
if not in_hole:
|
||||||
apply_friction(delta)
|
do_movement(delta)
|
||||||
if check_has_entered_hole():
|
rpc_sync_global_position.rpc(global_position)
|
||||||
enter_hole()
|
apply_friction(delta)
|
||||||
if check_has_stopped():
|
if check_has_entered_hole():
|
||||||
putt_controller.can_putt = true
|
rpc_sync_enter_hole.rpc()
|
||||||
|
if check_has_stopped():
|
||||||
|
putt_controller.can_putt = true
|
||||||
|
|
|
@ -20,10 +20,16 @@ radius = 4.0
|
||||||
[sub_resource type="CircleShape2D" id="CircleShape2D_aigrf"]
|
[sub_resource type="CircleShape2D" id="CircleShape2D_aigrf"]
|
||||||
radius = 1.0
|
radius = 1.0
|
||||||
|
|
||||||
[node name="GolfBall" type="CharacterBody2D"]
|
[node name="GolfBall" type="CharacterBody2D" node_paths=PackedStringArray("putt_controller", "hole_controller", "hole_sound", "player_label")]
|
||||||
|
collision_layer = 2
|
||||||
|
collision_mask = 4
|
||||||
motion_mode = 1
|
motion_mode = 1
|
||||||
wall_min_slide_angle = 0.0
|
wall_min_slide_angle = 0.0
|
||||||
script = ExtResource("1_1uswk")
|
script = ExtResource("1_1uswk")
|
||||||
|
putt_controller = NodePath("PuttController")
|
||||||
|
hole_controller = NodePath("HoleController")
|
||||||
|
hole_sound = NodePath("HoleSound")
|
||||||
|
player_label = NodePath("PlayerLabel")
|
||||||
physics_friction = 60.0
|
physics_friction = 60.0
|
||||||
physics_max_bounces = 4.0
|
physics_max_bounces = 4.0
|
||||||
physics_bounce_coefficient = 0.65
|
physics_bounce_coefficient = 0.65
|
||||||
|
|
10
scenes/golf_ball_directory.tscn
Normal file
10
scenes/golf_ball_directory.tscn
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[gd_scene load_steps=2 format=3 uid="uid://cywer226gp6vt"]
|
||||||
|
|
||||||
|
[sub_resource type="GDScript" id="GDScript_ek5xt"]
|
||||||
|
script/source = "extends Node
|
||||||
|
class_name GolfBallDirectory
|
||||||
|
|
||||||
|
"
|
||||||
|
|
||||||
|
[node name="GolfBallDirectory" type="Node"]
|
||||||
|
script = SubResource("GDScript_ek5xt")
|
|
@ -1,6 +1,11 @@
|
||||||
extends Node2D
|
extends Node2D
|
||||||
class_name GolfLevel
|
class_name GolfLevel
|
||||||
|
|
||||||
|
|
||||||
|
## Emitted when it's time to change to the next level.
|
||||||
|
signal level_completed
|
||||||
|
|
||||||
|
|
||||||
@export_category("Level Data")
|
@export_category("Level Data")
|
||||||
|
|
||||||
## Whether the [field camera] follows the active player or not.
|
## Whether the [field camera] follows the active player or not.
|
||||||
|
@ -23,7 +28,7 @@ class_name GolfLevel
|
||||||
@export var map: TileMap = null
|
@export var map: TileMap = null
|
||||||
|
|
||||||
|
|
||||||
## If on server, this variable indicates the [GolfLevel] to replicate on clients.
|
## If on server, this variable indicates the [GolfLevel] to replicate.
|
||||||
##
|
##
|
||||||
## The [GolfLevel] in question should not be added to the scene tree, or it will cause problems on the client acting as server.
|
## The [GolfLevel] in question should not be added to the scene tree, or it will cause problems on the client acting as server.
|
||||||
##
|
##
|
||||||
|
@ -31,22 +36,22 @@ class_name GolfLevel
|
||||||
var target: GolfLevel = null
|
var target: GolfLevel = null
|
||||||
|
|
||||||
|
|
||||||
## The [PlayerNodeDirectory] to use to determine which players to spawn on clients.
|
## The [PlayerNodeDirectory] to use to determine which players to spawn.
|
||||||
##
|
##
|
||||||
## It should be set on instantiation of a new [GolfLevel].
|
## It should be set on instantiation of a new [GolfLevel].
|
||||||
var player_dir: PlayerNodeDirectory = null
|
var player_dir: PlayerNodeDirectory = null
|
||||||
|
|
||||||
|
|
||||||
## The [PackedScene] to initialize on clients as [TileMap].
|
## The [PackedScene] to initialize as [TileMap].
|
||||||
const tilemap_scene: PackedScene = preload("res://scenes/golf_tilemap.tscn")
|
const tilemap_scene: PackedScene = preload("res://scenes/golf_tilemap.tscn")
|
||||||
|
|
||||||
## The [PackedScene] to initialize on clients as [GolfTee].
|
## The [PackedScene] to initialize as [GolfTee].
|
||||||
const tee_scene: PackedScene = preload("res://scenes/golf_tee.tscn")
|
const tee_scene: PackedScene = preload("res://scenes/golf_tee.tscn")
|
||||||
|
|
||||||
## The [PackedScene] to initialize on clients as [GolfHole].
|
## The [PackedScene] to initialize as [GolfHole].
|
||||||
const hole_scene: PackedScene = preload("res://scenes/golf_hole.tscn")
|
const hole_scene: PackedScene = preload("res://scenes/golf_hole.tscn")
|
||||||
|
|
||||||
## The [PackedScene] to initialize on clients as [Camera2D].
|
## The [PackedScene] to initialize as [Camera2D].
|
||||||
const camera_scene: PackedScene = preload("res://scenes/follow_camera.tscn")
|
const camera_scene: PackedScene = preload("res://scenes/follow_camera.tscn")
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,6 +60,7 @@ func build() -> void:
|
||||||
build_tilemap()
|
build_tilemap()
|
||||||
build_tilemap_cells()
|
build_tilemap_cells()
|
||||||
build_tee()
|
build_tee()
|
||||||
|
build_balls()
|
||||||
build_hole()
|
build_hole()
|
||||||
build_camera()
|
build_camera()
|
||||||
|
|
||||||
|
@ -65,8 +71,8 @@ func build_tilemap() -> void:
|
||||||
var tmap: TileMap = target.map
|
var tmap: TileMap = target.map
|
||||||
rpc_build_tilemap.rpc(tmap.global_position, tmap.global_rotation, tmap.global_scale)
|
rpc_build_tilemap.rpc(tmap.global_position, tmap.global_rotation, tmap.global_scale)
|
||||||
|
|
||||||
## Create the [field map] on clients.
|
## Create the [field map].
|
||||||
@rpc("authority", "call_remote", "reliable")
|
@rpc("authority", "call_local", "reliable")
|
||||||
func rpc_build_tilemap(tposition: Vector2, trotation: float, tscale: Vector2):
|
func rpc_build_tilemap(tposition: Vector2, trotation: float, tscale: Vector2):
|
||||||
Log.peer(self, "Building map...")
|
Log.peer(self, "Building map...")
|
||||||
map = tilemap_scene.instantiate()
|
map = tilemap_scene.instantiate()
|
||||||
|
@ -77,7 +83,7 @@ func rpc_build_tilemap(tposition: Vector2, trotation: float, tscale: Vector2):
|
||||||
|
|
||||||
## Replicate the cells of [field target]'s [field map] to the remote [field map].
|
## Replicate the cells of [field target]'s [field map] to the remote [field map].
|
||||||
##
|
##
|
||||||
## The [field map] must be already created on clients.
|
## The [field map] must be already created.
|
||||||
##
|
##
|
||||||
## Only takes layer 0 into consideration.
|
## Only takes layer 0 into consideration.
|
||||||
func build_tilemap_cells() -> void:
|
func build_tilemap_cells() -> void:
|
||||||
|
@ -90,8 +96,8 @@ func build_tilemap_cells() -> void:
|
||||||
var alternative_tile: int = tmap.get_cell_alternative_tile(0, coords)
|
var alternative_tile: int = tmap.get_cell_alternative_tile(0, coords)
|
||||||
rpc_build_tilemap_cell.rpc(layer, coords, source_id, atlas_coords, alternative_tile)
|
rpc_build_tilemap_cell.rpc(layer, coords, source_id, atlas_coords, alternative_tile)
|
||||||
|
|
||||||
## Create a cell of [field map] on clients.
|
## Create a cell of [field map].
|
||||||
@rpc("authority", "call_remote", "reliable")
|
@rpc("authority", "call_local", "reliable")
|
||||||
func rpc_build_tilemap_cell(
|
func rpc_build_tilemap_cell(
|
||||||
layer: int,
|
layer: int,
|
||||||
coords: Vector2i,
|
coords: Vector2i,
|
||||||
|
@ -108,31 +114,29 @@ func build_tee() -> void:
|
||||||
Log.peer(self, "Replicating tee...")
|
Log.peer(self, "Replicating tee...")
|
||||||
var ttee: GolfTee = target.tee
|
var ttee: GolfTee = target.tee
|
||||||
rpc_build_tee.rpc(ttee.global_position)
|
rpc_build_tee.rpc(ttee.global_position)
|
||||||
rpc_build_tee_balls.rpc()
|
|
||||||
|
|
||||||
## Create the [GolfTee] object on clients.
|
## Create the [GolfTee] object.
|
||||||
@rpc("authority", "call_remote", "reliable")
|
@rpc("authority", "call_local", "reliable")
|
||||||
func rpc_build_tee(tposition: Vector2):
|
func rpc_build_tee(tposition: Vector2):
|
||||||
Log.peer(self, "Building tee...")
|
Log.peer(self, "Building tee...")
|
||||||
tee = tee_scene.instantiate()
|
tee = tee_scene.instantiate()
|
||||||
tee.global_position = tposition
|
tee.global_position = tposition
|
||||||
|
tee.everyone_entered_hole.connect(_on_everyone_entered_hole)
|
||||||
add_child(tee)
|
add_child(tee)
|
||||||
|
|
||||||
## Create and initialize the [GolfBall] object on clients, at the [GolfTee]'s position.
|
|
||||||
@rpc("authority", "call_remote", "reliable")
|
## Replicate the currently connected players' [GolfBall]s to the remote [field tee].
|
||||||
func rpc_build_tee_balls():
|
func build_balls() -> void:
|
||||||
Log.peer(self, "Building tee balls...")
|
|
||||||
for playernode in player_dir.get_children():
|
for playernode in player_dir.get_children():
|
||||||
var ball: GolfBall = tee.create(playernode)
|
rpc_build_ball.rpc(playernode.player_name)
|
||||||
add_child(ball)
|
|
||||||
|
|
||||||
## Create and initialize a [GolfBall] for a single [PlayerNode] with the given name.
|
## Create and initialize a [GolfBall] for a single [PlayerNode] with the given name.
|
||||||
@rpc("authority", "call_remote", "reliable")
|
@rpc("authority", "call_local", "reliable")
|
||||||
func rpc_build_tee_ball(player_name: String):
|
func rpc_build_ball(player_name: String):
|
||||||
Log.peer(self, "Building tee ball for: %s" % player_name)
|
Log.peer(self, "Building tee ball for: %s" % player_name)
|
||||||
var playernode: PlayerNode = player_dir.get_playernode(player_name)
|
var playernode: PlayerNode = player_dir.get_playernode(player_name)
|
||||||
var ball: GolfBall = tee.create(playernode)
|
tee.spawn(playernode)
|
||||||
add_child(ball)
|
|
||||||
|
|
||||||
|
|
||||||
## Replicate the [field hole] of the [field target] to the remote [field hole].
|
## Replicate the [field hole] of the [field target] to the remote [field hole].
|
||||||
|
@ -141,8 +145,8 @@ func build_hole() -> void:
|
||||||
var thole: GolfHole = target.hole
|
var thole: GolfHole = target.hole
|
||||||
rpc_build_hole.rpc(thole.global_position, thole.global_scale)
|
rpc_build_hole.rpc(thole.global_position, thole.global_scale)
|
||||||
|
|
||||||
## Create the [GolfHole] object on clients.
|
## Create the [GolfHole] object.
|
||||||
@rpc("authority", "call_remote", "reliable")
|
@rpc("authority", "call_local", "reliable")
|
||||||
func rpc_build_hole(tposition: Vector2, tscale: Vector2):
|
func rpc_build_hole(tposition: Vector2, tscale: Vector2):
|
||||||
Log.peer(self, "Building hole...")
|
Log.peer(self, "Building hole...")
|
||||||
hole = hole_scene.instantiate()
|
hole = hole_scene.instantiate()
|
||||||
|
@ -157,18 +161,44 @@ func build_camera() -> void:
|
||||||
var tcamera: FollowCamera = target.camera
|
var tcamera: FollowCamera = target.camera
|
||||||
rpc_build_camera.rpc(tcamera.global_position, target.camera_follows_player)
|
rpc_build_camera.rpc(tcamera.global_position, target.camera_follows_player)
|
||||||
|
|
||||||
## Create the [Camera2D] object on clients.
|
## Create the [Camera2D] object.
|
||||||
@rpc("authority", "call_remote", "reliable")
|
@rpc("authority", "call_local", "reliable")
|
||||||
func rpc_build_camera(tposition: Vector2, tfocus: bool):
|
func rpc_build_camera(tposition: Vector2, tfocus: bool):
|
||||||
|
if multiplayer.is_server():
|
||||||
|
Log.peer(self, "Not building camera on the server.")
|
||||||
|
return
|
||||||
Log.peer(self, "Building camera...")
|
Log.peer(self, "Building camera...")
|
||||||
camera = camera_scene.instantiate()
|
camera = camera_scene.instantiate()
|
||||||
camera.global_position = tposition
|
camera.global_position = tposition
|
||||||
if tfocus:
|
if tfocus:
|
||||||
camera.target = null # TODO: Find local player
|
# This supports only a single player per peer, for now.
|
||||||
|
for child in player_dir.get_children():
|
||||||
|
var playernode = child as PlayerNode
|
||||||
|
if playernode.is_multiplayer_authority():
|
||||||
|
var ctarget = tee.get_node("GolfBall__%s" % child.player_name)
|
||||||
|
camera.target = ctarget
|
||||||
add_child(camera)
|
add_child(camera)
|
||||||
|
|
||||||
|
|
||||||
func _on_playernode_created(node: Node):
|
func _ready() -> void:
|
||||||
|
player_dir.child_entered_tree.connect(_on_playernode_created)
|
||||||
|
if multiplayer.is_server():
|
||||||
|
set_physics_process(false)
|
||||||
|
hide()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_playernode_created(node: Node) -> void:
|
||||||
Log.peer(self, "Spawning new player...")
|
Log.peer(self, "Spawning new player...")
|
||||||
var playernode: PlayerNode = node as PlayerNode
|
var playernode: PlayerNode = node as PlayerNode
|
||||||
rpc_build_tee_ball.rpc(playernode.player_name)
|
rpc_build_ball.rpc(playernode.player_name)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_everyone_entered_hole() -> void:
|
||||||
|
level_completed.emit()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_tree_exiting() -> void:
|
||||||
|
player_dir.child_entered_tree.disconnect(_on_playernode_created)
|
||||||
|
if target:
|
||||||
|
target.queue_free()
|
||||||
|
target = null
|
||||||
|
|
|
@ -4,3 +4,6 @@
|
||||||
|
|
||||||
[node name="GolfLevel" type="Node2D"]
|
[node name="GolfLevel" type="Node2D"]
|
||||||
script = ExtResource("1_evup0")
|
script = ExtResource("1_evup0")
|
||||||
|
camera_follows_player = null
|
||||||
|
|
||||||
|
[connection signal="tree_exiting" from="." to="." method="_on_tree_exiting"]
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
[node name="GolfLevel" type="Node2D" node_paths=PackedStringArray("camera", "tee", "hole", "map")]
|
[node name="GolfLevel" type="Node2D" node_paths=PackedStringArray("camera", "tee", "hole", "map")]
|
||||||
script = ExtResource("1_qljh7")
|
script = ExtResource("1_qljh7")
|
||||||
|
camera_follows_player = null
|
||||||
camera = NodePath("FollowCamera")
|
camera = NodePath("FollowCamera")
|
||||||
tee = NodePath("GolfTee")
|
tee = NodePath("GolfTee")
|
||||||
hole = NodePath("GolfHole")
|
hole = NodePath("GolfHole")
|
||||||
|
|
|
@ -14,11 +14,10 @@ tee = NodePath("GolfTee")
|
||||||
hole = NodePath("GolfHole")
|
hole = NodePath("GolfHole")
|
||||||
map = NodePath("TileMap")
|
map = NodePath("TileMap")
|
||||||
|
|
||||||
[node name="FollowCamera" parent="." node_paths=PackedStringArray("target") instance=ExtResource("1_3a7ly")]
|
[node name="FollowCamera" parent="." instance=ExtResource("1_3a7ly")]
|
||||||
process_callback = 0
|
process_callback = 0
|
||||||
position_smoothing_enabled = true
|
position_smoothing_enabled = true
|
||||||
drag_vertical_enabled = true
|
drag_vertical_enabled = true
|
||||||
target = NodePath("")
|
|
||||||
|
|
||||||
[node name="GolfTee" parent="." instance=ExtResource("3_6q2yk")]
|
[node name="GolfTee" parent="." instance=ExtResource("3_6q2yk")]
|
||||||
position = Vector2(0, -112)
|
position = Vector2(0, -112)
|
||||||
|
|
|
@ -1,22 +1,25 @@
|
||||||
extends Node2D
|
extends Node2D
|
||||||
class_name GolfTee
|
class_name GolfTee
|
||||||
|
|
||||||
|
## Emitted when all connected [GolfBall]s have their [field GolfBall.in_hole] parameter set to true.
|
||||||
|
signal everyone_entered_hole
|
||||||
|
|
||||||
|
|
||||||
## The [GolfBall] [PackedScene] to [method spawn].
|
## The [GolfBall] [PackedScene] to [method spawn].
|
||||||
const ball_scene: PackedScene = preload("res://scenes/golf_ball.tscn")
|
const ball_scene: PackedScene = preload("res://scenes/golf_ball.tscn")
|
||||||
|
|
||||||
|
|
||||||
## Create a new [GolfBall] from [field ball_scene], initialize it with details from the given [PlayerNode], and return it.
|
## Add a new [GolfBall] from [field ball_scene], initialize it with details from the given [PlayerNode], and return it.
|
||||||
##
|
func spawn(playernode: PlayerNode) -> GolfBall:
|
||||||
## Note that this does not add the [GolfBall] to the scene tree, as it is the [GolfLevel]'s responsibility to do so.
|
|
||||||
func create(playernode: PlayerNode) -> GolfBall:
|
|
||||||
# Create the [GolfBall]
|
# Create the [GolfBall]
|
||||||
var obj: GolfBall = ball_scene.instantiate()
|
var obj: GolfBall = ball_scene.instantiate()
|
||||||
# Setup the initial values
|
# Setup the initial values
|
||||||
obj.global_position = global_position
|
obj.position = Vector2.ZERO
|
||||||
|
obj.name = "GolfBall__%s" % playernode.player_name
|
||||||
obj.player_name = playernode.player_name
|
obj.player_name = playernode.player_name
|
||||||
obj.player_color = playernode.player_color
|
obj.player_color = playernode.player_color
|
||||||
obj.set_multiplayer_authority(playernode.get_multiplayer_authority())
|
obj.set_multiplayer_authority(playernode.get_multiplayer_authority())
|
||||||
|
obj.putt_controller.can_putt = is_multiplayer_authority()
|
||||||
# Create callables to be able to cleanup signals on destruction
|
# Create callables to be able to cleanup signals on destruction
|
||||||
var on_name_changed: Callable = _on_name_changed.bind(obj)
|
var on_name_changed: Callable = _on_name_changed.bind(obj)
|
||||||
var on_color_changed: Callable = _on_color_changed.bind(obj)
|
var on_color_changed: Callable = _on_color_changed.bind(obj)
|
||||||
|
@ -27,11 +30,27 @@ func create(playernode: PlayerNode) -> GolfBall:
|
||||||
playernode.color_changed.connect(on_color_changed)
|
playernode.color_changed.connect(on_color_changed)
|
||||||
playernode.possessed.connect(on_possessed)
|
playernode.possessed.connect(on_possessed)
|
||||||
obj.tree_exiting.connect(on_cleanup)
|
obj.tree_exiting.connect(on_cleanup)
|
||||||
|
obj.entered_hole.connect(_on_entered_hole)
|
||||||
|
# Add the golf ball as a child of the tee
|
||||||
|
add_child(obj)
|
||||||
# Return the created [GolfBall]
|
# Return the created [GolfBall]
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
func is_everyone_in_hole() -> bool:
|
||||||
|
for child in get_children():
|
||||||
|
var ball: GolfBall = child as GolfBall
|
||||||
|
if ball == null:
|
||||||
|
# Ignore collision sounds
|
||||||
|
continue
|
||||||
|
if ball.get_multiplayer_authority() == 1:
|
||||||
|
continue
|
||||||
|
if not ball.in_hole:
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
func _on_name_changed(_old: String, new: String, obj: GolfBall) -> void:
|
func _on_name_changed(_old: String, new: String, obj: GolfBall) -> void:
|
||||||
|
obj.name = "GolfBall__%s" % new
|
||||||
obj.player_name = new
|
obj.player_name = new
|
||||||
|
|
||||||
func _on_color_changed(_old: Color, new: Color, obj: GolfBall) -> void:
|
func _on_color_changed(_old: Color, new: Color, obj: GolfBall) -> void:
|
||||||
|
@ -39,8 +58,13 @@ func _on_color_changed(_old: Color, new: Color, obj: GolfBall) -> void:
|
||||||
|
|
||||||
func _on_possessed(_old: int, new: int, obj: GolfBall) -> void:
|
func _on_possessed(_old: int, new: int, obj: GolfBall) -> void:
|
||||||
obj.set_multiplayer_authority(new)
|
obj.set_multiplayer_authority(new)
|
||||||
|
obj.putt_controller.can_putt = is_multiplayer_authority()
|
||||||
|
|
||||||
func _on_cleanup(playernode: PlayerNode, on_name_changed: Callable, on_color_changed: Callable, on_possessed: Callable) -> void:
|
func _on_cleanup(playernode: PlayerNode, on_name_changed: Callable, on_color_changed: Callable, on_possessed: Callable) -> void:
|
||||||
playernode.name_changed.disconnect(on_name_changed)
|
playernode.name_changed.disconnect(on_name_changed)
|
||||||
playernode.color_changed.disconnect(on_color_changed)
|
playernode.color_changed.disconnect(on_color_changed)
|
||||||
playernode.possessed.disconnect(on_possessed)
|
playernode.possessed.disconnect(on_possessed)
|
||||||
|
|
||||||
|
func _on_entered_hole(_strokes: int) -> void:
|
||||||
|
if is_everyone_in_hole():
|
||||||
|
everyone_entered_hole.emit()
|
||||||
|
|
|
@ -20,20 +20,23 @@ var level: GolfLevel = null
|
||||||
func rpc_next_level():
|
func rpc_next_level():
|
||||||
# Destroy the current level
|
# Destroy the current level
|
||||||
if level != null:
|
if level != null:
|
||||||
level.target.queue_free()
|
|
||||||
player_dir.child_entered_tree.disconnect(level._on_playernode_created)
|
|
||||||
level.queue_free()
|
level.queue_free()
|
||||||
level = null
|
level = null
|
||||||
# Create the new level
|
# Create the new level
|
||||||
level = base_scene.instantiate()
|
level = base_scene.instantiate()
|
||||||
level.player_dir = player_dir
|
level.player_dir = player_dir
|
||||||
|
level.level_completed.connect(_on_level_completed)
|
||||||
# Determine the next level
|
# Determine the next level
|
||||||
if multiplayer.is_server():
|
if multiplayer.is_server():
|
||||||
var target = playlist.advance().instantiate()
|
var target = playlist.advance().instantiate()
|
||||||
level.target = target
|
level.target = target
|
||||||
player_dir.child_entered_tree.connect(level._on_playernode_created)
|
|
||||||
# Add the level to the tree
|
# Add the level to the tree
|
||||||
add_child(level)
|
add_child(level, true)
|
||||||
# Start the replication
|
# Start the replication
|
||||||
if multiplayer.is_server():
|
if multiplayer.is_server():
|
||||||
level.build()
|
level.build()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_level_completed() -> void:
|
||||||
|
if multiplayer.is_server():
|
||||||
|
rpc_next_level.rpc()
|
||||||
|
|
|
@ -87,7 +87,16 @@ func init_lobby_menu() -> void:
|
||||||
interface_instance.add_child(lobby_menu_instance)
|
interface_instance.add_child(lobby_menu_instance)
|
||||||
|
|
||||||
func deinit_lobby_menu() -> void:
|
func deinit_lobby_menu() -> void:
|
||||||
# TODO: Disconnect all signals above
|
lobby_menu_instance.leave_confirmed.disconnect(_on_leave_confirmed)
|
||||||
|
if server_game_instance:
|
||||||
|
lobby_menu_instance.start_confirmed.disconnect(server_game_instance._on_main_start_confirmed)
|
||||||
|
client_game_instance.multiplayer.server_disconnected.disconnect(_on_leave_confirmed)
|
||||||
|
client_game_instance.player_dir.child_entered_tree.disconnect(lobby_menu_instance.players_list._on_playernode_created)
|
||||||
|
client_game_instance.player_dir.child_exiting_tree.disconnect(lobby_menu_instance.players_list._on_playernode_destroyed)
|
||||||
|
client_game_instance.player_dir.playernode_name_changed.disconnect(lobby_menu_instance.players_list._on_playernode_name_changed)
|
||||||
|
client_game_instance.player_dir.playernode_color_changed.disconnect(lobby_menu_instance.players_list._on_playernode_color_changed)
|
||||||
|
client_game_instance.player_dir.playernode_possessed.disconnect(lobby_menu_instance.players_list._on_playernode_possessed)
|
||||||
|
client_game_instance.phase_tracker.phase_changed.disconnect(_on_phase_changed)
|
||||||
lobby_menu_instance.queue_free()
|
lobby_menu_instance.queue_free()
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
@ -105,8 +114,10 @@ func _on_connecting_confirmed(player_name: String, player_color: Color, server_a
|
||||||
init_lobby_menu()
|
init_lobby_menu()
|
||||||
|
|
||||||
func _on_leave_confirmed() -> void:
|
func _on_leave_confirmed() -> void:
|
||||||
deinit_lobby_menu()
|
if lobby_menu_scene:
|
||||||
deinit_client_game()
|
deinit_lobby_menu()
|
||||||
|
if client_game_instance:
|
||||||
|
deinit_client_game()
|
||||||
if server_game_instance != null:
|
if server_game_instance != null:
|
||||||
deinit_server_game()
|
deinit_server_game()
|
||||||
init_main_menu()
|
init_main_menu()
|
||||||
|
|
|
@ -1,24 +1,54 @@
|
||||||
extends Node2D
|
extends Node2D
|
||||||
class_name PuttController
|
class_name PuttController
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@export_category("References")
|
||||||
|
|
||||||
|
## The [Sprite2D] used to calculate [field sprite_texture_width] from.
|
||||||
|
@export var sprite: Sprite2D
|
||||||
|
|
||||||
|
## The [AudioStreamPlayer2D] to play when a putt happens.
|
||||||
|
@export var sound: AudioStreamPlayer2D
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@export_category("Physics")
|
||||||
|
|
||||||
## The maximum impulse that a putt can have.
|
## The maximum impulse that a putt can have.
|
||||||
@export var putt_max_impulse: float
|
@export var putt_max_impulse: float
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@export_category("Scale")
|
||||||
|
|
||||||
## How many game units a pixel of screen mouse movement corresponds to.
|
## How many game units a pixel of screen mouse movement corresponds to.
|
||||||
@export var putt_drag_scale: float
|
@export var putt_drag_scale: float
|
||||||
|
|
||||||
## Length multiplier of the putt ghost
|
## Length multiplier of the putt ghost
|
||||||
@export var putt_ghost_scale: float
|
@export var putt_ghost_scale: float
|
||||||
|
|
||||||
## Curve mapping relative putt impulse to putt sound volume.
|
## Curve mapping relative putt impulse to putt sound volume.
|
||||||
@export var putt_volume: Curve
|
@export var putt_volume: Curve
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Emitted when a putt has happened.
|
## Emitted when a putt has happened.
|
||||||
signal putt(putt_vector: Vector2)
|
signal putt(putt_vector: Vector2)
|
||||||
|
|
||||||
@onready var sprite: Sprite2D = $Sprite
|
|
||||||
@onready var sound: AudioStreamPlayer2D = $Sound
|
## The width in pixels that the putt ghost should have.
|
||||||
@onready var sprite_texture_width: float = sprite.texture.get_width()
|
@onready var sprite_texture_width: float = sprite.texture.get_width()
|
||||||
|
|
||||||
|
## Whether a putt is currently in progress of not.
|
||||||
|
##
|
||||||
|
## If this is true, then [field drag_start_point] should contain a value.
|
||||||
var is_putting: bool = false
|
var is_putting: bool = false
|
||||||
var putt_start_point: Vector2
|
|
||||||
|
|
||||||
|
## The position on the screen where the putt has started.
|
||||||
|
var drag_start_point: Vector2
|
||||||
|
|
||||||
|
## Whether a putt can currently be performed or not.
|
||||||
var can_putt: bool = false:
|
var can_putt: bool = false:
|
||||||
get:
|
get:
|
||||||
return can_putt
|
return can_putt
|
||||||
|
@ -30,7 +60,7 @@ var can_putt: bool = false:
|
||||||
|
|
||||||
func _input(event: InputEvent):
|
func _input(event: InputEvent):
|
||||||
if can_putt:
|
if can_putt:
|
||||||
if event is InputEventMouseButton:
|
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
|
||||||
if event.pressed:
|
if event.pressed:
|
||||||
if not is_putting:
|
if not is_putting:
|
||||||
start_putt(event.position)
|
start_putt(event.position)
|
||||||
|
@ -42,10 +72,10 @@ func _input(event: InputEvent):
|
||||||
else:
|
else:
|
||||||
push_warning("Attempted to end putt while none was in progress.")
|
push_warning("Attempted to end putt while none was in progress.")
|
||||||
if is_putting:
|
if is_putting:
|
||||||
update_putt_ghost(compute_putt(event.position, putt_start_point))
|
update_putt_ghost(compute_putt(event.position, drag_start_point))
|
||||||
if event is InputEventMouseMotion:
|
if event is InputEventMouseMotion:
|
||||||
if is_putting:
|
if is_putting:
|
||||||
update_putt_ghost(compute_putt(event.position, putt_start_point))
|
update_putt_ghost(compute_putt(event.position, drag_start_point))
|
||||||
|
|
||||||
|
|
||||||
func update_putt_ghost(putt_vector: Vector2):
|
func update_putt_ghost(putt_vector: Vector2):
|
||||||
|
@ -65,14 +95,14 @@ func compute_putt(start_position: Vector2, end_position: Vector2) -> Vector2:
|
||||||
func start_putt(start_position: Vector2):
|
func start_putt(start_position: Vector2):
|
||||||
visible = true
|
visible = true
|
||||||
is_putting = true
|
is_putting = true
|
||||||
putt_start_point = start_position
|
drag_start_point = start_position
|
||||||
|
|
||||||
|
|
||||||
func end_putt(end_position: Vector2):
|
func end_putt(end_position: Vector2):
|
||||||
visible = false
|
visible = false
|
||||||
is_putting = false
|
is_putting = false
|
||||||
can_putt = false
|
can_putt = false
|
||||||
var putt_vector = compute_putt(putt_start_point, end_position)
|
var putt_vector = compute_putt(drag_start_point, end_position)
|
||||||
putt.emit(putt_vector)
|
putt.emit(putt_vector)
|
||||||
play_putt_sound(putt_vector)
|
play_putt_sound(putt_vector)
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,12 @@ max_value = 0.0
|
||||||
_data = [Vector2(0, -16), 0.0, 16.0, 0, 1, Vector2(1, 0), 16.0, 0.0, 1, 0]
|
_data = [Vector2(0, -16), 0.0, 16.0, 0, 1, Vector2(1, 0), 16.0, 0.0, 1, 0]
|
||||||
point_count = 2
|
point_count = 2
|
||||||
|
|
||||||
[node name="PuttController" type="Node2D"]
|
[node name="PuttController" type="Node2D" node_paths=PackedStringArray("sprite", "sound")]
|
||||||
visible = false
|
visible = false
|
||||||
modulate = Color(1, 1, 1, 0.25098)
|
modulate = Color(1, 1, 1, 0.25098)
|
||||||
script = ExtResource("1_rb6v1")
|
script = ExtResource("1_rb6v1")
|
||||||
|
sprite = NodePath("Sprite")
|
||||||
|
sound = NodePath("Sound")
|
||||||
putt_max_impulse = 333.0
|
putt_max_impulse = 333.0
|
||||||
putt_drag_scale = 1.0
|
putt_drag_scale = 1.0
|
||||||
putt_ghost_scale = 0.3
|
putt_ghost_scale = 0.3
|
||||||
|
|
|
@ -31,7 +31,8 @@ use_texture_padding = false
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
tile_size = Vector2i(128, 128)
|
tile_size = Vector2i(128, 128)
|
||||||
physics_layer_0/collision_layer = 1
|
physics_layer_0/collision_layer = 4
|
||||||
|
physics_layer_0/collision_mask = 0
|
||||||
sources/1 = SubResource("TileSetAtlasSource_174yq")
|
sources/1 = SubResource("TileSetAtlasSource_174yq")
|
||||||
sources/2 = SubResource("TileSetAtlasSource_2md10")
|
sources/2 = SubResource("TileSetAtlasSource_2md10")
|
||||||
sources/3 = SubResource("TileSetAtlasSource_mbu12")
|
sources/3 = SubResource("TileSetAtlasSource_mbu12")
|
||||||
|
|
Loading…
Reference in a new issue