見出し画像

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

前回の記事はこちら
前回はドリルを実装しました。

ShotメソッドとReadメソッドの共通部分の切り分け

今回はコードの整理から始めたいと思います。
まずはActorUseItemsクラスのShotメソッドとReadメソッドの共通部分の切り分けから。

// 新しいメソッドを追加
/**
* 発射するアイテムを返す
*/
private void ShotItem(Item it, out Item shotItem, out GameObject shotItemObj)
{
   ExcelItemData database = Resources.Load<ExcelItemData>("Datas/ExcelItemData");
   shotItem = database.Goods.Find(n => n.id == it.shot).Get();
   shotItem.dmg = it.atk;
   shotItemObj = (GameObject)Resources.Load("Prefabs/" + shotItem.prefab);
}

// メソッドを変更
private bool Shot(Item it)
{
   if (usingItem == null)
   {
       if (it.type == EItemType.Wand) Message.Add(21, param.actorName, it.name);
       Item item; GameObject dummy;
       ShotItem(it, out item, out dummy);
       return Throw(item);
   }
   if (Throw(null))
   {
       BrokenJudgement(it);
       return true;
   }
   return false;
}

private bool Read(Item it)
{
   Field field = GetComponentInParent<Field>();
   if (it.extra.Contains("Shuffle"))
   {
       Message.Add(31, param.actorName, it.name);
       GetComponentInParent<Field>().Shuffle();
       Message.Add(32);
       BrokenJudgement(it);
       return true;
   }
   if (it.extra.Contains("RoomEffect"))
   {
       if (usingItem != null) return Shot(it);
       if (usingItems == null)
       {
           Message.Add(31, param.actorName, it.name);
           List<ActorMovement> actors = field.GetOtherActorInRoom(move);
           if (actors == null) return Shot(it);
           Item item; GameObject itemObj;
           ShotItem(it, out item, out itemObj);
           usingItems = new List<GameObject>();
           foreach (ActorMovement actor in actors)
           {
               usingItems.Add(Instantiate(itemObj, field.items.transform));
               usingItems[usingItems.Count - 1].GetComponent<ItemMovement>().SetPosition(move.grid.x, move.grid.z);
               usingItems[usingItems.Count - 1].GetComponent<ItemParamsController>().SetParams(item);
               Pos2D pos = new Pos2D();
               pos.x = actor.grid.x; pos.z = actor.grid.z;
               usingItems[usingItems.Count - 1].GetComponent<ItemMovement>().Throwing(move.direction, param, pos);
           }
           return false;
       }
       if (usingItems.Count > 0)
       {
           List<GameObject> removeItems = new List<GameObject>();
           foreach (GameObject item in usingItems)
           {
               if (item.GetComponent<ItemMovement>().Throwing(move.direction, param))
                   removeItems.Add(item);
           }
           foreach (GameObject item in removeItems) usingItems.Remove(item);
           return false;
       }
       if (usingItems.Count == 0)
       {
           usingItems = null;
           BrokenJudgement(it);
           return true;
       }
   }
   return false;
}

あまり変わった感じはありませんが、多少変更はしやすくなった気がします。

全てのエフェクトをEffectManagerで管理する

次にEffectManagerで管理できていない二つの箇所を管理できるようにします。
まずEffectManagerクラスを以下のように変更して下さい。

// ETypeに追記
public enum EType
{
   /*   省略   */
   Death
};

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

// メソッドを変更
public void Play(EType type, GameObject target, Vector3 position = new Vector3())
{
   switch (type)
   {
       /*   省略   */
       case EType.Death:
           playingEffects.Add(Instantiate<GameObject>(death, target.transform));
           break;

   }
   playingEffects[playingEffects.Count - 1].transform.position += position;
}

コンポーネントの消滅にはDeathEffectを指定しておきましょう。また、DeathEffect内エフェクトの「アクションを停止」には「なし」を選択しておきます。
FieldクラスのBreakWallメソッドを変更します。

public bool BreakWall(int xgrid, int zgrid)
{
   if (IsCollide(xgrid, zgrid) && !IsEdge(xgrid, zgrid))
   {
       map.Set(xgrid, zgrid, 0);
       foreach (ObjectPosition b in floor.transform.GetChild(0).GetComponentsInChildren<ObjectPosition>())
       {
           if (b.grid.x == xgrid && b.grid.z == zgrid)
           {
               // ここを変更
               GetComponent<EffectManager>().Play(EffectManager.EType.HitItem, gameObject, b.transform.position);
               Destroy(b.gameObject);
               break;
           }
       }
       return true;
   }
   return false;
}

ActorParamsControllerのDeathJudgementメソッドを変更します。

private bool DeathJudgment()
{
   if (parameter.hp <= 0)
   {
       if (parameter.id > EActor.PLAYER)
       {
           /*   省略   */
       }
       Field field = GetComponentInParent<Field>();
       field.GetComponent<EffectManager>().Play(EffectManager.EType.Death, field.gameObject, transform.position);
       Destroy(gameObject);
       return true;
   }
   return false;
}

今まで通りエフェクトが表示されればOKです。

やられモーションの追加

さて、今回の本題です。
敵が攻撃を受けた時に何らかのモーションがあると良いですね。
という訳で実装してみましょう。
(ファイティングユニティちゃんにもダメージを受けた時のモーションがあればよかったのですが、なさそうなので今回は諦めます)
まずはEnemyAnimator1(又は2)に新しいTriggerパラメーターを追加します。
名前を「Damaged」にして下さい。
次に空のステートを作成し、以下のように線を繋げましょう。

スクリーンショット 2020-10-26 16.46.03

そして、AnyStateから伸びる線をクリックし、ConditionsにDamagedを設定します。
最後にアニメーションを設定して下さい。
ファイティングユニティちゃんには攻撃が当たった時のアニメーションがありませんので、PlayerAnimatorはDamagedステートの作成を省略します(パラメーターは作成しておきます)。

ダメージを受けた時アニメーション再生

それではダメージを受けた時の処理を書いていきたいと思います。
ダメージを受けた時のアニメーションはActorAttackクラスで再生することにします。

// パラメーターを追加
private readonly int hashDamagedPara = Animator.StringToHash("Damaged");

// メソッドを追加
/**
* 被ダメージアニメーション開始
*/
public void Damaged()
{
   animator.SetTrigger(hashDamagedPara);
   animator.speed = ActorAction.runSpeedRate;
}

// メソッドを変更
public void DamageOpponent(GameObject actor)
{
   if (actor == null) return;
   ActorParamsController param = GetComponent<ActorParamsController>();
   float str = param.GetStr() + param.equipment.GetAllAtk();
   int xp = 0;
   actor.GetComponent<ActorParamsController>().Damaged(str, out xp);
   GetComponent<ActorParamsController>().GetXp(xp);
}

振り向くのが若干遅いのが気になっていたので、今ここで修正します。
ActorActionクラスに以下を追記して下さい。

private void ActBegin()
{
   actorAttack.Attack();
   // 以下7行を追記
   Pos2D grid = DirUtil.GetNewGrid(actorMovement.grid, actorMovement.direction);
   GameObject actor = GetComponentInParent<Field>().GetExistActor(grid.x, grid.z);
   if(actor != null)
   {
       actor.GetComponent<ActorMovement>().TurnAround(actorMovement.direction);
       actor.GetComponent<ActorAttack>().Damaged();
   }
   action = EAct.Act;
}

攻撃と同時に振り向かせ、アニメーションさせることにしました。
実行してみます。

スクリーンショット 2020-10-26 1.12.07

敵に攻撃した直後にダメージを受けた時のアニメーションが流れました。

アイテム使用時のアニメーション再生

アイテムを投げた時や飛び道具を使った時にもアニメーションが再生されると良いですね。
ItemMovementクラスのメソッドを変更します。

private bool HitActor(ActorParamsController tParam, GameObject actor)
{
   Item param = GetComponent<ItemParamsController>().parameter;
   if (actor == null || actor.Equals(tParam.gameObject))
   {
       if (param.type == EItemType.Magic)
       {
           Destroy(gameObject);
           return true;
       }
       else Message.Add(15, param.name);
       return false;
   }
   else
   {
       ActorParamsController actorParam = actor.GetComponent<ActorParamsController>();
       if (param.dmg > 0 || param.condition != ECondition.Normal)
       {
           actor.GetComponent<ActorMovement>().TurnAround(tParam.GetComponent<ActorMovement>().direction);
           actor.GetComponent<ActorAttack>().Damaged();
           float str = param.dmg;
           if (param.type != EItemType.Magic) str += tParam.GetStr();
           if (str > 0)
           {
               int xp = 0;
               actorParam.Damaged(str, out xp);
               tParam.GetXp(xp);
           }
           if (param.extra.Contains("AllBad")) actorParam.MakeAllBadCondition();
           actorParam.MakeCondition(param.condition);
           if ((int)param.id > 1000)
           {
               if (param.type != EItemType.Magic) Message.Add(14, param.name);
               Destroy(gameObject);
               return true;
           }
           else actor.GetComponent<ActorUseItems>().PickUp(param);
       }
       else if (param.hp > 0)
       {
           actorParam.RecoveryHp(param.hp);
           if (param.extra.Contains("Recover")) actorParam.ClearAllCondition(false);
           Message.Add(14, param.name);
           Destroy(gameObject);
           return true;
       }
   }
   return true;
}

