I am participating in the nuggets Community game creative submission Contest. For details, please see: Game Creative Submission Contest

preview

The online demo

Web version: V.idoo. top/web/fallDow… (Please use mobile phone to access, not suitable for PC)

Android version: Fall.apk

Introduction to the

When I was in high school, I used to go to the app store and look for casual games to play. One of my favorite games was FallDown Delux. Every time I swiped or got a new phone, I backed it up and didn’t want to delete it.

Then I became a programmer, and one day IT occurred to me: Why not make one myself?

So I was very excited to decompile it, want to learn from its source code to implement ideas.

But things didn’t work out.

When I unpack its APK installation package, I found that this product is written with cocos2D-X, I took apktool decompiled a lonely ~

Can’t, since can’t get somebody else’s source code to pry a few, that can only oneself brave scalp to write a come out, the final result is as follows:

Implementation details

The rules of the game

First, the rules of the game:

  1. The ball needs to pass through the gap in the floor and drop to the next floor before the red line catches up
  2. Each floor of the gap number, location is different, randomly generated
  3. The more floors the ball passes through, the higher the score
  4. When evading the red line, the ball can also “eat” coins randomly placed on the floor for extra points
  5. When a certain score is reached, the ball level is upgraded and the floor color is updated
  6. The red line catch-up speed increases with the number of floors the ball passes through
  7. When the red line catches up with the ball, the game ends

It can be seen that at present, we need to solve the following parts:

  1. The number and position of the gap and gold coins on the floor are randomly generated
  2. Measurement of the distance from the red line to the ball and adjustment of the red line descending speed
  3. The camera follows the ball so that it falls while still in the center of view
  4. The score rating system and the end of the game
  5. Monitor screen click events to control the direction of ball movement

Let’s implement each part together.

Initialize the

Here, we select Flutter + Flame + Box2D as the main project technology stack.

Flame is a 2D game engine inside the Flutter ecosystem, responsible for rendering the game scene and event loops.

Box2D, on the other hand, is a physics engine that does things like object motion and collision detection in the physical world.

Randomly generate floors and coins

First, we divide each row into 21 small cells, and then specify:

  1. A gap length or brick length is 3
  2. There are 1-2 vacancies on each floor
  3. There is at least one brick between the two openings
  4. The first level has only one vacancy in the middle
  5. No gold is generated in tier 1

To simplify the calculation further, let’s divide the 21 cells into 7 groups of 3,

Then randomly select one or two discontinuous groups as the vacant positions.

/// Group number
const kGroups = 7;

/// Each group is 3 boxes wide
const kGroupWidth = 3;

/// Each layer has 21 boxes
const kBoxsPerFloor = 21;

/// Floor height
const kFloorHeight = 20;

/// The brick width
const kBoxWidth = 30;

/// Pick the elements of the array at random
pickOne(List items) => items[Random().nextInt(items.length)];

/// Create a floor
void generateFloor(Box2DComponent box, double floor) {
  // Current floor height
  double floorHeight = floor * kFloorHeight;
  // Notch subscript
  List<int> blankIndexs = [];
  // Remaining available subscripts
  List<int> remainIndexs = 0.rangeTo(kGroups - 1);
  // Brick position
  List<int> boxPositions = [];
  // Notch position
  List<int> blankPositions = [];
  / / bricks
  varboxs = <Box>[].. length = kBoxsPerFloor;/ / gold
  varcoins = <Coin>[].. length = kBoxsPerFloor;if (floor == 0) {
    // The first barrier has only a gap in the middle
    blankIndexs = [(kGroups - 1) / 2];
  } else {
    // Generate a random number of vacancies (at least one)
    int blankCounts = pickOne([1.2]); [].. length = blankCounts .. forEach((_) {final blankIndex = pickOne(remainIndexs);
        blankIndexs.add(blankIndex);
        blankPositions.addAll((3 * blankIndex).rangeTo(3 * (blankIndex + 1)));
        remainIndexs.removeRange(blankIndex - 2, blankIndex + 1);
      });
  }
  // Fill the left and right sides of the empty position
  for (final blank in blankPositions) {
    / / the left
    if (blank - 1 > - 1 && !blankPositions.contains(blank - 1)) {
      boxs[blank - 1] = Box.right(
        box,
        Vector2(kBoxWidth * (blank - 1), floorHeight),
      );
    }
    / / right
    if (blank + 1< kBoxsPerFloor && ! blankPositions.contains(blank +1)) {
      boxs[blank + 1] = Box.left(
        box,
        Vector2(kBoxWidth * (blank + 1), floorHeight), ); }}// Fill the remaining positions with bricks
  for (int i in 0.rangeTo(kBoxsPerFloor - 1)) {
    if(! blankIndexs.contains(i) && boxs[i] ==null) { boxs[i] = Box.center( box, Vector2(kBoxWidth * i, floorHeight), ); boxPositions.add(i); }}// Fill gold
  if(floor ! =0) {
    // Generate random coins
    int coinCounts = Random().nextInt(boxPositions.length - 1); [].. length = coinCounts .. forEach((_) {// Randomly generate vacant positions
        final coinPosition = pickOne(boxPositions);
        boxPositions.remove(coinPosition);
        coins[coinPosition] = Coin(
          box,
          Vector2(kBoxWidth * coinPosition, floorHeight - kBoxWidth),
        );
      });
  }

  // ...
}
Copy the code

Red line position and falling speed

According to the rules of the game we know:

  1. The speed of the red line is proportional to the height of the ball
  2. The red line is always at the top of the screen, not beyond the scope of the screen view, that is, the maximum distance between the ball and the red line is half the height of the screen

As you can see, as the game progresses, it gets harder and harder,

