how to use godot fbx importer with script

Viewed 315

I'm trying to make a game that is online and I want to import some 3d files from the server but I don't know how to import them Into the game with a script.

Is it possible to convert fbx into PoolByteArray on the server-side and convert them to fbx again in the client-side?

2 Answers

Network Code

Basic Server setup

Since we want Client-Server and we want to send a PoolByteArray, I'm assuming we are making an authoritative server with Godot (not other technology). In particular we will be using the MultiplayerAPI.

We can start with an object of the class NetworkedMultiplayerEnet. And we will pick a port to listen to (Ideally you would make it configurable, but here it is hard coded):

SERVER Server.gd:

extends Node

var network := NetworkedMultiplayerENet.new()
var port:int = 1024

func _ready():
    # start server
    var error: = network.create_server(port)
    if error != OK:
        print ("Error Creating Server: ", error)
        return

    get_tree().multiplayer.set_network_peer(network)

Basic Client setup

The client code is very similar. Except we need to provide the address and port of the server (here I have hard coded "127.0.0.1").

CLIENT server.gd:

extends Node

var network := NetworkedMultiplayerENet.new()
var server_port:int = 1024
var server_address:String = "127.0.0.1"

func _ready():
    # start server
    var error: = network.create_client(server_address, server_port)
    if error != OK:
        print ("Error Connecting to the Server: ", error)
        return

    get_tree().multiplayer.set_network_peer(network)

Client-Server connection

We can connect the "network_peer_connected" and "network_peer_disconnected" signals of the MultiplayerAPI:

CLIENT and SERVER Server.gd:

get_tree().multiplayer.connect("network_peer_connected", self, "_on_peer_connected")
get_tree().multiplayer.connect("network_peer_disconnected", self, "_on_peer_disconnected")

We can find similar signals on the SceneTree and on NetworkedMultiplayerEnet. They all work. I'm using the MultiplayerAPI just to be consistent.

Regardless of which version of these signals we connect, the handlers will look and behave the same:

func _on_peer_connected(player_id:int) -> void:
    print("Player Connected with id: ", player_id)

func _on_peer_disconnected(player_id:int) -> void:
    print("Player Disconnected with id: ", player_id)

On the client you should get a peer connection with the player_id of 1. That is always the server. On the server you will get a peer connected signal with a large player_id number which identifies the client. If there are multiple clients, they get peer connection signals for each other.

Similarly you will get the peer disconnected signal when a client goes offline. However, the clients will not get a peer disconnected signal when the server goes offline. Instead you get a "server_disconnected" signal. There is also a "connected_to_server" signal available on the MultiplayerAPI (and on the SceneTree).


Sending a PoolByteArray across the network

On the server, let us send our PoolByteArray. For example on a successful connection:

func _on_peer_connected(player_id:int) -> void:
    print("Player Connected with id: ", player_id)
    get_tree().multiplayer.send_bytes(PoolByteArray([125, 30, 50]), player_id)

You would, of course, keep track of the ids of the connected players, and send the PoolByteArray to the appropiate client at the appropriate time. For explanations purposes I'm doing it like this.


Receiving a PoolByteArray across the network

On the client, we will connect the "network_peer_packet" signal:

get_tree().multiplayer.connect("network_peer_packet", self, "_on_network_packet")

And we get the PoolByteArray as parameter on the handler:

func _on_network_packet(player_id:int, packet:PoolByteArray) -> void:
    print("Packet from ", player_id)

Read and Write file as PoolByteArray

Read PoolByteArray

We will use an object of the File class, and the get_buffer function.

func read_file_to_pool_byte_array(path:String) -> PoolByteArray:
    var file = File.new()
    var error = file.open("user://path/to/file", File.READ)
    if error == OK:
        var result:PoolByteArray = file.get_buffer(file.get_len())
        file.close()
        return result
    else:
        print("Error opening the file.")

Write PoolByteArray

