見出し画像

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

前回の記事はこちら
前回は敵のアイテムドロップを実装しました。

ドリルの用意

今回は壁を崩せるアイテム、「ドリル」を実装したいと思います。
まず最初にドリルのプレハブを用意します。
今あるアセットではドリルに因んだものがなかったので、筆者は代わりに剣の画像を使用させて頂くことにしました。
EItemとEItemTypeに以下のように記述します。

// EItemに追加
DRILL01 = 1901,

// EItemTypeに追加
Drill,

そしてExcelItemDataファイルに以下を追記します。

スクリーンショット 2020-10-25 11.38.03

最後にこのドリルは普通に出現させたいので、ExcelAppearDataにも記述しておきます。

スクリーンショット 2020-10-25 11.45.15

スクリーンショット 2020-10-25 11.42.52

流石に+99はやり過ぎだったので上限を10にしました。
取り込んだらインベントリを両方とも一旦空にしておきましょう。

ドリル機能の実装

用意が整ったところで早速ドリルの機能を実装していきたいと思います。
Fieldクラスを変更します。

// 新しいメソッドの追加
/**
* 指定の座標がフィールドの縁かどうかをチェック
*/
private bool IsEdge(int xgrid, int zgrid)
{
   return xgrid <= 0 || zgrid <= 0 || xgrid >= (map.width - 1) || zgrid >= (map.height - 1);
}

/**
* 指定した座標が壁なら、壁を壊し、trueを返す
* 壊せなかったり、壁ではないなら、falseを返す
*/
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)
           {
               Destroy(b.gameObject);
               break;
           }
       }
       return true;
   }
   return false;
}

// メソッドを変更
public void Create(Array2D mapdata)
{
   map = mapdata;
   /*   省略   */
   floor.transform.position = new Vector3(floorx, 0, floorz);
   for (int z = 0; z < map.height; z++)
   {
       for (int x = 0; x < map.width; x++)
       {
           if (map.Get(x, z) > 0)
           {
               // 中身を変更
               GameObject block = Instantiate(wall);
               block.transform.localScale = new Vector3(oneTile, 2, oneTile);
               block.GetComponent<ObjectPosition>().SetPosition(x, z);
               block.transform.SetParent(floor.transform.GetChild(0));
           }
       }
   }
   // ShowGridEffects();
}

Fieldクラスに壁を壊すメソッドを加えました。また、それに伴いWallオブジェクトにObjectPositionスクリプトをアタッチすることにしました。
次にActorUseItemsクラスを変更します。

// 新しいメソッドを追加
/**
* アイテムが壊れるかどうか
*/
private void BrokenJudgement(Item it)
{
   it.hp--;
   if (it.hp < 1)
   {
       Message.Add(14, it.name);
       if (inventory.Remove(it)) return;
       GameObject item = GetComponentInParent<Field>().GetExistItem(move.grid.x, move.grid.z);
       if (item != null) Destroy(item);
   }
}

/**
* 壁を崩す
*/
private bool Dig(Item it)
{
   Message.Add(34, param.actorName, it.name);
   Pos2D newGrid = DirUtil.GetNewGrid(move.grid, move.direction);
   Field field = GetComponentInParent<Field>();
   if (!field.BreakWall(newGrid.x, newGrid.z)) Message.Add(18);
   BrokenJudgement(it);
   return true;
}

// メソッドを変更
public bool Use(Item it)
{
   if (!isActive)
   {
       if (it.type == EItemType.Wand || it.type == EItemType.Scroll || it.type == EItemType.Drill)
           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 || it.type == EItemType.Drill)
           return UseEffect(it);
       /*   省略   */
   }
   return UseEffect(it);
}

private bool UseEffect(Item it)
{
   if (isActive && !effect.IsPlaying())
   {
       /*   省略   */
   }
   if (isActive) return false;
   switch (it.type)
   {
       case EItemType.Food:
           Message.Add(5, param.actorName, it.name);
           Eat(it);
           break;
       case EItemType.Portion:
           Message.Add(16, param.actorName, it.name);
           Eat(it);
           break;
       case EItemType.Wand:
           return Shot(it);
       case EItemType.Scroll:
           return Read(it);
       case EItemType.Drill:
           return Dig(it);
   }
   isActive = true;
   return false;
}

private bool Shot(Item it)
{
   if (usingItem == null)
   {
       /*   省略   */
   }
   if (Throw(null))
   {
       BrokenJudgement(it);
       return true;
   }
   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)
       {
           /*   省略   */
       }
       if (usingItems.Count > 0)
       {
           /*   省略   */
       }
       if (usingItems.Count == 0)
       {
           usingItems = null;
           BrokenJudgement(it);
           return true;
       }
   }
   return false;
}

種類にドリルが増えたのでその判定及び機能の追加と、アイテムが壊れるかどうかの判定をメソッドに切り分けたので該当箇所の置き換えを行いました。
後、Itemクラスにバグがあったので修正します。すみません......。

public int atk { get{ return baseData.atk + extData.atk; } set { baseData.atk = value - extData.atk; } }
public int def { get { return baseData.def + extData.def; } set { baseData.def = value - extData.def; } }
public int hp { get { return baseData.hp + extData.hp; } set { baseData.hp = value - extData.hp; } }
public int food { get { return baseData.food + extData.food; } set { baseData.food = value - extData.food; } }

原因は恐らくgetでbaseDataの値にextDataの値を加えた状態で取得した値を更にbaseDataの値に代入していた為だと思います。ならばextDataの値を引けば大丈夫なはずです。
実行してみます。以下のようになればOKです。

スクリーンショット 2020-10-25 18.55.05

フィールドの縁までの間は全て崩せると思います。逆に縁も崩せる場合はIsEdgeメソッドの条件式が間違っているので確認しておきましょう。

崩すエフェクト

折角なので壁を崩した時にエフェクトも表示してみましょう。
Fieldクラスのメソッドを変更して下さい。

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)
           {
               // 中身を追加
               GameObject effectObj = (GameObject)Resources.Load("Prefabs/HitItemEffect");
               GameObject effect = Instantiate(effectObj, transform);
               effect.transform.position += b.transform.position;
               Destroy(b.gameObject);
               break;
           }
       }
       return true;
   }
   return false;
}

スクリーンショット 2020-10-25 19.13.57

筆者はHitItemEffectを使い回すことにしました。良い感じです。

RandBroken機能の実装

最後に使用回数が増えるごとに壊れる確率の上がるRandBroken機能を実装したいと思います。
ActorUseItemsクラスを変更します。

// メソッドを追加
/*
* アイテムが壊れた
*/
private void BrokenItem(Item it)
{
   Message.Add(14, it.name);
   if (inventory.Remove(it)) return;
   GameObject item = GetComponentInParent<Field>().GetExistItem(move.grid.x, move.grid.z);
   if (item != null) Destroy(item);
}

// メソッドを変更
private void BrokenJudgement(Item it)
{
   it.hp--;
   if (it.extra.Contains("RandBroken"))
   {
       if (Random.Range(0, it.hp + 1) == 0) BrokenItem(it);
   }
   if (it.hp < 1) BrokenItem(it);
}

試してみます。
ドリルが指定回数前に砕け散ればOKです。
因みに杖や巻物のextraに「RandBroken」と記述すれば同じような判定にすることができます。

というところで今回はここまでにします。
次回はコード整理から始めようと思っています。
余裕があればインベントリウィンドウ周りで説明を省いた箇所も、できる範囲で解説するかもしれません。よろしくお願いします。


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