ゲーム化!tomo_manaのブログ

ゲーム化!tomo-manaのブログ

Unityでゲームを作る方法について紹介しています

Unity学習#28 ランダムエンカウント (Unity 2019.4.4f1)

今回は、ランダムエンカウント(一定歩数歩いたら敵が出る)に挑戦しました。


UnityEngine.Random については、別の記事にまとめました。
tomo-mana.hatenablog.com

以下の2種類を試しました。
A) 1歩あるくごとに乱数を振り出す(1歩ごと乱数振出)
B) 目標歩数の補正値として乱数を振り出す(目標歩数+補正値)
⇒ AよりもBの方がゲームバランスが取りやすそうでした。

乱数データ

Random.InitState(255) を指定した後に振り出される300回分の乱数データを使いました。
f:id:tomo_mana:20210219232112p:plain

A:1歩ごと乱数振出

アルゴリズム

歩数の整数部が前回と異なる場合、エンカウント処理をします。エンカウントは20歩に1回(乱数の値が {0〜0.05} ならエンカウント)を目標にします。

(1)前回と今回の歩数が違うなら
(2)前回との今回の歩数の差だけ、乱数を振り出す
(3)乱数が1回でも閾値以下ならエンカウント回数+1

結果

乱数300回振り出す間に13回エンカウントし、平均エンカウント歩数は+3σ(99.7%)で 20.69±74.15歩、最小=2, 最大=78 でした。無限に繰り返せば平均値に収束すると思いますが、エンカウント歩数の最大値が予測できないのが弱点と思われます。

f:id:tomo_mana:20210218230613p:plain
ランダムエンカウントー1歩ごとに乱数振出

尚、処理速度の面で以下を懸念していましたが、大丈夫でした。
⚫︎1歩ごとに1回エンカウント処理は処理が間に合うか→ok
⚫︎ナナメ歩きすると2歩としてカウントアップされることが、処理が追従できるか→ok

B:目標歩数+補正値

アルゴリズム

A と同じく目標歩数は20歩とします。
a) 平均20±5歩以内に敵が出る確率は80%(10回中8回は15歩~25歩の間にエンカウント)
b) 45歩以内には100%敵が出現(10回中2回は25歩~45歩の間にエンカウント)

途中の式は省略しますが、それぞれ以下の式になります。
a) エンカウント歩数 = 12.5f x 乱数 + 12.5f
b) エンカウント歩数 = 100.0 x 乱数 + 25.0f

結果

A に合わせて13回分のエンカウント歩数を計算しました。平均エンカウント歩数は+3σ で 21.15±30.22歩、min=13, max=44でした。理論上は20歩と35歩を中心値としたふた山の平均に収束します。

f:id:tomo_mana:20210218230640p:plain
目標歩数+補正値

コード

第27回のコードを少し改造して、以下のようにしました。

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

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

public class PlayerControl2 : MonoBehaviour
{
    // キー入力
    private Vector2 input;
    
    // 移動
    private float speed;
    private Rigidbody2D rigidBody;
    
    // 歩数カウンタ
    [SerializeField]
    int stepsInt = 0;
    private float steps = 0.0f;
    
    // ランダムエンカウント
    [SerializeField]
    ulong encountered = 0;
    [SerializeField]
    ulong nextEncounterStep = 0;
    [SerializeField, Range(0f, 1f)]
    float encountRate = 0.05f;
    
    // Start is called before the first frame update
    void Start()
    {
        speed = 0.1f;
        rigidBody = GetComponent<Rigidbody2D>();
        animator = GetComponent<Animator>();
        steps = 0;
        
        UnityEngine.Random.InitState(255);
        NextEncount();
    }

    public void Move(Vector2 v)
    {
        input = v;
    }
    
    void FixedUpdate() {
        // 移動
        if (input == Vector2.zero){
            return;
        }
        rigidBody.position += input * speed;
        
        /* 中略 */
        
        // 歩数カウンタ
        float localSteps = steps;
        if( input.x != 0 ){
            localSteps += speed;
        }
        if( input.y != 0 ){
            localSteps += speed;
        }
        
        // ランダムエンカウント
        RandomEncount( localSteps );
    }
    
    void RandomEncount( float localSteps )
    {
        steps = localSteps;
        stepsInt = (int)steps;
        
        if ( steps > nextEncounterStep ){
            Encount();
            encountered++;
            
            // 次のエンカウント歩数を算出
            NextEncount();
        }
    }
    
    void NextEncount(){
        float r = Dice();
        float s;
        if( r > 0.2 ){
            s = 12.5f * (r - 0.2f) + 12.5f;
        } else {
            s = 100.0f * r + 25.0f;
        }
        nextEncounterStep += (ulong)s;
    }
    
    float Dice(){
        float r = UnityEngine.Random.value;
        return r;
    }
    
    void Encount(){
        // 今は何もしない
    }
}

画面表示

f:id:tomo_mana:20210219234207p:plain
Inspector - Player

※実際に動かしてみると、テスト前に取得していた乱数の値の2回目と3回目が(Start~最初のエンカウントの間の)どこかで使われてしまったのか、少しシミュレーションした値と算出結果が異なりました。

余談

昔、「魔界塔士SaGa2」というゲームでは、ロード直後の2~3回のエンカウントに再現性がありました。その2~3回のうちに能力値がアップするように行動すると、セーブ⇒電源再起動⇒エンカウントを繰り返すことで簡単に能力値を最大まで増やせるという裏技がありました。(ただし素早さを上げることで敵の攻撃パターンが変わってしまうと、乱数の振り出され方が変わるため能力値がアップしなくなる)

次回

戦闘シーンの作成に向けて、シーンロードに挑戦します。