Flutterアプリケーション開発概論

通行料の仕組みを理解しよう

15人生ゲームを作る。サイコロ処理・イベント分岐・プレイヤー管理・データ設計・アニメーション
FlutteriOSAndroidMacOSWindows基礎から学ぶ開発アプリ開発

忙しい方はここだけ見て

この章で見る大事な場所は、ここです。

if (owner.ownerPlayerId == _currentPlayer.id) {
  await _showLifeEventModal(
    title: tile.label,
    categoryLabel: 'OWN PROPERTY',
    description: 'ここは自分が所有している物件です。通行料は発生しません。',
    icon: Icons.home_work,
    accentColor: AppColors.green,
  );

  _addLog('${_currentPlayer.name} は自分の物件 ${tile.label} に止まりました。');
  _moveToNextPlayer();
  return;
}

await _payRent(tile, owner);

意味はこうです。

物件マスに止まる
↓
所有者を確認する
↓
自分の物件なら通行料なし
↓
他の人の物件なら通行料を支払う

通行料を支払う処理は、ここです。

Future<void> _payRent(BoardTile tile, PropertyOwner owner) async {
  final PlayerState payer = _currentPlayer;
  final int ownerIndex = _players.indexWhere(
    (PlayerState player) => player.id == owner.ownerPlayerId,
  );

  final PlayerState receiver = _players[ownerIndex];

  _players[_currentPlayerIndex] = payer.copyWith(
    cash: payer.cash - tile.rent,
  );

  _players[ownerIndex] = receiver.copyWith(
    cash: receiver.cash + tile.rent,
  );

  setState(() {});
}

つまり、こうです。

止まった人のお金を減らす
↓
所有者のお金を増やす

この章でやること

この章では、他のプレイヤーが所有している物件に止まったときの「通行料」の仕組みを見ていきます。

人生ゲームでは、物件を買うだけではなく、他の人がその物件に止まることで収入が入ります。

物件を買う
↓
他の人が止まる
↓
通行料を受け取る

この仕組みが入ると、ゲームに「資産を持つ意味」が出てきます。


今日のゴール

この章のゴールは、次の3つです。

1. 自分の物件なら通行料が発生しないと分かる
2. 他人の物件なら通行料を支払うと分かる
3. 支払う人のcashが減り、所有者のcashが増えると分かる

難しい計算はありません。

基本は、お金を移動しているだけです。


Step 1:物件には通行料がある

物件マスには、rent があります。

BoardTile(
  index: 2,
  age: 23,
  stage: '社会人スタート期',
  label: '小さなカフェ',
  description: '小さなカフェに投資できます。所有すると、他のプレイヤーが止まった時に通行料を得られます。',
  type: TileType.property,
  price: 300,
  rent: 80,
),

大事なのはここです。

rent: 80,

これは、

他のプレイヤーが止まったら、80を支払う

という意味です。


Step 2:物件マスに止まると所有者を確認する

物件マスに止まると、まず所有者を確認します。

final PropertyOwner? owner = _findOwner(tile.index);

これは、

この物件は誰かが買っているか?

を調べています。

まだ誰も買っていない場合は、購入できるかどうかの処理に進みます。

すでに誰かが買っている場合は、通行料の処理に進みます。


Step 3:自分の物件なら通行料なし

所有者がいる場合でも、それが自分の物件なら通行料は発生しません。

if (owner.ownerPlayerId == _currentPlayer.id) {
  await _showLifeEventModal(
    title: tile.label,
    categoryLabel: 'OWN PROPERTY',
    description: 'ここは自分が所有している物件です。通行料は発生しません。',
    icon: Icons.home_work,
    accentColor: AppColors.green,
  );

  _addLog('${_currentPlayer.name} は自分の物件 ${tile.label} に止まりました。');
  _moveToNextPlayer();
  return;
}

意味はこうです。

物件の所有者ID
=
今のプレイヤーID
↓
自分の物件
↓
通行料なし

Step 4:他人の物件なら通行料を払う

自分の物件ではない場合は、この処理に進みます。

await _payRent(tile, owner);

これは、

通行料を支払う処理に進む

という意味です。

_payRent() の中で、お金の移動が行われます。


Step 5:_payRent()を探す

lib/main.dart を開いて、次の文字を検索します。

_payRent

このような関数があります。

