Saturday, October 29, 2022
HomeWeb DevelopmentConstructing a gaming leaderboard in Flutter

Constructing a gaming leaderboard in Flutter


Flutter 3 features a handful of distinctive options, fixes, and enhancements. One of the thrilling adjustments is the addition of assist for creating informal video games with Flutter, alongside the Informal Video games Toolkit and template. This template comprises built-in functionalities, corresponding to sounds, commercials, play service linking, FlutterFire, and extra.

On this tutorial, we’ll discover these new options by constructing a Flutter gaming leaderboard for a easy asteroid sport and discover ways to monitor excessive scores with the Firebase Realtime Database.

Leap forward:

Constructing an informal asteroid sport in Flutter

Informal video games embody leaderboards to spice up competitors and promote engagement amongst gamers. Earlier than we dive into constructing a gaming leaderboard in Flutter, let’s look at our sport’s goal.

The sport idea is straightforward. The participant has to forestall a transferring asteroid from colliding with the spaceship utilizing a mouse or trackpad:

GIF of Flutter Game Example

 

It’s essential to notice that we gained’t use Flame as a result of it introduces ideas past this text’s scope, however test out this text to study extra about Flutter sport engines.

Creating the spaceship

To construct our Flutter sport, we’ll begin by making a spaceship that follows the mouse and trackpad by utilizing the MouseRegion widget to detect the mouse cursor’s place when it hovers over the area. This widget additionally hides the default mouse cursor by utilizing SystemMouseCursors.none:

class Participant extends StatefulWidget {
  const Participant({
    Key? key,
    required this.gameController,
  }) : tremendous(key: key);

  ultimate GameController gameController;

  @override
  State<Participant> createState() => _PlayerState();
}

class _PlayerState extends State<Participant> with SingleTickerProviderStateMixin {
  Offset? playerPosition;

  @override
  void initState() {
    tremendous.initState();
  }

  @override
  Widget construct(BuildContext context) {
    return MouseRegion(
      cursor: SystemMouseCursors.none,
      onHover: (occasion) {
        playerPosition = occasion.place;
        setState(() {});
      },
      youngster: Stack(
        youngsters: [
          Positioned(
            top: playerPosition?.dy ?? -100,
            left: playerPosition?.dx ?? -100,
            child: SizedBox(
              height: 50,
              width: 50,
              child: Image.asset(
                'assets/space_ship.png',
              ),
            ),
          )
        ],
      ),
    );
  }

  @override
  void dispose() {
    tremendous.dispose();
  }
}

Within the code above, we up to date the spaceship’s place when the mouse moved within the onHover callback, then rebuilt the display screen utilizing setState().

Constructing the obstacles

After creating the spaceship, we’ll add the randomly transferring obstacles (asteroids):

class Asteroid extends StatefulWidget {
  const Asteroid({
    Key? key,
    required this.index,
    required this.place,
    required this.gameController,
  }) : tremendous(key: key);

  ultimate int index;
  ultimate Offset place;
  ultimate GameController gameController;

  @override
  State<Asteroid> createState() => _AsteroidState();
}

class _AsteroidState extends State<Asteroid> with SingleTickerProviderStateMixin {
  ultimate Random random = Random();
  late Offset place = widget.place;

  Offset randomDirection = const Offset(1, 1);

  int lastTime = 0;
  int asteroidSize = 70;

  Ticker? ticker;

  @override
  void initState() {
    tremendous.initState();

    setRandomDirection();
    moveRandomly();
  }

  void moveRandomly() {
    ticker = createTicker((elapsed) {
      if (elapsed.inSeconds > lastTime + 5) {
        lastTime = elapsed.inSeconds;
        setRandomDirection();
      }

      changeDirectionWhenOutOfBounds();
      place += randomDirection * 2;
      widget.gameController.allAsteroids[widget.index] = place;

      setState(() {});
    });

    ticker?.begin();
  }

  setRandomDirection() {
    double x = random.nextInt(3) - 1;
    double y = random.nextInt(3) - 1;
    if (x == 0 && y == 0) {
      x = 1;
    }

    randomDirection = Offset(x, y);
  }

  // wall collision detection
  void changeDirectionWhenOutOfBounds() {
    ultimate screenSize = MediaQuery.of(context).measurement;

    if (place.dx < 0 ||
        place.dx + asteroidSize > screenSize.width ||
        place.dy < 0 ||
        place.dy + asteroidSize > screenSize.peak) {
      randomDirection = randomDirection * -1;
    }
  }

  @override
  Widget construct(BuildContext context) {
    return Positioned(
      high: place.dy,
      left: place.dx,
      youngster: SizedBox(
        peak: 70,
        width: 70,
        youngster: Picture.asset(
          'belongings/asteroids.png',
        ),
      ),
    );
  }

