HarmonyOS开发:游戏网络——多人联机与实时同步

举报
Jack20 发表于 2026/06/28 21:00:48 2026/06/28
【摘要】 HarmonyOS开发:游戏网络——多人联机与实时同步📌 核心要点:多人游戏的核心矛盾是"网络有延迟但游戏要即时",状态同步适合MMO、帧同步适合格斗RTS,延迟补偿和断线重连是上线前必须搞定的两个硬骨头。 背景与动机你有没有在手机上玩过多人联机游戏?开个房间,等朋友进来,一起打怪或者互殴。看起来很简单对吧?但你真上手做,就会发现一堆让人头秃的问题:玩家A看到的位置和玩家B看到的位置不一...

HarmonyOS开发:游戏网络——多人联机与实时同步

📌 核心要点:多人游戏的核心矛盾是"网络有延迟但游戏要即时",状态同步适合MMO、帧同步适合格斗RTS,延迟补偿和断线重连是上线前必须搞定的两个硬骨头。

背景与动机

你有没有在手机上玩过多人联机游戏?开个房间,等朋友进来,一起打怪或者互殴。

看起来很简单对吧?但你真上手做,就会发现一堆让人头秃的问题:

  • 玩家A看到的位置和玩家B看到的位置不一样,谁是对的?
  • 网络延迟200ms,角色操作要等0.2秒才生效,这谁受得了?
  • 玩家打着打着突然断网了,怎么处理?
  • 两个玩家同时开枪打中对方,谁先死?

这些问题,每一个都足以让你的多人游戏变成"没法玩的游戏"。

游戏网络编程和普通网络编程完全不是一回事。普通应用可以容忍几秒的延迟,游戏超过100ms就难受。普通应用断线重连就行,游戏断线意味着整局作废。

核心原理

两种同步模型

多人游戏有两种根本不同的同步思路:

graph TB
    A[多人游戏同步] --> B[状态同步]
    A --> C[帧同步]
    
    B --> B1[服务器计算所有逻辑]
    B --> B2[客户端只发输入]
    B --> B3[服务器广播结果状态]
    B --> B4[客户端插值渲染]
    
    C --> C1[所有客户端执行相同逻辑]
    C --> C2[只同步输入指令]
    C --> C3[确定性随机数]
    C --> C4[浮点数一致性]
    
    classDef mainStyle fill:#8E44AD,stroke:#6C3483,color:#fff,font-weight:bold
    classDef stateStyle fill:#E74C3C,stroke:#C0392B,color:#fff
    classDef lockStyle fill:#3498DB,stroke:#2980B9,color:#fff
    
    class A mainStyle
    class B,B1,B2,B3,B4 stateStyle
    class C,C1,C2,C3,C4 lockStyle
对比维度 状态同步 帧同步
逻辑执行方 服务器 所有客户端
网络带宽 大(传状态数据) 小(只传输入)
作弊难度 难(服务器权威) 易(客户端可篡改)
开发难度 中等 高(确定性要求)
适用类型 MMO、FPS、MOBA 格斗、RTS、棋类
回放功能 需额外记录 天然支持

选哪种?看你的游戏类型。需要服务器权威的(防作弊、大规模玩家)选状态同步;需要精确操作还原的(格斗、策略)选帧同步。

WebSocket实时通信

鸿蒙上做实时多人游戏,WebSocket是首选。HTTP太慢,TCP太底层,WebSocket刚好——全双工、低延迟、API简单。

连接流程:
客户端 → WebSocket握手 → 服务器
客户端 ← 连接确认 ← 服务器
客户端 → 游戏加入请求 → 服务器
客户端 ← 房间状态 ← 服务器
客户端 ↔ 游戏数据双向传输 ↔ 服务器

延迟补偿技术

网络延迟是客观存在的,你没法消除它,只能"补偿"它。

常用技术:

  1. 客户端预测(Client Prediction):玩家操作后,客户端先本地执行,不等服务器确认。如果服务器结果和预测一致,玩家感觉零延迟;如果不一致,回滚到服务器状态。

  2. 服务器回溯(Server Rewind):服务器收到玩家操作时,回溯到玩家发出操作时的游戏状态,在那个状态下计算结果。

  3. 插值平滑(Interpolation):其他玩家的位置不是直接跳到最新状态,而是从旧位置平滑插值到新位置,消除抖动。

  4. 外推预测(Extrapolation):如果服务器数据延迟了,根据之前的运动方向预测当前位置。

