67歩目 オーブを実装! Unityで1日1ステップ!ノンフィールドRPG開発日記

1日1歩開発日記

ふりかえり

前回は、テストプレイヤーの方からいただいたフィードバックを反映し、以下の改善を行いました。

  • 画面の向きを固定:プレイ中に画面の回転で操作しづらくなる問題を解消。
  • プレイヤーとモンスター画像の鮮明化:ぼやけて見えにくかった画像を調整し、くっきりと表示されるようにしました。

ゲームのプレイ体験をより快適にするための、地味ながら重要な改善でした!

オーブを実装!

今回はついに、プレイヤーを大幅に強化できる新要素「オーブ」を実装しました!

◆ オーブとは?

オーブは、モンスターを倒した際に低確率でドロップする強化アイテムです。1つだけ装備することができ、装備することでプレイヤーの各種ステータスが上昇します。

◆ オーブの特徴

  • メインオプション:オーブごとに1つだけ付いており、「攻撃力アップ」「HPアップ」などのステータス強化効果があります。
  • サブオプション:最大4つまで付きます。付与数の確率は以下の通りです。
サブオプション数付与確率
4つ10%
3つ20%
2つ30%
1つ40%
  • ステージが進むほど強化:後半のステージでドロップするオーブほど、より強力な強化値を持ちます。

◆ 装備による恩恵

装備中のオーブは、メイン・サブ両方の効果が合算されてステータスに反映されます。これにより、装備するだけでプレイヤーの攻撃力やHP、獲得ゴールド量などが一気に上昇します!

スクリプト

OrbManager.cs

今回の実装の中心は、OrbManager.csというスクリプトです。主に以下の機能を担っています。

【目的】
  • オーブの生成
  • ドロップ確率の管理
  • ステータス反映
  • オーブの装備/売却/所持管理
【主な処理の流れ】
  1. 敵撃破時にドロップ判定 (TryDropOrb)
    • ドロップ率(1%)に基づき、確率的に新しいオーブを生成。
    • 所持数が上限を超えていた場合は自動売却。
  2. オーブ生成 (GenerateRandomOrb)
    • 現在のステージに応じた強化段階を設定。
    • メイン&サブオプションを確率に基づいて付与。
    • アイコン、名前、説明文なども自動で設定。
  3. 装備処理 (EquipOrb, UnequipOrb)
    • 任意のオーブを装備、または解除。
    • ステータスを自動で再計算。
  4. ステータス反映 (ApplyOrbEffects)
    • 装備中のオーブの効果を、攻撃力やHPなどに加算。
  5. 所持オーブの管理
    • 最大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: 必要に応じてアタッチ。

動作確認

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

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

装備したいオーブを選択し装備ボタンを押し、装備します。

装備されました!

ステータスを見るとオーブのオプションの強化値が反映されています!

一気に強くなることができました!

まとめ

今回は、プレイヤーの育成に新たな楽しみを加える「オーブ」システムを実装しました!

  • ドロップ要素のランダム性
  • オプション厳選のやり込み要素
  • 装備による一発強化

といった要素が加わり、より深いゲームプレイが可能になりました。装備による成長の幅が大きいため、ゲーム全体のやり込み度もアップしたと思います!

次回予告

次回はセーブボタンを実装します!

お楽しみに!

タイトルとURLをコピーしました