public bool Throwing(EDir d, ActorParamsController tParam, Pos2D pos = null)
{
   if (effect == null) effect = GetComponentInParent<EffectManager>();
   if (!isThrowing)
   {
       if (pos == null) SetThrowPosition(d);
       else newGrid = pos;
       isThrowing = true;
       return false;
   }
   Field field = GetComponentInParent<Field>();
   if (isPlayingEffect && !effect.IsPlaying())
   {
       if (!gameObject.Equals(field.GetExistItem(grid.x, grid.z)))
           Destroy(gameObject);
       isPlayingEffect = false;
       return true;
   }
   if (isPlayingEffect) return false;
   if (!isPlayingEffect  && Moving() == EAct.MoveEnd)
   {
       GameObject actor = field.GetExistActor(grid.x, grid.z);
       HitEffect(actor);
       isThrowing = false;
       isPlayingEffect = effect.IsPlaying();
       return HitActor(tParam, actor);
   }
   return false;
}

ActorUseItemsクラスのReadメソッドも変更します。

private bool Read(Item it)
{
   Field field = GetComponentInParent<Field>();
   if (it.extra.Contains("Shuffle"))
   {
       Message.Add(31, param.actorName, it.name);
       GetComponentInParent<Field>().Shuffle();
       Message.Add(32);
       BrokenJudgement(it);
       return true;
   }
   if (it.extra.Contains("RoomEffect"))
   {
       if (usingItem != null) return Shot(it);
       if (usingItems == null)
       {
           Message.Add(31, param.actorName, it.name);
           List<ActorMovement> actors = field.GetOtherActorInRoom(move);
           if (actors == null) return Shot(it);
           Item item; GameObject itemObj;
           ShotItem(it, out item, out itemObj);
           usingItems = new List<GameObject>();
           foreach (ActorMovement actor in actors)
           {
               usingItems.Add(Instantiate(itemObj, field.items.transform));
               usingItems[usingItems.Count - 1].GetComponent<ItemMovement>().SetPosition(move.grid.x, move.grid.z);
               usingItems[usingItems.Count - 1].GetComponent<ItemParamsController>().SetParams(item);
               Pos2D pos = new Pos2D();
               pos.x = actor.grid.x; pos.z = actor.grid.z;
               usingItems[usingItems.Count - 1].GetComponent<ItemMovement>().Throwing(move.direction, param, pos);
           }
           return false;
       }
       if (usingItems.Count > 0)
       {
           List<GameObject> removeItems = new List<GameObject>();
           foreach (GameObject item in usingItems)
           {
               if (item == null)
                   removeItems.Add(item);
               else if (item.GetComponent<ItemMovement>().Throwing(move.direction, param))
                   removeItems.Add(item);
           }
           foreach (GameObject item in removeItems) usingItems.Remove(item);
       }
       if (usingItems.Count == 0)
       {
           usingItems = null;
           BrokenJudgement(it);
           return true;
       }
   }
   return false;
}

更にEffectManagerクラスのUpdateメソッドも変更します。

private void Update()
{
   if (playingEffects.Count < 1) return;
   List <GameObject> tempEffects = new List<GameObject>();
   foreach (var effect in playingEffects)
   {
       tempEffects.Add(effect);
   }
   foreach (var effect in tempEffects)
   {
       if (effect == null)
       {
           playingEffects.Remove(effect);
           return;
       }
       ParticleSystem particle = effect.GetComponentInChildren<ParticleSystem>();
       if (particle != null && particle.isStopped)
       {
           Destroy(effect);
           playingEffects.Remove(effect);
           return;
       }
       Animator animator = effect.GetComponentInChildren<Animator>();
       if (animator != null && animator.GetCurrentAnimatorStateInfo(0).normalizedTime >= 1)
       {
           Destroy(effect);
           playingEffects.Remove(effect);
           return;
       }
   }
}

ついでにアイテムがぶつかった時に発生していた遅延も改善しました。
飛び道具を使ってみます。

アイテム攻撃

(もしバグがあったらお知らせください)

Step9&10-4で説明を割愛した部分について

最後にこのコードの解説をしようと思っていたのですが、その余裕がないのとコードのコメントを読んでいる内に皆様理解できていそうだなと判断した為(それに筆者もそれ以上の詳しい解説ができるかどうか不安な為)、やっぱり省略します。
もしわからないところがあったらコメントでよろしくお願いします。
もちろんこの箇所以外のところでも連絡して頂ければ、力の及ぶ範囲で対応します(その際に詳細なコードやUnityエディタの画像をteratailなどに上げて頂ければより力になれると思います)。
スキまで頂いていたのにこのような結果になってしまい、本当に申し訳ありません.....!

次回は罠の作成かインベントリソート機能のどちらかを実装したいと思います。
こんな筆者ですが、どうぞ引き続きよろしくお願い致します。

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