1222 lines
46 KiB
C#
1222 lines
46 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.EventSystems;
|
||
using UnityEngine.Rendering;
|
||
using UnityEngine.UI;
|
||
using XWorld.Framework.Protocol;
|
||
using UObject = UnityEngine.Object;
|
||
|
||
namespace XGame
|
||
{
|
||
public sealed class LobbyWorldController : MonoBehaviour
|
||
{
|
||
private sealed class Avatar
|
||
{
|
||
public int PlayerId;
|
||
public int Figure;
|
||
public GameObject Obj;
|
||
public Animator Animator;
|
||
public Vector3 Target;
|
||
public bool HasMoveTarget;
|
||
public bool Moving;
|
||
public string AnimState;
|
||
public Vector3 LastFaceDirection = Vector3.forward;
|
||
public Vector3 StopFaceDirection;
|
||
public bool SuppressMoveFacing;
|
||
public bool HasServerPosition;
|
||
public bool IsPlaceholder; // Obj 当前是胶囊占位(真实角色预设还在异步加载),加载完成后会原地升级
|
||
}
|
||
|
||
private static readonly string[] ActorPaths =
|
||
{
|
||
"Game/Art/Actor/Prefab/Assasin.prefab",
|
||
"Game/Art/Actor/Prefab/bls.prefab",
|
||
"Game/Art/Actor/Prefab/boy.prefab",
|
||
"Game/Art/Actor/Prefab/captain.prefab",
|
||
"Game/Art/Actor/Prefab/habit.prefab",
|
||
"Game/Art/Actor/Prefab/rhino.prefab",
|
||
"Game/Art/Actor/Prefab/tiger.prefab",
|
||
};
|
||
|
||
private const string JoystickPrefabPath = "Assets/Game/Art/UI/Prefab/UI_LobbyJoystick.prefab";
|
||
private const string SelectedCharacterKey = "CharacterSwitch.SelectedCharacterId";
|
||
private const float MoveSpeed = 1f;
|
||
private const float StopDistance = 0.05f;
|
||
private const float MoveSyncInterval = 1f;
|
||
private const float MovePredictionSeconds = 1.5f;
|
||
private const float DirectionChangeMinInterval = 0.1f;
|
||
private const float DirectionChangeDot = 0.98f;
|
||
private const float JoystickRadius = 74f;
|
||
private const float JoystickKnobRadius = 28f;
|
||
private const float JoystickPadding = 34f;
|
||
private const float JoystickDeadZone = 0.18f;
|
||
private const float JoystickAcquireRadiusMultiplier = 1.6f;
|
||
private const float CameraMinDistance = 3.5f;
|
||
private const float CameraMaxDistance = 14f;
|
||
private const float CameraDefaultDistance = 9.5f;
|
||
private const float CameraMinPitch = 20f;
|
||
private const float CameraMaxPitch = 75f;
|
||
private const float CameraMouseOrbitSensitivity = 0.2f;
|
||
private const float CameraTouchOrbitSensitivity = 0.12f;
|
||
private const float CameraMouseZoomSensitivity = 1.4f;
|
||
private const float CameraPinchZoomSensitivity = 0.015f;
|
||
private const float CameraFollowSharpness = 12f;
|
||
|
||
private readonly Dictionary<int, Avatar> avatars = new Dictionary<int, Avatar>();
|
||
private readonly Dictionary<int, GameObject> prefabCache = new Dictionary<int, GameObject>();
|
||
private readonly HashSet<int> prefabLoading = new HashSet<int>();
|
||
private Action<FrameworkOpcode, byte[]> send;
|
||
private Transform worldRoot;
|
||
private int selfPlayerId;
|
||
private int selfFigure;
|
||
private string selfName;
|
||
private bool joined;
|
||
private Vector2 joystick;
|
||
private bool joystickActive;
|
||
private float nextMoveCommandTime;
|
||
private float lastMoveCommandTime;
|
||
private Vector3 lastMoveDirection;
|
||
private bool receivedSelfState;
|
||
private float nextLocalMoveLogTime;
|
||
private float nextRemoteMoveLogTime;
|
||
private float cameraYaw;
|
||
private float cameraPitch = 43f;
|
||
private float cameraDistance = CameraDefaultDistance;
|
||
private bool cameraMouseDragging;
|
||
private bool cameraTouchDragging;
|
||
private int cameraTouchFingerId = -1;
|
||
private Vector2 lastCameraPointerPosition;
|
||
private bool cameraPinching;
|
||
private float lastPinchDistance;
|
||
private RectTransform joystickRoot;
|
||
private RectTransform joystickWidget; // 视觉容器:新预设里的 "Joystick" 子节点(自带定位);找不到则回退为 joystickRoot
|
||
private RectTransform joystickKnob;
|
||
private bool joystickMouseCaptured;
|
||
private int joystickTouchFingerId = -1;
|
||
private bool joystickLoading;
|
||
private static int materialLogCount;
|
||
|
||
public void Initialize(Action<FrameworkOpcode, byte[]> sendFramework, int playerId, string playerName, int figure)
|
||
{
|
||
send = sendFramework;
|
||
selfPlayerId = playerId;
|
||
selfName = string.IsNullOrEmpty(playerName) ? "P" + playerId : playerName;
|
||
selfFigure = Mathf.Clamp(figure <= 0 ? GetSelectedFigure() : figure, 1, ActorPaths.Length);
|
||
EnsureWorldRoot();
|
||
worldRoot.gameObject.SetActive(true);
|
||
EnsureCamera();
|
||
StartCoroutine(EnsureJoystickUIAsync());
|
||
GetOrCreateAvatar(selfPlayerId, selfFigure, selfName);
|
||
receivedSelfState = false;
|
||
lastMoveDirection = Vector3.zero;
|
||
nextMoveCommandTime = 0f;
|
||
lastMoveCommandTime = -DirectionChangeMinInterval;
|
||
Debug.Log("[LobbyWorldController] initialize self pid=" + selfPlayerId + " name=" + selfName + " figure=" + selfFigure);
|
||
SendJoin();
|
||
}
|
||
|
||
public void SendJoin()
|
||
{
|
||
if (send == null || selfPlayerId <= 0)
|
||
{
|
||
return;
|
||
}
|
||
joined = true;
|
||
if (joystickRoot != null)
|
||
{
|
||
joystickRoot.gameObject.SetActive(true);
|
||
}
|
||
send(FrameworkOpcode.LobbyJoin, new LobbyJoinMsg { Name = selfName, Figure = selfFigure }.Encode());
|
||
}
|
||
|
||
public void SendLeave()
|
||
{
|
||
if (!joined || send == null)
|
||
{
|
||
return;
|
||
}
|
||
joined = false;
|
||
send(FrameworkOpcode.LobbyLeave, Array.Empty<byte>());
|
||
if (worldRoot != null)
|
||
{
|
||
worldRoot.gameObject.SetActive(false);
|
||
}
|
||
if (joystickRoot != null)
|
||
{
|
||
joystickRoot.gameObject.SetActive(false);
|
||
}
|
||
}
|
||
|
||
public void RefreshSelectedFigure()
|
||
{
|
||
int nextFigure = Mathf.Clamp(GetSelectedFigure(), 1, ActorPaths.Length);
|
||
if (selfFigure == nextFigure)
|
||
{
|
||
return;
|
||
}
|
||
|
||
selfFigure = nextFigure;
|
||
Vector3 position = Vector3.zero;
|
||
Quaternion rotation = Quaternion.identity;
|
||
bool hadAvatar = false;
|
||
if (avatars.TryGetValue(selfPlayerId, out Avatar avatar))
|
||
{
|
||
if (avatar.Obj != null)
|
||
{
|
||
position = avatar.Obj.transform.position;
|
||
rotation = avatar.Obj.transform.rotation;
|
||
hadAvatar = true;
|
||
}
|
||
Destroy(avatar.Obj);
|
||
avatars.Remove(selfPlayerId);
|
||
}
|
||
|
||
Avatar nextAvatar = GetOrCreateAvatar(selfPlayerId, selfFigure, selfName);
|
||
if (hadAvatar && nextAvatar.Obj != null)
|
||
{
|
||
nextAvatar.Obj.transform.position = position;
|
||
nextAvatar.Obj.transform.rotation = rotation;
|
||
nextAvatar.Target = position;
|
||
}
|
||
lastMoveDirection = Vector3.zero;
|
||
nextMoveCommandTime = 0f;
|
||
lastMoveCommandTime = -DirectionChangeMinInterval;
|
||
SendJoin();
|
||
}
|
||
|
||
public void HandleFrame(Frame frame)
|
||
{
|
||
if (frame.Opcode == (ushort)FrameworkOpcode.LobbyState)
|
||
{
|
||
ApplyState(LobbyStateMsg.Decode(frame.Payload));
|
||
}
|
||
else if (frame.Opcode == (ushort)FrameworkOpcode.LobbyMove)
|
||
{
|
||
ApplyMove(LobbyMoveMsg.Decode(frame.Payload));
|
||
}
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
UpdateRemoteAvatars();
|
||
UpdateLocalMove();
|
||
UpdateCamera();
|
||
}
|
||
|
||
private void ApplyState(LobbyStateMsg state)
|
||
{
|
||
ReconcileSelfPlayerId(state);
|
||
Debug.Log("[LobbyWorldController] state players=" + DescribeStatePlayers(state) + " self=" + selfPlayerId);
|
||
var seen = new HashSet<int>();
|
||
foreach (LobbyPlayerMsg player in state.Players)
|
||
{
|
||
seen.Add(player.PlayerId);
|
||
Avatar avatar = GetOrCreateAvatar(player.PlayerId, player.Figure, player.Name);
|
||
Vector3 pos = new Vector3(player.X, player.Y, player.Z);
|
||
if (player.PlayerId == selfPlayerId)
|
||
{
|
||
if (!receivedSelfState)
|
||
{
|
||
avatar.Obj.transform.position = pos;
|
||
avatar.Target = pos;
|
||
avatar.HasServerPosition = true;
|
||
}
|
||
receivedSelfState = true;
|
||
continue;
|
||
}
|
||
if (!avatar.HasServerPosition && avatar.Obj != null)
|
||
{
|
||
avatar.Obj.transform.position = pos;
|
||
avatar.HasServerPosition = true;
|
||
}
|
||
avatar.Target = pos;
|
||
avatar.Moving = player.Moving;
|
||
}
|
||
|
||
var remove = new List<int>();
|
||
foreach (int id in avatars.Keys)
|
||
{
|
||
if (!seen.Contains(id))
|
||
{
|
||
remove.Add(id);
|
||
}
|
||
}
|
||
foreach (int id in remove)
|
||
{
|
||
Destroy(avatars[id].Obj);
|
||
avatars.Remove(id);
|
||
}
|
||
}
|
||
|
||
private static string DescribeStatePlayers(LobbyStateMsg state)
|
||
{
|
||
if (state == null || state.Players == null || state.Players.Count == 0)
|
||
{
|
||
return "[]";
|
||
}
|
||
|
||
string result = "[";
|
||
for (int i = 0; i < state.Players.Count; i++)
|
||
{
|
||
LobbyPlayerMsg player = state.Players[i];
|
||
if (i > 0)
|
||
{
|
||
result += ", ";
|
||
}
|
||
result += player.PlayerId + ":" + player.Name + "@" + player.X.ToString("0.00") + "," + player.Z.ToString("0.00");
|
||
}
|
||
return result + "]";
|
||
}
|
||
|
||
private void ReconcileSelfPlayerId(LobbyStateMsg state)
|
||
{
|
||
if (state == null || state.Players == null || state.Players.Count == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
for (int i = 0; i < state.Players.Count; i++)
|
||
{
|
||
if (state.Players[i].PlayerId == selfPlayerId)
|
||
{
|
||
return;
|
||
}
|
||
}
|
||
|
||
int serverPlayerId = 0;
|
||
if (!string.IsNullOrEmpty(selfName))
|
||
{
|
||
for (int i = 0; i < state.Players.Count; i++)
|
||
{
|
||
if (state.Players[i].Name == selfName)
|
||
{
|
||
serverPlayerId = state.Players[i].PlayerId;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (serverPlayerId <= 0 && state.Players.Count == 1)
|
||
{
|
||
serverPlayerId = state.Players[0].PlayerId;
|
||
}
|
||
if (serverPlayerId <= 0 || serverPlayerId == selfPlayerId)
|
||
{
|
||
return;
|
||
}
|
||
|
||
int oldPlayerId = selfPlayerId;
|
||
if (avatars.TryGetValue(oldPlayerId, out Avatar oldAvatar))
|
||
{
|
||
avatars.Remove(oldPlayerId);
|
||
if (!avatars.ContainsKey(serverPlayerId))
|
||
{
|
||
oldAvatar.PlayerId = serverPlayerId;
|
||
avatars[serverPlayerId] = oldAvatar;
|
||
}
|
||
else if (oldAvatar.Obj != null)
|
||
{
|
||
Destroy(oldAvatar.Obj);
|
||
}
|
||
}
|
||
selfPlayerId = serverPlayerId;
|
||
Debug.Log("[LobbyWorldController] reconcile self player id " + oldPlayerId + " -> " + serverPlayerId);
|
||
}
|
||
|
||
private void ApplyMove(LobbyMoveMsg move)
|
||
{
|
||
if (!avatars.TryGetValue(move.PlayerId, out Avatar avatar))
|
||
{
|
||
avatar = GetOrCreateAvatar(move.PlayerId, 1, "P" + move.PlayerId);
|
||
}
|
||
Vector3 target = new Vector3(move.X, move.Y, move.Z);
|
||
if (!avatar.HasServerPosition && avatar.Obj != null)
|
||
{
|
||
avatar.Obj.transform.position = target;
|
||
avatar.HasServerPosition = true;
|
||
}
|
||
avatar.Target = target;
|
||
avatar.Moving = move.Moving;
|
||
Vector3 face = new Vector3(move.DirX, 0f, move.DirZ);
|
||
if (move.PlayerId == selfPlayerId)
|
||
{
|
||
return;
|
||
}
|
||
if (Time.unscaledTime >= nextRemoteMoveLogTime)
|
||
{
|
||
nextRemoteMoveLogTime = Time.unscaledTime + 1f;
|
||
Debug.Log("[LobbyWorldController] remote move pid=" + move.PlayerId + " target=" + move.X.ToString("0.00") + "," + move.Z.ToString("0.00") + " moving=" + move.Moving);
|
||
}
|
||
if (face.sqrMagnitude > 0.001f)
|
||
{
|
||
FaceAvatar(avatar, face);
|
||
}
|
||
}
|
||
|
||
private void UpdateLocalMove()
|
||
{
|
||
if (!joined || !avatars.TryGetValue(selfPlayerId, out Avatar avatar) || avatar.Obj == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
UpdateJoystick();
|
||
Vector3 dir = GetMoveDirection();
|
||
bool moving = dir.sqrMagnitude > 0.001f;
|
||
if (moving)
|
||
{
|
||
dir.Normalize();
|
||
MoveLocalAvatar(avatar, dir);
|
||
bool directionChanged = DirectionChanged(dir, lastMoveDirection);
|
||
bool canSendDirectionChange = Time.unscaledTime >= lastMoveCommandTime + DirectionChangeMinInterval;
|
||
if (Time.unscaledTime >= nextMoveCommandTime || (directionChanged && canSendDirectionChange))
|
||
{
|
||
Vector3 target = avatar.Obj.transform.position + dir * MoveSpeed * MovePredictionSeconds;
|
||
SendMove(target, dir, true);
|
||
lastMoveDirection = dir;
|
||
nextMoveCommandTime = Time.unscaledTime + MoveSyncInterval;
|
||
}
|
||
}
|
||
else if (lastMoveDirection.sqrMagnitude > 0.001f)
|
||
{
|
||
avatar.StopFaceDirection = lastMoveDirection;
|
||
avatar.SuppressMoveFacing = true;
|
||
avatar.Target = avatar.Obj.transform.position;
|
||
avatar.HasMoveTarget = false;
|
||
avatar.Moving = false;
|
||
SetMoveState(avatar, false);
|
||
SendMove(avatar.Obj.transform.position, lastMoveDirection, false);
|
||
lastMoveDirection = Vector3.zero;
|
||
nextMoveCommandTime = Time.unscaledTime;
|
||
}
|
||
}
|
||
|
||
private Vector3 GetMoveDirection()
|
||
{
|
||
Camera cam = Camera.main;
|
||
Vector3 forward = cam != null ? cam.transform.forward : Vector3.forward;
|
||
Vector3 right = cam != null ? cam.transform.right : Vector3.right;
|
||
forward.y = 0f;
|
||
right.y = 0f;
|
||
forward = forward.sqrMagnitude > 0.001f ? forward.normalized : Vector3.forward;
|
||
right = right.sqrMagnitude > 0.001f ? right.normalized : Vector3.right;
|
||
|
||
Vector3 dir = Vector3.zero;
|
||
if (Input.GetKey(KeyCode.W)) dir += forward;
|
||
if (Input.GetKey(KeyCode.S)) dir -= forward;
|
||
if (Input.GetKey(KeyCode.A)) dir -= right;
|
||
if (Input.GetKey(KeyCode.D)) dir += right;
|
||
if (joystickActive) dir += right * joystick.x + forward * joystick.y;
|
||
return dir;
|
||
}
|
||
|
||
private void UpdateRemoteAvatars()
|
||
{
|
||
foreach (Avatar avatar in avatars.Values)
|
||
{
|
||
if (avatar.PlayerId == selfPlayerId || avatar.Obj == null)
|
||
{
|
||
continue;
|
||
}
|
||
avatar.Obj.transform.position = Vector3.MoveTowards(avatar.Obj.transform.position, avatar.Target, MoveSpeed * Time.deltaTime);
|
||
SetMoveState(avatar, avatar.Moving);
|
||
if (avatar.Moving && Vector3.Distance(avatar.Obj.transform.position, avatar.Target) <= StopDistance)
|
||
{
|
||
avatar.Moving = false;
|
||
SetMoveState(avatar, false);
|
||
}
|
||
}
|
||
}
|
||
|
||
private void ApplyLocalMoveTo(Avatar avatar, Vector3 target, Vector3 direction)
|
||
{
|
||
avatar.Target = target;
|
||
avatar.HasMoveTarget = true;
|
||
avatar.Moving = true;
|
||
avatar.SuppressMoveFacing = false;
|
||
FaceAvatar(avatar, direction);
|
||
SetMoveState(avatar, true);
|
||
}
|
||
|
||
private void MoveLocalAvatar(Avatar avatar, Vector3 direction)
|
||
{
|
||
Vector3 pos = avatar.Obj.transform.position;
|
||
pos += direction * MoveSpeed * Time.deltaTime;
|
||
avatar.Obj.transform.position = pos;
|
||
avatar.Target = pos;
|
||
avatar.HasMoveTarget = true;
|
||
avatar.Moving = true;
|
||
avatar.SuppressMoveFacing = false;
|
||
FaceAvatar(avatar, direction);
|
||
SetMoveState(avatar, true);
|
||
}
|
||
|
||
private void AdvanceLocalAvatar(Avatar avatar)
|
||
{
|
||
if (!avatar.HasMoveTarget)
|
||
{
|
||
return;
|
||
}
|
||
|
||
Vector3 offset = avatar.Target - avatar.Obj.transform.position;
|
||
offset.y = 0f;
|
||
if (offset.magnitude <= StopDistance)
|
||
{
|
||
avatar.Obj.transform.position = avatar.Target;
|
||
avatar.HasMoveTarget = false;
|
||
avatar.Moving = false;
|
||
if (avatar.StopFaceDirection.sqrMagnitude > 0.001f)
|
||
{
|
||
FaceAvatar(avatar, avatar.StopFaceDirection);
|
||
}
|
||
avatar.StopFaceDirection = Vector3.zero;
|
||
avatar.SuppressMoveFacing = false;
|
||
SetMoveState(avatar, false);
|
||
return;
|
||
}
|
||
|
||
Vector3 stepDirection = offset.normalized;
|
||
avatar.Obj.transform.position = Vector3.MoveTowards(avatar.Obj.transform.position, avatar.Target, MoveSpeed * Time.deltaTime);
|
||
if (!avatar.SuppressMoveFacing)
|
||
{
|
||
FaceAvatar(avatar, stepDirection);
|
||
}
|
||
SetMoveState(avatar, true);
|
||
}
|
||
|
||
private void SendMove(Vector3 position, Vector3 direction, bool moving)
|
||
{
|
||
if (send == null)
|
||
{
|
||
return;
|
||
}
|
||
if (Time.unscaledTime >= nextLocalMoveLogTime)
|
||
{
|
||
nextLocalMoveLogTime = Time.unscaledTime + 1f;
|
||
Debug.Log("[LobbyWorldController] send move self=" + selfPlayerId + " target=" + position.x.ToString("0.00") + "," + position.z.ToString("0.00") + " moving=" + moving);
|
||
}
|
||
send(FrameworkOpcode.LobbyMove, new LobbyMoveMsg
|
||
{
|
||
PlayerId = selfPlayerId,
|
||
X = position.x,
|
||
Y = position.y,
|
||
Z = position.z,
|
||
DirX = direction.x,
|
||
DirZ = direction.z,
|
||
Moving = moving,
|
||
}.Encode());
|
||
lastMoveCommandTime = Time.unscaledTime;
|
||
}
|
||
|
||
private Avatar GetOrCreateAvatar(int playerId, int figure, string name)
|
||
{
|
||
figure = Mathf.Clamp(figure <= 0 ? 1 : figure, 1, ActorPaths.Length);
|
||
if (avatars.TryGetValue(playerId, out Avatar avatar))
|
||
{
|
||
if (avatar.Figure == figure)
|
||
{
|
||
return avatar;
|
||
}
|
||
Destroy(avatar.Obj);
|
||
avatars.Remove(playerId);
|
||
}
|
||
|
||
avatar = new Avatar { PlayerId = playerId, Figure = figure };
|
||
avatar.Obj = InstantiateAvatar(figure, out bool isPlaceholder);
|
||
avatar.IsPlaceholder = isPlaceholder;
|
||
avatar.Obj.name = "LobbyPlayer_" + playerId + "_" + name;
|
||
avatar.Target = avatar.Obj.transform.position;
|
||
avatar.Animator = avatar.Obj.GetComponentInChildren<Animator>();
|
||
avatars[playerId] = avatar;
|
||
SetMoveState(avatar, false);
|
||
return avatar;
|
||
}
|
||
|
||
private static bool DirectionChanged(Vector3 lhs, Vector3 rhs)
|
||
{
|
||
if (rhs.sqrMagnitude <= 0.001f)
|
||
{
|
||
return true;
|
||
}
|
||
lhs.y = 0f;
|
||
rhs.y = 0f;
|
||
lhs.Normalize();
|
||
rhs.Normalize();
|
||
return Vector3.Dot(lhs, rhs) < DirectionChangeDot;
|
||
}
|
||
|
||
private static void FaceAvatar(Avatar avatar, Vector3 direction)
|
||
{
|
||
if (avatar == null || avatar.Obj == null)
|
||
{
|
||
return;
|
||
}
|
||
direction.y = 0f;
|
||
if (direction.sqrMagnitude <= 0.001f)
|
||
{
|
||
return;
|
||
}
|
||
Vector3 forward = direction.normalized;
|
||
avatar.Obj.transform.forward = forward;
|
||
avatar.LastFaceDirection = forward;
|
||
}
|
||
|
||
private GameObject InstantiateAvatar(int figure, out bool isPlaceholder)
|
||
{
|
||
GameObject prefab = GetCachedPrefabOrLoadAsync(figure);
|
||
isPlaceholder = prefab == null;
|
||
GameObject obj = prefab != null ? Instantiate(prefab, worldRoot) : GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
||
if (prefab == null)
|
||
{
|
||
obj.transform.SetParent(worldRoot, false);
|
||
obj.transform.localScale = new Vector3(0.7f, 1f, 0.7f);
|
||
}
|
||
obj.SetActive(true);
|
||
ConfigureAvatarLightingAndLog(obj, "instantiate figure=" + figure);
|
||
return obj;
|
||
}
|
||
|
||
// 命中缓存则同步返回真实预设;未命中则返回 null(调用方用胶囊占位),并异步从 CDN 加载,
|
||
// 完成后写入缓存并把所有该 figure 的占位胶囊原地升级为真实角色。统一异步、避免读到旧底包。
|
||
private GameObject GetCachedPrefabOrLoadAsync(int figure)
|
||
{
|
||
if (prefabCache.TryGetValue(figure, out GameObject prefab) && prefab != null)
|
||
{
|
||
return prefab;
|
||
}
|
||
if (prefabLoading.Add(figure))
|
||
{
|
||
StartCoroutine(CoLoadActorPrefab(figure));
|
||
}
|
||
return null;
|
||
}
|
||
|
||
private IEnumerator CoLoadActorPrefab(int figure)
|
||
{
|
||
UObject loaded = null;
|
||
yield return XResLoader.coLoadRes(ActorPaths[figure - 1], typeof(GameObject), o => loaded = o);
|
||
prefabLoading.Remove(figure);
|
||
GameObject prefab = loaded as GameObject;
|
||
if (prefab == null)
|
||
{
|
||
Debug.LogWarning("[LobbyWorldController] load actor prefab failed figure=" + figure);
|
||
yield break;
|
||
}
|
||
prefabCache[figure] = prefab;
|
||
UpgradePlaceholders(figure, prefab);
|
||
}
|
||
|
||
// 真实角色预设加载完成后,把当前还在用胶囊占位的同 figure 头像原地替换为真实角色,保留位置/朝向/状态。
|
||
private void UpgradePlaceholders(int figure, GameObject prefab)
|
||
{
|
||
foreach (var kv in avatars)
|
||
{
|
||
Avatar a = kv.Value;
|
||
if (a == null || a.Figure != figure || !a.IsPlaceholder || a.Obj == null)
|
||
{
|
||
continue;
|
||
}
|
||
Vector3 pos = a.Obj.transform.position;
|
||
Quaternion rot = a.Obj.transform.rotation;
|
||
string objName = a.Obj.name;
|
||
Destroy(a.Obj);
|
||
a.Obj = Instantiate(prefab, worldRoot);
|
||
a.Obj.name = objName;
|
||
a.Obj.transform.position = pos;
|
||
a.Obj.transform.rotation = rot;
|
||
a.Obj.SetActive(true);
|
||
ConfigureAvatarLightingAndLog(a.Obj, "upgrade figure=" + figure);
|
||
a.Animator = a.Obj.GetComponentInChildren<Animator>();
|
||
a.IsPlaceholder = false;
|
||
a.AnimState = null; // 让 SetMoveState 重新触发动画
|
||
SetMoveState(a, a.Moving);
|
||
}
|
||
}
|
||
|
||
private static void ConfigureAvatarLightingAndLog(GameObject obj, string reason)
|
||
{
|
||
if (obj == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
Renderer[] renderers = obj.GetComponentsInChildren<Renderer>(true);
|
||
foreach (Renderer renderer in renderers)
|
||
{
|
||
if (renderer == null)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
renderer.lightProbeUsage = LightProbeUsage.BlendProbes;
|
||
renderer.reflectionProbeUsage = ReflectionProbeUsage.BlendProbes;
|
||
|
||
if (materialLogCount >= 24)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
Material[] materials = renderer.sharedMaterials;
|
||
for (int i = 0; i < materials.Length && materialLogCount < 24; i++)
|
||
{
|
||
Material mat = materials[i];
|
||
if (mat == null)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
Shader shader = mat.shader;
|
||
bool shaderRebound = false;
|
||
if (shader != null)
|
||
{
|
||
Shader runtimeShader = Game.GetRuntimeShader(shader.name);
|
||
if (runtimeShader != null && runtimeShader != shader)
|
||
{
|
||
mat.shader = runtimeShader;
|
||
shader = runtimeShader;
|
||
shaderRebound = true;
|
||
}
|
||
}
|
||
Texture mainTex = mat.HasProperty("_MainTex") ? mat.GetTexture("_MainTex") : null;
|
||
string textureInfo = mainTex != null ? mainTex.name : "null";
|
||
Texture2D tex2D = mainTex as Texture2D;
|
||
if (tex2D != null)
|
||
{
|
||
textureInfo += "/" + tex2D.format + "/" + tex2D.graphicsFormat;
|
||
}
|
||
|
||
Debug.Log("[LobbyWorldController] material " + reason +
|
||
" renderer=" + renderer.name +
|
||
" lightProbe=" + renderer.lightProbeUsage +
|
||
" reflectionProbe=" + renderer.reflectionProbeUsage +
|
||
" lightmapIndex=" + renderer.lightmapIndex +
|
||
" mat=" + mat.name +
|
||
" shader=" + (shader != null ? shader.name : "null") +
|
||
" supported=" + (shader != null && shader.isSupported) +
|
||
" shaderRebound=" + shaderRebound +
|
||
" keywords=" + string.Join(",", mat.shaderKeywords) +
|
||
" AO=" + (mat.HasProperty("_AO") ? mat.GetFloat("_AO").ToString("0.###") : "n/a") +
|
||
" UseMix=" + (mat.HasProperty("_UseMixTex") ? mat.GetFloat("_UseMixTex").ToString("0.###") : "n/a") +
|
||
" tex=" + textureInfo);
|
||
materialLogCount++;
|
||
}
|
||
}
|
||
}
|
||
|
||
private void SetMoveState(Avatar avatar, bool moving)
|
||
{
|
||
if (avatar.Animator == null)
|
||
{
|
||
return;
|
||
}
|
||
string state = moving ? "Walk" : "Stand";
|
||
if (avatar.AnimState == state)
|
||
{
|
||
return;
|
||
}
|
||
avatar.AnimState = state;
|
||
avatar.Animator.CrossFade(state, 0.1f);
|
||
}
|
||
|
||
private void UpdateJoystick()
|
||
{
|
||
joystick = Vector2.zero;
|
||
joystickActive = false;
|
||
Vector2 center = GetJoystickScreenCenter();
|
||
float radius = GetJoystickScreenRadius();
|
||
Vector2? pointer = null;
|
||
|
||
if (Input.touchCount > 0)
|
||
{
|
||
pointer = GetJoystickTouchPointer();
|
||
joystickMouseCaptured = false;
|
||
}
|
||
else
|
||
{
|
||
joystickTouchFingerId = -1;
|
||
if (Input.GetMouseButtonDown(0))
|
||
{
|
||
joystickMouseCaptured = IsInJoystickArea(Input.mousePosition);
|
||
}
|
||
if (Input.GetMouseButtonUp(0))
|
||
{
|
||
joystickMouseCaptured = false;
|
||
}
|
||
if (joystickMouseCaptured && Input.GetMouseButton(0))
|
||
{
|
||
// 一旦按住摇杆,拖到任意位置都继续跟随(方向取自中心、幅度钳到最大),松手才释放
|
||
pointer = Input.mousePosition;
|
||
}
|
||
}
|
||
|
||
if (pointer == null)
|
||
{
|
||
UpdateJoystickVisual();
|
||
return;
|
||
}
|
||
|
||
Vector2 raw = (pointer.Value - center) / radius;
|
||
joystick = Vector2.ClampMagnitude(raw, 1f);
|
||
joystickActive = joystick.magnitude >= JoystickDeadZone;
|
||
UpdateJoystickVisual();
|
||
}
|
||
|
||
private Vector2? GetJoystickTouchPointer()
|
||
{
|
||
if (joystickTouchFingerId >= 0)
|
||
{
|
||
for (int i = 0; i < Input.touchCount; i++)
|
||
{
|
||
Touch touch = Input.GetTouch(i);
|
||
if (touch.fingerId != joystickTouchFingerId)
|
||
{
|
||
continue;
|
||
}
|
||
if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled)
|
||
{
|
||
joystickTouchFingerId = -1;
|
||
return null;
|
||
}
|
||
// 已按住:拖到任意位置都继续跟随,抬指(Ended/Canceled)才释放
|
||
return touch.position;
|
||
}
|
||
joystickTouchFingerId = -1;
|
||
}
|
||
|
||
for (int i = 0; i < Input.touchCount; i++)
|
||
{
|
||
Touch touch = Input.GetTouch(i);
|
||
if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled)
|
||
{
|
||
continue;
|
||
}
|
||
if (IsInJoystickArea(touch.position))
|
||
{
|
||
joystickTouchFingerId = touch.fingerId;
|
||
return touch.position;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
private void UpdateJoystickVisual()
|
||
{
|
||
if (joystickRoot == null || joystickKnob == null)
|
||
{
|
||
return;
|
||
}
|
||
joystickRoot.gameObject.SetActive(joined);
|
||
joystickKnob.anchoredPosition = joystick * GetKnobTravelRadius();
|
||
}
|
||
|
||
// 摇杆头在视觉容器内的最大行程(本地单位):容器半径 - 摇杆头半径,按预设实际尺寸自适应,避免出界
|
||
private float GetKnobTravelRadius()
|
||
{
|
||
RectTransform w = joystickWidget != null ? joystickWidget : joystickRoot;
|
||
if (w == null)
|
||
{
|
||
return JoystickRadius;
|
||
}
|
||
float widgetHalf = Mathf.Min(w.rect.width, w.rect.height) * 0.5f;
|
||
float knobHalf = joystickKnob != null ? Mathf.Min(joystickKnob.rect.width, joystickKnob.rect.height) * 0.5f : 0f;
|
||
return Mathf.Max(1f, widgetHalf - knobHalf);
|
||
}
|
||
|
||
private void EnsureWorldRoot()
|
||
{
|
||
if (worldRoot != null)
|
||
{
|
||
return;
|
||
}
|
||
GameObject root = GameObject.Find("LobbyWorld");
|
||
if (root == null)
|
||
{
|
||
root = new GameObject("LobbyWorld");
|
||
}
|
||
worldRoot = root.transform;
|
||
}
|
||
|
||
private void EnsureCamera()
|
||
{
|
||
if (Camera.main != null)
|
||
{
|
||
return;
|
||
}
|
||
GameObject camObj = new GameObject("LobbyCamera");
|
||
camObj.tag = "MainCamera";
|
||
camObj.AddComponent<Camera>();
|
||
camObj.transform.position = new Vector3(0f, 7f, -8f);
|
||
camObj.transform.rotation = Quaternion.Euler(55f, 0f, 0f);
|
||
}
|
||
|
||
private IEnumerator EnsureJoystickUIAsync()
|
||
{
|
||
EnsureEventSystem();
|
||
if (joystickRoot != null)
|
||
{
|
||
joystickRoot.gameObject.SetActive(joined);
|
||
yield break;
|
||
}
|
||
if (joystickLoading)
|
||
{
|
||
yield break;
|
||
}
|
||
joystickLoading = true;
|
||
|
||
GameObject rootObj = null;
|
||
UObject prefab = null;
|
||
yield return XResLoader.coLoadRes(JoystickPrefabPath, typeof(GameObject), o => prefab = o);
|
||
joystickLoading = false;
|
||
// 异步期间可能已被其它调用创建好,避免重复
|
||
if (joystickRoot != null)
|
||
{
|
||
joystickRoot.gameObject.SetActive(joined);
|
||
yield break;
|
||
}
|
||
// 摇杆是抬头 HUD → 挂到 UIRoot/HUD 层(见 UI 挂载规则);Main 未就绪时降级到本地 HUD 画布
|
||
Transform parent = GetHudParent();
|
||
GameObject prefabObj = prefab as GameObject;
|
||
if (prefabObj == null)
|
||
{
|
||
Debug.LogError("[LobbyWorldController] load joystick prefab failed: " + JoystickPrefabPath);
|
||
yield break;
|
||
}
|
||
rootObj = Instantiate(prefabObj, parent, false);
|
||
|
||
rootObj.name = "UI_LobbyJoystick";
|
||
joystickRoot = rootObj.GetComponent<RectTransform>();
|
||
if (joystickRoot == null)
|
||
{
|
||
joystickRoot = rootObj.AddComponent<RectTransform>();
|
||
}
|
||
joystickRoot.localScale = Vector3.one; // 兜底:设备包 prefab 根缩放异常时仍可见
|
||
|
||
// 视觉容器:新预设里是 "Joystick" 子节点(预设自带定位);找不到则用根并由 ApplyJoystickLayout 代码定位
|
||
Transform widget = joystickRoot.FindTransform("Joystick");
|
||
joystickWidget = (widget as RectTransform) ?? joystickRoot;
|
||
if (joystickWidget == joystickRoot)
|
||
{
|
||
ApplyJoystickLayout(joystickRoot);
|
||
}
|
||
|
||
// Knob 递归查找,兼容"直接子节点"(代码兜底版)与"Joystick 子节点"(新预设)两种结构
|
||
Transform knob = joystickRoot.FindTransform("Knob");
|
||
joystickKnob = knob as RectTransform;
|
||
if (joystickKnob == null)
|
||
{
|
||
Debug.LogError("[LobbyWorldController] joystick prefab missing Knob node: " + JoystickPrefabPath);
|
||
Destroy(rootObj);
|
||
joystickRoot = null;
|
||
joystickWidget = null;
|
||
yield break;
|
||
}
|
||
|
||
joystickRoot.gameObject.SetActive(joined);
|
||
UpdateJoystickVisual();
|
||
}
|
||
|
||
private Transform GetHudParent()
|
||
{
|
||
RectTransform hud = UIManager.GetLayer(UILayer.HUD);
|
||
if (hud != null)
|
||
{
|
||
return hud;
|
||
}
|
||
return EnsureHudCanvas().transform; // 降级:Main 未就绪时用本地创建的 HUD 画布
|
||
}
|
||
|
||
private Canvas EnsureHudCanvas()
|
||
{
|
||
EnsureEventSystem();
|
||
// 优先复用主 UI 画布(Main)——与其它大厅 UI 一致(AttachToMain)。运行时新建的 Overlay 画布
|
||
// 在本设备上世界缩放为 0(lossyScale=0)导致摇杆不可见;复用已验证可用的 Main 画布可避免该问题。
|
||
GameObject main = GameObject.Find("Main");
|
||
if (main != null)
|
||
{
|
||
Canvas mainCanvas = main.GetComponentInParent<Canvas>();
|
||
if (mainCanvas != null)
|
||
{
|
||
return mainCanvas;
|
||
}
|
||
}
|
||
|
||
GameObject canvasObj = GameObject.Find("LobbyHudCanvas");
|
||
Canvas canvas = canvasObj != null ? canvasObj.GetComponent<Canvas>() : null;
|
||
if (canvas != null)
|
||
{
|
||
return canvas;
|
||
}
|
||
|
||
canvasObj = new GameObject("LobbyHudCanvas", typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
|
||
canvas = canvasObj.GetComponent<Canvas>();
|
||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||
canvas.sortingOrder = 50;
|
||
|
||
CanvasScaler scaler = canvasObj.GetComponent<CanvasScaler>();
|
||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||
scaler.referenceResolution = new Vector2(1280f, 720f);
|
||
scaler.matchWidthOrHeight = 0.5f;
|
||
return canvas;
|
||
}
|
||
|
||
private static void EnsureEventSystem()
|
||
{
|
||
if (EventSystem.current != null)
|
||
{
|
||
return;
|
||
}
|
||
GameObject eventSystem = new GameObject("EventSystem");
|
||
eventSystem.AddComponent<EventSystem>();
|
||
eventSystem.AddComponent<StandaloneInputModule>();
|
||
}
|
||
|
||
private static void ApplyJoystickLayout(RectTransform rect)
|
||
{
|
||
rect.localScale = Vector3.one; // 强制缩放为 1:设备包里的 prefab 根缩放可能是 0(会导致整体不可见)
|
||
rect.anchorMin = Vector2.zero;
|
||
rect.anchorMax = Vector2.zero;
|
||
rect.pivot = new Vector2(0.5f, 0.5f);
|
||
rect.anchoredPosition = new Vector2(JoystickPadding + JoystickRadius, JoystickPadding + JoystickRadius);
|
||
rect.sizeDelta = Vector2.one * (JoystickRadius * 2f);
|
||
}
|
||
|
||
private void UpdateCamera()
|
||
{
|
||
if (!avatars.TryGetValue(selfPlayerId, out Avatar avatar) || avatar.Obj == null)
|
||
{
|
||
return;
|
||
}
|
||
Camera cam = Camera.main;
|
||
if (cam == null)
|
||
{
|
||
return;
|
||
}
|
||
UpdateCameraInput();
|
||
Vector3 lookAt = avatar.Obj.transform.position + Vector3.up * 1.2f;
|
||
Quaternion orbit = Quaternion.Euler(cameraPitch, cameraYaw, 0f);
|
||
Vector3 targetPosition = lookAt + orbit * (Vector3.back * cameraDistance);
|
||
float t = 1f - Mathf.Exp(-CameraFollowSharpness * Time.deltaTime);
|
||
cam.transform.position = Vector3.Lerp(cam.transform.position, targetPosition, t);
|
||
cam.transform.LookAt(lookAt);
|
||
}
|
||
|
||
private void UpdateCameraInput()
|
||
{
|
||
UpdateCameraMouseInput();
|
||
UpdateCameraTouchInput();
|
||
cameraPitch = Mathf.Clamp(cameraPitch, CameraMinPitch, CameraMaxPitch);
|
||
cameraDistance = Mathf.Clamp(cameraDistance, CameraMinDistance, CameraMaxDistance);
|
||
}
|
||
|
||
private void UpdateCameraMouseInput()
|
||
{
|
||
bool pointerOverUi = IsPointerOverUi(-1);
|
||
float scroll = Input.mouseScrollDelta.y;
|
||
if (!pointerOverUi && Mathf.Abs(scroll) > 0.001f)
|
||
{
|
||
cameraDistance -= scroll * CameraMouseZoomSensitivity;
|
||
}
|
||
|
||
if (Input.touchCount > 0)
|
||
{
|
||
cameraMouseDragging = false;
|
||
return;
|
||
}
|
||
|
||
if (Input.GetMouseButtonDown(0))
|
||
{
|
||
Vector2 pos = Input.mousePosition;
|
||
cameraMouseDragging = !pointerOverUi && !IsInJoystickArea(pos);
|
||
lastCameraPointerPosition = pos;
|
||
}
|
||
else if (Input.GetMouseButtonUp(0))
|
||
{
|
||
cameraMouseDragging = false;
|
||
}
|
||
|
||
if (!cameraMouseDragging || !Input.GetMouseButton(0))
|
||
{
|
||
return;
|
||
}
|
||
|
||
Vector2 current = Input.mousePosition;
|
||
if (IsPointerOverUi(-1))
|
||
{
|
||
lastCameraPointerPosition = current;
|
||
return;
|
||
}
|
||
Vector2 delta = current - lastCameraPointerPosition;
|
||
lastCameraPointerPosition = current;
|
||
ApplyCameraOrbit(delta, CameraMouseOrbitSensitivity);
|
||
}
|
||
|
||
private void UpdateCameraTouchInput()
|
||
{
|
||
int cameraTouchCount = 0;
|
||
Touch first = default;
|
||
Touch second = default;
|
||
for (int i = 0; i < Input.touchCount; i++)
|
||
{
|
||
Touch touch = Input.GetTouch(i);
|
||
if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled)
|
||
{
|
||
continue;
|
||
}
|
||
if (IsInJoystickArea(touch.position))
|
||
{
|
||
continue;
|
||
}
|
||
if (IsPointerOverUi(touch.fingerId))
|
||
{
|
||
continue;
|
||
}
|
||
if (cameraTouchCount == 0)
|
||
{
|
||
first = touch;
|
||
}
|
||
else if (cameraTouchCount == 1)
|
||
{
|
||
second = touch;
|
||
}
|
||
cameraTouchCount++;
|
||
}
|
||
|
||
if (cameraTouchCount >= 2)
|
||
{
|
||
cameraTouchDragging = false;
|
||
cameraTouchFingerId = -1;
|
||
float pinchDistance = Vector2.Distance(first.position, second.position);
|
||
if (cameraPinching)
|
||
{
|
||
cameraDistance -= (pinchDistance - lastPinchDistance) * CameraPinchZoomSensitivity;
|
||
}
|
||
cameraPinching = true;
|
||
lastPinchDistance = pinchDistance;
|
||
return;
|
||
}
|
||
|
||
cameraPinching = false;
|
||
if (cameraTouchCount == 0)
|
||
{
|
||
cameraTouchDragging = false;
|
||
cameraTouchFingerId = -1;
|
||
return;
|
||
}
|
||
|
||
Touch active = first;
|
||
if (active.phase == TouchPhase.Began)
|
||
{
|
||
if (IsPointerOverUi(active.fingerId))
|
||
{
|
||
cameraTouchDragging = false;
|
||
cameraTouchFingerId = -1;
|
||
return;
|
||
}
|
||
cameraTouchDragging = true;
|
||
cameraTouchFingerId = active.fingerId;
|
||
lastCameraPointerPosition = active.position;
|
||
return;
|
||
}
|
||
|
||
if (!cameraTouchDragging || active.fingerId != cameraTouchFingerId)
|
||
{
|
||
return;
|
||
}
|
||
|
||
Vector2 delta = active.position - lastCameraPointerPosition;
|
||
lastCameraPointerPosition = active.position;
|
||
ApplyCameraOrbit(delta, CameraTouchOrbitSensitivity);
|
||
}
|
||
|
||
private static bool IsPointerOverUi(int pointerId)
|
||
{
|
||
EventSystem eventSystem = EventSystem.current;
|
||
if (eventSystem == null)
|
||
{
|
||
return false;
|
||
}
|
||
return pointerId >= 0
|
||
? eventSystem.IsPointerOverGameObject(pointerId)
|
||
: eventSystem.IsPointerOverGameObject();
|
||
}
|
||
|
||
private void ApplyCameraOrbit(Vector2 delta, float sensitivity)
|
||
{
|
||
cameraYaw += delta.x * sensitivity;
|
||
cameraPitch -= delta.y * sensitivity;
|
||
}
|
||
|
||
// 摇杆所在画布的渲染相机:Overlay 用 null,ScreenSpaceCamera/WorldSpace 用画布的 worldCamera。
|
||
// 不能一律传 null —— 相机空间画布的世界坐标(带缩放/平面距离)用 null 换算会得到错误屏幕点。
|
||
private Camera GetUICamera()
|
||
{
|
||
RectTransform w = joystickWidget != null ? joystickWidget : joystickRoot;
|
||
if (w == null)
|
||
{
|
||
return null;
|
||
}
|
||
Canvas cv = w.GetComponentInParent<Canvas>();
|
||
if (cv == null)
|
||
{
|
||
return null;
|
||
}
|
||
cv = cv.rootCanvas;
|
||
return cv.renderMode == RenderMode.ScreenSpaceOverlay ? null : cv.worldCamera;
|
||
}
|
||
|
||
private Vector2 GetJoystickScreenCenter()
|
||
{
|
||
RectTransform w = joystickWidget != null ? joystickWidget : joystickRoot;
|
||
if (w != null)
|
||
{
|
||
return RectTransformUtility.WorldToScreenPoint(GetUICamera(), w.position);
|
||
}
|
||
return new Vector2(JoystickPadding + JoystickRadius, JoystickPadding + JoystickRadius);
|
||
}
|
||
|
||
private float GetJoystickScreenRadius()
|
||
{
|
||
RectTransform w = joystickWidget != null ? joystickWidget : joystickRoot;
|
||
if (w == null)
|
||
{
|
||
return JoystickRadius;
|
||
}
|
||
|
||
Camera cam = GetUICamera();
|
||
Vector3[] corners = new Vector3[4];
|
||
w.GetWorldCorners(corners);
|
||
// 世界角点先转屏幕,再算屏幕空间半径(相机空间画布世界单位很小,不能直接用世界差值)
|
||
Vector2 s0 = RectTransformUtility.WorldToScreenPoint(cam, corners[0]);
|
||
Vector2 s2 = RectTransformUtility.WorldToScreenPoint(cam, corners[2]);
|
||
float width = Mathf.Abs(s2.x - s0.x);
|
||
float height = Mathf.Abs(s2.y - s0.y);
|
||
return Mathf.Max(1f, Mathf.Min(width, height) * 0.5f);
|
||
}
|
||
|
||
private bool IsInJoystickArea(Vector2 screenPosition)
|
||
{
|
||
Vector2 center = GetJoystickScreenCenter();
|
||
float radius = GetJoystickScreenRadius();
|
||
return Vector2.Distance(screenPosition, center) <= radius * JoystickAcquireRadiusMultiplier;
|
||
}
|
||
|
||
private static int GetSelectedFigure()
|
||
{
|
||
string selected = PlayerPrefs.GetString(SelectedCharacterKey, string.Empty).ToLowerInvariant();
|
||
for (int i = 0; i < ActorPaths.Length; i++)
|
||
{
|
||
string path = ActorPaths[i].ToLowerInvariant();
|
||
int slash = path.LastIndexOf('/');
|
||
int dot = path.LastIndexOf('.');
|
||
string id = dot > slash ? path.Substring(slash + 1, dot - slash - 1) : path;
|
||
if (id == selected)
|
||
{
|
||
return i + 1;
|
||
}
|
||
}
|
||
return 1;
|
||
}
|
||
}
|
||
}
|