Future<void> _payRent(BoardTile tile, PropertyOwner owner) async {
  final PlayerState payer = _currentPlayer;
  final int ownerIndex = _players.indexWhere(
    (PlayerState player) => player.id == owner.ownerPlayerId,
  );

  if (ownerIndex == -1) {
    _moveToNextPlayer();
    return;
  }

  final PlayerState receiver = _players[ownerIndex];

  await _showLifeEventModal(
    title: tile.label,
    categoryLabel: 'RENT PAYMENT',
    subtitle: '${tile.age}歳|${tile.stage}',
    description:
        '${receiver.name} が所有している物件です。${payer.name} は通行料 ${tile.rent} を支払います。',
    icon: Icons.currency_yen,
    accentColor: AppColors.orange,
    cashChange: -tile.rent,
  );

  _players[_currentPlayerIndex] = payer.copyWith(
    cash: payer.cash - tile.rent,
  );

  _players[ownerIndex] = receiver.copyWith(
    cash: receiver.cash + tile.rent,
  );

  _addLog('${payer.name} は ${receiver.name} に通行料 ${tile.rent} を支払いました。');

  setState(() {});
  _moveToNextPlayer();
}

少し長いですが、中心はとてもシンプルです。

支払う人を決める
↓
受け取る人を決める
↓
支払う人のお金を減らす
↓
受け取る人のお金を増やす

Step 6:支払う人を決める

最初に、支払う人を決めています。

final PlayerState payer = _currentPlayer;

payer は、通行料を支払う人です。

今のターンのプレイヤーが、他の人の物件に止まったので、その人が支払います。

payer = 今止まったプレイヤー

Step 7:受け取る人を探す

次に、所有者を探しています。

final int ownerIndex = _players.indexWhere(
  (PlayerState player) => player.id == owner.ownerPlayerId,
);

これは、

プレイヤー一覧の中から
物件の所有者IDと同じ人を探す

という意味です。

見つかった人が、通行料を受け取る人です。


Step 8:receiverとは?

所有者が見つかったら、次のコードで受け取る人を取り出します。

final PlayerState receiver = _players[ownerIndex];

receiver は、通行料を受け取る人です。

payer    = 支払う人
receiver = 受け取る人

この2つが分かれば、通行料処理はかなり理解しやすくなります。


Step 9:通行料の画面を表示する

お金を動かす前に、通行料の画面を表示します。

await _showLifeEventModal(
  title: tile.label,
  categoryLabel: 'RENT PAYMENT',
  subtitle: '${tile.age}歳|${tile.stage}',
  description:
      '${receiver.name} が所有している物件です。${payer.name} は通行料 ${tile.rent} を支払います。',
  icon: Icons.currency_yen,
  accentColor: AppColors.orange,
  cashChange: -tile.rent,
);

ここでは、画面にこう表示しています。

誰の物件か
誰が支払うか
いくら支払うか

cashChange: -tile.rent は、支払う人目線で「お金が減る」という意味です。


Step 10:支払う人のお金を減らす

実際にお金を減らしているのは、この部分です。

_players[_currentPlayerIndex] = payer.copyWith(
  cash: payer.cash - tile.rent,
);

例えば、支払う人の所持金が1500で、通行料が80なら、

1500 - 80 = 1420

になります。

つまり、支払う人の現金が80減ります。


Step 11:受け取る人のお金を増やす

次に、所有者のお金を増やします。

_players[ownerIndex] = receiver.copyWith(
  cash: receiver.cash + tile.rent,
);

例えば、受け取る人の所持金が1200で、通行料が80なら、

1200 + 80 = 1280

になります。

つまり、所有者の現金が80増えます。


Step 12:通行料は「お金の移動」

通行料は、ただお金が消えるのではありません。

支払う人から、所有者へ移動しています。

支払う人:-80
所有者:+80

全体で見ると、

プレイヤー間でお金が移動している

ということです。

この仕組みがあると、物件を買う意味が出てきます。


Step 13:ログを追加する

通行料を支払ったら、ログを追加します。

_addLog('${payer.name} は ${receiver.name} に通行料 ${tile.rent} を支払いました。');

例えば、こう表示されます。

Player 2 は Player 1 に通行料 80 を支払いました。

ログがあると、あとから何が起きたか確認しやすくなります。


Step 14:画面を更新して次のプレイヤーへ

お金を移動したあと、画面を更新します。

setState(() {});

そして、次のプレイヤーへ進みます。

_moveToNextPlayer();

流れはこうです。

