集めた仲間によって評価が変わるRPGの作成 ~まとめ~

 集めた仲間によって評価が変わるRPGの作成

まとめ

こんにちは、東京経済大学3年のSTです。
前回は、SEの追加、不具合の解消、評価ランクの設定などの説明をしました。
今回はこのブログ最後のまとめとなります。

Buddy Assembleまとめページ

— 追加要素:SE / スクロール / ボス演出 / 撃破消滅 / エンディング / 評価 / セーブ —

目次

  1. 全体構成(マップ&ステージ接続)

  2. ゲーム状態・移動・当たり判定

  3. 効果音(SE)一式 + 特別加入SE

  4. 仲間・アイテム・フラグAPI

  5. セーブ / ロード / タイトル画面

  6. NPC画像プリロード

  7. NPC配置と会話(条件加入・イベント)

  8. 3体撃破後に消える仕様

  9. ボス戦:確認→カットシーン→撃破処理

  10. 王へ報告→エンディング

  11. メニュー(右パネル表示+スクロール)

  12. 描画(タイル・NPC・プレイヤー・UI)

  13. 評価計算(SSS〜E)とボス条件(A以上)


1. 全体構成(マップ&ステージ接続)

役割

  • map0〜map11 を保持し、stage(現在地)で参照する

  • 4×3のグリッドで隣のステージへ移動できるようにする

要所コード(抜粋)

const COLS = 4, ROWS = 3; var map = [map0,map1,map2,map9, map3,map4,map5,map10, map6,map7,map8,map11]; function neighborIndex(idx, dir){ var r=Math.floor(idx/COLS), c=idx%COLS; if(dir==='left' && c>0)return idx-1; if(dir==='right'&& c<COLS-1)return idx+1; if(dir==='up' && r>0)return idx-COLS; if(dir==='down' && r<ROWS-1)return idx+COLS; return null; }

ポイント

  • ステージ番号が 地図の配置順になっている

  • 端に出たとき neighborIndex が移動先を返す


2. ゲーム状態・移動・当たり判定

役割

  • stage / px / py / onBoat / boatDir でプレイヤー状態を管理

  • タイル種別で「歩ける/歩けない」を判断する

要所コード(抜粋)

var gameMode = 'title'; var stage=0, px=10, py=5; var onBoat=false, boatDir='down'; var landWalk=[1,2,3,4,5,13,14,32,60,63,64,65]; var seaTiles=[8,30,31]; var boatTiles=[41,42,43,44]; var blocked=[6,7,9,10,11,12, ... ,75]; function canStep(st,x,y){ const t=tileAt(st,x,y); if(isBlocked(t)) return false; const n=npcAt(st,x,y); if(n && n.solid) return false; if(onBoat){ return seaTiles.indexOf(t)!==-1 || t===32 || boatTiles.indexOf(t)!==-1; }else{ return landWalk.indexOf(t)!==-1 || boatTiles.indexOf(t)!==-1; } }

ポイント

  • 船モード徒歩モードで歩行可能タイルが切り替わる

  • NPCが solid:true の場合、そこも通れない


3. 効果音(SE)一式 + 特別加入SE(更新後で追加)

役割

  • SEを統一管理し、操作・イベントに音を付ける

  • 重要加入時は 通常加入SE→特別SE の2段階演出

要所コード(抜粋)

var SE = {}; var seEnabled = true; var seVolume = 0.6; function initSE(){ SE.move = document.getElementById("seMove"); SE.talk = document.getElementById("seTalk"); ... SE.join = document.getElementById("seJoin"); SE.joinSp = document.getElementById("seJoinSp"); } function playSE(name){ if(!seEnabled) return; const a = SE[name]; if(!a) return; a.pause(); a.currentTime = 0; a.volume = seVolume; a.play(); } function playJoinSpecial(){ playSE("join"); setTimeout(()=>playSE("joinSp"), 220); }

ポイント

  • SEは <audio id="..."> をHTML側に追加している

  • 加入特別SEは playJoinSpecial() を呼ぶキャラだけ鳴る


4. 仲間・アイテム・フラグAPI(ゲーム進行の核)

役割

  • 「仲間加入」「アイテム取得」「一度きりイベント」を管理

要所コード(抜粋)

var party=[], items=[], flags={}; function setFlag(k,v=true){ flags[k]=v; } function hasFlag(k){ return !!flags[k]; } function addParty(n){ if(!party.includes(n)) party.push(n); } function inParty(n){ return party.includes(n); } function addItem(n){ if(!items.includes(n)){ items.push(n); playSE("item"); } } function once(flagKey,fn){ if(hasFlag(flagKey))return false; fn&&fn(); setFlag(flagKey,true); return true; }