  @override
  void dispose() {
    ticker?.dispose();
    tremendous.dispose();
  }
}

Right here, we added SingleTickerProviderStateMixin with out utilizing an AnimationController. This retains the asteroid transferring with out an higher sure, length, or AnimationTween. Other than animating the asteroid, we additionally up to date the route and place of the asteroid, which is saved in GameController to detect collisions.

As an alternative of utilizing the frequent AnimationControllers, we will use ticker to maintain the animation working for so long as attainable. Tickers are like periodic timers that run each body as an alternative of each second or minute, permitting us to create animations with higher efficiency.

Including the spaceship and obstacles right into a stack widget in Flutter

Within the sections above, we created the spaceship and asteroid widgets. Now, we will add the spaceship and a number of asteroids right into a stack widget.

First, let’s have a look at the addMultipleAsteroids() methodology to run a loop and add a number of asteroids to the GameController:

class GameView extends StatefulWidget {
  const GameView({Key? key}) : tremendous(key: key);

  @override
  State<GameView> createState() => _GameViewState();
}

class _GameViewState extends State<GameView> {
  GameController gameController = GameController();

  @override
  void initState() {
    tremendous.initState();

    addMultipleAsteroids();
    gameController.addListener(endGame);
  }

        // add 20 asteroids
  void addMultipleAsteroids() {
    for (int i = 0; i < 20; i++) {
      gameController.addAsteroid();
    }
    setState(() {});
  }

  @override
  void dispose() {
    gameController.removeListener(endGame);
    tremendous.dispose();
  }

  @override
  Widget construct(BuildContext context) {
    ultimate allAsteroids = gameController.allAsteroids;

    return Scaffold(
      backgroundColor: AppColors.appDarker,
      physique: Stack(
        youngsters: [
          // Background image
          Positioned.fill(
            child: Image.asset(
              'assets/space.jpeg',
              fit: BoxFit.cover,
            ),
          ),
          // All asteroids
          ...List.generate(allAsteroids.length, (index) {
            final position = allAsteroids[index];

            return Asteroid(
              index: index,
              place: place,
              gameController: gameController,
            );
          }),
          // Participant's ship
          Participant(
            gameController: gameController,
          ),
        ],
      ),
    );
  }
}

After the sport ends, navigate to the LeaderboardView() to show the person rating:

  void endGame() async {
    await Future.delayed(Length.zero);
    Navigator.pushReplacement(
      context,
      MaterialPageRoute(
        builder: (context) => const LeaderboardView(),
      ),
    );
  }

As you’ll be able to see, the sport ends when the person collides with an impediment. We’ll detect this with a for loop to calculate if the participant’s mouse place overlaps any asteroids.

The algorithm under detects a collision between two circles by evaluating the middle of each circles and periodically checking in the event that they overlap or collide.

class GameController extends ChangeNotifier {
  ultimate Record<Offset> allAsteroids = [];
  ultimate Random random = Random();
  ultimate LeaderboardRepository leaderboardRepository = LeaderboardRepository();

  ultimate double shipRadius = 25;
  ultimate double asteroidRadius = 35;

  bool endGame = false;

  addAsteroid() {
    allAsteroids.add(Offset.zero);
  }

  void checkCollision(Offset playerPosition) {
    if (endGame) return;

    for (Offset asteroidPosition in allAsteroids) {
      if (isColliding(playerPosition - Offset(shipRadius, shipRadius),
          asteroidPosition - Offset(asteroidRadius, asteroidRadius))) {
        endGame = true;
        notifyListeners();
      }
    }
  }

  bool isColliding(Offset mousePosition, Offset asteroidPosition) {
    double distX = mousePosition.dx - asteroidPosition.dx;
    double distY = mousePosition.dy - asteroidPosition.dy;
    double distance = sqrt((distX * distX) + (distY * distY));

    if (distance <= asteroidRadius + shipRadius) {
      return true;
    }

    return false;
  }
}

Upon collision, we finish the sport and take the participant to the leaderboard display screen.

Constructing the Flutter gaming leaderboard

Leaderboards present a person’s rating in comparison with all different on-line gamers to create a aggressive gaming atmosphere. To create a leaderboard, now we have to set a metric that reveals the person’s efficiency within the sport. Right here we’ll use the sport length:

int startTimestamp = DateTime.now().millisecondsSinceEpoch;
void endGame(){
  int endTimestamp = DateTime.now().millisecondsSinceEpoch;
  ...
  int rating = endTimestamp - startTimestamp
}

Monitoring the length is straightforward; subtract the saved sport begin time from the time that the sport ends to get the length and rating:

Building the Flutter Gaming Leaderboard

Establishing Firebase

I’m positive you’d agree that monitoring the length is the best a part of making a gaming leaderboard. Nonetheless, a leaderboard can be incomplete with out on-line interplay, saving, and viewing different gamers’ excessive scores.

