Flutter游戲:垃圾里會生蚊子

加載游戲資源

在開始下面的內(nèi)容之前哗讥,最好的話是先把《開始用Flutter做游戲吧》過一遍嚷那,然后再完成《Flutter游戲:萬有引力定律》里的游戲,因為下面的內(nèi)容是在該游戲的基礎(chǔ)上開發(fā)的杆煞。

首先下載這個游戲要用到的游戲資源文件魏宽,然后在項目目錄下建立assets/images目錄,在該目錄下再分別建立bgflies目錄决乎,用于存放背景圖片和組件圖片队询。

資源文件就位后,在pubspec.yaml文件里添加對這些資源文件的引用构诚。

flutter:

  uses-material-design: true

  assets:
    - assets/images/bg/backyard.png
    - assets/images/flies/agile-fly-1.png
    - assets/images/flies/agile-fly-2.png
    - assets/images/flies/agile-fly-dead.png
    - assets/images/flies/drooler-fly-1.png
    - assets/images/flies/drooler-fly-2.png
    - assets/images/flies/drooler-fly-dead.png
    - assets/images/flies/mosquito-fly-1.png
    - assets/images/flies/mosquito-fly-2.png
    - assets/images/flies/mosquito-fly-dead.png
    - assets/images/flies/hungry-fly-1.png
    - assets/images/flies/hungry-fly-2.png
    - assets/images/flies/hungry-fly-dead.png
    - assets/images/flies/macho-fly-1.png
    - assets/images/flies/macho-fly-2.png
    - assets/images/flies/macho-fly-dead.png

下面我們要在游戲開始時加載所有資源娘摔,這會花費幾毫秒的時間,但是又有誰會注意到這幾毫秒內(nèi)的黑屏顯示呢唤反。打開main.dart文件凳寺,在頂部添加代碼以導(dǎo)入flame/flame.dart包,然后就可以預(yù)加載游戲資源了彤侍。

...

import 'package:flame/flame.dart';

void main() async {
  Util flameUtil = Util();
  await flameUtil.fullScreen();
  await flameUtil.setOrientation(DeviceOrientation.portraitUp);

  Flame.images.loadAll(<String>[
    'bg/backyard.png',
    'flies/agile-fly-1.png',
    'flies/agile-fly-2.png',
    'flies/agile-fly-dead.png',
    'flies/drooler-fly-1.png',
    'flies/drooler-fly-2.png',
    'flies/drooler-fly-dead.png',
    'flies/mosquito-fly-1.png',
    'flies/mosquito-fly-2.png',
    'flies/mosquito-fly-dead.png',
    'flies/hungry-fly-1.png',
    'flies/hungry-fly-2.png',
    'flies/hungry-fly-dead.png',
    'flies/macho-fly-1.png',
    'flies/macho-fly-2.png',
    'flies/macho-fly-dead.png',
  ]);

  ...
}

上面的代碼中肠缨,使用一個String列表作為參數(shù)傳遞給imagesloadAll方法,該方法用于預(yù)加載String列表指向的圖像文件盏阶。這些圖像將緩存在Flame的靜態(tài)變量中晒奕,以便以后可以重復(fù)使用。

設(shè)置游戲背景

現(xiàn)在游戲的背景是一個灰藍純色背景,看起來還不錯脑慧,但是我們接下來還是要改變它魄眉。我們預(yù)加載的資源中有一個bg/backyard.png,這是一個高度很高的垂直圖片闷袒,因為我們的游戲目前只關(guān)心寬度坑律,不管手機的縱橫比如何,這張背景圖都可以覆蓋整個屏幕囊骤。

接下來晃择,創(chuàng)建一個組件文件components/backyard.dart,將背景邏輯分開來也物,該文件聲明了一個Backyard類宫屠,有一個構(gòu)造函數(shù)和另外渲染(render)、更新(update)方法滑蚯。

這個Backyard類還有一個最終(final)的HitGame實例變量浪蹂,它將作為包含該組件的游戲?qū)嵗捌鋵傩缘逆溄樱梢詤⒖?code>components/fly.dart中的實現(xiàn)告材。另一個實例變量是一個名為bgSprite的精靈(Sprite)乌逐,它會保存我們稍后將繪制在屏幕上的精靈(Sprite)數(shù)據(jù)。

