Đây là phần cuối cùng trong Sê-ri 4 phần của tôi, nơi tôi học cách xây dựng một trò chơi Platformer đơn giản với công cụ Flame. Chúng ta đã biết cách thêm một nhân vật người chơi hoạt hình mà tôi đặt tên là Cậu bé, cách tạo cấp độ trò chơi có thể cuộn bằng trình chỉnh sửa Lát gạch và cách thêm trọng lực và nhảy với sự trợ giúp của tính năng phát hiện va chạm (phần 1 , 2 , 3 ).
Trong phần này, chúng tôi sẽ thêm xu mà nhân vật có thể thu thập được, HUD để hiển thị số xu mà người chơi có và màn hình chiến thắng mà chúng tôi sẽ hiển thị sau khi thu thập tất cả xu.
Chúng ta cần cho trò chơi biết nơi sinh ra tiền (hoặc bất kỳ đối tượng trò chơi nào khác cho vấn đề đó). Như bạn có thể đoán, chúng ta sẽ sử dụng trình chỉnh sửa Lát gạch để thêm một lớp đối tượng khác, theo cách tương tự như cách chúng ta đã thêm Nền tảng, nhưng có hai điểm khác biệt:
Với các nền tảng, chúng tôi đưa hình ảnh sprite vào dữ liệu cấp độ. Phương pháp này không phù hợp lắm với tiền xu, vì chúng tôi muốn xóa đối tượng sau khi người chơi thu thập nó. Vì vậy, chúng tôi sẽ chỉ thêm các điểm sinh sản, để biết vị trí tiền xu sẽ xuất hiện trong trò chơi và việc kết xuất sẽ được thực hiện bằng cách sử dụng các thành phần Flame.
Đối với các nền tảng, chúng tôi đã sử dụng các hình chữ nhật có kích thước bất kỳ, nhưng đối với các đồng xu, chúng tôi sẽ thêm các điểm sinh sản có kích thước bằng 1 ô. Mặc dù vậy, nếu bạn muốn thêm từng dòng tiền xu (xin chào Mario), bạn có thể dễ dàng làm như vậy bằng cách sửa đổi mã trò chơi và xem xét kích thước của điểm xuất hiện khi thêm các đối tượng tiền xu. Nhưng với mục đích của loạt bài này, chúng tôi giả định rằng điểm sinh ra tiền xu là 1x1.
Mở cấp độ của chúng tôi trong trình chỉnh sửa Lát gạch và tạo một lớp đối tượng mới có tên là Tiền xu. Sau đó, sử dụng công cụ Hình chữ nhật, thêm một số điểm sinh sản trên bản đồ để chúng tôi thêm các thành phần tiền xu bằng công cụ trò chơi. Cấp độ của tôi bây giờ trông như thế này:
Tôi nên nói thêm rằng trò chơi của chúng tôi khá đơn giản và chúng tôi biết rằng những hình chữ nhật trống này sẽ biến thành tiền xu trong thời gian chạy trò chơi. Nhưng nếu chúng ta muốn thêm nhiều loại đối tượng hơn, sẽ rất khó để phân biệt chúng. May mắn thay, Tiled có một công cụ cho việc đó, được gọi là “Chèn ô xếp” có thể thêm tín hiệu trực quan cho mọi đối tượng, nhưng những hình ảnh này sẽ không được hiển thị trong trò chơi.
Được rồi, hãy lưu cấp độ và quay lại IDE. Hãy thêm lớp Coin
của chúng ta vào thư mục /objects/
.
class Coin extends SpriteAnimationComponent with HasGameRef<PlatformerGame> { late final SpriteAnimation spinAnimation; late final SpriteAnimation collectAnimation; Coin(Vector2 position) : super(position: position, size: Vector2.all(48)); @override Future<void> onLoad() async { spinAnimation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.COIN), SpriteAnimationData.sequenced( amount: 4, textureSize: Vector2.all(16), stepTime: 0.12, ), ); collectAnimation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.COIN), SpriteAnimationData.range( start: 4, end: 7, amount: 8, textureSize: Vector2.all(16), stepTimes: List.filled(4, 0.12), loop: false ), ); animation = spinAnimation; final hitbox = RectangleHitbox() ..collisionType = CollisionType.passive; add(hitbox); return super.onLoad(); } }
Chúng tôi có 2 hoạt ảnh khác nhau để quay và thu thập và một RectangleHitbox
, để kiểm tra va chạm với trình phát sau này.
Tiếp theo, quay lại game.dart
và sửa đổi phương thức spawnObjects
để sinh ra tiền của chúng ta:
final coins = tileMap.getLayer<ObjectGroup>("Coins"); for (final coin in coins!.objects) { add(Coin(Vector2(coin.x, coin.y))); }
Chạy trò chơi và xem thêm tiền:
Quay trở lại coin.dart
và thêm phương thức collect
:
void collect() { animation = collectAnimation; collectAnimation.onComplete = () => { removeFromParent() }; }
Khi phương thức này được gọi, chúng ta sẽ chuyển hoạt ảnh quay sang thu thập và sau khi hoàn thành, chúng ta sẽ xóa thành phần này khỏi trò chơi.
Sau đó, vào lớp theboy.dart
và ghi đè phương thức onCollisionStart
:
@override void onCollisionStart(Set<Vector2> intersectionPoints, PositionComponent other) { if (other is Coin) { other.collect(); } super.onCollisionStart(intersectionPoints, other); }
Lý do tại sao chúng tôi sử dụng onCollisionStart
thay vì onCollision
là vì chúng tôi muốn gọi lại va chạm chỉ kích hoạt một lần.
Tiền xu hiện đang biến mất khi va chạm với The Boy. Hãy thêm giao diện người dùng để theo dõi số lượng tiền thu được.
HUD, hay màn hình hiển thị head-up, chỉ đơn giản là một thanh trạng thái hiển thị bất kỳ thông tin nào về trò chơi: điểm đánh, đạn, v.v. Chúng tôi sẽ hiển thị một biểu tượng đồng xu cho mỗi đồng xu đã thu thập được.
Để đơn giản, tôi sẽ lưu trữ số xu trong một biến, nhưng đối với các giao diện phức tạp hơn, hãy cân nhắc sử dụng gói flame_bloc , gói này cho phép bạn cập nhật và quan sát trạng thái trò chơi một cách thuận tiện.
Thêm một lớp mới sẽ chứa logic HUD: lib/hud.dart
class Hud extends PositionComponent with HasGameRef<PlatformerGame> { Hud() { positionType = PositionType.viewport; } void onCoinsNumberUpdated(int total) { final coin = SpriteComponent.fromImage( game.images.fromCache(Assets.HUD), position: Vector2((50 * total).toDouble(), 50), size: Vector2.all(48)); add(coin); } }
Hai điều thú vị ở đây:
positionType
thành PositionType.viewport
để dán HUD của chúng tôi vào góc màn hình. Nếu chúng tôi không làm điều đó, do chuyển động của máy ảnh, HUD sẽ chuyển động theo cấp độ.onCoinsNumberUpdated
sẽ được gọi mỗi khi người chơi thu thập xu. Nó sử dụng total
tham số để tính toán độ lệch của biểu tượng đồng xu tiếp theo, sau đó thêm một sprite đồng xu mới vào vị trí được tính toán.
Tiếp theo, quay lại tệp game.dart
và thêm các biến lớp mới:
int _coins = 0; // Keeps track of collected coins late final Hud hud; // Reference to the HUD, to update it when the player collects a coin
Sau đó, thêm thành phần Hud
ở cuối phương thức onLoad
:
hud = Hud(); add(hud);
Và thêm một phương pháp mới:
void onCoinCollected() { _coins++; hud.onCoinsNumberUpdated(_coins); }
Cuối cùng, gọi nó từ phương thức collect
Coin
:
void collect() { game.onCoinCollected(); animation = collectAnimation; collectAnimation.onComplete = () => { removeFromParent() }; }
Tuyệt vời, HUD của chúng tôi hiện hiển thị số tiền chúng tôi đã thu thập được!
Điều cuối cùng tôi muốn thêm là màn hình Win, màn hình này sẽ được hiển thị sau khi người chơi thu thập tất cả tiền xu.
Thêm một const mới vào lớp PlatformerGame
:
late int _totalCoins;
Và gán cho nó số xu chúng ta có trong cấp độ. Thêm dòng này vào cuối phương thức spawnObjects
:
_totalCoins = coins.objects.length;
Và thêm phần này vào cuối phương thức onCoinCollected
.
Lưu ý rằng bạn có thể cần thêm import 'package:flutter/material.dart';
thủ công.
if (_coins == _totalCoins) { final text = TextComponent( text: 'U WIN!', textRenderer: TextPaint( style: TextStyle( fontSize: 200, fontWeight: FontWeight.bold, color: Colors.white, ), ), anchor: Anchor.center, position: camera.viewport.effectiveSize / 2, )..positionType = PositionType.viewport; add(text); Future.delayed(Duration(milliseconds: 200), () => { pauseEngine() }); }
Ở đây, tôi kiểm tra xem bộ đếm xu có bằng số xu trong cấp độ hay không và nếu nó có thêm nhãn Thắng trên đầu màn hình trò chơi hay không. Sau đó tạm dừng trò chơi để dừng chuyển động của người chơi. Tôi cũng đã thêm độ trễ 200 mili giây để nhãn được hiển thị trước khi tạm dừng.
Nó sẽ giống như thế này:
Và đó là trò chơi! Tất nhiên, bây giờ nó không giống như một trò chơi đã hoàn thành, nhưng với mọi thứ tôi đã giải thích, sẽ khá dễ dàng để thêm nhiều cấp độ, kẻ thù hoặc các vật phẩm sưu tập khác.
Phần này kết thúc loạt bài.
Công cụ Flame có nhiều thứ hơn để cung cấp mà tôi chưa đề cập đến, bao gồm công cụ vật lý Forge2D, hạt, hiệu ứng, menu trò chơi, âm thanh, v.v. Nhưng sau khi hoàn thành loạt bài này, tôi đã hiểu được công cụ đó như thế nào và tôi có hiểu biết về cách xây dựng các trò chơi phức tạp hơn.
Flame là một công cụ mạnh mẽ nhưng dễ sử dụng và dễ học. Đó là mô-đun, cho phép mang đến những thứ thú vị khác như Box2D và nó được duy trì tích cực. Nhưng một trong những ưu điểm lớn nhất của Flame là nó được xây dựng dựa trên Flutter, có nghĩa là nó cung cấp hỗ trợ đa nền tảng với một chút công việc bổ sung. Tuy nhiên, là một tiện ích mở rộng của Flutter có nghĩa là tất cả các vấn đề về Flutter cũng tồn tại trong Flame. Ví dụ: lỗi khử răng cưa của Flutter đã tồn tại trong vài năm mà không được giải quyết và bạn cũng có thể nhận thấy lỗi này trong trò chơi mà chúng tôi đã xây dựng. Nhưng nhìn chung, nó là một công cụ tuyệt vời để xây dựng trò chơi đáng để thử.
Mã hoàn chỉnh cho hướng dẫn này, bạn có thể tìm thấy trong github của tôi
Ở cuối mỗi phần, tôi sẽ thêm danh sách những người sáng tạo tuyệt vời và tài nguyên mà tôi đã học được.