关于移动同步

首先需要知道,不同的游戏类型、玩法,会有不同的移动同步方法。

本项目采用的移动同步方法为:

  1. 所有移动过程均在服务器端计算
  2. 服务器每隔100ms发送一次移动状态包
  3. 客户端根据最新的移动状态包,做追随移动

项目移动代码分析 - ScenePlayer.go / BallPlayer.go

  • 移动请求

    func (this *ScenePlayer) Move(power, angle float64, face uint32) {
    if power != 0 {
        power = 1 // power恒为1,减少移动同步影响因素
    }
    this.Power = power
    this.Face = face
    if power != 0 {
        this.Angle = angle
    }
    if power == 0 {
        this.isRunning = false
    }
    }
    

    服务器收到移动请求后,保存下次移动的角度等信息。怎么会调到该函数,略。

  • 每帧计算移动

    func (this *ScenePlayer) UpdateMove(perTime float64, frameRate float64) {
    if !this.IsLive {
        return
    }
    
    // 玩家球移动
    ball := this.SelfAnimal
    ball.UpdateForce(perTime)
    if ball.Move(perTime, frameRate) {
        ball.FixMapEdge() //修边
        this.isMoved = true
        ball.ResetRect()
    
        if this.isRunning {
            cost := frameRate * float64(consts.FrameTimeMS) * consts.DefaultRunCostMP
            diff := ball.GetMP() - cost
            if diff <= 0 {
                this.isRunning = false
            } else {
                ball.SetMP(diff)
            }
        }
    }
    }
    

    每帧都会计算移动

  • 移动计算细节

    func (this *BallPlayer) Move(perTime float64, frameRate float64) bool {
    // 有推力情况下, 忽略原来速度方向
    if this.HasForce() == true {
      force := this.GetForce()
      pos := this.PhysicObj.GetPostion()
      this.Pos = util.Vector2{float64(pos.X), float64(pos.Y)}
      this.PhysicObj.SetVelocity(&bmath.Vector2{float32(force.X), float32(force.Y)})
      return true
    }
    
    pos := this.PhysicObj.GetPostion()
    this.Pos = util.Vector2{float64(pos.X), float64(pos.Y)}
    
    speed := consts.DefaultBallSpeed
    
    powerMul := util.Clamp(this.player.GetPower(), 0, 1)
    
    if this.player.IsRunning() {
      speed *= consts.DefaultRunRatio
      powerMul = 1
    }
    
    speed *= powerMul
    this.speed = *this.angleVel.MultiMethod(speed)
    
    vel := this.speed
    vel.ScaleBy(frameRate) //几帧执行一次物理tick
    if 0 == this.player.GetPower() {
      this.PhysicObj.SetVelocity(&bmath.Vector2{0, 0})
    } else {
      this.PhysicObj.SetVelocity(&bmath.Vector2{float32(vel.X) / 30, float32(vel.Y) / 30})
    }
    
    return true
    }
    

    这里看似很多代码,实际上就做了3个事情:

    一. 从base/ape中,获取本球的实际位置( pos := this.PhysicObj.GetPostion() )

    二. 确定本帧的期望位移大小

    三. 把算好的期望位移传递给base/ape。( this.PhysicObj.SetVelocity( ... ) )

    在下一帧,base/ape内部会根据传递给它的期望位移,算出实际位移。然后又重复1、2、3这3个步骤

项目移动状态同步代码分析 - ScenePlayer.go

func (this *ScenePlayer) SendSceneMsg() {

    var (
    // ...(无关代码,略)
        Moves         []*usercmd.BallMove
        // ...(无关代码,略)
    )

    // ...(无关代码,略)

    ball := this.SelfAnimal
    if this.isMoved {
        ballmove := this.ScenePlayerPool.MsgBallMove
        ballmove.Id = ball.GetID()
        ballmove.X = int32(ball.Pos.X * bll.MsgPosScaleRate)
        ballmove.Y = int32(ball.Pos.Y * bll.MsgPosScaleRate)

        // angle && face
        if (this.SelfAnimal.HasForce() == false || this.Power == 0) && this.Face != 0 {
            ballmove.Face = uint32(this.Face)
            ballmove.Angle = 0
        } else {
            ballmove.Face = 0
            ballmove.Angle = int32(this.Angle)
        }

        ballmove.State = 0
        if this.isRunning {
            ballmove.State = 2
        }
        if skillid := this.Skill.GetCurSkillId(); skillid != 0 {
            ballmove.State = skillid
        }

        Moves = append(Moves, &ballmove)
    }

    //玩家广播
    for _, other := range this.Others {
        Eats = append(Eats, other.ScenePlayerPool.MsgEats...)
        Hits = append(Hits, other.ScenePlayerPool.MsgHits...)
        if other.isMoved {
            ball = other.SelfAnimal
            ballmove := other.ScenePlayerPool.MsgBallMove
            ballmove.Id = ball.GetID()
            ballmove.X = int32(ball.Pos.X * bll.MsgPosScaleRate)
            ballmove.Y = int32(ball.Pos.Y * bll.MsgPosScaleRate)

            // angle && face
            if (other.SelfAnimal.HasForce() == false || other.Power == 0) && other.Face != 0 {
                ballmove.Face = uint32(other.Face)
                ballmove.Angle = 0
            } else {
                ballmove.Face = 0
                ballmove.Angle = int32(other.Angle)
            }

            ballmove.State = 0
            if other.isRunning {
                ballmove.State = 2
            }
            if skillid := other.Skill.GetCurSkillId(); skillid != 0 {
                ballmove.State = skillid
            }

            if other != this {
                Moves = append(Moves, &ballmove)
            }
        }
    }

    // 玩家视野中的所有消息,发送给自己
    for _, cell := range this.LookCells {
        Moves = append(Moves, cell.MsgMoves...)
    }

    // ...(无关代码,略)

    if len(Moves) != 0 {
        msg := &this.msgPool.MsgSceneUDP
        msg.Moves = Moves
        msg.Frame = this.GetScene().Frame()
        if this.Sess != nil {
            // 优先采用可丢包的原生UDP信道发送
            this.Sess.SendUDPCmd(usercmd.MsgTypeCmd_SceneUDP, msg)
        }
    }
}

每100ms,会把视野内所有球的移动状态信息通过 MsgSceneUDP协议 发给客户端。

results matching ""

    No results matching ""