ひとりでのアプリ開発 - fineの備忘録 -

ひとりでアプリ開発をするなかで起こったことや学んだことを書き溜めていきます

Unity - タイプ相性を考慮したダメージ計算の仕組みを作る

初めに

 ポケモンのタイプ相性のようなダメージ計算の仕組みを作り方をまとめます。



Scriptable Object かを使ってタイプ相性を作る

 次のような仕様にします。

(仕様)

  • 攻撃をする属性、攻撃を受ける属性、相性の効果を Scriptble Object に入力できるようにする
  • 属性は enum でまとめておく
  • 攻撃をする属性と攻撃を受ける属性を引数にもつ相性の効果を返す関数を用意する

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(fileName = "NewTypeMatchups", menuName = "MyGame/TypeMatchups", order = 1)]
public class TypeAttribute : ScriptableObject
{
    public TypeMatchup[] matchups;

    [System.Serializable]
    public class TypeMatchup
    {
        public TypeAttribute.TypeAttributes attackAttribute; // 攻撃属性
        public TypeAttribute.TypeAttributes targetAttribute;  //攻撃を受ける属性
        public float effectiveness; // 相性の効果

        public TypeMatchup(TypeAttribute.TypeAttributes attackAttribute, TypeAttribute.TypeAttributes targetAttribute, float effectiveness)
        {
            this.attackAttribute = attackAttribute;
            this.targetAttribute = targetAttribute;
            this.effectiveness = effectiveness;
        }
    }

    public enum TypeAttributes
    {
        None, // 属性なし
        Fire, // 火属性
        Ice, // 氷属性
        Electric, // 電気属性
        Poison, //毒属性
    }

    // タイプ相性の効果を返す
    public float GetEffectiveness(TypeAttribute.TypeAttributes attackAttribute, TypeAttribute.TypeAttributes targetAttribute)
    {
        foreach (TypeMatchup matchup in matchups)
        {
            if (matchup.attackAttribute == attackAttribute && matchup.targetAttribute == targetAttribute)
            {
                return matchup.effectiveness;
            }
        }
        return 1f; // 一致する相性がない場合は通常の効果を返す
    }
}

 Assets で右クリックし、MyGame/TypeMatchups から Scriptble Object を生成します。

 生成した Scriptable Object からタイプ相性が入力できるようになっていれば ok です。


タイプ相性を返す関数を他のスクリプトから呼び出せるようにしておく

 タイプ相性を返す関数(先ほどの GetEffectiveness)を他のスクリプトから呼び出せるようにしておきます。

 先ほど生成した Scriptble Object を取得する必要があるので、Addressables を使います。Resources でも Assets 内のファイルを参照できますが、公式非推奨なので、Addressables を使ってみます。

(参考:公式非推奨な理由、Addressables の使い方)
light11.hatenadiary.com

 Addressables は Package Manager からインストールできます。

 Window > Asset Management > Addressables > Groups をクリックし、Addressables Groups ウィンドウを開きます。先ほどの Scriptable Object をドラッグ&ドロップし、アドレスを登録します。

 Inspector からもアドレスを確認できます。

 スクリプトを書いていきます。

スクリプトのポイント)

  • 他のスクリプトから使えるように新しい名前空間を作る
  • addressables を使って、先ほどの Scriptable Object を取得する
  • magicAttribute.GetEffectiveness を呼び出す関数を作る

using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine.AddressableAssets;//Addressablesを使うのに必要
using UnityEngine.ResourceManagement.AsyncOperations;  //asyncを使うのに必要
using UnityEngine;

namespace MethodIndex
{
    public class BattleMethod
    {
        public async Task<float> GetEffectiveness(TypeAttribute.TypeAttributes attackAttribute, TypeAttribute.TypeAttributes targetAttribute)
        {
            TypeAttribute typeAttribute = await Addressables.LoadAssetAsync<TypeAttribute>("Addressablesで登録されたアドレス").Task;
            return typeAttribute.GetEffectiveness(attackAttribute, targetAttribute);
        }
    }
}

ダメージ計算をするスクリプト

スクリプトのポイント)

  • 自分の最大HP, 現在のHP、自分のタイプを変数にしておく
  • ダメージ計算時にタイプ相性の効果を元のダメージに掛けたものをHPから減算する
  • タイプ相性の効果を取得する関数を呼び出すために、名前空間に先ほど自作したものを追加する

using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using MethodIndex;  //自作した名前空間

public class EnemyController : MonoBehaviour
{
    //MethodIndexの関数を使うためのもの
    BattleMethod BMethod;
    public MagicStats magicStats;

    public float MaxHp;
    private float hp;
    private bool isAlive;
    public TypeAttribute.TypeAttributes myType;

    // Start is called before the first frame update
    void Start()
    {
        BMethod = new BattleMethod();
        hp = MaxHp;
        isAlive = true;

        //Fireのタイプで100ダメージを与える
        var _ = TakeDamage(100, TypeAttribute.TypeAttributes.Fire);
    }

    public async Task TakeDamage(float attackPower, TypeAttribute.TypeAttributes type)
    {
        if (!isAlive) return;

        float damage = await CalculateDamage(attackPower, type);
        hp -= damage;

        Debug.Log($"maxHp{MaxHp}, hp{hp}");

        if (hp <= 0)
        {
            Die();
        }
    }

    private async Task<float> CalculateDamage(float attackPower, TypeAttribute.TypeAttributes type)
    {
        Task<float> task = BMethod.GetEffectiveness(type, myType);
        float result = await task;
        float damage = attackPower * result;
        return Mathf.Max(0f, damage);
    }

    private void Die()
    {
        isAlive = false;
        // 敵キャラクターを消去する処理など
    }
}

参考になるかもしれない過去の記事

fineworks-fine.hatenablog.com
fineworks-fine.hatenablog.com

最後に

 ダメージ計算のためのスクリプトは、今回は操作キャラ対敵キャラのような想定で作っています。ゲームの仕様によっては、自分のタイプやHPなども Scriptable Object から取得できるようにしておいたほうが便利だと思います。