見出し画像

【Unity】3Dローグライクゲームの作り方〜Step11-9〜

前回の記事はこちら
前回はダンジョン生成を改良しました。

有利な状態異常の追加

それでは今回は、状態異常の種類の増やし方ということで、新しく有利な状態異常を実装したいと思います。
有利な状態異常といえば、基本的に決まったターン数で解除されることが多いと思うのですが、現状全ての状態異常で共通のパラメーターを使用しているのでそれを改善し、状態異常それぞれの解除のされやすさなどを設定できるようにしたいと思います。

それぞれの状態異常の解除されやすさ

という訳で追加する前に状態異常の解除のされやすさの設定を変えていきたいと思います。
ActorParamsControllerクラスを以下のように変更して下さい。

public class ActorParamsController : MonoBehaviour
{
   [System.Serializable]
   public class Equipment
   {
       /*   省略   */
   }

   [System.Serializable]
   public class Condition
   {
       public ECondition condition;
       public float conditionRate;
       public float addClearRate;
   }

   [System.Serializable]
   public class ConditionInfo
   {
       public ECondition condition;
       public float firstClearRate = 10;
       public float addClearRate = 10;
   }

   public string actorName;
   public Params parameter;
   public ConditionInfo[] conditionInfos;
   public List<Condition> conditions;
   public Equipment equipment;
   public float decFoodPt = 0.25f;
   public float decHungerHpPer = 0.1f;
   public float normalRecoveryPer = 50;
   public float decPoisonPt = 2;

   private List<Params> paramsData;
   private int prevLv = 0;
   private EffectManager effect;

   void Reset()
   {
       /*   省略   */
   }

   private void OnValidate()
   {
       if (prevLv != parameter.lv)
       {
           SetLv(parameter.lv);
           prevLv = parameter.lv;
       }
   }

   private void Start()
   {
       /*   省略   */
   }

   /**
   * レベルを設定する
   */
   public void SetLv(int lv)
   {
       /*   省略   */
   }

   /**
   * レベルから参照したパラメーターを返す
   */
   private Params GetParameterFromLv(int lv)
   {
       /*   省略   */
   }

   /**
   * パラメーターのコピーを返す
   */
   public Params GetParameter()
   {
       /*   省略   */
   }

   /**
   * パラメーターを纏めて設定する
   */
   public void SetParameter(Params p)
   {
       /*   省略   */
   }

   /**
   * 状態異常の配列を返す
   */
   public Condition[] GetConditions() => conditions.ToArray();

   /**
   * 状態異常を纏めて設定する
   */
   public void SetConditions(Condition[] conditions)
   {
       this.conditions = new List<Condition>();
       this.conditions.AddRange(conditions);
   }

   /**
   * 満腹度を回復する
   */
   public void RecoveryFood(int food)
   {
       /*   省略   */
   }

   /**
   * 体力を回復する
   */
   public void RecoveryHp(int p)
   {
       /*   省略   */
   }

   /**
   * 満腹度を減少させる
   */
   public void DecreaseFood()
   {
       if (parameter.food > 0)
       {
           parameter.food -= decFoodPt;
           if (parameter.food < 0) parameter.food = 0;
           if (conditions.Find(c => c.condition == ECondition.Poison) == null)
           {
               if (parameter.hpmax == parameter.hp) return;
               float hp = parameter.hpmax / normalRecoveryPer;
               parameter.hp += hp;
               if (parameter.hp > parameter.hpmax) parameter.hp = parameter.hpmax;
           }
       }
       else
       {
           parameter.hp -= parameter.hpmax * decHungerHpPer;
           DeathJudgment();
       }
   }

   /*
   * もし毒にかかっていればダメージを受ける
   */
   public void DamagedPoison()
   {
       if (conditions.Find(c => c.condition == ECondition.Poison) != null)
       {
           effect.Play(EffectManager.EType.Poison, gameObject);
           parameter.hp -= decPoisonPt;
           DeathJudgment();
       }
   }

