マガジン

30分で作る天気の箱庭:ボタンで雨と晴れを切り替える

遊びをつくる

執筆にClaudeを使用しています。

一つのボールと短い足場だけで作られた小さなゲーム模型。
記事のテーマをもとに制作したAI生成イメージ。実際のゲーム画面ではありません。

得点も敵もゴールもない画面を作るとき、手元にあるのは「いまの状態」と「それをどう描くか」だけです。ボタンを押すと空の色が変わり、太陽が薄れて雲が現れ、雨が降り始めます。たったそれだけの画面で、ゲームを作る入口で必要な考え方がひととおり確かめられます。 この記事の問いは、「目標のない画面でも、状態の切り替えだけで、触っていたくなる手触りは作れるのか」です。答えは作った人の感覚によって変わるので、結論は書きません。代わりに、30分で動くものを作る手順と、自分の目で確かめるための観察の方法を示します。 必要なのはテキストエディタとブラウザだけです。ファイルは1つ、外部ライブラリも通信も使いません。

この記事の目次
  1. 作るものと、先に決めておく3つのこと
  2. 手順:コピーして動かすまで(目安10分)
  3. コードの読みどころ:状態から見た目を決める(目安8分)
  4. 自分で確かめる:切り替えの「間」は必要か(目安7分)
  5. つまずきやすい点と、次の30分の使い方
  6. 次の行動

作るものと、先に決めておく3つのこと

作るのは、480×320の画面に空と地面があり、下に「晴れ」「雨」の2つのボタンが並んだ箱庭です。目標はなく、押すと天気が変わるだけです。

実装の前に、次の3つを決めておきます。ここを曖昧にしたまま書き始めると、後で描画の処理が散らかります。

  1. 状態は1つだけ持ちます。 変数 weather に "sun" か "rain" を入れます。ボタンが行うのは、この変数を書き換えることだけです。
  2. 描画は状態から毎フレーム作り直します。 「雨になったら雲を出す」と変化のたびに差分を足すのではなく、「いまの状態ならこう見える」を毎回すべて描きます。状態と見た目がずれにくくなります。
  3. 切り替えの途中経過を表す数値を別に持ちます。 変数 mix を、0なら完全に晴れ、1なら完全に雨とします。これは3つ目の工夫のためで、あとで効果を比べます。

つまり、ボタンは weather を変え、mix が weather に向かって少しずつ近づき、描画が mix を見て色や透明度を決める、という3段の流れです。

手順:コピーして動かすまで(目安10分)

  1. 新しいフォルダを作り、その中に index.html という名前でファイルを作ります。アソバースへ投稿するときも、入口は直下の index.html になるので、最初から同じ名前にしておくと後が楽です。
  2. 次のコードをそのまま貼り付けて保存します。
