■7つ目「ブレイド・クエスト」
今回は「スゴロクを作ってみたい」という気持ちと、「冒険RPGのファンタジーな世界観をゲームに盛り込みたい」という思いを合わせて、AIと相談しながら「剣を進化させながら進むスゴロクRPG」を、制作しました。
武器の種類を増やしたい、属性を付けたい、マップの雰囲気をこうしたい、サイコロの表現を工夫したい……など、こだわりを指示に盛り込んでいくうちに、AIへの相談文がどんどん長くなってしまいました。
長くなりすぎたので、一部だけ抜粋します。( ⌒ _ ⌒ *)↓
■ AIとのやりとりで面白かった部分
(^^*)スゴロク風RPGを作ってみたい。絵の表示は難しそうなので、文字での表現をメインにして、装備品(剣)を集めて合成し、よりレアな装備へ進化させながらゴールを目指すゲームにしたい。
剣の強さがそのままプレイヤーの強さになり、マスに出てくるモンスターとのバトルは剣の強さで勝敗が決まる。
負けるとマスを戻されてしまうので、ゴールが遠のくイメージ。
剣をどれだけ早く強化できるかが勝負で、ゴールのボスを先に倒した人が勝ち。
(AI)「剣の進化」と「バトルによる後退」という、非常にRPGらしい面白い要素ですね!
( ̄v ̄*)実際にスゴロクゲームとして動かしてみたいね。サイコロは1~6、プレイヤーは2~4人。世界観は中世風で、ゴールは魔王城。
冒険の道中なので、サイコロの出目に合わせて一歩ずつ進む感じにしたい。可能なら、一歩ごとに足音も鳴らしたい。
ただ、ゴール前でボスに勝てず詰まってしまうと、早く到着したメリットが薄い気がする。
ボスに負けたときは、神の加護のような形で剣がパワーアップするのも良さそう。
(*=v=)文字が少し読みにくいので、もう少し大きく表示できるかな。
敵とのバトルが単調なので、文章で迫力や冒険の盛り上がりを感じたい。
道中のイベントも、もっとワクワクするような臨場感のある文章表現がほしい。
あと、ボスに負けた後は加護で強くなる感じだったけど、剣を進化させていく流れにしても良いかも。
(*つv~)せっかく魔物とのバトルがあるので、勝利したらワンランク上の剣を手に入れるのはどうかな。
それと、マスを進む足音は「ざっ、ざっ、ざっ」という感じの音を出せると雰囲気が出そう。
冒険の文章もだいぶ良くなってきたけど、もっと迫力や臨場感のあるバリエーションを増やしたいね。
あと、剣の種類も思い切って増やしたい。
などなど……
■ 実際の動作
4人までできます。

マップは1本道。

冒険は文字で雰囲気を表示。

AIが途中で出してくれたイメージ画像

