見出し画像

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

前回の記事はこちら
前回は敵の種類を増やしました。

アイテムの追加

それでは今回はアイテムの追加方法についてまとめたいと思います。
今回追加するのは「知恵のリンゴ(階段の位置がわかる)」、「シャッフルの巻物(フィールド全体のキャラクターの座標をランダムに入れ替え)」、「火の巻物(部屋全体攻撃)」の3種類です。

プレハブの用意

巻物に関しては初ですのでプレハブを用意しておきましょう。
まず使用するスプライトをResources/Spriteフォルダに入れます。
そしてプレハブのItem1をFieldのItems階層下に配置し、名前を「Item6」にします。
そのItem6の中にあるSpriteのスプライトの項目にさっきSpriteフォルダに入れた画像を指定します。
最後にItem6をプレハブ化し、ヒエラルキータブ内からは削除して下さい。

EItemType及びEItemの設定

巻物という種類を増やしたので、EItemTypeも増やします。

// 列挙定数EItemTypeに以下を追記
Scroll,

普段はEItemを増やすだけで大丈夫です。

// 列挙定数EItemに以下を追記
FOOD11 = 1011,

SCROLL01 = 1061,
SCROLL02 = 1062,

ExcelItemDataファイルの設定

追加したアイテムの詳細を記述していきます。

スクリーンショット 2020-10-15 13.38.00

スクリーンショット 2020-10-15 13.58.56

「FindStairs」は階段の位置をマップに表示し、「Shuffle」はフィールド全体のキャラクターの位置を総入れ替えし、「RoomEffect」は部屋全体に効果があるという意味です。
変更し終わったらXLSX形式で保存し、Datasフォルダ内のものと入れ替えておきましょう。

ExcelAppearDataファイルの設定

アイテムの出現確率を記入していきます。

スクリーンショット 2020-10-15 14.24.27

スクリーンショット 2020-10-15 14.24.49

こちらも変更し終わったらXLSX形式で保存し、Datasフォルダ内のものと入れ替えておいて下さい。
ここで一回テストプレイしてみます。プレイヤーのインベントリに既に何か入っている場合は、一旦全て削除して再度入れ直してから行うようにして下さい。
期待通りに動けばOKです。

FindStairs機能の実装

通常のアイテムであればこれで十分なのですが、今回はそれぞれに特殊な機能がついています。その実装をしなくてはいけません。
ということで知恵のリンゴの機能からコードを書いていきましょう。
Fieldクラスに以下のメソッドを追加します。

/**
* 階段の位置をマッピングする
*/
public void MappingStairs()
{
   for (int i = 0; i < 2; i++)
   {
       Pos2D p = stairs.GetComponentsInChildren<ObjectPosition>()[i].grid;
       if (!IsCollide(p.x, p.z)) autoMapping.stairsImages[i].SetActive(true);
   }
}

次はActorUseItemsクラスのEatメソッドに追記します。

private void Eat(Item it)
{
   if (it.hp == 0 && it.food == 0)
   {
       if (it.extra.Contains("Recover")) param.ClearAllCondition();
       else if (it.extra.Contains("AllBad")) param.MakeAllBadCondition();
       // 以下2行を追求
       else if (it.extra.Contains("FindStairs") && param.parameter.id == EActor.PLAYER)
           GetComponentInParent<Field>().MappingStairs();
       else Message.Add(18);
       return;
   }
   if (it.hp > 0) param.RecoveryHp(it.hp);
   if (it.food > 0) param.RecoveryFood(it.food);
   if (it.extra.Contains("Recover")) param.ClearAllCondition(false);
   else if (it.extra.Contains("AllBad")) param.MakeAllBadCondition();
   // 以下2行を追求
   else if (it.extra.Contains("FindStairs") && param.parameter.id == EActor.PLAYER)
       GetComponentInParent<Field>().MappingStairs();
   param.MakeCondition(it.condition);
   if (it.condition == ECondition.Normal)
       effect.Play(EffectManager.EType.Recovery, gameObject);
}