import 'dart:ui';
import 'package:flame/sprite.dart';
import 'package:hello_flame/hit-game.dart';

class Backyard {
  final HitGame game;
  Sprite bgSprite;

  Backyard(this.game) {
    bgSprite = Sprite('bg/backyard.png');
  }

  void render(Canvas c) {}

  void update(double t) {}
}

在構(gòu)造函數(shù)中创葡,通過創(chuàng)建一個新的精靈(Sprite)并傳遞要使用的資源文件名來初始化bgSprite變量浙踢。文件bg/backyard.png已經(jīng)在main.dart中被預(yù)加載,因此無需任何加載時間即可使用灿渴。

文件頂部的import語句導(dǎo)入了3個內(nèi)容洛波,dart:ui允許我們訪問畫布(Canvas)類,flame/sprite.dart允許我們使用精靈(Sprite)類骚露,hello_flame/hit-game.dart使我們可以訪問HitGame類蹬挤。

那么在添加背景圖以后,我們怎么定位游戲組件的位置勒棘幸?如果打開bg/backyard.png文件焰扳,可以看到它的大小為1080 x 2760像素,我們不用關(guān)注它的物理像素或邏輯像素误续,我們只要關(guān)心我們的背景有9個圖塊的寬度就好了吨悍。

1080(像素) ÷ 9(圖塊) = 120(每個圖塊的像素)

2760(像素) ÷ 120(每個圖塊的像素) = 23(圖塊)

如同上面的計算結(jié)果所示,我們當(dāng)前使用的背景圖像寬為9個圖塊蹋嵌、高為23個圖塊育瓜。

繪制游戲背景

現(xiàn)在我們可以開始繪制游戲背景了,將背景圖像底部錨定在屏幕的底部栽烂,為此需要定義一個包含背景尺寸的矩形(Rect)躏仇,這里需要正確計算大小恋脚,以便在渲染過程中保留背景圖像的寬高比。

components/backyard.dart文件中添加一個名為bgRect的矩形(Rect)實例變量焰手,并在構(gòu)造函數(shù)內(nèi)部糟描,初始化這個矩形(Rect)。

class Backyard {
  ...

  Rect bgRect;

  Backyard(this.game) {
    bgSprite = Sprite('bg/backyard.png');
    
    bgRect = Rect.fromLTWH(
      0,
      game.screenSize.height - (game.tileSize * 23),
      game.tileSize * 9,
      game.tileSize * 23,
    );
  }

  ...
}

上面代碼中书妻,矩形(Rect)構(gòu)造函數(shù)fromLTWH的4個參數(shù)分別對應(yīng)于x坐標(biāo)船响、y坐標(biāo)、寬度和高度的值驻子。我們以最大寬度繪制背景,因此估灿,它的寬度從x開始延伸到game.tileSize * 9為止崇呵,我們也可以在這里使用game.screenSize.width,因為game.tileSize是等于game.screenSize.width ÷ 9的馅袁。

在前面的計算中域慷,我們已知背景圖像為9 x 23的圖塊大小,因此要繪制整個背景圖像的話汗销,只需要設(shè)置game.tileSize * 23的高度即可犹褒。最后,y坐標(biāo)是一個負數(shù)弛针,對應(yīng)于屏幕大小和背景圖像的差異叠骑。

如果設(shè)備屏幕的寬高比為9:16,則屏幕的高度為16 * 圖塊大小削茁,如果從中減去23 * 圖塊大小宙枷,我們就可以得到-7 * 圖塊大小的值,這意味著背景圖片是使用屏幕頂部邊緣上方的7個圖塊大小的地方開始繪制的茧跋。

通過上面的計算慰丛,背景圖像將始終錨定在設(shè)備屏幕的底部,最后瘾杭,我們在調(diào)用此組件的渲染(render)方法時就繪制背景圖像诅病。

  void render(Canvas c) {
    bgSprite.renderRect(c, bgRect);
  }

到這里為止,我們的components/backyard.dart里面應(yīng)該有以下代碼粥烁。

import 'dart:ui';
import 'package:flame/sprite.dart';
import 'package:hello_flame/hit-game.dart';

class Backyard {
  final HitGame game;
  Sprite bgSprite;

  Rect bgRect;