通行料を支払う
↓
画面を更新する
↓
次のプレイヤーへ

Step 15:ownerIndex == -1 とは?

途中に、この処理があります。

if (ownerIndex == -1) {
  _moveToNextPlayer();
  return;
}

これは、念のための安全確認です。

indexWhere() は、見つからなかった場合に -1 を返します。

ownerIndex == -1
↓
所有者が見つからなかった
↓
安全のため次のプレイヤーへ

通常はあまり起きませんが、アプリが止まらないようにするための保険です。


Step 16:通行料を変えるには?

通行料は、物件マスの rent を変えます。

変更前です。

rent: 80,

高くしたい場合です。

rent: 150,

安くしたい場合です。

rent: 50,

_payRent() の中を直接変えるより、まずは _createTiles()rent を変えるのがおすすめです。


触ってみよう

今回は、小さなカフェの通行料を変えてみましょう。

_createTiles() の中から、この物件を探します。

BoardTile(
  index: 2,
  age: 23,
  stage: '社会人スタート期',
  label: '小さなカフェ',
  description: '小さなカフェに投資できます。所有すると、他のプレイヤーが止まった時に通行料を得られます。',
  type: TileType.property,
  price: 300,
  rent: 80,
),

通行料を 80 から 120 にします。

rent: 120,

保存して、他のプレイヤーがその物件に止まったときに、120支払われれば成功です。


もう1つ触ってみよう

通行料を高くすると、ゲームの逆転が起きやすくなります。

例えば、こうします。

price: 300,
rent: 200,

この場合、安く買えて高い通行料が入るので、かなり強い物件になります。

バランスを取りたい場合は、価格も上げると自然です。

price: 600,
rent: 200,

よくあるエラーと直し方

1. rentを文字にしてしまった

悪い例です。

rent: '120',

正しくはこちらです。

rent: 120,

数字は ' で囲みません。


2. 自分の物件でも通行料が発生してしまう

自分の物件では通行料を払わないように、この処理があります。

if (owner.ownerPlayerId == _currentPlayer.id) {
  // 自分の物件
}

これを消すと、自分の物件に止まっても通行料処理に進む可能性があります。

初心者のうちは消さないようにしましょう。


3. payerとreceiverを逆にしてしまう

支払う人と受け取る人を逆にすると、お金の流れが逆になります。

正しい考え方です。

payer    = 止まった人
receiver = 物件の所有者

お金の動きはこうです。

cash: payer.cash - tile.rent
cash: receiver.cash + tile.rent

4. setStateを消してしまった

通行料を支払ったあとには、画面更新が必要です。

setState(() {});

これを消すと、データは変わっていても画面の表示がすぐ変わらないことがあります。


5. _moveToNextPlayer()を消してしまった

最後には、次のプレイヤーへ進みます。

_moveToNextPlayer();

これを消すと、ターンが進まなくなることがあります。


この章で覚えること

この章で覚えることは、3つだけです。

1. rent は通行料
2. 他人の物件に止まると通行料を支払う
3. 支払う人のcashが減り、所有者のcashが増える

まずはこれだけで大丈夫です。


やる気を維持するコツ

通行料が入ると、ゲームに「戦略」が生まれます。

ただサイコロを振るだけではなく、

どの物件を買うか
どの物件の通行料を高くするか
安い物件を増やすか
高い物件を少なくするか

を考えられるようになります。

これは、ゲーム制作でとても大事な「バランス調整」です。

最初は難しく考えず、rent の数字を少し変えて遊んでみましょう。


チェックリスト

□ rent が通行料だと分かった
□ _handlePropertyTile() で所有者を確認していると分かった
□ 自分の物件なら通行料なしだと分かった
□ 他人の物件なら _payRent() に進むと分かった
□ payer が支払う人だと分かった
□ receiver が受け取る人だと分かった
□ payer.cash - tile.rent の意味が分かった
□ receiver.cash + tile.rent の意味が分かった
□ rent を変更して動作確認した

まとめ

この章では、通行料の仕組みを確認しました。

物件の通行料は rent で決まります。

自分の物件に止まった場合は、通行料は発生しません。

他のプレイヤーの物件に止まった場合は、止まった人の cash が減り、所有者の cash が増えます。

次の章では、イベントモーダルを作って、給料・税金・イベント・通行料などを画面に分かりやすく表示する仕組みを見ていきます。

教材トップへ戻る