第十章 让角色跳得自然(浮点数和类型)

上次我们学习了如何接收输入。这次我们将学习如何让角色跳得更自然。

10.1 让角色跳跃起来

以下是一个使角色(自然)跳跃的程序示例。

执行时,点击时 gopher 君会向上跳,松开时会向下落。

package main

import "github.com/eihigh/miniten"

var (
	y  = 0.0
	vy = 0.0 // Velocity of y (Y方向速度) 的略称
	g  = 0.1 // Gravity (重力加速度) 的略称
)

func main() {
	miniten.Run(draw)
}

func draw() {
	if miniten.IsClicked() {
		vy = -3
	}
	vy += g // 当前速度 = 速度+加速度
	y += vy // 当前位置 = 位置+速度
	miniten.DrawImage("gopher.png", 0, int(y))
}

10.2 速度与加速度

物体下落时,物体会朝着地面加速。在游戏中,加速通常通过每帧逐渐增加速度来表现。

上一章,是通过每帧增加或减少一个固定值 (3) 来表示恒速运动,

这次,通过每帧逐渐增加的速度 vy 反映到位置 y 上,从而实现加速度效果。

跳跃

此程序通过在点击期间将速度设置为 -3 (向上 3)来表现跳跃。

现实中的跳跃在离开地面前的瞬间,速度会达到向上的最高速,之后由于重力的作用,向上速度会逐渐减少。

但为了简单起见,这里在点击期间一直给予向上的固定速度。虽然是为了容易做,但这在游戏中的表现,看起来像是**“按得越久跳得越高”**,手感类似超级马里奥的跳跃,是一个意外好用的技巧。

10.3 数据类型与类型转换

此处用到的另一个新知识是类型转换。 就是int(y) 的部分。

10.3.1 浮点数

变量 y vy g 在声明时赋值为类似 0.0 的带小数点的数值,因此这些变量的类型是 float64浮点数/floating-point number 类型。浮点数与整数类型不同,可以表示小数点以下的数值。

var i = 0   // int 类型的数字
var f = 0.0 // float64 类型的数字

浮点数这个独特的名字,实际上是在提醒人们注意误差问题(比如3D 游戏中的墙穿墙 bug ,很多问题都可以来自于浮点数),所以故意用这样一个严肃的名字来代替“实数”。

为什么用“浮动小数点数”而不是“小数”? 

这是因为在计算机中表示小数的方法是“移动小数点”。例如 0.5 是将 5 下移 1 位, 0.05 是将 5 下移 2 位。

这种方法,可以表示包含小数点的非常广泛的数值,但因为计算机只能存储存有限位数,小数计算总是伴随着误差。例如 0.1 + 0.2 会变成 0.30000000000000004 。
编程中,浮点数应该避免使用等值比较( == )运算符。
如果浮点数误差成为问题,则需要采取如用取整后计算等对策。

10.3.2 类型转换

在 Go 中,不同类型之间的计算和赋值基本上被禁止。 miniten.DrawImage 函数接受 int 类型的坐标,所以不能直接传递 float64 类型的 y

不同类型的赋值,需要像 int(y) 这样,以 类型名称(变量名) 的形式进行转换。 float64 类型到 int 类型的转换,会将小数点以下部分舍去,变为整数。

a := 1
b := 2.5
c := a + b          // 不能这么做
c := a + int(b)     // 可以这么做(c 等于3)
d := float64(a) + b // 可以这么做(d 会变成 3.5)
vy = -3 难道不是不同类型(浮点数 与 整数)之间的计算吗?
答案是,Go 对原始值(字面量)之间的计算,会在类型上进行隐含的特殊处理,可以在没有显式类型转换的情况下,完成转换与计算。

10.3.3 数据类型

int 和 float64 等,所有变量都有称为类型/type 的数据类型。类型一旦声明后就无法再更改。类型作为“约束”起作用,大多数情况下,禁止不同类型之间的计算和赋值。这样可以防止“计算结果不明确”或“结果出错”。

在 Go 中的常用类型如下。

var i int     // 整数
var f float64 // 64bit浮点数
var s string  // 字符串
var b bool    // 布尔值
var e error   // 错误类型

各种类型,对应以下的“字面量”(指在程序中直接出现,且无法更改的值)。

i := 1       // 整数
f := 1.0     // 64bit浮点数
s := "Hello" // 字符串
b := true    // 布尔值

您可以基于这些类型,自己声明新类型。这种用法,用到的时候再说吧。

10.3.4 其他类型

Go 还具有许多其他类型。

首先,整数和浮点数,有不同大小的细分类型。不同的类型,有的占用空间小,有的误差小,是编程高手用的高级类型。

类型名大小用途
int88bit-128 ~ 127 的整数
int1616bit32768 ~ 32767 的整数
int3232bit-2147483648 ~ 2147483647 的整数
int6464bit-9223372036854775808 ~ 9223372036854775807 的整数
float3232bit32bit浮点数

此外,还有表示大于等于 0 的无符号整数类型 uint 。它也有不同大小,属于细分类型。

类型名大小用途
uint88bit0 ~ 255 的整数
uint1616bit0 ~ 65535 的整数
uint3232bit0 ~ 4294967295 的整数
uint6464bit0 ~ 18446744073709551615 的整数

此外, intuint 的大小在 32 位环境下为 32 位,在 64 位环境下为 64 位。现代计算机几乎都是 64 位环境。

其他类型大致总结如下。复数类型很小众,没人见过有人在用。在游戏中似乎有用,但实际上并没有……。

型名用途
byteuint8 的别名。用于表示字节数据。
runeint32 的别名。用于表示 Unicode 的代码点。
any任意类型。 interface{} 的别名
uintptr用于表示指针
complex64复数。表示实部和虚部的两个 32 位浮动小数点数的组合
complex128复数。表示实部和虚部的两个 64 位浮动小数点数的组合

10.3.5 默认值

Go 的变量,如果在声明时没有初始化,将会被赋予默认值。

根据类型的不同,默认值也不一样,具体如下:

var i int     // 0
var f float64 // 0.0
var s string  // ""
var b bool    // false

作者经常搞不清布尔值的默认值到底是什么?是 false。

本章总结

  • 要使角色自然地跳跃,需要用到速度和加速度。
  • 浮动小数点数可以表示小数点以下的数值。
  • 不同类型之间的计算和赋值基本上被禁止。需要进行显式的类型转换。