Chapter 15

キャラをジャンプさせよう / Goの浮動小数点数と型

eihigh
eihigh
2025.01.31に更新

前回は、ゲームの画面が動いて見える原理を学びました。今回は、キャラクターをふわりといい感じにジャンプさせる方法を学んでいきましょう。

キャラをジャンプさせる

以下がキャラをジャンプさせるプログラムの一例です。実行すると、クリック中はgopherくんが上にジャンプし、離すと下に落ちていきます。

package main

import "github.com/eihigh/miniten"

var (
	y  = 0.0 // 小数点が必要。後述。
	vy = 0.0 // Velocity of y(速度のy成分)の略
)

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

func draw() {
	if miniten.IsClicked() {
		vy = -6 // ジャンプ
	}
	vy += 0.5 // 速度に加速度を足す(重力)
	y += vy   // 位置に速度を足す

	// 結構な速度で画面の下に落ちていくので、360より下に行かないよう制限する
	if 360 < y {
		y = 360
	}

	miniten.DrawImage("gopher.png", 0, int(y))
}

新たに増えた変数 vy が重要です。

速度と加速度

落下

物体が落ちるとき、物体は地面に向かって加速します。ゲームの場合、加速は速度をフレームごとに少しずつ増やすことで表現できます。

前回は位置に一定の速度 6 を毎フレーム足したり引いたりしていましたが、今回は重力によって少しずつ増える速度 vy を位置に毎フレーム足すことで、ふわりとした加速を表現できるのです。

ジャンプ

このプログラムでは、クリック中に速度を -6(上方向に6)に設定することでジャンプを表現しています。

本当は現実のジャンプは地面を離れる直前だけ速度が上向き最高速になり、その後は重力によって速度が下向きになっていくのですが、今回は簡単のためにクリック中ずっと上向きの速度を与えています。物理的には嘘ですが、これはこれでゲーム的には「長押しするほど高くジャンプする」ように見える、いわゆるマリオジャンプに近い挙動になるので、意外と使えるテクニックだったりします。

まとめ

  • キャラを自然に落下させるには、速度をフレームごとに少しずつ増やす(加速させる)。
  • キャラがジャンプするときに上向きの速度を設定する。

詳しい解説

型と型変換

もう一つ新しい要素として、浮動小数点数と、型変換が登場しています。

浮動小数点数

変数 y, vy は宣言時に 0.0 という小数点をつけた数値を代入しているため、これらの変数の型は float64 という浮動小数点数/floating-point number型になります。浮動小数点数は整数型とは異なり、小数点以下の数値を表現できます。

var i = 0   // int 型になる
var f = 0.0 // float64 型になる

浮動小数点数という独特なネーミングは、本物の小数(実数)だと思うと誤差周りで地雷を踏み抜きまくる(例えば3Dゲームの壁抜けバグの少なくない部分がこいつのせいと考えられる)ので、気をつけるために敢えて「小数(実数)」の代わりにこんないかつい名前をつけていると思えばよいでしょう。

詳しいこと

なんで「小数」ではなく「浮動小数点数」かというと、小数を表現するのに「小数点を動かす」という方法を使っているからです。例えば 0.551 桁下げたもので、0.0552 桁下げたものです。

この方法は小数点以下を含む非常に幅広い数値を表現できる反面、常に誤差がつきまといます。例えば 0.1 + 0.20.30000000000000004 になります。少なくとも等値比較 == は避けるべきですし、誤差が問題になる場合は整数で計算するなどの対策が必要です。

型変換

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 = -6 は異なる型同士の計算じゃないの?

結論から言うとGoは 10 など生の値(リテラル)を含む定数は型に関して特別扱いするため、型変換なしでも結構よしなにしてくれます。

型(かた)

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    // 真偽値型

これらの型を元にあなた自身が新たに型を宣言することもできますが、それはまた別の機会に。

その他の型

Goは他にも多くの型を備えています。

まず、整数と浮動小数点数には、サイズ違いの型があります。これらは使用するデータ量を削減したり、データの作りに関して厳密なやり取りを行う必要のあるところで使う、上級者向けの型です。

型名 サイズ 用途
int8 8bit -128 ~ 127 の整数
int16 16bit -32768 ~ 32767 の整数
int32 32bit -2147483648 ~ 2147483647 の整数
int64 64bit -9223372036854775808 ~ 9223372036854775807 の整数
float32 32bit 32bit浮動小数点数

また、0以上の整数を表現する符号なし整数型 uint もあります。これにもサイズ違いの型があります。

型名 サイズ 用途
uint8 8bit 0 ~ 255 の整数
uint16 16bit 0 ~ 65535 の整数
uint32 32bit 0 ~ 4294967295 の整数
uint64 64bit 0 ~ 18446744073709551615 の整数

なお、intuint のサイズは、32bit環境では32bit、64bit環境では64bitになります。現代のコンピューターはほとんど64bit環境です。

他の型についてざっくりまとめると、以下のようになります。複素数型とかはマイナーすぎて誰も使ってるのを見たことがありません。ゲームとかで役に立ちそうで立たないんですよね...。

型名 用途
byte uint8 の別名。バイトデータを表現するのに使う
rune int32 の別名。Unicodeのコードポイントを表現するのに使う
any 任意の型。interface{} の別名
uintptr ポインタを表現するのに使う
complex64 複素数。実部と虚部を表す2つの32bit浮動小数点数の組
complex128 複素数。実部と虚部を表す2つの64bit浮動小数点数の組

ゼロ値

Goの変数は宣言時に初期化しないとゼロ値/zero value という値が入ります。ゼロ値は型ごとに決められており、以下のようになります。

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

筆者には真偽値のゼロ値がどっちだったっけ?って迷うことがよくあった記憶があります。falseです。