Bug Fixes

This commit is contained in:
2026-07-03 17:44:41 +03:00
parent 3d423974eb
commit 26b896c39d
8 changed files with 2006 additions and 119 deletions
+108
View File
@@ -6843,6 +6843,113 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1429067986}
m_CullTransparentMesh: 1
--- !u!1 &1447142351
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1447142354}
- component: {fileID: 1447142353}
- component: {fileID: 1447142352}
m_Layer: 0
m_Name: NetworkManager
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &1447142352
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1447142351}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6960e84d07fb87f47956e7a81d71c4e6, type: 3}
m_Name:
m_EditorClassIdentifier:
m_ProtocolType: 0
m_UseWebSockets: 0
m_UseEncryption: 0
m_MaxPacketQueueSize: 128
m_MaxPayloadSize: 6144
m_HeartbeatTimeoutMS: 500
m_ConnectTimeoutMS: 1000
m_MaxConnectAttempts: 60
m_DisconnectTimeoutMS: 30000
ConnectionData:
Address: 127.0.0.1
Port: 7777
WebSocketPath: /
ServerListenAddress:
ClientBindPort: 0
DebugSimulator:
PacketDelayMS: 0
PacketJitterMS: 0
PacketDropRate: 0
--- !u!114 &1447142353
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1447142351}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3}
m_Name:
m_EditorClassIdentifier:
NetworkManagerExpanded: 0
NetworkConfig:
ProtocolVersion: 0
NetworkTransport: {fileID: 1447142352}
PlayerPrefab: {fileID: 0}
Prefabs:
NetworkPrefabsLists:
- {fileID: 11400000, guid: f158307e9cf5aba428bc18f230e15ec4, type: 2}
TickRate: 30
ClientConnectionBufferTimeout: 10
ConnectionApproval: 0
ConnectionData:
EnableTimeResync: 0
TimeResyncInterval: 30
EnsureNetworkVariableLengthSafety: 0
EnableSceneManagement: 1
ForceSamePrefabs: 1
RecycleNetworkIds: 1
NetworkIdRecycleDelay: 120
RpcHashSize: 0
LoadSceneTimeOut: 120
SpawnTimeout: 10
EnableNetworkLogs: 1
NetworkTopology: 0
UseCMBService: 0
AutoSpawnPlayerPrefabClientSide: 1
NetworkMessageMetrics: 1
NetworkProfilingMetrics: 1
OldPrefabList: []
RunInBackground: 1
LogLevel: 1
--- !u!4 &1447142354
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1447142351}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!64 &1461559516
MeshCollider:
m_ObjectHideFlags: 0
@@ -8201,3 +8308,4 @@ SceneRoots:
- {fileID: 13746291}
- {fileID: 1383824589}
- {fileID: 1852104040}
- {fileID: 1447142354}
File diff suppressed because it is too large Load Diff
@@ -38,6 +38,11 @@ public class GhostRoundManager : MonoBehaviour
{
if (!IsServer) return;
// Убираем клонов прошлого раунда (их специально оставили висеть на экране победы)
foreach (GhostPlayer _ghost in activeGhosts)
if (_ghost != null && _ghost.IsSpawned)
_ghost.NetworkObject.Despawn();
roundTick = 0;
activeGhosts.Clear();
@@ -48,6 +53,8 @@ public class GhostRoundManager : MonoBehaviour
{
if (_recording.positions.Count == 0) continue;
Debug.Log($"[GHOST] spawn clone owner={_recording.ownerClientId} at positions[0]={_recording.positions[0]} ticks={_recording.positions.Count} last={_recording.positions[_recording.positions.Count - 1]}");
GameObject _go = Instantiate(ghostPrefab, _recording.positions[0], _recording.rotations[0]);
_go.GetComponent<NetworkObject>().Spawn();
@@ -93,13 +100,8 @@ public class GhostRoundManager : MonoBehaviour
if (!IsServer || !roundRunning) return;
roundRunning = false;
// Убираем клонов со сцены
foreach (GhostPlayer _ghost in activeGhosts)
{
if (_ghost != null && _ghost.IsSpawned)
_ghost.NetworkObject.Despawn();
}
activeGhosts.Clear();
// Клонов НЕ убираем здесь — пусть остаются (замирают, т.к. roundRunning=false)
// на экране победы. Деспавн происходит в начале ServerStartRound перед спавном новых.
// Останавливаем запись и складываем раунд в архив
foreach (NetworkClient _client in NetworkManager.Singleton.ConnectedClientsList)
@@ -115,6 +117,12 @@ public class GhostRoundManager : MonoBehaviour
archive[_client.ClientId] = _list;
}
GhostRecording _rec = _recorder.Recording;
if (_rec.positions.Count > 0)
Debug.Log($"[GHOST] archive owner={_client.ClientId} ticks={_rec.positions.Count} first={_rec.positions[0]} last={_rec.positions[_rec.positions.Count - 1]}");
else
Debug.LogWarning($"[GHOST] archive owner={_client.ClientId} EMPTY recording (0 ticks)");
_list.Add(_recorder.Recording);
if (_list.Count > maxGhostsPerPlayer) _list.RemoveAt(0);
}
@@ -531,6 +531,7 @@ MonoBehaviour:
MinSecondsBetweenUpdates: 0
MaxSecondsBetweenUpdates: 0
m_InternalValue: 0
pitImmunityDuration: 1
--- !u!136 &5519469453299969230
CapsuleCollider:
m_ObjectHideFlags: 0
@@ -1093,9 +1094,6 @@ GameObject:
serializedVersion: 6
m_Component:
- component: {fileID: 2142486437948056408}
- component: {fileID: 358431225805014765}
- component: {fileID: 4782745331580793913}
- component: {fileID: 3678077479963786265}
m_Layer: 0
m_Name: Camera
m_TagString: Untagged
@@ -1120,109 +1118,6 @@ Transform:
- {fileID: 3553217504045761826}
m_Father: {fileID: 2264751121530880260}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!20 &358431225805014765
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5009018705790251557}
m_Enabled: 0
serializedVersion: 2
m_ClearFlags: 1
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
m_projectionMatrixMode: 1
m_GateFitMode: 2
m_FOVAxisMode: 0
m_Iso: 200
m_ShutterSpeed: 0.005
m_Aperture: 16
m_FocusDistance: 10
m_FocalLength: 50
m_BladeCount: 5
m_Curvature: {x: 2, y: 11}
m_BarrelClipping: 0.25
m_Anamorphism: 0
m_SensorSize: {x: 36, y: 24}
m_LensShift: {x: 0, y: 0}
m_NormalizedViewPortRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
near clip plane: 0.1
far clip plane: 1000
field of view: 60
orthographic: 0
orthographic size: 5
m_Depth: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 439
m_RenderingPath: -1
m_TargetTexture: {fileID: 0}
m_TargetDisplay: 0
m_TargetEye: 3
m_HDR: 1
m_AllowMSAA: 1
m_AllowDynamicResolution: 0
m_ForceIntoRT: 0
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
--- !u!81 &4782745331580793913
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5009018705790251557}
m_Enabled: 0
--- !u!114 &3678077479963786265
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5009018705790251557}
m_Enabled: 0
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3}
m_Name:
m_EditorClassIdentifier:
m_RenderShadows: 1
m_RequiresDepthTextureOption: 2
m_RequiresOpaqueTextureOption: 2
m_CameraType: 0
m_Cameras: []
m_RendererIndex: -1
m_VolumeLayerMask:
serializedVersion: 2
m_Bits: 1
m_VolumeTrigger: {fileID: 0}
m_VolumeFrameworkUpdateModeOption: 2
m_RenderPostProcessing: 1
m_Antialiasing: 3
m_AntialiasingQuality: 2
m_StopNaN: 1
m_Dithering: 1
m_ClearDepth: 1
m_AllowXRRendering: 1
m_AllowHDROutput: 1
m_UseScreenCoordOverride: 0
m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0}
m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0}
m_RequiresDepthTexture: 0
m_RequiresColorTexture: 0
m_Version: 2
m_TaaSettings:
m_Quality: 3
m_FrameInfluence: 0.1
m_JitterScale: 1
m_MipBias: 0
m_VarianceClampScale: 0.9
m_ContrastAdaptiveSharpening: 0
--- !u!1 &5793395671431581970
GameObject:
m_ObjectHideFlags: 0
@@ -19,6 +19,7 @@ public class PitZone : NetworkBehaviour
GhostPlayer _ghost = _other.GetComponentInParent<GhostPlayer>();
if (_ghost != null)
{
Debug.Log($"[PIT] pit{pitIndex} <- GHOST owner={_ghost.OriginalOwnerId} ghostPos={_ghost.transform.position} colliderPos={_other.transform.position}");
RoundManager.Current.OnPlayerEnteredPit(pitIndex, _ghost.OriginalOwnerId);
_ghost.GetComponent<PlayerHealth>().TakeDamage(999);
return;
@@ -27,6 +28,16 @@ public class PitZone : NetworkBehaviour
PlayerHealth _playerHealth = _other.GetComponentInParent<PlayerHealth>();
if (_playerHealth == null) return;
Debug.Log($"[PIT] pit{pitIndex} <- PLAYER {_playerHealth.OwnerClientId} pos={_playerHealth.transform.position} immune={_playerHealth.IsPitImmune}");
// Только что респавнулся — серверный коллайдер мог не успеть выйти из ямы.
// Игнорируем, чтобы не засчитать ложную мгновенную победу.
if (_playerHealth.IsPitImmune)
{
Debug.Log($"[PIT] pit{pitIndex} IGNORED: player {_playerHealth.OwnerClientId} pit-immune");
return;
}
RoundManager.Current.OnPlayerEnteredPit(pitIndex, _playerHealth.OwnerClientId);
_playerHealth.TakeDamage(999);
@@ -20,6 +20,12 @@ public class PlayerHealth : NetworkBehaviour
private bool isServer;
// Короткое окно после респавна, когда вход в яму не засчитывается — страховка на случай,
// если серверная позиция коллайдера не успела выйти из ямы (сетевая задержка).
[SerializeField] private float pitImmunityDuration = 1f;
private float pitImmuneUntil;
public bool IsPitImmune => Time.time < pitImmuneUntil;
public void Init(bool _isServer)
{
isServer = _isServer;
@@ -76,6 +82,7 @@ public class PlayerHealth : NetworkBehaviour
GetComponent<PlayerShield>()?.SetShieldState(false);
isDead.Value = false;
currentHealth.Value = maxHealth;
pitImmuneUntil = Time.time + pitImmunityDuration;
RespawnClientRpc(_spawnPosition);
}
@@ -97,15 +104,18 @@ public class PlayerHealth : NetworkBehaviour
{
SetPhysicsAndVisualsActive(true);
// Позицию задаёт только владелец — он авторитет над NetworkTransform (Owner authority)
// Позицию задаёт ТОЛЬКО владелец — он авторитет над NetworkTransform (Owner authority).
// На сервере/не-владельце трогать transform бессмысленно: NetworkTransform всё равно
// перезапишет его синхронизацией от владельца. Серверную позицию догонит RoundManager
// через WaitForPlayersAtSpawn перед стартом записи (иначе запись стартует в яме).
if (!IsOwner) return;
TeleportOwner(_spawnPosition);
Teleport(_spawnPosition);
}
// Телепорт без интерполяции. CharacterController кэширует свою позицию,
// поэтому его надо выключить на время установки трансформа.
private void TeleportOwner(Vector3 _spawnPosition)
private void Teleport(Vector3 _spawnPosition)
{
CharacterController _controller = GetComponent<CharacterController>();
if (_controller != null) _controller.enabled = false;
@@ -38,6 +38,10 @@ public class RoundManager : NetworkBehaviour
private List<PlayerHealth> alivePlayers = new List<PlayerHealth>();
// Точки спавна, назначенные игрокам в этом раунде (параллельно alivePlayers).
// Нужны, чтобы дождаться, пока серверная позиция догонит телепорт, перед стартом записи.
private List<Vector3> spawnAssignments = new List<Vector3>();
private NetworkList<PlayerScore> playerScores = new NetworkList<PlayerScore>();
// pitDefenders[i] = ClientId игрока, которому принадлежит яма с pitIndex == i
@@ -97,8 +101,13 @@ public class RoundManager : NetworkBehaviour
// Своя яма — не считается, побеждают только за попадание в ЧУЖУЮ яму.
// Для клона сюда приходит ClientId его ОРИГИНАЛА (см. PitZone) — правило работает так же.
if (_enteringClientId == pitDefenders[_pitIndex]) return;
if (_enteringClientId == pitDefenders[_pitIndex])
{
Debug.Log($"[ROUND] pit{_pitIndex} own pit by {_enteringClientId} — no score");
return;
}
Debug.Log($"[ROUND] WIN pit{_pitIndex} by client={_enteringClientId} (defender={pitDefenders[_pitIndex]}) round={currentRound.Value} timeLeft={roundTimeRemaining.Value:F1}");
AddScore(_enteringClientId);
StartCoroutine(EndRound(_enteringClientId));
}
@@ -177,6 +186,7 @@ public class RoundManager : NetworkBehaviour
private IEnumerator EndRound(ulong _winnerClientId)
{
Debug.Log($"[ROUND] EndRound round={currentRound.Value} winner={_winnerClientId} timeLeft={roundTimeRemaining.Value:F1}");
isRoundActive.Value = false;
// Сразу: стоп клонов + запись раунда в архив.
@@ -219,6 +229,7 @@ public class RoundManager : NetworkBehaviour
_players.Sort((_a, _b) => _a.OwnerClientId.CompareTo(_b.OwnerClientId));
alivePlayers.Clear();
spawnAssignments.Clear();
// Одна яма на игрока: игрок с индексом _q защищает яму pitIndex == _q
pitDefenders = new ulong[_players.Count];
@@ -231,12 +242,59 @@ public class RoundManager : NetworkBehaviour
pitDefenders[_q] = _clientId;
Vector3 _spawnPosition = spawnPoints[_q % spawnPoints.Length].position;
// Диагностика: спавн-поинт не должен быть внутри триггера ямы,
// иначе клон заспавнится в яме и мгновенно "победит".
Collider[] _overlaps = Physics.OverlapSphere(_spawnPosition, 0.5f, ~0, QueryTriggerInteraction.Collide);
foreach (Collider _c in _overlaps)
if (_c.GetComponentInParent<PitZone>() != null)
Debug.LogError($"[ROUND] SPAWN {_q} at {_spawnPosition} INSIDE PIT '{_c.name}' для client={_clientId} — это причина мгновенной победы клона!");
_players[_q].Respawn(_spawnPosition);
alivePlayers.Add(_players[_q]);
spawnAssignments.Add(_spawnPosition);
}
}
// Ждём, пока серверная позиция каждого игрока догонит телепорт на спавн.
// Иначе первый сэмпл записи (positions[0]) окажется там, где игрок умер (в яме),
// и клон следующего раунда заспавнится прямо в яме → мгновенная ложная победа.
private IEnumerator WaitForPlayersAtSpawn()
{
float _timeout = Time.time + 3f;
float _start = Time.time;
while (Time.time < _timeout)
{
bool _allAtSpawn = true;
for (int _i = 0; _i < alivePlayers.Count; _i++)
{
if (alivePlayers[_i] == null) continue;
float _dist = Vector3.Distance(alivePlayers[_i].transform.position, spawnAssignments[_i]);
if (_dist > 1.5f)
{
_allAtSpawn = false;
break;
}
}
if (_allAtSpawn)
{
Debug.Log($"[ROUND] players at spawn after {Time.time - _start:F2}s");
yield break;
}
yield return null;
}
Debug.LogWarning($"[ROUND] WaitForPlayersAtSpawn TIMEOUT after 3s — positions may still be stale:");
for (int _i = 0; _i < alivePlayers.Count; _i++)
if (alivePlayers[_i] != null)
Debug.LogWarning($"[ROUND] player {alivePlayers[_i].OwnerClientId} pos={alivePlayers[_i].transform.position} target={spawnAssignments[_i]} dist={Vector3.Distance(alivePlayers[_i].transform.position, spawnAssignments[_i]):F2}");
}
private IEnumerator StartRoundWithDelay()
{
yield return new WaitForSeconds(1f);
@@ -245,6 +303,10 @@ public class RoundManager : NetworkBehaviour
RespawnAllPlayers();
// КРИТИЧНО: ждём, пока серверная позиция игроков реально станет спавном,
// прежде чем начинать запись — иначе клоны будут спавниться в яме (см. WaitForPlayersAtSpawn).
yield return WaitForPlayersAtSpawn();
// Спавним клонов прошлых раундов и включаем запись у живых игроков
if (GhostRoundManager.Current != null)
GhostRoundManager.Current.ServerStartRound();
@@ -252,6 +314,7 @@ public class RoundManager : NetworkBehaviour
roundTimeRemaining.Value = roundDuration.Value;
isRoundActive.Value = true;
Debug.Log($"[ROUND] START round={currentRound.Value} players={alivePlayers.Count}");
RoundStartedClientRPC(currentRound.Value);
}
+2 -2
View File
@@ -96,7 +96,7 @@ PlayerSettings:
bakeCollisionMeshes: 0
forceSingleInstance: 0
useFlipModelSwapchain: 1
resizableWindow: 0
resizableWindow: 1
useMacAppStoreValidation: 0
macAppStoreCategory: public.app-category.games
gpuSkinning: 1
@@ -108,7 +108,7 @@ PlayerSettings:
xboxEnableFitness: 0
visibleInBackground: 1
allowFullscreenSwitch: 1
fullscreenMode: 1
fullscreenMode: 3
xboxSpeechDB: 0
xboxEnableHeadOrientation: 0
xboxEnableGuest: 0