using System; using System.Collections; using System.Collections.Generic; using Fantasy; using Fantasy.Async; using Fantasy.Entitas; using Fantasy.Network; using Newtonsoft.Json; using SimpleJSON; using UnityEditor; using UnityEngine; using UnityEngine.UI; public sealed class EntryComponent : Entity { public Entry Entry; } public class Entry : MonoBehaviour { private static cfg.Tables _tables; public static cfg.Tables Tables => _tables; private Scene _scene; private Session _session; public GameObject LoginPanel; public GameObject ChatPanel; public Text MessageText; public InputField PrivateText; public InputField UserName; public InputField SendMessageText; public Button LoginButton; public Button ExitButton; public Button BroadcastButton; public Button ChannelButton; public Button PrivateButton; public Button SendButton1; public Button ChatNodeEventButton; void Start() { LoginPanel.SetActive(true); ChatPanel.SetActive(false); StartAsync().Coroutine(); LoginButton.onClick.RemoveAllListeners(); LoginButton.onClick.AddListener(() => { OnLoginButtonClick().Coroutine(); }); ExitButton.onClick.RemoveAllListeners(); ExitButton.onClick.AddListener(() => { OnExitButtonClick().Coroutine(); }); SendButton1.onClick.RemoveAllListeners(); SendButton1.onClick.AddListener(() => { OnSendButton1Click().Coroutine(); }); BroadcastButton.onClick.RemoveAllListeners(); BroadcastButton.onClick.AddListener(() => { OnBroadcastButtonClick().Coroutine(); }); ChannelButton.onClick.RemoveAllListeners(); ChannelButton.onClick.AddListener(() => { OnChannelButtonClick().Coroutine(); }); PrivateButton.onClick.RemoveAllListeners(); PrivateButton.onClick.AddListener(() => { OnPrivateButtonClick().Coroutine(); }); } private async FTask StartAsync() { LoadData(); // 1. 初始化 Fantasy 框架 await Fantasy.Platform.Unity.Entry.Initialize(); // 2. 创建一个 Scene (客户端场景) // Scene 是 Fantasy 框架的核心容器,所有功能都在 Scene 下运行 // SceneRuntimeMode.MainThread 表示在 Unity 主线程运行 _scene = await Scene.Create(SceneRuntimeMode.MainThread); _scene.AddComponent().Entry = this; } private async FTask OnLoginButtonClick() { // 创建一个连接,这里是连接到目标的Gate服务器 _session = _scene.Connect( "127.0.0.1:20001", NetworkProtocolType.KCP, () => { Log.Debug("连接成功!"); _session.AddComponent().Start(2000); }, () => { Log.Debug("连接失败!"); }, () => { Log.Debug("断开连接!"); }, false, 5000); // 发送登录的请求给服务器 var response = (A2C_LoginResponse)await _session.Call(new C2A_LoginRequest() { Username = UserName.text, Password = UserName.text, LoginType = 1 }); if (response.ErrorCode != 0) { Log.Error($"登录发生错误{response.ErrorCode}"); return; } if (!ParseJWT(response.ToKen, out var payload)) { return; } _session = _scene.Connect( payload.Address, NetworkProtocolType.KCP, () => { Log.Debug("连接成功!"); _session.AddComponent().Start(2000); }, () => { Log.Debug("连接失败!"); }, () => { Log.Debug("断开连接!"); }, false, 5000); // 发送登录请求到Gate服务器 var loginResponse = (G2C_LoginResponse)await _session.Call(new C2G_LoginRequest() { ToKen = response.ToKen }); if (loginResponse.ErrorCode != 0) { Log.Error($"登录发生错误{loginResponse.ErrorCode}"); return; } LoginPanel.SetActive(false); ChatPanel.SetActive(true); Log.Debug("登录成功!"); } private async FTask OnExitButtonClick() { // var response = (G2C_ExitResponse)await _session.Call(new C2G_ExitRequest()); // if (response.ErrorCode != 0) // { // Log.Error($"退出游戏错误 ErrorCode:{response.ErrorCode}"); // return; // } // LoginPanel.SetActive(true); // ChatPanel.SetActive(false); // Log.Debug("退出游戏成功!"); } private async FTask OnSendButton1Click() { await FTask.CompletedTask; // SendButton1.interactable = false; // var response = (Chat2C_SendMessageResponse) await _session.Call(new C2Chat_SendMessageRequest()); // if (response.ErrorCode != 0) // { // Log.Error($"发送聊天消息失败 ErrorCode:{response.ErrorCode}"); // return; // } // Log.Debug("发送聊天消息成功!"); // SendButton1.interactable = true; } private async FTask OnBroadcastButtonClick() { BroadcastButton.interactable = false; var tree = ChatTreeFactory.Broadcast(_scene); tree = tree.AddendPositionNode(SendMessageText.text, "勇者大陆", 121, 131, 111); var response = (Chat2C_SendMessageResponse)await _session.Call(new C2Chat_SendMessageRequest() { ChatInfoTree = tree }); if (response.ErrorCode != 0) { Log.Error($"发送聊天消息失败 ErrorCode:{response.ErrorCode}"); } BroadcastButton.interactable = true; } private async FTask OnChannelButtonClick() { ChannelButton.interactable = false; var tree = ChatTreeFactory.Team(_scene); tree.ChatChannelId = 1; // tree = tree.AddendTextNode("你好,欢迎来到Fantasy Chat!").AddendLinkNode("点击这里http://www.fantasy.com.cn"); var response = (Chat2C_SendMessageResponse)await _session.Call(new C2Chat_SendMessageRequest() { ChatInfoTree = tree }); if (response.ErrorCode != 0) { Log.Error($"发送频道聊天消息失败 ErrorCode:{response.ErrorCode}"); } ChannelButton.interactable = true; } private async FTask OnPrivateButtonClick() { PrivateButton.interactable = false; var tree = ChatTreeFactory.Private(_scene); tree.Target.Add(Convert.ToInt64(PrivateText.text)); // tree = tree.AddendTextNode("你好,欢迎来到Fantasy Chat!").AddendLinkNode("点击这里http://www.fantasy.com.cn"); var response = (Chat2C_SendMessageResponse)await _session.Call(new C2Chat_SendMessageRequest() { ChatInfoTree = tree }); if (response.ErrorCode != 0) { Log.Error($"发送私聊消息失败 ErrorCode:{response.ErrorCode}"); } PrivateButton.interactable = true; } #region Config private void LoadData() { // 一行代码可以加载所有配置。 cfg.Tables 包含所有表的一个实例字段。 _tables = new cfg.Tables(LoadJson); } private static JSONNode LoadJson(string file) { //var json = File.ReadAllText($"{your_json_dir}/{file}.json", System.Text.Encoding.UTF8); //Assets/ResRaw/config/tb/tbbait.json var jsonAsset = AssetDatabase.LoadAssetAtPath($"Assets/ResRaw/config/tb/{file}.json"); return JSON.Parse(jsonAsset.text); } #endregion #region JWT public bool ParseJWT(string toKen, out Payload payloadData) { payloadData = null; try { // JWT通常是由三个部分组成的,Header,Payload,Signature。 var tokens = toKen.Split('.'); if (tokens.Length != 3) { Log.Error("Invalid JWT token"); return false; } // JWT的Payload不是标准的Base64格式,因为咱们C#的Convert要求是一个标准的Base64格式。 // JWT的Payload是Base64URL格式,它里面的"-"替代成了"+","_"替代成了"/",需要把这些给还原成Base64格式 var basePayload = tokens[1].Replace('-', '+').Replace('_', '/'); // 因为Base64的编码长度需要是4的倍数,如果不是的话咱们需要把这个长度用=来填充,使其长度符合要求 switch (basePayload.Length % 4) { // case 0: // { // // 如果这个余数是0,那就表示长度已经是4的倍数,那就没必要处理了。 // break; // } // case 1: // { // // 如果这个余数是1,那就表示这个Base64格式是不正确的,不是咱们发放的token。 // // 这样情况下,也不需要处理了。 // break; // } case 2: { basePayload += "=="; break; } case 3: { basePayload += "="; break; } } // 将修复后的字符串解码为数组 var basePayloadBytes = Convert.FromBase64String(basePayload); var payload = System.Text.Encoding.UTF8.GetString(basePayloadBytes); payloadData = JsonConvert.DeserializeObject(payload); //JsonHelper.Deserialize(payload); return true; } catch (Exception e) { Log.Error(e); return false; } } #endregion } public sealed class Payload { public long aId { get; set; } public string Address { get; set; } public uint SceneId { get; set; } public int exp { get; set; } public string iss { get; set; } public string aud { get; set; } }