Refactor some code into SyncController class

Besides (hopefully) helping with code cleanliness, this also
allows us to store extra per-player or per-model information.
main
copygirl 3 weeks ago
parent fdfdfc0634
commit b0fdfe0e41
  1. 107
      copyMultiplayer.gd
  2. 71
      sync_controller.gd

@ -1,3 +1,4 @@
class_name copyMultiplayer
extends Mod_Base
@export var cache := ""
@ -5,11 +6,11 @@ extends Mod_Base
@export var address := ""
@export var port := 52410
var main_controller: ModelController
## Hardcoded list of bone names that will get syncronized.
var tracked_bones: Array[String]
var main_controller: ModelController
# Temporary positioning system.
# TODO: Add a setting that allows syncing model positions, as an alternative
# to letting the local player choose where each model is going to appear.
@ -17,9 +18,6 @@ var player_order: Array[int] = []
var starting_offset := Vector3(-1.25, 0.0, 0.0)
var accumulative_offset := Vector3( 0.35, 0.0, 0.0)
# Allows us to use the "apply_animations" function to apply blendshapes.
var functions_blendshapes: Script = load("res://Mods/MediaPipe/MediaPipeController_BlendShapes.gd")
func _ready() -> void:
# FIXME: This is just thrown together. Dunno if this is an accurate list.
# TODO: Allow specifying additional bones, with the help of a hierachical list of existing bones in the model.
@ -117,9 +115,12 @@ func peer_connected(id: int) -> void:
update_status()
print_log(["Player ", id, " connected"])
var new_controller := ModelController.new()
new_controller.name = str(id)
add_child(new_controller)
var model_controller := ModelController.new()
model_controller.name = str(id)
var sync_controller := SyncController.new()
sync_controller.name = "SyncController"
model_controller.add_child(sync_controller)
add_child(model_controller)
player_order.append(id)
update_model_transforms()
@ -162,6 +163,14 @@ func update_enabled_state(is_online: bool) -> void:
window.get_node("Buttons/Host" ).disabled = is_online
window.get_node("Buttons/Disconnect").disabled = !is_online
func update_status() -> void:
var num_players := 1 + multiplayer.get_peers().size()
var side := "Hosting" if multiplayer.is_server() else "Connected"
var s := "s" if num_players != 1 else ""
set_status([side, ": ", num_players, " player", s])
# FIXME: Temporary hardcoded way to assign some offset to other players.
func update_model_transforms() -> void:
var offset := starting_offset
for id in player_order:
@ -178,12 +187,7 @@ func update_model_rotation(controller: ModelController) -> void:
var to_2d := Vector2(camera.position.x, camera.position.z)
controller.rotation.y = from_2d.angle_to_point(to_2d) / 4 # Magic value, probably depends on FOV.
func update_status() -> void:
var num_players := 1 + multiplayer.get_peers().size()
var side := "Hosting" if multiplayer.is_server() else "Connected"
var s := "s" if num_players != 1 else ""
set_status([side, ": ", num_players, " player", s])
## Removes all networked player models.
func clear_player_models() -> void:
for controller in get_children():
if controller is ModelController:
@ -192,25 +196,8 @@ func clear_player_models() -> void:
player_order.clear()
@rpc("any_peer", "reliable")
func change_model(filename: String) -> void:
var player_id := multiplayer.get_remote_sender_id()
var controller := get_node_or_null(str(player_id)) as ModelController
if not controller: return
if not filename.is_valid_filename():
print_log(["ERROR: '", filename, "' is not a valid file name!"])
return
var full_path := cache.path_join(filename)
if not FileAccess.file_exists(full_path):
print_log(["Player ", player_id, " wanted to switch to '", filename, "', but it could not be found, skipping"])
return
if controller.load_vrm(full_path):
print_log(["Player ", player_id, " switched to '", filename, "'"])
else:
print_log(["ERROR: Model '", filename, "' could not be loaded!"])
func _process(_delta: float) -> void:
SyncController.send_model_animation(self)
# Called when a node is added to the main ModelController.
func model_changed(child: Node) -> void:
@ -221,42 +208,20 @@ func model_changed(child: Node) -> void:
if filename.is_valid_filename(): change_model.rpc(filename)
# FIXME: This sends way more information than necessary, but works as a proof-of-concept!
@rpc("any_peer", "unreliable_ordered")
func sync_model_animation(
model_transform: Transform3D,
shape_dict: Dictionary, # Dictionary[String, float]
bone_poses: Dictionary, # Dictionary[String, Transform3D]
) -> void:
var player_id := multiplayer.get_remote_sender_id()
var controller := get_node_or_null(str(player_id))
if not controller: return
var model := controller.get_node_or_null("Model") as Node3D
if model:
model.transform = model_transform
functions_blendshapes.apply_animations(model, shape_dict)
var skeleton := controller._get_model_skeleton() as Skeleton3D
if skeleton:
for bone_name in bone_poses:
var pose: Transform3D = bone_poses[bone_name]
var idx := skeleton.find_bone(bone_name)
if idx != -1: skeleton.set_bone_pose(idx, pose)
## Gets the SyncController for the player with the specified peer id.
func get_sync_controller(peer_id: int) -> SyncController:
var model_controller := get_node_or_null(str(peer_id)) as ModelController
if not model_controller: return null
return model_controller.get_node_or_null("SyncController") as SyncController
func _process(_delta: float) -> void:
if multiplayer.get_peers().size() == 0: return
var model := get_model()
var skeleton := get_skeleton()
var media_pipe_ctrl = $"../MediaPipeController"
if (not model) or (not skeleton) or (not media_pipe_ctrl): return
var shape_dict = media_pipe_ctrl.blend_shape_last_values
var bone_poses = {}
for bone_name in tracked_bones:
var bone_idx = skeleton.find_bone(bone_name)
if bone_idx == -1: continue
var bone_pose = skeleton.get_bone_pose(bone_idx)
bone_poses[bone_name] = bone_pose
sync_model_animation.rpc(model.transform, shape_dict, bone_poses)
@rpc("any_peer", "reliable")
func change_model(filename: String) -> void:
var peer_id := multiplayer.get_remote_sender_id()
var controller := get_sync_controller(peer_id)
if controller: controller.change_model(filename)
@rpc("any_peer", "unreliable_ordered")
func sync_model_animation(model_transform: Transform3D, shape_dict: Dictionary, bone_poses: Dictionary) -> void:
var peer_id := multiplayer.get_remote_sender_id()
var controller := get_sync_controller(peer_id)
if controller: controller.sync_model_animation(model_transform, shape_dict, bone_poses)

