Go言語で昔懐かしのPongを作ってみた話

## はじめに
前回は画面上でボールが動くだけの処理について記載しましたが、
ボールが動くのであれば、昔懐かしのpongのようなホッケーゲームができると思い作成しました。
以下はソースについて記載します。

## 必要なパッケージのインポート

```go
import (
	"image/color"  // 色を扱うためのパッケージ
	"log"  // ロギングを行うためのパッケージ

	"github.com/hajimehoshi/ebiten/v2"  // Ebitenのパッケージ
	"github.com/hajimehoshi/ebiten/v2/ebitenutil"  // Ebitenのユーティリティパッケージ
)
```

## 定数の定義

```go
const (
	screenWidth, screenHeight = 640, 480  // 画面の幅と高さ
	paddleWidth, paddleHeight = 20, 80  // パドルの幅と高さ
	ballSize                  = 20  // ボールの大きさ
	paddleSpeed               = 4  // パドルの速度
)
```

## Game構造体の定義

```go
type Game struct {
	ballPositionX, ballPositionY float64  // ボールの位置
	ballDX, ballDY               float64  // ボールの移動量
	paddle1Y, paddle2Y           float64  // 両パドルの位置
}
```

Game構造体はゲームの状態を保持します。ボールの位置と移動量、そしてパドルの位置が含まれます。

## Updateメソッド

```go
func (g *Game) Update() error {
	g.ballPositionX += g.ballDX  // ボールのX座標を更新
	g.ballPositionY += g.ballDY  // ボールのY座標を更新

	// ボールが画面の左右端に触れたら反射
	if g.ballPositionX < 0 {
		g.ballDX = -g.ballDX
		g.ballPositionX = 0
	} else if g.ballPositionX > screenWidth-ballSize {
		g.ballDX = -g.ballDX
		g.ballPositionX = screenWidth - ballSize
	}
	// ボールが画面の上下端に触れたら反射
	if g.ballPositionY < 0 {
		g.ballDY = -g.ballDY
		g.ballPositionY = 0
	} else if g.ballPositionY > screenHeight-ballSize {
		g.ballDY = -g.ballDY
		g.ballPositionY = screenHeight - ballSize
	}

	// パドルの移動
	if ebiten.IsKeyPressed(ebiten.KeyW) {
		g.paddle1Y -= paddleSpeed  // Wキーでパドル1を上に移動
	}
	if ebiten.IsKeyPressed(ebiten.KeyS) {
		g.paddle1Y += paddleSpeed  // Sキーでパドル1を下に移動
	}
	if ebiten.IsKeyPressed(ebiten.KeyUp) {
		g.paddle2Y -= paddleSpeed  // 上矢印キーでパドル2を上に移動
	}
	if ebiten.IsKeyPressed(ebiten.KeyDown) {
		g.paddle2Y += paddleSpeed  // 下矢印キーでパドル2を下に移動
	}

	// ボールがパドルに当たったら反射
	if (g.ballPositionX < paddleWidth && g.ballPositionY+ballSize > g.paddle1Y && g.ballPositionY < g.paddle1Y+paddleHeight) ||
		(g.ballPositionX+ballSize > screenWidth-paddleWidth && g.ballPositionY+ballSize > g.paddle2Y && g.ballPositionY < g.paddle2Y+paddleHeight) {
		g.ballDX = -g.ballDX
	}

	return nil
}
```

Updateメソッドでは、ゲームの状態を更新します。ここではボールの位置、ボールの反射、パドルの移動、そしてボールとパドルの衝突を処理しています。

## Drawメソッド

```go
func (g *Game) Draw(screen *ebiten.Image) {
	ebitenutil.DrawRect(screen, 10, g.paddle1Y, paddleWidth, paddleHeight, color.White)  // パドル1を描画
	ebitenutil.DrawRect(screen, screenWidth-30, g.paddle2Y, paddleWidth, paddleHeight, color.White)  // パドル2を描画
	ebitenutil.DrawRect(screen, g.ballPositionX, g.ballPositionY, ballSize, ballSize, color.White)  // ボールを描画
}
```

Drawメソッドでは、ゲームの状態に基づいて画面を描画します。ここではパドルとボールを描画しています。

## Layoutメソッド

```go
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
	return screenWidth, screenHeight
}
```

Layoutメソッドでは、ゲーム画面のサイズを設定します。

## main関数

```go
func main() {
	g := &Game{
		ballPositionX: screenWidth / 2,
		ballPositionY: screenHeight / 2,
		ballDX:        2,
		ballDY:        2,
		paddle1Y:      screenHeight/2 - paddleHeight/2,
		paddle2Y:      screenHeight/2 - paddleHeight/2,
	}

	ebiten.SetWindowSize(screenWidth, screenHeight)  // ウィンドウの大きさを設定
	ebiten.SetWindowTitle("Pong")  // ウィンドウのタイトルを設定

	if err := ebiten.RunGame(g); err != nil {
		log.Fatal(err)  // エラーハンドリング
	}
}
```

main関数では、ゲームの初期化と実行を行います。ゲームの状態を表すGame構造体を初期化し、ウィンドウの大きさとタイトルを設定した後、ゲームを実行します。

現状は、パドルに当たり判定を付け,ただ永遠とはじくだけではありますが、
今後、pongと同じような機能を追加していこうと思います。

以上

## 前回

https://qiita.com/Tester777/items/0d08efee9dd7b557daf2