You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Supabase实时日志与Presence事件不同步,玩家位置获取值为undefined

问题背景

我正在实现玩家位置广播功能,核心代码如下:

广播玩家位置

// 订阅操作用于客户端向服务器注册
this.channel.subscribe(async (status: any) => {
  if (status === 'SUBSCRIBED') {
    const status = await this.channel.track({
      online_at: new Date().toISOString(),
      user_id: this.userId
    })
    // 广播玩家位置
    setInterval(() => {
      const player = this.player
      if (player) {
        const key = player.anims.currentAnim?.key;
        const velocityX = player.body.velocity.x;
        const velocityY = player.body.velocity.y;
        const positionX = player.x;
        const positionY = player.y;
        this.channel.send({
          type: 'broadcast',
          event: 'player-pos',
          payload: { 
            velocityX: velocityX, 
            velocityY: velocityY,
            positionX: positionX,
            positionY: positionY, 
            key: key 
          },
        })
      }
    }, 100)
  }
})

监听广播并保存位置

.on('broadcast', { event: 'player-pos' }, (payload: any) => {
        this.playerMap.forEach((obj => {
          if (this.userId !== obj.user_id) {
            // 创建玩家精灵并添加物理属性
            this.otherPlayer?.setVelocity(payload.payload.velocityX, payload.payload.velocityY) 
            if (payload.payload.key) {
              this.otherPlayer?.play(payload.payload.key, true)
            }
            if (obj.user_id && payload.payload.positionX && payload.payload.positionY) {
              this.playerMap.get(obj.user_id).positionX = payload.payload.positionX
              this.playerMap.get(obj.user_id).positionY = payload.payload.positionY
            }
          }
        }))
      })

Presence同步事件处理

.on('presence', { event: 'sync' }, async () => {
    const usersObj = await this.channel.presenceState()
    for (const userId in usersObj) {
      if (!this.playerMap.has(userId)) {
        this.playerMap.set(userId, usersObj[userId][0])
      }
    }
    console.log('同步playerMap', this.playerMap, 'totalPlayers', this.totalPlayers, 'playerMap.size', this.playerMap.size)
    this.playerMap.forEach((obj => {
      // 需要判断this.totalPlayers < this.playerMap.size,因为presence至少会触发两次
      // 第二次连接会创建重复的其他玩家
      if (this.userId !== obj.user_id && this.totalPlayers < this.playerMap.size) {
        // 创建玩家精灵并添加物理属性
        const otherPlayerObj = this.playerMap.get(obj.user_id)
        console.log('otherPlayerObj', otherPlayerObj, 'otherPlayerObj.positionX', otherPlayerObj.positionX)
        this.totalPlayers++
        this.otherPlayer = new Player(
          this,
          otherPlayerObj.positionX,
          otherPlayerObj.positionY,
          'up'
        );
      }
    }))
  })

问题现象

打开两个客户端,先加载第一个客户端,再打开第二个客户端:

  • 第二个客户端能正常看到第一个玩家的位置,控制台日志显示正常;
  • 第一个客户端控制台显示otherPlayerObj存在positionX属性,但获取otherPlayerObj.positionX时返回undefined。

请问这是什么原因导致的?我哪里操作有误?


原因分析及解决方案

核心问题

第一个客户端在Presence同步时拿到的用户数据没有positionX和positionY属性:

  1. 客户端调用track方法时,只上传了online_at和user_id,没有包含初始位置信息;
  2. 第二个客户端连接后触发Presence同步,第一个客户端拿到的是第二个客户端track时上传的仅含基础字段的对象;
  3. 控制台打印otherPlayerObj时看到的positionX,是后续广播事件触发后才添加的属性,但你在Presence同步回调里立刻读取,此时属性还未被赋值,所以返回undefined。

具体修复方案

  1. 修改track方法,上传初始位置
    在首次向服务器注册时,就把玩家的初始位置一并传入,这样其他客户端同步时能直接拿到完整数据:
const status = await this.channel.track({
  online_at: new Date().toISOString(),
  user_id: this.userId,
  positionX: this.player?.x || 0, // 新增初始X位置,加默认值兜底
  positionY: this.player?.y || 0  // 新增初始Y位置,加默认值兜底
})
  1. 优化广播监听的位置更新逻辑
    当前遍历整个playerMap的效率低且逻辑模糊,建议广播时带上user_id,直接定位目标玩家更新:
// 广播时新增user_id字段
this.channel.send({
  type: 'broadcast',
  event: 'player-pos',
  payload: { 
    user_id: this.userId, // 新增
    velocityX: velocityX, 
    velocityY: velocityY,
    positionX: positionX,
    positionY: positionY, 
    key: key 
  },
})

// 监听广播时精准更新
.on('broadcast', { event: 'player-pos' }, (payload: any) => {
  const { user_id, velocityX, velocityY, positionX, positionY, key } = payload.payload;
  if (user_id !== this.userId) {
    const targetPlayer = this.playerMap.get(user_id);
    if (targetPlayer) {
      targetPlayer.positionX = positionX;
      targetPlayer.positionY = positionY;
    }
    this.otherPlayer?.setVelocity(velocityX, velocityY);
    key && this.otherPlayer?.play(key, true);
  }
})
  1. 给位置读取加兜底默认值
    即使存在位置未同步的极端情况,也能避免创建玩家时出现异常:
this.otherPlayer = new Player(
  this,
  otherPlayerObj.positionX || 0,
  otherPlayerObj.positionY || 0,
  'up'
);

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 20:17:02