断线重连策略

断线重连不是"重新连接"那么简单。玩家断线期间游戏还在继续,回来后要同步到最新状态。

graph LR
    A[检测断线] --> B[暂停该玩家逻辑]
    B --> C[保存断线时刻状态]
    C --> D[尝试重连]
    D -->|成功| E[同步最新状态]
    D -->|超时| F[踢出房间]
    E --> G[恢复游戏]
    
    classDef stepStyle fill:#E67E22,stroke:#D35400,color:#fff,font-weight:bold
    classDef successStyle fill:#27AE60,stroke:#229954,color:#fff
    classDef failStyle fill:#E74C3C,stroke:#C0392B,color:#fff
    
    class A,B,C,D stepStyle
    class E,G successStyle
    class F failStyle

代码实战

基础用法:WebSocket通信层

先搞定最基础的——能连、能发、能收。

// GameWebSocket.ets - WebSocket通信层
import { webSocket } from '@kit.NetworkKit'

// 网络消息类型
enum MsgType {
  JOIN = 'join',           // 加入房间
  LEAVE = 'leave',         // 离开房间
  INPUT = 'input',         // 玩家输入
  STATE = 'state',         // 游戏状态
  CHAT = 'chat',           // 聊天消息
  PING = 'ping',           // 心跳
  PONG = 'pong',           // 心跳回复
  RECONNECT = 'reconnect', // 重连请求
  SYNC = 'sync'            // 全量同步
}

// 网络消息
interface NetMessage {
  type: MsgType
  seq: number        // 序列号
  timestamp: number  // 时间戳
  data: string       // JSON数据
}

// 连接状态
enum ConnState {
  DISCONNECTED,
  CONNECTING,
  CONNECTED,
  RECONNECTING
}

// WebSocket管理器
class GameWebSocket {
  private ws: webSocket.WebSocket | null = null
  private url: string = ''
  private state: ConnState = ConnState.DISCONNECTED
  private seq: number = 0
  private lastPingTime: number = 0
  private latency: number = 0       // 往返延迟(ms)
  private heartbeatTimer: number = -1
  private reconnectTimer: number = -1
  private reconnectAttempts: number = 0
  private maxReconnectAttempts: number = 5

  // 回调
  private onConnected?: () => void
  private onDisconnected?: (reason: string) => void
  private onMessage?: (msg: NetMessage) => void
  private onLatencyUpdate?: (ms: number) => void

  // 设置回调
  setCallbacks(
    onConnected: () => void,
    onDisconnected: (reason: string) => void,
    onMessage: (msg: NetMessage) => void,
    onLatencyUpdate?: (ms: number) => void
  ): void {
    this.onConnected = onConnected
    this.onDisconnected = onDisconnected
    this.onMessage = onMessage
    this.onLatencyUpdate = onLatencyUpdate
  }

  // 连接
  async connect(url: string): Promise<boolean> {
    if (this.state === ConnState.CONNECTED || this.state === ConnState.CONNECTING) {
      return false
    }

    this.url = url
    this.state = ConnState.CONNECTING

    try {
      this.ws = webSocket.createWebSocket()

      // 注册事件
      this.ws.on('open', () => {
        this.state = ConnState.CONNECTED
        this.reconnectAttempts = 0
        this.startHeartbeat()
        if (this.onConnected) this.onConnected()
      })

      this.ws.on('message', (err: Error, data: string | ArrayBuffer) => {
        if (err) return
        const msg = this.parseMessage(typeof data === 'string' ? data : '')
        if (msg) {
          // 处理PONG
          if (msg.type === MsgType.PONG) {
            this.latency = Date.now() - this.lastPingTime
            if (this.onLatencyUpdate) this.onLatencyUpdate(this.latency)
            return
          }
          if (this.onMessage) this.onMessage(msg)
        }
      })

      this.ws.on('close', () => {
        this.stopHeartbeat()
        if (this.state === ConnState.CONNECTED) {
          this.state = ConnState.DISCONNECTED
          if (this.onDisconnected) this.onDisconnected('连接关闭')
          this.tryReconnect()
        }
      })

      this.ws.on('error', () => {
        this.state = ConnState.DISCONNECTED
        if (this.onDisconnected) this.onDisconnected('连接错误')
        this.tryReconnect()
      })

      await this.ws.connect(url)
      return true
    } catch (e) {
      console.error('WebSocket连接失败: ' + e)
      this.state = ConnState.DISCONNECTED
      return false
    }
  }

