Go从入门到精通——结构体(struct)——示例:二维矢量模拟玩家移动

示例:二维矢量模拟玩家移动

  在游戏中,一般使用二维矢量保存玩家的位置。使用矢量运算可以计算出玩家移动的位置。本例子中,首先实现二维矢量对象,接着构造玩家对象,最后使用矢量对象和玩家对象共同模拟玩家移动的过程。

1、实现二维矢量结构

  矢量是数学中的概念,二维矢量拥有两个方向的信息,同时可以进行加、减、乘(缩放)、距离、单位化等计算。

  在计算机中,使用拥有 X 和 Y 两个分量的 Vec2 结构体实现数学中二维向量的概念。

package main

import "math"

type Vec2 struct {
	X, Y float32
}

//使用矢量加上另外一个矢量,生成新的矢量
func (v Vec2) Add(other Vec2) Vec2 {
	return Vec2{
		v.X + other.X,
		v.Y + other.Y,
	}
}

//使用矢量减去另外一个矢量,生成新的矢量
func (v Vec2) subtraction(other Vec2) Vec2 {
	return Vec2{
		v.X - other.X,
		v.Y - other.Y,
	}
}

//使用矢量乘以另外一个矢量,生成新的矢量
func (v Vec2) Scale(s float32) Vec2 {
	return Vec2{
		v.X * s,
		v.Y * s,
	}
}

//计算两个矢量的距离
func (v Vec2) DistanceTo(other Vec2) float32 {
	dx := v.X - other.X
	dy := v.X - other.Y

	return float32(math.Sqrt(float64(dx*dx + dy*dy)))

}

//返回当前矢量的标准化矢量
func (v Vec2) Normalize() Vec2 {
	mag := v.X*v.X + v.Y*v.Y
	if mag > 0 {
		oneOverMag := 1 / float32(math.Sqrt(float64(mag))) //math.Sqrt()开方函数
		return Vec2{v.X * oneOverMag, v.Y * oneOverMag}
	}
	return Vec2{0, 0}
}

2、实现玩家对象

  玩家对象负责存储玩家的当前位置、目标位置和速度。使用 MoveTo() 方法为玩家设定移动的目标,使用 Update() 方法更新玩家位置。在 Update() 方法中,通过一系列的矢量计算获得玩家移动后的新位置,步骤如下:

  (1) 使用矢量减法,将目标位置(targetPos)减去当前位置(currPos)即可计算出位于两个位置之间的新矢量,如下图:

  (2) 使用 Normalize() 方法将方向矢量变为模为 1 的单位化矢量。这里需要将矢量单位化后才能进行后续计算,如下图:

  (3) 获得方向后,将单位化方向矢量根据速度进行等比缩放,速度越快,速度数值越大,乘上方向后生成的矢量就越大(模很大),如下图:

  (4) 获得方向后,将单位化方向矢量根据速度进行等比缩放,速度越快,速度数值越大,乘上方向后生成的矢量就越大(模很大),如下图:

package main

type Player struct {
	currPos   Vec2    //当前位置
	targetPos Vec2    //目标位置
	speed     float32 //移动速度
}

//设置玩家移动的目标位置
func (p *Player) MoveTo(v Vec2) {
	p.targetPos = v
}

//获取当前的位置
func (p *Player) Pos() Vec2 {
	return p.currPos
}

//判断是否到达目的地
func (p *Player) IsArrived() bool {
	//通过计算当前玩家位置与目标位置的距离不超过移动的不长,判断已经到达目标点
	return p.currPos.DistanceTo(p.targetPos) < p.speed
}

//更新玩家的位置
func (p *Player) Update() {

	if !p.IsArrived() {

		//计算出当前位置指向目标位的朝向
		dir := p.targetPos.subtraction(p.currPos).Normalize()

		//添加速度矢量生成新的位置
		newPos := p.currPos.Add(dir.Scale(p.speed))

		//移动完成后,更新当前位置
		p.currPos = newPos
	}
}

//创建新玩家
func NewPlayer(speed float32) *Player {

	return &Player{
		speed: speed,
	}
}

3、处理移动逻辑

  将 Player 实例化后,设定玩家移动的最终目标点。之后开始进行移动的过程,这是一个不断更新位置的循环过程,每次检测玩家是否靠近目标点附近,如果还没有到达,则不断地更新位置,让玩家朝着目标点不停的修改当前位置,代码如下:

package main

import "fmt"

func main() {

	//实例化玩家对象,并设速度为 0.5
	p := NewPlayer(0.5)

	//让玩家移动到 3,1 点
	p.MoveTo(Vec2{3, 1})

	//如果没有到达就一直循环
	for !p.IsArrived() {

		//更新玩家位置
		p.Update()

		//打印每次移动后的玩家位置
		fmt.Println(p.Pos())
	}
} 
posted @ 2022-05-30 15:31  左扬  阅读(129)  评论(0编辑  收藏  举报
levels of contents