litenetlibで始まる
図書館には多くのチュートリアルやドキュメンテーションがないので、設定を始めるためのガイドを書くことにしましたLiteNetLib 基本的なマルチプレイヤーゲーム.
あなたはそれに精通していない場合は、LitenetLibは、信頼性の高いUDPプロトコルを実装するゲーム開発のための軽量Cの経典ネットワークライブラリです.これは、商業ゲームで使用されている7 Days to Die .
この記事のコードスニペットはGodotを使用しますが、Unityのためのコードまたはあなたが使用しているどんな他のプラットホームでも簡単に適応できます.
クライアントとサーバ
私は実装 すべてのコールバックとして、今のところほとんどメソッドを空に保つことができます. もし 次のステップはクライアントをサーバに接続します.
おそらく最終的にサーバーに接続できるピアの数を制限する必要があります.これはチェックで行うことができます サーバーへの「パスワード」(接続キー)を加えることができます.そして、それは時代遅れのクライアントがサーバーに加わるのを妨げるプレーヤーのようなものに役立ちます.のパラメータを見てください パケットとの通信
あなたが以前にstructを定義したならば LitenetLibは、カップルの異なるパケット配信方法を持っています.あなたは、使いたいです あなたは2番目の引数として何を渡すことができます あなたが今ゲームをテストする場合は、すべてうまくいくはずです.クライアントはAを送信します
選手の更新
サーバから更新を送信するためのコードを、すべてのフレームで行うのではなく、タイマー上に置くべきでしょう.20秒から30回の更新は、ほとんどの場合に十分です.これは一般的にクライアントよりも重要ではありません.一般的にサーバよりも高速な更新を行うことができます. サーバから受信したデータの一部を確認するのに便利かもしれません.たとえば、クライアントが無効なPIDを与えると、Godotは存在しないノードにアクセスしようとします.それらのチェックは、簡単のための例コードから除外されました. あなたが今ゲームを実行する場合は、他の接続された選手があなたと一緒に移動を参照してくださいする必要があります.
前進
あなたはそれに精通していない場合は、LitenetLibは、信頼性の高いUDPプロトコルを実装するゲーム開発のための軽量Cの経典ネットワークライブラリです.これは、商業ゲームで使用されている7 Days to Die .
この記事のコードスニペットはGodotを使用しますが、Unityのためのコードまたはあなたが使用しているどんな他のプラットホームでも簡単に適応できます.
クライアントとサーバ
クライアントとサーバの基本クラスから始めましょう.Godotを使用している場合は、autoloadにクライアントを追加して、シーン間でロードし続ける必要があります.
また、サーバークラスの別のシーンを作成します.CLI GODOTは同時にあなたのゲームの2つのインスタンスを実行するために使用することができます.cd
プロジェクトのディレクトリにgodot-mono scenes/Server.tscn
.
using Godot;
using System;
using LiteNetLib;
using LiteNetLib.Utils;
public class Client : Node, INetEventListener {
private NetManager client;
public void Connect() {
client = new NetManager(this) {
AutoRecycle = true,
};
}
public override void _Process(float delta) {
if (client != null) {
client.PollEvents();
}
}
// ... INetEventListener methods omitted
}
using Godot;
using System;
using LiteNetLib;
using LiteNetLib.Utils;
public class Server : Node, INetEventListener {
private NetManager server;
public override void _Ready() {
server = new NetManager(this) {
AutoRecycle = true,
};
}
public override void _Process(float delta) {
server.PollEvents();
}
// ... INetEventListener methods omitted
}
注意事項
using Godot;
using System;
using LiteNetLib;
using LiteNetLib.Utils;
public class Client : Node, INetEventListener {
private NetManager client;
public void Connect() {
client = new NetManager(this) {
AutoRecycle = true,
};
}
public override void _Process(float delta) {
if (client != null) {
client.PollEvents();
}
}
// ... INetEventListener methods omitted
}
using Godot;
using System;
using LiteNetLib;
using LiteNetLib.Utils;
public class Server : Node, INetEventListener {
private NetManager server;
public override void _Ready() {
server = new NetManager(this) {
AutoRecycle = true,
};
}
public override void _Process(float delta) {
server.PollEvents();
}
// ... INetEventListener methods omitted
}
INetEventListener
クラス自体です.うまくいけば、コードエディターは自動的にインタフェース実装を生成することができますEventBasedNetListener
そして、NetManager
代わりにREADME example . client != null
通常、サーバーとは異なり、すぐに起動されません. private NetPeer server;
public void Connect() {
// ...
client.Start();
GD.Print("Connecting to server");
client.Connect("localhost", 12345, "");
}
public void OnPeerConnected(NetPeer peer) {
GD.Print("Connected to server");
server = peer;
}
public override void _Ready() {
// ...
GD.Print("Starting server");
server.Start(12345);
}
public void OnConnectionRequest(ConnectionRequest request) {
GD.Print($"Incoming connection from {request.RemoteEndPoint.ToString()}");
request.Accept();
}
注意事項server.ConnectedPeerCounts
必要に応じて要求を拒否する.client.Connect
and connection.AcceptIfKey
. パケットとの通信
クライアントとサーバ間で通信するには、まずいくつかのパケットを定義しなければなりません.
public class JoinPacket {
public string username { get; set; }
}
public class JoinAcceptPacket {
public PlayerState state { get; set; }
}
public struct PlayerState : INetSerializable {
public uint pid;
public Vector2 position;
public void Serialize(NetDataWriter writer) {
writer.Put(pid);
writer.Put(position);
}
public void Deserialize(NetDataReader reader) {
pid = reader.GetUInt();
position = reader.GetVector2();
}
}
public class ClientPlayer {
public PlayerState state;
public string username;
}
public class ServerPlayer {
public NetPeer peer;
public PlayerState state;
public string username;
}
パケットクラスは、litenetlibによって自動的にシリアル化されます.ただし、プロパティは{ get; set; }
必要です.代わりに構造体を定義し、実装することもできますINetSerializable
手動でPlayerState
. これは、構造体のコピーセマンティクスを取得するので有用です.
しかしながら、我々はまだ何かを逃しています.あなたは気づいたかもしれないreader.GetVector2
and writer.Put(Vector2)
関数は実際には存在しません.LitenetLibはデフォルトで最も基本的なデータ型をシリアル化することができますが、GodotのVector2
, それがstructであるので.拡張メソッドを実装しましょうNetDataWriter
and NetDataReader
:
public static class SerializingExtensions {
public static void Put(this NetDataWriter writer, Vector2 vector) {
writer.Put(vector.x);
writer.Put(vector.y);
}
public static Vector2 GetVector2(this NetDataReader reader) {
return new Vector2(reader.GetFloat(), reader.GetFloat());
}
}
ではパケットを送信しましょう.
private NetDataWriter writer;
private NetPacketProcessor packetProcessor;
private ClientPlayer player = new ClientPlayer();
public void Connect(string username) {
player.username = username;
writer = new NetDataWriter();
packetProcessor = new NetPacketProcessor();
packetProcessor.RegisterNestedType((w, v) => w.Put(v), reader => reader.GetVector2());
packetProcessor.RegisterNestedType<PlayerState>();
packetProcessor.SubscribeReusable<JoinAcceptPacket>(OnJoinAccept);
// ...
}
public void SendPacket<T>(T packet, DeliveryMethod deliveryMethod) where T : class, new() {
if (server != null) {
writer.Reset();
packetProcessor.Write(writer, packet);
server.Send(writer, deliveryMethod);
}
}
public void OnJoinAccept(JoinAcceptPacket packet) {
GD.Print($"Join accepted by server (pid: {packet.state.pid})");
player.state = packet.state;
}
public void OnPeerConnected(NetPeer peer) {
// ...
SendPacket(new JoinPacket { username = player.username }, DeliveryMethod.ReliableOrdered);
}
public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) {
packetProcessor.ReadAllPackets(reader);
}
[Export] public Vector2 initialPosition = new Vector2();
private NetDataWriter writer;
private NetPacketProcessor packetProcessor;
private Dictionary<uint, ServerPlayer> players = new Dictionary<uint, ServerPlayer>();
public override void _Ready() {
writer = new NetDataWriter();
packetProcessor = new NetPacketProcessor();
packetProcessor.RegisterNestedType((w, v) => w.Put(v), reader => reader.GetVector2());
packetProcessor.RegisterNestedType<PlayerState>();
packetProcessor.SubscribeReusable<JoinPacket, NetPeer>(OnJoinReceived);
// ...
}
public void SendPacket<T>(T packet, NetPeer peer, DeliveryMethod deliveryMethod) where T : class, new() {
if (peer != null) {
writer.Reset();
packetProcessor.Write(writer, packet);
peer.Send(writer, deliveryMethod);
}
}
public void OnJoinReceived(JoinPacket packet, NetPeer peer) {
GD.Print($"Received join from {packet.username} (pid: {(uint)peer.Id})");
ServerPlayer newPlayer = (players[(uint)peer.Id] = new ServerPlayer {
peer = peer,
state = new PlayerState {
pid = (uint)peer.Id,
position = initialPosition,
},
username = packet.username,
});
SendPacket(new JoinAcceptPacket { state = newPlayer.state }, peer, DeliveryMethod.ReliableOrdered);
}
public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) {
packetProcessor.ReadAllPackets(reader, peer);
}
public void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) {
if (peer.Tag != null) {
players.Remove((uint)peer.Id);
}
}
注意事項
public class JoinPacket {
public string username { get; set; }
}
public class JoinAcceptPacket {
public PlayerState state { get; set; }
}
public struct PlayerState : INetSerializable {
public uint pid;
public Vector2 position;
public void Serialize(NetDataWriter writer) {
writer.Put(pid);
writer.Put(position);
}
public void Deserialize(NetDataReader reader) {
pid = reader.GetUInt();
position = reader.GetVector2();
}
}
public class ClientPlayer {
public PlayerState state;
public string username;
}
public class ServerPlayer {
public NetPeer peer;
public PlayerState state;
public string username;
}
public static class SerializingExtensions {
public static void Put(this NetDataWriter writer, Vector2 vector) {
writer.Put(vector.x);
writer.Put(vector.y);
}
public static Vector2 GetVector2(this NetDataReader reader) {
return new Vector2(reader.GetFloat(), reader.GetFloat());
}
}
private NetDataWriter writer;
private NetPacketProcessor packetProcessor;
private ClientPlayer player = new ClientPlayer();
public void Connect(string username) {
player.username = username;
writer = new NetDataWriter();
packetProcessor = new NetPacketProcessor();
packetProcessor.RegisterNestedType((w, v) => w.Put(v), reader => reader.GetVector2());
packetProcessor.RegisterNestedType<PlayerState>();
packetProcessor.SubscribeReusable<JoinAcceptPacket>(OnJoinAccept);
// ...
}
public void SendPacket<T>(T packet, DeliveryMethod deliveryMethod) where T : class, new() {
if (server != null) {
writer.Reset();
packetProcessor.Write(writer, packet);
server.Send(writer, deliveryMethod);
}
}
public void OnJoinAccept(JoinAcceptPacket packet) {
GD.Print($"Join accepted by server (pid: {packet.state.pid})");
player.state = packet.state;
}
public void OnPeerConnected(NetPeer peer) {
// ...
SendPacket(new JoinPacket { username = player.username }, DeliveryMethod.ReliableOrdered);
}
public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) {
packetProcessor.ReadAllPackets(reader);
}
[Export] public Vector2 initialPosition = new Vector2();
private NetDataWriter writer;
private NetPacketProcessor packetProcessor;
private Dictionary<uint, ServerPlayer> players = new Dictionary<uint, ServerPlayer>();
public override void _Ready() {
writer = new NetDataWriter();
packetProcessor = new NetPacketProcessor();
packetProcessor.RegisterNestedType((w, v) => w.Put(v), reader => reader.GetVector2());
packetProcessor.RegisterNestedType<PlayerState>();
packetProcessor.SubscribeReusable<JoinPacket, NetPeer>(OnJoinReceived);
// ...
}
public void SendPacket<T>(T packet, NetPeer peer, DeliveryMethod deliveryMethod) where T : class, new() {
if (peer != null) {
writer.Reset();
packetProcessor.Write(writer, packet);
peer.Send(writer, deliveryMethod);
}
}
public void OnJoinReceived(JoinPacket packet, NetPeer peer) {
GD.Print($"Received join from {packet.username} (pid: {(uint)peer.Id})");
ServerPlayer newPlayer = (players[(uint)peer.Id] = new ServerPlayer {
peer = peer,
state = new PlayerState {
pid = (uint)peer.Id,
position = initialPosition,
},
username = packet.username,
});
SendPacket(new JoinAcceptPacket { state = newPlayer.state }, peer, DeliveryMethod.ReliableOrdered);
}
public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) {
packetProcessor.ReadAllPackets(reader, peer);
}
public void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) {
if (peer.Tag != null) {
players.Remove((uint)peer.Id);
}
}
packetProcessor.SubscribeReusable
は新しいパケットを作成するのではなく、同じパケットクラスインスタンスを再利用するので、その内容やその内容への参照を格納しないようにしてください!これは製造の別の利点ですPlayerState
構造体、簡単にコピーできるように.INetSerializable
, あなたはそれを使用して登録する必要がありますpacketProcessor.RegisterNestedType<YourType>
. DeliveryMethod.ReliableOrdered
ほとんどの物についてはDeliveryMethod.Unreliable
高速更新の場合、ドロップパケットや2つの問題はあまりにも問題はない.packetProcessor.ReadAllPackets
を使う.この場合、我々は同輩を必要とするだけです.JoinPacket
そして、サーバは適切に対応するでしょうJoinAcceptPacket
.選手の更新
ネットワーク上でプレイヤー情報を送信するには、まずプレイヤークラスが必要です.あなたはすでにあなたのゲームのいずれかを持っている必要があります.
public class Player : Node2D {
[Export] public float moveSpeed = 200;
public static Player instance;
public override void _Ready() {
instance = this;
}
public override void _Process(float delta) {
Vector2 velocity = new Vector2();
if (Input.IsActionPressed("ui_left")) velocity.x -= 1;
if (Input.IsActionPressed("ui_right")) velocity.x += 1;
if (Input.IsActionPressed("ui_up")) velocity.y -= 1;
if (Input.IsActionPressed("ui_down")) velocity.y += 1;
Position += velocity * moveSpeed * delta;
}
}
どんな些細なゲームでも、クライアントプレイヤーのすべての処理ロジックを必要としないので、他のプレイヤーを代表する別のクラスを必要とするでしょう.
public class RemotePlayer : Node2D {}
ではもう少しパケットを定義しましょう.
public class PlayerSendUpdatePacket {
public Vector2 position { get; set; }
}
public class PlayerReceiveUpdatePacket {
public PlayerState[] states { get; set; }
}
public class PlayerJoinedGamePacket {
public ClientPlayer player { get; set; }
}
public class PlayerLeftGamePacket {
public uint pid { get; set; }
}
// ...
public struct ClientPlayer : INetSerializable {
public PlayerState state;
public string username;
public void Serialize(NetDataWriter writer) {
state.Serialize(writer);
writer.Put(username);
}
public void Deserialize(NetDataReader reader) {
state.Deserialize(reader);
username = reader.GetString();
}
}
我々は送信されるのでClientPlayer
ネットワーク上で、我々はそれを構造体と実装に変えますINetSerializable
.
では、これらのパケットを送信します.
public override void Connect() {
// ...
packetProcessor.RegisterNestedType<ClientPlayer>();
packetProcessor.SubscribeReusable<PlayerReceiveUpdatePacket>(OnReceiveUpdate);
packetProcessor.SubscribeReusable<PlayerJoinedGamePacket>(OnPlayerJoin);
packetProcessor.SubscribeReusable<PlayerLeftGamePacket>(OnPlayerLeave);
// ...
}
public override void _Process(float delta) {
if (client != null) {
client.PollEvents();
if (Player.instance != null) {
SendPacket(new PlayerSendUpdatePacket { position = Player.instance.Position }, DeliveryMethod.Unreliable);
}
}
}
public void OnJoinAccept(JoinAcceptPacket packet) {
// ...
Player.instance.Position = player.state.position;
}
public void OnReceiveUpdate(PlayerReceiveUpdatePacket packet) {
foreach (PlayerState state in packet.states) {
if (state.pid == player.state.pid) {
continue;
}
((RemotePlayer)Player.instance.GetParent().GetNode(state.pid.ToString())).Position = state.position;
}
}
public void OnPlayerJoin(PlayerJoinedGamePacket packet) {
GD.Print($"Player '{packet.player.username}' (pid: {packet.player.state.pid}) joined the game");
RemotePlayer remote = (RemotePlayer)((PackedScene)GD.Load("res://scenes/RemotePlayer.tscn")).Instance();
remote.Name = packet.player.state.pid.ToString();
remote.Position = packet.player.state.position;
Player.instance.GetParent().AddChild(remote);
}
public void OnPlayerLeave(PlayerLeftGamePacket packet) {
GD.Print($"Player (pid: {packet.pid}) left the game");
((RemotePlayer)Player.instance.GetParent().GetNode(packet.pid.ToString())).QueueFree();
}
public override void _Ready() {
// ...
packetProcessor.RegisterNestedType<ClientPlayer>();
packetProcessor.SubscribeReusable<PlayerSendUpdatePacket, NetPeer>(OnPlayerUpdate);
// ...
}
public override void _Process(float delta) {
// ...
PlayerState[] states = players.Values.Select(p => p.state).ToArray();
foreach (ServerPlayer player in players.Values) {
SendPacket(new PlayerReceiveUpdatePacket { states = states }, player.peer, DeliveryMethod.Unreliable);
}
}
public void OnJoinReceived(JoinPacket packet, NetPeer peer) {
// ...
foreach (ServerPlayer player in players.Values) {
if (player.state.pid != newPlayer.state.pid) {
SendPacket(new PlayerJoinedGamePacket {
player = new ClientPlayer {
username = newPlayer.username,
state = newPlayer.state,
},
}, player.peer, DeliveryMethod.ReliableOrdered);
SendPacket(new PlayerJoinedGamePacket {
player = new ClientPlayer {
username = player.username,
state = player.state,
},
}, newPlayer.peer, DeliveryMethod.ReliableOrdered);
}
}
}
public void OnPlayerUpdate(PlayerSendUpdatePacket packet, NetPeer peer) {
players[(uint)peer.Id].state.position = packet.position;
}
public void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) {
GD.Print($"Player (pid: {(uint)peer.Id}) left the game");
if (peer.Tag != null) {
ServerPlayer playerLeft;
if (players.TryGetValue(((uint)peer.Id), out playerLeft)) {
foreach (ServerPlayer player in players.Values) {
if (player.state.pid != playerLeft.state.pid) {
SendPacket(new PlayerLeftGamePacket { pid = playerLeft.state.pid }, player.peer, DeliveryMethod.ReliableOrdered);
}
}
players.Remove((uint)peer.Id);
}
}
}
注意事項
public class Player : Node2D {
[Export] public float moveSpeed = 200;
public static Player instance;
public override void _Ready() {
instance = this;
}
public override void _Process(float delta) {
Vector2 velocity = new Vector2();
if (Input.IsActionPressed("ui_left")) velocity.x -= 1;
if (Input.IsActionPressed("ui_right")) velocity.x += 1;
if (Input.IsActionPressed("ui_up")) velocity.y -= 1;
if (Input.IsActionPressed("ui_down")) velocity.y += 1;
Position += velocity * moveSpeed * delta;
}
}
public class RemotePlayer : Node2D {}
public class PlayerSendUpdatePacket {
public Vector2 position { get; set; }
}
public class PlayerReceiveUpdatePacket {
public PlayerState[] states { get; set; }
}
public class PlayerJoinedGamePacket {
public ClientPlayer player { get; set; }
}
public class PlayerLeftGamePacket {
public uint pid { get; set; }
}
// ...
public struct ClientPlayer : INetSerializable {
public PlayerState state;
public string username;
public void Serialize(NetDataWriter writer) {
state.Serialize(writer);
writer.Put(username);
}
public void Deserialize(NetDataReader reader) {
state.Deserialize(reader);
username = reader.GetString();
}
}
public override void Connect() {
// ...
packetProcessor.RegisterNestedType<ClientPlayer>();
packetProcessor.SubscribeReusable<PlayerReceiveUpdatePacket>(OnReceiveUpdate);
packetProcessor.SubscribeReusable<PlayerJoinedGamePacket>(OnPlayerJoin);
packetProcessor.SubscribeReusable<PlayerLeftGamePacket>(OnPlayerLeave);
// ...
}
public override void _Process(float delta) {
if (client != null) {
client.PollEvents();
if (Player.instance != null) {
SendPacket(new PlayerSendUpdatePacket { position = Player.instance.Position }, DeliveryMethod.Unreliable);
}
}
}
public void OnJoinAccept(JoinAcceptPacket packet) {
// ...
Player.instance.Position = player.state.position;
}
public void OnReceiveUpdate(PlayerReceiveUpdatePacket packet) {
foreach (PlayerState state in packet.states) {
if (state.pid == player.state.pid) {
continue;
}
((RemotePlayer)Player.instance.GetParent().GetNode(state.pid.ToString())).Position = state.position;
}
}
public void OnPlayerJoin(PlayerJoinedGamePacket packet) {
GD.Print($"Player '{packet.player.username}' (pid: {packet.player.state.pid}) joined the game");
RemotePlayer remote = (RemotePlayer)((PackedScene)GD.Load("res://scenes/RemotePlayer.tscn")).Instance();
remote.Name = packet.player.state.pid.ToString();
remote.Position = packet.player.state.position;
Player.instance.GetParent().AddChild(remote);
}
public void OnPlayerLeave(PlayerLeftGamePacket packet) {
GD.Print($"Player (pid: {packet.pid}) left the game");
((RemotePlayer)Player.instance.GetParent().GetNode(packet.pid.ToString())).QueueFree();
}
public override void _Ready() {
// ...
packetProcessor.RegisterNestedType<ClientPlayer>();
packetProcessor.SubscribeReusable<PlayerSendUpdatePacket, NetPeer>(OnPlayerUpdate);
// ...
}
public override void _Process(float delta) {
// ...
PlayerState[] states = players.Values.Select(p => p.state).ToArray();
foreach (ServerPlayer player in players.Values) {
SendPacket(new PlayerReceiveUpdatePacket { states = states }, player.peer, DeliveryMethod.Unreliable);
}
}
public void OnJoinReceived(JoinPacket packet, NetPeer peer) {
// ...
foreach (ServerPlayer player in players.Values) {
if (player.state.pid != newPlayer.state.pid) {
SendPacket(new PlayerJoinedGamePacket {
player = new ClientPlayer {
username = newPlayer.username,
state = newPlayer.state,
},
}, player.peer, DeliveryMethod.ReliableOrdered);
SendPacket(new PlayerJoinedGamePacket {
player = new ClientPlayer {
username = player.username,
state = player.state,
},
}, newPlayer.peer, DeliveryMethod.ReliableOrdered);
}
}
}
public void OnPlayerUpdate(PlayerSendUpdatePacket packet, NetPeer peer) {
players[(uint)peer.Id].state.position = packet.position;
}
public void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) {
GD.Print($"Player (pid: {(uint)peer.Id}) left the game");
if (peer.Tag != null) {
ServerPlayer playerLeft;
if (players.TryGetValue(((uint)peer.Id), out playerLeft)) {
foreach (ServerPlayer player in players.Values) {
if (player.state.pid != playerLeft.state.pid) {
SendPacket(new PlayerLeftGamePacket { pid = playerLeft.state.pid }, player.peer, DeliveryMethod.ReliableOrdered);
}
}
players.Remove((uint)peer.Id);
}
}
}
前進
それはあなたがlitenetlibとマルチプレイヤーゲームを開発を開始するために必要なすべてについてです.このガイドでは、不正行為やクライアント側の予測を防ぐために権威のあるサーバーを作成するなどのより高度なトピックをカバーしていませんが、これらのコードはここでコードが提供する上に実装することができます.
より複雑な機能の例の実装はNetGameExample , それは私がlitenetlibを学んで、これの全てを書くのに用いられた資源のうちの1つであったので、私もあなたを読むことを奨励します.
litenetlibのソースコードにはほとんどの機能に対するdocコメントがありますので、少なくとも見てみてください.ここではカバーされていないライブラリの多くの機能と機能があります.
うまくいけば、この記事はあなたに役に立ちました.読書ありがとう!
Reference
この問題について(litenetlibで始まる), 我々は、より多くの情報をここで見つけました
https://dev.to/deagahelio/getting-started-with-litenetlib-2fok
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
Reference
この問題について(litenetlibで始まる), 我々は、より多くの情報をここで見つけました https://dev.to/deagahelio/getting-started-with-litenetlib-2fokテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol