Add networked Fighter3D minigame

This commit is contained in:
ud18010
2026-08-10 16:47:16 +08:00
parent bff617091d
commit ef7f24738e
53 changed files with 5458 additions and 19 deletions
@@ -14,6 +14,7 @@ namespace XGame.ActorBehavior
public static string GetControllerPath(string catalogType)
{
return string.Equals(catalogType, LobbyActorBehaviorRuntime.DefaultCatalogType, StringComparison.OrdinalIgnoreCase)
|| string.Equals(catalogType, "fighter3d", StringComparison.OrdinalIgnoreCase)
? "Assets/Game/Art/Actor/Controller/fighter.controller"
: null;
}
@@ -16,18 +16,23 @@ namespace XGame.ActorBehavior
public const string ConfigRoot = "Assets/Game/Config/ActorBehavior";
public static IEnumerator LoadCatalog(string type, Action<BehaviorConfigCatalog> onLoaded)
{
yield return LoadCatalogFromRoot(type, ConfigRoot, onLoaded);
}
public static IEnumerator LoadCatalogFromRoot(string type, string configRoot, Action<BehaviorConfigCatalog> onLoaded)
{
string skillJson = null;
string flyObjectJson = null;
string buffJson = null;
yield return LoadText(GetSkillPath(type), text => skillJson = text);
yield return LoadText(GetFlyObjectPath(type), text => flyObjectJson = text);
yield return LoadText(GetBuffPath(type), text => buffJson = text);
yield return LoadText(GetSkillPath(type, configRoot), text => skillJson = text);
yield return LoadText(GetFlyObjectPath(type, configRoot), text => flyObjectJson = text);
yield return LoadText(GetBuffPath(type, configRoot), text => buffJson = text);
if (string.IsNullOrEmpty(skillJson) || string.IsNullOrEmpty(flyObjectJson) || string.IsNullOrEmpty(buffJson))
{
Debug.LogError("[ActorBehaviorConfigLoader] missing behavior config for type=" + type);
Debug.LogError("[ActorBehaviorConfigLoader] missing behavior config for type=" + type + ", root=" + configRoot);
onLoaded?.Invoke(null);
yield break;
}
@@ -48,16 +53,38 @@ namespace XGame.ActorBehavior
return ConfigRoot + "/" + type + "_skill.json";
}
public static string GetSkillPath(string type, string configRoot)
{
return NormalizeRoot(configRoot) + "/" + type + "_skill.json";
}
public static string GetFlyObjectPath(string type)
{
return ConfigRoot + "/" + type + "_flyobj.json";
}
public static string GetFlyObjectPath(string type, string configRoot)
{
return NormalizeRoot(configRoot) + "/" + type + "_flyobj.json";
}
public static string GetBuffPath(string type)
{
return ConfigRoot + "/" + type + "_buff.json";
}
public static string GetBuffPath(string type, string configRoot)
{
return NormalizeRoot(configRoot) + "/" + type + "_buff.json";
}
private static string NormalizeRoot(string configRoot)
{
return string.IsNullOrEmpty(configRoot)
? ConfigRoot
: configRoot.TrimEnd('/', '\\');
}
private static IEnumerator LoadText(string assetPath, Action<string> onLoaded)
{
#if UNITY_EDITOR
@@ -26,6 +26,7 @@ namespace XGame
private const string LoginPlayerIdKey = "LoginPlayerId";
private const string LocalClientIdKey = "LocalClientId";
private const double TokenValidSeconds = 7 * 24 * 60 * 60;
private const float LobbyGameListRefreshIntervalSeconds = 3f;
public static CSharpClientApp Instance { get; private set; }
@@ -55,6 +56,7 @@ namespace XGame
private string matchMessage = string.Empty;
private string pendingGameId = string.Empty;
private int pendingGameVersion;
private float nextGameListRefreshTime;
private float nextLobbyMoveFrameLogTime;
private int m_pid;//当前已鉴权的玩家 id(来自登录/注册返回的 token)
@@ -113,6 +115,11 @@ namespace XGame
if (lobbyActive)
{
lobbyNet?.Pump();
float now = Time.unscaledTime;
if (ShouldPollGameList(lobbyActive, lobbyNet != null && lobbyNet.IsConnected, matchRequestActive, now, nextGameListRefreshTime))
{
RequestGameList();
}
}
}
@@ -435,9 +442,13 @@ namespace XGame
RefreshLobby();
return;
}
nextGameListRefreshTime = Time.unscaledTime + LobbyGameListRefreshIntervalSeconds;
lobbyNet.SendFramework(FrameworkOpcode.GameListRequest, Array.Empty<byte>());
}
private static bool ShouldPollGameList(bool isLobbyActive, bool hasConnectedLobbyNet, bool isMatchRequestActive, float now, float nextPollTime)
=> isLobbyActive && hasConnectedLobbyNet && !isMatchRequestActive && now >= nextPollTime;
private void RefreshLobby()
{
if (lobbyController == null)
@@ -0,0 +1,625 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using XGame.ActorBehavior;
using XWorld.Framework.ActorBehavior;
using UObject = UnityEngine.Object;
namespace XGame.MiniGame
{
public struct MiniGameActorFrame
{
public int ActorId;
public int Figure;
public int TeamId;
public Vector3 Position;
public Vector3 Forward;
public bool Moving;
public bool Alive;
public string TriggerKey;
public string BehaviorId;
}
public sealed class MiniGameActorPresentation
{
private sealed class ActorAvatar
{
public int ActorId;
public int Figure;
public int TeamId;
public GameObject Obj;
public Animator Animator;
public Vector3 Target;
public bool Moving;
public bool Alive;
public bool IsPlaceholder;
public bool HasPosition;
public string AnimState;
public string LastTriggerKey;
public Vector3 LastForward = Vector3.forward;
}
private sealed class CoroutineHost : MonoBehaviour
{
}
private const string CharacterConfigResourcePath = "Config/CharacterConfig";
private const string FighterStandState = "140_stand01_qt";
private const string FighterWalkState = "140_walk01_qt";
private const string LegacyStandState = "Stand";
private const string LegacyWalkState = "Walk";
private const float MoveSpeed = 12f;
private const float StopDistance = 0.04f;
private readonly Dictionary<int, ActorAvatar> _avatars = new Dictionary<int, ActorAvatar>();
private readonly Dictionary<int, GameObject> _prefabCache = new Dictionary<int, GameObject>();
private readonly HashSet<int> _prefabLoading = new HashSet<int>();
private readonly string _catalogType;
private readonly string _behaviorConfigRoot;
private readonly GameObject _root;
private readonly Transform _actorRoot;
private readonly CoroutineHost _coroutineHost;
private readonly ActorBehaviorPresentation _behaviorPresentation;
private readonly BehaviorWorld _behaviorWorld;
private CharacterConfigData _characterConfig;
private bool _catalogLoading;
private bool _catalogLoaded;
private bool _disposed;
private MiniGameActorPresentation(string rootName, string catalogType, string behaviorConfigRoot)
{
_catalogType = catalogType;
_behaviorConfigRoot = behaviorConfigRoot;
_root = new GameObject(string.IsNullOrEmpty(rootName) ? "MiniGameActorPresentation" : rootName);
UObject.DontDestroyOnLoad(_root);
_coroutineHost = _root.AddComponent<CoroutineHost>();
_behaviorPresentation = _root.AddComponent<ActorBehaviorPresentation>();
_behaviorWorld = new BehaviorWorld(new BehaviorWorldOptions
{
Mode = BehaviorRunMode.ClientPresentation
});
_actorRoot = new GameObject("Actors").transform;
_actorRoot.SetParent(_root.transform, false);
EnsureScenePresentation();
EnsureCharacterConfig();
EnsureBehaviorCatalog();
}
public static MiniGameActorPresentation Create(string rootName, string catalogType, string behaviorConfigRoot)
{
if (string.IsNullOrEmpty(catalogType))
{
throw new ArgumentException("catalogType is required", nameof(catalogType));
}
return new MiniGameActorPresentation(rootName, catalogType, behaviorConfigRoot);
}
public void RenderActor(MiniGameActorFrame frame)
{
if (_disposed || frame.ActorId <= 0)
{
return;
}
int figure = frame.Figure <= 0 ? 1 : frame.Figure;
ActorAvatar avatar = GetOrCreateAvatar(frame.ActorId, figure);
if (avatar == null || avatar.Obj == null)
{
return;
}
avatar.TeamId = frame.TeamId;
avatar.Alive = frame.Alive;
avatar.Target = frame.Position;
avatar.Moving = frame.Moving;
Vector3 forward = ResolveForward(frame.Forward, avatar.LastForward);
avatar.LastForward = forward;
if (!avatar.HasPosition)
{
avatar.Obj.transform.position = frame.Position;
avatar.Target = frame.Position;
avatar.HasPosition = true;
}
avatar.Obj.transform.forward = forward;
RegisterOrUpdateActor(avatar);
StartBehaviorIfNeeded(avatar, frame.BehaviorId, frame.TriggerKey, forward);
SetMoveState(avatar, IsAvatarMoving(avatar));
}
public void Tick(float deltaTime)
{
if (_disposed)
{
return;
}
float step = Mathf.Max(MoveSpeed * Mathf.Max(deltaTime, 0f), 0.01f);
foreach (ActorAvatar avatar in _avatars.Values)
{
if (avatar == null || avatar.Obj == null)
{
continue;
}
Vector3 before = avatar.Obj.transform.position;
avatar.Obj.transform.position = Vector3.MoveTowards(before, avatar.Target, step);
RegisterOrUpdateActor(avatar);
if (!_behaviorWorld.HasActiveBehavior(avatar.ActorId))
{
SetMoveState(avatar, IsAvatarMoving(avatar));
}
}
_behaviorWorld.Tick(deltaTime);
DrainBehaviorEvents();
}
public void RemoveActor(int actorId)
{
if (!_avatars.TryGetValue(actorId, out ActorAvatar avatar))
{
return;
}
_behaviorPresentation.UnregisterActor(actorId, true);
_behaviorWorld.RemoveActor(actorId);
DestroyUnityObject(avatar.Obj);
_avatars.Remove(actorId);
}
public void Destroy()
{
if (_disposed)
{
return;
}
_disposed = true;
_avatars.Clear();
DestroyUnityObject(_root);
}
private ActorAvatar GetOrCreateAvatar(int actorId, int figure)
{
if (_avatars.TryGetValue(actorId, out ActorAvatar avatar))
{
if (avatar.Figure == figure)
{
return avatar;
}
RemoveActor(actorId);
}
GameObject obj = InstantiateAvatar(figure, out bool isPlaceholder);
avatar = new ActorAvatar
{
ActorId = actorId,
Figure = figure,
TeamId = actorId,
Obj = obj,
Animator = obj != null ? obj.GetComponentInChildren<Animator>() : null,
Target = obj != null ? obj.transform.position : Vector3.zero,
Alive = true,
IsPlaceholder = isPlaceholder
};
if (obj != null)
{
obj.name = "MiniGameActor_" + actorId + "_F" + figure;
_behaviorPresentation.RegisterActor(actorId, avatar.Animator, obj.transform, obj.transform);
ApplyActorBehaviorAnimatorController(avatar);
}
_avatars[actorId] = avatar;
return avatar;
}
private GameObject InstantiateAvatar(int figure, out bool isPlaceholder)
{
GameObject prefab = GetCachedPrefabOrLoadAsync(figure);
isPlaceholder = prefab == null;
GameObject obj = prefab != null
? UObject.Instantiate(prefab, _actorRoot)
: GameObject.CreatePrimitive(PrimitiveType.Capsule);
if (prefab == null)
{
obj.transform.SetParent(_actorRoot, false);
obj.transform.localScale = new Vector3(0.7f, 1f, 0.7f);
}
obj.SetActive(true);
ConfigureAvatarRenderers(obj);
return obj;
}
private GameObject GetCachedPrefabOrLoadAsync(int figure)
{
if (_prefabCache.TryGetValue(figure, out GameObject prefab) && prefab != null)
{
return prefab;
}
if (_prefabLoading.Add(figure))
{
_coroutineHost.StartCoroutine(CoLoadActorPrefab(figure));
}
return null;
}
private IEnumerator CoLoadActorPrefab(int figure)
{
string prefabPath = GetPrefabPath(figure);
if (string.IsNullOrEmpty(prefabPath))
{
_prefabLoading.Remove(figure);
Debug.LogError("[MiniGameActorPresentation] missing prefab path for figure=" + figure);
yield break;
}
UObject loaded = null;
yield return XResLoader.coLoadRes(prefabPath, typeof(GameObject), obj => loaded = obj);
_prefabLoading.Remove(figure);
GameObject prefab = loaded as GameObject;
if (prefab == null)
{
Debug.LogWarning("[MiniGameActorPresentation] actor prefab load failed: " + prefabPath);
yield break;
}
_prefabCache[figure] = prefab;
UpgradePlaceholders(figure, prefab);
}
private void UpgradePlaceholders(int figure, GameObject prefab)
{
foreach (ActorAvatar avatar in _avatars.Values)
{
if (avatar == null || avatar.Figure != figure || !avatar.IsPlaceholder || avatar.Obj == null)
{
continue;
}
Vector3 position = avatar.Obj.transform.position;
Quaternion rotation = avatar.Obj.transform.rotation;
string objectName = avatar.Obj.name;
DestroyUnityObject(avatar.Obj);
avatar.Obj = UObject.Instantiate(prefab, _actorRoot);
avatar.Obj.name = objectName;
avatar.Obj.transform.position = position;
avatar.Obj.transform.rotation = rotation;
avatar.Obj.SetActive(true);
ConfigureAvatarRenderers(avatar.Obj);
avatar.Animator = avatar.Obj.GetComponentInChildren<Animator>();
avatar.IsPlaceholder = false;
avatar.AnimState = null;
_behaviorPresentation.RegisterActor(avatar.ActorId, avatar.Animator, avatar.Obj.transform, avatar.Obj.transform);
ApplyActorBehaviorAnimatorController(avatar);
SetMoveState(avatar, avatar.Moving);
}
}
private void ApplyActorBehaviorAnimatorController(ActorAvatar avatar)
{
if (avatar == null || avatar.Obj == null || avatar.Animator == null)
{
return;
}
_coroutineHost.StartCoroutine(ApplyActorBehaviorAnimatorControllerAsync(
avatar.ActorId,
avatar.Obj,
avatar.Animator));
}
private IEnumerator ApplyActorBehaviorAnimatorControllerAsync(int actorId, GameObject actorObject, Animator animator)
{
bool applied = false;
yield return ActorBehaviorAnimatorControllerLoader.ApplyController(_catalogType, animator, value => applied = value);
if (!applied || actorObject == null || animator == null)
{
yield break;
}
if (!_avatars.TryGetValue(actorId, out ActorAvatar avatar) || avatar.Obj != actorObject || avatar.Animator != animator)
{
yield break;
}
avatar.AnimState = null;
SetMoveState(avatar, avatar.Moving);
}
private void StartBehaviorIfNeeded(ActorAvatar avatar, string behaviorId, string triggerKey, Vector3 forward)
{
if (avatar == null)
{
return;
}
if (string.IsNullOrEmpty(behaviorId))
{
if (!string.IsNullOrEmpty(triggerKey))
{
avatar.LastTriggerKey = triggerKey;
}
return;
}
string key = string.IsNullOrEmpty(triggerKey) ? behaviorId : triggerKey;
if (avatar.LastTriggerKey == key)
{
return;
}
avatar.LastTriggerKey = key;
if (!_catalogLoaded || _behaviorWorld.HasActiveBehavior(avatar.ActorId))
{
return;
}
BehaviorStartResult result = _behaviorWorld.TryStartBehavior(
avatar.ActorId,
behaviorId,
BehaviorInput.FromDirection(forward.x, forward.z));
if (!result.Success)
{
Debug.LogWarning("[MiniGameActorPresentation] start behavior failed actorId=" + avatar.ActorId +
", behaviorId=" + behaviorId + ", error=" + result.Error);
}
}
private void RegisterOrUpdateActor(ActorAvatar avatar)
{
if (avatar == null || avatar.Obj == null)
{
return;
}
_behaviorWorld.AddActor(new BehaviorActorState
{
ActorId = avatar.ActorId,
CatalogType = _catalogType,
TeamId = avatar.TeamId,
Position = ToBehavior(avatar.Obj.transform.position),
Forward = ToBehaviorForward(avatar.Obj.transform.forward),
Radius = 0.5f,
Alive = avatar.Alive
});
}
private void DrainBehaviorEvents()
{
IReadOnlyList<BehaviorEvent> events = _behaviorWorld.DrainEvents();
if (events.Count == 0)
{
return;
}
_behaviorPresentation.PlayEvents(events);
for (int i = 0; i < events.Count; i++)
{
BehaviorEvent evt = events[i];
if (evt.Kind != BehaviorEventKind.BehaviorStopped)
{
continue;
}
if (_avatars.TryGetValue(evt.ActorId, out ActorAvatar avatar))
{
avatar.AnimState = null;
SetMoveState(avatar, avatar.Moving);
}
}
}
private void EnsureBehaviorCatalog()
{
if (_catalogLoaded || _catalogLoading)
{
return;
}
_catalogLoading = true;
_coroutineHost.StartCoroutine(CoLoadBehaviorCatalog());
}
private IEnumerator CoLoadBehaviorCatalog()
{
BehaviorConfigCatalog catalog = null;
yield return ActorBehaviorConfigLoader.LoadCatalogFromRoot(_catalogType, _behaviorConfigRoot, loaded => catalog = loaded);
_catalogLoading = false;
if (catalog == null)
{
yield break;
}
_behaviorWorld.RegisterCatalog(catalog);
_catalogLoaded = true;
}
private bool EnsureCharacterConfig()
{
if (_characterConfig != null)
{
return true;
}
TextAsset asset = Resources.Load<TextAsset>(CharacterConfigResourcePath);
if (asset == null)
{
Debug.LogError("[MiniGameActorPresentation] missing character config resource: " + CharacterConfigResourcePath);
return false;
}
_characterConfig = JsonUtility.FromJson<CharacterConfigData>(asset.text);
if (_characterConfig == null || _characterConfig.GetDefault() == null)
{
Debug.LogError("[MiniGameActorPresentation] invalid character config.");
return false;
}
return true;
}
private string GetPrefabPath(int figure)
{
return EnsureCharacterConfig() ? _characterConfig.GetPrefabPath(figure) : null;
}
private static bool IsAvatarMoving(ActorAvatar avatar)
{
return avatar != null
&& (avatar.Moving || Vector3.Distance(avatar.Obj.transform.position, avatar.Target) > StopDistance);
}
private void SetMoveState(ActorAvatar avatar, bool moving)
{
if (avatar == null || avatar.Animator == null || _behaviorWorld.HasActiveBehavior(avatar.ActorId))
{
return;
}
string state = ResolveMoveState(avatar.Animator, moving);
if (string.IsNullOrEmpty(state) || avatar.AnimState == state)
{
return;
}
avatar.AnimState = state;
avatar.Animator.CrossFade(state, 0.1f);
}
private static string ResolveMoveState(Animator animator, bool moving)
{
string fighterState = moving ? FighterWalkState : FighterStandState;
if (HasAnimatorState(animator, fighterState))
{
return fighterState;
}
string legacyState = moving ? LegacyWalkState : LegacyStandState;
return HasAnimatorState(animator, legacyState) ? legacyState : null;
}
private static bool HasAnimatorState(Animator animator, string state)
{
return animator != null
&& animator.runtimeAnimatorController != null
&& animator.HasState(0, Animator.StringToHash(state));
}
private void EnsureScenePresentation()
{
GameObject lightObject = new GameObject("MiniGameDirectionalLight");
lightObject.transform.SetParent(_root.transform, false);
lightObject.transform.rotation = Quaternion.Euler(45f, -35f, 0f);
Light light = lightObject.AddComponent<Light>();
light.type = LightType.Directional;
light.intensity = 1.15f;
GameObject cameraObject = new GameObject("MiniGameCamera");
cameraObject.transform.SetParent(_root.transform, false);
cameraObject.transform.position = new Vector3(0f, 4.2f, -6.2f);
cameraObject.transform.rotation = Quaternion.Euler(31f, 0f, 0f);
Camera camera = cameraObject.AddComponent<Camera>();
camera.clearFlags = CameraClearFlags.SolidColor;
camera.backgroundColor = new Color(0.06f, 0.075f, 0.09f, 1f);
camera.fieldOfView = 42f;
camera.nearClipPlane = 0.1f;
camera.farClipPlane = 80f;
GameObject ground = GameObject.CreatePrimitive(PrimitiveType.Cube);
ground.name = "MiniGameGround";
ground.transform.SetParent(_root.transform, false);
ground.transform.localPosition = new Vector3(0f, -0.05f, 0f);
ground.transform.localScale = new Vector3(7.5f, 0.1f, 2.8f);
Renderer renderer = ground.GetComponent<Renderer>();
if (renderer != null)
{
renderer.sharedMaterial = CreateRuntimeMaterial(new Color(0.16f, 0.18f, 0.19f, 1f));
}
}
private static void ConfigureAvatarRenderers(GameObject obj)
{
if (obj == null)
{
return;
}
Renderer[] renderers = obj.GetComponentsInChildren<Renderer>(true);
for (int i = 0; i < renderers.Length; i++)
{
Renderer renderer = renderers[i];
if (renderer == null)
{
continue;
}
renderer.lightProbeUsage = LightProbeUsage.BlendProbes;
renderer.reflectionProbeUsage = ReflectionProbeUsage.BlendProbes;
}
}
private static Material CreateRuntimeMaterial(Color color)
{
Shader shader = Shader.Find("Universal Render Pipeline/Lit");
if (shader == null)
{
shader = Shader.Find("Standard");
}
var material = new Material(shader);
material.color = color;
return material;
}
private static Vector3 ResolveForward(Vector3 forward, Vector3 fallback)
{
forward.y = 0f;
if (forward.sqrMagnitude <= 0.0001f)
{
forward = fallback;
}
forward.y = 0f;
if (forward.sqrMagnitude <= 0.0001f)
{
forward = Vector3.forward;
}
return forward.normalized;
}
private static BehaviorVector3 ToBehavior(Vector3 value)
{
return new BehaviorVector3(value.x, value.y, value.z);
}
private static BehaviorVector3 ToBehaviorForward(Vector3 value)
{
Vector3 forward = ResolveForward(value, Vector3.forward);
return new BehaviorVector3(forward.x, 0f, forward.z);
}
private static void DestroyUnityObject(UObject obj)
{
if (obj == null)
{
return;
}
if (Application.isPlaying)
{
UObject.Destroy(obj);
}
else
{
UObject.DestroyImmediate(obj);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 61b12bbed7cddf446a576ec50f6a915c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -30,7 +30,7 @@ namespace XGame
private const float LobbyPanelWidth = 520f;
private const float LobbyPanelHeight = 296f;
private const float LobbyPanelLeft = 24f;
private const float LobbyPanelTop = -24f;
private const float LobbyPanelMiddleY = 0f;
private const float CollapsedPanelWidth = 56f;
private void Awake()
@@ -208,10 +208,10 @@ namespace XGame
buttonGo.transform.SetParent(parent, false);
RectTransform rt = buttonGo.GetComponent<RectTransform>();
rt.anchorMin = new Vector2(0f, 1f);
rt.anchorMax = new Vector2(0f, 1f);
rt.pivot = new Vector2(0f, 1f);
rt.anchoredPosition = new Vector2(LobbyPanelLeft + LobbyPanelWidth, LobbyPanelTop);
rt.anchorMin = new Vector2(0f, 0.5f);
rt.anchorMax = new Vector2(0f, 0.5f);
rt.pivot = new Vector2(0f, 0.5f);
rt.anchoredPosition = new Vector2(LobbyPanelLeft + LobbyPanelWidth, LobbyPanelMiddleY);
rt.sizeDelta = new Vector2(48f, 72f);
Image img = buttonGo.GetComponent<Image>();
@@ -254,18 +254,18 @@ namespace XGame
if (panelLobby != null)
{
panelLobby.anchorMin = new Vector2(0f, 1f);
panelLobby.anchorMax = new Vector2(0f, 1f);
panelLobby.pivot = new Vector2(0f, 1f);
panelLobby.anchoredPosition = new Vector2(LobbyPanelLeft, LobbyPanelTop);
panelLobby.anchorMin = new Vector2(0f, 0.5f);
panelLobby.anchorMax = new Vector2(0f, 0.5f);
panelLobby.pivot = new Vector2(0f, 0.5f);
panelLobby.anchoredPosition = new Vector2(LobbyPanelLeft, LobbyPanelMiddleY);
panelLobby.sizeDelta = new Vector2(LobbyPanelWidth, LobbyPanelHeight);
}
if (collapseButtonRect != null)
{
collapseButtonRect.anchorMin = new Vector2(0f, 1f);
collapseButtonRect.anchorMax = new Vector2(0f, 1f);
collapseButtonRect.pivot = new Vector2(0f, 1f);
collapseButtonRect.anchoredPosition = new Vector2(LobbyPanelLeft + LobbyPanelWidth, LobbyPanelTop);
collapseButtonRect.anchorMin = new Vector2(0f, 0.5f);
collapseButtonRect.anchorMax = new Vector2(0f, 0.5f);
collapseButtonRect.pivot = new Vector2(0f, 0.5f);
collapseButtonRect.anchoredPosition = new Vector2(LobbyPanelLeft + LobbyPanelWidth, LobbyPanelMiddleY);
collapseButtonRect.sizeDelta = new Vector2(48f, 72f);
}