To maintain issues easy, we’ll use Firebase Realtime Database to assist us authenticate the person and save their excessive scores. Like each different database, Firebase requires some setup to operate appropriately in Flutter.

First, create a Firebase account, navigate to the Firebase console, and choose create a brand new undertaking:

Starting the Firebase Flutter Gaming Leaderboard

Subsequent, click on add an app, the place you’ll discover the checklist of functions you’ll be able to add. By choosing the Flutter icon within the picture under, you’ll be able to add Firebase to your platforms and cut back your written boilerplates:

Firebase Project for Flutter Gaming Leaderboard

After finishing the Firebase setup course of, run the command flutterfire configure --project=projectname to generate the boilerplate and add Firebase to your chosen platforms.

Subsequent, replace the pubspec.yaml file with the required Firebase packages or plugins:

...
dependencies:
  firebase_auth: ^3.11.2
  firebase_core: ^1.24.0
  firebase_database: ^9.1.7
  flutter:
    sdk: flutter
...

Authenticating and logging within the person

Authentication is important for creating our gaming leaderboard as a result of we have to hyperlink the person information to the identical person in each sport. With out authentication, it will be not possible to avoid wasting and retrieve particular person scores.

Firebase supplies sign-in strategies that may be simply built-in into our app. For this tutorial, we are going to use the e-mail and password methodology and create a login display screen to retrieve the person’s electronic mail and password, after which signal within the person by working the code under:

Future<void> signIn(String electronic mail, String password) async {
  await FirebaseAuth.occasion.signInWithEmailAndPassword(
    electronic mail: electronic mail,
    password: password,
  );
}

Customers also can anonymously sign up, however this doesn’t save their information, they usually gained’t be capable to play on different units.

Monitoring excessive scores with the Firebase database

We have to construction our information to trace excessive scores so we will simply save, retrieve, and type based on the ranks. Among the finest strategies for doing that is to make use of the Firebase database. When the sport ends, the person rating shall be despatched to Firebase’s Realtime Database. Firebase supplies every person with a novel person ID that permits us to construction the info in order that customers can simply override the earlier sport rating with a brand new one.

The construction is proven within the picture under:

- leaderboard
      | - user1
            | - identify: 'Person identify'
            | - rating: 10

      | - user2
            | - identify: 'Person identify'
            | - rating: 15

Tracking Flutter Gaming Leaderboard High Score Example

Our implementation in Flutter saves the info to the participant’s ID, and we’ll use a repository to make sure our code is loosely coupled and separated from the view.

Subsequent, we’ll name saveHighScore() when the sport ends:

class LeaderboardRepository {
        ...
  Future<void> saveHighScore(String identify, int newScore) async {
    ultimate currentUser = FirebaseAuth.occasion.currentUser;
    if (currentUser == null) return;

    strive {
      ultimate uid = currentUser.uid;
      ultimate userName = getUserName();

      // Get the earlier rating
      ultimate scoreRef = FirebaseDatabase.occasion.ref('leaderboard/$uid');
      ultimate userScoreResult = await scoreRef.youngster('rating').as soon as();
      ultimate rating = (userScoreResult.snapshot.worth as int?) ?? 0;

      // Return if it isn't the excessive rating
      if (newScore < rating) {
        return;
      }

      await scoreRef.set({
        'identify': userName,
        'rating': newScore,
      });
    } catch (e) {
      // deal with error
    }
  }
        ...
}

We have to kind the info from the very best to the bottom to show all person scores. For functions or video games with small numbers of customers or information, you’ll be able to kind the info straight in Flutter. Nonetheless, this methodology is inefficient for apps and video games with extra customers and information.

For our sport, we are going to use Firebase queries, that are used for ordering, filtering, and limiting the server’s information, to extend pace and reduce price.


Extra nice articles from LogRocket:


The code under orders information by the excessive rating and retrieves the final 20 information current on the leaderboard. Since information retrieved from Firebase is at all times in ascending order, we have to reverse the info to verify it begins from the very best rating:

class LeaderboardRepository {
        ...
  Future<Iterable<LeaderboardModel>> getTopHighScores() async {
    ultimate currentUser = FirebaseAuth.occasion.currentUser;
    ultimate userId = currentUser?.uid;

    // Retrieve first 20 information from highest to lowest in firebase
    ultimate outcome = await FirebaseDatabase.occasion
        .ref()
        .youngster('leaderboard')
        .orderByChild('rating')
        .limitToLast(20)
        .as soon as();

    ultimate leaderboardScores = outcome.snapshot.youngsters
        .map(
          (e) => LeaderboardModel.fromJson(e.worth as Map, e.key == userId),
        )
        .toList();

    return leaderboardScores.reversed;
  }
        ...
}