  // 断开连接
  async disconnect(): Promise<void> {
    this.stopHeartbeat()
    this.stopReconnect()
    this.state = ConnState.DISCONNECTED

    if (this.ws) {
      await this.ws.close()
      this.ws = null
    }
  }

  // 发送消息
  send(type: MsgType, data: object): boolean {
    if (this.state !== ConnState.CONNECTED || !this.ws) return false

    const msg: NetMessage = {
      type,
      seq: ++this.seq,
      timestamp: Date.now(),
      data: JSON.stringify(data)
    }

    try {
      this.ws.send(JSON.stringify(msg))
      return true
    } catch (e) {
      console.error('发送消息失败: ' + e)
      return false
    }
  }

  // 解析消息
  private parseMessage(raw: string): NetMessage | null {
    try {
      return JSON.parse(raw) as NetMessage
    } catch {
      return null
    }
  }

  // 心跳
  private startHeartbeat(): void {
    this.heartbeatTimer = setInterval(() => {
      if (this.state === ConnState.CONNECTED) {
        this.lastPingTime = Date.now()
        this.send(MsgType.PING, {})
      }
    }, 3000) // 每3秒一次心跳
  }

  private stopHeartbeat(): void {
    if (this.heartbeatTimer !== -1) {
      clearInterval(this.heartbeatTimer)
      this.heartbeatTimer = -1
    }
  }

  // 重连
  private tryReconnect(): void {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      if (this.onDisconnected) this.onDisconnected('重连失败')
      return
    }

    this.state = ConnState.RECONNECTING
    this.reconnectAttempts++

    const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts - 1), 10000) // 指数退避
    this.reconnectTimer = setTimeout(() => {
      this.connect(this.url)
    }, delay)
  }

  private stopReconnect(): void {
    if (this.reconnectTimer !== -1) {
      clearTimeout(this.reconnectTimer)
      this.reconnectTimer = -1
    }
    this.reconnectAttempts = 0
  }

  // 获取延迟
  getLatency(): number {
    return this.latency
  }

  // 获取连接状态
  getState(): ConnState {
    return this.state
  }

  isConnected(): boolean {
    return this.state === ConnState.CONNECTED
  }
}

进阶用法:状态同步系统

状态同步的核心:客户端只发输入,服务器算好状态再广播回来。

// StateSync.ets - 状态同步系统

// 玩家输入数据
interface PlayerInput {
  moveX: number      // 移动方向 -1~1
  moveY: number      // 移动方向 -1~1
  attacking: boolean // 是否攻击
  jumping: boolean   // 是否跳跃
  seq: number        // 输入序列号
  timestamp: number  // 客户端时间戳
}

// 游戏状态快照
interface GameSnapshot {
  frame: number           // 帧号
  timestamp: number       // 服务器时间戳
  players: PlayerState[]  // 所有玩家状态
  entities: EntityState[] // 游戏实体状态
}

// 玩家状态
interface PlayerState {
  id: string
  x: number
  y: number
  vx: number
  vy: number
  hp: number
  facing: number   // 朝向角度
  animState: string // 动画状态
  lastProcessedInput: number // 服务器最后处理的输入序号
}

// 实体状态
interface EntityState {
  id: string
  type: string
  x: number
  y: number
  active: boolean
}

// 状态同步客户端
class StateSyncClient {
  private ws: GameWebSocket = new GameWebSocket()
  private playerId: string = ''
  private inputSeq: number = 0

  // 客户端预测
  private pendingInputs: PlayerInput[] = []  // 已发送但未确认的输入
  private predictedState: PlayerState | null = null

  // 服务器状态插值
  private serverStates: GameSnapshot[] = []   // 服务器状态缓冲
  private renderState: GameSnapshot | null = null
  private interpolationDelay: number = 100    // 插值延迟(ms)

  // 回调
  private onStateUpdate?: (snapshot: GameSnapshot) => void

  constructor() {
    this.setupWsCallbacks()
  }

