以前にも同じような記事を書きましたが、動作を改善と実装をシンプルにした改善版を作りました。
概要
フィールド移動に関しては、前回の記事の概要と同じで、描画用に対象物のオフセット座標を計算する処理が必要になります。
このオフセット計算処理を行う処理をカメラという構造体として実装します。
コード
最初の実装
- 捉えたいオブジェクトをカメラの中心に捉える実装です
// SimpleMove は対象物を常に中心に捉える
func (c *Camera) SimpleMove(x, y int) {
// x,y座標に常に画面の半分の座標の値をオフセットとして与える
c.X = (c.Width / 2) - x
c.Y = (c.Height / 2) - y
}
カメラの座標(=オフセット座標)に常に画面範囲の半分を加え続けることで、常に対象物を中心に捉えるようにしています。
実行結果
問題点
最初の実装は、とても簡単なのですが、実行結果から分かるようにフィールドの端に到達すると、端に空白領域が表示されてしまいます。
フィールドの端として意図した実装をしていても、対象物を常に中心に捉えるため、カメラのオフセット座標がフィールドの端の座標を越えてしまうからです。
イメージとしては以下のようになります。
キャラクターが画面端に移動した際に、赤色の空白領域が発生していしまいます。
改善した実装
最初の実装で発生した、画面端の空白領域を生まないためには、カメラのオフセットが実装者が想定してる画面端の座標に到達したらオフセットの値を更新しない、という処理が必要になります。
動作イメージとしては以下のようになります。
動作イメージにあるように、オフセットの最大値は表示領域の縦横の値とフィールドの縦横の値の差分になります。この最大値を超えてしまうと、空白領域が生まれてしまいます。
実装
- 上記の点を考慮した実装は以下です。
// Move は対象物を常に中心に捉えるが, 画面端に到達した場合はカメラを移動しない
func (c *Camera) Move(x, y int) {
maxXOffset := -(c.MaxWidth - c.Width)
maxYOffset := -(c.MaxHeight - c.Height)
restX := (c.Width / 2) - x
restY := (c.Height / 2) - y
// オフセット値の調整
if restX > 0 {
c.X = 0
} else if restX < maxXOffset {
c.X = maxXOffset
} else {
c.X = restX
}
if restY > 0 {
c.Y = 0
} else if restY < maxYOffset {
c.Y = maxYOffset
} else {
c.Y = restY
}
}
実行結果
空白領域が生まれなくなりました。
まとめ
この手の処理は実装してみると簡単ですが、考えを整理するのが大変でした。
参考文献
-
StackOverflow - Add scrolling to a platformer in pygame
- 実装と考え方はほぼこの記事から来ています
- 実装コードも python から単純に Go(ebiten) にポートしただけです