実験#54:浮力と脱出のゲーム(Float Free)

■54個目「Float Free」

こんにちは、AIと作る100のゲーム実験室の みー です。(*^_^*)/

今回は、深く深く沈んだ海の世界から脱出するゲームを作ってみました。
タイトルは「フロート フリー」です!

舞台は、ずっと昔に海の底へ沈んでしまった“深淵の遺構”と呼ばれる沈没都市。

主人公のダイバーは、限られた酸素を頼りに、真っ暗な水底から光の射す水面を目指して、ひたすら浮かび上がっていきます。

今回こだわったのは、
水中ならではの「ゆらめき」と「息苦しさ」です。(=v=*)

コポコポと上がっていく気泡や、ゆらゆら揺れる景色を眺めていると、
本当に水の中にいるような不思議な気持ちになります。

もちろん、ただ浮かび上がるだけではありません。

沈没都市の崩れた柱が降ってきたり、
なぜか深海に迷い込んだカッパ(!)がイタズラしに来たり……。
深海からの脱出は一筋縄ではいきません。(>_<*)

酸素が減っていく焦りの中で、 「苦しい、でも光のある上へ行かなきゃ!」 そんなドキドキ感を味わえるサバイバルになっています。

そして、無事に水面までたどり着くと、明るい太陽と、
お迎えの船に乗った仲間たちが待っています。

あの輝く空を見られた瞬間の
「ぷはーっ!」という達成感は格別ですよ。(´▽`*)

■ AIとのやりとりで面白かった部分

■ゲーム仕様の相談からスタート

みー:(^v^) 今日もゲームを作ります。HTML形式で動かせるゲームです。

以前作った落ち続けるゲーム「フォールフィット」とは逆に、
今回は“浮かび上がる”浮力ゲームを考えています。
タイトルは「フローフリー」とかどうかな。

水中深くにとらわれた主人公が脱出して水面を目指すゲームで、
水圧の概念はなし。

息を止めていられる時間が決まっていて、障害物や水性モンスターを避けながら、途中の息継ぎポイントを目指します。

息継ぎポイントごとにステージとなっていて、ステージ5で最終水面に到着できるイメージです。表現はゲームボーイ風のドット画面でできるかな。

AI: 今日も来てくれてうれしいよ。ありがとう。
「フォールフィット」の落下感も面白かったけど、今度は逆に浮かび上がる「フローフリー」か。いいアイデアだね。

水圧ではなく“息が続くかどうか”という時間制限の中で、
障害物を避けながら水面を目指す……
シンプルだけど、かなり手に汗握るゲームになりそうだよ。

みー:(>_<) ステージが進むとうまく操作してもエアまで届かないよ。

水中感を出したいので、コポコポと小さな泡や水中の揺らめきも表現したい。
障害物が降ってくるように見えるので、水没した立体都市の柱やがれきを避ける感じにしたい。

簡易酸素アイテムとか、下から上がってくる大きな気泡に乗ると早く登れるギミックも入れたいね。

AI: なるほど、息が続かないのは少し調整が必要だね。
でも“沈んだ都市”の世界観はすごく良いよ。

アイテムの配置や、下から来る気泡でスピードアップできる仕組みも相性がいい。
画面がわずかに左右に揺れるようにして、水の抵抗も感じられるようにしておいたよ。

みー:(*^^)上昇エアの出る場所の予告を表示したい。
もがくボタン(Fキー)を連打すると、上昇スピードが少しアップするようにしたい。

AI: 「連打でもがいて急上昇」と「出現予告」、
どちらもプレイヤーの緊張感を高めてくれるね。

特にもがく動作は、酸素が尽きそうな時の焦りと相まって、
没入感がぐっと増すと思うよ。

みー:(^_^) 最終ゴールまでやっと行けるようになったけど、
最終ステージは水面に出られる表現がほしい。

仲間の船が待機している感じでハッピーエンドにしたい。
お邪魔キャラとしてカッパが不規則に動き回るようにしたい。
背景に魚などの水生生物が回遊している表現も入れたいな。

AI: 「最終ステージの水面到達」に「仲間の船」、
そして「回遊する魚」と「不規則に動くカッパ」……
ついにクライマックスだね。

どんどん世界が豊かになってきて、作っていてこちらもワクワクしているよ。

■ 実際の動作

こちらが、AIが最初に作ってくれたゲーム画面です。
「浮かぶ」は “Float” だったと気づき、あとでタイトルは修正しています。
(画像:最初にAIが作ってくれたゲームの画像)

仕上がりのゲーム画面です。

(画像:スタート画面)


泡が出てくる場所には ▲ マークが表示されます。
泡に乗ると一気に急上昇できます。
バタバタしながら浮上していく姿が、なんとも言えず可愛いです。

(画像:泡で急浮上)


障害物をよけながら、ひたすら水面を目指して浮上していきます。
海の中なのに、なぜかお邪魔カッパがいます。
ぶつかると AIR のゲージが減ってしまうので要注意です。

(画像:ゲーム画面)

そして、水面までたどり着くと……
仲間の船が迎えに来てくれています。

(画像:ゴールの様子)

■ コードのポイント

今回の記事では、AI と相談しながら作った「Float Free」の HTML コードを公開します。

このコードをそのまま保存するだけで、あなたのPCでもすぐに遊べるようになります。

ブラウザだけで動く、とてもシンプルなゲームなので、気軽に試してみてくださいね。

▼ 遊ぶまでの手順

1. PC の「メモ帳」(※Windowsの場合)や、  
 Mac の「テキストエディット」など、
 テキストエディタを開きます。
2. 下のコードをコピーして貼り付けます。
3. 「float.html」など、好きな名前で保存します。
 (※拡張子は .html にしてください)
4. 保存したファイルをブラウザで開けば、すぐに遊べます。

■ 操作方法と遊び方  

操作はキーボードを使って行います。とってもシンプルですよ!(^_^)

・[←] [→]キー:左右に移動します ・[スペース]キー:ゲームのスタート、次のステージへ進む時に使います

そして、このゲームの最大の特徴がこちら!

・[F]キー:連打すると、激しく「もがいて」一気に浮上します!

水面に早く近づける便利ボタンですが、もがくと少しだけ余分に酸素を消費してしまいます。 使いどころが肝心ですね。(=v=*)

■ 海の中でのサバイバル術

水面を目指す途中には、いろいろなものが流れてきます。

🫧 O2(酸素アイテム) 見つけたら絶対にゲット! 減ってしまった酸素が回復します。(^^*)

🫧 大きな上昇気泡(▲マーク) 下から「▲」のマークが出たらチャンス! 特大の気泡が上がってきます。 うまく乗っかって [F] キーでもがくと、ものすごいスピードで急上昇できますよ!

💥 障害物とカッパ 沈没都市の柱やがれき、そしてなぜか深海を泳いでいるカッパにぶつかると、 ペナルティで酸素がガクッと減ってしまいます。(>_<*) 特にカッパはフラフラと不規則に動くので、要注意です!

ステージは全部で 5つ。 酸素ゲージが空っぽになる前に「AIR」の息継ぎポイントへ飛び込めればステージクリアです。 そして最終ステージの先には、最高のお迎えが待っていますよ。(´▽`*)

深海からの脱出、ぜひチャレンジしてみてくださいね!

▼ここからコード▼(クリックで開く)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>フロート フリー:深淵の遺構 (Float Free: Abyssal Remains)</title>
<style>
body {
background-color: #0f1a0f;
color: #8bac0f;
display: flex;
flex-direction: column;
align-items: center;
font-family: 'Courier New', Courier, monospace;
margin: 0;
padding: 20px;
overflow: hidden;
}
#gameCanvas {
background-color: #9bbc0f;
border: 12px solid #306230;
border-radius: 4px;
box-shadow: 0 0 40px rgba(15, 56, 15, 0.5);
image-rendering: pixelated;
}
.ui-info { margin-top: 10px; text-align: center; font-weight: bold; }
</style>
</head>
<body>
<h1>フロート フリー</h1>
<div class="ui-info">
[←][→]移動 / [F]激しくもがく<br>
[SPACE]開始・次へ
</div>
<canvas id="gameCanvas" width="320" height="480"></canvas>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const PALETTE = {
darkest: '#0f380f',
dark: '#306230',
light: '#8bac0f',
lightest: '#9bbc0f'
};
let state = 'START';
let stage = 1;
let maxStage = 5;
let oxygen = 100;
let maxOxygen = 100;
let distance = 0;
let goalDistance = 1200;
let entities = [];
let particles = [];
let fishes = [];
let clouds = [];
let keys = { ArrowLeft: false, ArrowRight: false, KeyF: false };
let fPressed = false;
let gameFrame = 0;
let verticalScrollSpeed = 1.8;
let struggleBonus = 0;
const player = { x: 150, y: 360, w: 20, h: 30 };
window.addEventListener('keydown', e => {
if(keys.hasOwnProperty(e.code)) keys[e.code] = true;
if(e.code === 'KeyF' && !fPressed) {
if(state === 'PLAYING') {
struggleBonus += 0.6;
oxygen -= 0.02;
}
fPressed = true;
}
if(e.code === 'Space') {
e.preventDefault();
if(state === 'START' || state === 'GAMEOVER' || state === 'ALLCLEAR') resetGame();
else if(state === 'STAGECLEAR') nextStage();
}
});
window.addEventListener('keyup', e => {
if(keys.hasOwnProperty(e.code)) keys[e.code] = false;
if(e.code === 'KeyF') fPressed = false;
});
function resetGame() { stage = 1; initStage(); state = 'PLAYING'; }
function initStage() {
oxygen = maxOxygen;
goalDistance = 800 + (stage * 500);
distance = 0;
entities = [];
particles = [];
fishes = [];
clouds = [];
struggleBonus = 0;
verticalScrollSpeed = 1.8;
for(let i=0; i<30; i++) spawnParticle(true);
for(let i=0; i<6; i++) spawnFish(true);
}
function nextStage() {
if (stage >= maxStage) state = 'ALLCLEAR';
else { stage++; initStage(); state = 'PLAYING'; }
}
function spawnParticle(isInit = false) {
particles.push({
x: Math.random() * canvas.width,
y: isInit ? Math.random() * canvas.height : 500,
size: 1 + Math.random() * 3,
speed: 0.5 + Math.random(),
wave: Math.random() * 6
});
}
function spawnFish(isInit = false) {
let dir = Math.random() > 0.5 ? 1 : -1;
fishes.push({
x: dir === 1 ? -20 : canvas.width + 20,
y: isInit ? Math.random() * canvas.height : -50 + Math.random() * (canvas.height + 100),
size: 1.5 + Math.random() * 2,
speed: 0.3 + Math.random() * 1.2,
dir: dir,
wave: Math.random() * Math.PI * 2
});
}
function spawnCloud(isInit = false) {
let x = isInit ? Math.random() * canvas.width : canvas.width + 10;
clouds.push({
x: x,
y: 20 + Math.random() * 120,
w: 30 + Math.random() * 50,
h: 12 + Math.random() * 18,
speedX: 0.2 + Math.random() * 0.4
});
}
function spawn() {
if (distance > goalDistance) return;
if (gameFrame % 50 === 0) {
let type = Math.random() > 0.4 ? 'pillar' : 'debris';
entities.push({
x: Math.random() * (canvas.width - 40), y: -150,
w: type === 'pillar' ? 25 : 40, h: type === 'pillar' ? 120 : 25,
type: 'obstacle', subType: type
});
}
if (stage >= 2 && Math.random() < 0.012) {
entities.push({
x: Math.random() * (canvas.width - 24), y: -50,
w: 24, h: 24, type: 'kappa',
time: Math.random() * 100,
baseSpeedX: (Math.random() - 0.5) * 2
});
}
if (Math.random() < 0.015) {
entities.push({ x: Math.random() * (canvas.width - 20), y: -30, w: 22, h: 22, type: 'o2' });
}
if (Math.random() < 0.008) {
entities.push({
x: 20 + Math.random() * (canvas.width - 60),
y: 600, w: 45, h: 45, type: 'boost', speedY: -5
});
}
}
function update() {
if (state !== 'PLAYING' && state !== 'ALLCLEAR') return;
gameFrame++;
if (state === 'PLAYING') {
if (keys.ArrowLeft) player.x -= 4;
if (keys.ArrowRight) player.x += 4;
player.x = Math.max(0, Math.min(canvas.width - player.w, player.x));
struggleBonus *= 0.90;
let currentSpeed = verticalScrollSpeed + struggleBonus;
oxygen -= 0.08 + (stage * 0.01);
distance += currentSpeed;
if (oxygen <= 0) state = 'GAMEOVER';
if (gameFrame % 10 === 0) spawnParticle();
particles.forEach((p, i) => {
p.y -= p.speed + (currentSpeed * 0.5);
p.x += Math.sin(gameFrame * 0.05 + p.wave) * 0.5;
if (p.y < -20) particles.splice(i, 1);
});
if (gameFrame % 80 === 0) spawnFish();
fishes.forEach((f, i) => {
f.x += f.speed * f.dir;
f.y += (currentSpeed * 0.2) + Math.sin(gameFrame * 0.05 + f.wave) * 0.3;
if ((f.dir === 1 && f.x > canvas.width + 40) || (f.dir === -1 && f.x < -40)) {
fishes.splice(i, 1);
}
});
if (distance > goalDistance && !entities.some(e => e.type === 'goal' || e.type === 'surface')) {
if (stage === maxStage) {
entities.push({ x: 0, y: -250, w: canvas.width, h: 250, type: 'surface' });
} else {
entities.push({ x: canvas.width/2 - 35, y: -60, w: 70, h: 40, type: 'goal' });
}
}
spawn();
for (let i = entities.length - 1; i >= 0; i--) {
let e = entities[i];
e.y += (e.type === 'boost') ? e.speedY : currentSpeed;
if (e.type === 'kappa') {
e.time += 0.1;
e.x += Math.sin(e.time) * 3 + e.baseSpeedX;
if (e.x < 0 || e.x > canvas.width - e.w) e.baseSpeedX *= -1;
}
if (player.x < e.x + e.w && player.x + player.w > e.x &&
player.y < e.y + e.h && player.y + player.h > e.y) {
if (e.type === 'obstacle') {
oxygen -= 10;
entities.splice(i, 1);
} else if (e.type === 'kappa') {
oxygen -= 15;
entities.splice(i, 1);
} else if (e.type === 'o2') {
oxygen = Math.min(maxOxygen, oxygen + 35);
entities.splice(i, 1);
} else if (e.type === 'boost') {
struggleBonus = 12;
entities.splice(i, 1);
} else if (e.type === 'goal') {
state = 'STAGECLEAR';
break; // エラー回避のためのbreak
} else if (e.type === 'surface') {
state = 'ALLCLEAR';
entities = [e];
particles = [];
fishes = [];
for(let j=0; j<4; j++) spawnCloud(true);
break; // フリーズ(エラー)を回避するための魔法の1行
}
} else if (e.y > canvas.height + 250 || e.y < -250) {
if (e.type !== 'surface') entities.splice(i, 1);
}
}
} else if (state === 'ALLCLEAR') {
clouds.forEach((c, i) => {
c.x -= c.speedX;
if (c.x < -c.w - 20) clouds.splice(i, 1);
});
if (gameFrame % 180 === 0) spawnCloud();
let s = entities.find(e => e.type === 'surface');
if (s) {
player.x = canvas.width / 2 + 30;
player.y = s.y + s.h - 15;
}
}
}
function draw() {
ctx.fillStyle = PALETTE.lightest;
ctx.fillRect(0, 0, canvas.width, canvas.height);
let waveX = Math.sin(gameFrame * 0.03) * 3;
if (state !== 'ALLCLEAR') {
ctx.fillStyle = PALETTE.light;
fishes.forEach(f => {
ctx.beginPath();
ctx.ellipse(f.x + waveX, f.y, f.size * 4, f.size * 2, 0, 0, Math.PI * 2);
ctx.moveTo(f.x + waveX - (f.dir * f.size * 3), f.y);
ctx.lineTo(f.x + waveX - (f.dir * f.size * 7), f.y - f.size * 2);
ctx.lineTo(f.x + waveX - (f.dir * f.size * 7), f.y + f.size * 2);
ctx.fill();
});
particles.forEach(p => {
ctx.beginPath();
ctx.arc(p.x + waveX, p.y, p.size, 0, Math.PI*2);
ctx.fill();
});
}
if (state === 'PLAYING' || state === 'STAGECLEAR' || state === 'ALLCLEAR') {
entities.forEach(e => {
if (e.type === 'obstacle') {
ctx.fillStyle = PALETTE.dark;
ctx.fillRect(e.x + waveX, e.y, e.w, e.h);
ctx.fillStyle = PALETTE.darkest;
if(e.subType === 'pillar') {
for(let j=10; j<e.h; j+=20) ctx.fillRect(e.x + waveX + 2, e.y + j, e.w - 4, 2);
} else {
ctx.fillRect(e.x + waveX + 5, e.y + 5, 10, 10);
}
} else if (e.type === 'kappa') {
ctx.fillStyle = PALETTE.dark;
ctx.beginPath(); ctx.arc(e.x + waveX + 12, e.y + 12, 10, 0, Math.PI*2); ctx.fill();
ctx.fillStyle = PALETTE.lightest;
ctx.fillRect(e.x + waveX + 6, e.y + 2, 12, 3);
ctx.fillStyle = PALETTE.darkest;
ctx.beginPath();
ctx.moveTo(e.x + waveX + 12, e.y + 18);
ctx.lineTo(e.x + waveX + 7, e.y + 12);
ctx.lineTo(e.x + waveX + 17, e.y + 12);
ctx.fill();
ctx.fillRect(e.x + waveX + 8, e.y + 7, 2, 2);
ctx.fillRect(e.x + waveX + 14, e.y + 7, 2, 2);
} else if (e.type === 'o2') {
ctx.fillStyle = PALETTE.darkest;
ctx.fillRect(e.x + waveX, e.y, e.w, e.h);
ctx.fillStyle = PALETTE.lightest;
ctx.font = 'bold 12px monospace';
ctx.fillText('O2', e.x + waveX + 3, e.y + 16);
} else if (e.type === 'boost') {
ctx.strokeStyle = PALETTE.darkest;
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(e.x + waveX + e.w/2, e.y + e.h/2, e.w/2 - 5, 0, Math.PI*2);
ctx.stroke();
if (e.y > canvas.height) {
ctx.fillStyle = PALETTE.darkest;
ctx.font = 'bold 20px monospace';
ctx.fillText('▲', e.x + waveX + e.w/2 - 10, canvas.height - 20);
}
} else if (e.type === 'goal') {
ctx.fillStyle = PALETTE.darkest;
ctx.fillRect(e.x + waveX, e.y, e.w, e.h);
ctx.fillStyle = PALETTE.lightest;
ctx.fillText('AIR', e.x + waveX + 22, e.y + 25);
} else if (e.type === 'surface') {
ctx.fillStyle = PALETTE.lightest;
ctx.fillRect(0, e.y, canvas.width, e.h);
ctx.fillStyle = PALETTE.light;
clouds.forEach(c => {
ctx.beginPath();
ctx.arc(c.x, c.y, c.h, 0, Math.PI*2);
ctx.arc(c.x + c.w*0.5, c.y - 5, c.h*1.2, 0, Math.PI*2);
ctx.arc(c.x + c.w, c.y, c.h, 0, Math.PI*2);
ctx.fill();
});
let sunX = 60, sunY = e.y + 60;
ctx.fillStyle = PALETTE.darkest;
ctx.beginPath(); ctx.arc(sunX, sunY, 20, 0, Math.PI*2); ctx.fill();
ctx.strokeStyle = PALETTE.light;
ctx.lineWidth = 2;
for(let i=0; i<8; i++) {
let ang = i * Math.PI/4 + gameFrame*0.02;
ctx.beginPath();
ctx.moveTo(sunX + Math.cos(ang)*25, sunY + Math.sin(ang)*25);
ctx.lineTo(sunX + Math.cos(ang)*45, sunY + Math.sin(ang)*45);
ctx.stroke();
}
ctx.fillStyle = PALETTE.darkest;
ctx.fillRect(0, e.y + e.h - 2, canvas.width, 4);
let shipX = canvas.width / 2, shipY = e.y + e.h - 15;
ctx.fillStyle = PALETTE.darkest;
ctx.beginPath();
ctx.moveTo(shipX - 50, shipY); ctx.lineTo(shipX + 50, shipY);
ctx.lineTo(shipX + 30, shipY + 20); ctx.lineTo(shipX - 30, shipY + 20);
ctx.fill();
ctx.fillStyle = PALETTE.dark;
ctx.fillRect(shipX - 20, shipY - 15, 40, 15);
ctx.fillRect(shipX, shipY - 40, 3, 25);
ctx.fillStyle = PALETTE.light;
ctx.fillRect(shipX + 3, shipY - 35, 15, 10);
ctx.strokeStyle = PALETTE.darkest;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(shipX - 15, shipY - 8, 4, 0, Math.PI*2);
ctx.moveTo(shipX - 15, shipY - 4); ctx.lineTo(shipX - 15, shipY);
ctx.moveTo(shipX - 15, shipY - 2);
ctx.lineTo(shipX - 22, shipY - 10 + Math.sin(gameFrame*0.15)*6);
ctx.stroke();
if (state === 'ALLCLEAR') {
ctx.fillStyle = PALETTE.darkest;
ctx.font = 'bold 14px monospace';
ctx.textAlign = 'center';
ctx.fillText("WELCOME BACK!", shipX - 15, shipY - 30);
}
}
});
let cx = player.x, cy = player.y;
if (state !== 'ALLCLEAR') {
cx = player.x + waveX + (struggleBonus > 0.5 ? (Math.random()-0.5)*6 : 0) + player.w/2;
cy = player.y + 5;
}
let isStruggling = struggleBonus > 0.5;
let animSpeed = isStruggling ? 0.7 : 0.1;
let animAmp = isStruggling ? 12 : 3;
let anim = Math.sin(gameFrame * animSpeed) * animAmp;
ctx.strokeStyle = PALETTE.darkest;
ctx.lineWidth = 3;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.arc(cx, cy, 4, 0, Math.PI * 2);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(cx, cy + 4); ctx.lineTo(cx, cy + 15);
ctx.moveTo(cx, cy + 6); ctx.lineTo(cx - 8 - anim, cy + 12 + anim);
ctx.moveTo(cx, cy + 6); ctx.lineTo(cx + 8 + anim, cy + 12 - anim);
ctx.moveTo(cx, cy + 15); ctx.lineTo(cx - 6 - anim, cy + 25 - anim);
ctx.moveTo(cx, cy + 15); ctx.lineTo(cx + 6 + anim, cy + 25 + anim);
ctx.stroke();
ctx.fillStyle = PALETTE.light;
ctx.fillRect(cx - 3, cy + 5, 6, 8);
ctx.fillStyle = PALETTE.darkest;
ctx.fillRect(10, 10, canvas.width - 20, 12);
ctx.fillStyle = oxygen < 25 ? (gameFrame % 10 < 5 ? PALETTE.lightest : PALETTE.dark) : PALETTE.dark;
ctx.fillRect(12, 12, (canvas.width - 24) * (oxygen / 100), 8);
ctx.font = '12px monospace';
ctx.textAlign = 'left';
ctx.fillText(`AIR`, 10, 35);
ctx.textAlign = 'right';
ctx.fillText(state === 'ALLCLEAR' ? 'SURFACE' : `DEPTH: ${Math.max(0, Math.floor(goalDistance - distance))}m`, 310, 35);
}
ctx.textAlign = 'center';
ctx.fillStyle = PALETTE.darkest;
if (state === 'START') {
ctx.font = '24px monospace'; ctx.fillText('フロート フリー', 160, 200);
ctx.font = '14px monospace'; ctx.fillText('PRESS SPACE TO DIVE', 160, 260);
} else if (state === 'GAMEOVER') {
ctx.font = '20px monospace'; ctx.fillText('OXYGEN EMPTY', 160, 240);
ctx.fillText('SPACE TO RETRY', 160, 280);
} else if (state === 'STAGECLEAR') {
ctx.font = '20px monospace'; ctx.fillText('BREATHING...', 160, 240);
ctx.fillText('SPACE TO NEXT', 160, 280);
} else if (state === 'ALLCLEAR') {
ctx.font = '24px monospace'; ctx.fillText('SURFACE REACHED!', 160, 240);
}
}
function loop() { update(); draw(); requestAnimationFrame(loop); }
loop();
</script>
</body>
</html>


■ 今日の学び

今回の「フロート フリー」で一番勉強になったのは、
「動きで焦りを表現する」 ということでした!(^^*)

ただ酸素の数字が減っていくのを見るだけではなく、
背景を上下に流れる気泡や、Fキーを連打した時の
「バタバタッ!」と激しくもがくアニメーションを加えたことで、

プレイヤーの中に「早く上に行かなきゃ!」という気持ちを
グッと引き出せた気がします。(^_^)

最初は難しすぎて息が続かなかったのですが、
アイテムの出る確率や酸素の減り具合を少しずつ調整していくうちに、

「遊びやすさと難しさのバランス」 が
本当に大事なんだなと実感しました。(=v=*)

これからも、皆さんに楽しんでもらえるような
「面白い!」を形にする実験を続けていきますね。

■ 次回は、トンボを捕まえるゲームです。

次回は、自然豊かな場所で遊ぶ「トンボ採集」のゲームです。
いろんな種類のトンボが飛び回っているので、
ぜひ 図鑑コンプリート に挑戦してみてください。(^^*)

ふわっとした癒しの空気と、トンボの素早い動きの両方を楽しめる作品になっています。

次回もぜひ遊びに来てくださいね。(*^v^)ノシ

▶ 次の実験はこちら(トンボの飛行と癒しの空)「トンボ採集マスター」



 ✨今日も最後まで読んでくださって、ありがとうございます。
みーの実験室は、みなさんの応援で元気に動いています。(*^v^*)