Subsequent, we have to convert the info right into a mannequin. Flutter fashions are lessons used to transform and cleanly symbolize information. For instance, the LeaderboardModel is created by defining the rating and identify values, as proven under:

class LeaderboardModel {
  ultimate String identify;
  ultimate int rating;

  LeaderboardModel({
    required this.identify,
    required this.rating,
  });

  manufacturing unit LeaderboardModel.fromJson(Map json, bool isUser) {
    return LeaderboardModel(
      identify: isUser ? 'You' : json['name'],
      rating: json['score'],
    );
  }
}

The leaderboard web page comprises the checklist of excessive scores proven in a tabular type with the textual content Sport Over. To create this, we’ll begin by constructing a StatefulWidget to navigate to on the finish of the sport:

class LeaderboardView extends StatefulWidget {
  const LeaderboardView({
    this.isGameOver = true,
    Key? key,
  }) : tremendous(key: key);
  ultimate bool isGameOver;

  @override
  State<LeaderboardView> createState() => _LeaderboardViewState();
}

class _LeaderboardViewState extends State<LeaderboardView> {

  @override
  void initState() {
    tremendous.initState();
    // We get the leaderboard scores from the repository right here
  }

  @override
  Widget construct(BuildContext context) {
    // UI widget goes right here
  }
}

Subsequent, we retrieve the info from LeaderboardRepository and retailer it in a _leaderboardScores checklist.

Then, we are going to rebuild the web page by calling setState(() {}):

ultimate Record<LeaderboardModel> _leaderboardScores = [];

  @override
  void initState() {
    tremendous.initState();

    getLeaderboardScores();
  }

  void getLeaderboardScores() async {
    ultimate leaderboardScores = await LeaderboardRepository().getTopHighScores();
    setState(() {
      _leaderboardScores.addAll(leaderboardScores);
    });
  }

Displaying all leaderboard excessive scores with a knowledge desk

Subsequent, we’ll create the leaderboard desk within the construct methodology. In Flutter, information tables assist show information in tabular type by utilizing DataColumn, DataRow, and DataCells, as proven under:

  @override
  Widget construct(BuildContext context) {
    return Scaffold(
      backgroundColor: AppColors.appDarker,
      physique: Heart(
        youngster: Column(
          mainAxisAlignment: MainAxisAlignment.heart,
          youngsters: [
            Text(
              'Game Over',
              style: context.textTheme.bodyMedium?.copyWith(
                color: AppColors.appColor,
                fontWeight: FontWeight.w900,
                fontSize: 40,
              ),
            ),
            const SizedBox(height: 20),
            Container(
              height: 500,
              width: 500,
              child: SingleChildScrollView(
                child: DefaultTextStyle(
                  style: const TextStyle(color: Colors.white),
                  child: DataTable(
                      dataTextStyle: const TextStyle(color: Colors.white),
                      columns: const [
                        DataColumn(
                          label: Text('Rank'),
                        ),
                        DataColumn(
                          label: Text('Name'),
                        ),
                        DataColumn(
                          label: Text('Score'),
                        ),
                      ],
                      rows: Record.generate(_leaderboardScores.size, (index) {
                        ultimate leaderboard = _leaderboardScores[index];
                        return DataRow(
                          cells: [
                            DataCell(Text('${index + 1}')),
                            DataCell(Text(
                              leaderboard.name,
                              style: context.textTheme.bodyMedium?.copyWith(
                                color: leaderboard.name == 'You'
                                    ? AppColors.appColor
                                    : Colors.white,
                              ),
                            )),
                            DataCell(Text(leaderboard.score.toString())),
                          ],
                        );
                      })),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

Right here, the DataColumn represents the titles and shows the person rating and ranks. To implement this, we’ll use Record.generate to loop by the info from LeaderboardRepository and return a DataRow with the person rating, identify, and rank.

Conclusion

Constructing informal video games in Flutter is getting extra assist from the Flutter group. To discover different examples of informal video games constructed with Flutter, take a look at Omni chess, 4 pics 1 phrase, and Orbit.

On this article, we constructed a Flutter gaming leaderboard with Firebase and realized concerning the Flutter Informal Video games Toolkit. Thanks for studying!

: Full visibility into your internet and cell apps

LogRocket is a frontend utility monitoring answer that permits you to replay issues as in the event that they occurred in your individual browser. As an alternative of guessing why errors occur, or asking customers for screenshots and log dumps, LogRocket enables you to replay the session to shortly perceive what went fallacious. It really works completely with any app, no matter framework, and has plugins to log further context from Redux, Vuex, and @ngrx/retailer.

Along with logging Redux actions and state, LogRocket data console logs, JavaScript errors, stacktraces, community requests/responses with headers + our bodies, browser metadata, and customized logs. It additionally devices the DOM to report the HTML and CSS on the web page, recreating pixel-perfect movies of even probably the most complicated single-page and cell apps.

.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments