サイコロ処理を理解しよう
忙しい方はここだけ見て
この章で見る場所は、ここです。
Future<void> _rollDiceAndMove() async {
if (_phase != GamePhase.waitingRoll) {
return;
}
final PlayerState player = _currentPlayer;
_startDiceShuffle();
setState(() {
_phase = GamePhase.rolling;
});
await Future<void>.delayed(const Duration(milliseconds: 750));
_stopDiceShuffle();
final int dice = _random.nextInt(6) + 1;
final int nextPosition = min(player.position + dice, _tiles.length - 1);
}
サイコロの数字を作っているのは、この1行です。
final int dice = _random.nextInt(6) + 1;
意味はこれです。
0〜5の数字をランダムで作る
↓
+1する
↓
1〜6のサイコロになる
つまり、
_random.nextInt(6) + 1
が、サイコロ処理の中心です。
この章でやること
この章では、サイコロを振る処理を見ていきます。
人生ゲームでは、サイコロを振るとプレイヤーが進みます。
このアプリでも、流れは同じです。
サイコロを振る
↓
1〜6の数字が出る
↓
その数だけ進む
↓
止まったマスのイベントが起きる
まずは、この流れが分かればOKです。
今日のゴール
この章のゴールは、次の3つです。
1. サイコロの数字がどこで作られているか分かる
2. 1〜6の数字をランダムで出す仕組みが分かる
3. サイコロ後にプレイヤーが進む流れが分かる
難しいところは、あとからで大丈夫です。
まずは「ランダムで数字を出している」と分かればOKです。
Step 1:_rollDiceAndMove()を探す
lib/main.dart を開きます。
検索で、次の文字を探してください。
_rollDiceAndMove
この関数が、サイコロを振ってプレイヤーを進める処理です。
Future<void> _rollDiceAndMove() async {
if (_phase != GamePhase.waitingRoll) {
return;
}
final PlayerState player = _currentPlayer;
if (player.isFinished) {
_moveToNextPlayer();
return;
}
_startDiceShuffle();
setState(() {
_phase = GamePhase.rolling;
});
await Future<void>.delayed(const Duration(milliseconds: 750));
_stopDiceShuffle();
final int dice = _random.nextInt(6) + 1;
final int nextPosition = min(player.position + dice, _tiles.length - 1);
}
長く見えますが、やっていることはシンプルです。
今サイコロを振ってよいか確認
↓
サイコロ演出を開始
↓
少し待つ
↓
1〜6の数字を作る
↓
プレイヤーの次の位置を決める
Step 2:サイコロを押せる状態か確認する
最初に、この処理があります。
if (_phase != GamePhase.waitingRoll) {
return;
}
これは、
今が「サイコロ待ち」の状態でなければ、何もしない
という意味です。
例えば、イベント画面が出ている最中に、何度もサイコロを押せたら困ります。
そのため、今のゲーム状態を確認しています。
Step 3:GamePhaseを思い出そう
ゲームの状態は、GamePhase で管理しています。
enum GamePhase {
waitingRoll,
rolling,
showingModal,
choosingProperty,
gameOver,
}
意味はこちらです。
| 状態 | 意味 |
|---|---|
waitingRoll | サイコロを振ってよい |
rolling | サイコロ中 |
showingModal | イベント画面を表示中 |
choosingProperty | 物件購入を選択中 |
gameOver | ゲーム終了 |
サイコロを振れるのは、waitingRoll のときだけです。
Step 4:現在のプレイヤーを取り出す
次に、このコードがあります。
final PlayerState player = _currentPlayer;
これは、今のターンのプレイヤーを取り出しています。
例えば、今が Player 1 のターンなら、
player = Player 1
になります。
今が Player 2 のターンなら、
player = Player 2
になります。
Step 5:すでにゴールしているか確認する
次に、この処理があります。
if (player.isFinished) {
_moveToNextPlayer();
return;
}
これは、
もし今のプレイヤーがゴール済みなら
次のプレイヤーに交代する
という意味です。
ゴールした人が、またサイコロを振らないようにしています。
Step 6:サイコロ演出を始める
次に、サイコロの数字が動く演出を始めます。
_startDiceShuffle();
この中では、短い間隔でサイコロの見た目の数字を変えています。
void _startDiceShuffle() {
_diceShuffleTimer?.cancel();
_diceShuffleTimer = Timer.periodic(
const Duration(milliseconds: 80),
(_) {
if (!mounted) {
return;
}
setState(() {
_visibleDice = _random.nextInt(6) + 1;
});
},
);
}
ここでは、80ミリ秒ごとに数字を変えています。
1
4
2
6
3
...
のように、サイコロが回っているように見せています。
Step 7:ゲーム状態をrollingにする
次に、ゲーム状態を変更します。
setState(() {
_phase = GamePhase.rolling;
});
これは、
今はサイコロ中です
という状態に変えています。
setState() は、画面を更新するための命令です。
状態が変わったら、画面も変わります。
Step 8:少し待つ
次に、このコードがあります。
await Future<void>.delayed(const Duration(milliseconds: 750));
これは、750ミリ秒だけ待つ処理です。
750ミリ秒 = 0.75秒
サイコロを押してすぐ結果が出るより、少し待った方がゲームっぽく見えます。
この待ち時間が、サイコロ演出の時間です。
Step 9:サイコロ演出を止める
待ち時間が終わったら、演出を止めます。
_stopDiceShuffle();
中身はこうです。
void _stopDiceShuffle() {
_diceShuffleTimer?.cancel();
_diceShuffleTimer = null;
}
これは、数字をシャッフルするタイマーを止めています。
止めないと、ずっと数字が変わり続けてしまいます。
Step 10:サイコロの数字を決める
ここが一番大事です。
final int dice = _random.nextInt(6) + 1;
この1行で、1〜6の数字を作っています。
分解すると、こうです。
_random.nextInt(6)
これは、0〜5の数字をランダムで作ります。
0, 1, 2, 3, 4, 5
でも、サイコロは1〜6です。
そこで、+ 1 します。
_random.nextInt(6) + 1
すると、こうなります。
0 + 1 = 1
1 + 1 = 2
2 + 1 = 3
3 + 1 = 4
4 + 1 = 5
5 + 1 = 6
これで、サイコロになります。
Step 11:プレイヤーの次の位置を決める
サイコロの数字が決まったら、次の位置を計算します。
final int nextPosition = min(player.position + dice, _tiles.length - 1);
意味はこうです。
今いる位置 + サイコロの数
例えば、今の位置が2で、サイコロが4なら、
2 + 4 = 6
6番目のマスに進みます。
Step 12:min()でゴールを超えないようにする
この部分も大事です。
min(player.position + dice, _tiles.length - 1)
これは、ゴールを超えないようにするための処理です。
例えば、最後のマスが19番だとします。
現在地が17で、サイコロが6の場合、
17 + 6 = 23
になってしまいます。
でも、23番のマスはありません。
そこで、min() を使って、
23 と 19 の小さい方を使う
↓
19になる
という処理をしています。
つまり、ゴールを超えそうなときは、ゴールに止まります。
Step 13:実際にプレイヤーの位置を変える
次のコードで、プレイヤーの位置を更新しています。
_players[_currentPlayerIndex] = player.copyWith(position: nextPosition);
これは、
今のプレイヤーの位置を
nextPositionに変更する
という意味です。
copyWith() は、プレイヤー情報の一部だけを変えるための関数です。
ここでは、position だけ変えています。
Step 14:画面に表示するサイコロも更新する
次のコードもあります。
_lastDice = dice;
_visibleDice = dice;
意味はこちらです。
| 変数 | 役割 |
|---|---|
_lastDice | 最後に出たサイコロの目 |
_visibleDice | 画面に表示するサイコロの目 |
サイコロ演出中は、_visibleDice がどんどん変わります。
最後に、確定した dice を入れます。
Step 15:ログを追加する
サイコロを振った結果は、ログにも追加されます。
_addLog(
'${player.name} が $dice を出しました。${landedTile.age}歳「${landedTile.label}」に進みました。',
);
例えば、こう表示されます。
Player 1 が 4 を出しました。25歳「初任給」に進みました。
ログがあると、何が起きたか分かりやすくなります。
Step 16:止まったマスの処理へ進む
最後に、止まったマスの効果を実行します。
await _handleTileEffect(landedTile);
これは、
止まったマスで何が起きるかを処理する
という意味です。
例えば、
給料マスならお金が増える
税金マスならお金が減る
イベントマスならランダムイベント
物件マスなら購入確認
ゴールなら終了
という流れになります。
サイコロ処理の全体像
ここまでをまとめると、サイコロ処理はこうです。
サイコロボタンを押す
↓
今サイコロを振れる状態か確認
↓
サイコロ演出を開始
↓
0.75秒待つ
↓
1〜6の数字を決める
↓
次の位置を計算する
↓
プレイヤーの位置を更新する
↓
止まったマスの処理をする
これが、人生ゲームの基本の動きです。
触ってみよう
今回は、サイコロ演出の時間を少し変えてみます。
変更前です。
await Future<void>.delayed(const Duration(milliseconds: 750));
少し短くしたい場合です。
await Future<void>.delayed(const Duration(milliseconds: 500));
少し長くしたい場合です。
await Future<void>.delayed(const Duration(milliseconds: 1000));
まずは、1000 にしてみましょう。
サイコロを押す
↓
少し長く回る
↓
結果が出る
ゲームっぽさが変わるのを確認できます。
もう1つ触ってみよう
サイコロの数字が切り替わる速さも変えられます。
変更前です。
const Duration(milliseconds: 80)
ゆっくりにするなら、こうです。
const Duration(milliseconds: 150)
速くするなら、こうです。
const Duration(milliseconds: 50)
最初は、150 にして試してみましょう。
よくあるエラーと直し方
1. + 1 を消してしまった
悪い例です。
final int dice = _random.nextInt(6);
これだと、0が出ることがあります。
0, 1, 2, 3, 4, 5
サイコロとして使うなら、正しくはこちらです。
final int dice = _random.nextInt(6) + 1;
2. nextInt(6) の数字を変えてしまった
例えば、こうすると1〜10のサイコロになります。
final int dice = _random.nextInt(10) + 1;
これは悪くありません。
ただし、ゲームの進み方がかなり速くなります。
初心者のうちは、まず 6 のままでOKです。
3. min()を消してしまった
悪い例です。
final int nextPosition = player.position + dice;
これだと、ゴールを超えて存在しないマスに進む可能性があります。
安全な書き方はこちらです。
final int nextPosition = min(player.position + dice, _tiles.length - 1);
ゴールを超えないために必要です。
4. setState()を消してしまった
setState() を消すと、データは変わっていても画面が更新されないことがあります。
setState(() {});
これは、
画面を更新してください
という合図です。
初心者のうちは、むやみに消さないようにしましょう。
5. awaitを消してしまった
悪い例です。
Future<void>.delayed(const Duration(milliseconds: 750));
これだと、待たずに次へ進んでしまいます。
正しくはこちらです。
await Future<void>.delayed(const Duration(milliseconds: 750));
await は、
ここで少し待ってから次に進む
という意味です。
この章で覚えること
この章で覚えることは、3つだけです。
1. サイコロは _random.nextInt(6) + 1 で作る
2. player.position + dice で次の位置を決める
3. min() でゴールを超えないようにする
まずはこれだけで大丈夫です。
やる気を維持するコツ
サイコロ処理が分かると、ゲーム開発の楽しさが一気に見えてきます。
なぜなら、ランダムが入ると、毎回違う展開になるからです。
同じボタンを押しても
毎回違う数字が出る
↓
止まるマスが変わる
↓
イベントも変わる
↓
ゲームになる
ランダムは、ゲームを面白くする強力な仕組みです。
まずは、サイコロの速さや待ち時間を少し変えて遊んでみましょう。
チェックリスト
□ _rollDiceAndMove() を見つけた
□ サイコロを作る1行を見つけた
□ _random.nextInt(6) + 1 の意味が分かった
□ 0〜5に+1して1〜6にしていると分かった
□ nextPosition の意味が分かった
□ min() がゴール超え防止だと分かった
□ _visibleDice が画面表示用だと分かった
□ サイコロ演出の待ち時間を変えてみた
□ 保存して動作確認した
まとめ
この章では、サイコロ処理を確認しました。
サイコロの数字は、_random.nextInt(6) + 1 で作っています。
プレイヤーの次の位置は、player.position + dice で決まります。
ただし、ゴールを超えないように min() を使っています。
次の章では、プレイヤーが進む仕組みを見ながら、現在地の更新や画面スクロールの流れを理解していきます。