func write_file_from_pool_byte_array(path:String, data:PoolByteArray) -> void:
    var file = File.new()
    var error = file.open("user://path/to/file", File.WRITE)
    if error == OK:
        file.store_buffer(data)
        file.close()
    else:
        print("Error opening the file.")

Resource serialization

As you would know, you can load a resource with load, preload, ResourceLoader.load or ResourceLoader.load_interactive (see Background loading).

The counterpart is ResourceSaver. You can save a resource to a file with ResourceSaver.save.


FBX?

No.

The features necessary to import FBX can only be instantiated by the editor. It is not available on game builds. Ok, at least it is not available by default, but enabling it requires a custom build of Godot. Unless you want to build Godot from source to enable them… That is a "No".


Bringing it all together

Import your FBX on a Godot project. It gets imported as a scene. Open it and save it as a resource format. That is the file you will send, not the FBX.

On the server:

  • Read the resource file to a PoolByteArray.
  • Send the PoolByteArray to the client.

On the client:

  • Receive the PoolByteArray from the server.
  • Save the PoolByteArray to a file.
  • Load the file with ResourceLoader.load or ResourceLoader.load_interactive, so you get a PackedScene

Then you can instance the PackedScene by calling instance on it when necessary.

Since you will be storing the resource file on the client. You can get more involved in the solution and have the client request the resource from the server only when it does not have it.

You might even have the server tell the client a hash of the file... Then, for each file, the client can check if it has it, and if the hash matches (see HashingContext). If it does not have it, or the hash does not match, then the client can request the file from the server.

You might do this on demand… Or have the server send a manifest with all the files and their hashes, and do it up front, with a progress bar.

You could also consider having a binary diff solution, but that is beyond the scope of this post.

thanks to the theraot for solving my problem I do it like this. and this code should work on any project just copy and paste just change the port number

`### clint side
# main clint server code
var network
var port = 9166
var ip = "127.0.0.1"

# create clint server
func _ready():
    network = NetworkedMultiplayerENet.new()
    network.create_client(ip,port)
    get_tree().set_network_peer(network)
    print("server start")
    
    network.connect("connection_failed",self,"_Network_connection_failed")
    network.connect("connection_succeeded",self,"_Network_connection_succeeded")
    
# sinal shot when you connect to server
func _Network_connection_succeeded():
    print("connect")
# sinal shot when you disconnect to server
func _Network_connection_failed():
    print("disconnect")

# must be called in the game for file that you want
func send_object_requast(object):
    rpc_id(1,"send_back_object", object)

# server response to your requast
remote func received_object(informatoin):
    # i dont know have to create new file so i copy existing file
    # if you know help me
    var file = Directory.new()
    file.copy("res://sourse/2d material/white.png","res://sourse/2d material/123.png")
    
    # store file in flie you create
    var object = File.new()
    object.open("res://sourse/2d material/123.png", File.WRITE)
    object.store_buffer(informatoin)
    object.close()
    print("done")`

and you should make the server project

`## server side
# main ENet code
var network = NetworkedMultiplayerENet.new()
var port = 9166
var max_player = 100

# create server
func _ready():
    network.create_server(port,max_player)
    get_tree().set_network_peer(network)
    print("main server start")
    network.connect("peer_connected", self,"peer_connected_")
    network.connect("peer_disconnected", self, "peer_disconnected_")

# signal shot when any clint connect
func peer_connected_(player_id):
    print("user " + str(player_id) + "connect")

# signal shot when any clint diconnect
func peer_disconnected_(player_id):
    print("user" + str(player_id)+ "disconnect")


# received and response back to clint
remote func send_back_object(object):
    var plyer_id = get_tree().get_rpc_sender_id()
    var file = File.new()
    # you can choise any file for testing i choise godot icon
    file.open("res://icon.png", File.READ)
    var bytes : PoolByteArray = file.get_buffer(file.get_len())
    file.close()
    rpc_id(plyer_id, "received_object", bytes)`
Related