The high score requires the ball to fall faster and faster until the red line catches up with the ball and the game is over.

class Deadline {.../// Update the deadline
    @override
    void update(double dt) {
        super.update(dt);
        if (deadline.positionY > ball.positionY) {
            // When the Y-axis passes the top of the ball, the game is over
            gameOver();
        } else {
            // Update deadline location
            deadline.positionY -= speed * ball.size;
            if (deadline.positionY < 0) {
            // Deadline is always in screen view
            deadline.positionY = 0;
            }
            if (deadline.positionY > ball.positionY) {
            // Deadline stops falling after catching the ball
            deadline.positionY = ball.positionY + 1; }}// Update speed
        if (score % 20= =0&& lastScore ! = score) {// Every 20 points, the deadline drops faster and the difficulty increases
            lastScore = score;
            speed += 0.01; }}}Copy the code

The camera view follows the ball

The entire game scene can be viewed as a 2D physical world, with gravity applied vertically down the screen as follows:

  1. The ball cannot roll out of the screen horizontally, that is, the left and right sides of the screen are the physical boundary (wall).
  2. The ball stays in the middle of the screen as it falls, meaning the camera follows the ball down
  3. Floors out of view will be recycled memory resources in time
  4. The next floor into view will be generated before entering

Much like dropping a ball into a “bottomless pit” and letting it fall free, forever.

class Ball {.../// Update the ball
    @override
    void update(double dt) {
        super.update(dt);
        // Get the location of the ball's physical world and map it to the screen
        ball.positionY = ball.viewport.getWorldToScreen(ball.center).y;
        if (positionY > game.cameraDistance) {
            // Wait until the ball is in the center of the field of view before the camera starts tracking, otherwise the ball will blink
            box.cameraFollow(body, vertical: . 0);
        }
        // Ball impulse value
        final linearImpulse = ball.gravity * // Gravitational acceleration
            ball.density * / / density
            3.14 * //pi
            (ball.size.width / 2) * //r^2
            (ball.size.width / 2);
        // Apply a constant impulse down the middle of the ball
        ball.applyLinearImpulse(
            Vector2(0, -linearImpulse),
            ball.center,
        );
        if (movingLeft) {
            // The ball moves left, applying a constant impulse to the left in the middle of the ball
            ball.applyLinearImpulse(
            Vector2(-linearImpulse, 0),
            ball.center,
            );
        }
        if (movingRight) {
            // The ball moves to the right, applying a constant impulse to the right in the middle of the ball
            ball.applyLinearImpulse(
            Vector2(linearImpulse, 0), ball.center, ); }}}/// Number of floors that can be accommodated in the screen
const kScreenFloors = 10;

class Floor {.../// Update the floor
    @override
    void update(double dt) {
        super.update(dt);
        // Clear floors off the screen
        floors.where((b) => b.positionY < 0).toList().forEach((floor) {
            floors.remove(floor); // Delete the floor
        });
        // Add a new floor
        double lastPositionY;
        while (floors.length < kScreenFloors) {
            // Last floor locationlastPositionY ?? = floors.last.positionY;// Next floor location
            lastPositionY += floorHeight;
            // Add a flooraddFloor(lastPositionY); }}}Copy the code

Ball movement direction control

There are two ways to control the direction of the ball, one by tapping the left and right areas of the screen, and the other by gravity sensing.

lass MyGame extends Box2DGame with HasTapableComponents, KeyboardEvents {
  // Acceleration sensor x axis status
  double sensorX = 0;
  // Accelerometer subscription stream
  StreamSubscription<dynamic> _stream;
  // Whether to use gravity control
  bool _useGravity = false;
  // If you are using gravity control and not on the Web side, enable gravity control mode
  getuseGravity => _useGravity && ! kIsWeb;// The Web side does not support gravity control

  init() async {
    if (useGravity) {
      // Enable acceleration sensor listening
      _stream = accelerometerEvents.listen((AccelerometerEvent event) {
        sensorX = event.x;
      });
    }
  }

  dispose() {
    if (useGravity) {
      / / close the flow
      _stream?.cancel();
    }
  }

  @override
  void update(double dt) {
    / /...

    // Gravity sensor control direction
    if (useGravity) {
      if (sensorX) {
        ball.startMoveLeft();
      } else{ ball.stopMoveLeft(); }}}@override
  void onKeyEvent(event) {
    if (event is RawKeyDownEvent) {
      if (event.data.keyLabel == 'ArrowLeft') {
        ball.startMoveLeft();
      } else{ ball.stopMoveLeft(); }}}@override
  void onTapDown(pointerId, TapDownDetails details) {
    if (details.globalPosition.dx < screenSize.width / 2) {
      ball.startMoveLeft();
    } else{ ball.startMoveRight(); }}@override
  void onTapUp(pointerId, TapUpDetails details) {
    if (details.globalPosition.dx < screenSize.width / 2) {
      ball.stopMoveLeft();
    } else{ ball.stopMoveRight(); }}}Copy the code

conclusion

The above is all the implementation points involved in the whole game, I believe that you can also be smart to implement this simple game.

If you are interested in more detailed code implementation, you can go to github.com/idootop/fal… Consult here, refuse white piao, beg a Star 🌟

PS: The code of the project is relatively old (my work in 2019). At that time, the code was still relatively green. It is just for your reference.

One more thing

The fun Learning about Flutter “Mini Game” is the last chapter of the “Fun Learning about Flutter” series. It is a fun practice part of Flutter development after you have mastered the basics of Flutter development.

PS: The basic part of “Flutter” series has not been written yet. I will spend some time every week to gradually improve the series. If you are interested in Flutter, please stay tuned.