   /*
   * 混乱状態かどうか
   */
   public bool IsConfusion() => conditions.Find(c => c.condition == ECondition.Confusion) != null;

   /*
   * 麻痺状態かどうか
   */
   public bool IsParalysis() => conditions.Find(c => c.condition == ECondition.Paralysis) != null;

   /*
   * 睡眠状態かどうか
   */
   public bool IsSleep() => conditions.Find(c => c.condition == ECondition.Sleep) != null;

   /*
   * 行動できないかどうか
   */
   public bool CantAction() => IsParalysis() || IsSleep();

   /**
   * もしまだかかっていなければ、指定した状態異常にする
   */
   public void MakeCondition(ECondition c)
   {
       if (c == ECondition.Normal) return;
       int idx = conditions.FindIndex(con => con.condition == c);
       ConditionInfo cinfo = System.Array.Find(conditionInfos, con => con.condition == c);
       if (idx < 0)
       {
           Condition condition = new Condition();
           condition.condition = c;
           condition.conditionRate = cinfo.firstClearRate;
           condition.addClearRate = cinfo.addClearRate;
           conditions.Add(condition);
       }
       else conditions[idx].conditionRate = cinfo.firstClearRate;
       switch (c)
       {
           /*   省略   */
       }
   }

   /**
   * 全ての不利な状態異常にかかる
   */
   public void MakeAllBadCondition()
   {
       foreach (ECondition c in System.Enum.GetValues(typeof(ECondition)))
           MakeCondition(c);
   }

   /**
   * 指定した状態異常を解除する
   */
   public bool ClearCondition(ECondition c)
   {
       int index = conditions.FindIndex(con => con.condition == c);
       if (index < 0) return false;
       conditions.RemoveAt(index);
       if (conditions.Count < 1) Message.Add(22, actorName);
       return true;
   }

   /**
   * 確率で状態異常を解除する
   */
   public void ClearConditionsWithRate()
   {
       for (int i = 0; i < conditions.Count; i++)
       {
           int isClear = Random.Range(0, 100);
           if (isClear < conditions[i].conditionRate) ClearCondition(conditions[i].condition);
           else conditions[i].conditionRate += conditions[i].addClearRate;
       }
   }

   /**
   * 全ての状態異常を解除する
   */
   public void ClearAllCondition(bool doNothingMessage = true)
   {
       if (conditions.Count > 0)
       {
           conditions.Clear();
           Message.Add(22, actorName);
           return;
       }
       if (doNothingMessage) Message.Add(18);
   }

   /*
   * 経験値を得る
   */
   public void GetXp(int xp)
   {
       /*   省略   */
   }

   /**
   * 現在の経験値が貯まっていたらレベルアップする
   */
   public void LvUpJudgment()
   {
       /*   省略   */
   }

   /**
   * ダメージを受ける
   */
   public void Damaged(int str, out int xp)
   {
       /*   省略   */
   }

   /**
   * ダメージを計算する
   */
   private static int CalcDamage(int str, int def)
   {
       /*   省略   */
   }

   /**
   * 死亡判定
   */
   private bool DeathJudgment()
   {
       /*   省略   */
   }

   /**
   * アイテムを装備する
   */
   public void EquipItem(Item it)
   {
       /*   省略   */
   }

   /**
   * 装備中のアイテムを外す
   */
   public void RemoveEquipment(Item it)
   {
       /*   省略   */
   }
}

変更した箇所は状態異常を新しく構造体にしたところと、それぞれの状態異常の治りやすさを設定をする構造体を作成したところです。
後はconditionRateが上がれば上がるほど解除されやすくなり、100で確実に解除されるようになったところでしょうか。逆に0以下の値を入力すると、絶対に解除されなくなるようにしました。
同時にActorSaveDataクラスとSaveDataManagerクラスも変更しておきましょう。

