カテゴリー: ゲーム開発
🎮 クロノクロス リメイク企画書(提案書)
🎮 クロノクロス リメイク企画書(提案書)
■ タイトル(仮)
CHRONO CROSS Re:Dreamers(クロノクロス リ・ドリーマーズ)
■ 開発目的
- 名作『クロノクロス』(1999年/PS)の世界観・物語・音楽を継承しつつ、現代の技術と表現力でフルリメイク。
- クロノシリーズの価値とブランドを再定義し、次世代のファンを獲得する。
- クロノ・トリガーから続く「時」と「次元」をテーマにした壮大な物語を、新たな感動体験として再構築。
■ ターゲット層
- 30〜40代:オリジナルファン(ノスタルジー層)
- 10〜20代:JRPG・アニメ調ゲームに興味がある若年層
- 世界市場向け:海外人気も高いため、グローバル対応必須(字幕・音声)
■ 主な特徴
| 項目 | 内容 |
|---|---|
| グラフィック | Unreal Engine 5を使用したセルルック風3D |
| サウンド | 全曲アレンジ+原曲切替可能/フルオーケストラ対応 |
| ボイス | 主要キャラクターにフルボイス対応(ON/OFF可) |
| UI | モダン+クラシック切替可能なデザイン |
| バトル | ターン制+リアルタイム演出のハイブリッドバトル |
| クロス要素 | 40人以上の仲間、選択によるマルチストーリー |
| 新要素 | 新規シナリオ分岐、外伝ストーリー、キャラエピソード |
■ ストーリー概要(簡易)
夢を旅する少年セルジュが、もう一つの世界で自らの存在が「死んでいたこと」を知る。
交錯する次元、因果のねじれ、「時を喰らうもの」によって歪められた歴史を、仲間たちとともに解き明かす物語。
『クロノ・トリガー』との繋がりも明確に描かれ、真実のエンディングへ導かれる。
■ プラットフォーム案
- PS5 / Xbox Series X|S / PC(Steam / Epic) / Nintendo Switch 2(次世代機を想定)
- クラウド対応 / Steam Deck対応予定
■ 追加要素・リメイク特有要素(例)
| 種別 | 内容 |
|---|---|
| DLC対応 | クロノトリガーエピソード、旧キャラコスチュームなど |
| クロスセーブ | 複数プラットフォームでの共有セーブ |
| ギャラリーモード | アート、BGM視聴、ボイス再生可能なコレクション |
| フォトモード | アングル調整+フィルターありの撮影機能 |
| 難易度設定 | イージー〜クラシック(敵の強化・MP制限など) |
■ 開発スケジュール案(例)
| 期間 | 内容 |
|---|---|
| Q1〜Q2 | プロトタイプ制作・初期アート制作 |
| Q3〜Q4 | メイン開発・音楽収録・シナリオ検証 |
| Q5〜Q6 | ベータ版、デバッグ、調整、プロモーション |
| Q7 | グローバルリリース(発売時期例:2027年冬) |
■ 予算感(概算・中規模プロジェクト)
- 総開発費:約15〜25億円(3年開発・UE5・全ボイス)
- 人員:50〜70名体制(内外注含む)
■ 参考資料
- クロノクロスHDリマスター(2022)
- ファイナルファンタジーVII リメイク
- ライブアライブHD-2D
- ゼノブレイドシリーズ(シナリオ設計・多人数管理)
■ 最後に
クロノクロスは「ゲーム音楽」「次元の物語」「美しいドットと詩的なセリフ」で多くのファンの心に残る名作。
本リメイクは、単なる懐古主義ではなく、「再構築」と「夢の継承」をテーマに、今の時代に語り直すことを目指す。
必要であれば、PDF書式の企画書風に整えることも可能だし、ゲーム画面のモックアップや仲間キャラ一覧風資料も作れるよ!
もっと深く踏み込みたい部分ある?(キャラ紹介・UI案・音楽面とか)
4o
RPG
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>レトロRPG</title>
<style>
body {
background: black;
color: white;
text-align: center;
font-family: monospace;
}
canvas {
border: 2px solid white;
background: #202020;
image-rendering: pixelated;
}
#ui {
margin-top: 10px;
}
</style>
</head>
<body>
<h1>レトロ風RPG</h1>
<canvas id="game" width="160" height="160"></canvas>
<div id="ui">
<p id="status">HP: 10</p>
<p id="log"></p>
</div>
<script>
const canvas = document.getElementById("game");
const ctx = canvas.getContext("2d");
const statusEl = document.getElementById("status");
const logEl = document.getElementById("log");
const tileSize = 16;
const map = [
[0, 0, 1, 0, 0, 0, 0, 0, 1, 0],
[1, 0, 1, 0, 1, 1, 0, 0, 1, 0],
[1, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[1, 1, 1, 1, 1, 0, 1, 1, 0, 1],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
[0, 1, 1, 0, 0, 0, 1, 1, 0, 0],
[0, 0, 1, 1, 1, 1, 0, 1, 1, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
];
const player = {
x: 0,
y: 0,
hp: 10,
color: "#ff0000"
};
const enemies = [
{ x: 4, y: 2, hp: 5, alive: true },
{ x: 8, y: 7, hp: 7, alive: true },
];
function drawMap() {
for (let y = 0; y < map.length; y++) {
for (let x = 0; x < map[0].length; x++) {
ctx.fillStyle = map[y][x] === 1 ? "#444" : "#88cc88";
ctx.fillRect(x * tileSize, y * tileSize, tileSize, tileSize);
}
}
}
function drawPlayer() {
ctx.fillStyle = player.color;
ctx.fillRect(player.x * tileSize, player.y * tileSize, tileSize, tileSize);
}
function drawEnemies() {
ctx.fillStyle = "#ffcc00";
enemies.forEach(enemy => {
if (enemy.alive) {
ctx.fillRect(enemy.x * tileSize, enemy.y * tileSize, tileSize, tileSize);
}
});
}
function canMove(x, y) {
return map[y] && map[y][x] === 0;
}
function updateUI() {
statusEl.textContent = `HP: ${player.hp}`;
}
function showLog(text) {
logEl.textContent = text;
}
function battle(enemy) {
showLog("戦闘開始!");
const battleInterval = setInterval(() => {
// プレイヤーの攻撃
let playerDmg = Math.floor(Math.random() * 3) + 1;
enemy.hp -= playerDmg;
showLog(`あなたの攻撃! 敵に${playerDmg}ダメージ!`);
if (enemy.hp <= 0) {
showLog("敵を倒した!");
enemy.alive = false;
clearInterval(battleInterval);
gameLoop();
return;
}
// 敵の攻撃
let enemyDmg = Math.floor(Math.random() * 3) + 1;
player.hp -= enemyDmg;
updateUI();
showLog(`敵の反撃! あなたは${enemyDmg}ダメージを受けた!`);
if (player.hp <= 0) {
showLog("あなたは倒れた… GAME OVER");
clearInterval(battleInterval);
document.removeEventListener("keydown", handleKey);
}
}, 1000);
}
function checkEnemy(x, y) {
for (let enemy of enemies) {
if (enemy.x === x && enemy.y === y && enemy.alive) {
battle(enemy);
return true;
}
}
return false;
}
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawMap();
drawEnemies();
drawPlayer();
updateUI();
}
function handleKey(e) {
let nx = player.x;
let ny = player.y;
if (e.key === "ArrowUp") ny--;
if (e.key === "ArrowDown") ny++;
if (e.key === "ArrowLeft") nx--;
if (e.key === "ArrowRight") nx++;
if (canMove(nx, ny)) {
player.x = nx;
player.y = ny;
if (!checkEnemy(nx, ny)) {
showLog(""); // 戦闘中じゃないならログを消す
}
}
gameLoop();
}
document.addEventListener("keydown", handleKey);
gameLoop();
</script>
</body>
</html>
ダンジョンのギミック
1. ダンジョンの構成要素
- 入口・出口
- プレイヤーがダンジョンに入るポイントと、クリアのために到達する出口。
- 部屋(Room)
- 敵との戦闘やイベントが起こる場所。
- 宝箱やNPCとの出会いが配置可能。
- 通路(Corridor)
- 部屋同士を繋ぐ経路。
- 単純な一本道、迷路、あるいは分岐などが考えられる。
- 罠(Trap)
- プレイヤーの行動を制限・妨害する仕掛け。
- 鍵や扉
- 特定のアイテムや条件を満たさないと先へ進めない仕掛け。
2. 敵の出現パターン
- 固定配置型
- 敵が決まった場所に存在。
- ランダムエンカウント型
- 移動時や特定条件でランダムに敵が出現。
- 条件トリガー型
- 宝箱を開けたりスイッチを押すと敵が出現。
3. アイテム配置ロジック
- 宝箱の配置
- 回復アイテムや装備、重要な鍵アイテムを適度に分散。
- ドロップアイテム
- 敵がランダムで落とすアイテム。
4. 探索と謎解き要素
- パズルギミック
- スイッチやレバー、順番通りに動かすパズルなど。
- ヒントの設置
- 壁画やメモ、NPCの会話などでプレイヤーに手がかりを与える。
5. 難易度調整ロジック
- ダンジョン階層
- 下に行くほど敵が強くなるような階層型。
- プレイヤーレベル連動
- プレイヤーのレベルに応じて敵や報酬の内容が変わる。
6. 報酬のロジック
- ボス討伐時の報酬
- 装備品、経験値、特別なスキル。
- クリア後のリプレイ性
- 特定条件を満たしてクリアすると追加報酬や新規ルートが解放。
C# Unity 3D RPG
QuestifyInfinity.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Questify Advanced Battle</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- レトロゲーム風フォント -->
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
<style>
/* ========== 全体 ========== */
body {
background-color: #1a1a1a;
color: #fff;
font-family: 'Press Start 2P', cursive;
margin: 0; padding: 0;
}
header, footer {
background: #333; text-align: center; padding: 15px; border-bottom: 2px solid #555;
}
header h1 { font-size: 1.3rem; margin: 0; }
main { max-width: 1200px; margin: 10px auto; padding-bottom: 80px; }
.box {
background: #2b2b2b; margin-bottom: 20px;
border: 2px solid #555; padding: 20px; position: relative;
}
.section-title { font-size: 1.2rem; margin-bottom: 10px; }
button {
font-family: 'Press Start 2P', cursive;
border: none; cursor: pointer; padding: 6px 10px; margin-right: 5px;
}
button:hover { opacity: 0.8; }
.btn-primary { background: #006699; color: #fff; }
.btn-delete { background: #bb3333; color: #fff; }
/* ステータス表示 */
.hero-info p { margin: 5px 0; }
.xp-bar {
background: #555; width: 100%; height: 20px; margin: 5px 0;
position: relative; overflow: hidden;
}
.xp-fill {
background: #00aa00; width: 0%; height: 100%;
transition: width 0.3s ease;
}
/* パーティ */
.party-member {
background: #333; border: 2px solid #555;
padding: 10px; margin-bottom: 10px;
}
/* スキルツリー */
.skill-tree .skill {
background: #333; border: 2px solid #555;
padding: 10px; margin-bottom: 10px;
}
/* 装備・素材 */
.equipment-info p { margin: 3px 0; }
.craft-recipe {
border: 1px dashed #999;
margin-bottom: 10px; padding: 5px;
}
/* クエスト */
.quest-list .quest {
background: #333; border: 2px solid #555;
padding: 10px; margin-bottom: 10px;
}
/* 実績 */
.achievements .achievement {
background: #333; border: 2px solid #555;
padding: 10px; margin-bottom: 10px; position: relative;
}
.achievement.locked { opacity: 0.5; }
.locked-label {
position: absolute; top: 5px; right: 5px;
background: #cc0000; padding: 3px 5px; font-size: 0.7rem;
}
/* バトルモーダル */
.modal-bg {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.8);
display: none; justify-content: center; align-items: center;
}
.modal {
background: #2b2b2b; border: 2px solid #555;
padding: 20px; max-width: 600px; width: 90%;
position: relative;
}
.close-btn {
position: absolute; top: 10px; right: 10px;
background: #dd3333; color: #fff; padding: 5px 8px; border: none;
}
.battle-enemy-info, .battle-hero-info {
margin-bottom: 10px;
}
#battleLog {
border: 1px solid #555;
min-height: 60px; padding: 5px;
margin: 10px 0; max-height: 200px; overflow-y: auto;
}
.battle-action-buttons { margin-top: 10px; }
.battle-action-buttons button { margin: 5px 5px 0 0; }
</style>
</head>
<body>
<header>
<h1>Questify Advanced Battle</h1>
</header>
<main>
<!-- ===================== ヒーロー & パーティステータス ===================== -->
<section class="box">
<h2 class="section-title">冒険者ステータス</h2>
<div class="hero-info">
<p>勇者: <span id="heroNameDisplay">No Name</span></p>
<p>職業: <span id="heroJobDisplay">未設定</span></p>
<p>
Lv.<span id="heroLevel">1</span>
HP:<span id="heroHp">?</span>/<span id="heroMaxHp">?</span>
</p>
<div class="xp-bar">
<div class="xp-fill" id="xpFill"></div>
</div>
<p>EXP: <span id="heroXp">0</span> / <span id="xpToNextLevel">100</span></p>
<p>Gold: <span id="heroGold">0</span></p>
<p>Skill Pts: <span id="heroSkillPts">0</span></p>
<label>勇者名: <input type="text" id="heroNameInput" placeholder="アルス" /></label>
<button onclick="changeHeroName()">変更</button>
</div>
</section>
<section class="box">
<h2 class="section-title">パーティメンバー</h2>
<div id="partyList"></div>
<button class="btn-primary" onclick="addPartyMember()">仲間を雇う (最大2人)</button>
</section>
<!-- ===================== スキルツリー ===================== -->
<section class="box">
<h2 class="section-title">職業 & スキルツリー</h2>
<p>
<button onclick="setJob('戦士')">戦士</button>
<button onclick="setJob('魔法使い')">魔法使い</button>
<button onclick="setJob('盗賊')">盗賊</button>
</p>
<div class="skill-tree" id="skillTree"></div>
</section>
<!-- ===================== 装備 & クラフト ===================== -->
<section class="box">
<h2 class="section-title">装備 & クラフト</h2>
<div class="equipment-info" id="heroEquipment"></div>
<h3>クラフトレシピ</h3>
<div id="craftContainer"></div>
</section>
<!-- ===================== クエスト (デイリー/通常/ウィークリー) ===================== -->
<section class="box">
<h2 class="section-title">クエスト</h2>
<div style="margin-bottom:10px;">
<button class="btn-primary" onclick="switchQuestTab('daily')">デイリー</button>
<button class="btn-primary" onclick="switchQuestTab('normal')">通常</button>
<button class="btn-primary" onclick="switchQuestTab('weekly')">ウィークリー</button>
</div>
<div id="dailyQuests" class="quest-list"></div>
<div id="normalQuests" class="quest-list" style="display:none;"></div>
<div id="weeklyQuests" class="quest-list" style="display:none;"></div>
</section>
<!-- ===================== マップ & ボス戦 ===================== -->
<section class="box">
<h2 class="section-title">ワールドマップ</h2>
<div id="mapAreaContainer"></div>
</section>
<!-- ===================== ランダムエンカウント ===================== -->
<section class="box">
<h2 class="section-title">ダンジョン潜入 (複数敵ランダムエンカウント)</h2>
<p>ボタンを押すと **複数の敵** が出る場合も!</p>
<button class="btn-primary" onclick="startRandomEncounter()">ダンジョンに潜る</button>
</section>
<!-- ===================== 実績 & ストーリー ===================== -->
<section class="box achievements">
<h2 class="section-title">実績(Achievements)</h2>
<div id="achievementList"></div>
</section>
<section class="box">
<h2 class="section-title">ストーリー進行</h2>
<div id="storyProgress"></div>
</section>
</main>
<footer>
<p>© 2025 Questify Advanced Battle</p>
</footer>
<!-- ========== 個別ターン制バトルモーダル ========== -->
<div class="modal-bg" id="battleModalBg">
<div class="modal" id="battleModal">
<button class="close-btn" onclick="closeBattleModal()">×</button>
<h2 id="battleTitle">バトル</h2>
<p id="battleDesc"></p>
<!-- 敵一覧表示 -->
<div class="battle-enemy-info" id="battleEnemyInfo"></div>
<!-- 味方一覧表示 -->
<div class="battle-hero-info" id="battleHeroInfo"></div>
<!-- 行動ログ -->
<div id="battleLog"></div>
<!-- プレイヤー操作ボタン (アクション選択) -->
<div class="battle-action-buttons" id="battleActionButtons">
<button onclick="chooseAttack()">攻撃</button>
<button onclick="chooseSkill()">スキル</button>
<button onclick="chooseItem()">アイテム</button>
<button onclick="chooseDefend()">防御</button>
<button onclick="chooseFlee()">逃げる</button>
</div>
</div>
</div>
<script>
/* =========================================
1) データ構造 & ローカルストレージ
========================================= */
const STORAGE_KEY = "questify_battle_data";
let gameData = {
hero: {
name: "No Name",
job: "未設定",
level: 1,
xp: 0,
gold: 0,
skillPts: 0,
hp: 50,
maxHp: 50,
speed: 8, // 追加: 素早さ
equipment: { weapon: null, armor: null, accessory: null },
materials: { wood: 0, ore: 0, magicCrystal: 0 },
consumables: { potion: 2 },
// アクティブスキル(例): 攻撃スキル
activeSkills: [
{ id: "slash", name: "パワースラッシュ", baseDamage: 15 },
// ここに追加スキルを増やす
]
},
party: [
// 仲間も speed を持つ、activeSkills を持つなど拡張可能
// { id, name, level, xp, hp, maxHp, speed, attack, equipment, activeSkills, ... }
],
// 職業ごとのパッシブスキル
skills: {
warrior: [
{ id: 1, name: "剣術熟練", level: 0, maxLevel: 5, desc: "攻撃力+2/Lv" },
{ id: 2, name: "体力増強", level: 0, maxLevel: 5, desc: "HP+10/Lv" }
],
mage: [
{ id: 1, name: "魔力増強", level: 0, maxLevel: 5, desc: "魔法攻撃力+3/Lv" },
{ id: 2, name: "精神集中", level: 0, maxLevel: 5, desc: "ボス戦追加ダメージ+5/Lv" }
],
thief: [
{ id: 1, name: "素早さ強化", level: 0, maxLevel: 5, desc: "攻撃力+2/Lv" },
{ id: 2, name: "ゴールド盗み", level: 0, maxLevel: 5, desc: "討伐時Gold+5/Lv" }
]
},
// クラフトレシピ & クエスト & マップ & 実績 等は前回コードと同じ
// …(省略なし、全て記載)…
craftingRecipes: [
{
id: 1,
result: { name: "回復薬", type: "consumable", itemKey: "potion", amount: 1, hpRestore: 30 },
materialsRequired: { wood: 1, magicCrystal: 1 },
desc: "木材1 & 魔力結晶1 で回復薬を1つ生成"
}
],
dailyQuests: [
{ id: 1, title: "【デイリー】部屋掃除", exp: 10, gold: 5, completed: false },
{ id: 2, title: "【デイリー】筋トレ15分", exp: 15, gold: 5, completed: false }
],
normalQuests: [
{ id: 1, title: "HTML/CSSの学習", exp: 20, gold: 10, completed: false },
{ id: 2, title: "ランニング30分", exp: 25, gold: 10, completed: false }
],
weeklyQuests: [
{ id: 1, title: "【ウィークリー】5日連続早起き", exp: 50, gold: 30, completed: false },
{ id: 2, title: "【ウィークリー】合計5時間の学習", exp: 60, gold: 40, completed: false }
],
mapAreas: [
{ id: 1, name: "街", levelReq: 1, desc: "安全な街", boss: null },
{ id: 2, name: "森", levelReq: 3, desc: "木材が手に入るかも", boss: { name: "森の主", hp: 60, maxHp: 60, speed: 6, atk: 12, rewardExp: 50, rewardGold: 30 } },
{ id: 3, name: "洞窟", levelReq: 5, desc: "鉱石が眠る", boss: { name: "ゴーレム", hp: 100, maxHp: 100, speed: 4, atk: 20, rewardExp: 100, rewardGold: 80 } },
{ id: 4, name: "魔王城", levelReq: 10, desc: "魔王が支配する城", boss: { name: "魔王", hp: 200, maxHp: 200, speed: 10, atk: 35, rewardExp: 300, rewardGold: 200 } }
],
achievements: [
{ id: 1, title: "初クエスト達成", desc: "クエストを1回完了", type: "questCount", target: 1, unlocked: false },
{ id: 2, title: "レベル10到達", desc: "Lv.10になる", type: "level", target: 10, unlocked: false },
{ id: 3, title: "森の主撃破", desc: "森の主を倒す", type: "bossKill", bossName: "森の主", unlocked: false },
{ id: 4, title: "魔王撃破", desc: "魔王を倒す", type: "bossKill", bossName: "魔王", unlocked: false },
{ id: 5, title: "金持ち", desc: "ゴールドが100を超える", type: "gold", target: 100, unlocked: false }
],
story: [
{ bossName: "森の主", text: "森の主を倒し、森に平穏が戻った…!" },
{ bossName: "ゴーレム", text: "洞窟のゴーレムを粉砕し、鉱山への道が開けた。" },
{ bossName: "魔王", text: "魔王を倒し、世界に平和が訪れた。あなたは真の勇者!" }
],
enemies: [
{ name: "森の狼", hp: 30, maxHp: 30, speed: 7, atk: 8, exp: 15, gold: 5, dropMat: { wood: 1 } },
{ name: "洞窟コウモリ", hp: 35, maxHp: 35, speed: 9, atk: 10, exp: 20, gold: 8, dropMat: { ore: 1 } },
{ name: "ゴブリン", hp: 40, maxHp: 40, speed: 5, atk: 12, exp: 25, gold: 10, dropMat: { wood: 1, ore: 1 } }
],
lastDailyReset: null,
lastWeeklyReset: null
};
function loadData() {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
gameData = JSON.parse(saved);
}
}
function saveData() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(gameData));
}
/* =========================================
2) 初期化処理
========================================= */
window.addEventListener("load", () => {
loadData();
dailyQuestResetCheck();
weeklyQuestResetCheck();
applySkillPassive();
updateAllUI();
});
/* =========================================
3) ユーティリティ & ステータス系
========================================= */
function xpNeededForLevel(level) {
return level * 100;
}
function checkLevelUp() {
while (gameData.hero.xp >= xpNeededForLevel(gameData.hero.level)) {
gameData.hero.xp -= xpNeededForLevel(gameData.hero.level);
gameData.hero.level++;
gameData.hero.skillPts++;
applySkillPassive();
alert(`レベルアップ!Lv.${gameData.hero.level} になった。スキルポイント+1`);
}
}
function gainExp(amount) {
gameData.hero.xp += amount;
checkLevelUp();
saveData();
updateHeroUI();
checkAchievements();
}
function gainGold(amount) {
gameData.hero.gold += amount;
saveData();
updateHeroUI();
checkAchievements();
}
function updateHeroUI() {
const h = gameData.hero;
document.getElementById("heroNameDisplay").textContent = h.name;
document.getElementById("heroJobDisplay").textContent = h.job;
document.getElementById("heroLevel").textContent = h.level;
document.getElementById("heroHp").textContent = h.hp;
document.getElementById("heroMaxHp").textContent = h.maxHp;
document.getElementById("heroXp").textContent = h.xp;
document.getElementById("xpToNextLevel").textContent = xpNeededForLevel(h.level);
document.getElementById("heroGold").textContent = h.gold;
document.getElementById("heroSkillPts").textContent = h.skillPts;
const ratio = (h.xp / xpNeededForLevel(h.level)) * 100;
document.getElementById("xpFill").style.width = ratio + "%";
updateHeroEquipmentUI();
}
function changeHeroName() {
const input = document.getElementById("heroNameInput");
const newName = input.value.trim();
if (!newName) return;
gameData.hero.name = newName;
input.value = "";
saveData();
updateHeroUI();
}
function applySkillPassive() {
const hero = gameData.hero;
hero.maxHp = 50 + (hero.level - 1) * 5;
let skillList = [];
if (hero.job === "戦士") skillList = gameData.skills.warrior;
if (hero.job === "魔法使い") skillList = gameData.skills.mage;
if (hero.job === "盗賊") skillList = gameData.skills.thief;
skillList.forEach(s => {
if (s.name === "体力増強") {
hero.maxHp += (s.level * 10);
}
});
if (hero.hp > hero.maxHp) hero.hp = hero.maxHp;
}
/* =========================================
4) パーティ管理
========================================= */
function addPartyMember() {
if (gameData.party.length >= 2) {
alert("仲間は最大2人までです。");
return;
}
const newMem = {
id: Date.now(),
name: `仲間${gameData.party.length + 1}`,
level: 1,
xp: 0,
hp: 40,
maxHp: 40,
speed: 5,
attack: 5,
equipment: { weapon: null, armor: null, accessory: null },
activeSkills: [
// 例: 仲間専用スキルを入れたければここに
]
};
gameData.party.push(newMem);
alert(`${newMem.name} を雇いました!`);
saveData();
updateAllUI();
}
function updatePartyUI() {
const container = document.getElementById("partyList");
container.innerHTML = "";
if (gameData.party.length === 0) {
container.innerHTML = "<p>仲間はいません</p>";
return;
}
gameData.party.forEach(m => {
const div = document.createElement("div");
div.className = "party-member";
div.innerHTML = `<p>${m.name} (Lv.${m.level}) HP:${m.hp}/${m.maxHp} 攻:${m.attack}</p>`;
const leaveBtn = document.createElement("button");
leaveBtn.className = "btn-delete";
leaveBtn.textContent = "離脱";
leaveBtn.onclick = () => {
if (confirm(`${m.name}を外しますか?`)) {
gameData.party = gameData.party.filter(x => x.id !== m.id);
saveData();
updateAllUI();
}
};
div.appendChild(leaveBtn);
container.appendChild(div);
});
}
function distributePartyExp(amount) {
gameData.party.forEach(m => {
m.xp += amount;
while (m.xp >= m.level * 50) {
m.xp -= m.level * 50;
m.level++;
m.attack += 2;
m.maxHp += 5;
m.hp += 5;
}
});
}
/* =========================================
5) 職業 & スキルツリー
========================================= */
function setJob(job) {
gameData.hero.job = job;
alert(`職業を「${job}」に変更しました。`);
saveData();
applySkillPassive();
updateAllUI();
}
function updateSkillTreeUI() {
const container = document.getElementById("skillTree");
container.innerHTML = "";
const job = gameData.hero.job;
if (job === "未設定") {
container.innerHTML = "<p>職業を選択してください</p>";
return;
}
let skillList = [];
if (job === "戦士") skillList = gameData.skills.warrior;
if (job === "魔法使い") skillList = gameData.skills.mage;
if (job === "盗賊") skillList = gameData.skills.thief;
skillList.forEach(s => {
const skillDiv = document.createElement("div");
skillDiv.className = "skill";
skillDiv.innerHTML = `
<h4>${s.name} (Lv.${s.level}/${s.maxLevel})</h4>
<p>${s.desc}</p>
`;
const btn = document.createElement("button");
btn.className = "btn-primary";
if (s.level >= s.maxLevel) {
btn.textContent = "MAX";
btn.disabled = true;
} else {
btn.textContent = "強化";
btn.onclick = () => {
if (gameData.hero.skillPts <= 0) {
alert("スキルポイントが足りません");
return;
}
s.level++;
gameData.hero.skillPts--;
saveData();
applySkillPassive();
updateAllUI();
};
}
skillDiv.appendChild(btn);
container.appendChild(skillDiv);
});
}
/* =========================================
6) 装備 & クラフト(簡易化)
========================================= */
function updateHeroEquipmentUI() {
const eqDiv = document.getElementById("heroEquipment");
const h = gameData.hero;
eqDiv.innerHTML = `
<p>武器: ${h.equipment.weapon ? h.equipment.weapon.name : "なし"}</p>
<p>防具: ${h.equipment.armor ? h.equipment.armor.name : "なし"}</p>
<p>アクセ: ${h.equipment.accessory ? h.equipment.accessory.name : "なし"}</p>
<p>素材: 木材(${h.materials.wood}), 鉱石(${h.materials.ore}), 結晶(${h.materials.magicCrystal})</p>
<p>回復薬: ${h.consumables.potion || 0}個</p>
`;
}
function updateCraftUI() {
const cDiv = document.getElementById("craftContainer");
cDiv.innerHTML = "";
gameData.craftingRecipes.forEach(r => {
const div = document.createElement("div");
div.className = "craft-recipe";
let matText = Object.keys(r.materialsRequired).map(m => {
return `${m}:${r.materialsRequired[m]}`;
}).join(", ");
div.innerHTML = `
<strong>${r.result.name}</strong> → 必要素材 ${matText}<br>
${r.desc}
`;
const btn = document.createElement("button");
btn.textContent = "クラフト";
btn.onclick = () => craftItem(r);
div.appendChild(btn);
cDiv.appendChild(div);
});
}
function craftItem(recipe) {
for (let mat in recipe.materialsRequired) {
if ((gameData.hero.materials[mat] || 0) < recipe.materialsRequired[mat]) {
alert("素材が足りません!");
return;
}
}
for (let mat in recipe.materialsRequired) {
gameData.hero.materials[mat] -= recipe.materialsRequired[mat];
}
if (recipe.result.type === "consumable") {
const key = recipe.result.itemKey;
gameData.hero.consumables[key] = (gameData.hero.consumables[key] || 0) + recipe.result.amount;
alert(`${recipe.result.name} を${recipe.result.amount}個、作成しました!`);
}
saveData();
updateAllUI();
}
/* =========================================
7) クエスト関連 (デイリー/通常/ウィークリー)
========================================= */
function switchQuestTab(tab) {
document.getElementById("dailyQuests").style.display = (tab === "daily") ? "" : "none";
document.getElementById("normalQuests").style.display = (tab === "normal") ? "" : "none";
document.getElementById("weeklyQuests").style.display = (tab === "weekly") ? "" : "none";
}
function updateQuestsUI() {
renderQuestList("dailyQuests", gameData.dailyQuests, completeDailyQuest);
renderQuestList("normalQuests", gameData.normalQuests, completeNormalQuest);
renderQuestList("weeklyQuests", gameData.weeklyQuests, completeWeeklyQuest);
}
function renderQuestList(containerId, questArr, completeFn) {
const container = document.getElementById(containerId);
container.innerHTML = "";
questArr.forEach(q => {
const div = document.createElement("div");
div.className = "quest";
const h4 = document.createElement("h4");
h4.textContent = q.completed ? `【達成済】${q.title}` : q.title;
const p = document.createElement("p");
p.textContent = `EXP:${q.exp} Gold:${q.gold}`;
const btn = document.createElement("button");
btn.className = "btn-primary";
if (q.completed) {
btn.textContent = "完了済";
btn.disabled = true;
} else {
btn.textContent = "達成";
btn.onclick = () => completeFn(q.id);
}
div.appendChild(h4);
div.appendChild(p);
div.appendChild(btn);
container.appendChild(div);
});
}
function completeDailyQuest(id) {
const q = gameData.dailyQuests.find(x => x.id === id);
if (!q || q.completed) return;
q.completed = true;
alert(`${q.title} を達成!\nEXP+${q.exp}, Gold+${q.gold}`);
gainExp(q.exp);
gainGold(q.gold);
saveData();
updateAllUI();
}
function completeNormalQuest(id) {
const q = gameData.normalQuests.find(x => x.id === id);
if (!q || q.completed) return;
q.completed = true;
alert(`${q.title} を達成!\nEXP+${q.exp}, Gold+${q.gold}`);
gainExp(q.exp);
gainGold(q.gold);
saveData();
updateAllUI();
}
function completeWeeklyQuest(id) {
const q = gameData.weeklyQuests.find(x => x.id === id);
if (!q || q.completed) return;
q.completed = true;
alert(`${q.title} を達成!\nEXP+${q.exp}, Gold+${q.gold}`);
gainExp(q.exp);
gainGold(q.gold);
saveData();
updateAllUI();
}
function dailyQuestResetCheck() {
const now = new Date();
const todayStr = now.toDateString();
if (gameData.lastDailyReset !== todayStr) {
gameData.dailyQuests.forEach(q => q.completed = false);
gameData.lastDailyReset = todayStr;
alert("デイリークエストをリセットしました!");
saveData();
}
}
function weeklyQuestResetCheck() {
const now = new Date();
const year = now.getFullYear();
const weekNum = Math.floor((now.getDate() - now.getDay() + 10) / 7);
const currentWeekKey = `${year}-W${weekNum}`;
if (gameData.lastWeeklyReset !== currentWeekKey) {
gameData.weeklyQuests.forEach(q => q.completed = false);
gameData.lastWeeklyReset = currentWeekKey;
alert("ウィークリークエストをリセットしました!");
saveData();
}
}
/* =========================================
8) ワールドマップ & ボス(複数ターン制)
========================================= */
function updateMapUI() {
const container = document.getElementById("mapAreaContainer");
container.innerHTML = "";
const heroLevel = gameData.hero.level;
gameData.mapAreas.forEach(area => {
const areaDiv = document.createElement("div");
areaDiv.className = "map-area";
if (heroLevel < area.levelReq) {
areaDiv.classList.add("locked-area");
}
areaDiv.innerHTML = `<h3>${area.name} (Lv.${area.levelReq}~)</h3><p>${area.desc}</p>`;
if (area.boss && heroLevel >= area.levelReq) {
const btn = document.createElement("button");
btn.className = "btn-primary";
btn.textContent = `${area.boss.name} と戦う`;
btn.onclick = () => {
// ボスも enemies配列扱い
const boss = JSON.parse(JSON.stringify(area.boss));
startBattle([boss], `ボス戦: ${boss.name}`, true);
};
areaDiv.appendChild(btn);
}
container.appendChild(areaDiv);
});
}
/* =========================================
9) ランダムエンカウント (複数敵対応)
========================================= */
function startRandomEncounter() {
// ランダムに1~2体の敵を出す
const n = Math.random() < 0.5 ? 1 : 2;
let chosenEnemies = [];
for (let i = 0; i < n; i++) {
const e = gameData.enemies[Math.floor(Math.random() * gameData.enemies.length)];
chosenEnemies.push(JSON.parse(JSON.stringify(e)));
}
startBattle(chosenEnemies, "ダンジョン潜入 - 複数敵が出現!");
}
/* =========================================
10) 個別ターン制バトルロジック
========================================= */
let battleState = {
combatants: [], // 全ユニット(勇者/仲間/敵)
turnIndex: 0,
log: [],
battleOver: false,
isBossFight: false
};
function startBattle(enemyList, title, isBoss=false) {
battleState.combatants = [];
battleState.turnIndex = 0;
battleState.log = [];
battleState.battleOver = false;
battleState.isBossFight = isBoss;
// 味方(勇者)
const hero = gameData.hero;
battleState.combatants.push({
unitType: "hero",
name: hero.name,
hp: hero.hp,
maxHp: hero.maxHp,
speed: hero.speed,
isDown: false,
ref: hero,
activeSkills: hero.activeSkills || []
});
// 仲間
gameData.party.forEach(m => {
battleState.combatants.push({
unitType: "party",
name: m.name,
hp: m.hp,
maxHp: m.maxHp,
speed: m.speed || 5,
isDown: false,
ref: m,
activeSkills: m.activeSkills || []
});
});
// 敵
enemyList.forEach(e => {
battleState.combatants.push({
unitType: "enemy",
name: e.name,
hp: e.hp,
maxHp: e.maxHp,
speed: e.speed || 5,
atk: e.atk || 5,
exp: e.exp || 0,
gold: e.gold || 0,
dropMat: e.dropMat || null,
rewardExp: e.rewardExp,
rewardGold: e.rewardGold,
isDown: false
});
});
// ソート(速度降順)
battleState.combatants.sort((a,b) => b.speed - a.speed);
document.getElementById("battleTitle").textContent = title;
document.getElementById("battleDesc").textContent = isBoss ? "ボス戦だ!" : "敵が現れた!";
openBattleModal();
updateBattleUI();
battleState.log.push("バトル開始!");
// 行動者チェック
checkCurrentTurn();
}
function openBattleModal() {
document.getElementById("battleModalBg").style.display = "flex";
}
function closeBattleModal() {
document.getElementById("battleModalBg").style.display = "none";
updateAllUI();
}
function updateBattleUI() {
// 敵一覧
let enemyText = "<h3>敵ユニット</h3>";
battleState.combatants
.filter(c => c.unitType === "enemy")
.forEach(c => {
if (!c.isDown) {
enemyText += `<p>${c.name} HP:${c.hp}/${c.maxHp}</p>`;
} else {
enemyText += `<p>${c.name} (撃破)</p>`;
}
});
document.getElementById("battleEnemyInfo").innerHTML = enemyText;
// 味方一覧
let allyText = "<h3>味方ユニット</h3>";
battleState.combatants
.filter(c => c.unitType==="hero" || c.unitType==="party")
.forEach(c => {
if (!c.isDown) {
allyText += `<p>${c.name} HP:${c.hp}/${c.maxHp}</p>`;
} else {
allyText += `<p>${c.name} (戦闘不能)</p>`;
}
});
document.getElementById("battleHeroInfo").innerHTML = allyText;
// ログ
document.getElementById("battleLog").innerHTML = battleState.log.join("<br>");
}
/* 行動者確認 */
function checkCurrentTurn() {
if (battleState.battleOver) return;
if (battleState.turnIndex >= battleState.combatants.length) {
battleState.turnIndex = 0;
}
let currentUnit = battleState.combatants[battleState.turnIndex];
if (currentUnit.isDown) {
// 次へ
battleState.turnIndex++;
checkBattleEnd();
checkCurrentTurn();
return;
}
if (currentUnit.unitType === "hero" || currentUnit.unitType === "party") {
// プレイヤー(味方)行動
battleState.log.push(`[${currentUnit.name} のターン]`);
updateBattleUI();
// ボタン操作で行動を待つ
} else {
// 敵行動
battleState.log.push(`[${currentUnit.name} のターン(敵)]`);
updateBattleUI();
setTimeout(() => {
enemyAction(currentUnit);
}, 600);
}
}
/* プレイヤー行動系 */
// 今どのキャラが行動中か
function getCurrentUnit() {
if (battleState.turnIndex < battleState.combatants.length) {
return battleState.combatants[battleState.turnIndex];
}
return null;
}
// 攻撃
function chooseAttack() {
const currentUnit = getCurrentUnit();
if (!currentUnit) return;
if (currentUnit.unitType==="enemy") return; // 敵は自動行動
// ターゲット: 生存している敵
const aliveEnemies = battleState.combatants.filter(c => c.unitType==="enemy" && !c.isDown);
if (aliveEnemies.length === 0) return; // 敵がいない
const target = aliveEnemies[0]; // 仮に先頭を攻撃(本当は選択UIを用意してもOK)
doAttack(currentUnit, target);
}
function doAttack(attacker, defender) {
battleState.log.push(`${attacker.name}の攻撃!`);
const dmg = calcDamage(attacker, defender, false);
defender.hp -= dmg;
battleState.log.push(`→ ${defender.name} に ${dmg} ダメージ!`);
if (defender.hp<=0) {
defender.hp=0;
defender.isDown=true;
battleState.log.push(`${defender.name}は倒れた!`);
}
endPlayerAction();
}
// スキル
function chooseSkill() {
const currentUnit = getCurrentUnit();
if (!currentUnit) return;
if (currentUnit.activeSkills.length === 0) {
alert("スキルがありません!");
return;
}
// 例: 1つだけスキルがあるなら即使用 or promptで選択
const skill = currentUnit.activeSkills[0];
// ターゲット選択(敵)
const aliveEnemies = battleState.combatants.filter(c => c.unitType==="enemy" && !c.isDown);
if (aliveEnemies.length === 0) {
alert("敵がいません");
return;
}
const target = aliveEnemies[0]; // 簡易: 先頭
battleState.log.push(`${currentUnit.name}は${skill.name}を発動!`);
const dmg = skill.baseDamage + Math.floor(Math.random()*3);
target.hp -= dmg;
battleState.log.push(`→ ${target.name} に ${dmg} ダメージ!`);
if (target.hp<=0) {
target.hp=0;
target.isDown=true;
battleState.log.push(`${target.name}は倒れた!`);
}
endPlayerAction();
}
// アイテム
function chooseItem() {
const currentUnit = getCurrentUnit();
if (!currentUnit) return;
if (currentUnit.unitType!=="hero") {
alert("このキャラはアイテムを使えません。");
return;
}
const hero = currentUnit.ref;
if ((hero.consumables.potion || 0) <1) {
alert("回復薬がありません。");
return;
}
// 対象を自分に限定(簡易)
hero.consumables.potion--;
const heal=30;
currentUnit.hp += heal;
if(currentUnit.hp>currentUnit.maxHp) currentUnit.hp=currentUnit.maxHp;
battleState.log.push(`${currentUnit.name}は回復薬を使用 → HP+${heal}`);
endPlayerAction();
}
// 防御
function chooseDefend() {
const currentUnit = getCurrentUnit();
if (!currentUnit) return;
currentUnit.isDefending=true; // 被ダメ半減など
battleState.log.push(`${currentUnit.name}は身を守っている!(被ダメ軽減)`);
endPlayerAction();
}
// 逃げる
function chooseFlee() {
battleState.log.push("逃げ出した!");
endBattle(false);
}
// プレイヤー行動終了
function endPlayerAction() {
battleState.turnIndex++;
checkBattleEnd();
setTimeout(() => {
checkCurrentTurn();
updateBattleUI();
}, 400);
}
// 敵行動
function enemyAction(enemyUnit) {
// ターゲット: 生存している味方( hero/party )
const aliveAllies = battleState.combatants.filter(c => (c.unitType==="hero"||c.unitType==="party") && !c.isDown);
if (aliveAllies.length===0) {
checkBattleEnd();
return;
}
const target = aliveAllies[Math.floor(Math.random()*aliveAllies.length)];
battleState.log.push(`${enemyUnit.name}の攻撃 → ${target.name}`);
const dmg = calcDamage(enemyUnit, target, target.isDefending);
target.hp -= dmg;
if (target.isDefending) {
battleState.log.push("(防御中で被ダメ軽減)");
}
battleState.log.push(`→ ${target.name}に${dmg}ダメージ!`);
target.isDefending=false; // 防御は1ターンのみ
if (target.hp<=0) {
target.hp=0;
target.isDown=true;
battleState.log.push(`${target.name}は倒れた…`);
}
battleState.turnIndex++;
checkBattleEnd();
setTimeout(() => {
checkCurrentTurn();
updateBattleUI();
}, 400);
}
/* ダメージ計算 */
function calcDamage(attacker, defender, defenderIsDefending) {
let baseAtk=0;
if (attacker.unitType==="hero"||attacker.unitType==="party") {
// 味方の攻撃力
// (装備や職業スキルなどは未細分化。下のcalcPlayerTotalAttackを簡易流用でもOK)
baseAtk=10;
if(attacker.ref && attacker.ref.job==="戦士") {
const wSkills=gameData.skills.warrior;
wSkills.forEach(s => {
if(s.name==="剣術熟練") baseAtk+=(s.level*2);
});
}
if(attacker.ref && attacker.ref.job==="魔法使い") {
const mSkills=gameData.skills.mage;
mSkills.forEach(s => {
if(s.name==="魔力増強") baseAtk+=(s.level*3);
if(s.name==="精神集中" && battleState.isBossFight) baseAtk+=(s.level*5);
});
}
if(attacker.ref && attacker.ref.job==="盗賊") {
const tSkills=gameData.skills.thief;
tSkills.forEach(s=>{
if(s.name==="素早さ強化") baseAtk+=(s.level*2);
});
}
// 装備
if(attacker.ref && attacker.ref.equipment.weapon) {
baseAtk += (attacker.ref.equipment.weapon.attack||0);
}
if(attacker.ref && attacker.ref.equipment.armor) {
baseAtk += (attacker.ref.equipment.armor.attack||0);
}
if(attacker.ref && attacker.ref.equipment.accessory) {
baseAtk += (attacker.ref.equipment.accessory.attack||0);
}
// さらに仲間の場合はattackプロパティ?
if(attacker.unitType==="party") {
baseAtk += attacker.ref.attack; // m.attack
}
} else {
// 敵
baseAtk=attacker.atk||5;
}
// 防御中なら半減
let finalDmg = baseAtk + Math.floor(Math.random()*3);
if(defenderIsDefending) {
finalDmg = Math.floor(finalDmg/2);
}
return finalDmg;
}
/* 勝利/敗北判定 */
function checkBattleEnd() {
// 味方生存
const aliveAllies = battleState.combatants.filter(c => (c.unitType==="hero"||c.unitType==="party") && !c.isDown);
if(aliveAllies.length===0) {
// 敗北
battleState.log.push("味方は全滅した…");
doLoseBattle();
return true;
}
// 敵生存
const aliveEnemies= battleState.combatants.filter(c => c.unitType==="enemy" && !c.isDown);
if(aliveEnemies.length===0) {
// 勝利
battleState.log.push("敵を全て倒した! 勝利!");
doWinBattle();
return true;
}
return false;
}
function doWinBattle() {
battleState.battleOver=true;
// ボス or 雑魚敵全体のEXP/Gold
let totalExp=0, totalGold=0;
battleState.combatants.forEach(c=>{
if(c.unitType==="enemy") {
if(c.rewardExp) totalExp += c.rewardExp; else totalExp += (c.exp||0);
if(c.rewardGold) totalGold += c.rewardGold; else totalGold += (c.gold||0);
}
});
// 盗賊スキルでGoldボーナス
if(gameData.hero.job==="盗賊"){
const tSkills=gameData.skills.thief;
tSkills.forEach(s=>{
if(s.name==="ゴールド盗み") {
totalGold+=(s.level*5);
}
});
}
gainExp(totalExp);
gainGold(totalGold);
battleState.log.push(`報酬 → EXP:${totalExp}, Gold:${totalGold}`);
// 倒れた味方をHP1で復帰
battleState.combatants.forEach(c=>{
if((c.unitType==="hero"||c.unitType==="party") && c.isDown){
c.isDown=false; c.hp=1;
if(c.ref) c.ref.hp=1;
} else {
if(c.ref) c.ref.hp=c.hp; // HPを反映
}
});
setTimeout(()=>{
endBattle(true);
},800);
}
function doLoseBattle() {
battleState.battleOver=true;
// ゴールド半減
gameData.hero.gold = Math.floor(gameData.hero.gold/2);
battleState.log.push("所持Goldが半分になった…");
// 全員HP1で復帰
battleState.combatants.forEach(c=>{
if(c.unitType==="hero"||c.unitType==="party"){
c.isDown=false;
c.hp=1;
if(c.ref) c.ref.hp=1;
}
});
setTimeout(()=>{
endBattle(false);
},800);
}
function endBattle(win) {
battleState.log.push(win ? "(勝利) バトル終了" : "(終了) バトル終了");
updateBattleUI();
setTimeout(()=>{
closeBattleModal();
},1000);
}
function calcPlayerTotalAttack(){
// ※ 旧の一斉攻撃計算用は使わなくなったが、参考に残しておく
return 10;
}
/* ボス撃破時の実績 & ストーリー */
function onBossDefeated(bossName) {
gameData.achievements.forEach(a => {
if(a.type==="bossKill" && a.bossName===bossName && !a.unlocked){
unlockAchievement(a);
}
});
const st= gameData.story.find(x=>x.bossName===bossName);
if(st){
battleState.log.push(st.text);
}
saveData();
}
/* =========================================
11) 実績 & ストーリー
========================================= */
function updateAchievementsUI() {
const listEl = document.getElementById("achievementList");
listEl.innerHTML = "";
gameData.achievements.forEach(a => {
const div = document.createElement("div");
div.className = "achievement";
if(!a.unlocked) div.classList.add("locked");
const h4 = document.createElement("h4");
h4.textContent = a.title;
const p = document.createElement("p");
p.textContent = a.desc;
if(!a.unlocked){
const lockedLabel=document.createElement("div");
lockedLabel.className="locked-label";
lockedLabel.textContent="Locked";
div.appendChild(lockedLabel);
}
div.appendChild(h4);
div.appendChild(p);
listEl.appendChild(div);
});
}
function checkAchievements() {
const hero=gameData.hero;
const totalQuestCompleted= gameData.dailyQuests.filter(q=>q.completed).length
+ gameData.normalQuests.filter(q=>q.completed).length
+ gameData.weeklyQuests.filter(q=>q.completed).length;
gameData.achievements.forEach(a=>{
if(a.unlocked) return;
switch(a.type){
case "questCount":
if(totalQuestCompleted>=a.target){
unlockAchievement(a);
}
break;
case "level":
if(hero.level>=a.target){
unlockAchievement(a);
}
break;
case "bossKill":
// boss討伐時に individually check
break;
case "gold":
if(hero.gold>=a.target){
unlockAchievement(a);
}
break;
}
});
}
function unlockAchievement(a) {
a.unlocked=true;
alert(`実績解除!「${a.title}」`);
saveData();
updateAchievementsUI();
}
function updateStoryProgress(){
let text="";
const bossKills= gameData.achievements.filter(a=>a.type==="bossKill"&&a.unlocked).map(a=>a.bossName);
bossKills.forEach(bn=>{
const st=gameData.story.find(x=>x.bossName===bn);
if(st){
text+=`<p>【${bn}】<br>${st.text}</p>`;
}
});
if(!text) text="<p>まだ大きなストーリーは進んでいません。</p>";
document.getElementById("storyProgress").innerHTML=text;
}
/* =========================================
12) UIの一括更新
========================================= */
function updateAllUI(){
updateHeroUI();
updatePartyUI();
updateSkillTreeUI();
updateCraftUI();
updateQuestsUI();
updateMapUI();
updateAchievementsUI();
updateStoryProgress();
}
</script>
</body>
</html>
ブロック崩し
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Breakout Game</title>
<style>
/* 画面中央にキャンバスを表示 */
canvas {
background: #eee;
display: block;
margin: 30px auto;
border: 1px solid #333;
}
</style>
</head>
<body>
<canvas id="myCanvas" width="480" height="320"></canvas>
<script>
// キャンバスとコンテキストを取得
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
// ボールの設定
let x = canvas.width / 2; // ボールの初期位置 X
let y = canvas.height - 30; // ボールの初期位置 Y
const ballRadius = 8; // ボールの半径
let dx = 2; // ボールの移動速度 X方向
let dy = -2; // ボールの移動速度 Y方向
// パドルの設定
const paddleHeight = 10;
const paddleWidth = 75;
let paddleX = (canvas.width - paddleWidth) / 2; // パドルの初期位置
let rightPressed = false; // 右キーが押されているか
let leftPressed = false; // 左キーが押されているか
// ブロック(レンガ)の設定
const brickRowCount = 5; // 行数
const brickColumnCount = 7; // 列数
const brickWidth = 50; // 幅
const brickHeight = 20; // 高さ
const brickPadding = 10; // ブロック間の余白
const brickOffsetTop = 30; // 画面上端からのオフセット
const brickOffsetLeft = 30; // 画面左端からのオフセット
// スコアやライフ
let score = 0;
let lives = 3; // 残機
// ブロックを格納する2次元配列
let bricks = [];
for(let c = 0; c < brickColumnCount; c++) {
bricks[c] = [];
for(let r = 0; r < brickRowCount; r++) {
// x, yは後で計算するので、とりあえずステータスだけ持たせておく
bricks[c][r] = { x: 0, y: 0, status: 1 };
}
}
// キーボードイベントのリスナー登録
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);
function keyDownHandler(e) {
if(e.key === "Right" || e.key === "ArrowRight") {
rightPressed = true;
}
else if(e.key === "Left" || e.key === "ArrowLeft") {
leftPressed = true;
}
}
function keyUpHandler(e) {
if(e.key === "Right" || e.key === "ArrowRight") {
rightPressed = false;
}
else if(e.key === "Left" || e.key === "ArrowLeft") {
leftPressed = false;
}
}
// ブロックとボールの当たり判定
function collisionDetection() {
for(let c = 0; c < brickColumnCount; c++) {
for(let r = 0; r < brickRowCount; r++) {
let b = bricks[c][r];
// status=1 のブロックだけ当たり判定をする
if(b.status === 1) {
if(
x > b.x &&
x < b.x + brickWidth &&
y > b.y &&
y < b.y + brickHeight
) {
dy = -dy;
b.status = 0; // ブロックを消す
score++;
// 全ブロック破壊 → クリア
if(score === brickRowCount * brickColumnCount) {
alert("YOU WIN, CONGRATS!");
document.location.reload(); // ページをリロードして再開
}
}
}
}
}
}
// ボールを描画
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
// パドルを描画
function drawPaddle() {
ctx.beginPath();
ctx.rect(paddleX, canvas.height - paddleHeight - 5, paddleWidth, paddleHeight);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
// ブロックを描画
function drawBricks() {
for(let c = 0; c < brickColumnCount; c++) {
for(let r = 0; r < brickRowCount; r++) {
if(bricks[c][r].status === 1) {
let brickX = (c * (brickWidth + brickPadding)) + brickOffsetLeft;
let brickY = (r * (brickHeight + brickPadding)) + brickOffsetTop;
bricks[c][r].x = brickX;
bricks[c][r].y = brickY;
ctx.beginPath();
ctx.rect(brickX, brickY, brickWidth, brickHeight);
ctx.fillStyle = "#6CBE47";
ctx.fill();
ctx.closePath();
}
}
}
}
// スコアを描画
function drawScore() {
ctx.font = "16px Arial";
ctx.fillStyle = "#333";
ctx.fillText("Score: " + score, 8, 20);
}
// ライフを描画
function drawLives() {
ctx.font = "16px Arial";
ctx.fillStyle = "#333";
ctx.fillText("Lives: " + lives, canvas.width - 65, 20);
}
// 毎フレーム呼び出して描画&更新するメイン関数
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 各パーツの描画
drawBricks();
drawBall();
drawPaddle();
drawScore();
drawLives();
collisionDetection();
// ボールを左右の壁で反射
if(x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
dx = -dx;
}
// 上壁で反射
if(y + dy < ballRadius) {
dy = -dy;
}
// 下に落ちたらライフを1減らしてリセット
else if(y + dy > canvas.height - ballRadius) {
// パドルの範囲内かどうか
if(x > paddleX && x < paddleX + paddleWidth) {
// パドルに当たったらはね返す
dy = -dy;
} else {
// ミス → ライフ減少
lives--;
if(!lives) {
// ライフ0 → ゲームオーバー
alert("GAME OVER");
document.location.reload();
} else {
// ボールとパドルを初期位置へ
x = canvas.width / 2;
y = canvas.height - 30;
dx = 2;
dy = -2;
paddleX = (canvas.width - paddleWidth) / 2;
}
}
}
// ボール位置の更新
x += dx;
y += dy;
// パドルの操作
if(rightPressed && paddleX < canvas.width - paddleWidth) {
paddleX += 7;
} else if(leftPressed && paddleX > 0) {
paddleX -= 7;
}
requestAnimationFrame(draw);
}
// ゲーム開始
draw();
</script>
</body>
</html>
ペルソナ6 企画書
ペルソナ6 企画書
1. プロジェクト概要
- プロジェクト名: ペルソナ6
- ジャンル: JRPG (Japanese Role-Playing Game)
- ターゲットプラットフォーム: PlayStation 5, PC (Steam/Epic Games Store),その他次世代コンソール
- ターゲットユーザー: シリーズファン、RPG愛好者、新規ユーザー層(20代~40代)
2. プロジェクトの目的
- 「ペルソナ」シリーズの世界観を制断しつつ、復制的な要素を用いて新しいファン層にも反応するゲーム体験を提供する。
- 深い社会テーマや人間関係を詰め込んだストーリーテリングを実現。
- 新革的なゲームシステムとビジュアルで、シリーズの次の進化を提示。
3. コンセプト
- テーマ: 「選択と責任」
- キーワード: 多重人格、社会コンフリクト、幸福の定義
- ターゲット: 実世界と想像世界の分裂、個人のアイデンティティと社会のルールの衝突
4. ストーリー概要
- 若者達が、個人の問題や社会問題を中心に、ペルソナを駐らせ、想像世界での戦いを通して我々の社会の問題を問いかける。
- 主人公と不思議なペルソナ達との関係の構築。
- 大部分が新しい大部事件で構成される一方、これまでの作品の世界観の続きもさりげなく組み込み、シリーズファンの期待に対応。
5. ゲームシステム
- ダンジョンシステム
- コンフィグとストイリーベースの自由な選択を重要視。
- 毎日の生活と戦闘の構成のバランスを改善。
- ペルソナシステム
- 主人公の想像を反映する個性を持つペルソナを検索。
- ペルソナのカスタマイズ可能な進化、新たなスキルの実装。
- 世界設定システム
- 実世界の「社会」と想像世界の「サブワールド」の分裂を描く。
- 想像世界の裏に与えられた意味深い設定を検討。
6. ビジュアルスタイル
- アニメ風のキャラクターデザイン、実写風エフェクトとのバランスを持たせる。
- 光と影の表現を重視したビジュアル。
7. 開発スケジュール
- 開発期間: 3年規模
- 開発チーム: プロデューサー、ディレクター、アーティスト、シナリオライター、デバッグチーム
8. 予想売上高
- 日本内: 150万本
- 国外: 300万本
9. 総括とメッセージ
Persona 6は、新たな社会テーマを描き出し、それによって主人公たちが自分たちの存在意義を問う。これは現代社会を我々に問いかける作品として、新たな次元を提示する。
「ゴエモンリメイク」企画書
「ゴエモンリメイク」企画書
タイトル
ゴエモンリメイク (仮タイトル)
概要
「ゴエモン」シリーズを現代のゲームプレイ環境に合わせてリメイクする企画。原作の魅力を残しながら、グラフィックとゲームシステムを現代化し、新たなプレイヤーや世代に展開。
目的
- 原作ファンの幸福感を増大させる。
- 新たなファン層の開拓。
- シリーズのブランドリフィング。
ターゲットオーディエンス
- 原作のファン
- レトロゲーマー
- 若年層
- 和風世界観が好きな人
ゲームジャンル
- アクションゲーム ハイテンポの探索やダイナミックアクションを重視。
- ハイフンディエンスアクション テンポを駆使した手験性の高いアクション。
要素
- 新しいグラフィックデザイン 原作の和風感を残しながら、高解像度や現代的エッセンスを解釈。
- コオプレイモード ローカルマルチプレイとオンラインマルチプレイに対応。
- シリーズ企画
- 原作をフルリメイク。
- 新規ストーリーの追加。
- 付加要素 オリジナルゲーム音楽のリプリント。
スケジュール
- 開発階段とスケジュール
- 企画フェース
- デザインフェース
- 開発
- テスト
- リリース
- 予定開発期間 18ヶ月
カバーター
- ゴエモン 新たな3Dモデルで再現。
- エビゾン コミック作品やキャラを重視。
- 新キャラクターを追加
市場調査と要望
- 原作ファンからの強い要望。
- 和風ゲームの展開に希望。
計画予算
- 開発費
- マーケティング費
- オープンワールド開発費
結論
「ゴエモン」リメイクは、現代のゲームプレイやグラフィックデザインに適応させながら、原作の魅力を残す作品として展開。
ファイナルファンタジー17 (仮) 企画書
プロジェクト概要
- タイトル: ファイナルファンタジーXVII (仮)
- ジャンル: オープンワールドRPG
- プラットフォーム: PlayStation 5 / Xbox Series X|S / PC
- テーマ: 「希望と喪失の調和」
- 開発期間: 約3~4年
- 販売目標: 1000万本以上
1. ゲームコンセプト
- ファイナルファンタジーシリーズの伝統を継承しながら、新しいプレイ体験を提供。
- シリーズ初の完全オープンワールドで、プレイヤーが自由に探索できる世界を構築。
- ストーリーとプレイの選択肢がプレイヤーの選択によって変化する「動的ナラティブシステム」を導入。
- 革新的なリアルタイム戦闘システムとクラシックRPGの要素を融合。
2. ストーリー概要
- 舞台: 滅びゆく惑星「ルシフェリア」。過去に大いなる文明を築いたが、今では自然と機械の均衡が崩れ、終末が訪れつつある。
- 主人公:
- 名前: リアム(Liam)
- 選ばれし「調和者」として、世界の崩壊を止める使命を持つ青年。
- 相棒: アルティナ(Altina)
- 謎のクリスタル生命体で、過去と未来の記憶を宿している。
- 名前: リアム(Liam)
- 敵対勢力:
- 「終焉の王」ゼノリス(Xenolis)
- 世界の終焉を望む強大な存在。
- 人間の欲望による勢力間の争いも物語の重要なテーマ。
- 「終焉の王」ゼノリス(Xenolis)
- テーマ:
- 人間と自然、機械の共存。
- 喪失を抱えながらも希望を見つける旅。
3. ゲームシステム
3.1. 戦闘システム
- リアルタイムアクションバトル:
- プレイヤーが自由に操作できるキャラクター切り替え機能。
- 魔法や召喚獣(エイドロン)が戦闘の戦略に影響。
- 戦略性:
- 戦闘中に時間を止めて指示を出せる「タイムスローモード」を搭載。
- 魔法のクラフトシステムで、自分だけの魔法を作成可能。
3.2. オープンワールド
- 多種多様な地形やエリア(雪山、砂漠、機械都市、古代遺跡など)。
- サイドクエストでNPCのバックストーリーや世界の謎を解明。
- ダイナミックな昼夜サイクルと天候変化。
3.3. キャラクター育成
- クリスタルスフィアシステムでスキルと能力を自由にカスタマイズ。
- 武器や防具のクラフト、召喚獣の進化など多彩な育成要素。
4. ビジュアル・アート
- 壮大で美麗なグラフィックを採用。
- 古代の文明と未来的な機械技術が融合したアートスタイル。
- 映画のようなカメラワークとシネマティックシーン。
5. 音楽
- 作曲: 植松伸夫氏を中心としたチーム。
- テーマソングはオーケストラとボーカルの融合。
- 各エリアやキャラクターに専用の音楽を用意し、没入感を強化。
6. マーケティングプラン
- 発表時に印象的なティザートレーラーを公開。
- 大型イベント(E3やTGS)での体験デモ。
- コレクターズエディション、デジタル特典付きパッケージ販売。
7. 開発スケジュール
- プリプロダクション: 1年
- コンセプトアート、ゲームデザイン、技術検証。
- プロダクション: 2年
- メインストーリー、オープンワールド、戦闘システムの構築。
- テストフェーズ: 1年
- デバッグ、バランス調整、ユーザーテスト。
- ローンチ: 2028年予定。
8. まとめ
ファイナルファンタジー17は、シリーズの革新と伝統の調和を目指し、世界中のプレイヤーに愛されるタイトルを目標とします。この新たな冒険が、FFファンの記憶に残る伝説となることを約束します。