■ コードのポイント
今回の記事では、AI と相談しながら作った「BLADE QUEST」の HTML コードを公開します。 このゲームはブラウザだけで動く、とてもシンプルな仕組みです。
PC の「メモ帳」を開き、下のコードをすべてコピーして貼り付け、 「sugoroku.html」など好きな名前で保存 → ブラウザで開く。 これだけで、すぐに遊べます。
操作はとても簡単で、サイコロをクリックして振るだけ。
今回も AI が作ってくれたコードをそのまま載せているので、気になるところを自分で改造したり、色や動きをアレンジして遊んでみてください(^^*)
「※スマホでは動作しない場合があります」
「※コードが長いので、必要な方だけコピーしてください」
▼ここからコード▼(クリックで開く)
<!DOCTYPE html><html lang="ja"><head> <meta charset="UTF-8"> <title>Blade Quest - Chronicle of Valor</title> <style> :root { --paper: #d2b48c; --ink: #3e2723; --gold: #ffd700; --blood: #8b0000; } body { background: #0a0a0a; color: #eee; font-family: 'Hiragino Mincho ProN', serif; display: flex; flex-direction: column; align-items: center; margin: 0; overflow-x: hidden; } #world-container { position: relative; width: 1000px; height: 1850px; background: #111; padding: 40px; } #map { position: relative; width: 1000px; height: 1750px; background: linear-gradient(145deg, #e6cc9f 0%, #b59a6d 100%); border: 10px solid #2a1b18; border-radius: 25px; box-shadow: 0 0 80px rgba(0,0,0,1); } .cell { position: absolute; width: 75px; height: 75px; background: rgba(62, 39, 35, 0.25); border: 2px dashed #2a1b18; border-radius: 50%; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 14px; font-weight: bold; z-index: 2; color: #2a1b18; } .cell.special { background: rgba(139, 0, 0, 0.2); border-style: solid; box-shadow: 0 0 15px var(--blood); } .cell.boss { background: #000; color: #ff3300; border: 5px double #ff3300; font-size: 20px; box-shadow: 0 0 40px #ff3300; } .player-piece { position: absolute; width: 60px; height: 60px; font-size: 50px; transition: all 0.45s cubic-bezier(0.175, 0.885, 0.32, 1.275); z-index: 10; text-shadow: 5px 5px 8px rgba(0,0,0,0.7); } #fixed-ui { position: fixed; bottom: 0; left: 0; width: 100%; height: 280px; background: #050505; color: #00ff41; display: flex; padding: 20px; border-top: 6px solid #2a1b18; z-index: 100; box-sizing: border-box; } .log-panel { flex: 2; border: 2px solid #333; padding: 15px; overflow-y: auto; font-family: 'Consolas', monospace; font-size: 19px; line-height: 1.7; background: #000; color: #bbb; border-radius: 5px; } .stat-panel { flex: 1; padding: 0 30px; text-align: center; border-left: 3px solid #2a1b18; display: flex; flex-direction: column; justify-content: center; } .dice-display { font-size: 110px; color: #fff; text-shadow: 0 0 30px var(--gold); margin-bottom: 10px; } button { font-family: inherit; font-size: 26px; padding: 18px 45px; cursor: pointer; background: #4a332d; color: white; border: 3px solid var(--gold); border-radius: 10px; font-weight: bold; transition: 0.2s; } button:hover:not(:disabled) { background: #8b0000; transform: scale(1.05); } .log-p-id { color: var(--gold); font-weight: bold; } .log-event { color: #fff; border-bottom: 1px dashed #444; margin-bottom: 8px; padding-bottom: 4px; } </style></head><body><div id="setup" style="padding: 150px; text-align: center;"> <h1 style="color: var(--gold); font-size: 70px; text-shadow: 5px 5px #000;">⚔️ けもの道の叙事詩<br><span style="font-size: 32px; color: #fff;">〜覇王の剣と血塗られた進軍〜</span></h1> <button onclick="startGame(2)">二人の勇士</button> <button onclick="startGame(3)">三人の勇士</button> <button onclick="startGame(4)">四人の勇士</button></div><div id="game-area" style="display:none;"> <div id="world-container"> <div id="map"> <svg id="path-svg" style="position:absolute; width:100%; height:100%; pointer-events:none;"></svg> </div> </div> <div id="fixed-ui"> <div class="log-panel" id="log"></div> <div class="stat-panel"> <div id="turn-display" style="font-size:26px; margin-bottom:12px;"></div> <div id="dice-view" class="dice-display">🎲</div> <button id="dice-btn" onclick="rollDice()">運命のダイス</button> </div> <div class="stat-panel" id="p-stats" style="text-align:left; font-size:22px; color: #fff;"></div> </div></div><script>const audio = new (window.AudioContext || window.webkitAudioContext)();function playTone(freq, dur, type='square', vol=0.1) { const osc = audio.createOscillator(); const g = audio.createGain(); osc.type = type; osc.frequency.setValueAtTime(freq, audio.currentTime); g.gain.setValueAtTime(vol, audio.currentTime); g.gain.exponentialRampToValueAtTime(0.01, audio.currentTime + dur); osc.connect(g); g.connect(audio.destination); osc.start(); osc.stop(audio.currentTime + dur);}// 「ざっざっ」という足音(ノイズ合成)function playStep() { const bufferSize = audio.sampleRate * 0.1; const buffer = audio.createBuffer(1, bufferSize, audio.sampleRate); const data = buffer.getChannelData(0); for (let i = 0; i < bufferSize; i++) data[i] = Math.random() * 2 - 1; const noise = audio.createBufferSource(); noise.buffer = buffer; const filter = audio.createBiquadFilter(); filter.type = 'lowpass'; filter.frequency.value = 800; const g = audio.createGain(); g.gain.setValueAtTime(0.08, audio.currentTime); g.gain.exponentialRampToValueAtTime(0.001, audio.currentTime + 0.1); noise.connect(filter); filter.connect(g); g.connect(audio.destination); noise.start();}const weaponNames = [ ["石の短刀", "木の棒", "鈍い錆刀", "壊れた斧", "竹槍", "棍棒", "鉈", "古いクワ", "尖った石", "枝葉の鞭", "石槌", "木の盾"], ["青銅の剣", "鉄の爪", "ブロンズメイス", "投げナイフ", "兵士の槍", "狩人の弓", "カトラス", "小太刀", "手斧", "戦槌", "鉄糸", "鉄扇"], ["バスタードソード", "フランベルジュ", "長槍", "ハルバード", "バトルアックス", "モーニングスター", "エストック", "長脇差し", "斬馬刀", "金砕棒", "重クロスボウ", "パルチザン"], ["鋼鉄の業物", "双剣", "クレセントアクス", "雷光の短剣", "グレートソード", "パイク", "ウォーハンマー", "名刀・無銘", "曲刀", "蛇腹剣", "連弩", "ツヴァイハンダー"], ["🔥灼熱のシミター", "❄️氷結晶のレイピア", "🍃突風の槍", "⚡迅雷の双斧", "魔銀の剣", "聖騎士の剣", "大身槍", "黒曜石の槌", "阿修羅の爪", "首切りの大斧", "剛弓", "ルニックブレイド"] // ...以降は神話級へ(プログラム内で拡張)];const victoryMsgs = ["魂を震わせる一閃が敵を両断した!", "敵の心臓を正確に射抜き、沈黙させた。", "咆哮と共に振り下ろされた一撃が大地を砕き、敵を粉砕した。", "電光石火の身のこなしで敵の隙を突き、致命傷を与えた。"];const moveMsgs = ["険しい獣道を、自らの意志で切り拓いていく。", "古の戦場跡を通り抜ける。亡霊たちの囁きが聞こえた気がした。", "立ち込める霧の中、獲物を狙う鋭い眼光を背中に感じる。", "静寂が支配する森を進む。聞こえるのは自らの足音だけだ。"];const mapData = [];for(let i=0; i<41; i++) { let type = "road", n = "道", e = 0; if(i===0) n="出発点"; else if(i===40) { n="魔王城"; type="boss"; e=180; } else if(i%8 === 0) { n="聖鍛冶"; type="up"; } else if(i%5 === 0) { n="秘宝"; type="item"; } else if(i%3 === 0) { const t = ["fire","ice","wind"][Math.floor(Math.random()*3)]; n = (t==="fire"?"🔥巨獣":(t==="ice"?"❄️亡霊":"🍃怪鳥")); type = t; e = i * 3.2 + 20; } mapData.push({n, type, e});}let players = []; let currentPlayer = 0; let actionLocked = false;function startGame(num) { audio.resume(); document.getElementById('setup').style.display = 'none'; document.getElementById('game-area').style.display = 'block'; const icons = ['⚔️', '🧙', '🏹', '🛡️']; for(let i=0; i<num; i++) players.push({ id:i+1, pos:0, atk:20, rank:0, weapon:weaponNames[0][i%12], icon:icons[i], bless:0 }); createMap(); updateUI();}function createMap() { const map = document.getElementById('map'); const svg = document.getElementById('path-svg'); let pts = ""; mapData.forEach((m, i) => { const x = 150 + (Math.sin(i*0.5)*300 + 350); const y = 100 + (i * 38); m.x = x; m.y = y; const div = document.createElement('div'); div.className = 'cell' + (m.type!=="road"?" special":"") + (m.type==="boss"?" boss":""); div.style.left = x+'px'; div.style.top = y+'px'; div.innerHTML = `<span>${i}</span><br>${m.n}`; map.appendChild(div); pts += `${x+37},${y+37} `; }); svg.innerHTML = `<polyline points="${pts}" fill="none" stroke="#2a1b18" stroke-width="8" stroke-dasharray="10"/>`; players.forEach(p => { const div = document.createElement('div'); div.id = 'p-'+p.id; div.className = 'player-piece'; div.innerText = p.icon; map.appendChild(div); movePiece(p); });}function movePiece(p) { const el = document.getElementById('p-'+p.id); const m = mapData[p.pos]; el.style.left = (m.x + 8) + 'px'; el.style.top = (m.y + 8) + 'px'; window.scrollTo({ top: m.y - 450, behavior: 'smooth' });}function updateUI() { const p = players[currentPlayer]; document.getElementById('turn-display').innerHTML = `<span style="color:#fff">${p.icon} プレイヤー${p.id}</span>`; document.getElementById('p-stats').innerHTML = `武具:<span style="color:var(--gold)">${p.weapon}${p.bless>0?'+'+p.bless:''}</span><br>攻撃力:<span style="font-size:32px; color:var(--gold)">${Math.floor(p.atk)}</span>`;}function rollDice() { if(actionLocked) return; actionLocked = true; document.getElementById('dice-btn').disabled = true; let count = 0; const iv = setInterval(() => { const d = Math.floor(Math.random()*6)+1; document.getElementById('dice-view').innerText = d; playTone(200+d*100, 0.05, 'square', 0.02); if(count++ > 15) { clearInterval(iv); walkSteps(d); } }, 80);}async function walkSteps(dice) { let p = players[currentPlayer]; for (let i = 0; i < dice; i++) { if (p.pos < 40) { p.pos++; movePiece(p); playStep(); await new Promise(r => setTimeout(r, 450)); } } processCell();}function getNewWeapon(rank) { const r = Math.min(rank, 4); // 5段階目以降は神話武器を生成 if (rank <= 4) return weaponNames[rank][Math.floor(Math.random()*12)]; const myth = ["ラグナロク", "天叢雲", "エクスカリバー", "グングニル", "ゲイボルグ", "ムラマサ", "デュランダル"]; return myth[Math.floor(Math.random()*myth.length)] + (rank > 10 ? "・真" : "");}function processCell() { let p = players[currentPlayer]; const cell = mapData[p.pos]; const log = document.getElementById('log'); let msg = ""; if (cell.type === "road") { msg = moveMsgs[Math.floor(Math.random()*moveMsgs.length)]; } else if (cell.e) { let power = p.atk; const bonus = ((cell.type==='fire' && p.weapon.includes('❄️')) || (cell.type==='ice' && p.weapon.includes('🍃')) || (cell.type==='wind' && p.weapon.includes('🔥'))); if(bonus) { power *= 2.5; msg += `<span style="color:cyan">【属性覚醒】</span> `; } msg += `<span style="color:#fff">VS ${cell.n}。</span> `; if(power < cell.e) { if(cell.type === "boss") { p.bless++; p.atk += 30; msg += `敗北…! だが神の雷光が剣を叩き、<span style="color:var(--gold)">${p.weapon}は+${p.bless}へ昇華した!</span>`; } else { msg += `不覚! 致命的な打撃を浴び、這走して逃げ出した。`; } p.pos = Math.max(0, p.pos - 5); playTone(60, 0.8, 'sawtooth', 0.1); setTimeout(() => movePiece(p), 600); } else { p.rank++; p.atk += 20; p.weapon = getNewWeapon(p.rank); msg += victoryMsgs[Math.floor(Math.random()*victoryMsgs.length)] + ` 戦利品として<span style="color:var(--gold)">「${p.weapon}」</span>を奪った!`; playTone(1000, 0.3, 'sine', 0.1); } } else if (cell.type === 'up' || cell.type === 'item') { p.rank += 2; p.atk += 35; p.bless = 0; p.weapon = getNewWeapon(p.rank); msg = `古の英霊の声を聞いた。武具が<span style="color:var(--gold)">「${p.weapon}」</span>へと再構築される!`; playTone(1500, 0.5, 'sine', 0.1); } log.innerHTML = `<div class="log-event"><span class="log-p-id">${p.icon} P${p.id}:</span> ${msg}</div>` + log.innerHTML; if(p.pos === 40 && p.atk >= cell.e) { alert(`【伝説の終焉】プレイヤー${p.id}は魔王を断罪し、永遠の英雄となりました。`); location.reload(); } else { currentPlayer = (currentPlayer + 1) % players.length; actionLocked = false; document.getElementById('dice-btn').disabled = false; updateUI(); }}</script></body></html>
■ 今日の学び
2回目の記事で作ろうとしていた「盆栽ゲーム」のときと同じく、今回も夢中になりすぎそうだったので、「まずはその日のうちにゲームとして遊べる形まで仕上げる」ことを目標にして作りました。
まだ物足りないところは多いので、次に修正するときはストーリーや面白さの要素をもっと足していきたいです(^v^;)
ゲーム作りは今のところ毎日1個ずつ作っていて、気づけば 40 個を超えて順調な感じです。40 個を超えたあたりから AI を有料版に切り替えてみました(1 か月は無料とのこと)。
そこからいくつかゲームを作ってみて、色々と感じた違いもあるので、そのあたりも今後の記事で伝えていければと思います(^^*)
ブログのアイキャッチも、AI が面白い感じに作ってくれるので、少しずつ入れ替えていく予定です(*>v<)b”
次回は、「茶室でハムスターを飛ばして乗っける」ゲームです。
どうぞお楽しみに(*^v^)ノシ
次の実験(飛ばし乗せゲーム):「ハムスター・タワー」

コメントを残す