リクエスト詳細
✨ 既存アプリの改善
対応完了
対象アプリ: RPGストーリーフォージ AI風ドット絵冒険
戦闘・マップ画面にアイテムドロップ演出とモンスター撃破後のルート獲得ログを追加
## 1. 目的
現在、モンスターを倒してもゴールド・EXP獲得はログテキストに流れるだけで、視覚的な達成感が薄い。戦闘終了時に「アイテムドロップ」「ゴールド獲得」「EXP獲得」を専用のポップアップウィンドウ(DQ風「せんとうのせいか」ウィンドウ)でCanvas上に表示し、プレイヤーがリザルトを確認してから次に進めるようにする。これにより戦闘の満足感と中毒性が大幅に向上する。
## 2. 具体的な仕様
### 2-1. 戦闘リザルトウィンドウ(JS/Canvas側)
- 敵撃破時(`status==='win'`)に、既存の戦闘Canvasの手前にDOMオーバーレイとして `<div id="rpgsf-battle-result">` を表示する
- ウィンドウデザイン: SFC風の黒背景+金枠、ピクセルフォント
- 表示内容(PHP側から `data-result` 属性に JSON で渡す):
```
✨ せんとうのせいか ✨
──────────────────
EXP + {exp} ポイント
GOLD + {gold} ゴールド
{アイテム名} を手に入れた! ← ドロップがある場合のみ
──────────────────
[ つぎへ ]
```
- 「つぎへ」ボタンを押すとウィンドウが閉じ、通常のマップ画面に戻る
- スマホ対応: タップで閉じられるよう `touchstart` にも対応
- アニメーション: ウィンドウが下からスライドインし、各行が0.1秒ずつ遅延してフェードイン(CSS `@keyframes`で実装)
- EXP行にはミニゲージ(横幅100px程度の細いバー)をインラインで表示し、獲得後のEXP割合まで伸びるアニメーションを付ける
### 2-2. アイテムドロップ判定(PHP側 `lib.php` の `rpgsf_apply_action` 内)
- 既存の勝利判定ロジック内に、モンスターのタグに応じたドロップテーブルを追加する
```php
$drop_table = [
'slime' => ['やくそう', null, null], // 1/3 でやくそう
'bat' => ['どくけし', null, null],
'skeleton' => ['てつのやり', null, null],
'dragon' => ['ドラゴンのうろこ', 'ほのおのつるぎ', null],
// ...モンスターIDをキーに最大3候補をランダム抽選
];
$drops = $drop_table[$enemy_id] ?? [null, null, null];
$drop = $drops[array_rand($drops)];
if ($drop) {
$state['bag'][] = $drop;
$state['last_drop'] = $drop;
} else {
$state['last_drop'] = null;
}
```
- `$state['battle_result']` に `['exp'=>int, 'gold'=>int, 'drop'=>string|null]` を格納し、次のGETリダイレクト後のページで参照できるようセッション or stateに保持
- 既存の `battle_fx` セッション機構に `result` キーを追加して渡す:
```php
$_SESSION['battle_fx']['result'] = $state['battle_result'] ?? null;
```
### 2-3. PHP → JS へのデータ受け渡し(`pages/play.php`)
- `$battle_fx['result']` が存在する場合、`play.php` の `<div id="rpgsf-game">` などにデータ属性で渡す:
```php
$result_json = isset($battle_fx['result'])
? h(json_encode($battle_fx['result'], JSON_UNESCAPED_UNICODE))
: '';
```
```html
<div id="rpgsf-battle-result-data" data-result="<?= $result_json ?>" hidden></div>
```
- JS起動時にこの要素を読み取り、`data-result` が空でなければリザルトウィンドウを表示する
### 2-4. HTML/CSS(`play.php` 末尾 `<style>` ブロックに追記)
```css
#rpgsf-battle-result {
position: fixed;
bottom: 80px;
left: 50%;
transform: translateX(-50%) translateY(40px);
background: #0a0a0f;
border: 3px solid #d4af37;
box-shadow: 4px 4px 0 #000;
padding: 18px 28px;
min-width: 280px;
max-width: 90vw;
font-family: 'Press Start 2P', monospace, sans-serif;
font-size: 0.78rem;
color: #f0e6c8;
z-index: 9000;
animation: resultSlideIn .35s ease forwards;
line-height: 2;
}
@keyframes resultSlideIn {
from { opacity:0; transform: translateX(-50%) translateY(60px); }
to { opacity:1; transform: translateX(-50%) translateY(0); }
}
#rpgsf-battle-result .result-row { opacity:0; animation: fadeRow .3s ease forwards; }
#rpgsf-battle-result .result-row:nth-child(1){animation-delay:.1s}
#rpgsf-battle-result .result-row:nth-child(2){animation-delay:.25s}
#rpgsf-battle-result .result-row:nth-child(3){animation-delay:.4s}
#rpgsf-battle-result .result-row:nth-child(4){animation-delay:.55s}
@keyframes fadeRow { to { opacity:1; } }
#rpgsf-battle-result .exp-bar-wrap { display:inline-block; width:100px; height:6px; background:#333; vertical-align:middle; border:1px solid #555; margin-left:8px; }
#rpgsf-battle-result .exp-bar-fill { height:100%; background:#4fc; width:0; transition: width 1s ease .6s; }
#rpgsf-battle-result-btn { margin-top:14px; display:block; width:100%; background:#1a1a2e; border:2px solid #d4af37; color:#d4af37; padding:8px; cursor:pointer; font-size:.75rem; }
#rpgsf-battle-result-btn:hover { background:#d4af37; color:#000; }
```
### 2-5. JavaScript
```js
(function(){
var el = document.getElementById('rpgsf-battle-result-data');
if (!el) return;
var raw = el.getAttribute('data-result');
if (!raw) return;
var r;
try { r = JSON.parse(raw); } catch(e){ return; }
// ウィンドウ構築
var win = document.createElement('div');
win.id = 'rpgsf-battle-result';
var expPct = r.exp_pct || 0; // PHP側で計算して渡す
win.innerHTML = [
'<div class="result-row" style="color:#ffd700;text-align:center">✨ せんとうのせいか ✨</div>',
'<div class="result-row">EXP <b>+' + r.exp + '</b>',
' <span class="exp-bar-wrap"><span class="exp-bar-fill" id="rpgsf-exp-bar"></span></span></div>',
'<div class="result-row">GOLD <b>+' + r.gold + '</b> G</div>',
r.drop ? '<div class="result-row" style="color:#7fffff">🎁 ' + r.drop + ' を手に入れた!</div>' : '',
'<button id="rpgsf-battle-result-btn">▶ つぎへ</button>'
].join('');
document.body.appendChild(win);
// EXPバーアニメ
setTimeout(function(){
var bar = document.getElementById('rpgsf-exp-bar');
if (bar) bar.style.width = Math.min(100, expPct) + '%';
}, 100);
// 閉じる
function closeResult() { if (win.parentNode) win.parentNode.removeChild(win); }
document.getElementById('rpgsf-battle-result-btn').addEventListener('click', closeResult);
document.getElementById('rpgsf-battle-result-btn').addEventListener('touchstart', closeResult);
})();
```
## 3. 既存機能との整合(壊さない点)
- 既存の `battle_fx`(ダメージポップアップ・HPゲージアニメ)は手を加えない。`result` キーを追加するだけ
- `state['bag']` への追加は既存のショップ購入・宝箱開封と同じ配列操作で整合
- `last_drop` / `battle_result` はGET後に参照したら `unset` してstateを再保存し、二重表示を防ぐ
- スマホのタッチ操作でも閉じられるため、モバイル体験を損なわない
- ドロップなし(`drop: null`)の場合はドロップ行を非表示にするだけで、既存の戦闘フローに影響しない
## 4. 規模感
- PHP側: `lib.php` の勝利判定ブロックに約30行追加
- JS/CSS: `play.php` に約60行追加
- 合計約90行の追加で完結する現実的な規模
現在、モンスターを倒してもゴールド・EXP獲得はログテキストに流れるだけで、視覚的な達成感が薄い。戦闘終了時に「アイテムドロップ」「ゴールド獲得」「EXP獲得」を専用のポップアップウィンドウ(DQ風「せんとうのせいか」ウィンドウ)でCanvas上に表示し、プレイヤーがリザルトを確認してから次に進めるようにする。これにより戦闘の満足感と中毒性が大幅に向上する。
## 2. 具体的な仕様
### 2-1. 戦闘リザルトウィンドウ(JS/Canvas側)
- 敵撃破時(`status==='win'`)に、既存の戦闘Canvasの手前にDOMオーバーレイとして `<div id="rpgsf-battle-result">` を表示する
- ウィンドウデザイン: SFC風の黒背景+金枠、ピクセルフォント
- 表示内容(PHP側から `data-result` 属性に JSON で渡す):
```
✨ せんとうのせいか ✨
──────────────────
EXP + {exp} ポイント
GOLD + {gold} ゴールド
{アイテム名} を手に入れた! ← ドロップがある場合のみ
──────────────────
[ つぎへ ]
```
- 「つぎへ」ボタンを押すとウィンドウが閉じ、通常のマップ画面に戻る
- スマホ対応: タップで閉じられるよう `touchstart` にも対応
- アニメーション: ウィンドウが下からスライドインし、各行が0.1秒ずつ遅延してフェードイン(CSS `@keyframes`で実装)
- EXP行にはミニゲージ(横幅100px程度の細いバー)をインラインで表示し、獲得後のEXP割合まで伸びるアニメーションを付ける
### 2-2. アイテムドロップ判定(PHP側 `lib.php` の `rpgsf_apply_action` 内)
- 既存の勝利判定ロジック内に、モンスターのタグに応じたドロップテーブルを追加する
```php
$drop_table = [
'slime' => ['やくそう', null, null], // 1/3 でやくそう
'bat' => ['どくけし', null, null],
'skeleton' => ['てつのやり', null, null],
'dragon' => ['ドラゴンのうろこ', 'ほのおのつるぎ', null],
// ...モンスターIDをキーに最大3候補をランダム抽選
];
$drops = $drop_table[$enemy_id] ?? [null, null, null];
$drop = $drops[array_rand($drops)];
if ($drop) {
$state['bag'][] = $drop;
$state['last_drop'] = $drop;
} else {
$state['last_drop'] = null;
}
```
- `$state['battle_result']` に `['exp'=>int, 'gold'=>int, 'drop'=>string|null]` を格納し、次のGETリダイレクト後のページで参照できるようセッション or stateに保持
- 既存の `battle_fx` セッション機構に `result` キーを追加して渡す:
```php
$_SESSION['battle_fx']['result'] = $state['battle_result'] ?? null;
```
### 2-3. PHP → JS へのデータ受け渡し(`pages/play.php`)
- `$battle_fx['result']` が存在する場合、`play.php` の `<div id="rpgsf-game">` などにデータ属性で渡す:
```php
$result_json = isset($battle_fx['result'])
? h(json_encode($battle_fx['result'], JSON_UNESCAPED_UNICODE))
: '';
```
```html
<div id="rpgsf-battle-result-data" data-result="<?= $result_json ?>" hidden></div>
```
- JS起動時にこの要素を読み取り、`data-result` が空でなければリザルトウィンドウを表示する
### 2-4. HTML/CSS(`play.php` 末尾 `<style>` ブロックに追記)
```css
#rpgsf-battle-result {
position: fixed;
bottom: 80px;
left: 50%;
transform: translateX(-50%) translateY(40px);
background: #0a0a0f;
border: 3px solid #d4af37;
box-shadow: 4px 4px 0 #000;
padding: 18px 28px;
min-width: 280px;
max-width: 90vw;
font-family: 'Press Start 2P', monospace, sans-serif;
font-size: 0.78rem;
color: #f0e6c8;
z-index: 9000;
animation: resultSlideIn .35s ease forwards;
line-height: 2;
}
@keyframes resultSlideIn {
from { opacity:0; transform: translateX(-50%) translateY(60px); }
to { opacity:1; transform: translateX(-50%) translateY(0); }
}
#rpgsf-battle-result .result-row { opacity:0; animation: fadeRow .3s ease forwards; }
#rpgsf-battle-result .result-row:nth-child(1){animation-delay:.1s}
#rpgsf-battle-result .result-row:nth-child(2){animation-delay:.25s}
#rpgsf-battle-result .result-row:nth-child(3){animation-delay:.4s}
#rpgsf-battle-result .result-row:nth-child(4){animation-delay:.55s}
@keyframes fadeRow { to { opacity:1; } }
#rpgsf-battle-result .exp-bar-wrap { display:inline-block; width:100px; height:6px; background:#333; vertical-align:middle; border:1px solid #555; margin-left:8px; }
#rpgsf-battle-result .exp-bar-fill { height:100%; background:#4fc; width:0; transition: width 1s ease .6s; }
#rpgsf-battle-result-btn { margin-top:14px; display:block; width:100%; background:#1a1a2e; border:2px solid #d4af37; color:#d4af37; padding:8px; cursor:pointer; font-size:.75rem; }
#rpgsf-battle-result-btn:hover { background:#d4af37; color:#000; }
```
### 2-5. JavaScript
```js
(function(){
var el = document.getElementById('rpgsf-battle-result-data');
if (!el) return;
var raw = el.getAttribute('data-result');
if (!raw) return;
var r;
try { r = JSON.parse(raw); } catch(e){ return; }
// ウィンドウ構築
var win = document.createElement('div');
win.id = 'rpgsf-battle-result';
var expPct = r.exp_pct || 0; // PHP側で計算して渡す
win.innerHTML = [
'<div class="result-row" style="color:#ffd700;text-align:center">✨ せんとうのせいか ✨</div>',
'<div class="result-row">EXP <b>+' + r.exp + '</b>',
' <span class="exp-bar-wrap"><span class="exp-bar-fill" id="rpgsf-exp-bar"></span></span></div>',
'<div class="result-row">GOLD <b>+' + r.gold + '</b> G</div>',
r.drop ? '<div class="result-row" style="color:#7fffff">🎁 ' + r.drop + ' を手に入れた!</div>' : '',
'<button id="rpgsf-battle-result-btn">▶ つぎへ</button>'
].join('');
document.body.appendChild(win);
// EXPバーアニメ
setTimeout(function(){
var bar = document.getElementById('rpgsf-exp-bar');
if (bar) bar.style.width = Math.min(100, expPct) + '%';
}, 100);
// 閉じる
function closeResult() { if (win.parentNode) win.parentNode.removeChild(win); }
document.getElementById('rpgsf-battle-result-btn').addEventListener('click', closeResult);
document.getElementById('rpgsf-battle-result-btn').addEventListener('touchstart', closeResult);
})();
```
## 3. 既存機能との整合(壊さない点)
- 既存の `battle_fx`(ダメージポップアップ・HPゲージアニメ)は手を加えない。`result` キーを追加するだけ
- `state['bag']` への追加は既存のショップ購入・宝箱開封と同じ配列操作で整合
- `last_drop` / `battle_result` はGET後に参照したら `unset` してstateを再保存し、二重表示を防ぐ
- スマホのタッチ操作でも閉じられるため、モバイル体験を損なわない
- ドロップなし(`drop: null`)の場合はドロップ行を非表示にするだけで、既存の戦闘フローに影響しない
## 4. 規模感
- PHP側: `lib.php` の勝利判定ブロックに約30行追加
- JS/CSS: `play.php` に約60行追加
- 合計約90行の追加で完結する現実的な規模
💬 返信 (3)
🛠 開発を開始しました (機能追加 rpg-story-forge)
ご要望ありがとうございます。AI 開発ワーカーが実装を開始します。
通常 5〜30 分で Pull Request を作成し、レビュー後にリリースされます。
ご要望ありがとうございます。AI 開発ワーカーが実装を開始します。
通常 5〜30 分で Pull Request を作成し、レビュー後にリリースされます。
📝 開発が完了しました
ご要望いただいた内容の実装が完了し、最終チェック段階に入りました。
レビュー (自動) → リリース、の流れで進みます。
もう少々お待ちください。
ご要望いただいた内容の実装が完了し、最終チェック段階に入りました。
レビュー (自動) → リリース、の流れで進みます。
もう少々お待ちください。
✅ リリース完了のお知らせ
ご要望いただいた「RPGストーリーフォージ AI風ドット絵冒険」を実装し、リリースいたしました。
【ご利用方法】
ダッシュボード: https://www.aiapps.jp/?action=dashboard
アプリ詳細: https://www.aiapps.jp/apps/show.php?slug=rpg-story-forge
デモ環境は 1 時間以内に自動構築されます:
https://www.aiapps.jp/demo/rpg-story-forge/
ご利用ありがとうございます!
ご要望いただいた「RPGストーリーフォージ AI風ドット絵冒険」を実装し、リリースいたしました。
【ご利用方法】
ダッシュボード: https://www.aiapps.jp/?action=dashboard
アプリ詳細: https://www.aiapps.jp/apps/show.php?slug=rpg-story-forge
デモ環境は 1 時間以内に自動構築されます:
https://www.aiapps.jp/demo/rpg-story-forge/
ご利用ありがとうございます!
Echo
Iris