[System.Serializable]
public class ActorSaveData
{
   public Pos2D grid;
   public EDir direction;
   public Params parameter;
   public ActorParamsController.Condition[] conditions;
   public InventorySaveData inventory;
}
// SaveDataManagerクラスの以下のメソッドを変更
private ActorSaveData MakeActorData(Transform actor)
{
   ActorMovement move = actor.GetComponent<ActorMovement>();
   ActorSaveData actorSaveData = new ActorSaveData();
   actorSaveData.grid = new Pos2D();
   actorSaveData.grid.x = move.grid.x;
   actorSaveData.grid.z = move.grid.z;
   actorSaveData.direction = move.direction;
   ActorParamsController param = actor.GetComponent<ActorParamsController>();
   actorSaveData.parameter = param.GetParameter();
   actorSaveData.conditions = param.GetConditions();
   actorSaveData.inventory = MakeInventoryData(actor);
   return actorSaveData;
}

private void LoadActorData(ActorSaveData data, Transform actor)
{
   ActorMovement move = actor.GetComponent<ActorMovement>();
   move.SetPosition(data.grid.x, data.grid.z);
   move.SetDirection(data.direction);
   ActorParamsController param = actor.GetComponent<ActorParamsController>();
   param.SetParameter(data.parameter);
   param.SetConditions(data.conditions);
   LoadInventoryData(data.inventory, actor);
}

ビルドしたら、テストプレイする前に、プレイヤーと敵のCondition Infosのサイズを4にし、それぞれの状態異常の最初の治りやすさなどを設定しておきます。

スクリーンショット 2020-10-19 16.25.26

プレイヤーに使ってみたり投げたりして、ちゃんと状態異常になるか、あるいは状態異常が治るかどうかも見ておきましょう。
また、セーブされているかどうかも確認しておいて下さい。

スクリーンショット 2020-10-19 16.34.11

「怒り」の実装

準備が整ったところで、有利な状態異常を実装していきたいと思います。
今回は例として「怒り」という攻撃力が上昇する状態異常を実装しましょう。
最初に列挙定数EConditionに追記します。

Angry

ActorParamsControllerクラスを変更します。

// 定数を追記
private const float angryStrPer = 1.5f;

// メソッドを追加
/*
* 怒り状態かどうか
*/
public bool IsAngry() => conditions.Find(c => c.condition == ECondition.Angry) != null;

/**
* 計算済みパラメーターの力を返す
*/
public float GetStr()
{
   float str = parameter.str;
   if (IsAngry()) str *= angryStrPer;
   return str;
}

// メソッドを変更
public void Damaged(float str, out int xp)
{
   float d = CalcDamage(str, parameter.def + equipment.GetAllDef());
   parameter.hp -= d;
   if (parameter.id == EActor.PLAYER) Message.Add(1, actorName, d.ToString());
   else Message.Add(2, actorName, d.ToString());
   xp = 0;
   if (DeathJudgment()) xp = parameter.xp;
   else ClearCondition(ECondition.Sleep);
}

private static int CalcDamage(float str, float def)
{
   return Mathf.CeilToInt(str * Mathf.Pow(0.9375f, def));
}

public void MakeAllBadCondition()
{
   foreach (ECondition c in System.Enum.GetValues(typeof(ECondition)))
   {
       if (c == ECondition.Angry) break;
       MakeCondition(c);
   }  
}

ActorAttackクラスのDamageOpponentメソッドを変更します。

public void DamageOpponent(GameObject actor)
{
   if (actor == null) return;
   ActorParamsController param = GetComponent<ActorParamsController>();
   float str = param.GetStr() + param.equipment.GetAllAtk(); // この1行を変更
   int xp = 0;
   actor.GetComponent<ActorParamsController>().Damaged(str, out xp);
   GetComponent<ActorParamsController>().GetXp(xp);
   actor.GetComponent<ActorMovement>().TurnAround(GetComponent<ActorMovement>().direction);
}

最後にItemMovementクラスのHitActorメソッドを変更します。

