All right - here’s the full script for the project:
This script is used as a global autoload to keep track of player data.
extends Node
var Players = {}
Here’s the script for the network start. The scene has 3 buttons: Host, Join, and Start.
extends Control
@export var network = "127.0.0.1"
@export var PORT = 9999
var max_players = 10
var peer
# Called when the node enters the scene tree for the first time.
func _ready():
multiplayer.peer_connected.connect(peer_connected)
multiplayer.peer_disconnected.connect(peer_disconnected)
multiplayer.connected_to_server.connect(connected_to_server)
multiplayer.connection_failed.connect(connection_failed)
multiplayer.server_disconnected.connect(server_disconnected)
func server_disconnected():
print("Server Disconnected")
get_tree().quit()
func peer_connected(id):
print("Player Connected " + str(id))
func peer_disconnected(id):
print("Player Disconnected " + str(id))
GameManager.Players.erase(id)
var players = get_tree().get_nodes_in_group("Player")
for i in players:
if i.name == str(id):
i.queue_free()
func connected_to_server():
print("Connected to server!")
# rpc the host with the name and unique id of the player who joined
Send_Player_Information.rpc_id(1,"", multiplayer.get_unique_id())
func connection_failed(): print("Couldn't connect!")
# update the player information (called from rpc when a new player joins the server)
@rpc("any_peer")
func Send_Player_Information(name, id):
if not GameManager.Players.has(id):
GameManager.Players[id]={
"name" : name,
"id" : id
}
if multiplayer.is_server():
for i in GameManager.Players:
Send_Player_Information.rpc(GameManager.Players[i].name, i)
func host_game():
peer = ENetMultiplayerPeer.new()
# port to listen on, max number of players
var error = peer.create_server(PORT, max_players)
if error != OK:
print("cannot host " + error)
return
multiplayer.set_multiplayer_peer(peer)
# send host name and id
Send_Player_Information("", multiplayer.get_unique_id())
print("waiting for players")
func join_game():
peer = ENetMultiplayerPeer.new()
# first the IP address, then the port to listen on (1--9999)
peer.create_client(network, PORT)
multiplayer.set_multiplayer_peer(peer)
# everybody gets this rpc, including the player who calls it
@rpc("any_peer", "call_local")
func start_game():
var scene = load("res://level.tscn").instantiate()
get_tree().get_root().add_child(scene)
self.hide()
func _on_start_button_pressed():
start_game.rpc()
Here’s the script for the game level:
extends Node2D
@export var PlayerScene : PackedScene
func _ready():
var index = 0
for i in GameManager.Players:
var CurrentPlayer = PlayerScene.instantiate()
CurrentPlayer.name = str(GameManager.Players[i].id)
add_child(CurrentPlayer)
for spawn in get_tree().get_nodes_in_group("SpawnLocations"):
if spawn.name == str(index):
CurrentPlayer.global_position = spawn.global_position
index += 1
And finally, here’s the full player script. Please note that many functions are commented out, as they are still in progress:
extends CharacterBody2D
### ONREADY VARIABLES ###
@onready var kill_cooldown = $Kill_Cooldown
@onready var kill_button = $UI_Vampire/VBoxContainer/Kill
@onready var pivot = $pivot
### SIGNALS ###
# used to tell if an object in the kill or vision zone is a player or not
signal player
### PLAYER TYPE VARIABLES ###
@export var is_vampire = true
@export var is_human = false
@export var is_ghost = false
# if the vampire can kill or not
var cooldown_active = false
# number of lives the player has
var lives = 3
# blood points that can be spent on buffs TODO
var blood_points = 0
# whether the player is hiding or not TODO
var is_hiding = false
var animated_sprite
var killable_player = ""
### CHARACTER MOVEMENT ###
# character speed
var speed = 100
var walking = false
var action_in_progress = false
# Called when the node enters the scene tree for the first time.
func _ready():
if is_vampire:
animated_sprite = $Vampire_Sprite
$MultiplayerSynchronizer.set_multiplayer_authority(str(name).to_int())
if $MultiplayerSynchronizer.get_multiplayer_authority() == multiplayer.get_unique_id():
# shows the vampire UI, connects the kill button, connects the kill cooldown timer, sets number of lives
if is_vampire:
$UI_Vampire.show()
kill_button.pressed.connect(kill)
kill_cooldown.timeout.connect(reset_cooldown)
lives = 3
animated_sprite = $Vampire_Sprite
animated_sprite.show()
print(str(animated_sprite))
# shows the human UI
if is_human:
$UI_Human.show()
lives = 1
animated_sprite = $Human_Sprite
animated_sprite.show()
print(str(animated_sprite))
$Camera2D.enabled = true
func get_input():
if $MultiplayerSynchronizer.get_multiplayer_authority() == multiplayer.get_unique_id():
var input_dir = Input.get_vector("left", "right", "up", "down")
if Input.is_action_pressed("left"):
animated_sprite.flip_h = true
animated_sprite.play("walk")
pivot.rotation_degrees = 0
walking = true
elif Input.is_action_pressed("right"):
animated_sprite.flip_h = false
animated_sprite.play("walk")
pivot.rotation_degrees = 180
walking = true
elif Input.is_action_pressed("down"):
animated_sprite.play("walk")
pivot.rotation_degrees = 270
walking = true
elif Input.is_action_pressed("up"):
animated_sprite.play("walk")
pivot.rotation_degrees = 90
walking = true
else: walking = false
# animated_sprite.play("idle")
if Input.is_action_pressed("down") and Input.is_action_pressed("left"):
pivot.rotation_degrees = 315
elif Input.is_action_pressed("down") and Input.is_action_pressed("right"):
pivot.rotation_degrees = 225
elif Input.is_action_pressed("up") and Input.is_action_pressed("left"):
pivot.rotation_degrees = 45
elif Input.is_action_pressed("up") and Input.is_action_pressed("right"):
pivot.rotation_degrees = 135
#if walking == false and action_in_progress == false:
#animated_sprite.play("idle")
velocity = input_dir * speed
func _physics_process(delta):
if $MultiplayerSynchronizer.get_multiplayer_authority() == multiplayer.get_unique_id():
get_input()
move_and_slide()
func _input(event):
#if $MultiplayerSynchronizer.get_multiplayer_authority() == multiplayer.get_unique_id():
if Input.is_action_pressed("test"):
print(str(animated_sprite))
func kill():
if cooldown_active == false and killable_player != "":
animated_sprite.play("kill")
action_in_progress = true
#print("kill!")
cooldown_active = true
kill_cooldown.start()
kill_button.disabled = true
rpc_id(int(killable_player), "die")
# reset kill cooldown when you feed on a body
func reset_cooldown():
cooldown_active = false
kill_button.disabled = false
@rpc("any_peer")
func feed():
kill_cooldown.time_left = 0
#action_in_progress = true TODO
# character death
@rpc("any_peer", "reliable")
func die():
if is_vampire:
# subtract a life
lives = lives - 1
# if vampire is totally dead
if lives <= 0:
action_in_progress = true
#animated_sprite = $Vampire_Sprite
animated_sprite.play("die")
print("Vampire's been killed!")
# insert victory here! TODO
# if vampire can respawn
elif lives > 0:
action_in_progress = true
#animated_sprite = $Vampire_Sprite
animated_sprite.play("crumble")
print("Vampire's lost a life: now has " + str(lives))
# respawn at the sarcophagus! TODO
if is_human:
print("killed")
lives -= 1
if lives >= 0: print("human's dead!")
animated_sprite.play("die")
action_in_progress = true
# turn into ghost and leave corpse behind here! TODO
func hide_self():
is_hiding = true
# insert hiding code here TODO !
# if another player enters the kill zone
func kill_zone_entered(character):
if character.has_signal("player") and character != self:
print("this player entered the kill zone" + str(character.name))
killable_player = str(character.name)
# if a player exits the kill zone
func kill_zone_exited(character):
if character.has_signal("player") and character != self:
print("this player exited the kill zone" + str(character.name))
killable_player = ""
#func vision_zone_entered(character):
#if character.has_signal("player") and character != self:
#print("this player entered the vision zone" + str(character.name))
#character.show()
#func vision_zone_exited(character):
#if character.has_signal("player") and character != self:
#print("this player exited the vision zone" + str(character.name))
#character.hide()
# emitted when any animated_sprite ends an animation
func animated_sprite_animation_timeout():
action_in_progress = false
Here’s the node layout for the player. Only the Vampire UIs and AnimatedSprite2Ds are used.

I’ll add a picture of what properties are synced in the MultiplayerSynchronizer in another post - the forum won’t let me post more than one in a reply yet.
I hope this is enough - please let me know if I need to add anything.
Thanks so much, @pennyloafers!