ポイント

  • once() が「一回だけ拾える箱」「一回だけ入手できる武具」に使える

  • addItem にはアイテムSEが組み込まれている


5. セーブ / ロード / タイトル画面(更新後で追加)

役割

  • localStorage を使って進行を保存する

  • タイトル画面からニューゲーム/コンティニューが可能

要所コード(抜粋)

const SAVE_KEY="buddyAssembleSave_v4"; function saveGame(){ const data={stage,px,py,onBoat,boatDir,party,items,flags}; localStorage.setItem(SAVE_KEY,JSON.stringify(data)); playSE("save"); showMsg("セーブ","冒険の記録を保存しました。"); } function loadGame(){ const raw=localStorage.getItem(SAVE_KEY); if(!raw){ showMsg("ロード","セーブデータが見つかりません。"); return; } const d=JSON.parse(raw); stage=d.stage; px=d.px; py=d.py; onBoat=d.onBoat; boatDir=d.boatDir; party=d.party||[]; items=d.items||[]; flags=d.flags||{}; gameMode='play'; fieldpaint(); }

ポイント

  • 位置・船状態・仲間・アイテム・フラグまで保存対象

  • タイトルは gameMode==='title' で描画切替


6. NPC画像プリロード(NPCスプライト管理)

役割

  • NPCスプライトをキー名で管理し、ステージ描画時に呼び出す

要所コード(抜粋)

const NPC_SRC = { granny1:"おばあさん1.png", ... , ghost:"ゴースト.png" }; const imgNPC={}; (function preloadNPCs(){ Object.entries(NPC_SRC).forEach(([k,src])=>{ const im=new Image(); im.src=src; imgNPC[k]=im; }); })();

ポイント

  • NPC側は sprite:"ghost" のようにキーを指定するだけでOK


7. NPC配置と会話(条件加入・イベント)

役割

  • npcs[] にすべてのNPCをまとめて定義

  • talk:function(){...} で条件分岐イベントを作る

典型例(加入判定)

{ active:true, name:"戦士A", sprite:"warrior3", stage:0, x:14,y:6, solid:true, talk:function(){ if(inParty("戦士A")){ showMsg("戦士A","もう仲間になっている。"); return; } if(!hasItem("長剣A")){ showMsg("戦士A","『長剣A』がほしい。"); return; } showChoice("戦士A","それは…『長剣A』じゃないか?",["はい","いいえ"],(i)=>{ if(i===0){ addParty("戦士A"); showMsg("戦士A","よし、ともに行こう。"); } }); } }

ポイント

  • 「持ち物」「噂フラグ」「王に会った」などを条件にできる

  • 会話・選択肢は showMsg / showChoice で統一


8. 3体撃破後に消える仕様(更新後で追加)

役割

  • 条件達成型NPCを「一度倒したら消える」存在にする

要所コード(抜粋)

function isNpcHidden(n){ if(n.active===false) return true; if(n.name==="ゴースト" && hasFlag("defeated_ghost")) return true; if(n.name==="骸骨剣士" && hasFlag("defeated_skeletonSword")) return true; if(n.name==="骸骨騎士" && hasFlag("defeated_skeletonKnight")) return true; return false; } function npcAt(st,x,y){ for(let n of npcs){ if(isNpcHidden(n)) continue; if(n.stage===st && n.x===x && n.y===y) return n; } return null; }

ポイント

  • 消える条件は「フラグ」または active=false

  • fieldpaint() のNPC描画にも同じ判定が効く


9. ボス戦:確認→カットシーン→撃破処理(更新後で追加)

役割

  • ボス前に「現在ランク」を表示して確認させる

  • A以上なら戦闘演出(複数ページ)→撃破フラグ→城へ戻す導線

要所コード(抜粋)

talk:function(){ if(hasFlag("bossCleared")){ showMsg("ダンジョンボス","……すでにこの部屋の主は消え去っている。"); this.active=false; return; } const eva=calcFinalEvaluation(); const rank=eva.rank, score=eva.score; showChoice("ダンジョンボス", "今の評価ランクは『"+rank+"』\nスコア:"+score+"\n本当に挑むか?", ["挑む","挑まない"],(i)=>{ if(i!==0){ showMsg("ダンジョンボス","……覚悟が定まった時に来るがよい。"); return; } const okRanks=["SSS","SS","S","A"]; if(!okRanks.includes(rank)){ showMsg("ダンジョンボス","……まだ力が足りぬ。\n(条件:A以上)"); return; } playSE("battle"); startCutscene("ボス戦カットシーン", pages, ()=>{ setFlag("bossCleared", true); this.active=false; playSE("clear"); showMsg("ダンジョンボス","……見事だ……\n城へ戻り、王に報告しよう。"); }); } ); }

