Animace + Menu

This commit is contained in:
Dominik G.
2025-10-05 18:18:12 +02:00
parent b52b3aa830
commit 550efdfaad
2192 changed files with 602748 additions and 2703 deletions

8
Networking/NGO.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c74f702b13c317f45bf90bfcdb7ab3fe
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
namespace MegaKoop.Networking
{
// Implement this interface on a component attached to your NetworkManager GameObject.
// Use it to configure your chosen Steam transport (SteamNetworkingSockets) for NGO.
public interface ISteamNGOAdapter
{
void ConfigureHost(ulong hostSteamId);
void ConfigureClient(ulong hostSteamId);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ba76ae1e67da5874bb01a8d78d494bfd

View File

@@ -0,0 +1,77 @@
using UnityEngine;
namespace MegaKoop.Networking
{
/// <summary>
/// Thin bootstrap around Netcode for GameObjects start/stop.
/// Uses compile guards so the project compiles without NGO.
/// </summary>
public static class NetworkBootstrap
{
public static bool StartHost(string gameScene = "GameScene")
{
#if UNITY_NETCODE || NETCODE_PRESENT
var nm = Unity.Netcode.NetworkManager.Singleton;
if (nm == null)
{
Debug.LogError("[NetworkBootstrap] NetworkManager.Singleton not found in scene.");
return false;
}
Object.DontDestroyOnLoad(nm.gameObject);
bool ok = nm.StartHost();
if (!ok)
{
Debug.LogError("[NetworkBootstrap] StartHost failed.");
return false;
}
// Prefer NGO SceneManager if available
if (nm.SceneManager != null)
{
nm.SceneManager.LoadScene(gameScene, Unity.Netcode.AdditiveScenes.NetworkSceneManager.LoadSceneMode.Single);
}
else
{
UnityEngine.SceneManagement.SceneManager.LoadScene(gameScene);
}
return true;
#else
Debug.LogWarning("[NetworkBootstrap] Netcode for GameObjects not present. Define UNITY_NETCODE or NETCODE_PRESENT and add the package.");
return false;
#endif
}
public static bool StartClient(string gameScene = "GameScene")
{
#if UNITY_NETCODE || NETCODE_PRESENT
var nm = Unity.Netcode.NetworkManager.Singleton;
if (nm == null)
{
Debug.LogError("[NetworkBootstrap] NetworkManager.Singleton not found in scene.");
return false;
}
Object.DontDestroyOnLoad(nm.gameObject);
bool ok = nm.StartClient();
if (!ok)
{
Debug.LogError("[NetworkBootstrap] StartClient failed.");
return false;
}
// Client will be moved to scene by NGO server scene management
return true;
#else
Debug.LogWarning("[NetworkBootstrap] Netcode for GameObjects not present. Define UNITY_NETCODE or NETCODE_PRESENT and add the package.");
return false;
#endif
}
public static void Stop()
{
#if UNITY_NETCODE || NETCODE_PRESENT
var nm = Unity.Netcode.NetworkManager.Singleton;
if (nm == null) return;
if (nm.IsServer || nm.IsHost) nm.Shutdown(true);
else if (nm.IsClient) nm.Shutdown();
#endif
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1b54f132c1f719e44b5d94bc0252c78f

View File

@@ -0,0 +1,69 @@
#if UNITY_NETCODE || NETCODE_PRESENT
using System;
using System.Reflection;
using UnityEngine;
using Unity.Netcode;
namespace MegaKoop.Networking
{
/// <summary>
/// Reflection-based adapter that tries to configure your Steam transport (attached to NetworkManager)
/// for host/client using provided SteamIDs. If it can't find a known method, it logs a clear instruction.
/// Replace this with a strongly typed adapter when you pick a specific Steam transport.
/// </summary>
public class SteamNGOAdapter : MonoBehaviour, ISteamNGOAdapter
{
private object Transport => NetworkManager.Singleton ? NetworkManager.Singleton.NetworkConfig.NetworkTransport : null;
public void ConfigureHost(ulong hostSteamId)
{
var t = Transport;
if (t == null)
{
Debug.LogWarning("[SteamNGOAdapter] NetworkTransport not found on NetworkManager.");
return;
}
if (TryInvoke(t, "ConfigureHost", hostSteamId)) return;
if (TryInvoke(t, "SetHostSteamId", hostSteamId)) return;
if (TryInvoke(t, "SetServerSteamId", hostSteamId)) return;
if (TryInvoke(t, "InitAsServer", hostSteamId)) return;
Debug.LogWarning("[SteamNGOAdapter] Could not configure host on transport via reflection. Please implement host configuration for your transport.");
}
public void ConfigureClient(ulong hostSteamId)
{
var t = Transport;
if (t == null)
{
Debug.LogWarning("[SteamNGOAdapter] NetworkTransport not found on NetworkManager.");
return;
}
if (TryInvoke(t, "ConfigureClient", hostSteamId)) return;
if (TryInvoke(t, "SetClientSteamId", hostSteamId)) return;
if (TryInvoke(t, "ConnectToSteamId", hostSteamId)) return;
if (TryInvoke(t, "InitAsClient", hostSteamId)) return;
Debug.LogWarning("[SteamNGOAdapter] Could not configure client on transport via reflection. Please implement client configuration for your transport.");
}
private bool TryInvoke(object target, string methodName, ulong steamId)
{
var mi = target.GetType().GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
if (mi == null) return false;
try
{
if (mi.IsStatic)
mi.Invoke(null, new object[] { steamId });
else
mi.Invoke(target, new object[] { steamId });
Debug.Log($"[SteamNGOAdapter] Invoked {target.GetType().Name}.{methodName}({steamId})");
return true;
}
catch (Exception e)
{
Debug.LogWarning($"[SteamNGOAdapter] Invoke {methodName} failed: {e.Message}");
return false;
}
}
}
}
#endif

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4d8309c9ecb40434aa684e073cf0a76b

8
Networking/Steam.meta Normal file
View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 545fa01b0673e0444ac8597399ab2126
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,468 @@
#if STEAMWORKSNET
using System;
using System.Collections;
using System.Collections.Generic;
using Steamworks;
using UnityEngine;
namespace MegaKoop.Steam
{
/// <summary>
/// Steam Lobby Service using Steamworks.NET
/// Handles create/join/leave/invite and lobby member updates.
/// </summary>
public class SteamLobbyService : MonoBehaviour
{
public static SteamLobbyService Instance { get; private set; }
public bool IsInLobby => _lobbyId.m_SteamID != 0 && _inLobby;
public bool IsHost { get; private set; }
public string LobbyCode { get; private set; } = string.Empty;
public int MaxMembers { get; private set; } = 4;
public CSteamID LobbyId => _lobbyId;
public string LobbyIdString => _lobbyId.m_SteamID.ToString();
public string LocalSteamIdString => SteamManager.Initialized ? SteamUser.GetSteamID().m_SteamID.ToString() : "0";
public string HostSteamIdString => IsInLobby ? SteamMatchmaking.GetLobbyData(_lobbyId, "host") : string.Empty;
public event Action OnLobbyCreated;
public event Action OnLobbyEntered;
public event Action OnLobbyLeft;
public event Action OnMembersChanged;
public event Action OnLobbyDataUpdated;
public event Action<string> OnJoinFailed; // reason
public event Action<string> OnAvatarUpdated; // steamId string
private Callback<LobbyCreated_t> _cbLobbyCreated;
private Callback<LobbyEnter_t> _cbLobbyEnter;
private Callback<LobbyChatUpdate_t> _cbLobbyChatUpdate;
private Callback<LobbyDataUpdate_t> _cbLobbyDataUpdate;
private Callback<LobbyMatchList_t> _cbLobbyMatchList;
private Callback<GameLobbyJoinRequested_t> _cbLobbyJoinRequested;
private Callback<GameRichPresenceJoinRequested_t> _cbRichPresenceJoinRequested;
private Callback<GameOverlayActivated_t> _cbOverlayActivated;
private Callback<AvatarImageLoaded_t> _cbAvatarImageLoaded;
private CSteamID _lobbyId;
private bool _inLobby;
private readonly List<CSteamID> _members = new();
private readonly System.Collections.Generic.Dictionary<ulong, Sprite> _avatarCache = new();
private bool _overlayWarned = false;
private bool _lastOverlayActive = false;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
if (!SteamManager.Initialized)
{
Debug.LogWarning("[SteamLobbyService] Steam not initialized. Service is idle.");
return;
}
_cbLobbyCreated = Callback<LobbyCreated_t>.Create(OnLobbyCreatedCb);
_cbLobbyEnter = Callback<LobbyEnter_t>.Create(OnLobbyEnterCb);
_cbLobbyChatUpdate = Callback<LobbyChatUpdate_t>.Create(OnLobbyChatUpdateCb);
_cbLobbyDataUpdate = Callback<LobbyDataUpdate_t>.Create(OnLobbyDataUpdateCb);
_cbLobbyMatchList = Callback<LobbyMatchList_t>.Create(OnLobbyMatchListCb);
_cbLobbyJoinRequested = Callback<GameLobbyJoinRequested_t>.Create(OnLobbyJoinRequestedCb);
_cbRichPresenceJoinRequested = Callback<GameRichPresenceJoinRequested_t>.Create(OnRichPresenceJoinRequestedCb);
_cbOverlayActivated = Callback<GameOverlayActivated_t>.Create(OnOverlayActivatedCb);
_cbAvatarImageLoaded = Callback<AvatarImageLoaded_t>.Create(OnAvatarImageLoadedCb);
}
#region Public API
public void CreateLobby(int maxPlayers, bool isPublic)
{
if (!SteamManager.Initialized) { OnJoinFailed?.Invoke("Steam not initialized"); return; }
MaxMembers = maxPlayers;
ELobbyType type = isPublic ? ELobbyType.k_ELobbyTypePublic : ELobbyType.k_ELobbyTypeFriendsOnly;
SteamMatchmaking.CreateLobby(type, maxPlayers);
}
public void LeaveLobby()
{
if (!IsInLobby) return;
SteamMatchmaking.LeaveLobby(_lobbyId);
_inLobby = false;
_members.Clear();
_lobbyId = default;
IsHost = false;
LobbyCode = string.Empty;
OnLobbyLeft?.Invoke();
}
public void InviteFriends()
{
if (!IsInLobby) return;
_lastOverlayActive = false;
SteamFriends.ActivateGameOverlayInviteDialog(_lobbyId);
StartCoroutine(EnsureOverlayOpenedRoutine());
}
public void OpenFriendsOverlay()
{
SteamFriends.ActivateGameOverlay("Friends");
}
private void OnOverlayActivatedCb(GameOverlayActivated_t cb)
{
_lastOverlayActive = cb.m_bActive != 0;
}
private IEnumerator EnsureOverlayOpenedRoutine()
{
float t = 0f;
const float timeout = 0.5f;
while (t < timeout)
{
if (_lastOverlayActive) yield break;
t += Time.unscaledDeltaTime;
yield return null;
}
if (!_lastOverlayActive)
{
if (!_overlayWarned)
{
Debug.LogWarning("[SteamLobbyService] Overlay did not activate; opening Friends overlay as fallback.");
_overlayWarned = true;
}
SteamFriends.ActivateGameOverlay("Friends");
}
}
public void Kick(string steamId)
{
if (!IsInLobby || !IsHost) return;
if (ulong.TryParse(steamId, out ulong sid))
{
// KickLobbyMember is not exposed in some Steamworks.NET versions.
// Try via reflection, otherwise log a warning and rely on custom policy (client leaves on request).
try
{
var mm = typeof(SteamMatchmaking);
var mi = mm.GetMethod("KickLobbyMember", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
if (mi != null)
{
mi.Invoke(null, new object[] { _lobbyId, new CSteamID(sid) });
return;
}
}
catch { }
// Fallback: set a lobby data key to signal the target client to leave by itself
var key = $"kick_{sid}";
SteamMatchmaking.SetLobbyData(_lobbyId, key, "1");
Debug.LogWarning($"[SteamLobbyService] KickLobbyMember not available; set lobby key '{key}' to signal client to leave.");
}
}
public void SetReady(bool ready)
{
if (!IsInLobby) return;
SteamMatchmaking.SetLobbyMemberData(_lobbyId, "ready", ready ? "1" : "0");
}
public void StartGameSignal()
{
if (!IsInLobby || !IsHost) return;
SteamMatchmaking.SetLobbyData(_lobbyId, "start", "1");
}
public bool IsStartSignaled()
{
if (!IsInLobby) return false;
return SteamMatchmaking.GetLobbyData(_lobbyId, "start") == "1";
}
public bool IsOverlayEnabled() => SteamUtils.IsOverlayEnabled();
public bool TryGetAvatarSprite(string steamId, out Sprite sprite, bool large = true)
{
sprite = null;
if (!ulong.TryParse(steamId, out var sid)) return false;
if (_avatarCache.TryGetValue(sid, out var cached)) { sprite = cached; return true; }
var csid = new CSteamID(sid);
int imageId = large ? SteamFriends.GetLargeFriendAvatar(csid) : SteamFriends.GetSmallFriendAvatar(csid);
if (imageId <= 0)
{
// Ask Steam to fetch persona data (which includes avatar) and try later via callback
SteamFriends.RequestUserInformation(csid, true);
return false;
}
if (!SteamUtils.GetImageSize(imageId, out uint w, out uint h) || w == 0 || h == 0) return false;
byte[] data = new byte[w * h * 4];
if (!SteamUtils.GetImageRGBA(imageId, data, (int)data.Length)) return false;
// Flip vertically (Steam image origin differs from Unity)
var flipped = new byte[data.Length];
int rowSize = (int)w * 4;
for (int y = 0; y < h; y++)
{
int src = (int)((h - 1 - y) * rowSize);
int dst = (int)(y * rowSize);
System.Buffer.BlockCopy(data, src, flipped, dst, rowSize);
}
var tex = new Texture2D((int)w, (int)h, TextureFormat.RGBA32, false);
tex.LoadRawTextureData(flipped);
tex.Apply();
var spr = Sprite.Create(tex, new Rect(0,0, tex.width, tex.height), new Vector2(0.5f,0.5f), 100f);
_avatarCache[sid] = spr;
sprite = spr;
return true;
}
private void OnAvatarImageLoadedCb(AvatarImageLoaded_t cb)
{
// Build and cache sprite, then notify listeners
if (!SteamUtils.GetImageSize(cb.m_iImage, out uint w, out uint h) || w == 0 || h == 0) return;
byte[] data = new byte[w * h * 4];
if (!SteamUtils.GetImageRGBA(cb.m_iImage, data, (int)data.Length)) return;
// Flip vertically
var flipped = new byte[data.Length];
int rowSize = (int)w * 4;
for (int y = 0; y < h; y++)
{
int src = (int)((h - 1 - y) * rowSize);
int dst = (int)(y * rowSize);
System.Buffer.BlockCopy(data, src, flipped, dst, rowSize);
}
var tex = new Texture2D((int)w, (int)h, TextureFormat.RGBA32, false);
tex.LoadRawTextureData(flipped);
tex.Apply();
var spr = Sprite.Create(tex, new Rect(0,0, tex.width, tex.height), new Vector2(0.5f,0.5f), 100f);
var sid = cb.m_steamID.m_SteamID;
_avatarCache[sid] = spr;
OnAvatarUpdated?.Invoke(sid.ToString());
}
public IReadOnlyList<(string steamId, string name, bool online)> GetFriends()
{
var list = new List<(string, string, bool)>();
if (!SteamManager.Initialized) return list;
int count = SteamFriends.GetFriendCount(EFriendFlags.k_EFriendFlagImmediate);
for (int i = 0; i < count; i++)
{
var fid = SteamFriends.GetFriendByIndex(i, EFriendFlags.k_EFriendFlagImmediate);
var name = SteamFriends.GetFriendPersonaName(fid);
var state = SteamFriends.GetFriendPersonaState(fid);
bool online = state != EPersonaState.k_EPersonaStateOffline;
// Proactively request avatar if not ready yet
int imgId = SteamFriends.GetSmallFriendAvatar(fid);
if (imgId <= 0) SteamFriends.RequestUserInformation(fid, true);
list.Add((fid.m_SteamID.ToString(), name, online));
}
return list;
}
private string GetLobbyConnectString()
{
if (!IsInLobby) return string.Empty;
var appId = SteamUtils.GetAppID().m_AppId.ToString();
return $"steam://joinlobby/{appId}/{_lobbyId.m_SteamID}/{SteamUser.GetSteamID().m_SteamID}";
}
public void InviteFriendBySteamId(string steamId)
{
if (!IsInLobby) return;
if (!ulong.TryParse(steamId, out var fid)) return;
var conn = GetLobbyConnectString();
if (string.IsNullOrEmpty(conn)) return;
SteamFriends.SetRichPresence("connect", conn);
SteamFriends.InviteUserToGame(new CSteamID(fid), conn);
Debug.Log($"[SteamLobbyService] Sent game invite to {steamId} using connect string.");
}
public bool IsKickedLocal()
{
if (!IsInLobby) return false;
var key = $"kick_{LocalSteamIdString}";
return SteamMatchmaking.GetLobbyData(_lobbyId, key) == "1";
}
public IReadOnlyList<(string steamId, string name, bool isReady, bool isHost)> GetMembers()
{
var list = new List<(string, string, bool, bool)>();
if (!IsInLobby) return list;
int count = SteamMatchmaking.GetNumLobbyMembers(_lobbyId);
for (int i = 0; i < count; i++)
{
var member = SteamMatchmaking.GetLobbyMemberByIndex(_lobbyId, i);
bool ready = SteamMatchmaking.GetLobbyMemberData(_lobbyId, member, "ready") == "1";
bool isHost = SteamMatchmaking.GetLobbyOwner(_lobbyId) == member;
string name = SteamFriends.GetFriendPersonaName(member);
list.Add((member.m_SteamID.ToString(), name, ready, isHost));
}
return list;
}
public void JoinLobbyByCode(string code)
{
if (!SteamManager.Initialized) { OnJoinFailed?.Invoke("Steam not initialized"); return; }
SteamMatchmaking.AddRequestLobbyListStringFilter("code", code, ELobbyComparison.k_ELobbyComparisonEqual);
SteamMatchmaking.RequestLobbyList();
}
#endregion
#region Callbacks
private void OnLobbyCreatedCb(LobbyCreated_t cb)
{
if (cb.m_eResult != EResult.k_EResultOK)
{
OnJoinFailed?.Invoke($"Create failed: {cb.m_eResult}");
return;
}
_lobbyId = new CSteamID(cb.m_ulSteamIDLobby);
IsHost = true;
_inLobby = true;
LobbyCode = GenerateCode();
SteamMatchmaking.SetLobbyData(_lobbyId, "code", LobbyCode);
SteamMatchmaking.SetLobbyData(_lobbyId, "name", SteamFriends.GetPersonaName() + "'s Lobby");
SteamMatchmaking.SetLobbyData(_lobbyId, "host", SteamUser.GetSteamID().m_SteamID.ToString());
SteamMatchmaking.SetLobbyMemberLimit(_lobbyId, MaxMembers);
SteamMatchmaking.SetLobbyJoinable(_lobbyId, true);
SteamMatchmaking.SetLobbyMemberData(_lobbyId, "ready", "1");
RefreshMembers();
OnLobbyCreated?.Invoke();
OnLobbyEntered?.Invoke();
OnLobbyDataUpdated?.Invoke();
}
private void OnLobbyEnterCb(LobbyEnter_t cb)
{
_lobbyId = new CSteamID(cb.m_ulSteamIDLobby);
_inLobby = true;
IsHost = SteamMatchmaking.GetLobbyOwner(_lobbyId) == SteamUser.GetSteamID();
LobbyCode = SteamMatchmaking.GetLobbyData(_lobbyId, "code");
if (!IsHost)
SteamMatchmaking.SetLobbyMemberData(_lobbyId, "ready", "0");
RefreshMembers();
OnLobbyEntered?.Invoke();
}
private void OnLobbyChatUpdateCb(LobbyChatUpdate_t cb)
{
if (new CSteamID(cb.m_ulSteamIDLobby) != _lobbyId) return;
RefreshMembers();
OnMembersChanged?.Invoke();
}
private void OnLobbyDataUpdateCb(LobbyDataUpdate_t cb)
{
if (new CSteamID(cb.m_ulSteamIDLobby) != _lobbyId) return;
OnLobbyDataUpdated?.Invoke();
}
private void OnLobbyMatchListCb(LobbyMatchList_t cb)
{
int lobbies = (int)cb.m_nLobbiesMatching;
if (lobbies <= 0)
{
OnJoinFailed?.Invoke("No lobby with this code");
return;
}
var id = SteamMatchmaking.GetLobbyByIndex(0);
SteamMatchmaking.JoinLobby(id);
}
private void OnLobbyJoinRequestedCb(GameLobbyJoinRequested_t cb)
{
SteamMatchmaking.JoinLobby(cb.m_steamIDLobby);
}
private void OnRichPresenceJoinRequestedCb(GameRichPresenceJoinRequested_t cb)
{
// Some Steamworks.NET versions expose only m_rgchConnect here.
// We cannot reliably parse lobby id across versions; log and rely on GameLobbyJoinRequested_t for lobby joins.
Debug.Log("[SteamLobbyService] RichPresence join requested - handle via GameLobbyJoinRequested or custom connect string.");
}
#endregion
private void RefreshMembers()
{
_members.Clear();
if (!IsInLobby) return;
int count = SteamMatchmaking.GetNumLobbyMembers(_lobbyId);
for (int i = 0; i < count; i++)
{
_members.Add(SteamMatchmaking.GetLobbyMemberByIndex(_lobbyId, i));
}
}
private static string GenerateCode()
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
System.Text.StringBuilder sb = new System.Text.StringBuilder(6);
var rnd = new System.Random();
for (int i = 0; i < 6; i++) sb.Append(chars[rnd.Next(chars.Length)]);
return sb.ToString();
}
}
}
#else
using System;
using System.Collections.Generic;
using UnityEngine;
namespace MegaKoop.Steam
{
// Safe stub without Steamworks.NET
public class SteamLobbyService : MonoBehaviour
{
public static SteamLobbyService Instance { get; private set; }
public bool IsInLobby => _isInLobby;
public bool IsHost { get; private set; }
public string LobbyCode { get; private set; } = string.Empty;
public int MaxMembers { get; private set; } = 4;
public string LobbyIdString => "0";
public string LocalSteamIdString => "0";
public event Action OnLobbyCreated;
public event Action OnLobbyEntered;
public event Action OnLobbyLeft;
public event Action OnMembersChanged;
public event Action OnLobbyDataUpdated;
public event Action<string> OnJoinFailed;
public event Action<string> OnAvatarUpdated;
private bool _isInLobby;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
Debug.LogWarning("[SteamLobbyService] Stub active. Install Steamworks.NET and define STEAMWORKSNET.");
}
public void CreateLobby(int maxPlayers, bool isPublic)
{
MaxMembers = maxPlayers; IsHost = true; _isInLobby = true; LobbyCode = "AAAAAA";
OnLobbyCreated?.Invoke(); OnLobbyEntered?.Invoke(); OnLobbyDataUpdated?.Invoke();
}
public void LeaveLobby() { _isInLobby = false; IsHost = false; LobbyCode = string.Empty; OnLobbyLeft?.Invoke(); }
public void InviteFriends() { Debug.Log("[SteamLobbyService] InviteFriends stub"); }
public void Kick(string steamId) { Debug.Log($"[SteamLobbyService] Kick stub {steamId}"); }
public void SetReady(bool ready) { Debug.Log($"[SteamLobbyService] SetReady {ready}"); OnLobbyDataUpdated?.Invoke(); }
public void StartGameSignal() { Debug.Log("[SteamLobbyService] StartGame signal"); }
public bool IsStartSignaled() => false;
public bool IsOverlayEnabled() => false;
public bool TryGetAvatarSprite(string steamId, out Sprite sprite, bool large = true) { sprite = null; return false; }
public void OpenFriendsOverlay() { Debug.Log("[SteamLobbyService] OpenFriendsOverlay stub"); }
public IReadOnlyList<(string steamId, string name, bool online)> GetFriends() => new List<(string, string, bool)>();
public void InviteFriendBySteamId(string steamId) { Debug.Log("[SteamLobbyService] InviteFriendBySteamId stub"); }
public IReadOnlyList<(string steamId, string name, bool isReady, bool isHost)> GetMembers() => new List<(string,string,bool,bool)>();
public void JoinLobbyByCode(string code) { _isInLobby = true; IsHost = false; LobbyCode = code; OnLobbyEntered?.Invoke(); }
}
}
#endif

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4845f8cb316c7f740b1c39a4a21e4174

View File

@@ -0,0 +1,77 @@
#if STEAMWORKSNET
using UnityEngine;
using Steamworks;
namespace MegaKoop.Steam
{
/// <summary>
/// Minimal Steam bootstrapper. Keeps SteamAPI initialized and runs callbacks.
/// Add this to your bootstrap scene once.
/// </summary>
public class SteamManager : MonoBehaviour
{
public static SteamManager Instance { get; private set; }
public static bool Initialized { get; private set; }
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
try
{
Initialized = SteamAPI.Init();
}
catch (System.DllNotFoundException e)
{
Debug.LogError($"[SteamManager] Steamworks DLL not found: {e.Message}");
Initialized = false;
}
if (!Initialized)
{
Debug.LogError("[SteamManager] SteamAPI.Init failed. Is Steam running? Is the app configured?");
}
}
private void Update()
{
if (Initialized)
{
SteamAPI.RunCallbacks();
}
}
private void OnDestroy()
{
if (Instance == this)
{
if (Initialized)
{
SteamAPI.Shutdown();
Initialized = false;
}
Instance = null;
}
}
}
}
#else
using UnityEngine;
namespace MegaKoop.Steam
{
public class SteamManager : MonoBehaviour
{
private void Awake()
{
Debug.LogWarning("[SteamManager] STEAMWORKSNET define not set. Install Steamworks.NET and add Scripting Define Symbol 'STEAMWORKSNET'.");
}
}
}
#endif

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c571869ecd8ac364d8c6fc0c27a36a4b