1
1

Flutterで作る本格的なPongゲーム!スコア表示付き

Last updated at Posted at 2024-07-31

Flutterでゲームを作ろう!

こんにちは、Flutter開発者の皆さん!今回は、Flutterを使って本格的なPongゲームを作成する方法を詳しく解説します。スマートフォン向けに最適化され、右上にスコアを表示する機能も実装します。さあ、一緩に作っていきましょう!

1. プロジェクトのセットアップ

まずは、新しいFlutterプロジェクトを作成し、必要なパッケージをインストールします。

flutter create pong_game
cd pong_game
flutter pub add flame

2. main.dartファイルの作成

lib/main.dartファイルを以下のように編集します。

import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'pong_game.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: GameWidget(game: PongGame()),
      ),
    ),
  );
}

3. pong_game.dartファイルの作成

lib/pong_game.dartファイルを作成し、以下のコードを追加します。

import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'dart:math' as math;

class PongGame extends FlameGame with TapDetector, HasCollisionDetection {
  late Ball ball;
  late Paddle playerPaddle;
  late Paddle aiPaddle;
  late TextComponent playerScoreText;
  late TextComponent aiScoreText;
  int playerScore = 0;
  int aiScore = 0;

  @override
  Future<void> onLoad() async {
    await super.onLoad();

    ball = Ball();
    playerPaddle = Paddle(isPlayer: true);
    aiPaddle = Paddle(isPlayer: false);
    playerScoreText = TextComponent(
      text: 'Player: $playerScore',
      position: Vector2(size.x - 100, 20),
      textRenderer: TextPaint(style: const TextStyle(color: Colors.white, fontSize: 16)),
    );
    aiScoreText = TextComponent(
      text: 'AI: $aiScore',
      position: Vector2(size.x - 100, 40),
      textRenderer: TextPaint(style: const TextStyle(color: Colors.white, fontSize: 16)),
    );

    add(ball);
    add(playerPaddle);
    add(aiPaddle);
    add(playerScoreText);
    add(aiScoreText);
  }

  @override
  void update(double dt) {
    super.update(dt);
    aiPaddle.moveTowards(ball.y);
    checkCollisions();
  }

  void checkCollisions() {
    if (ball.collidesWithPaddle(playerPaddle) || ball.collidesWithPaddle(aiPaddle)) {
      ball.bounceOffPaddle();
    }

    if (ball.x <= 0) {
      aiScore++;
      aiScoreText.text = 'AI: $aiScore';
      resetBall();
    } else if (ball.x >= size.x) {
      playerScore++;
      playerScoreText.text = 'Player: $playerScore';
      resetBall();
    }
  }

  void resetBall() {
    ball.position = size / 2;
    ball.velocity = Vector2(ball.speed, 0);
    if (math.Random().nextBool()) {
      ball.velocity.x *= -1;
    }
  }

  @override
  void onTapDown(TapDownInfo info) {
    playerPaddle.moveTo(info.eventPosition.game.y);
  }
}

class Ball extends PositionComponent with CollisionCallbacks {
  static const double radius = 10;
  double speed = 300;
  late Vector2 velocity;

  Ball() : super(size: Vector2.all(radius * 2)) {
    anchor = Anchor.center;
  }

  @override
  void onLoad() {
    super.onLoad();
    position = gameRef.size / 2;
    velocity = Vector2(speed, 0);
    if (math.Random().nextBool()) {
      velocity.x *= -1;
    }
  }

  @override
  void update(double dt) {
    super.update(dt);
    position += velocity * dt;

    if (y <= radius || y >= gameRef.size.y - radius) {
      velocity.y *= -1;
    }
  }

  bool collidesWithPaddle(Paddle paddle) {
    return position.x >= paddle.x &&
        position.x <= paddle.x + paddle.width &&
        position.y >= paddle.y &&
        position.y <= paddle.y + paddle.height;
  }

  void bounceOffPaddle() {
    velocity.x *= -1.1; // Increase speed slightly
    velocity.y = (position.y - (gameRef.size.y / 2)) * 2;
    velocity = velocity.normalized() * speed;
  }

  @override
  void render(Canvas canvas) {
    super.render(canvas);
    canvas.drawCircle(Offset(radius, radius), radius, Paint()..color = Colors.white);
  }
}

class Paddle extends PositionComponent {
  static const double width = 10;
  static const double height = 100;
  final bool isPlayer;

  Paddle({required this.isPlayer}) : super(size: Vector2(width, height)) {
    anchor = Anchor.center;
  }

  @override
  void onLoad() {
    super.onLoad();
    y = gameRef.size.y / 2;
    x = isPlayer ? 30 : gameRef.size.x - 30;
  }

  void moveTo(double yPosition) {
    y = yPosition.clamp(height / 2, gameRef.size.y - height / 2);
  }

  void moveTowards(double targetY) {
    const aiSpeed = 3;
    if ((targetY - y).abs() > aiSpeed) {
      y += (targetY > y ? aiSpeed : -aiSpeed);
    }
  }

  @override
  void render(Canvas canvas) {
    super.render(canvas);
    canvas.drawRect(
      Rect.fromLTWH(0, 0, width, height),
      Paint()..color = Colors.white,
    );
  }
}

4. コードの解説

PongGameクラス

  • onLoad(): ゲームの初期化を行います。ボール、パドル、スコアテキストを作成し、画面に追加します。
  • update(): 毎フレーム呼び出され、AIパドルの動きやコリジョンチェックを行います。
  • checkCollisions(): ボールとパドルの衝突判定、得点の更新を行います。
  • resetBall(): ボールの位置と速度をリセットします。
  • onTapDown(): 画面タップ時にプレイヤーのパドルを移動させます。

Ballクラス

  • ボールの動きや衝突判定のロジックを実装しています。
  • update(): ボールの位置を更新し、画面端での反射を処理します。
  • collidesWithPaddle(): パドルとの衝突判定を行います。
  • bounceOffPaddle(): パドルに当たった時の挙動を定義します。

Paddleクラス

  • プレイヤーとAIのパドルを表現します。
  • moveTo(): プレイヤーのパドル移動に使用します。
  • moveTowards(): AIパドルの移動に使用します。

5. ゲームの実行

プロジェクトのルートディレクトリで以下のコマンドを実行します。

flutter run

これで、スマートフォン上でPongゲームが動作し、右上にスコアが表示されるはずです!

まとめ

今回は、Flutterを使って本格的なPongゲームを作成しました。Flameエンジンを活用することで、ゲームの基本的な構造を簡単に実装できました。さらに、スコア表示機能を追加することで、よりゲーム性の高いアプリケーションになりました。

このコードをベースに、さらなる機能追加や改良を行ってみてください。例えば、難易度設定、サウンド効果、パーティクル効果などを追加すると、より魅力的なゲームになるでしょう。

Flutterでのゲーム開発は非常に楽しく、多くの可能性を秘めています。ぜひ、自分なりのアイデアを取り入れて、オリジナルのゲームを作ってみてください!

それでは、楽しいFlutter開発を!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1