  Backyard(this.game) {
    bgSprite = Sprite('bg/backyard.png');

    bgRect = Rect.fromLTWH(
      0,
      game.screenSize.height - (game.tileSize * 23),
      game.tileSize * 9,
      game.tileSize * 23,
    );
  }

  void render(Canvas c) {
    bgSprite.renderRect(c, bgRect);
  }

  void update(double t) {}
}

添加游戲背景

上面我們已經(jīng)完成了背景組件贤笆,現(xiàn)在讓我們把它添加到游戲邏輯中,打開hit-game.dart文件讨阻,導(dǎo)入hello_flame/components/backyard.dart苏潜,然后添加一個名為backgroundBackyard類型的新實例變量。

然后在initialize方法中变勇,實例化一個新的Backyard對象恤左,并將其分配給實例變量background贴唇,而且,必須在確定屏幕大小后再執(zhí)行此操作飞袋,因為Backyard類的構(gòu)造函數(shù)中使用到了屏幕大小和圖塊大小值戳气。

還有就是,要像我們創(chuàng)建游戲組件Fly一樣巧鸭,使用關(guān)鍵字this傳遞當(dāng)前的HitGame實例瓶您。

...
import 'package:hello_flame/components/backyard.dart';

class HitGame extends Game {
  ...
  Backyard background;

  ...

  void initialize() async {
    enemy = List<Fly>();
    rnd = Random();
    resize(await Flame.util.initialDimensions());

    background = Backyard(this);
    produceFly();
  }

  ...
}

然后在渲染(render)方法中,調(diào)用background的渲染(render)方法并將畫布(Canvas)傳遞給它纲仍。同時呀袱,刪除我們之前繪制的一個純色矩形背景。

  void render(Canvas canvas) {
    // 刪除以下內(nèi)容
    // Rect bgRect = Rect.fromLTWH(0, 0, screenSize.width, screenSize.height);
    // Paint bgPaint = Paint();
    // bgPaint.color = Color(0xff576574);
    // canvas.drawRect(bgRect, bgPaint);
    background.render(canvas);

    enemy.forEach((Fly fly) => fly.render(canvas));
  }

當(dāng)我們現(xiàn)在運行游戲時郑叠,應(yīng)該可以看到游戲背景在不同手機上會有不同的高度夜赵。

游戲背景組件

改變游戲組件

目前,文件的預(yù)加載資源中乡革,有五種不同的蚊子素材寇僧,我們現(xiàn)在就重點看下它們的視覺差異。具體可以通過子類來實現(xiàn)沸版,就是創(chuàng)建一個類作為子類擴展現(xiàn)有的父類嘁傀。

我們的蚊子素材的大小相對于實例變量flyRect的矩形來說,會占用更大的圖塊大小视粮,要解釋清楚的話细办,可能要借助于下面的示例圖。

蚊子素材占用的圖塊大小.png

在上面的示例圖中蕾殴,精靈(sprite)將被繪制在藍色圖塊內(nèi)蟹腾,我們就稱它為精靈(sprite)矩形。但是點擊需要發(fā)生在紅色圖塊內(nèi)区宇,我們就稱它為命中矩形娃殖,而在代碼中它被命名為flyRect

在開始創(chuàng)建第一個子類之前议谷,我們要先準(zhǔn)備好一個可以進行擴展的父類炉爆。打開components/fly.dart文件,Fly類將具有所有蚊子種類共享的常用方法和變量卧晓。首先芬首,刪除畫矩形(drawRect)方法,因為我們不需要繪制矩形了逼裆,再清空渲染(render)方法郁稍。

然后,刪除所有對flyPaint的引用胜宇,因為該對象僅用于繪制矩形耀怜,從實例變量恢着、onTapDown處理方法和構(gòu)造函數(shù)中都刪除它。但是我們?nèi)匀粫褂?code>flyRect作為命中矩形财破,所以讓它留在文件中掰派。

class Fly {
  final HitGame game;
  Rect flyRect;
  // 刪除內(nèi)容
  // Paint flyPaint;
  bool isDead = false;
  bool isOffScreen = false;

  Fly(this.game, double x, double y) {
    flyRect = Rect.fromLTWH(x, y, game.tileSize, game.tileSize);
    // 刪除內(nèi)容
    // flyPaint = Paint();
    // flyPaint.color = Color(0xff6ab04c);
  }

