ふりかえり
前回は、テストプレイヤーの方からいただいたフィードバックを反映し、以下の改善を行いました。
- 画面の向きを固定:プレイ中に画面の回転で操作しづらくなる問題を解消。
- プレイヤーとモンスター画像の鮮明化:ぼやけて見えにくかった画像を調整し、くっきりと表示されるようにしました。
ゲームのプレイ体験をより快適にするための、地味ながら重要な改善でした!
オーブを実装!
今回はついに、プレイヤーを大幅に強化できる新要素「オーブ」を実装しました!
◆ オーブとは?
オーブは、モンスターを倒した際に低確率でドロップする強化アイテムです。1つだけ装備することができ、装備することでプレイヤーの各種ステータスが上昇します。
◆ オーブの特徴
- メインオプション:オーブごとに1つだけ付いており、「攻撃力アップ」「HPアップ」などのステータス強化効果があります。
- サブオプション:最大4つまで付きます。付与数の確率は以下の通りです。
| サブオプション数 | 付与確率 |
|---|---|
| 4つ | 10% |
| 3つ | 20% |
| 2つ | 30% |
| 1つ | 40% |
- ステージが進むほど強化:後半のステージでドロップするオーブほど、より強力な強化値を持ちます。
◆ 装備による恩恵
装備中のオーブは、メイン・サブ両方の効果が合算されてステータスに反映されます。これにより、装備するだけでプレイヤーの攻撃力やHP、獲得ゴールド量などが一気に上昇します!
スクリプト
OrbManager.cs
今回の実装の中心は、OrbManager.csというスクリプトです。主に以下の機能を担っています。
【目的】
- オーブの生成
- ドロップ確率の管理
- ステータス反映
- オーブの装備/売却/所持管理
【主な処理の流れ】
- 敵撃破時にドロップ判定 (TryDropOrb)
- ドロップ率(1%)に基づき、確率的に新しいオーブを生成。
- 所持数が上限を超えていた場合は自動売却。
- オーブ生成 (GenerateRandomOrb)
- 現在のステージに応じた強化段階を設定。
- メイン&サブオプションを確率に基づいて付与。
- アイコン、名前、説明文なども自動で設定。
- 装備処理 (EquipOrb, UnequipOrb)
- 任意のオーブを装備、または解除。
- ステータスを自動で再計算。
- ステータス反映 (ApplyOrbEffects)
- 装備中のオーブの効果を、攻撃力やHPなどに加算。
- 所持オーブの管理
- 最大20個まで所持可能。
- 超過時はダイヤに変換して自動売却。
- 売却時には獲得ダイヤ数をログ表示。
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
public class OrbManager : MonoBehaviour
{
[Header("設定")]
public OrbDropConfig dropConfig;
[Header("オーブデータ")]
public List<OrbData> ownedOrbs = new List<OrbData>();
public OrbData equippedOrb = null;
[Header("所持数制限")]
public int maxOrbCount = 20; // 最大所持数
[Header("参照")]
public PlayerManager playerManager;
public BattleLogManager battleLogManager;
public StageManager stageManager;
// シングルトン
public static OrbManager Instance { get; private set; }
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
private void Start()
{
// デフォルトのドロップ設定を初期化
if (dropConfig == null)
{
InitializeDefaultDropConfig();
}
// デフォルトのオプションタイプを設定
if (dropConfig.availableOptionTypes == null || dropConfig.availableOptionTypes.Count == 0)
{
InitializeDefaultOptionTypes();
}
// StageManager 参照を補完
if (stageManager == null)
{
stageManager = FindObjectOfType<StageManager>();
}
}
// デフォルトのドロップ設定を初期化
private void InitializeDefaultDropConfig()
{
dropConfig = ScriptableObject.CreateInstance<OrbDropConfig>();
dropConfig.dropRate = 0.01f; // 1%
dropConfig.option1Probability = 0.4f;
dropConfig.option2Probability = 0.3f;
dropConfig.option3Probability = 0.2f;
dropConfig.option4Probability = 0.1f;
dropConfig.minValue = 0.1f; // 10%
dropConfig.maxValue = 0.5f; // 50%
}
// デフォルトのオプションタイプを初期化
private void InitializeDefaultOptionTypes()
{
dropConfig.availableOptionTypes = new List<OrbOptionType>
{
OrbOptionType.HP_PERCENTAGE,
OrbOptionType.ATTACK_PERCENTAGE,
OrbOptionType.DEFENSE_PERCENTAGE,
OrbOptionType.GOLD_GAIN,
OrbOptionType.EXP_GAIN,
OrbOptionType.CRITICAL_RATE,
OrbOptionType.CRITICAL_DAMAGE,
OrbOptionType.DIAMOND_DROP_RATE
};
}
// 敵を倒した時にオーブドロップを試行
public void TryDropOrb()
{
if (dropConfig == null)
{
Debug.LogWarning("OrbDropConfigが設定されていません");
return;
}
float random = Random.Range(0f, 1f);
if (random < dropConfig.dropRate)
{
OrbData newOrb = GenerateRandomOrb();
if (newOrb != null)
{
// 最大所持数チェック
if (ownedOrbs.Count >= maxOrbCount)
{
// 所持数が上限に達している場合は自動売却
AutoSellOrb(newOrb);
}
else
{
// 通常通りオーブを追加
ownedOrbs.Add(newOrb);
// バトルログに追加
if (battleLogManager != null)
{
battleLogManager.AddLog($"<color=yellow>オーブ「{newOrb.GetDisplayName()}」を獲得しました!</color>", new Color(1f, 0.5f, 0f));
}
Debug.Log($"<color=yellow>オーブ「{newOrb.GetDisplayName()}」を獲得しました!</color>");
Debug.Log($"オプション: {newOrb.GetOptionsDescription()}");
}
}
}
}
// ランダムなオーブを生成
private OrbData GenerateRandomOrb()
{
if (dropConfig == null) return null;
OrbData orb = new OrbData();
// 現在ステージに基づく強化段階(3ステージ毎に+1)
int currentStage = 1;
if (stageManager != null)
{
currentStage = Mathf.Max(1, stageManager.GetCurrentStage());
}
int enhancementLevel = (currentStage - 1) / 3; // 1-3:0, 4-6:1, 7-9:2, ...
// オプション数を決定
int optionCount = dropConfig.DetermineOptionCount();
orb.rarity = optionCount;
// オーブ種類を決定
orb.orbType = DetermineOrbType();
// オーブ名、説明、アイコンを設定(強化段階に応じて名称を拡張)
string baseName = GetOrbNameByType(orb.orbType);
orb.orbName = enhancementLevel > 0 ? $"{baseName}+{enhancementLevel}" : baseName;
orb.description = GetOrbDescriptionByType(orb.orbType);
orb.orbIcon = dropConfig.GetOrbIconByType(orb.orbType);
orb.frameIcon = dropConfig.GetFrameIconByRarity(orb.rarity);
// アイコンのインデックス情報を保存
orb.orbIconIndex = dropConfig.GetOrbIconIndex(orb.orbType, orb.orbIcon);
orb.frameIconIndex = dropConfig.GetFrameIconIndex(orb.frameIcon);
// メインオプションを生成
OrbOptionType mainOptionType = dropConfig.GetMainOptionByType(orb.orbType);
float mainOptionValue = dropConfig.GetRandomMainOptionValue(mainOptionType, enhancementLevel); // 強化段階を適用
string mainDisplayName = GetOptionDisplayName(mainOptionType);
string mainDescription = GetOptionDescription(mainOptionType);
orb.mainOption = new OrbOption(mainOptionType, mainOptionValue, mainDisplayName, mainDescription);
var mainRangeAdj = dropConfig.GetMainOptionValueRange(mainOptionType, enhancementLevel);
Debug.Log($"メインオプション生成: {mainOptionType} = {mainOptionValue:F1}% (範囲: {mainRangeAdj.minValue}-{mainRangeAdj.maxValue}%) 強化段階={enhancementLevel}");
// 通常オプションを生成(重複制限なし)
for (int i = 0; i < optionCount; i++)
{
// ランダムなオプションタイプを選択(重複制限なし)
OrbOptionType optionType = dropConfig.GetRandomOptionType();
// サブオプション用の強化値を生成
float value = dropConfig.GetRandomSubOptionValue(optionType, enhancementLevel);
// オプションを作成
OrbOption option = new OrbOption(optionType, value, GetOptionDisplayName(optionType), GetOptionDescription(optionType));
orb.options.Add(option);
var subRangeAdj = dropConfig.GetSubOptionValueRange(optionType, enhancementLevel);
Debug.Log($"サブオプション生成: {optionType} = {value:F1}% (範囲: {subRangeAdj.minValue}-{subRangeAdj.maxValue}%) 強化段階={enhancementLevel}");
}
orb.isGenerated = true;
orb.generatedTime = System.DateTime.Now;
return orb;
}
// オプションタイプの表示名を取得
private string GetOptionDisplayName(OrbOptionType optionType)
{
switch (optionType)
{
case OrbOptionType.HP_PERCENTAGE:
return "HP強化";
case OrbOptionType.ATTACK_PERCENTAGE:
return "攻撃力強化";
case OrbOptionType.DEFENSE_PERCENTAGE:
return "防御力強化";
case OrbOptionType.GOLD_GAIN:
return "G獲得量";
case OrbOptionType.EXP_GAIN:
return "EXP獲得量";
case OrbOptionType.CRITICAL_RATE:
return "会心率";
case OrbOptionType.CRITICAL_DAMAGE:
return "会心ダメージ";
case OrbOptionType.DIAMOND_DROP_RATE:
return "ダイヤ獲得率";
default:
return "未知の強化";
}
}
// オプションタイプの説明を取得
private string GetOptionDescription(OrbOptionType optionType)
{
switch (optionType)
{
case OrbOptionType.HP_PERCENTAGE:
return "最大HPを%で強化";
case OrbOptionType.ATTACK_PERCENTAGE:
return "攻撃力を%で強化";
case OrbOptionType.DEFENSE_PERCENTAGE:
return "防御力を%で強化";
case OrbOptionType.GOLD_GAIN:
return "獲得Gを%で増加";
case OrbOptionType.EXP_GAIN:
return "獲得EXPを%で増加";
case OrbOptionType.CRITICAL_RATE:
return "会心率を%で増加";
case OrbOptionType.CRITICAL_DAMAGE:
return "会心ダメージ倍率を%で増加";
case OrbOptionType.DIAMOND_DROP_RATE:
return "ダイヤ獲得確率を%で増加";
default:
return "未知の効果";
}
}
// オーブを装備
public bool EquipOrb(OrbData orb)
{
if (orb == null || !ownedOrbs.Contains(orb))
{
Debug.LogWarning("装備できないオーブです");
return false;
}
equippedOrb = orb;
// バトルログに追加
// if (battleLogManager != null)
// {
// battleLogManager.AddLog($"<color=cyan>オーブ「{orb.GetDisplayName()}」を装備しました</color>", Color.cyan);
// }
Debug.Log($"オーブ「{orb.GetDisplayName()}」を装備しました");
// プレイヤーのステータスを更新
UpdatePlayerStats();
return true;
}
// オーブの装備を解除
public void UnequipOrb()
{
if (equippedOrb != null)
{
string orbName = equippedOrb.GetDisplayName();
equippedOrb = null;
// バトルログに追加
// if (battleLogManager != null)
// {
// battleLogManager.AddLog($"<color=gray>オーブ「{orbName}」の装備を解除しました</color>", Color.gray);
// }
Debug.Log($"オーブ「{orbName}」の装備を解除しました");
// プレイヤーのステータスを更新
UpdatePlayerStats();
}
}
// プレイヤーのステータスを更新
private void UpdatePlayerStats()
{
if (playerManager == null || playerManager.player == null) return;
// オーブの効果を適用
ApplyOrbEffects();
// プレイヤーのHPを更新(最大HPが変わった場合)
if (playerManager.player.currentHP > playerManager.player.maxHP)
{
playerManager.player.currentHP = playerManager.player.maxHP;
}
// UIを更新
if (playerManager.playerUIManager != null)
{
playerManager.playerUIManager.UpdateUI();
}
}
// オーブの効果を適用
private void ApplyOrbEffects()
{
if (playerManager == null || playerManager.player == null) return;
// 既存のオーブ効果をリセット
ResetOrbEffects();
// 装備中のオーブの効果を適用
if (equippedOrb != null)
{
// メインオプションの効果を適用
if (equippedOrb.mainOption != null)
{
ApplyOrbOption(equippedOrb.mainOption);
}
// 通常オプションの効果を適用
if (equippedOrb.options != null)
{
foreach (var option in equippedOrb.options)
{
ApplyOrbOption(option);
}
}
}
}
// オーブ効果をリセット
private void ResetOrbEffects()
{
if (playerManager == null || playerManager.player == null) return;
// オーブ効果をリセット
playerManager.player.orbPowerUpHPPercentage = 0f;
playerManager.player.orbPowerUpAttackPercentage = 0f;
playerManager.player.orbPowerUpDefensePercentage = 0f;
playerManager.player.orbPowerUpGoldGain = 0f;
playerManager.player.orbPowerUpExpGain = 0f;
playerManager.player.orbPowerUpCriticalRate = 0f;
playerManager.player.orbPowerUpCriticalDamage = 0f;
playerManager.player.orbPowerUpDiamondDropRate = 0f;
}
// オーブオプションの効果を適用(%表記を小数へ変換して適用)
private void ApplyOrbOption(OrbOption option)
{
if (playerManager == null || playerManager.player == null) return;
// option.value は%表記(例: 27.2)。内部は小数(0.272)で扱う
float valueAsRatio = option.value / 100f;
switch (option.optionType)
{
case OrbOptionType.HP_PERCENTAGE:
playerManager.player.orbPowerUpHPPercentage += valueAsRatio;
break;
case OrbOptionType.ATTACK_PERCENTAGE:
playerManager.player.orbPowerUpAttackPercentage += valueAsRatio;
break;
case OrbOptionType.DEFENSE_PERCENTAGE:
playerManager.player.orbPowerUpDefensePercentage += valueAsRatio;
break;
case OrbOptionType.GOLD_GAIN:
playerManager.player.orbPowerUpGoldGain += valueAsRatio;
break;
case OrbOptionType.EXP_GAIN:
playerManager.player.orbPowerUpExpGain += valueAsRatio;
break;
case OrbOptionType.CRITICAL_RATE:
playerManager.player.orbPowerUpCriticalRate += valueAsRatio;
break;
case OrbOptionType.CRITICAL_DAMAGE:
playerManager.player.orbPowerUpCriticalDamage += valueAsRatio;
break;
case OrbOptionType.DIAMOND_DROP_RATE:
playerManager.player.orbPowerUpDiamondDropRate += valueAsRatio; // 合算は専用フィールドに加算
break;
}
}
// オーブの効果値を取得
public float GetOrbEffectValue(OrbOptionType optionType)
{
if (equippedOrb == null || equippedOrb.options == null) return 0f;
foreach (var option in equippedOrb.options)
{
if (option.optionType == optionType)
{
return option.value;
}
}
return 0f;
}
// 所有オーブ数を取得
public int GetOwnedOrbCount()
{
return ownedOrbs != null ? ownedOrbs.Count : 0;
}
// 装備中のオーブを取得
public OrbData GetEquippedOrb()
{
return equippedOrb;
}
// オーブを削除
public bool RemoveOrb(OrbData orb)
{
if (orb == null || !ownedOrbs.Contains(orb)) return false;
// 装備中のオーブの場合は装備を解除
if (equippedOrb == orb)
{
UnequipOrb();
}
ownedOrbs.Remove(orb);
return true;
}
// オーブを売却
public bool SellOrb(OrbData orb)
{
if (orb == null || !ownedOrbs.Contains(orb)) return false;
// 売却価格を計算(オプション数 × 10ダイヤ)
int sellPrice = orb.GetOptionCount() * 10;
// プレイヤーにダイヤを付与
if (playerManager != null && playerManager.player != null)
{
playerManager.player.diamond += sellPrice;
// バトルログに追加
if (battleLogManager != null)
{
battleLogManager.AddLog($"<color=yellow>オーブ「{orb.GetDisplayName()}」を売却しました!ダイヤ{sellPrice}個を獲得!</color>", new Color(1f, 0.5f, 0f));
}
Debug.Log($"オーブ「{orb.GetDisplayName()}」を売却しました!ダイヤ{sellPrice}個を獲得!");
}
// オーブを削除
RemoveOrb(orb);
return true;
}
// オーブの売却価格を取得
public int GetOrbSellPrice(OrbData orb)
{
if (orb == null) return 0;
return orb.GetOptionCount() * 10;
}
// オーブを自動売却(所持数上限時の処理)
private void AutoSellOrb(OrbData orb)
{
if (orb == null) return;
// 売却価格を計算
int sellPrice = GetOrbSellPrice(orb);
// プレイヤーにダイヤを付与
if (playerManager != null && playerManager.player != null)
{
playerManager.player.diamond += sellPrice;
// バトルログに追加
if (battleLogManager != null)
{
battleLogManager.AddLog($"<color=orange>所持数上限のため、オーブ「{orb.GetDisplayName()}」を自動売却しました!ダイヤ{sellPrice}個を獲得!</color>", new Color(1f, 0.5f, 0f));
}
Debug.Log($"<color=orange>所持数上限のため、オーブ「{orb.GetDisplayName()}」を自動売却しました!ダイヤ{sellPrice}個を獲得!</color>");
}
}
// 現在の所持数を取得
public int GetCurrentOrbCount()
{
return ownedOrbs != null ? ownedOrbs.Count : 0;
}
// 最大所持数を取得
public int GetMaxOrbCount()
{
return maxOrbCount;
}
// 所持数が上限に達しているかチェック
public bool IsOrbInventoryFull()
{
return GetCurrentOrbCount() >= maxOrbCount;
}
// プレイヤーのステータスを更新(外部から呼び出し可能)
public void RefreshPlayerStats()
{
UpdatePlayerStats();
}
// オーブのSpriteを復元
public void RestoreOrbSprites()
{
if (dropConfig == null) return;
// 所持オーブのSpriteを復元
foreach (var orb in ownedOrbs)
{
RestoreOrbSprite(orb);
}
// 装備中のオーブのSpriteを復元
if (equippedOrb != null)
{
RestoreOrbSprite(equippedOrb);
}
}
// 個別のオーブのSpriteを復元
private void RestoreOrbSprite(OrbData orb)
{
if (orb == null || dropConfig == null) return;
// フレームアイコンを復元
if (orb.frameIconIndex >= 0)
{
orb.frameIcon = dropConfig.GetFrameIconByIndex(orb.frameIconIndex);
}
// メインアイコンを復元
if (orb.orbIconIndex >= 0)
{
orb.orbIcon = dropConfig.GetOrbIconByIndex(orb.orbType, orb.orbIconIndex);
}
}
// オーブ種類を決定
private OrbType DetermineOrbType()
{
// ランダムにオーブ種類を決定
OrbType[] orbTypes = (OrbType[])System.Enum.GetValues(typeof(OrbType));
return orbTypes[Random.Range(0, orbTypes.Length)];
}
// オーブ種類に応じた名前を取得
private string GetOrbNameByType(OrbType orbType)
{
switch (orbType)
{
case OrbType.Power:
return "力のオーブ";
case OrbType.Defense:
return "守りのオーブ";
case OrbType.Speed:
return "速さのオーブ";
case OrbType.Wisdom:
return "知恵のオーブ";
case OrbType.Courage:
return "勇気のオーブ";
case OrbType.Hope:
return "希望のオーブ";
case OrbType.Destiny:
return "運命のオーブ";
case OrbType.Mystery:
return "神秘のオーブ";
default:
return "未知のオーブ";
}
}
// オーブ種類に応じた説明を取得
private string GetOrbDescriptionByType(OrbType orbType)
{
switch (orbType)
{
case OrbType.Power:
return "力強いエネルギーを宿したオーブ";
case OrbType.Defense:
return "守護の力を秘めたオーブ";
case OrbType.Speed:
return "素早い動きを可能にするオーブ";
case OrbType.Wisdom:
return "知恵と知識を授けるオーブ";
case OrbType.Courage:
return "勇気と決意を与えるオーブ";
case OrbType.Hope:
return "希望と光を宿したオーブ";
case OrbType.Destiny:
return "運命を操る力を持つオーブ";
case OrbType.Mystery:
return "神秘的な力に満ちたオーブ";
default:
return "不思議な力を持つオーブ";
}
}
}
Unityでの設定
【必要なコンポーネント】
- OrbManager.cs: ゲーム全体で1つ設置し、Singleton化。
- OrbDropConfig: ScriptableObjectでドロップ条件やアイコンなどを設定。
- OrbUIManager: UI表示処理。
- PlayerManager, StageManager, BattleLogManager: 必要に応じてアタッチ。



動作確認
左上のオーブ装備画面を開くためのボタンを選択します。

所持オーブを選択すると、メインオプションと、サブオプションが付いているのがわかります。
装備したいオーブを選択し装備ボタンを押し、装備します。

装備されました!

ステータスを見るとオーブのオプションの強化値が反映されています!
一気に強くなることができました!

まとめ
今回は、プレイヤーの育成に新たな楽しみを加える「オーブ」システムを実装しました!
- ドロップ要素のランダム性
- オプション厳選のやり込み要素
- 装備による一発強化
といった要素が加わり、より深いゲームプレイが可能になりました。装備による成長の幅が大きいため、ゲーム全体のやり込み度もアップしたと思います!
次回予告
次回はセーブボタンを実装します!
お楽しみに!