@ -0,0 +1,71 @@
class_name SyncController
extends Node
var module: copyMultiplayer
var model_controller: ModelController
var peer_id: int
var nickname: String # FIXME: Not assigned or used.
var model_name: String
var model: Node
var skeleton: Skeleton3D
# Allows us to use the "apply_animations" function to apply blendshapes to a model.
static var _functions_blendshapes: Script = load("res://Mods/MediaPipe/MediaPipeController_BlendShapes.gd")
func _ready() -> void:
module = get_parent().get_parent()
model_controller = get_parent()
peer_id = model_controller.name.to_int()
## Attempts to change the model of this player.
func change_model(filename: String) -> void:
if not filename.is_valid_filename():
module.print_log("ERROR: '%s' is not a valid file name!" % filename)
return
var full_path := module.cache.path_join(filename)
if not FileAccess.file_exists(full_path):
module.print_log("Player %d wanted to switch to '%s', but it doesn't exist, skipping" % [ peer_id, filename ])
return
if not model_controller.load_vrm(full_path):
module.print_log("ERROR: Model '%s' could not be loaded!" % filename)
return
module.print_log("Player %d switched to '%s'" % [ peer_id, filename ])
model_name = filename
model = model_controller.get_node_or_null("Model")
skeleton = model_controller._get_model_skeleton()
func sync_model_animation(
model_transform: Transform3D,
shape_dict: Dictionary, # Dictionary[String, float]
bone_poses: Dictionary, # Dictionary[String, Transform3D]
) -> void:
if (not model) or (not skeleton): return
model.transform = model_transform
_functions_blendshapes.apply_animations(model, shape_dict)
for bone_name in bone_poses:
var pose: Transform3D = bone_poses[bone_name]
var idx := skeleton.find_bone(bone_name)
if idx != -1: skeleton.set_bone_pose(idx, pose)
@warning_ignore("shadowed_variable")
static func send_model_animation(module: copyMultiplayer) -> void:
# Check if there's other players we're connected to.
if module.multiplayer.get_peers().size() == 0: return
var model := module.get_model()
var skeleton := module.get_skeleton()
var media_pipe = module.get_node("../MediaPipeController")
if (not model) or (not skeleton) or (not media_pipe): return
var shape_dict = media_pipe.blend_shape_last_values
var bone_poses = {}
for bone_name in module.tracked_bones:
var bone_idx = skeleton.find_bone(bone_name)
if bone_idx == -1: continue
var bone_pose = skeleton.get_bone_pose(bone_idx)
bone_poses[bone_name] = bone_pose
# FIXME: This sends way more information than necessary, but works as a proof-of-concept!
module.sync_model_animation.rpc(model.transform, shape_dict, bone_poses)
Loading…
Cancel
Save