  void render(Canvas c) {
    // 刪除內(nèi)容
    // c.drawRect(flyRect, flyPaint);
  }

  ...

  void onTapDown() {
    isDead = true;
    // 刪除內(nèi)容
    // flyPaint.color = Color(0xffff4757);
    game.produceFly();
  }
}

對于Fly類或其子類的每個實例,都需要準(zhǔn)備和存儲2組精靈(sprite)左痢,1組將由2個精靈(sprite)組成靡羡,這些精靈將1個接1個地顯示,以繪制出“飛行”的動畫效果俊性,我們需要一個List略步。

另一組將只有1個精靈(sprite)將在蚊子死亡時顯示,這里需要另一個實例變量來存儲將為“飛行”動畫顯示的精靈(sprite)定页。

在文件頂部導(dǎo)入flame/sprite.dart趟薄,并在實例變量部分中添加下面代碼。

...
import 'package:flame/sprite.dart';

class Fly {
  ...
  List<Sprite> flyingSprite;
  Sprite deadSprite;
  double flyingSpriteIndex = 0;

但是這些精靈(sprite)變量不會在Fly類中初始化拯勉,因為每個子類都會使用不同的精靈(sprite)竟趾,在渲染(render)方法中憔购,我們會根據(jù)實例的狀態(tài)(死或生)來渲染精靈(sprite)宫峦。

  void render(Canvas c) {
    if (isDead) {
      deadSprite.renderRect(c, flyRect.inflate(2));
    } else {
      flyingSprite[flyingSpriteIndex.toInt()].renderRect(c, flyRect.inflate(2));
    }
  }

在上面的代碼中,渲染(render)方法通過檢查isDead變量來決定顯示哪個精靈(sprite)玫鸟,如果當(dāng)前實例已死导绷,則渲染deadSprite,如果沒有屎飘,則渲染flyingSprite列表中的第0個下標(biāo)項妥曲。

對于flyingSpriteIndex.toInt()來說,List的精靈(sprite)項由整數(shù)索引訪問钦购,而flyingSpriteIndex是雙精度(double)類型的檐盟,所以需要先轉(zhuǎn)換為整型(int)。那么為啥它是雙精度(double)類型的呢押桃,因為我們將使用更新(update)方法中的時間增量(t)來遞增它葵萎。

最后一部分的.inflate(2)只是創(chuàng)建了一個被調(diào)用的矩形的副本,但是從中心開始按乘數(shù)膨脹唱凯,這里我們把乘數(shù)設(shè)置為2羡忘,因為從蚊子素材的大小來看,精靈(sprite)矩形的大小約是命中矩形的2倍磕昼。

到這里為止卷雕,我們的fly.dart里面應(yīng)該有以下代碼。

import 'dart:ui';
import 'package:hello_flame/hit-game.dart';
import 'package:flame/sprite.dart';

class Fly {
  final HitGame game;
  List<Sprite> flyingSprite;
  Sprite deadSprite;
  double flyingSpriteIndex = 0;
  Rect flyRect;
  bool isDead = false;
  bool isOffScreen = false;

  Fly(this.game, double x, double y) {
    flyRect = Rect.fromLTWH(x, y, game.tileSize, game.tileSize);
  }

  void render(Canvas c) {
    if (isDead) {
      deadSprite.renderRect(c, flyRect.inflate(2));
    } else {
      flyingSprite[flyingSpriteIndex.toInt()].renderRect(c, flyRect.inflate(2));
    }
  }

  void update(double t) {
    if (isDead) {
      flyRect = flyRect.translate(0, game.tileSize * 12 * t);
      if (flyRect.top > game.screenSize.height) {
        isOffScreen = true;
      }
    }
  }

