Have you fully followed the tutorial?
Here are some methods (maybe not the best ones)
I store all the back-end logic in an Autoload node called DatabaseLinker
. This node handles everything related to the server, such as disconnection, connection, and reconnection. It also triggers any changes made in the database through Server-Sent Event.
To solve your issues, you can try this:
- You can send player data to Firebase. For each retrieved entry, you can create a new player in your scene. When this instance gets hurt,
DatabaseLinker
notifies the server.
Your data on the server may look like this:
"hosts": {
# The id returned by Firebase when a player create a lobby
# is used as ID server/lobby id
"-YRoZcAzsROP8fDJCaZz": {
"timestamp": 4684646544363157,
"max_players": 200,
# The id of the player who first registered
"host": "-OEnNcAzLRWJ5fDJCaZz",
"notification": "",
"expulsed_player": "",
"players": {
# The id returned when a player register its data
# in the data base is used as ID in the game
"-OEnNcAzLRWJ5fDJCaZz": {
"name": "player1",
"id": "-OEnNcAzLRWJ5fDJCaZz",
"ready": true,
"requested": {
"method": "_on_damaged",
"arguments": {
"amount": 200,
"critique": 12,
"from": "-OEnNcAzLWJ5fJsdCaZz",
}
},
"in_results": false,
"state": {
"position": "Vector3(45,86,98)",
"rotation": "Vector3(0,0,0)",
}
}
}
}
}
In the DatabaseLinker
script, you can detect when a change is made to requested
and then parse the information. In this example, the _on_damaged
method will be called with the specified arguments."
-
You can simply use the ID returned by Firebase (e.g., -YRoZcAzsROP8fDJCaZz) as a host/server ID. Your DatabaseLinker
script should retrieve this ID and then send all requests to Firebase within this data ID.
-
With Server-Sent Events, you don’t need to read every server or the entire server data, only the specific properties you want to trigger. You can listen to data, as described in part 2 of the tutorial. Scroll down until you see ‘Server-Sent Events’."
-
It might be because you’re fetching the server data every frame.You can implement a function that only triggers necessary changes and updates your scene accordingly.
Take a look at what I use:
var _players_url: String = "%s/hosts/%s/players.json" % [DATA_BASE_URL, "%s"]:
set(value):
_players_url = value
_remote_data_to_listen["players"]["url"] = _players_url
var _expulsed_player_url: String = "%s/hosts/%s/expulsed_player.json" % [DATA_BASE_URL, "%s"]:
set(value):
_expulsed_player_url = value
_remote_data_to_listen["expulsed_player"]["url"] = _expulsed_player_url
var _vars_depending_on_host_id := PackedStringArray([
"_players_url", "_host_name_url", "_expulsed_player_url",
"_notification_url", "_game_data_url", "_log_url", "_host_open_prop_url",
])
## Syntax: {"url": (...), "func": (...)}
## func -> function to call if changes are made in data base
var _remote_data_to_listen := {
"players": {
"func": "_on_players_changed",
},
"host_name": {
"func": "_on_host_name_changed",
},
"expulsed_player": {
"func": "_on_player_expulsed",
},
"notification": {
"func": "_on_database_notified",
},
"log": {
"func": "_on_in_game_log_updated",
},
}
func _setup_tcp_stream() -> StreamPeerTCP:
var tcp: StreamPeerTCP = StreamPeerTCP.new()
var error = tcp.connect_to_host(DATA_BASE, HTTPS_PORT)
assert(error == OK)
tcp.poll()
var status = tcp.get_status()
while status != StreamPeerTCP.STATUS_CONNECTED:
await get_tree().process_frame
tcp.poll()
status = tcp.get_status()
return tcp
func _setup_tls_stream(tcp: StreamPeerTCP) -> StreamPeerTLS:
var stream: StreamPeerTLS = StreamPeerTLS.new()
var error = stream.connect_to_stream(tcp, DATA_BASE)
assert(error == OK)
stream.poll()
var status = stream.get_status()
while status != StreamPeerTLS.STATUS_CONNECTED:
await get_tree().process_frame
stream.poll()
status = stream.get_status()
return stream
func _start_sse_stream(stream: StreamPeer, url: String = HOSTS_URL) -> void:
var request_line: String = "GET %s HTTP/1.1" % url
var headers: Array = [
"Host: %s" % DATA_BASE,
"Accept: text/event-stream",
]
var _request: String = ""
_request += request_line + "\n" # request line
_request += "\n".join(headers) + "\n" # headers
_request += "\n" # empty line
stream.put_data(_request.to_ascii_buffer())
func _read_stream_response(stream: StreamPeer) -> String:
stream.poll()
var available_bytes: int = stream.get_available_bytes()
while available_bytes == 0:
await get_tree().process_frame
stream.poll()
available_bytes = stream.get_available_bytes()
return stream.get_string(available_bytes)
class EventData:
var type: String
var data: Dictionary
func _parse_event_data(str_event: String) -> EventData:
var event_lines: Array = str_event.split("\n")
if event_lines.size() != 2: return null
var event_type_line = event_lines[0]
if !event_type_line.begins_with(EVENT_TYPE_PREFIX):
return null
var event_data_line = event_lines[1]
if !event_data_line.begins_with(EVENT_DATA_PREFIX):
return null
var event_type_str = event_type_line.substr(EVENT_TYPE_PREFIX.length())
var event_data_str = event_data_line.substr(EVENT_DATA_PREFIX.length())
var event_data_json = Game.get_parsed_json_data(event_data_str)#JSON.parse_string(event_data_str)
if event_data_json == null:
event_data_json = {}
var event: EventData = EventData.new()
event.type = event_type_str
event.data = event_data_json
return event
func _parse_response_event_data(response: String) -> Array[Dictionary]:
var response_parts: PackedStringArray = response.replace("\r", "").split("\n\n")
var event_data: Array[Dictionary] = []
for response_part: String in response_parts:
var event = _parse_event_data(response_part)
if event:
print(event.data)
if event == null or event.type != "put": continue
event_data.append(event.data)
return event_data
func _start_listenning_cur_host() -> void:
stop_listenning_cur_host = false
for _data: String in _remote_data_to_listen:
# Doesn't wait func to fisnish in order to allow others "_data" to be processed
_listen_remote_data(_data)
func _listen_remote_data(_data: String) -> void:
var tcp: StreamPeerTCP = await _setup_tcp_stream()
var stream: StreamPeerTLS = await _setup_tls_stream(tcp)
_start_sse_stream(stream, _remote_data_to_listen[_data]["url"])
#await _read_stream_response(stream)
while not stop_listenning_cur_host:
var response = await _read_stream_response(stream)
var events: Array[Dictionary] = _parse_response_event_data(response)
if is_hoster:
print("events: ", events)
if (
events.is_empty()
or events[0].is_empty()
or events[0].get("data") == null
or (typeof(events[0]["data"]) == TYPE_STRING)# and events[0]["data"] == "")
or (typeof(events[0]["data"]) == TYPE_DICTIONARY and events[0]["data"].is_empty())
):
continue
for event: Dictionary in events:
call(_remote_data_to_listen[_data]["func"], event["data"])
-
You can trigger a game exit using the DatabaseLinker
script and send the information to other players. In your player script, you can also ping the server every 10 seconds to check if the player is still connected. If no data is received after a certain delay, the player is considered disconnected.
-
Same as response 3.
Additionally, you can check out this demo project. It’s not mine, and I use a very different method for handling data with Firebase.