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())
}
}