private void HitActor(ActorParamsController tParam, GameObject actor)
{
   Item param = GetComponent<ItemParamsController>().parameter;
   if (actor == null)
   {
       /*   省略   */
   }
   else
   {
       ActorParamsController actorParam = actor.GetComponent<ActorParamsController>();
       if (param.dmg > 0 || param.condition != ECondition.Normal)
       {
           actor.GetComponent<ActorMovement>().TurnAround(tParam.GetComponent<ActorMovement>().direction);
           // 以下2行を変更
           float str = param.dmg;
           if (param.type != EItemType.Magic) str += tParam.GetStr();
           /*   省略   */
       }
       else if (param.hp > 0)
       {
           /*   省略   */
       }
   }
}

ビルドしたら、コンポーネントのCondition Infosの項目にAngryを追加します。
もし3ターン確定で効果が続くようにしたければ、

スクリーンショット 2020-10-19 21.15.57

とすればいいでしょう。
一度テストプレイしてみます。

スクリーンショット 2020-10-19 21.23.26

Condition(インスペクター上では「条件」と表示されます)にAngryを設定します。そうしたら上記のようにダメージが増え(=力が増加)ればOKです。
投げた時もダメージが増えれば大丈夫です。

怒りのエフェクトの追加

まず最初にメッセージデータを追加します。

スクリーンショット 2020-10-19 17.42.26

怒りのエフェクトのプレハブを作成し、「AngryEffect」と名前をつけます。ループのチェックは外しておいて下さい。
EffectManagerクラスに追記します。

// 列挙定数に追加
public enum EType
{
   /*   省略   */
   Angry 
};

// パラメーターを追加
public GameObject angry;

// メソッドを変更
public void Play(EType type, GameObject target)
{
   switch (type)
   {
       /*   省略   */
       // 以下3行を追加
       case EType.Angry:
           playingEffects.Add(Instantiate<GameObject>(angry, target.transform));
           break;
   }
}

一度ビルドして、Effect Managerコンポーネントの項目Angryに先ほど作ったAngryEffectを指定します。
次にActorParamsControllerクラスのMakeConditionメソッドに追記します。

public void MakeCondition(ECondition c)
{
   if (c == ECondition.Normal) return;
   int idx = conditions.FindIndex(con => con.condition == c);
   ConditionInfo cinfo = System.Array.Find(conditionInfos, con => con.condition == c);
   if (idx < 0)
   {
       /*   省略   */
   }
   else conditions[idx].conditionRate = cinfo.firstClearRate;
   switch (c)
   {
       /*   省略   */
       // 以下4行を追加
       case ECondition.Angry:
           Message.Add(33, actorName);
           effect.Play(EffectManager.EType.Angry, gameObject);
           break;
   }
}

最後にActorActionクラスのKeyInputメソッドに追記します。

private void KeyInput()
{
   /*   省略   */
   if (isConfusion && action != EAct.KeyInput)
       effect.Play(EffectManager.EType.Confusion, gameObject);
   // 以下3行追記
   bool isAngry = actorParamsController.IsAngry();
   if (isAngry && action != EAct.KeyInput)
       effect.Play(EffectManager.EType.Angry, gameObject);
   if (action == EAct.TurnEnd) action = EAct.KeyInput;
   if (action != EAct.MoveBegin) actorMovement.Stop();
}

できたら、ビルドして確認してみます。

スクリーンショット 2020-10-19 22.29.55

怒りの状態になった時、エフェクトが表示されればOKです。

怒りを付与するアイテム

最後にキャラクターが怒り状態になるアイテムを作成したいと思います。
アイテムIDを追加します。

FOOD12 = 1012,

ExcelItemDataに新しいアイテムを追加します。

スクリーンショット 2020-10-19 22.38.45

出現確率も記述しておきます。

スクリーンショット 2020-10-19 22.44.00

テストしてみます。

スクリーンショット 2020-10-19 22.56.13

食べた時にプレイヤーが怒ったり、投げてキャラクターにぶつかった時そのキャラクターが怒ればOKです。

という訳で今回はここまでに致します。
次回はダンジョン生成のパラメーター設定で不具合を少々確認しているので、それを直すところから始めたいと思います。

この記事が気に入ったらサポートをしてみませんか?