  // 设置WebSocket回调
  private setupWsCallbacks(): void {
    this.ws.setCallbacks(
      () => {
        console.info('已连接到服务器')
        // 发送加入房间请求
        this.ws.send(MsgType.JOIN, { playerId: this.playerId })
      },
      (reason) => {
        console.error('连接断开: ' + reason)
      },
      (msg) => {
        this.handleServerMessage(msg)
      },
      (ms) => {
        // 根据延迟调整插值延迟
        this.interpolationDelay = Math.max(50, ms * 1.5)
      }
    )
  }

  // 连接服务器
  async connect(url: string, playerId: string): Promise<boolean> {
    this.playerId = playerId
    return await this.ws.connect(url)
  }

  // 发送玩家输入
  sendInput(input: Omit<PlayerInput, 'seq' | 'timestamp'>): void {
    const fullInput: PlayerInput = {
      ...input,
      seq: ++this.inputSeq,
      timestamp: Date.now()
    }

    // 发送到服务器
    this.ws.send(MsgType.INPUT, fullInput)

    // 保存到待确认列表(用于客户端预测)
    this.pendingInputs.push(fullInput)

    // 客户端预测:立即应用输入
    if (this.predictedState) {
      this.applyInputToState(this.predictedState, fullInput)
    }
  }

  // 处理服务器消息
  private handleServerMessage(msg: NetMessage): void {
    switch (msg.type) {
      case MsgType.STATE:
        const snapshot = JSON.parse(msg.data) as GameSnapshot
        this.processServerState(snapshot)
        break

      case MsgType.SYNC:
        const fullSync = JSON.parse(msg.data) as GameSnapshot
        this.processFullSync(fullSync)
        break
    }
  }

  // 处理服务器状态更新
  private processServerState(snapshot: GameSnapshot): void {
    // 缓存服务器状态
    this.serverStates.push(snapshot)

    // 只保留最近的状态
    if (this.serverStates.length > 10) {
      this.serverStates.shift()
    }

    // 找到自己的服务器状态
    const myServerState = snapshot.players.find(p => p.id === this.playerId)
    if (!myServerState) return

    // 服务器已确认的输入序号
    const lastConfirmed = myServerState.lastProcessedInput

    // 清除已确认的输入
    this.pendingInputs = this.pendingInputs.filter(inp => inp.seq > lastConfirmed)

    // 用服务器状态作为基准,重新应用未确认的输入
    this.predictedState = { ...myServerState }
    for (const inp of this.pendingInputs) {
      this.applyInputToState(this.predictedState, inp)
    }
  }

  // 处理全量同步(断线重连后)
  private processFullSync(snapshot: GameSnapshot): void {
    this.serverStates = [snapshot]
    this.pendingInputs = []
    this.predictedState = snapshot.players.find(p => p.id === this.playerId) ?? null
    if (this.onStateUpdate) this.onStateUpdate(snapshot)
  }

  // 应用输入到状态(客户端预测)
  private applyInputToState(state: PlayerState, input: PlayerInput): void {
    const speed = 200
    const dt = 1 / 60

    state.vx = input.moveX * speed
    state.vy = input.moveY * speed
    state.x += state.vx * dt
    state.y += state.vy * dt

    if (input.moveX !== 0 || input.moveY !== 0) {
      state.facing = Math.atan2(input.moveY, input.moveX)
    }
  }

  // 获取渲染用的插值状态
  getInterpolatedState(): GameSnapshot | null {
    if (this.serverStates.length < 2) {
      return this.serverStates[0] ?? null
    }

    const renderTime = Date.now() - this.interpolationDelay

    // 找到插值前后两个状态
    let prev: GameSnapshot | null = null
    let next: GameSnapshot | null = null

    for (let i = 0; i < this.serverStates.length - 1; i++) {
      if (this.serverStates[i].timestamp <= renderTime &&
          this.serverStates[i + 1].timestamp >= renderTime) {
        prev = this.serverStates[i]
        next = this.serverStates[i + 1]
        break
      }
    }

    if (!prev || !next) {
      return this.serverStates[this.serverStates.length - 1]
    }

    // 线性插值
    const t = (renderTime - prev.timestamp) / (next.timestamp - prev.timestamp)
    return this.interpolateSnapshots(prev, next, t)
  }

