diff --git a/Assets/Scripts/Studio/Connect.meta b/Assets/Scripts/Studio/Connect.meta new file mode 100644 index 00000000..277985c7 --- /dev/null +++ b/Assets/Scripts/Studio/Connect.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 90095164b80e1ac428e5e93715a05704 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Studio/Connect/AuthEntity.cs b/Assets/Scripts/Studio/Connect/AuthEntity.cs new file mode 100644 index 00000000..3154e601 --- /dev/null +++ b/Assets/Scripts/Studio/Connect/AuthEntity.cs @@ -0,0 +1,36 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using System; +using UnityEngine; + +namespace Studio.Auth +{ + public enum AuthEntitiState + { + Login, Logout, Loading, Error + } +#nullable enable + + public class AuthEntity + { + public AuthEntitiState State { get; set; } + public string? accessToken { get; set; } + + public string? message { get; set; } + + public AuthEntity Copy() + { + return new AuthEntity() + { + State = this.State, + accessToken = this.accessToken, + message = this.message + }; + } + + public override string ToString() + { + return $"AuthEntity State:{State}, accessToken:{accessToken} message:{message}"; + } + } +} diff --git a/Assets/Scripts/Studio/Connect/AuthEntity.cs.meta b/Assets/Scripts/Studio/Connect/AuthEntity.cs.meta new file mode 100644 index 00000000..d09ac4eb --- /dev/null +++ b/Assets/Scripts/Studio/Connect/AuthEntity.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 26179ff2e39783a4a80005112ece2abf \ No newline at end of file diff --git a/Assets/Scripts/Studio/Connect/AuthRepository.cs b/Assets/Scripts/Studio/Connect/AuthRepository.cs new file mode 100644 index 00000000..bbdde9a5 --- /dev/null +++ b/Assets/Scripts/Studio/Connect/AuthRepository.cs @@ -0,0 +1,60 @@ +using Best.HTTP; +using Studio.Conifg; +using Studio.Setting.Connect; +using System.Collections.Generic; +using System.Security.Policy; +using System; +using System.Threading.Tasks; +using UnityEngine; + +namespace Studio.Auth +{ +#nullable enable + + public class AuthRepository + { + internal async Task Login(string email, string password) + { + return await LoginRemote(email, password); + } + + private async Task LoginRemote(string email, string password) + { + try + { + var response = await RestAPI.RequestPost>("/api/auth/token", + new Dictionary + { + ["email"] = email, + ["password"] = password, + }, false); + + if (response.code == "SUCCESS") + return new AuthEntity() { State = AuthEntitiState.Login, accessToken = response.data.accessToken }; + else + return new AuthEntity() { State = AuthEntitiState.Error, message = response.message }; + } + catch (AsyncHTTPException e) + { + Debug.LogError($"LoginRemote {e}"); + string message = e.ToString(); + try + { + ResponseModel response = JsonUtility.FromJson>(e.Content); + message = response.message; + } + catch (Exception ex) + { + Debug.LogWarning($"LoginRemote {ex}"); + } + ; + return new AuthEntity() { State = AuthEntitiState.Error, message = message }; + } + catch (Exception e) + { + Debug.LogError($"LoginRemote {e}"); + return new AuthEntity() { State = AuthEntitiState.Error, message = e.Message }; + } + } + } +} diff --git a/Assets/Scripts/Studio/Connect/AuthRepository.cs.meta b/Assets/Scripts/Studio/Connect/AuthRepository.cs.meta new file mode 100644 index 00000000..fc376cb1 --- /dev/null +++ b/Assets/Scripts/Studio/Connect/AuthRepository.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3c37a4285d6a9374c9a22f9d132460e7 \ No newline at end of file diff --git a/Assets/Scripts/Studio/Connect/AuthService.cs b/Assets/Scripts/Studio/Connect/AuthService.cs new file mode 100644 index 00000000..550a19ef --- /dev/null +++ b/Assets/Scripts/Studio/Connect/AuthService.cs @@ -0,0 +1,79 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using System; +using System.Threading.Tasks; +using UnityEngine; + +namespace Studio.Auth +{ + public class AuthService + { + #region Singleton + private static readonly AuthService instance = new AuthService(new AuthRepository()); + public static AuthService Instance => instance; + static AuthService() { } + #endregion + + public EventHandler OnChanged { get; set; } + + public AuthEntity Entiti { get => entiti; } + private AuthEntity entiti; + + private readonly AuthRepository _repository; + + private AuthService(AuthRepository repository) + { + entiti = new AuthEntity() + { + State = AuthEntitiState.Logout, + }; + _repository = repository; + } + + public async Task Login(string email, string password) + { + // Simulate login + if (email.Length > 0 && password.Length > 0) + { + entiti = new AuthEntity() + { + State = AuthEntitiState.Loading, + }; + //OnChanged?.Invoke(this, entiti.Copy()); + entiti = await _repository.Login(email, password); + //OnChanged?.Invoke(this, entiti.Copy()); + } + } + public void Logout() + { + entiti = new AuthEntity() + { + State = AuthEntitiState.Logout, + }; + OnChanged?.Invoke(this, entiti.Copy()); + } + + + public void LoginTest() + { + entiti = new AuthEntity() + { + State = AuthEntitiState.Login, + accessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiZW1haWwiOiJzeXN0ZW0iLCJncm91cElkIjoxLCJncm91cENvZGUiOm51bGwsIm5hbWUiOiLsi5zsiqTthZwg6rSA66as7J6QIiwibGljZW5zZSI6InByb3ZpZGVyIiwicGhvbmUiOiIwMTAtMDAwMC0wMDAwIiwibWVudUF1dGgiOiJTeXN0ZW1BZG1pbiIsImlhdCI6MTc0NTIwMDg5MH0.voyvIbZH7OXBdfIy1gug53Z0LLK3AwJR5OBE_X1w4Bg", + }; + } + + /// + /// 바뀐 로그인 프로세스. Unity 실행 시 token을 전달 받아 호출 + /// + /// + public void LoginDirect(string token) + { + entiti = new AuthEntity() + { + State = AuthEntitiState.Login, + accessToken = token + }; + } + } +} diff --git a/Assets/Scripts/Studio/Connect/AuthService.cs.meta b/Assets/Scripts/Studio/Connect/AuthService.cs.meta new file mode 100644 index 00000000..da50ce52 --- /dev/null +++ b/Assets/Scripts/Studio/Connect/AuthService.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0feb2cf99fab3204c9ba243a03a3afe3 \ No newline at end of file diff --git a/Assets/Scripts/Studio/Connect/ConnectedEntity.cs b/Assets/Scripts/Studio/Connect/ConnectedEntity.cs new file mode 100644 index 00000000..23c98dd7 --- /dev/null +++ b/Assets/Scripts/Studio/Connect/ConnectedEntity.cs @@ -0,0 +1,34 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using System.Collections.Generic; +using UnityEngine; + +namespace Studio +{ + public class ConnectedEntity + { + [JsonProperty("APIs")] + public List apiS { get; set; } + [JsonProperty("MQTTs")] + public List mqtts { get; set; } + } + + public class APIEntity + { + [JsonProperty("APIDomain")] + public string APIDomain { get; set; } + [JsonProperty("Port")] + public int APIPort { get; set; } + [JsonProperty("URL")] + public List url { get; set; } + } + public class MQTTEntity + { + [JsonProperty("MQTTDomain")] + public string MQTTDomain { get; set; } + [JsonProperty("Port")] + public int MQTTPort { get; set; } + [JsonProperty("Topics")] + public List topics { get; set; } + } +} diff --git a/Assets/Scripts/Studio/Connect/ConnectedEntity.cs.meta b/Assets/Scripts/Studio/Connect/ConnectedEntity.cs.meta new file mode 100644 index 00000000..daa4072a --- /dev/null +++ b/Assets/Scripts/Studio/Connect/ConnectedEntity.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: fbcc575483b274d4382f6a2b8d380d11 \ No newline at end of file diff --git a/Assets/Scripts/Studio/Connect/Constants.cs b/Assets/Scripts/Studio/Connect/Constants.cs new file mode 100644 index 00000000..f9dc0086 --- /dev/null +++ b/Assets/Scripts/Studio/Connect/Constants.cs @@ -0,0 +1,15 @@ +using UnityEngine; + +namespace Studio.Conifg +{ + public class Constants + { + public static string APIDomain; + + public static int APIPort; + + public static string MQTTDomain; + + public static int MQTTPort; + } +} diff --git a/Assets/Scripts/Studio/Connect/Constants.cs.meta b/Assets/Scripts/Studio/Connect/Constants.cs.meta new file mode 100644 index 00000000..0f8a6e61 --- /dev/null +++ b/Assets/Scripts/Studio/Connect/Constants.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: dcc5387e9d1cf9943b3e1d36a9b2bf17 \ No newline at end of file diff --git a/Assets/Scripts/Studio/Connect/IStudioEntity.cs b/Assets/Scripts/Studio/Connect/IStudioEntity.cs new file mode 100644 index 00000000..f22d9dc1 --- /dev/null +++ b/Assets/Scripts/Studio/Connect/IStudioEntity.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Studio +{ + public interface IStudioEntity + { + public string Id { get; } + } +} diff --git a/Assets/Scripts/Studio/Connect/IStudioEntity.cs.meta b/Assets/Scripts/Studio/Connect/IStudioEntity.cs.meta new file mode 100644 index 00000000..f03cc6da --- /dev/null +++ b/Assets/Scripts/Studio/Connect/IStudioEntity.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4661549154da4794f8e145234f7f4566 \ No newline at end of file diff --git a/Assets/Scripts/Studio/Connect/ResponseModel.cs b/Assets/Scripts/Studio/Connect/ResponseModel.cs new file mode 100644 index 00000000..a1a7dd76 --- /dev/null +++ b/Assets/Scripts/Studio/Connect/ResponseModel.cs @@ -0,0 +1,57 @@ +using UnityEngine; + +namespace Studio.Setting.Connect +{ + /// + /// API Domain,URL, Port Setting + /// + /// + public enum APIState + { + Loaded, Loading, Error + } + + [System.Serializable] + public class ResponseModel + { + + public int status; + public string code; + public string message; + public T data; + + public override string ToString() + { + return $"ResponseModel status:{status}, code:{code} message:{message} data:{data}"; + } + } + + /// + /// Data로 한번 감싸진 response때문에 + /// + /// + [System.Serializable] + public class ResponseDataModel + { + public T data; + } + + public class EntityWithState + { + private APIState state; + public APIState State { get => state; } + + private T entity; + public T Entity { get => entity; } + + private string? message; + public string? Message { get => message; } + + public EntityWithState(APIState state, T entity, string? message = null) + { + this.state = state; + this.entity = entity; + this.message = message; + } + } +} diff --git a/Assets/Scripts/Studio/Connect/ResponseModel.cs.meta b/Assets/Scripts/Studio/Connect/ResponseModel.cs.meta new file mode 100644 index 00000000..ca46c735 --- /dev/null +++ b/Assets/Scripts/Studio/Connect/ResponseModel.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ac67491ed4be8f44c820f2d1ac95b730 \ No newline at end of file diff --git a/Assets/Scripts/Studio/Connect/RestAPI.cs b/Assets/Scripts/Studio/Connect/RestAPI.cs new file mode 100644 index 00000000..7b872808 --- /dev/null +++ b/Assets/Scripts/Studio/Connect/RestAPI.cs @@ -0,0 +1,81 @@ +using Best.HTTP; +using Studio.Auth; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace Studio.Conifg +{ + public class RestAPI + { + public static async Task RequestPost(string url, Dictionary body = null, bool useAuth = true) + { + return await Request(url, body, HTTPMethods.Post, useAuth); + } + public static async Task RequestGet(string url, Dictionary body = null, bool useAuth = true) + { + return await Request(url, body, HTTPMethods.Get, useAuth); + } + private static async Task Request(string url, object body = null, HTTPMethods method = HTTPMethods.Post, bool useAuth = true) + { + string bodyString = ""; + if (body != null) + { + if (body is string) + { + bodyString = body.ToString(); + } + else if (body is Dictionary) + { + bodyString = JsonUtility.ToJson(body); + } + } + url = $"http://{Constants.APIDomain}:{Constants.APIPort}{url}"; + + var request = SelectHTTPRequest(method, url); + + request.DownloadSettings = new Best.HTTP.Request.Settings.DownloadSettings() { ContentStreamMaxBuffered = 1024 * 1024 * 200 }; + request.MethodType = method; + request.SetHeader("Content-Type", "application/json; charset=utf-8"); + + if (useAuth) request.SetHeader("access-token", AuthService.Instance.Entiti.accessToken); + if (body != null) + { + if (body is string) + { + request.UploadSettings.UploadStream = + new MemoryStream(Encoding.UTF8.GetBytes(body as string)); + } + else if (body is Dictionary) + { + request.UploadSettings.UploadStream = + new MemoryStream(Encoding.UTF8.GetBytes(JsonUtility.ToJson(body))); + } + } + + var response = await request.GetAsStringAsync(); + return JsonUtility.FromJson(response); + } + + + private static HTTPRequest SelectHTTPRequest(HTTPMethods methods, string url, OnRequestFinishedDelegate onRequest = null) + { + switch (methods) + { + case HTTPMethods.Get: + + if (onRequest != null) + return HTTPRequest.CreateGet(url, onRequest); + else + return HTTPRequest.CreateGet(url); + case HTTPMethods.Post: + return HTTPRequest.CreatePost(url); + } + + return null; + } + } +} diff --git a/Assets/Scripts/Studio/Connect/RestAPI.cs.meta b/Assets/Scripts/Studio/Connect/RestAPI.cs.meta new file mode 100644 index 00000000..2404c52b --- /dev/null +++ b/Assets/Scripts/Studio/Connect/RestAPI.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: be7e1446b881dce40aa1890c7219dc01 \ No newline at end of file diff --git a/Assets/Scripts/Studio/Connect/StudioEntityWithState.cs b/Assets/Scripts/Studio/Connect/StudioEntityWithState.cs new file mode 100644 index 00000000..2fcd22a0 --- /dev/null +++ b/Assets/Scripts/Studio/Connect/StudioEntityWithState.cs @@ -0,0 +1,27 @@ +using System; +using UnityEngine; +#nullable enable +namespace Studio.Setting.Connect +{ + /// + /// MQTT Connect Setting + /// + public class StudioEntityWithState + { + private APIState state; + public APIState State { get => state; } + + private string? entity; + public string Entity { get => entity; } + + private string? message; + public string? Message { get => message; } + + public StudioEntityWithState(APIState state, string entity , string? message = null) + { + this.state = state; + this.entity = entity; + this.message = message; + } + } +} diff --git a/Assets/Scripts/Studio/Connect/StudioEntityWithState.cs.meta b/Assets/Scripts/Studio/Connect/StudioEntityWithState.cs.meta new file mode 100644 index 00000000..4a12d2de --- /dev/null +++ b/Assets/Scripts/Studio/Connect/StudioEntityWithState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ceeafcd53e4a2504f98183ba2fc8ba40 \ No newline at end of file diff --git a/Assets/Scripts/Studio/Connect/StudioRepoistory.cs b/Assets/Scripts/Studio/Connect/StudioRepoistory.cs new file mode 100644 index 00000000..bcacc5aa --- /dev/null +++ b/Assets/Scripts/Studio/Connect/StudioRepoistory.cs @@ -0,0 +1,183 @@ +using Best.HTTP.JSON.LitJson; +using Best.MQTT; +using Best.MQTT.Packets.Builders; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using RTG; +using Studio.Conifg; +using Studio.Setting.Connect; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using UnityEngine; + +namespace Studio +{ + public class StudioRepoistory + { + private MQTTClient client; + public Action>> OnTopicList; + public StudioRepoistory() + { + MQTTCreateConnect(); + Application.quitting += OnDestroy; + } + + public void MQTTCreateConnect() + { + Debug.Log($"MQTT Domain{Constants.MQTTDomain} , MQTTPORT{Constants.MQTTPort}"); + var options = new ConnectionOptionsBuilder() + .WithTCP(Constants.MQTTDomain, Constants.MQTTPort) + .Build(); + + if (client != null) + { + client.OnConnected -= OnConnectedMQTT; + client.OnStateChanged -= OnStateChangedMQTT; + client.OnDisconnect -= OnDisconnectedMQTT; + client.OnError -= OnErrorMQTT; + } + client = new MQTTClient(options); + client.OnConnected += OnConnectedMQTT; + client.OnStateChanged += OnStateChangedMQTT; + client.OnDisconnect += OnDisconnectedMQTT; + client.OnError += OnErrorMQTT; + } + private ConnectPacketBuilder ConnectPacketBuilderCallback(MQTTClient client, ConnectPacketBuilder builder) + { + return builder.WithKeepAlive(60 * 60);//keep alive 1시간으로 + } + + public async Task> BaseInfo(string url) + { + try + { + return await Task.Run>(async () => + { + ResponseModel response = await RestAPI.RequestPost>(url); + if (response.code == "SUCCESS") + return new StudioEntityWithState(APIState.Loaded, response.data); + + return new StudioEntityWithState(APIState.Error, null, response.message); + }); + } + catch (Exception e) + { + return new StudioEntityWithState(APIState.Error, null, e.Message); + } + } + + public void MQTTConnect() + { + client.BeginConnect(ConnectPacketBuilderCallback); + } + + private void OnErrorMQTT(MQTTClient client, string error) + { + Debug.Log($"OnError reason: '{error}'"); + } + + private void OnDisconnectedMQTT(MQTTClient client, DisconnectReasonCodes reasonCode, string reasonMessage) + { + if (Application.isPlaying) + { + + } + } + + private void OnStateChangedMQTT(MQTTClient client, ClientStates oldState, ClientStates newState) + { + Debug.Log($"{oldState} => {newState}"); + } + + + private void OnConnectedMQTT(MQTTClient client) + { + Debug.Log($"MQTT OnConnected"); + } + + /// + /// 구독 + /// + /// + public void Subscribe(string topic) + { + client.CreateBulkSubscriptionBuilder() + .WithTopic(new SubscribeTopicBuilder(topic).WithMessageCallback(OnTopic)) + .BeginSubscribe(); + } + + /// + /// 구독 취소 + /// + /// + public void UnSubscibe(string topic) + { + client.CreateUnsubscribePacketBuilder(topic) + .WithAcknowledgementCallback((client, topicFilter, reasonCode) => Debug.Log($"Unsubscribe request to topic filter '{topicFilter}' returned with code: {reasonCode}")) + .BeginUnsubscribe(); + } + + private void OnTopic(MQTTClient client, SubscriptionTopic topic, string topicName, ApplicationMessage message) + { + string payload = Encoding.UTF8.GetString(message.Payload.Data, message.Payload.Offset, message.Payload.Count); + + var type = topic.Filter.ToString(); + JObject json = JObject.Parse(payload); + foreach(JProperty prop in json.Children()) + { + string key = prop.Name.ToString(); + string value = prop.Value.ToString(); + Debug.Log($"kEY : {key}, Value:{value}"); + } + + var T = json["data"]; + var split = T.ToString().Split('['); + var t = $"[{split[1]}"; + Debug.Log(T.ToString()); + JArray jarray = JArray.Parse(t); + var list = new Dictionary>(); + foreach (JObject obj in jarray.Children()) + { + Dictionary keyvalue = new(); + int index = 0; + string id = string.Empty; + foreach (JProperty prop in obj.Children()) + { + string key = prop.Name.ToString(); + string value = prop.Value.ToString(); + if (index == 0) + { + list.Add(value, new()); + id = value; + } + keyvalue.Add(key, value); + // Debug.Log($"kEY : {key}, Value:{value}"); + } + list[id] = keyvalue; + } + //JObject json2 = JObject.Parse(jarray.ToString()); + CheckUpdate(type, list); + //var json = JsonConvert.SerializeObject(t.ToString()); + } + + private void CheckUpdate(string type, Dictionary> data) + { + if (data == null) + return; + if(data.Count > 0) + OnTopicList?.Invoke(type, data); + } + + private void OnDestroy() + { + client?.CreateDisconnectPacketBuilder() + .WithReasonCode(DisconnectReasonCodes.NormalDisconnection) + .WithReasonString($"{Constants.MQTTDomain} Disconnecting") + .BeginDisconnect(); + } + } +} diff --git a/Assets/Scripts/Studio/Connect/StudioRepoistory.cs.meta b/Assets/Scripts/Studio/Connect/StudioRepoistory.cs.meta new file mode 100644 index 00000000..229ef151 --- /dev/null +++ b/Assets/Scripts/Studio/Connect/StudioRepoistory.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 08b6f925bff664748b4509de6c5f2e66 \ No newline at end of file diff --git a/Assets/Scripts/Studio/Connect/StudioService.cs b/Assets/Scripts/Studio/Connect/StudioService.cs new file mode 100644 index 00000000..b6d95f95 --- /dev/null +++ b/Assets/Scripts/Studio/Connect/StudioService.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; +using System; +using UnityEngine; +using Studio; +using System.Threading.Tasks; +using Studio.Setting.Connect; + +namespace Studio +{ + public class StudioServiceTypeEventArgs : EventArgs + { + public readonly string Type; + List Entitis; + + public StudioServiceTypeEventArgs(string type, List entity) + { + Type = type; + Entitis = entity; + } + } + + public class StudioService + { + #region Singleton + private static readonly StudioService instance = new StudioService(new StudioRepoistory()); + public static StudioService Instance => instance; + static StudioService() { } + #endregion + + private Dictionary> listenerTypeMap; + private StudioRepoistory repository; + private StudioService(StudioRepoistory repository) + { + this.repository = repository; + listenerTypeMap = new Dictionary>(); + + repository.OnTopicList += OnTopicList; + // repository.OnDetectedError += OnDetectedError; + } + + private void OnTopicList(string type, Dictionary> entities) + { + foreach(var idKey in entities) + { + UpdateTopicData(type, idKey.Value); + } + } + + private void UpdateTopicData(string type, Dictionary entity) + { + + } + + public void AddTypeListener(string type, EventHandler listener) + { + if (!listenerTypeMap.ContainsKey(type)) + { + listenerTypeMap[type] = listener; + } + else + { + listenerTypeMap[type] += listener; + } + } + + public async Task LoadBaeData(string url) + { + StudioEntityWithState data = await repository.BaseInfo(url); + if(data.State.Equals(APIState.Loaded)) + { + //Debug.Log("Test"); + //UpdateEntity(data.Entity); + } + else if(data.State == APIState.Error) + { + //TODO 에러메세지 + Debug.Log($"APIState : Error , Message :{data.Message}"); + } + } + + public void UpdateEntity(IStudioEntity data) + { + //DispatchMachineEvent() + } + + private void DispatchMachineEvent(string type, List entity) + { + if (listenerTypeMap.ContainsKey(type)) + { + listenerTypeMap[type].Invoke(this, new StudioServiceTypeEventArgs(type, entity)); + } + } + } +} diff --git a/Assets/Scripts/Studio/Connect/StudioService.cs.meta b/Assets/Scripts/Studio/Connect/StudioService.cs.meta new file mode 100644 index 00000000..443c766d --- /dev/null +++ b/Assets/Scripts/Studio/Connect/StudioService.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 12899bf44d0ad334fac9db78807dca00 \ No newline at end of file