online fix
This commit is contained in:
@@ -13,7 +13,7 @@ MonoBehaviour:
|
|||||||
m_Name: BossSchedule
|
m_Name: BossSchedule
|
||||||
m_EditorClassIdentifier: Assembly-CSharp::Game.Scripts.Runtime.Data.BossSchedule
|
m_EditorClassIdentifier: Assembly-CSharp::Game.Scripts.Runtime.Data.BossSchedule
|
||||||
events:
|
events:
|
||||||
- TimeSinceStart: 30
|
- TimeSinceStart: 1000
|
||||||
Boss: {fileID: 11400000, guid: 1bc4888fa172eb99f94756653be6c1ed, type: 2}
|
Boss: {fileID: 11400000, guid: 1bc4888fa172eb99f94756653be6c1ed, type: 2}
|
||||||
Count: 1
|
Count: 1
|
||||||
useSpawnRadiusOverride: 0
|
useSpawnRadiusOverride: 0
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ MonoBehaviour:
|
|||||||
projectilePrefab: {fileID: -6920969466594260193, guid: 6703b124cb13a577c8aae6a4851d0274, type: 3}
|
projectilePrefab: {fileID: -6920969466594260193, guid: 6703b124cb13a577c8aae6a4851d0274, type: 3}
|
||||||
projectileSpeed: 18
|
projectileSpeed: 18
|
||||||
projectileLifetime: 5
|
projectileLifetime: 5
|
||||||
shotsPerSecond: 1
|
shotsPerSecond: 2
|
||||||
baseDamage: 10
|
baseDamage: 50
|
||||||
range: 25
|
range: 25
|
||||||
projectilesPerShot: 1
|
projectilesPerShot: 2
|
||||||
spreadAngle: 0
|
spreadAngle: 0
|
||||||
hitMask:
|
hitMask:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
|
|||||||
79
Game/Scripts/Networking/EnemyDefinitionRegistry.cs
Normal file
79
Game/Scripts/Networking/EnemyDefinitionRegistry.cs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Game.Scripts.Runtime.Data;
|
||||||
|
|
||||||
|
namespace MegaKoop.Game.Networking
|
||||||
|
{
|
||||||
|
internal static class EnemyDefinitionRegistry
|
||||||
|
{
|
||||||
|
private static readonly Dictionary<string, EnemyDefinition> DefinitionsById = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private static readonly Dictionary<string, EnemyDefinition> DefinitionsByName = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
internal static void Register(EnemyDefinition definition)
|
||||||
|
{
|
||||||
|
if (definition == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = NormalizeId(definition);
|
||||||
|
if (!string.IsNullOrEmpty(id))
|
||||||
|
{
|
||||||
|
DefinitionsById[id] = definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(definition.name))
|
||||||
|
{
|
||||||
|
DefinitionsByName[definition.name] = definition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool TryGet(string idOrName, out EnemyDefinition definition)
|
||||||
|
{
|
||||||
|
definition = null;
|
||||||
|
if (string.IsNullOrWhiteSpace(idOrName))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DefinitionsById.TryGetValue(idOrName, out definition))
|
||||||
|
{
|
||||||
|
return definition != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DefinitionsByName.TryGetValue(idOrName, out definition))
|
||||||
|
{
|
||||||
|
return definition != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string ResolveId(EnemyDefinition definition)
|
||||||
|
{
|
||||||
|
if (definition == null)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = NormalizeId(definition);
|
||||||
|
Register(definition);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeId(EnemyDefinition definition)
|
||||||
|
{
|
||||||
|
if (definition == null)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(definition.Id))
|
||||||
|
{
|
||||||
|
return definition.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return definition.name ?? string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Game/Scripts/Networking/EnemyDefinitionRegistry.cs.meta
Normal file
2
Game/Scripts/Networking/EnemyDefinitionRegistry.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fe91b8e8bcd3c2b8cb3f037428048c0b
|
||||||
@@ -12,7 +12,10 @@ namespace MegaKoop.Game.Networking
|
|||||||
WeaponFire = 4,
|
WeaponFire = 4,
|
||||||
HealthSync = 5,
|
HealthSync = 5,
|
||||||
ProjectileSpawn = 6,
|
ProjectileSpawn = 6,
|
||||||
ProjectileImpact = 7
|
ProjectileImpact = 7,
|
||||||
|
GameState = 8,
|
||||||
|
EnemySpawn = 9,
|
||||||
|
EnemyDespawn = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ProjectileImpactKind : byte
|
public enum ProjectileImpactKind : byte
|
||||||
@@ -252,4 +255,139 @@ namespace MegaKoop.Game.Networking
|
|||||||
return new ProjectileImpactMessage(kind, position, normal);
|
return new ProjectileImpactMessage(kind, position, normal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum GameStateEvent : byte
|
||||||
|
{
|
||||||
|
Started = 0,
|
||||||
|
Paused = 1,
|
||||||
|
Resumed = 2,
|
||||||
|
Stopped = 3,
|
||||||
|
WaveAdvanced = 4,
|
||||||
|
BossSpawned = 5,
|
||||||
|
Heartbeat = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct GameStateMessage
|
||||||
|
{
|
||||||
|
public readonly GameStateEvent Event;
|
||||||
|
public readonly float Elapsed;
|
||||||
|
public readonly int CurrentWave;
|
||||||
|
public readonly int PreviousWave;
|
||||||
|
public readonly bool IsPaused;
|
||||||
|
public readonly bool IsRunning;
|
||||||
|
public readonly string DefinitionId;
|
||||||
|
public readonly int Count;
|
||||||
|
|
||||||
|
public GameStateMessage(GameStateEvent evt, float elapsed, int currentWave, int previousWave, bool isPaused, bool isRunning, string definitionId, int count)
|
||||||
|
{
|
||||||
|
Event = evt;
|
||||||
|
Elapsed = elapsed;
|
||||||
|
CurrentWave = currentWave;
|
||||||
|
PreviousWave = previousWave;
|
||||||
|
IsPaused = isPaused;
|
||||||
|
IsRunning = isRunning;
|
||||||
|
DefinitionId = definitionId ?? string.Empty;
|
||||||
|
Count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] Serialize(GameStateMessage message)
|
||||||
|
{
|
||||||
|
using var writer = new NetworkWriter();
|
||||||
|
writer.Write((byte)message.Event);
|
||||||
|
writer.Write(message.Elapsed);
|
||||||
|
writer.Write(message.CurrentWave);
|
||||||
|
writer.Write(message.PreviousWave);
|
||||||
|
writer.Write(message.IsPaused);
|
||||||
|
writer.Write(message.IsRunning);
|
||||||
|
writer.Write(message.DefinitionId ?? string.Empty);
|
||||||
|
writer.Write(message.Count);
|
||||||
|
return writer.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameStateMessage Deserialize(byte[] buffer)
|
||||||
|
{
|
||||||
|
using var reader = new NetworkReader(buffer);
|
||||||
|
GameStateEvent evt = (GameStateEvent)reader.ReadByte();
|
||||||
|
float elapsed = reader.ReadFloat();
|
||||||
|
int currentWave = reader.ReadInt();
|
||||||
|
int previousWave = reader.ReadInt();
|
||||||
|
bool isPaused = reader.ReadBool();
|
||||||
|
bool isRunning = reader.ReadBool();
|
||||||
|
string definitionId = reader.ReadString();
|
||||||
|
int count = reader.ReadInt();
|
||||||
|
return new GameStateMessage(evt, elapsed, currentWave, previousWave, isPaused, isRunning, definitionId, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct EnemySpawnMessage
|
||||||
|
{
|
||||||
|
public readonly string DefinitionId;
|
||||||
|
public readonly Vector3 Position;
|
||||||
|
public readonly Quaternion Rotation;
|
||||||
|
public readonly int NetworkId;
|
||||||
|
public readonly bool IsBoss;
|
||||||
|
public readonly int WaveIndex;
|
||||||
|
public readonly float Timestamp;
|
||||||
|
|
||||||
|
public EnemySpawnMessage(string definitionId, Vector3 position, Quaternion rotation, int networkId, bool isBoss, int waveIndex, float timestamp)
|
||||||
|
{
|
||||||
|
DefinitionId = definitionId ?? string.Empty;
|
||||||
|
Position = position;
|
||||||
|
Rotation = rotation;
|
||||||
|
NetworkId = networkId;
|
||||||
|
IsBoss = isBoss;
|
||||||
|
WaveIndex = waveIndex;
|
||||||
|
Timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] Serialize(EnemySpawnMessage message)
|
||||||
|
{
|
||||||
|
using var writer = new NetworkWriter();
|
||||||
|
writer.Write(message.DefinitionId ?? string.Empty);
|
||||||
|
writer.Write(message.Position);
|
||||||
|
writer.Write(message.Rotation);
|
||||||
|
writer.Write(message.NetworkId);
|
||||||
|
writer.Write(message.IsBoss);
|
||||||
|
writer.Write(message.WaveIndex);
|
||||||
|
writer.Write(message.Timestamp);
|
||||||
|
return writer.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EnemySpawnMessage Deserialize(byte[] buffer)
|
||||||
|
{
|
||||||
|
using var reader = new NetworkReader(buffer);
|
||||||
|
string definitionId = reader.ReadString();
|
||||||
|
Vector3 position = reader.ReadVector3();
|
||||||
|
Quaternion rotation = reader.ReadQuaternion();
|
||||||
|
int networkId = reader.ReadInt();
|
||||||
|
bool isBoss = reader.ReadBool();
|
||||||
|
int waveIndex = reader.ReadInt();
|
||||||
|
float timestamp = reader.ReadFloat();
|
||||||
|
return new EnemySpawnMessage(definitionId, position, rotation, networkId, isBoss, waveIndex, timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct EnemyDespawnMessage
|
||||||
|
{
|
||||||
|
public readonly int NetworkId;
|
||||||
|
|
||||||
|
public EnemyDespawnMessage(int networkId)
|
||||||
|
{
|
||||||
|
NetworkId = networkId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] Serialize(EnemyDespawnMessage message)
|
||||||
|
{
|
||||||
|
using var writer = new NetworkWriter();
|
||||||
|
writer.Write(message.NetworkId);
|
||||||
|
return writer.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EnemyDespawnMessage Deserialize(byte[] buffer)
|
||||||
|
{
|
||||||
|
using var reader = new NetworkReader(buffer);
|
||||||
|
int id = reader.ReadInt();
|
||||||
|
return new EnemyDespawnMessage(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ namespace MegaKoop.Game.Networking
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static SteamCharacterStateCache instance;
|
private static SteamCharacterStateCache instance;
|
||||||
|
private static bool isApplicationQuitting;
|
||||||
|
|
||||||
private readonly Dictionary<int, CharacterState> states = new();
|
private readonly Dictionary<int, CharacterState> states = new();
|
||||||
private SteamCoopNetworkManager networkManager;
|
private SteamCoopNetworkManager networkManager;
|
||||||
@@ -40,9 +41,10 @@ namespace MegaKoop.Game.Networking
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (instance == null)
|
if (instance == null && !isApplicationQuitting)
|
||||||
{
|
{
|
||||||
var go = new GameObject("SteamCharacterStateCache");
|
var go = new GameObject("SteamCharacterStateCache");
|
||||||
|
go.hideFlags = HideFlags.HideAndDontSave;
|
||||||
instance = go.AddComponent<SteamCharacterStateCache>();
|
instance = go.AddComponent<SteamCharacterStateCache>();
|
||||||
DontDestroyOnLoad(go);
|
DontDestroyOnLoad(go);
|
||||||
}
|
}
|
||||||
@@ -68,6 +70,21 @@ namespace MegaKoop.Game.Networking
|
|||||||
EnsureSubscription();
|
EnsureSubscription();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
if (instance == this)
|
||||||
|
{
|
||||||
|
Unsubscribe();
|
||||||
|
states.Clear();
|
||||||
|
instance = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnApplicationQuit()
|
||||||
|
{
|
||||||
|
isApplicationQuitting = true;
|
||||||
|
}
|
||||||
|
|
||||||
private void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
EnsureSubscription();
|
EnsureSubscription();
|
||||||
|
|||||||
355
Game/Scripts/Networking/SteamEnemySpawnerNetworkBridge.cs
Normal file
355
Game/Scripts/Networking/SteamEnemySpawnerNetworkBridge.cs
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Game.Scripts.Runtime.Data;
|
||||||
|
using Game.Scripts.Runtime.Game;
|
||||||
|
using Game.Scripts.Runtime.Pooling;
|
||||||
|
using Game.Scripts.Runtime.Spawning;
|
||||||
|
using Steamworks;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace MegaKoop.Game.Networking
|
||||||
|
{
|
||||||
|
[DefaultExecutionOrder(-120)]
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
public class SteamEnemySpawnerNetworkBridge : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("References")]
|
||||||
|
[SerializeField] private EnemySpawner spawner;
|
||||||
|
[SerializeField] private GameController gameController;
|
||||||
|
[SerializeField] private ObjectPooler pooler;
|
||||||
|
[SerializeField] private List<EnemyDefinition> additionalDefinitions = new();
|
||||||
|
|
||||||
|
[Header("Behaviour")]
|
||||||
|
[SerializeField] private bool autoFindReferences = true;
|
||||||
|
|
||||||
|
private SteamCoopNetworkManager networkManager;
|
||||||
|
private bool handlersRegistered;
|
||||||
|
private bool spawnerSubscribed;
|
||||||
|
private bool poolerSubscribed;
|
||||||
|
private bool cachedAuthority;
|
||||||
|
private bool hasPendingRemoteSpawn;
|
||||||
|
private EnemySpawnMessage pendingRemoteSpawn;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
if (autoFindReferences)
|
||||||
|
{
|
||||||
|
spawner ??= GetComponent<EnemySpawner>();
|
||||||
|
gameController ??= GetComponent<GameController>();
|
||||||
|
pooler ??= spawner != null ? spawner.Pool : ObjectPooler.SharedInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedAuthority = DetermineAuthority();
|
||||||
|
ApplyAuthorityOverride(cachedAuthority);
|
||||||
|
RegisterDefinitionsFromSpawner();
|
||||||
|
RegisterAdditionalDefinitions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
RefreshNetworkManager();
|
||||||
|
RegisterHandlers();
|
||||||
|
SubscribeEvents();
|
||||||
|
cachedAuthority = DetermineAuthority();
|
||||||
|
ApplyAuthorityOverride(cachedAuthority);
|
||||||
|
RegisterDefinitionsFromSpawner();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
UnsubscribeEvents();
|
||||||
|
UnregisterHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
RefreshNetworkManager();
|
||||||
|
|
||||||
|
bool authority = DetermineAuthority();
|
||||||
|
if (authority != cachedAuthority)
|
||||||
|
{
|
||||||
|
cachedAuthority = authority;
|
||||||
|
ApplyAuthorityOverride(authority);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handlersRegistered)
|
||||||
|
{
|
||||||
|
RegisterHandlers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SubscribeEvents()
|
||||||
|
{
|
||||||
|
if (spawner != null && !spawnerSubscribed)
|
||||||
|
{
|
||||||
|
spawner.OnEnemySpawned += HandleEnemySpawned;
|
||||||
|
spawnerSubscribed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pooler != null && !poolerSubscribed)
|
||||||
|
{
|
||||||
|
pooler.InstanceDespawned += HandleInstanceDespawned;
|
||||||
|
poolerSubscribed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnsubscribeEvents()
|
||||||
|
{
|
||||||
|
if (spawner != null && spawnerSubscribed)
|
||||||
|
{
|
||||||
|
spawner.OnEnemySpawned -= HandleEnemySpawned;
|
||||||
|
spawnerSubscribed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pooler != null && poolerSubscribed)
|
||||||
|
{
|
||||||
|
pooler.InstanceDespawned -= HandleInstanceDespawned;
|
||||||
|
poolerSubscribed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterHandlers()
|
||||||
|
{
|
||||||
|
if (handlersRegistered)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshNetworkManager();
|
||||||
|
if (networkManager == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
networkManager.RegisterHandler(NetworkMessageType.EnemySpawn, HandleEnemySpawnMessage);
|
||||||
|
networkManager.RegisterHandler(NetworkMessageType.EnemyDespawn, HandleEnemyDespawnMessage);
|
||||||
|
handlersRegistered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnregisterHandlers()
|
||||||
|
{
|
||||||
|
if (!handlersRegistered || networkManager == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
networkManager.UnregisterHandler(NetworkMessageType.EnemySpawn, HandleEnemySpawnMessage);
|
||||||
|
networkManager.UnregisterHandler(NetworkMessageType.EnemyDespawn, HandleEnemyDespawnMessage);
|
||||||
|
handlersRegistered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshNetworkManager()
|
||||||
|
{
|
||||||
|
if (networkManager == null)
|
||||||
|
{
|
||||||
|
networkManager = SteamCoopNetworkManager.Instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DetermineAuthority()
|
||||||
|
{
|
||||||
|
RefreshNetworkManager();
|
||||||
|
if (networkManager == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!networkManager.IsConnected)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return networkManager.IsHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyAuthorityOverride(bool authority)
|
||||||
|
{
|
||||||
|
spawner?.SetAuthorityOverride(authority);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterDefinitionsFromSpawner()
|
||||||
|
{
|
||||||
|
if (spawner == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var table = spawner.ActiveTable;
|
||||||
|
if (table?.Entries == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var entry in table.Entries)
|
||||||
|
{
|
||||||
|
if (entry?.Def == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnemyDefinitionRegistry.Register(entry.Def);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterAdditionalDefinitions()
|
||||||
|
{
|
||||||
|
if (additionalDefinitions == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var definition in additionalDefinitions)
|
||||||
|
{
|
||||||
|
EnemyDefinitionRegistry.Register(definition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleEnemySpawned(GameObject instance, EnemyDefinition definition)
|
||||||
|
{
|
||||||
|
if (definition != null)
|
||||||
|
{
|
||||||
|
EnemyDefinitionRegistry.Register(definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DetermineAuthority() || networkManager == null)
|
||||||
|
{
|
||||||
|
if (hasPendingRemoteSpawn)
|
||||||
|
{
|
||||||
|
ApplyPendingRemoteSpawn(instance, definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instance == null || definition == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string definitionId = EnemyDefinitionRegistry.ResolveId(definition);
|
||||||
|
var identity = instance.GetComponent<NetworkIdentity>();
|
||||||
|
int networkId = identity != null ? identity.NetworkId : 0;
|
||||||
|
Vector3 spawnRootPosition = instance.transform.position - definition.PrefabPivotOffset;
|
||||||
|
Quaternion rotation = instance.transform.rotation;
|
||||||
|
bool isBoss = definition.IsBoss;
|
||||||
|
int waveIndex = spawner != null ? spawner.CurrentWaveIndex : -1;
|
||||||
|
float timestamp = gameController != null ? gameController.Elapsed : Time.time;
|
||||||
|
|
||||||
|
var message = new EnemySpawnMessage(definitionId, spawnRootPosition, rotation, networkId, isBoss, waveIndex, timestamp);
|
||||||
|
byte[] payload = EnemySpawnMessage.Serialize(message);
|
||||||
|
networkManager.SendToAll(NetworkMessageType.EnemySpawn, payload, EP2PSend.k_EP2PSendReliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleInstanceDespawned(GameObject instance, EnemyDefinition definition)
|
||||||
|
{
|
||||||
|
if (!DetermineAuthority() || networkManager == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instance == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var identity = instance.GetComponent<NetworkIdentity>();
|
||||||
|
if (identity == null || identity.NetworkId == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = new EnemyDespawnMessage(identity.NetworkId);
|
||||||
|
byte[] payload = EnemyDespawnMessage.Serialize(message);
|
||||||
|
SteamCharacterStateCache.RemoveState(identity.NetworkId);
|
||||||
|
networkManager.SendToAll(NetworkMessageType.EnemyDespawn, payload, EP2PSend.k_EP2PSendReliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleEnemySpawnMessage(NetworkMessage message)
|
||||||
|
{
|
||||||
|
if (DetermineAuthority())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingRemoteSpawn = EnemySpawnMessage.Deserialize(message.Payload);
|
||||||
|
hasPendingRemoteSpawn = true;
|
||||||
|
|
||||||
|
if (!EnemyDefinitionRegistry.TryGet(pendingRemoteSpawn.DefinitionId, out var definition))
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[SteamEnemySpawnerNetworkBridge] Missing EnemyDefinition for id '{pendingRemoteSpawn.DefinitionId}'.");
|
||||||
|
hasPendingRemoteSpawn = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = spawner != null && spawner.TrySpawn(definition, pendingRemoteSpawn.Position);
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameObject instance = pooler != null
|
||||||
|
? pooler.Spawn(definition, pendingRemoteSpawn.Position, pendingRemoteSpawn.Rotation)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (instance == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[SteamEnemySpawnerNetworkBridge] Fallback spawn failed for '{pendingRemoteSpawn.DefinitionId}'.");
|
||||||
|
hasPendingRemoteSpawn = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyPendingRemoteSpawn(instance, definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleEnemyDespawnMessage(NetworkMessage message)
|
||||||
|
{
|
||||||
|
if (DetermineAuthority())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnemyDespawnMessage despawnMessage = EnemyDespawnMessage.Deserialize(message.Payload);
|
||||||
|
if (despawnMessage.NetworkId == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NetworkIdentity.TryGet(despawnMessage.NetworkId, out var identity) || identity == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SteamCharacterStateCache.RemoveState(identity.NetworkId);
|
||||||
|
|
||||||
|
var pooled = identity.GetComponent<PooledInstance>();
|
||||||
|
if (pooled != null)
|
||||||
|
{
|
||||||
|
pooled.ReturnToPool();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
identity.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyPendingRemoteSpawn(GameObject instance, EnemyDefinition definition)
|
||||||
|
{
|
||||||
|
if (!hasPendingRemoteSpawn || instance == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasPendingRemoteSpawn = false;
|
||||||
|
instance.transform.rotation = pendingRemoteSpawn.Rotation;
|
||||||
|
if (definition != null)
|
||||||
|
{
|
||||||
|
EnemyDefinitionRegistry.Register(definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
var identity = instance.GetComponent<NetworkIdentity>();
|
||||||
|
if (identity != null && pendingRemoteSpawn.NetworkId != 0 && identity.NetworkId != pendingRemoteSpawn.NetworkId)
|
||||||
|
{
|
||||||
|
identity.SetNetworkId(pendingRemoteSpawn.NetworkId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5137a566078d27c81957d1c4040e32eb
|
||||||
365
Game/Scripts/Networking/SteamGameControllerNetworkBridge.cs
Normal file
365
Game/Scripts/Networking/SteamGameControllerNetworkBridge.cs
Normal file
@@ -0,0 +1,365 @@
|
|||||||
|
using Game.Scripts.Runtime.Data;
|
||||||
|
using Game.Scripts.Runtime.Game;
|
||||||
|
using Game.Scripts.Runtime.Spawning;
|
||||||
|
using Steamworks;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace MegaKoop.Game.Networking
|
||||||
|
{
|
||||||
|
[DefaultExecutionOrder(-140)]
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
public class SteamGameControllerNetworkBridge : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("References")]
|
||||||
|
[SerializeField] private GameController gameController;
|
||||||
|
[SerializeField] private EnemySpawner enemySpawner;
|
||||||
|
|
||||||
|
[Header("Heartbeat")]
|
||||||
|
[SerializeField, Min(0.1f)] private float heartbeatInterval = 0.5f;
|
||||||
|
|
||||||
|
private SteamCoopNetworkManager networkManager;
|
||||||
|
private bool handlersRegistered;
|
||||||
|
private bool controllerSubscribed;
|
||||||
|
private bool cachedAuthority;
|
||||||
|
private float heartbeatTimer;
|
||||||
|
private int lastWaveBroadcast = -1;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
gameController ??= GetComponent<GameController>();
|
||||||
|
enemySpawner ??= GetComponent<EnemySpawner>();
|
||||||
|
|
||||||
|
cachedAuthority = DetermineAuthority();
|
||||||
|
ApplyAuthorityOverride(cachedAuthority);
|
||||||
|
RegisterBossDefinitions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
RefreshNetworkManager();
|
||||||
|
RegisterHandlers();
|
||||||
|
SubscribeControllerEvents();
|
||||||
|
|
||||||
|
cachedAuthority = DetermineAuthority();
|
||||||
|
ApplyAuthorityOverride(cachedAuthority);
|
||||||
|
heartbeatTimer = heartbeatInterval;
|
||||||
|
RegisterBossDefinitions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
UnsubscribeControllerEvents();
|
||||||
|
UnregisterHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
RefreshNetworkManager();
|
||||||
|
|
||||||
|
bool authority = DetermineAuthority();
|
||||||
|
if (authority != cachedAuthority)
|
||||||
|
{
|
||||||
|
cachedAuthority = authority;
|
||||||
|
ApplyAuthorityOverride(authority);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authority)
|
||||||
|
{
|
||||||
|
RunHeartbeat(Time.deltaTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunHeartbeat(float deltaTime)
|
||||||
|
{
|
||||||
|
heartbeatTimer -= deltaTime;
|
||||||
|
if (heartbeatTimer > 0f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
heartbeatTimer = Mathf.Max(0.1f, heartbeatInterval);
|
||||||
|
SendState(GameStateEvent.Heartbeat, lastWaveBroadcast);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SubscribeControllerEvents()
|
||||||
|
{
|
||||||
|
if (gameController == null || controllerSubscribed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gameController.OnGameStarted += HandleGameStarted;
|
||||||
|
gameController.OnGamePaused += HandleGamePaused;
|
||||||
|
gameController.OnGameResumed += HandleGameResumed;
|
||||||
|
gameController.OnGameStopped += HandleGameStopped;
|
||||||
|
gameController.OnWaveStarted += HandleWaveStarted;
|
||||||
|
gameController.OnBossSpawned += HandleBossSpawned;
|
||||||
|
controllerSubscribed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnsubscribeControllerEvents()
|
||||||
|
{
|
||||||
|
if (gameController == null || !controllerSubscribed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gameController.OnGameStarted -= HandleGameStarted;
|
||||||
|
gameController.OnGamePaused -= HandleGamePaused;
|
||||||
|
gameController.OnGameResumed -= HandleGameResumed;
|
||||||
|
gameController.OnGameStopped -= HandleGameStopped;
|
||||||
|
gameController.OnWaveStarted -= HandleWaveStarted;
|
||||||
|
gameController.OnBossSpawned -= HandleBossSpawned;
|
||||||
|
controllerSubscribed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleGameStarted()
|
||||||
|
{
|
||||||
|
if (!DetermineAuthority())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendState(GameStateEvent.Started, -1);
|
||||||
|
lastWaveBroadcast = gameController != null ? gameController.CurrentWaveIndex : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleGamePaused()
|
||||||
|
{
|
||||||
|
if (!DetermineAuthority())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendState(GameStateEvent.Paused, gameController != null ? gameController.CurrentWaveIndex : -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleGameResumed()
|
||||||
|
{
|
||||||
|
if (!DetermineAuthority())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendState(GameStateEvent.Resumed, gameController != null ? gameController.CurrentWaveIndex : -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleGameStopped()
|
||||||
|
{
|
||||||
|
if (!DetermineAuthority())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendState(GameStateEvent.Stopped, gameController != null ? gameController.CurrentWaveIndex : -1);
|
||||||
|
lastWaveBroadcast = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleWaveStarted(int waveIndex)
|
||||||
|
{
|
||||||
|
if (!DetermineAuthority())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int previousWave = lastWaveBroadcast >= 0 ? lastWaveBroadcast : waveIndex - 1;
|
||||||
|
lastWaveBroadcast = waveIndex;
|
||||||
|
SendState(GameStateEvent.WaveAdvanced, previousWave);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleBossSpawned(EnemyDefinition boss, int count)
|
||||||
|
{
|
||||||
|
if (!DetermineAuthority())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnemyDefinitionRegistry.Register(boss);
|
||||||
|
string definitionId = EnemyDefinitionRegistry.ResolveId(boss);
|
||||||
|
SendState(GameStateEvent.BossSpawned, lastWaveBroadcast, definitionId, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendState(GameStateEvent evt, int previousWave, string definitionId = "", int count = 0)
|
||||||
|
{
|
||||||
|
if (networkManager == null || gameController == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float elapsed = gameController.Elapsed;
|
||||||
|
int currentWave = gameController.CurrentWaveIndex;
|
||||||
|
bool isPaused = gameController.IsPaused;
|
||||||
|
bool isRunning = gameController.IsGameActive;
|
||||||
|
|
||||||
|
var message = new GameStateMessage(evt, elapsed, currentWave, previousWave, isPaused, isRunning, definitionId, count);
|
||||||
|
byte[] payload = GameStateMessage.Serialize(message);
|
||||||
|
var sendType = evt == GameStateEvent.Heartbeat ? EP2PSend.k_EP2PSendUnreliableNoDelay : EP2PSend.k_EP2PSendReliable;
|
||||||
|
networkManager.SendToAll(NetworkMessageType.GameState, payload, sendType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterHandlers()
|
||||||
|
{
|
||||||
|
if (handlersRegistered)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshNetworkManager();
|
||||||
|
if (networkManager == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
networkManager.RegisterHandler(NetworkMessageType.GameState, HandleGameStateMessage);
|
||||||
|
handlersRegistered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnregisterHandlers()
|
||||||
|
{
|
||||||
|
if (!handlersRegistered || networkManager == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
networkManager.UnregisterHandler(NetworkMessageType.GameState, HandleGameStateMessage);
|
||||||
|
handlersRegistered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleGameStateMessage(NetworkMessage message)
|
||||||
|
{
|
||||||
|
if (DetermineAuthority())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameStateMessage state = GameStateMessage.Deserialize(message.Payload);
|
||||||
|
ApplyRemoteState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyRemoteState(GameStateMessage state)
|
||||||
|
{
|
||||||
|
if (gameController == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (state.Event)
|
||||||
|
{
|
||||||
|
case GameStateEvent.Started:
|
||||||
|
if (!gameController.IsGameActive)
|
||||||
|
{
|
||||||
|
gameController.StartGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
gameController.SetElapsedFromRemote(state.Elapsed);
|
||||||
|
gameController.SetRunningFlagsFromRemote(state.IsRunning, state.IsPaused);
|
||||||
|
gameController.ApplyRemoteWaveStarted(state.CurrentWave);
|
||||||
|
lastWaveBroadcast = state.CurrentWave;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GameStateEvent.Paused:
|
||||||
|
if (!gameController.IsPaused && gameController.IsGameActive)
|
||||||
|
{
|
||||||
|
gameController.PauseGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
gameController.SetElapsedFromRemote(state.Elapsed);
|
||||||
|
gameController.SetRunningFlagsFromRemote(state.IsRunning, state.IsPaused);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GameStateEvent.Resumed:
|
||||||
|
if (gameController.IsPaused)
|
||||||
|
{
|
||||||
|
gameController.ResumeGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
gameController.SetElapsedFromRemote(state.Elapsed);
|
||||||
|
gameController.SetRunningFlagsFromRemote(state.IsRunning, state.IsPaused);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GameStateEvent.Stopped:
|
||||||
|
if (gameController.IsGameActive)
|
||||||
|
{
|
||||||
|
gameController.StopGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
lastWaveBroadcast = -1;
|
||||||
|
gameController.SetElapsedFromRemote(state.Elapsed);
|
||||||
|
gameController.SetRunningFlagsFromRemote(state.IsRunning, state.IsPaused);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GameStateEvent.WaveAdvanced:
|
||||||
|
gameController.SetElapsedFromRemote(state.Elapsed);
|
||||||
|
gameController.ApplyRemoteWaveAdvance(state.PreviousWave, state.CurrentWave);
|
||||||
|
lastWaveBroadcast = state.CurrentWave;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GameStateEvent.BossSpawned:
|
||||||
|
gameController.SetElapsedFromRemote(state.Elapsed);
|
||||||
|
if (EnemyDefinitionRegistry.TryGet(state.DefinitionId, out var bossDefinition))
|
||||||
|
{
|
||||||
|
gameController.ApplyRemoteBossSpawned(bossDefinition, state.Count);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GameStateEvent.Heartbeat:
|
||||||
|
gameController.SetElapsedFromRemote(state.Elapsed);
|
||||||
|
gameController.SetRunningFlagsFromRemote(state.IsRunning, state.IsPaused);
|
||||||
|
if (state.CurrentWave >= 0 && state.CurrentWave != gameController.CurrentWaveIndex)
|
||||||
|
{
|
||||||
|
gameController.ApplyRemoteWaveAdvance(gameController.CurrentWaveIndex, state.CurrentWave);
|
||||||
|
lastWaveBroadcast = state.CurrentWave;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshNetworkManager()
|
||||||
|
{
|
||||||
|
if (networkManager == null)
|
||||||
|
{
|
||||||
|
networkManager = SteamCoopNetworkManager.Instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DetermineAuthority()
|
||||||
|
{
|
||||||
|
RefreshNetworkManager();
|
||||||
|
if (networkManager == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!networkManager.IsConnected)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return networkManager.IsHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyAuthorityOverride(bool authority)
|
||||||
|
{
|
||||||
|
gameController?.SetAuthorityOverride(authority);
|
||||||
|
enemySpawner?.SetAuthorityOverride(authority);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterBossDefinitions()
|
||||||
|
{
|
||||||
|
if (gameController?.BossSchedule?.Events == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var bossEvent in gameController.BossSchedule.Events)
|
||||||
|
{
|
||||||
|
if (bossEvent?.Boss == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnemyDefinitionRegistry.Register(bossEvent.Boss);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a4ee45b08060ff11b9c4de6b6ab9d671
|
||||||
@@ -19,7 +19,10 @@ namespace Game.Scripts.Runtime.Game
|
|||||||
|
|
||||||
public float Elapsed => _elapsed;
|
public float Elapsed => _elapsed;
|
||||||
public bool IsRunning => _isRunning && !_isPaused;
|
public bool IsRunning => _isRunning && !_isPaused;
|
||||||
|
public bool IsGameActive => _isRunning;
|
||||||
|
public bool IsPaused => _isPaused;
|
||||||
public int CurrentWaveIndex => _currentWaveIndex;
|
public int CurrentWaveIndex => _currentWaveIndex;
|
||||||
|
public BossSchedule BossSchedule => bossSchedule;
|
||||||
|
|
||||||
public event Action OnGameStarted;
|
public event Action OnGameStarted;
|
||||||
public event Action OnGamePaused;
|
public event Action OnGamePaused;
|
||||||
@@ -35,6 +38,8 @@ namespace Game.Scripts.Runtime.Game
|
|||||||
private bool _isPaused;
|
private bool _isPaused;
|
||||||
private int _currentWaveIndex = -1;
|
private int _currentWaveIndex = -1;
|
||||||
private int _bossCursor;
|
private int _bossCursor;
|
||||||
|
private bool _hasAuthorityOverride;
|
||||||
|
private bool _authorityOverride;
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
@@ -66,9 +71,16 @@ namespace Game.Scripts.Runtime.Game
|
|||||||
spawner?.SetClock(this);
|
spawner?.SetClock(this);
|
||||||
spawner?.Begin();
|
spawner?.Begin();
|
||||||
|
|
||||||
|
if (HasAuthority())
|
||||||
|
{
|
||||||
UpdateWaveState();
|
UpdateWaveState();
|
||||||
|
|
||||||
_loop = StartCoroutine(GameLoop());
|
_loop = StartCoroutine(GameLoop());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_loop = null;
|
||||||
|
}
|
||||||
|
|
||||||
OnGameStarted?.Invoke();
|
OnGameStarted?.Invoke();
|
||||||
Log($"Game started – waveDuration={waveDurationSeconds}s");
|
Log($"Game started – waveDuration={waveDurationSeconds}s");
|
||||||
}
|
}
|
||||||
@@ -268,5 +280,84 @@ namespace Game.Scripts.Runtime.Game
|
|||||||
Debug.Log($"[{name}] {message}", this);
|
Debug.Log($"[{name}] {message}", this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void SetAuthorityOverride(bool hasAuthority)
|
||||||
|
{
|
||||||
|
_hasAuthorityOverride = true;
|
||||||
|
if (_authorityOverride == hasAuthority)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_authorityOverride = hasAuthority;
|
||||||
|
|
||||||
|
if (!HasAuthority() && _loop != null)
|
||||||
|
{
|
||||||
|
StopCoroutine(_loop);
|
||||||
|
_loop = null;
|
||||||
|
}
|
||||||
|
else if (HasAuthority() && _isRunning && _loop == null)
|
||||||
|
{
|
||||||
|
_loop = StartCoroutine(GameLoop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool HasAuthority() => !_hasAuthorityOverride || _authorityOverride;
|
||||||
|
|
||||||
|
internal void SetElapsedFromRemote(float elapsed)
|
||||||
|
{
|
||||||
|
_elapsed = Mathf.Max(0f, elapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetRunningFlagsFromRemote(bool isRunning, bool isPaused)
|
||||||
|
{
|
||||||
|
_isRunning = isRunning;
|
||||||
|
_isPaused = isPaused;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ApplyRemoteWaveAdvance(int previousWave, int newWave)
|
||||||
|
{
|
||||||
|
int oldWave = _currentWaveIndex;
|
||||||
|
|
||||||
|
if (previousWave >= 0 && oldWave == previousWave)
|
||||||
|
{
|
||||||
|
OnWaveCompleted?.Invoke(previousWave);
|
||||||
|
}
|
||||||
|
else if (oldWave >= 0 && oldWave != newWave)
|
||||||
|
{
|
||||||
|
OnWaveCompleted?.Invoke(oldWave);
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentWaveIndex = newWave;
|
||||||
|
if (_currentWaveIndex >= 0)
|
||||||
|
{
|
||||||
|
spawner?.SetWaveIndex(_currentWaveIndex);
|
||||||
|
OnWaveStarted?.Invoke(_currentWaveIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ApplyRemoteWaveStarted(int waveIndex)
|
||||||
|
{
|
||||||
|
if (_currentWaveIndex != waveIndex)
|
||||||
|
{
|
||||||
|
_currentWaveIndex = waveIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_currentWaveIndex >= 0)
|
||||||
|
{
|
||||||
|
spawner?.SetWaveIndex(_currentWaveIndex);
|
||||||
|
OnWaveStarted?.Invoke(_currentWaveIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ApplyRemoteWaveCompleted(int waveIndex)
|
||||||
|
{
|
||||||
|
OnWaveCompleted?.Invoke(waveIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ApplyRemoteBossSpawned(EnemyDefinition boss, int count)
|
||||||
|
{
|
||||||
|
OnBossSpawned?.Invoke(boss, count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using Game.Scripts.Runtime.Abstractions;
|
using Game.Scripts.Runtime.Abstractions;
|
||||||
using Game.Scripts.Runtime.Data;
|
using Game.Scripts.Runtime.Data;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using Unity.Netcode;
|
||||||
|
|
||||||
namespace Game.Scripts.Runtime.Pooling
|
namespace Game.Scripts.Runtime.Pooling
|
||||||
{
|
{
|
||||||
@@ -112,7 +113,7 @@ namespace Game.Scripts.Runtime.Pooling
|
|||||||
}
|
}
|
||||||
|
|
||||||
bucket.Active.Add(go);
|
bucket.Active.Add(go);
|
||||||
go.transform.SetParent(null, true);
|
SetParentSafely(go, null, true);
|
||||||
go.transform.SetPositionAndRotation(position + definition.PrefabPivotOffset, rotation);
|
go.transform.SetPositionAndRotation(position + definition.PrefabPivotOffset, rotation);
|
||||||
go.SetActive(true);
|
go.SetActive(true);
|
||||||
|
|
||||||
@@ -137,7 +138,12 @@ namespace Game.Scripts.Runtime.Pooling
|
|||||||
bucket.Active.Remove(instance);
|
bucket.Active.Remove(instance);
|
||||||
|
|
||||||
instance.SetActive(false);
|
instance.SetActive(false);
|
||||||
instance.transform.SetParent(poolRoot, false);
|
var netObj = instance.GetComponent<NetworkObject>();
|
||||||
|
SetParentSafely(instance, poolRoot, false, netObj, false);
|
||||||
|
if (netObj != null)
|
||||||
|
{
|
||||||
|
Destroy(netObj);
|
||||||
|
}
|
||||||
bucket.Available.Enqueue(instance);
|
bucket.Available.Enqueue(instance);
|
||||||
item.Handle?.NotifyDespawned();
|
item.Handle?.NotifyDespawned();
|
||||||
InstanceDespawned?.Invoke(instance, item.Definition);
|
InstanceDespawned?.Invoke(instance, item.Definition);
|
||||||
@@ -172,7 +178,8 @@ namespace Game.Scripts.Runtime.Pooling
|
|||||||
}
|
}
|
||||||
|
|
||||||
EnsureRoot();
|
EnsureRoot();
|
||||||
var go = Instantiate(definition.Prefab, poolRoot);
|
var go = Instantiate(definition.Prefab);
|
||||||
|
SetParentSafely(go, poolRoot, false);
|
||||||
go.name = $"{definition.Prefab.name}_Pooled";
|
go.name = $"{definition.Prefab.name}_Pooled";
|
||||||
go.SetActive(false);
|
go.SetActive(false);
|
||||||
|
|
||||||
@@ -230,6 +237,38 @@ namespace Game.Scripts.Runtime.Pooling
|
|||||||
root.transform.SetParent(transform, false);
|
root.transform.SetParent(transform, false);
|
||||||
poolRoot = root.transform;
|
poolRoot = root.transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SetParentSafely(GameObject instance, Transform parent, bool worldPositionStays, NetworkObject cachedNetworkObject = null, bool restoreAutoSync = true)
|
||||||
|
{
|
||||||
|
if (instance == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var netObj = cachedNetworkObject != null ? cachedNetworkObject : instance.GetComponent<NetworkObject>();
|
||||||
|
var shouldRestore = false;
|
||||||
|
var previousAutoSync = false;
|
||||||
|
|
||||||
|
if (netObj != null && !HasListeningNetworkManager(netObj))
|
||||||
|
{
|
||||||
|
previousAutoSync = netObj.AutoObjectParentSync;
|
||||||
|
netObj.AutoObjectParentSync = false;
|
||||||
|
shouldRestore = restoreAutoSync;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.transform.SetParent(parent, worldPositionStays);
|
||||||
|
|
||||||
|
if (shouldRestore && netObj != null)
|
||||||
|
{
|
||||||
|
netObj.AutoObjectParentSync = previousAutoSync;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasListeningNetworkManager(NetworkObject netObj)
|
||||||
|
{
|
||||||
|
var manager = netObj != null ? (netObj.NetworkManager != null ? netObj.NetworkManager : NetworkManager.Singleton) : NetworkManager.Singleton;
|
||||||
|
return manager != null && manager.IsListening;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[DisallowMultipleComponent]
|
[DisallowMultipleComponent]
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Game.Scripts.Runtime.Data;
|
|||||||
using Game.Scripts.Runtime.Pooling;
|
using Game.Scripts.Runtime.Pooling;
|
||||||
using Game.Scripts.Runtime.Navigation;
|
using Game.Scripts.Runtime.Navigation;
|
||||||
using MegaKoop.Game;
|
using MegaKoop.Game;
|
||||||
|
using MegaKoop.Game.Networking;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.AI;
|
using UnityEngine.AI;
|
||||||
|
|
||||||
@@ -68,6 +69,8 @@ namespace Game.Scripts.Runtime.Spawning
|
|||||||
private IEnemyFactory _factory;
|
private IEnemyFactory _factory;
|
||||||
private bool _warnedMissingPlayer;
|
private bool _warnedMissingPlayer;
|
||||||
private bool _warnedMissingNavMesh;
|
private bool _warnedMissingNavMesh;
|
||||||
|
private bool _authorityOverrideSet;
|
||||||
|
private bool _authorityOverrideValue = true;
|
||||||
|
|
||||||
private void OnValidate()
|
private void OnValidate()
|
||||||
{
|
{
|
||||||
@@ -170,9 +173,17 @@ namespace Game.Scripts.Runtime.Spawning
|
|||||||
_spawnedThisWave.Clear();
|
_spawnedThisWave.Clear();
|
||||||
_localElapsed = 0f;
|
_localElapsed = 0f;
|
||||||
_nextSpawnTimestamp = 0f;
|
_nextSpawnTimestamp = 0f;
|
||||||
|
if (HasAuthority())
|
||||||
|
{
|
||||||
_spawnLoop = StartCoroutine(SpawnLoop());
|
_spawnLoop = StartCoroutine(SpawnLoop());
|
||||||
LogLifecycle($"Begin – wave {CurrentWaveIndex}, pool prewarm complete, maxConcurrent={_runtimeConfig.MaxConcurrent}");
|
LogLifecycle($"Begin – wave {CurrentWaveIndex}, pool prewarm complete, maxConcurrent={_runtimeConfig.MaxConcurrent}");
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_spawnLoop = null;
|
||||||
|
LogLifecycle($"Begin – follower mode (no spawn loop), wave {CurrentWaveIndex}, maxConcurrent={_runtimeConfig?.MaxConcurrent}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void End()
|
public void End()
|
||||||
{
|
{
|
||||||
@@ -226,6 +237,12 @@ namespace Game.Scripts.Runtime.Spawning
|
|||||||
_localElapsed = _clock.Elapsed;
|
_localElapsed = _clock.Elapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!HasAuthority())
|
||||||
|
{
|
||||||
|
yield return null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (_localElapsed >= _nextSpawnTimestamp)
|
if (_localElapsed >= _nextSpawnTimestamp)
|
||||||
{
|
{
|
||||||
ExecuteSpawnTick();
|
ExecuteSpawnTick();
|
||||||
@@ -640,6 +657,22 @@ namespace Game.Scripts.Runtime.Spawning
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void SetAuthorityOverride(bool hasAuthority)
|
||||||
|
{
|
||||||
|
_authorityOverrideSet = true;
|
||||||
|
_authorityOverrideValue = hasAuthority;
|
||||||
|
|
||||||
|
if (!HasAuthority() && _spawnLoop != null)
|
||||||
|
{
|
||||||
|
StopCoroutine(_spawnLoop);
|
||||||
|
_spawnLoop = null;
|
||||||
|
}
|
||||||
|
else if (HasAuthority() && _spawnLoop == null && _runtimeConfig != null && _runtimeTable != null)
|
||||||
|
{
|
||||||
|
_spawnLoop = StartCoroutine(SpawnLoop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool RequirePlayerReference()
|
private bool RequirePlayerReference()
|
||||||
{
|
{
|
||||||
if (EnsurePlayerReference())
|
if (EnsurePlayerReference())
|
||||||
@@ -706,5 +739,26 @@ namespace Game.Scripts.Runtime.Spawning
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool HasAuthority()
|
||||||
|
{
|
||||||
|
if (_authorityOverrideSet)
|
||||||
|
{
|
||||||
|
return _authorityOverrideValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var manager = SteamCoopNetworkManager.Instance;
|
||||||
|
if (manager == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!manager.IsConnected)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return manager.IsHost;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ MonoBehaviour:
|
|||||||
m_EditorClassIdentifier: Assembly-CSharp::Game.Scripts.Runtime.Data.SpawnTable
|
m_EditorClassIdentifier: Assembly-CSharp::Game.Scripts.Runtime.Data.SpawnTable
|
||||||
entries:
|
entries:
|
||||||
- Def: {fileID: 11400000, guid: 1bc4888fa172eb99f94756653be6c1ed, type: 2}
|
- Def: {fileID: 11400000, guid: 1bc4888fa172eb99f94756653be6c1ed, type: 2}
|
||||||
Weight: 10
|
Weight: 5
|
||||||
MinWave: 10
|
MinWave: 0
|
||||||
MaxWave: 40
|
MaxWave: 10
|
||||||
Cap: 10
|
Cap: 50
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ MonoBehaviour:
|
|||||||
m_EditorClassIdentifier: Assembly-CSharp::Game.Scripts.Runtime.Data.SpawnerConfig
|
m_EditorClassIdentifier: Assembly-CSharp::Game.Scripts.Runtime.Data.SpawnerConfig
|
||||||
MinSpawnRadius: 8
|
MinSpawnRadius: 8
|
||||||
MaxSpawnRadius: 24
|
MaxSpawnRadius: 24
|
||||||
SpawnInterval: 2
|
SpawnInterval: 10
|
||||||
SpawnRateOverTime:
|
SpawnRateOverTime:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
m_Curve:
|
m_Curve:
|
||||||
@@ -39,7 +39,7 @@ MonoBehaviour:
|
|||||||
m_PreInfinity: 2
|
m_PreInfinity: 2
|
||||||
m_PostInfinity: 2
|
m_PostInfinity: 2
|
||||||
m_RotationOrder: 4
|
m_RotationOrder: 4
|
||||||
MaxConcurrent: 30
|
MaxConcurrent: 20
|
||||||
MaxPlacementAttempts: 6
|
MaxPlacementAttempts: 6
|
||||||
PrewarmPerType: 4
|
PrewarmPerType: 4
|
||||||
PoolHardCapPerType: 60
|
PoolHardCapPerType: 60
|
||||||
@@ -55,5 +55,5 @@ MonoBehaviour:
|
|||||||
NavMeshAreaMask: -1
|
NavMeshAreaMask: -1
|
||||||
useSeed: 1
|
useSeed: 1
|
||||||
seedValue: 0
|
seedValue: 0
|
||||||
DrawGizmos: 1
|
DrawGizmos: 0
|
||||||
GizmoSampleCount: 16
|
GizmoSampleCount: 16
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 743d0432c7ac8c691a555633664b7f42
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 08fec3cef01a59840bc3352757c5902a
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 946d6a345d25ee5a4ac39b2e4f4fc7d5
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 461055c25fc0ac12aad6aca9b078522b
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c18dd2a2ad9b70929b5c16d0e41d6be1
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 471c514198bed37fd9284116d5179806
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7a9b28d2e66142e338fccaac4b38ab54
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 404c3ba9b2e2f4d9e92f1203076a27ac
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5f03c271dacccd7838fe3ef9912b3a1d
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b493c55f3d9b7dd109844d107fbc736d
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: eb80a25771da36073b8e99e258928dc4
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 35f19552c16c41a9784dafae0030c705
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5a72258c1ebeb18ef944c22eefb22959
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 10fd9e4d197a4c420bc87ae7c1bb1795
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 612e7d3b2a9b0d024a61498804de9108
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6f5659dffb22f2d58be6edc348f2120c
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9738e0f3db2d4c8249bff68e37aa9955
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e6e29844605285ef3afba4029f9c3dd0
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b24c5acf7c2737f608c2315ac1c43ae3
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f015df6cfb8e7c141bb69128bbf07f36
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c3b988bb70367bd0ca77081c5565a0b8
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d197b958d50776784b025cc8bb26db95
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e9785a9b0ac0eeaf3801225ac8fbc4e1
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c77915bfbd12c8eae96286f1123f9836
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 27ba183aaa1be5c24b9a6b1cabbf5a72
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 16920c5001a27193383d3f39d66020aa
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 266a22bfb73742039a571640d31eafd7
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ff003878a86d7084ea4daee894d6b77a
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 163a06188dba81d07983ebcfa5367ca8
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 578683690548686bd8a36fa39db9a30c
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 20d83a7bb2ed395fb83d4ec7a9c2e661
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 348b05ab0e3576d3294228718d0d8937
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e6f36aae0168306f69987b7951ebd3b1
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 85741d2af2b1065baae0bdcf2e7455e9
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4b781103bbb35b594900f5959dab063f
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a17616b0065626cd2974694e8d515f0c
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3630f7764fc2dd4b698db73c9f5b4488
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d0f797227056f216ea586fd533747640
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 67c9adac75213a3cabad86e62c73df0d
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 83c933666518ed7649fcae5e8f847bb6
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9fee1d5bc83e13cfcbd6a225dbfe7e23
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9d8a8cf9eea4b75059c402cfd48b646a
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 23800000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user