  void onTapDown() {
    isDead = true;
    game.produceFly();
  }
}

創(chuàng)建組件子類

現(xiàn)在創(chuàng)建第一個蚊子種類票从,這是一個“正陈瘢”的種類滨嘱,就將它命名為MosquitoFly,一只正常飛行的蚊子蝎亚。在components文件夾下新建一個mosquito-fly.dart文件并打開它九孩,創(chuàng)建基本的組件類,但這次我們擴展了Fly類发框。

import 'package:flame/sprite.dart';
import 'package:hello_flame/components/fly.dart';
import 'package:hello_flame/hit-game.dart';

class MosquitoFly extends Fly {
  MosquitoFly(HitGame game, double x, double y) : super(game, x, y) {
    flyingSprite = List<Sprite>();
    flyingSprite.add(Sprite('flies/mosquito-fly-1.png'));
    flyingSprite.add(Sprite('flies/mosquito-fly-2.png'));
    deadSprite = Sprite('flies/mosquito-fly-dead.png');
  }
}

上面的代碼中躺彬,先導(dǎo)入該類所依賴的包和類,然后聲明一個名為MosquitoFly的類梅惯,并使其擴展Fly類宪拥,從而有效地創(chuàng)建一個Fly子類,其可以訪問和覆蓋Fly類的變量和方法铣减。

構(gòu)造函數(shù)中調(diào)用super她君,這樣在構(gòu)造函數(shù)執(zhí)行代碼當(dāng)前類代碼之前,就會先運行父類的構(gòu)造函數(shù)葫哗。構(gòu)造函數(shù)只映射父類構(gòu)造函數(shù)所需的參數(shù)缔刹,并在調(diào)用super期間轉(zhuǎn)發(fā)它們。

在構(gòu)造函數(shù)中劣针,通過創(chuàng)建一個精靈(Sprite)列表的新實例來初始化此子類從Fly類繼承的flyingSprite變量校镐,然后我們在這個列表中添加兩個精靈(Sprite),它們對應(yīng)于飛行動畫的2個幀捺典。

然后我們將“正衬窭”蚊子的掉落圖加載到精靈(Sprite)中并將其分配給deadSprite

我們現(xiàn)在不會覆蓋更新(update)和渲染(render)方法襟己,因為目前沒有針對這類蚊子的特定內(nèi)容引谜,所有蚊子都相同。

生產(chǎn)正常蚊子

現(xiàn)在回到hit-game.dart文件中擎浴,編輯produceFly方法以一個MosquitoFly而不是父類Fly员咽。在文件頂部導(dǎo)入剛剛創(chuàng)建的子類,然后替換之前生成Fly的代碼贮预。

...
import 'package:hello_flame/components/mosquito-fly.dart';

class HitGame extends Game {
  ...

  void produceFly() {
    double x = rnd.nextDouble() * (screenSize.width - tileSize);
    double y = rnd.nextDouble() * (screenSize.height - tileSize);
    // 刪除內(nèi)容
    // enemy.add(Fly(this, x, y));
    enemy.add(MosquitoFly(this, x, y));
  }

現(xiàn)在運行游戲贝室,應(yīng)該可以看到下面圖片所示的效果。

換上背景和素材后.PNG
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末萌狂,一起剝皮案震驚了整個濱河市档玻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌茫藏,老刑警劉巖误趴,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異务傲,居然都是意外死亡凉当,警方通過查閱死者的電腦和手機枣申,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來看杭,“玉大人忠藤,你說我怎么就攤上這事÷ケⅲ” “怎么了模孩?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長贮缅。 經(jīng)常有香客問我榨咐,道長,這世上最難降的妖魔是什么谴供? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任块茁,我火速辦了婚禮,結(jié)果婚禮上桂肌,老公的妹妹穿的比我還像新娘数焊。我一直安慰自己,他們只是感情好崎场,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布佩耳。 她就那樣靜靜地躺著,像睡著了一般照雁。 火紅的嫁衣襯著肌膚如雪蚕愤。 梳的紋絲不亂的頭發(fā)上答恶,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天饺蚊,我揣著相機與錄音,去河邊找鬼悬嗓。 笑死腰素,一個胖子當(dāng)著我的面吹牛末早,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼着降,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了箍鼓?” 一聲冷哼從身側(cè)響起樊销,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎声诸,沒想到半個月后酱讶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡彼乌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年泻肯,在試婚紗的時候發(fā)現(xiàn)自己被綠了渊迁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡灶挟,死狀恐怖琉朽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情稚铣,我是刑警寧澤箱叁,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站惕医,受9級特大地震影響蝌蹂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜曹锨,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一孤个、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沛简,春花似錦齐鲤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至捧灰,卻和暖如春淆九,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背毛俏。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工炭庙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人煌寇。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓焕蹄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親阀溶。 傳聞我的和親對象是個殘疾皇子腻脏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355