  // 状态快照插值
  private interpolateSnapshots(a: GameSnapshot, b: GameSnapshot, t: number): GameSnapshot {
    const players: PlayerState[] = []

    for (const pa of a.players) {
      const pb = b.players.find(p => p.id === pa.id)
      if (!pb) continue

      // 自己用预测状态,其他人用插值
      if (pa.id === this.playerId && this.predictedState) {
        players.push({ ...this.predictedState })
      } else {
        players.push({
          ...pa,
          x: pa.x + (pb.x - pa.x) * t,
          y: pa.y + (pb.y - pa.y) * t,
          hp: pb.hp // HP不插值
        })
      }
    }

    return {
      frame: b.frame,
      timestamp: b.timestamp,
      players,
      entities: b.entities
    }
  }

  // 设置状态更新回调
  setOnStateUpdate(cb: (snapshot: GameSnapshot) => void): void {
    this.onStateUpdate = cb
  }

  // 断开连接
  async disconnect(): Promise<void> {
    await this.ws.disconnect()
  }
}

完整示例:多人房间管理

把WebSocket、状态同步、房间管理串起来:

// GameRoom.ets - 多人房间管理
import { webSocket } from '@kit.NetworkKit'

// 房间状态
interface RoomState {
  roomId: string
  players: RoomPlayer[]
  maxPlayers: number
  gameStarted: boolean
  hostId: string
}

// 房间玩家
interface RoomPlayer {
  id: string
  name: string
  ready: boolean
  latency: number
}

// 多人游戏管理器
class MultiplayerManager {
  private syncClient: StateSyncClient = new StateSyncClient()
  private roomState: RoomState | null = null
  private playerId: string = ''
  private playerName: string = ''

  // 回调
  private onRoomUpdate?: (room: RoomState) => void
  private onGameStart?: () => void
  private onPlayerJoin?: (player: RoomPlayer) => void
  private onPlayerLeave?: (playerId: string) => void
  private onGameSnapshot?: (snapshot: GameSnapshot) => void

  // 初始化
  async init(playerName: string): Promise<void> {
    this.playerId = 'player_' + Date.now()
    this.playerName = playerName
  }

  // 创建房间
  async createRoom(maxPlayers: number = 4): Promise<string | null> {
    const serverUrl = 'ws://game-server.example.com/ws'
    const connected = await this.syncClient.connect(serverUrl, this.playerId)
    if (!connected) return null

    const roomId = 'room_' + Math.random().toString(36).substring(2, 8)
    this.roomState = {
      roomId,
      players: [{ id: this.playerId, name: this.playerName, ready: false, latency: 0 }],
      maxPlayers,
      gameStarted: false,
      hostId: this.playerId
    }

    return roomId
  }

  // 加入房间
  async joinRoom(roomId: string): Promise<boolean> {
    const serverUrl = 'ws://game-server.example.com/ws'
    const connected = await this.syncClient.connect(serverUrl, this.playerId)
    if (!connected) return false

    // 发送加入请求
    this.syncClient.send(MsgType.JOIN, { roomId, playerId: this.playerId, name: this.playerName })
    return true
  }

  // 准备/取消准备
  toggleReady(): void {
    if (!this.roomState) return
    const me = this.roomState.players.find(p => p.id === this.playerId)
    if (me) me.ready = !me.ready
  }

  // 开始游戏(仅房主可操作)
  startGame(): void {
    if (!this.roomState || this.roomState.hostId !== this.playerId) return

    // 检查所有人是否准备
    const allReady = this.roomState.players.every(p => p.ready)
    if (!allReady) {
      console.warn('还有玩家未准备')
      return
    }

    this.syncClient.send(MsgType.INPUT, { action: 'start_game' })
  }

  // 发送游戏输入
  sendGameInput(moveX: number, moveY: number, attacking: boolean): void {
    this.syncClient.sendInput({ moveX, moveY, attacking, jumping: false })
  }

  // 获取当前游戏状态
  getCurrentSnapshot(): GameSnapshot | null {
    return this.syncClient.getInterpolatedState()
  }

  // 离开房间
  async leaveRoom(): Promise<void> {
    this.syncClient.send(MsgType.LEAVE, { playerId: this.playerId })
    await this.syncClient.disconnect()
    this.roomState = null
  }