html
<!DOCTYPE html>
<html lang="ja"><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>天気の箱庭</title>
<style>
body{margin:0;background:#222;color:#fff;text-align:center}
canvas{width:min(96vw,480px);display:block;margin:8px auto}
button{font-size:18px;padding:10px 20px}
</style>
<canvas id="c" width="480" height="320"></canvas>
<button id="sun">晴れ</button> <button id="rain">雨</button>
<script>
const g=document.getElementById("c").getContext("2d");
const calm=matchMedia("(prefers-reduced-motion:reduce)").matches;
let weather="sun",mix=0; // mix: 0=晴れ 1=雨
const drops=Array.from({length:80},()=>({x:Math.random()*480,y:Math.random()*320,v:4+Math.random()*3}));
document.getElementById("sun").onclick=()=>{weather="sun";};
document.getElementById("rain").onclick=()=>{weather="rain";};
const lerp=(a,b,t)=>a+(b-a)*t;
function draw(){
  mix+=((weather==="rain"?1:0)-mix)*(calm?1:0.04);
  g.fillStyle=`rgb(${lerp(130,70,mix)|0},${lerp(200,85,mix)|0},${lerp(250,110,mix)|0})`;
  g.fillRect(0,0,480,320);
  g.globalAlpha=1-mix; // 太陽
  g.fillStyle="#ffd54a";g.beginPath();g.arc(380,70,36,0,7);g.fill();
  g.globalAlpha=mix; // 雲(1つの経路にまとめて重なりを均一にする)
  g.fillStyle="#555b66";g.beginPath();
  for(const x of[120,200,280]){g.moveTo(x+40,70);g.arc(x,70,40,0,7);}
  g.fill();
  g.globalAlpha=1;
  g.fillStyle=`rgb(${lerp(108,63,mix)|0},${lerp(191,125,mix)|0},${lerp(90,74,mix)|0})`;
  g.fillRect(0,240,480,80);
  g.globalAlpha=mix; // 雨粒
  g.strokeStyle="#cfe8ff";
  for(const d of drops){
    g.beginPath();g.moveTo(d.x,d.y);g.lineTo(d.x-2,d.y+12);g.stroke();
    if(mix>0.01&&!calm){d.y+=d.v;if(d.y>320){d.y=-12;d.x=Math.random()*480;}}
  }
  g.globalAlpha=1;
  requestAnimationFrame(draw);
}
draw();
</script>
  1. index.html をブラウザで開きます。ダブルクリックで開けば動きます。
  2. 「雨」を押します。空が暗くなり、太陽が薄れて雲が現れ、雨粒が落ち始めれば成功です。「晴れ」を押すと、逆の順で戻ります。
  3. 何も表示されないときは、開発者ツールのコンソールにエラーが出ていないかを見ます。多くは貼り付けのときに文字が欠けた場合です。

コードの読みどころ:状態から見た目を決める(目安8分)

動いたら、コードの流れを3か所だけ確かめます。

ボタンは状態を書き換えるだけ

ボタンの処理は weather="sun" と weather="rain" だけです。ここに「雲を出す」「雨粒を動かす」などを書かないことが大切です。後から天気を増やしても、ボタン側のコードは「状態の名前を変える」だけで済みます。

mixが状態に追いつく

mix+=(目標-mix)*0.04 は、目標との差の4%ずつ近づく書き方です。目標が1なら、最初は大きく動き、近づくほど動きが小さくなります。切り替えた直後に素早く変わり、終わり際に落ち着く動きになります。

calm が真のときは係数が1になり、一瞬で切り替わります。これは、OSの「視差効果を減らす」「アニメーションを減らす」といった設定に応える部分です。アソバースの公式作品も「動きを抑える」設定に対応しているので、自作でも同じ配慮を最初から入れておくと、後から直す手間が省けます。

描画はmixだけを見る

太陽の透明度は 1-mix、雲と雨粒の透明度は mix です。空の色と地面の色は、晴れの色と雨の色のあいだを lerp(2つの値を比率で混ぜる関数)で行き来します。描画の中に「いま雨かどうか」を判定する if は雨粒を動かす1か所しかなく、見た目のほとんどは数値1つで決まります。

雨粒の移動条件が weather ではなく mix>0.01 なのは、晴れに切り替えた後もしばらく粒を落とし続けて、フェードアウトの間に雨が止まって見えないようにするためです。

自分で確かめる:切り替えの「間」は必要か(目安7分)

ここからは観察です。結果はこの記事には書きません。自分の画面で確かめて、記録してください。

問い: 即座に切り替わる箱庭と、少しずつ切り替わる箱庭では、触り続けたくなる度合いは変わるでしょうか。

準備するもの: 上のコード、メモ用紙(または表計算)、タイマー。

手順:

  1. 係数 0.04 を、1 に書き換えた版(即時)、0.04 の版、0.01 の版の3つを用意します。ファイル名を変えて保存すると比べやすくなります。
  2. それぞれの版で、「晴れ」「雨」を交互に押し続けます。1版につき60秒を目安にし、押した回数を数えます。
  3. 60秒のあいだ、自分の手が止まった回数と、「もう一度押したくなった」瞬間をメモします。細かい感想で構いません。
  4. 版の順番を変えて、もう一度繰り返します。順番による慣れの影響を減らすためです。可能なら、別の人にも同じ手順で試してもらいます。人に頼む場合は、画面を見ていてほしいことだけを伝え、感想を誘導する言葉は添えません。

記録の取り方: 版ごとに「押した回数」「手が止まった回数」「気づいたこと」の3列を、2回分ずつ記入します。

結果の読み方:

  • 即時の版で押した回数が最も多ければ、変化の確認を優先して素早く試したくなる画面だと読めます。ただし、それは「触っていたくなる」のとは別の可能性があります。
  • 中間の版で手が止まる回数が少なく、気づいたことに「変わるのを待った」といった記述が出るなら、変化の過程そのものが見どころになっている可能性があります。
  • 一番遅い版で押す間隔が空きすぎるなら、待ち時間が長いと感じている可能性があります。切り替わり中にボタンを押し直したとき、どう見えるかも合わせて確認してください。

起こりやすい誤り: 1回だけ比べて判断すること、自分の好みと「遊ぶ人の好み」を混同すること、画面の大きさが版ごとに違ってしまうことなどです。判断は、複数回の記録を並べてから行ってください。

つまずきやすい点と、次の30分の使い方

動かないときや、思った見た目にならないときは、次の点を確認します。

  • 雲の重なりが濃く見える。 円ごとに fill を呼ぶと、半透明のとき重なった部分が濃くなります。上のコードのように1つの経路にまとめてから塗ります。
  • ボタンを連打すると動きがおかしい。 requestAnimationFrame を、ボタンの処理から呼んでいないか確認します。draw を呼ぶのは最後の1か所だけです。呼び出しが増えると、毎フレームの更新も増えます。
  • 見た目だけ変えて状態を忘れる。 色を直接書き換えると、あとで天気の種類を増やしたときに整合が取れなくなります。変えるのは必ず weather と mix です。

次の30分で足せる拡張を、手間の小さい順に挙げます。

  1. 天気を「曇り」まで増やす。mix を3段階の目標値(0、0.5、1)に変えるだけで試せます。
  2. 雷を足す。雨のあいだ、一定間隔で画面を白く光らせる処理を入れます。タイマーの変数を別に持つのがコツです。
  3. 昼夜を足す。weather とは別に time を持つと、状態の組み合わせで見た目が決まる構造になります。

作ったものをアソバースに投稿する場合は、index.html を直下に置いたHTMLまたはZIPをアップロードします。この箱庭は外部通信もサーバー側のコードも使わないので、制限に収まる形です。投稿は入力、表示の確認、公開の3段階で、下書き保存もできます。公開にはメールアドレスの確認が必要です。天気の状態を次回に引き継ぎたい場合は、アップロード作品内の localStorage は一時メモリー扱いなので、クラウドセーブ用のSDKを使う方法を、現在の案内で確認してください。

次の行動

index.html を作って動かし、係数を3通りに変えて、60秒ずつ触ったときの記録を取ってみてください。そのうえで、天気を1種類増やし、状態が増えたときにコードのどこが変わるかを確かめます。気に入った形になったら、アソバースに下書きとして投稿して、表示の確認まで進めてみてください。