mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-01 19:12:31 +08:00
fix: Klondike tutorial to use built-in world and camera (#2813)
Firstly, (re Issue #2799) Flame 1.9.x broke the Klondike Tutorial, which was not set up to use the new built-in world and camera. So I modified the KlondikeGame's onLoad() code to use the built-in world and camera and no longer create its own World and CameraComponent. I also updated the step2.md documentation file, which instructs new users of Flame on how to use the world and the camera, how to set up a game-oriented co-ordinate system and how to fit it into the device screen. Secondly, (re Issue #2798) Klondike was not adhering to the design stated in the Tutorial, that all 52 Card objects would be owned by the KlondikeGame. During the shuffle and deal sequence it was using removeLast() on the List\<Card\>cards, where the cards are kept, the result being that only 24 of the 52 cards were left in the list after the deal. I have fixed the bug and updated the Tutorial documentation (step4) which discusses how the deal is coded. This bug does not affect the gameplay as it stands, but it does block the way to future features at the game level, such as an option to re-deal or a win-animation where all cards fly off the screen.
This commit is contained in:
@ -45,19 +45,15 @@ class KlondikeGame extends FlameGame {
|
||||
),
|
||||
);
|
||||
|
||||
final world = World()
|
||||
..add(stock)
|
||||
..add(waste)
|
||||
..addAll(foundations)
|
||||
..addAll(piles);
|
||||
add(world);
|
||||
world.add(stock);
|
||||
world.add(waste);
|
||||
world.addAll(foundations);
|
||||
world.addAll(piles);
|
||||
|
||||
final camera = CameraComponent(world: world)
|
||||
..viewfinder.visibleGameSize =
|
||||
Vector2(cardWidth * 7 + cardGap * 8, 4 * cardHeight + 3 * cardGap)
|
||||
..viewfinder.position = Vector2(cardWidth * 3.5 + cardGap * 4, 0)
|
||||
..viewfinder.anchor = Anchor.topCenter;
|
||||
add(camera);
|
||||
camera.viewfinder.visibleGameSize =
|
||||
Vector2(cardWidth * 7 + cardGap * 8, 4 * cardHeight + 3 * cardGap);
|
||||
camera.viewfinder.position = Vector2(cardWidth * 3.5 + cardGap * 4, 0);
|
||||
camera.viewfinder.anchor = Anchor.topCenter;
|
||||
|
||||
final cards = [
|
||||
for (var rank = 1; rank <= 13; rank++)
|
||||
@ -66,13 +62,16 @@ class KlondikeGame extends FlameGame {
|
||||
cards.shuffle();
|
||||
world.addAll(cards);
|
||||
|
||||
var cardToDeal = cards.length - 1;
|
||||
for (var i = 0; i < 7; i++) {
|
||||
for (var j = i; j < 7; j++) {
|
||||
piles[j].acquireCard(cards.removeLast());
|
||||
piles[j].acquireCard(cards[cardToDeal--]);
|
||||
}
|
||||
piles[i].flipTopCard();
|
||||
}
|
||||
cards.forEach(stock.acquireCard);
|
||||
for (var n = 0; n <= cardToDeal; n++) {
|
||||
stock.acquireCard(cards[n]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -220,16 +220,15 @@ are calculated using simple arithmetics. This should all happen inside the
|
||||
);
|
||||
```
|
||||
|
||||
Then we create the main `World` component, add to it all the components that
|
||||
we just created, and finally add the `world` to the game.
|
||||
Since Flame version 1.9.0, `FlameGame` sets up default `world` and `camera`
|
||||
objects. `KlondikeGame` is an extension of `FlameGame`, so we can add to that
|
||||
`world` all the components that we just created.
|
||||
|
||||
```dart
|
||||
final world = World()
|
||||
..add(stock)
|
||||
..add(waste)
|
||||
..addAll(foundations)
|
||||
..addAll(piles);
|
||||
add(world);
|
||||
world.add(stock)
|
||||
world.add(waste)
|
||||
world.addAll(foundations)
|
||||
world.addAll(piles);
|
||||
```
|
||||
|
||||
```{note}
|
||||
@ -247,11 +246,11 @@ If you don't `await` the future from `.add()`, then the component will be added
|
||||
to the game anyways, and in the same amount of time.
|
||||
```
|
||||
|
||||
Lastly, we create a camera object to look at the `world`. Internally, the camera
|
||||
consists of two parts: a **viewport** and a **viewfinder**. The default viewport
|
||||
is `MaxViewport`, which takes up the entire available screen size -- this is
|
||||
exactly what we need for our game, so no need to change anything. The
|
||||
viewfinder, on the other hand, needs to be set up to properly take the
|
||||
Lastly, we use FlameGame's `camera` object to look at the `world`. Internally,
|
||||
the camera consists of two parts: a **viewport** and a **viewfinder**. The
|
||||
default viewport is `MaxViewport`, which takes up the entire available screen
|
||||
size -- this is exactly what we need for our game, so no need to change
|
||||
anything. The viewfinder, on the other hand, needs to be set up to take the
|
||||
dimensions of the underlying world into account.
|
||||
|
||||
We want the entire card layout to be visible on the screen without the need to
|
||||
@ -281,12 +280,10 @@ but if the screen is too tall, we want the content to be aligned at the
|
||||
top.
|
||||
|
||||
```dart
|
||||
final camera = CameraComponent(world: world)
|
||||
..viewfinder.visibleGameSize =
|
||||
Vector2(cardWidth * 7 + cardGap * 8, 4 * cardHeight + 3 * cardGap)
|
||||
..viewfinder.position = Vector2(cardWidth * 3.5 + cardGap * 4, 0)
|
||||
..viewfinder.anchor = Anchor.topCenter;
|
||||
add(camera);
|
||||
camera.viewfinder.visibleGameSize =
|
||||
Vector2(cardWidth * 7 + cardGap * 8, 4 * cardHeight + 3 * cardGap);
|
||||
camera.viewfinder.position = Vector2(cardWidth * 3.5 + cardGap * 4, 0);
|
||||
camera.viewfinder.anchor = Anchor.topCenter;
|
||||
```
|
||||
|
||||
If you run the game now, you should see the placeholders for where the various
|
||||
|
||||
@ -425,19 +425,32 @@ method so that it looks like this:
|
||||
cards.shuffle();
|
||||
world.addAll(cards);
|
||||
|
||||
int cardToDeal = cards.length - 1;
|
||||
for (var i = 0; i < 7; i++) {
|
||||
for (var j = i; j < 7; j++) {
|
||||
piles[j].acquireCard(cards.removeLast());
|
||||
piles[j].acquireCard(cards[cardToDeal--]);
|
||||
}
|
||||
piles[i].flipTopCard();
|
||||
}
|
||||
cards.forEach(stock.acquireCard);
|
||||
for(int n = 0; n <= cardToDeal; n++) {
|
||||
stock.acquireCard(cards[n]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note how we remove the cards from the deck and place them into `TableauPile`s one by one, and only
|
||||
after that we put the remaining cards into the stock. Also, the `flipTopCard` method in the
|
||||
`TableauPile` class is as trivial as it sounds:
|
||||
Note how we deal the cards from the deck and place them into `TableauPile`s one by one, and only
|
||||
after that we put the remaining cards into the stock.
|
||||
|
||||
Recall that we decided earlier that all the cards would be owned by the `KlondikeGame` itself. So
|
||||
they are put into a generated List structure called `cards`, shuffled and added to the `world`. This
|
||||
List should always have 52 cards in it, so a descending index `cardToDeal` is used to deal 28 cards
|
||||
one by one from the top of the deck into piles that acquire references to the cards in the deck. An
|
||||
ascending index is used to deal the remaining 24 cards into the stock in correct shuffled order. At
|
||||
the end of the deal there are still 52 `Card` objects in the `cards` list. In the card piles we
|
||||
used `removeList()` to retrieve a card from a pile, but not here because it would remove cards
|
||||
from `KlondikeGame`'s ownership.
|
||||
|
||||
The `flipTopCard` method in the `TableauPile` class is as trivial as it sounds:
|
||||
|
||||
```dart
|
||||
void flipTopCard() {
|
||||
|
||||
Reference in New Issue
Block a user