  // 设置回调
  setCallbacks(
    onRoomUpdate: (room: RoomState) => void,
    onGameStart: () => void,
    onSnapshot: (snapshot: GameSnapshot) => void
  ): void {
    this.onRoomUpdate = onRoomUpdate
    this.onGameStart = onGameStart
    this.onGameSnapshot = onSnapshot

    this.syncClient.setOnStateUpdate((snapshot) => {
      if (this.onGameSnapshot) this.onGameSnapshot(snapshot)
    })
  }

  getRoomState(): RoomState | null {
    return this.roomState
  }

  getPlayerId(): string {
    return this.playerId
  }
}

踩坑与注意事项

坑1:WebSocket在后台断开

鸿蒙应用切到后台后,WebSocket连接可能被系统回收。你需要在onBackground回调里暂停游戏逻辑,在onForeground里检查连接状态,断了就重连。

坑2:状态同步的带宽

每个玩家状态包含位置、速度、HP等数据,10个玩家每秒20次同步,带宽就是10×20×100字节=20KB/s。在4G网络下没问题,但弱网环境下可能扛不住。优化方法:只同步变化的数据(增量同步),位置用更短的整数表示。

帧同步的浮点数一致性

帧同步要求所有客户端的计算结果完全一致。但浮点数运算在不同CPU架构上可能有微小差异,导致"蝴蝶效应"——初始状态相同,几百帧后状态完全不同。解决方案:用定点数代替浮点数,或者用整数运算。

坑4:客户端预测的回滚

客户端预测和服务器结果不一致时,需要回滚到服务器状态再重放未确认的输入。如果回滚幅度太大(比如服务器延迟了1秒才响应),画面会"跳"一下。优化方法:限制回滚范围,超过就做平滑过渡。

坑5:房间状态的一致性

房间的创建、加入、退出这些操作,如果多个玩家同时操作,可能出现状态不一致。比如两个人同时加入最后一个名额。解决方案:所有房间操作都通过服务器处理,客户端只做展示。

HarmonyOS 6适配说明

HarmonyOS 6在网络方面有几个重要更新:

  1. QUIC协议支持:新增了@ohos.net.quic模块,支持QUIC协议。相比TCP+WebSocket,QUIC的连接建立更快(0-RTT),弱网恢复更快,更适合游戏场景。
// HarmonyOS 6 QUIC连接示例
import { quic } from '@ohos.net.quic'

const connection = await quic.connect({
  host: 'game-server.example.com',
  port: 443,
  maxIdleTimeout: 10000,
  keepAliveInterval: 3000
})

// 发送数据
connection.send(new Uint8Array([...]))

// 接收数据
connection.on('data', (data: Uint8Array) => {
  this.processGameData(data)
})
  1. 弱网检测API:新增了网络质量检测能力,可以实时获取网络延迟、丢包率、带宽等指标。根据网络质量自动调整同步频率。

  2. 本地发现协议:HarmonyOS 6支持mDNS/DNS-SD本地设备发现。同一WiFi下的鸿蒙设备可以自动发现彼此,实现局域网联机,不需要中心服务器。

  3. 分布式软总线:利用鸿蒙的分布式能力,多设备之间可以直接通信,延迟更低。适合同一家庭内的多人游戏场景。

总结

多人游戏网络编程是游戏开发中最难的部分之一。状态同步和帧同步是两种根本不同的思路,各有优劣。WebSocket是鸿蒙上做实时通信的首选方案。

延迟是客观存在的,你没法消除它,只能补偿它。客户端预测让本地操作"零延迟",服务器插值让其他玩家"看起来流畅",断线重连让掉线的玩家"能回来"。

网络编程没有银弹,只有不断调试和优化的过程。先让基本功能跑通,再逐步优化延迟和稳定性。

评估维度 学习难度 使用频率 重要程度
WebSocket通信 ★★★☆☆ ★★★★★ ★★★★★
状态同步 ★★★★★ ★★★★☆ ★★★★★
帧同步 ★★★★★ ★★★☆☆ ★★★★☆
客户端预测 ★★★★★ ★★★★☆ ★★★★★
插值平滑 ★★★★☆ ★★★★☆ ★★★★☆
断线重连 ★★★★☆ ★★★★☆ ★★★★★
房间管理 ★★★☆☆ ★★★★★ ★★★★☆

下一篇讲游戏性能优化——帧率稳定与内存控制。游戏能跑和跑得流畅是两码事。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。