ポイント

  • 条件不足でも“挑む”を選べるが、そこで止める設計

  • startCutscene() を使ってページ送り演出が可能


10. 王へ報告→エンディング(更新後で追加)

役割

  • ボス撃破後、王に話しかけることで「終わり」を出す

要所コード(抜粋)

if(hasFlag("bossCleared") && !hasFlag("endingShown")){ setFlag("endingShown",true); finalEvaluate(); return; }

ポイント

  • endingShown を入れて 二重発火を防止している


11. メニュー(右パネル表示+スクロール)(更新後で追加)

役割

  • Escでメニューを開き、仲間・アイテム・セーブなどを操作

  • 右パネルで仲間・アイテムを一覧表示し、左右キーでスクロール

要所コード(抜粋)

let partyScroll = 0; let itemScroll = 0; if(gameMode==='menu'){ if(code===37 || code===39){ const dir = (code===37)? -1 : 1; if(current==="仲間"){ const maxScroll = Math.max(0, party.length - maxLines); partyScroll = Math.max(0, Math.min(maxScroll, partyScroll + dir)); playSE("move"); fieldpaint(); return; } if(current==="アイテム"){ const maxScroll = Math.max(0, items.length - maxLines); itemScroll = Math.max(0, Math.min(maxScroll, itemScroll + dir)); playSE("move"); fieldpaint(); return; } } }

ポイント

  • maxLines を超えるとスクロールUIが表示される

  • スクロール操作にもSEを付けている


12. 描画(タイル・NPC・プレイヤー・UI)

役割

  • マップ→NPC→プレイヤー→UI(メッセージ/選択/メニュー)の順で描画

要所コード(抜粋)

function fieldpaint(){ if(gameMode==='title'){ drawTitle(); return; } for(y...) for(x...){ drawTile(t,x,y); } for(npcs...){ if(isNpcHidden(n)) continue; if(n.stage!==stage) continue; gc.drawImage(imgNPC[n.sprite], n.x*40, n.y*40, 40, 40); } if(onBoat){ gc.drawImage(boatImg, px*40, py*40, 40, 40); } else{ gc.drawImage(heroImg, px*40, py*40, 40, 40); } if(msgBox) drawMsg(msgBox); if(choiceBox) drawChoice(choiceBox); if(gameMode==='menu') drawMenu(); }

ポイント

  • NPC消滅判定が描画にも直結している

  • UIは常に最前面


13. 評価計算(SSS〜E)とボス条件(A以上)(更新後で追加)

役割

  • 仲間加入数+イベント達成フラグ数でスコア化

  • その比率からランクを決定(SSS〜E)

  • ボス戦の条件にランクを使用(A以上)

要所コード(抜粋)

function calcFinalEvaluation(){ const joinableMembers=[ "戦士A",...,"骸骨王" ]; const evalFlags=[ "bossCleared","skeletonKingDefeated","defeated_skeletonSword","defeated_ghost" ]; let memberScore=0; for(const n of joinableMembers){ if(inParty(n)) memberScore++; } let flagScore=0; for(const f of evalFlags){ if(hasFlag(f)) flagScore++; } const score = memberScore + flagScore; const maxScore = joinableMembers.length + evalFlags.length; const ratio = (maxScore>0)?(score/maxScore):0; let rank="E"; if(ratio>=0.95) rank="SSS"; else if(ratio>=0.85) rank="SS"; else if(ratio>=0.70) rank="S"; else if(ratio>=0.55) rank="A"; else if(ratio>=0.40) rank="B"; else if(ratio>=0.25) rank="C"; else if(ratio>=0.10) rank="D"; return {score, rank}; }

ポイント

  • 収集型ゲームとして「やり込みが可視化」される

  • bossCleared 自体も評価に入るので周回でも差が出る


まとめ

  • SEによる操作感

  • 仲間/アイテム管理の視認性(スクロール)

  • 条件達成型の敵

  • ボス戦演出(確認→カットシーン)

  • ボス→王→エンディングという物語構造

  • SSS〜Eの評価方式



参考文献・引用サイト

ぴぽや倉庫
ドット絵世界
DOT  ILLOST
効果音ラボ