プレイヤー以外が食べても反応がないようにしました。
ビルドして、テストしてみます。プレイヤーが知恵のリンゴを食べた時のみ、階段が表示されればOKです。

スクリーンショット 2020-10-15 15.43.47

Shuffle機能の実装

次はシャッフルの巻物の機能を実装したいと思います。
その前にメッセージデータを追加しておきます。

スクリーンショット 2020-10-16 10.06.14

Fieldクラスに新しいメソッドを追加します。

/**
* キャラクターの位置を総入れ替え
*/
public void Shuffle()
{
   Pos2D[] p = new Pos2D[enemies.transform.childCount + 1];
   p[0] = playerMovement.grid;
   int i = 1;
   foreach (var enemyMovement in enemies.GetComponentsInChildren<ActorMovement>())
   {
       p[i] = enemyMovement.grid;
       i++;
   }
   for (i = 0; i < p.Length; i++)
   {
       int r = Random.Range(i, p.Length);
       int x = p[i].x; int z = p[i].z;
       p[i].x = p[r].x; p[i].z = p[r].z;
       p[r].x = x; p[r].z = z;
   }
   playerMovement.SetPosition(p[0].x, p[0].z);
   i = 1;
   foreach (var enemyMovement in enemies.GetComponentsInChildren<ActorMovement>())
   {
       enemyMovement.SetPosition(p[i].x, p[i].z);
       Pos2D pos = enemyMovement.grid;
       i++;
   }
}

一度全てのキャラクターの位置を配列に入れ、その配列の中身を入れ替えて、対応する位置をそれぞれのキャラクターに設定しています。
次にObjectMovementクラスに以下のメソッドを追加します。

/**
* 指定したグリッド座標に合わせて位置を変更する
*/
public new void SetPosition(int xgrid, int zgrid)
{
   base.SetPosition(xgrid, zgrid);
   newGrid = grid;
}

baseで継承元のメソッドを呼び出しています。
最後にActorUseItemsクラスに追記します。

// メソッドを追加
/**
* 読む
*/
private bool Read(Item it)
{
   Message.Add(31, param.actorName, it.name);
   if (it.extra.Contains("Shuffle"))
   {
       GetComponentInParent<Field>().Shuffle();
       it.hp--;
       if (it.hp < 1)
       {
           Message.Add(14, it.name);
           if (inventory.Remove(it)) return true;
           GameObject item = GetComponentInParent<Field>().GetExistItem(move.grid.x, move.grid.z);
           if (item != null) Destroy(item);
       }
       return true;
   }
   return false;
}

// メソッドに追記
private bool UseEffect(Item it)
{
   /*   省略   */
   switch (it.type)
   {
       /*   省略   */
       case EItemType.Scroll:
           return Read(it);
   }
   isActive = true;
   return false;
}

テストプレイしてみます。

スクリーンショット 2020-10-16 15.57.32

自分と敵の位置が入れ替わればOKです(一度使っただけではプレイヤーが動かないこともあります。複数回やっても駄目な時はご連絡ください)。

RoomEffect機能の実装

最後に火の巻物の機能を実装したいと思います。
部屋に効果がある巻物ですので、もし使用者が部屋にいない場合は火の杖と同じ効果があることにします(直線上にいる敵に攻撃)。

Fieldクラスに新しいメソッドを追加します。

