You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Unity移动端Firebase游戏:如何确保玩家退出游戏时正确标记Firestore中的离线状态

可靠标记玩家离线状态至Firestore的解决方案

嘿,这个问题我之前在开发多人移动端游戏时也踩过坑——只靠Application的焦点事件确实覆盖不了设备断电、App崩溃这类极端场景,毕竟这些情况下客户端根本没机会触发回调。下面分享几个经过实践验证的方案,你可以根据项目需求组合使用:

方案1:Firestore心跳+超时检测(最推荐)

核心思路是让客户端定期向Firestore发送"心跳",其他玩家通过判断心跳的超时时间来标记离线状态:

  • 玩家上线时,在其Firestore文档中添加/更新lastActive字段,值用FieldValue.ServerTimestamp()(确保用服务器时间,避免客户端时间偏差)
  • 客户端每隔固定时间(比如30秒),通过协程或定时器更新这个lastActive字段
  • 其他玩家端监听所有玩家的文档,当某个玩家的lastActive与当前服务器时间的差值超过阈值(比如1分钟),就显示离线图标

Unity实现示例:

using Firebase.Firestore;
using UnityEngine;

public class PlayerHeartbeat : MonoBehaviour
{
    private string _playerId;
    private float _heartbeatInterval = 30f;
    private Coroutine _heartbeatCoroutine;

    void Start()
    {
        _playerId = PlayerData.Instance.PlayerId; // 替换为你的玩家ID获取逻辑
        _heartbeatCoroutine = StartCoroutine(SendHeartbeatLoop());
        // 上线时先初始化一次
        UpdateLastActive();
    }

    IEnumerator SendHeartbeatLoop()
    {
        while (true)
        {
            yield return new WaitForSeconds(_heartbeatInterval);
            UpdateLastActive();
        }
    }

    async void UpdateLastActive()
    {
        try
        {
            var playerDoc = FirebaseFirestore.Instance.Collection("players").Document(_playerId);
            await playerDoc.UpdateAsync("lastActive", FieldValue.ServerTimestamp());
        }
        catch (System.Exception e)
        {
            Debug.LogWarning($"心跳发送失败: {e.Message}");
        }
    }

    void OnDestroy()
    {
        if (_heartbeatCoroutine != null)
        {
            StopCoroutine(_heartbeatCoroutine);
        }
    }
}

优势:完全不依赖客户端的退出事件,即使App崩溃或设备断电,只要心跳停止,其他玩家就能通过超时判断出离线状态;实现简单,无需额外服务。
注意点:阈值时间要设置合理——太短可能因为网络波动误判,太长会导致离线状态更新延迟。

方案2:Firebase Realtime Database的OnDisconnect触发器(兜底方案)

Firebase Realtime Database提供了OnDisconnect() API,当客户端与服务器的连接断开(无论正常还是异常),服务器会自动执行预设的操作。你可以用这个来同步状态到Firestore:

  1. 在Realtime Database中为每个玩家维护一个isOnline字段
  2. 客户端连接时,将isOnline设为true,并调用OnDisconnect().SetValueAsync(false)——这个操作是存在服务器端的,只要连接断开就会触发
  3. 编写一个Cloud Function,监听Realtime Database中isOnline字段的变化,自动同步到Firestore的对应玩家文档中

Unity端设置OnDisconnect示例:

using Firebase.Database;
using UnityEngine;

public class PlayerOnlineStatus : MonoBehaviour
{
    private string _playerId;

    void Start()
    {
        _playerId = PlayerData.Instance.PlayerId;
        var statusRef = FirebaseDatabase.Instance.GetReference($"players/{_playerId}/isOnline");
        
        // 设置在线状态
        statusRef.SetValueAsync(true);
        // 设置断开连接时的操作
        statusRef.OnDisconnect().SetValueAsync(false);
    }
}

优势:能覆盖所有连接断开的场景,包括崩溃、断电;服务器端自动执行,无需客户端干预。
注意点:需要同时使用Realtime Database和Firestore(或直接用Realtime Database存储状态),需要额外配置Cloud Function进行同步。

方案3:客户端多场景事件补充(优化正常退出体验)

作为前两个方案的补充,你可以在客户端监听更多退出/切换事件,让正常情况下的状态更新更及时:

  • 监听Application.quitting:捕获正常退出游戏的场景
  • 监听Application.focusChanged:捕获App切换到后台的场景
  • OnDestroy/OnDisable中添加状态更新逻辑

示例代码:

using Firebase.Firestore;
using UnityEngine;

public class ClientStatusUpdater : MonoBehaviour
{
    private string _playerId;

    void Awake()
    {
        _playerId = PlayerData.Instance.PlayerId;
        Application.quitting += OnApplicationQuitting;
        Application.focusChanged += OnFocusChanged;
    }

    void OnApplicationQuitting()
    {
        UpdatePlayerStatus(false);
    }

    void OnFocusChanged(bool hasFocus)
    {
        // 切换到后台时标记为离线,回到前台标记为在线
        UpdatePlayerStatus(hasFocus);
    }

    async void UpdatePlayerStatus(bool isOnline)
    {
        try
        {
            var playerDoc = FirebaseFirestore.Instance.Collection("players").Document(_playerId);
            await playerDoc.UpdateAsync(
                "isOnline", isOnline,
                "lastActive", FieldValue.ServerTimestamp()
            );
        }
        catch (System.Exception e)
        {
            Debug.LogError($"状态更新失败: {e.Message}");
        }
    }

    void OnDestroy()
    {
        Application.quitting -= OnApplicationQuitting;
        Application.focusChanged -= OnFocusChanged;
    }
}

组合建议

最稳妥的方式是方案1 + 方案2 + 方案3

  • 用方案3处理正常退出/后台切换的即时状态更新
  • 用方案1的心跳超时覆盖网络波动或客户端异常的场景
  • 用方案2的OnDisconnect作为终极兜底,确保任何连接断开的情况都能正确标记离线

内容的提问来源于stack exchange,提问作者Scozirge2

火山引擎 最新活动