修了不少东西
This commit is contained in:
@@ -1289,7 +1289,7 @@ public class GameManager : MonoBehaviour
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
gNotice.recommendation.display(LocalizationService.Get("gameplay.notice.game_start", "游戏开始!"));
|
||||
// gNotice.recommendation.display(LocalizationService.Get("gameplay.notice.game_start", "游戏开始!"));
|
||||
|
||||
// Resume using PauseManager
|
||||
PauseManager.Instance?.Pause(false);
|
||||
@@ -1349,7 +1349,7 @@ public class GameManager : MonoBehaviour
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
gNotice.recommendation.display(LocalizationService.Get("gameplay.notice.game_start", "游戏开始!"));
|
||||
// gNotice.recommendation.display(LocalizationService.Get("gameplay.notice.game_start", "游戏开始!"));
|
||||
|
||||
// Resume
|
||||
PauseManager.Instance?.Pause(false);
|
||||
@@ -1441,7 +1441,7 @@ public class GameManager : MonoBehaviour
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
gNotice.recommendation.display(LocalizationService.Get("gameplay.notice.game_start", "游戏开始!"));
|
||||
// gNotice.recommendation.display(LocalizationService.Get("gameplay.notice.game_start", "游戏开始!"));
|
||||
|
||||
// Resume
|
||||
PauseManager.Instance?.Pause(false);
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
using UnityEngine.InputSystem;
|
||||
using UnityEngine.InputSystem.EnhancedTouch;
|
||||
using ETouch = UnityEngine.InputSystem.EnhancedTouch.Touch;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 手游触屏输入分发器:每帧遍历所有触摸点(及编辑器鼠标),用 Physics2D.OverlapPoint
|
||||
/// 命中带 TrackTouchZone 的透明 Collider2D,把按下/抬起转发给 InputManager。
|
||||
///
|
||||
/// 为什么用这种方式而不是 UI 的 IPointerDownHandler:
|
||||
/// 【多点触控根因】项目 activeInputHandler = Both(同时启用新旧输入系统)。
|
||||
/// 在这种配置下,旧版 UnityEngine.Input.touches 走的是兼容 shim,同时按多指时
|
||||
/// 常常只上报一个触点 → 只能触发一个轨道。所以这里优先用新输入系统的
|
||||
/// EnhancedTouch(Touch.activeTouches),它能可靠上报所有并发手指;
|
||||
/// 仅在没有新输入系统时回退到旧版 Input.touches。判定逻辑完全不变。
|
||||
///
|
||||
/// 为什么用物理命中而不是 UI 的 IPointerDownHandler:
|
||||
/// - 轨道会平移+旋转晃动,点击区必须跟随晃动的 Collider(OverlapPoint 支持旋转过的碰撞体)。
|
||||
/// - 需要多点触控:每根手指独立按住各自的轨道(靠 fingerId 配对按下/抬起)。
|
||||
/// - 需要多点触控:每根手指独立按住各自的轨道(靠 touchId 配对按下/抬起)。
|
||||
///
|
||||
/// 判定逻辑完全不变:这里只调用现成的 PressTrack/ReleaseTrack,
|
||||
/// 判定时间戳仍由 Note.HandlePress 读取 GameplayClock.NowSongTime(dspTime)。
|
||||
///
|
||||
/// 用法:场景里放一个空物体挂本脚本,把主相机拖到 gameplayCamera(留空则自动取 Camera.main)。
|
||||
@@ -35,10 +45,8 @@ public class TrackTouchInput : MonoBehaviour
|
||||
"留空自动按相机 orthographic 判断;一般不用手动改。")]
|
||||
[SerializeField] private bool forceOrthographicMode = false;
|
||||
|
||||
// fingerId -> 当前按住的轨道索引。用于在抬起/取消时精确释放对应轨道。
|
||||
// 触点 id -> 当前按住的轨道索引。用于在抬起/取消时精确释放对应轨道。
|
||||
private readonly Dictionary<int, int> _activeTouches = new Dictionary<int, int>();
|
||||
// 鼠标模拟用的"手指 id",取一个不会和真实 fingerId 冲突的值。
|
||||
private const int MouseFingerId = -100;
|
||||
private int _mouseHeldTrack = -1;
|
||||
|
||||
private Camera Cam
|
||||
@@ -50,14 +58,13 @@ public class TrackTouchInput : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
private void OnEnable()
|
||||
{
|
||||
// 【多点触控根因修复】若 multiTouchEnabled 为 false,Input.touchCount 永远 ≤1,
|
||||
// 同时按多条轨道时只会报告一个触点 → 只能触发一个轨道。强制开启多点触控。
|
||||
// simulateMouseWithTouches 关掉:避免触摸被合成成鼠标事件,与真实多指冲突。
|
||||
Input.multiTouchEnabled = true;
|
||||
Input.simulateMouseWithTouches = false;
|
||||
// 开启增强触摸,Touch.activeTouches 才会被填充。
|
||||
EnhancedTouchSupport.Enable();
|
||||
}
|
||||
#endif
|
||||
|
||||
private void Update()
|
||||
{
|
||||
@@ -67,50 +74,101 @@ public class TrackTouchInput : MonoBehaviour
|
||||
Camera cam = Cam;
|
||||
if (cam == null) return;
|
||||
|
||||
ProcessTouches(im, cam);
|
||||
int touchCount = ProcessTouches(im, cam);
|
||||
|
||||
if (enableMouseFallback && Input.touchCount == 0)
|
||||
if (enableMouseFallback && touchCount == 0)
|
||||
{
|
||||
ProcessMouse(im, cam);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessTouches(InputManager im, Camera cam)
|
||||
/// <summary>处理所有并发触点,返回本帧触点数量。</summary>
|
||||
private int ProcessTouches(InputManager im, Camera cam)
|
||||
{
|
||||
for (int i = 0; i < Input.touchCount; i++)
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
// 优先走新输入系统的增强触摸:可靠上报所有并发手指。
|
||||
var touches = ETouch.activeTouches;
|
||||
int count = touches.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ETouch t = touches[i];
|
||||
switch (t.phase)
|
||||
{
|
||||
case UnityEngine.InputSystem.TouchPhase.Began:
|
||||
HandleTouchBegan(im, cam, t.touchId, t.screenPosition);
|
||||
break;
|
||||
case UnityEngine.InputSystem.TouchPhase.Ended:
|
||||
case UnityEngine.InputSystem.TouchPhase.Canceled:
|
||||
HandleTouchEnded(im, t.touchId);
|
||||
break;
|
||||
// Moved / Stationary:手指在轨道内保持按住即可,不重新判定归属。
|
||||
}
|
||||
}
|
||||
return count;
|
||||
#else
|
||||
// 回退:旧版 Input.touches(仅在未启用新输入系统时使用)。
|
||||
int count = Input.touchCount;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Touch t = Input.GetTouch(i);
|
||||
|
||||
switch (t.phase)
|
||||
{
|
||||
case TouchPhase.Began:
|
||||
{
|
||||
int track = ResolveTrack(cam, t.position);
|
||||
if (track >= 0)
|
||||
{
|
||||
_activeTouches[t.fingerId] = track;
|
||||
im.PressTrack(track);
|
||||
}
|
||||
HandleTouchBegan(im, cam, t.fingerId, t.position);
|
||||
break;
|
||||
}
|
||||
case TouchPhase.Ended:
|
||||
case TouchPhase.Canceled:
|
||||
{
|
||||
if (_activeTouches.TryGetValue(t.fingerId, out int track))
|
||||
{
|
||||
_activeTouches.Remove(t.fingerId);
|
||||
im.ReleaseTrack(track);
|
||||
}
|
||||
HandleTouchEnded(im, t.fingerId);
|
||||
break;
|
||||
}
|
||||
// Moved / Stationary:手指在轨道内保持按住即可,不重新判定归属,
|
||||
// 避免手指轻微滑动跨到相邻轨道时反复 Press/Release 造成误判。
|
||||
}
|
||||
}
|
||||
return count;
|
||||
#endif
|
||||
}
|
||||
|
||||
private void HandleTouchBegan(InputManager im, Camera cam, int touchId, Vector2 screenPos)
|
||||
{
|
||||
int track = ResolveTrack(cam, screenPos);
|
||||
if (track >= 0)
|
||||
{
|
||||
_activeTouches[touchId] = track;
|
||||
im.PressTrack(track);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleTouchEnded(InputManager im, int touchId)
|
||||
{
|
||||
if (_activeTouches.TryGetValue(touchId, out int track))
|
||||
{
|
||||
_activeTouches.Remove(touchId);
|
||||
im.ReleaseTrack(track);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessMouse(InputManager im, Camera cam)
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
var mouse = Mouse.current;
|
||||
if (mouse == null) return;
|
||||
|
||||
if (mouse.leftButton.wasPressedThisFrame)
|
||||
{
|
||||
int track = ResolveTrack(cam, mouse.position.ReadValue());
|
||||
if (track >= 0)
|
||||
{
|
||||
_mouseHeldTrack = track;
|
||||
im.PressTrack(track);
|
||||
}
|
||||
}
|
||||
else if (mouse.leftButton.wasReleasedThisFrame)
|
||||
{
|
||||
if (_mouseHeldTrack >= 0)
|
||||
{
|
||||
im.ReleaseTrack(_mouseHeldTrack);
|
||||
_mouseHeldTrack = -1;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (Input.GetMouseButtonDown(0))
|
||||
{
|
||||
int track = ResolveTrack(cam, Input.mousePosition);
|
||||
@@ -128,6 +186,7 @@ public class TrackTouchInput : MonoBehaviour
|
||||
_mouseHeldTrack = -1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -135,8 +194,7 @@ public class TrackTouchInput : MonoBehaviour
|
||||
///
|
||||
/// 相机移动/晃动/推拉时,每帧都用当前相机状态转换,所以命中自动跟随相机。
|
||||
/// - 正交相机:ScreenToWorldPoint 忽略深度,直接取 x/y。
|
||||
/// - 透视相机:从相机发射线,求与轨道平面(Z = trackPlaneWorldZ)的交点,
|
||||
/// 避免因相机 Z 变化(如 cameraDash)导致命中点偏移。
|
||||
/// - 透视相机:从相机发射线,求与轨道平面(Z = trackPlaneWorldZ)的交点。
|
||||
/// </summary>
|
||||
private int ResolveTrack(Camera cam, Vector2 screenPos)
|
||||
{
|
||||
@@ -149,7 +207,6 @@ public class TrackTouchInput : MonoBehaviour
|
||||
}
|
||||
else
|
||||
{
|
||||
// 透视:射线与轨道平面(法线 +Z,过 z = trackPlaneWorldZ)求交。
|
||||
Ray ray = cam.ScreenPointToRay(screenPos);
|
||||
float denom = ray.direction.z;
|
||||
if (Mathf.Abs(denom) < 1e-6f)
|
||||
@@ -187,5 +244,9 @@ public class TrackTouchInput : MonoBehaviour
|
||||
}
|
||||
_activeTouches.Clear();
|
||||
_mouseHeldTrack = -1;
|
||||
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
EnhancedTouchSupport.Disable();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,30 +201,51 @@ namespace GameServer.Client
|
||||
|
||||
public async Task<ArenaRoomSnapshot> CreateRoom(string songId, string songName, string difficulty, string password = null)
|
||||
{
|
||||
await EnsureConnected();
|
||||
JObject resp = await SendAction("create_room", new
|
||||
try
|
||||
{
|
||||
song_id = songId,
|
||||
song_name = songName,
|
||||
difficulty = difficulty,
|
||||
password = string.IsNullOrWhiteSpace(password) ? null : password
|
||||
});
|
||||
ArenaRoomSnapshot snapshot = NormalizeSnapshotFromActionData("create_room", resp);
|
||||
SetCurrentRoom(snapshot);
|
||||
return snapshot;
|
||||
await EnsureConnected();
|
||||
JObject resp = await SendAction("create_room", new
|
||||
{
|
||||
song_id = songId,
|
||||
song_name = songName,
|
||||
difficulty = difficulty,
|
||||
password = string.IsNullOrWhiteSpace(password) ? null : password
|
||||
});
|
||||
ArenaRoomSnapshot snapshot = NormalizeSnapshotFromActionData("create_room", resp);
|
||||
SetCurrentRoom(snapshot);
|
||||
return snapshot;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NetworkManager.ShowUserFacingNetworkError(ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ArenaRoomSnapshot> JoinRoom(string roomCode, string password = null)
|
||||
{
|
||||
await EnsureConnected();
|
||||
JObject resp = await SendAction("join_room", new
|
||||
try
|
||||
{
|
||||
room_code = roomCode,
|
||||
password = string.IsNullOrWhiteSpace(password) ? null : password
|
||||
});
|
||||
ArenaRoomSnapshot snapshot = NormalizeSnapshotFromActionData("join_room", resp);
|
||||
SetCurrentRoom(snapshot);
|
||||
return snapshot;
|
||||
if (string.IsNullOrWhiteSpace(roomCode))
|
||||
{
|
||||
throw new Exception("MISSING_ROOM_CODE: room_code is empty");
|
||||
}
|
||||
|
||||
await EnsureConnected();
|
||||
JObject resp = await SendAction("join_room", new
|
||||
{
|
||||
room_code = roomCode.Trim(),
|
||||
password = string.IsNullOrWhiteSpace(password) ? null : password
|
||||
});
|
||||
ArenaRoomSnapshot snapshot = NormalizeSnapshotFromActionData("join_room", resp);
|
||||
SetCurrentRoom(snapshot);
|
||||
return snapshot;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NetworkManager.ShowUserFacingNetworkError(ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> LeaveRoom()
|
||||
@@ -385,6 +406,31 @@ namespace GameServer.Client
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
public async Task<ArenaRoomSnapshot> ChangeRoomSong(string songId, string songName, string difficulty)
|
||||
{
|
||||
try
|
||||
{
|
||||
await EnsureConnected();
|
||||
JObject resp = await SendAction("change_room_song", new
|
||||
{
|
||||
song_id = songId,
|
||||
song_name = songName,
|
||||
difficulty = difficulty
|
||||
});
|
||||
ArenaRoomSnapshot snapshot = NormalizeSnapshotFromActionData("change_room_song", resp);
|
||||
if (snapshot != null)
|
||||
{
|
||||
SetCurrentRoom(snapshot);
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NetworkManager.ShowUserFacingNetworkError(ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ArenaRoomSnapshot> StartGame()
|
||||
{
|
||||
await EnsureConnected();
|
||||
@@ -804,11 +850,11 @@ namespace GameServer.Client
|
||||
NetworkManager network = NetworkManager.Instance;
|
||||
if (network == null)
|
||||
{
|
||||
throw new Exception("NetworkManager is not initialized");
|
||||
throw new Exception("NETWORK_NOT_READY: NetworkManager is not initialized");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(network.SteamId))
|
||||
{
|
||||
throw new Exception("steam_id is empty");
|
||||
throw new Exception("MISSING_STEAM_ID: steam_id is empty");
|
||||
}
|
||||
|
||||
_isConnecting = true;
|
||||
@@ -864,11 +910,11 @@ namespace GameServer.Client
|
||||
NetworkManager network = NetworkManager.Instance;
|
||||
if (network == null)
|
||||
{
|
||||
throw new Exception("NetworkManager is not initialized");
|
||||
throw new Exception("NETWORK_NOT_READY: NetworkManager is not initialized");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(network.SteamId))
|
||||
{
|
||||
throw new Exception("steam_id is empty");
|
||||
throw new Exception("MISSING_STEAM_ID: steam_id is empty");
|
||||
}
|
||||
|
||||
_isSocialConnecting = true;
|
||||
@@ -1213,6 +1259,15 @@ namespace GameServer.Client
|
||||
}
|
||||
return;
|
||||
}
|
||||
case "room_song_changed":
|
||||
{
|
||||
ArenaRoomSnapshot snapshot = ReadRoomSnapshot(message);
|
||||
if (snapshot != null)
|
||||
{
|
||||
Enqueue(() => SetCurrentRoom(snapshot));
|
||||
}
|
||||
return;
|
||||
}
|
||||
case "room_dismissed":
|
||||
{
|
||||
string roomCode = ReadString(message, "room_code");
|
||||
@@ -1734,13 +1789,19 @@ namespace GameServer.Client
|
||||
{
|
||||
_shutdownLeaveAttempted = false;
|
||||
string previousRoomCode = CurrentRoom != null ? CurrentRoom.room_code : null;
|
||||
string previousSongId = CurrentRoom != null ? CurrentRoom.song_id : null;
|
||||
string previousDifficulty = CurrentRoom != null ? CurrentRoom.difficulty : null;
|
||||
if (snapshot == null || !IsRoomStartedState(snapshot.status))
|
||||
{
|
||||
_isLaunchingArenaGameplay = false;
|
||||
}
|
||||
|
||||
if (snapshot == null || (!string.IsNullOrWhiteSpace(previousRoomCode)
|
||||
&& !string.Equals(previousRoomCode, snapshot.room_code, StringComparison.Ordinal)))
|
||||
&& !string.Equals(previousRoomCode, snapshot.room_code, StringComparison.Ordinal))
|
||||
|| (snapshot != null
|
||||
&& string.Equals(previousRoomCode, snapshot.room_code, StringComparison.Ordinal)
|
||||
&& (!string.Equals(previousSongId, snapshot.song_id, StringComparison.Ordinal)
|
||||
|| !string.Equals(previousDifficulty, snapshot.difficulty, StringComparison.OrdinalIgnoreCase))))
|
||||
{
|
||||
ClearRoomRankingScores();
|
||||
}
|
||||
@@ -2353,7 +2414,7 @@ namespace GameServer.Client
|
||||
{
|
||||
string finalCode = code ?? "ERROR";
|
||||
string finalMessage = message ?? "Unknown arena error";
|
||||
gNotice.error.display($"{finalCode}: {finalMessage}");
|
||||
gNotice.error.display(NetworkManager.DescribeUserFacingNetworkError($"{finalCode}: {finalMessage}"));
|
||||
OnErrorReceived?.Invoke(finalCode, finalMessage);
|
||||
});
|
||||
}
|
||||
@@ -3890,4 +3951,3 @@ namespace GameServer.Client
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -337,6 +337,7 @@ namespace GameServer.Client
|
||||
[JsonProperty("receiver_id")] public string receiver_id;
|
||||
[JsonProperty("uid")] public int uid;
|
||||
[JsonProperty("display_name")] public string display_name;
|
||||
[JsonProperty("player_title")] public string player_title;
|
||||
[JsonProperty("avatar_url")] public string avatar_url;
|
||||
[JsonProperty("content")] public string content;
|
||||
[JsonProperty("created_at")] public string created_at;
|
||||
|
||||
@@ -21,6 +21,8 @@ public class NetworkManager : MonoBehaviour
|
||||
public static NetworkManager Instance { get; private set; }
|
||||
private static string _startupSteamId = string.Empty;
|
||||
private static string _startupSteamDisplayName = string.Empty;
|
||||
private static string _lastUserFacingNetworkError = string.Empty;
|
||||
private static float _lastUserFacingNetworkErrorAt;
|
||||
private static readonly Dictionary<string, CachedRemoteIdentity> _remoteIdentityCache =
|
||||
new Dictionary<string, CachedRemoteIdentity>(StringComparer.Ordinal);
|
||||
private static readonly Dictionary<string, Task<ProfileData>> _remoteProfileTasks =
|
||||
@@ -99,6 +101,8 @@ public class NetworkManager : MonoBehaviour
|
||||
Instance = null;
|
||||
_startupSteamId = string.Empty;
|
||||
_startupSteamDisplayName = string.Empty;
|
||||
_lastUserFacingNetworkError = string.Empty;
|
||||
_lastUserFacingNetworkErrorAt = 0f;
|
||||
_remoteIdentityCache.Clear();
|
||||
_remoteProfileTasks.Clear();
|
||||
}
|
||||
@@ -627,6 +631,7 @@ public class NetworkManager : MonoBehaviour
|
||||
{
|
||||
if (OnlineModeSettings.IsLocalOnlyMode)
|
||||
{
|
||||
NotifyNetworkError("LOCAL_ONLY_MODE");
|
||||
return new ArenaCreateResponse
|
||||
{
|
||||
success = false,
|
||||
@@ -635,6 +640,7 @@ public class NetworkManager : MonoBehaviour
|
||||
};
|
||||
}
|
||||
|
||||
EnsureSteamIdAvailableForOnlineFeature();
|
||||
var req = new ArenaCreateRequest
|
||||
{
|
||||
steam_id = SteamId,
|
||||
@@ -643,6 +649,13 @@ public class NetworkManager : MonoBehaviour
|
||||
password = password
|
||||
};
|
||||
ArenaCreateResponse resp = await PostJson<ArenaCreateResponse>(BuildApiUrl("/api/arena/create"), req, CancellationToken.None);
|
||||
if (resp == null || !resp.success)
|
||||
{
|
||||
string error = BuildErrorText(resp?.error_code, resp?.message, "CREATE_ROOM_FAILED");
|
||||
NotifyNetworkError(error);
|
||||
throw new Exception(error);
|
||||
}
|
||||
|
||||
OnArenaCreated?.Invoke(resp);
|
||||
return resp;
|
||||
}
|
||||
@@ -651,6 +664,7 @@ public class NetworkManager : MonoBehaviour
|
||||
{
|
||||
if (OnlineModeSettings.IsLocalOnlyMode)
|
||||
{
|
||||
NotifyNetworkError("LOCAL_ONLY_MODE");
|
||||
return new ArenaJoinResponse
|
||||
{
|
||||
success = false,
|
||||
@@ -660,6 +674,7 @@ public class NetworkManager : MonoBehaviour
|
||||
};
|
||||
}
|
||||
|
||||
EnsureSteamIdAvailableForOnlineFeature();
|
||||
var req = new ArenaJoinRequest
|
||||
{
|
||||
steam_id = SteamId,
|
||||
@@ -667,6 +682,13 @@ public class NetworkManager : MonoBehaviour
|
||||
password = password
|
||||
};
|
||||
ArenaJoinResponse resp = await PostJson<ArenaJoinResponse>(BuildApiUrl("/api/arena/join"), req, CancellationToken.None);
|
||||
if (resp == null || !resp.success)
|
||||
{
|
||||
string error = BuildErrorText(resp?.error_code, resp?.message, "JOIN_ROOM_FAILED");
|
||||
NotifyNetworkError(error);
|
||||
throw new Exception(error);
|
||||
}
|
||||
|
||||
OnArenaJoined?.Invoke(resp);
|
||||
return resp;
|
||||
}
|
||||
@@ -1682,6 +1704,15 @@ public class NetworkManager : MonoBehaviour
|
||||
return new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
private void EnsureSteamIdAvailableForOnlineFeature()
|
||||
{
|
||||
RefreshSteamIdentity(true);
|
||||
if (string.IsNullOrWhiteSpace(SteamId))
|
||||
{
|
||||
throw new InvalidOperationException("MISSING_STEAM_ID: steam_id is empty");
|
||||
}
|
||||
}
|
||||
|
||||
private ProfileData BuildLocalOnlyProfile(string targetSteamId)
|
||||
{
|
||||
string normalizedSteamId = string.IsNullOrWhiteSpace(targetSteamId) ? string.Empty : targetSteamId.Trim();
|
||||
@@ -1850,11 +1881,135 @@ public class NetworkManager : MonoBehaviour
|
||||
|
||||
private static void NotifyNetworkError(string message)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(message))
|
||||
ShowUserFacingNetworkError(message);
|
||||
}
|
||||
|
||||
public static void ShowUserFacingNetworkError(string message)
|
||||
{
|
||||
string userMessage = DescribeUserFacingNetworkError(message);
|
||||
if (!string.IsNullOrWhiteSpace(userMessage))
|
||||
{
|
||||
gNotice.error.display(message);
|
||||
float now = Time.realtimeSinceStartup;
|
||||
if (string.Equals(_lastUserFacingNetworkError, userMessage, StringComparison.Ordinal)
|
||||
&& now - _lastUserFacingNetworkErrorAt < 0.75f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lastUserFacingNetworkError = userMessage;
|
||||
_lastUserFacingNetworkErrorAt = now;
|
||||
gNotice.error.display(userMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public static string DescribeUserFacingNetworkError(string message)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
return "网络错误,请稍后重试";
|
||||
}
|
||||
|
||||
string raw = message.Trim();
|
||||
string upper = raw.ToUpperInvariant();
|
||||
|
||||
if (upper.Contains("LOCAL_ONLY_MODE") || raw.IndexOf("local-only mode", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
return "当前为离线模式,无法使用联网功能";
|
||||
}
|
||||
|
||||
if (upper.Contains("MISSING_STEAM_ID")
|
||||
|| upper.Contains("STEAM_ID IS EMPTY")
|
||||
|| upper.Contains("TARGETSTEAMID IS EMPTY")
|
||||
|| upper.Contains("STEAMID IS EMPTY")
|
||||
|| upper.Contains("STEAM ID IS EMPTY"))
|
||||
{
|
||||
return "无法得到 SteamID,请确认已启动 Steam、登录账号并保持在线";
|
||||
}
|
||||
|
||||
if (upper.Contains("NETWORKMANAGER IS NOT INITIALIZED") || upper.Contains("NETWORK_NOT_READY"))
|
||||
{
|
||||
return "网络模块尚未初始化,请稍后重试";
|
||||
}
|
||||
|
||||
if (upper.Contains("ROOM_NOT_FOUND")
|
||||
|| raw.IndexOf("room not found", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| raw.IndexOf("房间不存在", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
return "房间不存在或已解散";
|
||||
}
|
||||
|
||||
if (upper.Contains("ALREADY_IN_ROOM"))
|
||||
{
|
||||
return "你已经在房间中";
|
||||
}
|
||||
|
||||
if (upper.Contains("MISSING_ROOM_CODE") || upper.Contains("ROOM_CODE IS EMPTY"))
|
||||
{
|
||||
return "房间号不能为空";
|
||||
}
|
||||
|
||||
if (upper.Contains("ROOM_FULL"))
|
||||
{
|
||||
return "房间已满";
|
||||
}
|
||||
|
||||
if (upper.Contains("ROOM_STARTED") || upper.Contains("GAME_STARTED") || upper.Contains("ALREADY_STARTED"))
|
||||
{
|
||||
return "房间已开始游戏,无法加入";
|
||||
}
|
||||
|
||||
if (upper.Contains("WRONG_PASSWORD") || upper.Contains("INVALID_PASSWORD") || upper.Contains("PASSWORD"))
|
||||
{
|
||||
return "房间密码错误";
|
||||
}
|
||||
|
||||
if (upper.Contains("NOT_IN_ROOM"))
|
||||
{
|
||||
return "当前不在房间中";
|
||||
}
|
||||
|
||||
if (upper.Contains("TIMED OUT")
|
||||
|| upper.Contains("TIMEOUT")
|
||||
|| upper.Contains("CONNECTFAILURE")
|
||||
|| upper.Contains("CONNECTIONERROR")
|
||||
|| upper.Contains("CONNECTION FAILED")
|
||||
|| upper.Contains("CANNOT CONNECT")
|
||||
|| upper.Contains("COULD NOT RESOLVE")
|
||||
|| upper.Contains("RESOLVE DESTINATION HOST")
|
||||
|| upper.Contains("NAMERESOLUTION")
|
||||
|| upper.Contains("HOST UNREACHABLE")
|
||||
|| upper.Contains("CONNECTION REFUSED")
|
||||
|| upper.Contains("CONNECTION RESET")
|
||||
|| upper.Contains("SOCKET")
|
||||
|| raw.IndexOf("unable to connect", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
|| raw.IndexOf("failed to connect", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
return "未联网或服务器连接失败,请检查网络后重试";
|
||||
}
|
||||
|
||||
if (upper.Contains("HTTP") && (upper.Contains(" 404") || upper.Contains("404:")))
|
||||
{
|
||||
return "请求的服务不存在,请稍后重试";
|
||||
}
|
||||
|
||||
if (upper.Contains("HTTP") && (upper.Contains(" 500") || upper.Contains("500:")))
|
||||
{
|
||||
return "服务器暂时无法处理请求,请稍后重试";
|
||||
}
|
||||
|
||||
if (raw.Length > 80)
|
||||
{
|
||||
return "网络错误,请稍后重试";
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
private static string BuildErrorText(string code, string message, string fallbackCode)
|
||||
{
|
||||
string safeCode = string.IsNullOrWhiteSpace(code) ? fallbackCode : code.Trim();
|
||||
string safeMessage = string.IsNullOrWhiteSpace(message) ? safeCode : message.Trim();
|
||||
return $"{safeCode}: {safeMessage}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -403,10 +403,10 @@ MonoBehaviour:
|
||||
rkPrefab: {fileID: 4439332187331332072, guid: 5d56483f68516d94e9277bbac63f8525, type: 3}
|
||||
rkParent: {fileID: 4242620377161803188}
|
||||
btmSprites:
|
||||
- {fileID: 21300000, guid: 040be0e474e09894ca320b08d9462cc8, type: 3}
|
||||
- {fileID: 21300000, guid: 60cfd84ae231a4943b3f0f6cf0dcf409, type: 3}
|
||||
- {fileID: 21300000, guid: 1258ac417dd158a48b05eeedb739c469, type: 3}
|
||||
- {fileID: 21300000, guid: 28c51f93b6260224c867f45f411a0e85, type: 3}
|
||||
- {fileID: 21300000, guid: 9e6a75c290497c4469b9b86d5eab72a1, type: 3}
|
||||
- {fileID: 21300000, guid: 9e6a75c290497c4469b9b86d5eab72a1, type: 3}
|
||||
- {fileID: 21300000, guid: 9e6a75c290497c4469b9b86d5eab72a1, type: 3}
|
||||
- {fileID: 21300000, guid: 7f3e2cfd199d3a844b15751f1eddf15b, type: 3}
|
||||
--- !u!1 &2889696334540093026
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -140,7 +140,7 @@ MonoBehaviour:
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: 28c51f93b6260224c867f45f411a0e85, type: 3}
|
||||
m_Sprite: {fileID: 21300000, guid: 9e6a75c290497c4469b9b86d5eab72a1, type: 3}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
@@ -208,7 +208,7 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_Color: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
@@ -287,7 +287,7 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_Color: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
@@ -422,7 +422,7 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_Color: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
@@ -501,7 +501,7 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_Color: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
@@ -589,7 +589,7 @@ MonoBehaviour:
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: 28c51f93b6260224c867f45f411a0e85, type: 3}
|
||||
m_Sprite: {fileID: 21300000, guid: 9e6a75c290497c4469b9b86d5eab72a1, type: 3}
|
||||
m_Type: 1
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
@@ -657,7 +657,7 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_Color: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
|
||||
@@ -58,7 +58,7 @@ public class rewardPrefab : MonoBehaviour
|
||||
switch (rewardType)
|
||||
{
|
||||
case RewardVisualType.Coins:
|
||||
return "金币";
|
||||
return "算力";
|
||||
case RewardVisualType.Material:
|
||||
return "记忆碎片";
|
||||
case RewardVisualType.PlayerExp:
|
||||
|
||||
Reference in New Issue
Block a user