/*
* 同じ部屋内の自分以外のキャラクターを返す
* 部屋にいなかったり、自分しかいなければnullを返す
*/
public List<ActorMovement> GetOtherActorInRoom(ActorMovement actor)
{
   Pos2D ap = actor.grid;
   ObjectPosition room = GetInRoom(ap.x, ap.z);
   if (room == null) return null;
   List<ActorMovement> others = new List<ActorMovement>();
   Pos2D op = playerMovement.grid;
   if (!(ap.x == op.x && ap.z == op.z) && IsInRoomOf(room, op.x, op.z))
       others.Add(playerMovement);
   foreach (var enemyMovement in enemies.GetComponentsInChildren<ActorMovement>())
   {
       op = enemyMovement.grid;
       if (!(ap.x == op.x && ap.z == op.z) && IsInRoomOf(room, op.x, op.z))
           others.Add(enemyMovement);
   }
   if (others.Count > 0) return others;
   return null;
}

ItemMovementクラスのThrowingメソッドに追記します。

public bool Throwing(EDir d, ActorParamsController tParam, Pos2D pos = null)
{
   if (effect == null) effect = GetComponentInParent<EffectManager>();
   if (!isThrowing && !effect.IsPlaying())
   {
       if (pos == null) SetThrowPosition(d);
       else newGrid = pos;
       isThrowing = true;
       return false;
   }
   /*   省略   */
   return false;
}

ObjectMovementクラスを変更します。

// 以下のパラメーターとメソッドを変更
private float complementFrame = 0;

void Start()
{
   complementFrame = maxPerFrame * 60;
}

private Pos2D Move(Pos2D currentPos, Pos2D newPos, ref int frame)
{
   if (complementFrame == 0) Start();
   float px1 = Field.ToWorldX(currentPos.x);
   /*   省略   */
   return currentPos;
}

ActorUseItemsスクリプトを以下のように変更して下さい。

// スクリプトの最初に記述
using System.Collections.Generic;

// クラスのパラメーターに追記
private List<GameObject> usingItems = null;

// メソッドを変更
public bool Use(Item it)
{
   if (!isActive)
   {
       if (it.type == EItemType.Wand || it.type == EItemType.Scroll) return UseEffect(it);
       inventory.Remove(it);
   }
   return UseEffect(it);
}

public bool PickUpUse(Item it)
{
   if (!isActive)
   {
       if (it.type == EItemType.Wand || it.type == EItemType.Scroll) return UseEffect(it);
       GameObject item = GetComponentInParent<Field>().GetExistItem(move.grid.x, move.grid.z);
       if (item != null) Destroy(item);
   }
   return UseEffect(it);
}

private bool Shot(Item it)
{
   if (usingItem == null)
   {
       if (it.type == EItemType.Wand) Message.Add(21, param.actorName, it.name);
       ExcelItemData database = Resources.Load<ExcelItemData>("Datas/ExcelItemData");
       Item item = database.Goods.Find(n => n.id == it.shot).Get();
       return Throw(item);
   }
   if (Throw(null))
   {
       /*   省略   */
   }
   return false;
}

private bool Read(Item it)
{
   Field field = GetComponentInParent<Field>();
   if (it.extra.Contains("Shuffle"))
   {
       /*   省略   */
   }
   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);
           ExcelItemData database = Resources.Load<ExcelItemData>("Datas/ExcelItemData");
           Item item = database.Goods.Find(n => n.id == it.shot).Get();
           GameObject itemObj = (GameObject)Resources.Load("Prefabs/" + item.prefab);
           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;
           it.hp--;
           if (it.hp < 1)
           {
               Message.Add(14, it.name);
               if (inventory.Remove(it)) return true;
               GameObject item = field.GetExistItem(move.grid.x, move.grid.z);
               if (item != null) Destroy(item);
           }
           return true;
       }
   }
   return false;
}

基本的な流れは杖の時と同じです。ただ、それを複数にするに辺り、投げるアイテムをリスト化し、それぞれを動かすようにしただけです。
テストしてみて、以下のように複数敵に攻撃できればOKです。

スクリーンショット 2020-10-17 1.51.46

(実はこの辺り変更し過ぎて記述に漏れがあるかもしれません......。もしその時はお手数ですがご連絡ください)

ということで今回はここまでにします。
次回は細かい箇所の改善をしていきたいと思います。

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