👾

Go言語でドット絵のキャラクターを動かす

2024/03/20に公開

はじめに

先日Ebitengineを使ってスイカゲームもどきを作りましたが、このソースを流用してRPGのようなドット絵のキャラクターを動かすことにチャレンジしてみました。ちなみに今回は描画処理だけを作ったのでゲームっぽいシステムは実装していません。

今回はこれをどうやって作ったのか解説してみようと思います。

スイカゲームをコピー

まずは以前作ったスイカゲームのソースをコピーして、関係ないコードを消します。
フルーツもリンゴの定義だけを残してオレンジやスイカなどの定義を消したり、フルーツを落とす処理を消したりします。
最後にフルーツを何個か仮で画面に落下させておきます。

一旦シンプルな状態にできました。

重力を消す

続いてフルーツを落下させる処理を消して、下に移動させなくします。

プログラム的にはY方向の加速度を加算する処理を消します。

  func (u *Calc) move(fruits []*Fruit) {
  	l := len(fruits)
  	for i := 0; i < l; i++ {
  		f := fruits[i]
  		f.VX *= friction
  		f.VY *= friction
- 		f.VY += gravity
  		f.X += f.VX
  		f.Y += f.VY
  	}
  }

この変更により落下しなくなり、空中を漂ったままになりました。

プレイヤーキャラクターを配置する

まず player と名付けたフルーツを1つ追加します。スライスにはこの player というフルーツのみを入れるように書き直します。これで画面に1つだけフルーツが描画されるようになりました。

ちなみに以前スイカゲームを作ったときに、スライスに基づいて描画する処理は作ってあり、それをそのまま今回も利用するので描画処理は何も書き換えません。

	player = NewApple(200, 200)
	fruits = []*Fruit{player}

これで player というフルーツが1つだけ表示されるようになりました。

モブキャラを配置する

続いて、モブキャラとしてフルーツをランダムで複数生成してスライスに追加します。前述の通りスライスに追加するだけで画面に表示される作りになっています。

	for _ = range 30 {
		fruits = append(
			fruits,
			NewApple(
				rand.Float64()*screenWidth,
				rand.Float64()*screenHeight,
			),
		)
	}

画面にフルーツがたくさん描画されるようになりましたね。

プレイヤーキャラクターをキーボードで操作出来るようにする

最後にキーボード操作をした時、 player に加速度を追加して座標を移動するようにします。加速度を追加すると移動する処理は以前つくってあるので、変更点はこれだけで大丈夫です。

	ac := 0.1 // 加速度
	if ebiten.IsKeyPressed(ebiten.KeySpace) {
		ac = 0.5 // スペースを押されていたらさらに加速
	}

	if ebiten.IsKeyPressed(ebiten.KeyArrowLeft) {
		player.VX -= ac
	} else if ebiten.IsKeyPressed(ebiten.KeyArrowRight) {
		player.VX += ac
	}
	if ebiten.IsKeyPressed(ebiten.KeyArrowUp) {
		player.VY -= ac
	} else if ebiten.IsKeyPressed(ebiten.KeyArrowDown) {
		player.VY += ac
	}

これによってキーボード操作によって1つだけフルーツが移動するようになりました。移動しているのは player という変数に入っているフルーツでs。

なお衝突判定はすでにスイカゲームを作った時に実装済みなので、衝突するとフルーツが跳ね返るようになっています。

ここまでで基本的な部分はできましたが、現在はまだスイカゲームのリンゴ画像が描画されていますので、ここからドット絵に置き換えていこうと思います。

キャラクターを描画する(画像の準備)

まずキャラクターのアニメーションが並んでいる1枚の画像を用意します。画像はこちらを使いました。この1枚の画像を切り出してパラパラ漫画のようにアニメーションしているように描画します。

この画像は上下左右の方向につき4枚のアニメーション画像になっています。

キャラクターを描画する(画像を描画)

一般的なゲームとおなじようにキャラクターの当たり判定は足元だけにしたいので、フルーツの位置に足元がくるようにして、上半身には当たり判定がないように描画しようと思います。

ちなみにEbitengineには SubImage というメソッドが用意されていて、これを使うと1枚の画像から特定の部分を切り出して使う事ができます。

func (i *Image) SubImage(r image.Rectangle) image.Image

キャラクターの向きと移動距離を考慮しつつ SubImage で画像を切り出して、パラパラ漫画のように画像を切り替えながら描画するとこのようになりました。

キャラクターの重なり方がおかしいですが、一旦なんとなくドット絵っぽい描画をできるようになりました。

キャラクターを描画する(ソート)

描画はできたもののキャラクターが重なったときの描画がおかしかったので、これを直していきます。
基本的な考え方としては、奥にあるもの(Y座標が小さいもの)から順に手前側に向かって画像を描画していくことで解消しようと思います。

ソースコードはこんな感じで(Github Copilotに書いてもらいました)、この関数でソートしたスライスを使って描画処理を通すだけです。簡単ですね。

func sortFruitsByY(fruits []*Fruit) []*Fruit {
	l := len(fruits)
	sortedFruits := make([]*Fruit, l)
	copy(sortedFruits, fruits)
	for i := 0; i < l; i++ {
		for j := i + 1; j < l; j++ {
			if sortedFruits[i].Y > sortedFruits[j].Y {
				sortedFruits[i], sortedFruits[j] = sortedFruits[j], sortedFruits[i]
			}
		}
	}
	return sortedFruits
}

これで違和感のある重なりが解消できました

今回は一旦これで完成としようと思います。

ちなみに今回ドット絵の描画処理に置き換えましたが、元々のスイカゲームの描画に切り替えるとこんな感じです。見た目が変わると印象も変わりますね。

今回作ったものは下記のリンクからブラウザで実際に動かすことができます。PCの場合はキーボードまたはマウスで、スマホの場合はタッチで操作します。

https://demouth.github.io/ebitengine-sketch/002/

以上です。

Discussion