B-Teck!

お仕事からゲームまで幅広く

【VBA】文字列の大文字小文字を判定する

自分自身を大文字に変換した後、変換前後を比較すると大文字小文字を判定できる。
VBAでは大文字小文字がイコールではうまく判定されないので、

 StrComp("変換文字列1", "変換文字列2", vbBinaryCompare) 

でバイナリでの比較を行う必要がある。

Option Explicit

'/**
' * IsUpperString
' * 入力文字列が大文字かを調べる
' * @param  inputValue   比較する文字列
' * @param  Optional inputValue 比較する文字数  1以上  :先頭からの文字数
' *                                             0(省略):文字列全体
' *                                             -1以下 :末尾からの文字数
' * @return True:大文字 False:小文字
' */
Public Function IsUpperString(ByVal inputValue As String, _
                              Optional ByVal checkLen As Integer = 0) As Boolean

    Dim checkString As String
    Dim upperString As String
    
    '引数に応じてチェック対象の文字を取得
    If checkLen < 0 Then
        checkString = Right(inputValue, Abs(checkLen))
    ElseIf checkLen > 0 Then
        checkString = Left(inputValue, checkLen)
    Else
        checkString = inputValue
    End If
    
    'チェック対象の文字を大文字に変換
    upperString = StrConv(checkString, vbUpperCase)
    
    '大文字にしたチェック対象の文字列と、変換前の文字列を比較
    If StrComp(upperString, checkString, vbBinaryCompare) = 0 Then
        IsUpperString = True
    Else
        IsUpperString = False
    End If

End Function

'動作確認
Public Sub test()

    'True
    Debug.Print IsUpperString("A")
    'False
    Debug.Print IsUpperString("a")
    
    'True
    Debug.Print IsUpperString("ABCd", 2)
    'True
    Debug.Print IsUpperString("ABCd", 3)
    'False
    Debug.Print IsUpperString("ABCd", 4)
    
    'True
    Debug.Print IsUpperString("aBCD", -2)
    'True
    Debug.Print IsUpperString("aBCD", -3)
    'False
    Debug.Print IsUpperString("aBCD", -4)

End Sub

【C#/Unity】穴掘り法でダンジョンをランダムで生成する

f:id:beatdjam:20160701004513g:plain

せっかく経路探索して敵を追いかけるアルゴリズムを書くなら迷路がほしいなと思ったので。

穴掘り法を複数回ループして、分岐を無理やり増やしてるけど袋小路はうまれる。
仕方ないね。

穴掘り法とは

f:id:beatdjam:20160701004224p:plain f:id:beatdjam:20160701004232p:plain f:id:beatdjam:20160701004238p:plain

実装

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

public class MakeDungeon : MonoBehaviour
{
    /*
    *設定する値
    */
    public int max = 5;        //縦横のサイズ ※必ず奇数にすること
    public GameObject wall;    //壁用オブジェクト
    public GameObject floor;    //床用オブジェクト
    public GameObject start;   //スタート地点に配置するオブジェクト
    public GameObject goal;    //ゴール地点に配置するオブジェクト

    /*
    *内部パラメータ
    */
    private int[,] walls;      //マップの状態 0:壁 1:通路
    private int[] startPos;    //スタートの座標
    private int[] goalPos;     //ゴールの座標

    void Start()
    {
        //マップ状態初期化
        walls = new int[max, max];

        //スタート地点の取得
        startPos = GetStartPosition();

        //通路の生成
        //初回はゴール地点を設定する
        goalPos = MakeDungeonMap(startPos);
        //通路生成を繰り返して袋小路を減らす
        int[] tmpStart = goalPos;
        for (int i = 0; i < max * 5; i++)
        {
            MakeDungeonMap(tmpStart);
            tmpStart = GetStartPosition();
        }

        //マップの状態に応じて壁と通路を生成する
        BuildDungeon();

        //スタート地点とゴール地点にオブジェクトを配置する
        //初回で取得したスタート地点とゴール地点は必ずつながっているので破綻しない
        GameObject startObj = Instantiate(start, new Vector3(startPos[0], 1, startPos[1]), Quaternion.identity) as GameObject;
        GameObject goalObj = Instantiate(goal, new Vector3(goalPos[0], 1, goalPos[1]), Quaternion.identity) as GameObject;
        startObj.transform.parent = transform;
        goalObj.transform.parent = transform;
    }

    /*
    *スタート地点の取得
    */
    int[] GetStartPosition()
    {
        //ランダムでx,yを設定
        int randx = Random.Range(0, max);
        int randy = Random.Range(0, max);

        //x、yが両方共偶数になるまで繰り返す
        while (randx % 2 != 0 || randy % 2 != 0)
        {
            randx = Mathf.RoundToInt(Random.Range(0, max));
            randy = Mathf.RoundToInt(Random.Range(0, max));
        }

        return new int[] { randx, randy };
    }

    /*
    *マップ生成
    */
    int[] MakeDungeonMap(int[] _startPos)
    {
        //スタート位置配列を複製
        int[] tmpStartPos = new int[2];
        _startPos.CopyTo(tmpStartPos, 0);
        //移動可能な座標のリストを取得
        Dictionary<int, int[]> movePos = GetPosition(tmpStartPos);

        //移動可能な座標がなくなるまで探索を繰り返す
        while (movePos != null)
        {
            //移動可能な座標からランダムで1つ取得し通路にする
            int[] tmpPos = movePos[Random.Range(0, movePos.Count)];
            walls[tmpPos[0], tmpPos[1]] = 1;

            //元の地点と通路にした座標の間を通路にする
            int xPos = tmpPos[0] + (tmpStartPos[0] - tmpPos[0]) / 2;
            int yPos = tmpPos[1] + (tmpStartPos[1] - tmpPos[1]) / 2;
            walls[xPos, yPos] = 1;

            //移動後の座標を一時変数に格納し、再度移動可能な座標を探索する
            tmpStartPos = tmpPos;
            movePos = GetPosition(tmpStartPos);
        }
        //探索終了時の座標を返す
        return tmpStartPos;
    }

    /*
    *移動可能な座標のリストを取得する
    */
    Dictionary<int, int[]> GetPosition(int[] _startPos)
    {
        //可読性のため座標を変数に格納
        int x = _startPos[0];
        int y = _startPos[1];

        //移動方向毎に2つ先のx,y座標を仮計算
        List<int[]> position = new List<int[]> {
            new int[] {x, y + 2},
            new int[] {x, y - 2},
            new int[] {x + 2, y},
            new int[] {x - 2, y}
        };

        //移動方向毎に移動先の座標が範囲内かつ壁であるかを判定する
        //真であれば、返却用リストに追加する
        Dictionary<int, int[]> positions = position.Where(p => !isOutOfRange(p[0], p[1]) && walls[p[0], p[1]] == 0)
                                                   .Select((p, i) => new { p, i })
                                                   .ToDictionary(p => p.i, p => p.p);
        //移動可能な場所が存在しない場合nullを返す
        return positions.Count() != 0 ? positions : null;
    }

    //与えられたx、y座標が範囲外の場合真を返す
    bool isOutOfRange(int x, int y)
    {
        return (x < 0 || y < 0 || x >= max || y >= max);
    }

    //パラメータに応じてオブジェクトを生成する
    void BuildDungeon()
    {
        //縦横1マスずつ大きくループを回し、壁とする
        for (int i = -1; i <= max; i++)
        {
            for (int j = -1; j <= max; j++)
            {
                //範囲外、または壁の場合に壁オブジェクトを生成する
                if (isOutOfRange(i, j)
                    || walls[i, j] == 0)
                {
                    GameObject wallObj = Instantiate(wall, new Vector3(i, 0, j), Quaternion.identity) as GameObject;
                    wallObj.transform.parent = transform;
                }

                //全ての場所に床オブジェクトを生成
                GameObject floorObj = Instantiate(floor, new Vector3(i, -1, j), Quaternion.identity) as GameObject;
                floorObj.transform.parent = transform;
            }
        }

    }
}

【C#/Unity】相手を追尾するAIを考える その2

前回 beatdjam.hatenablog.com

はじめに

さっきUnityをインストールして環境作ったので、昨日の記事を改めてUnityで実装してみました。
今回も最後に全文のソースコードを載せます。

また、今回掲載するソースは以前掲載した下記記事から一部流用しています。
こちらの記事もよろしくお願いします。 beatdjam.hatenablog.com

前回の記事の内容に合わせて簡単に解説します。

解説

動作のgif
f:id:beatdjam:20160628202903g:plain

changeDirection

前回の記事で実装したmoveEnemychangeDirectionに対応します。
基本的な考えは変わらず、目標と自分自身の位置の差分を取得し、縮めるように動かすだけです。
ポイントはtransform.rotationQuaternion.Eulerですかね。
transform.rotationは、オブジェクトから見た相対的な回転ではなく、シーン全体から見た回転量を表します。
インスペクタに出てくるやつですね。
Quaternion.Eulerは、角度をtransform.rotationに対応した値に変換するメソッドです。
Y軸のみを変えることで正面を常に移動したい方向に向けています。

//playerの座標に応じて向きを変える
void changeDirection(GameObject player){
    //playerとenemyの座標差分を取得する
    int xDistance = Mathf.RoundToInt(this.transform.position.x - player.transform.position.x);
    int zDistance = Mathf.RoundToInt(this.transform.position.z - player.transform.position.z);

    //向きたい角度
    int rotateDir = 0;

    //x座標,z座標の差分から向きたい角度を取得する
    //playerとenemyに距離がある場合
    if(xDistance == 0){
        //x座標が同じ場合z座標のみ向き取得
        rotateDir = this.getDirection(zDistance, "z");
    }else if(zDistance == 0){
        //z座標が同じ場合x座標のみ向き取得
        rotateDir = this.getDirection(xDistance, "x");
    }else{
        //どちらも差がある場合、ランダムで進む向き取得
        int rand = UnityEngine.Random.Range (0, 2);
        if(rand == 0){ 
            //z座標向き取得
            rotateDir = this.getDirection(zDistance, "z");
        }else{
            //x座標向き取得
            rotateDir = this.getDirection(xDistance, "x");
        }
    }

    //取得した方向にオブジェクトの向きを変える
    this.transform.rotation = Quaternion.Euler (0, rotateDir, 0);
}

getDirection

前回の記事で実装したgetMovePosgetDirectionに対応します。
こちらは少し見栄えが変わったかなと思います。
渡された軸、距離から、どちらの軸を移動するか、軸に対してマイナス・プラスのどちらに移動するかを算出しています。
0 90 180 270 がそれぞれ4方向に対応しています。

//向きの角度を取得する
int getDirection(int distance,string axis){
    //距離がプラスかマイナスかを取得
    int flag = distance > 0 ? 1 : 0;

    //角度を返却
    if(axis == "x"){
        return flag == 1 ? 270 : 90;
    }else{
        return flag == 1 ? 180 : 0 ;
    }
}

おわりに

わからないことがあればまずはググるかUnityのリファレンスを読んでみましょう。
できることや方法、考え方を知ることは、間違いなくプログラマーとしての力につながります。
自分なりに考えてみて、それでもわからなければ、コメント欄等で質問いただければ力になります!

というわけで、次回(があれば)は経路探索についてかけたらいいなと思います。

ソースコードは下記になります。

using UnityEngine;
using System.Collections;

public class nearTest : MonoBehaviour{    
    private GameObject nearObj;      //最も近いオブジェクト
    private float searchTime = 0;    //経過時間

    void Start(){
        //最も近かったオブジェクトを取得
        this.nearObj = this.serchTag(gameObject, "Player");
    }

    void Update () {
        //経過時間を取得
        this.searchTime += Time.deltaTime;

        if (this.searchTime >= 1.0f) {
            //最も近かったオブジェクトを取得
            this.nearObj = this.serchTag(gameObject, "Player");
            //経過時間を初期化
            this.searchTime = 0;

            //1秒おきに移動
            //対象の方向を向く
            this.changeDirection(this.nearObj);
            //自分自身の位置から相対的に移動する
            this.transform.Translate(Vector3.forward * 1.00f);
        }   

    }

    //playerの座標に応じて向きを変える
    void changeDirection(GameObject player){
        //playerとenemyの座標差分を取得する
        int xDistance = Mathf.RoundToInt(this.transform.position.x - player.transform.position.x);
        int zDistance = Mathf.RoundToInt(this.transform.position.z - player.transform.position.z);

        //向きたい角度
        int rotateDir = 0;

        //x座標,z座標の差分から向きたい角度を取得する
        //playerとenemyに距離がある場合
        if(xDistance == 0){
            //x座標が同じ場合z座標のみ向き取得
            rotateDir = this.getDirection(zDistance, "z");
        }else if(zDistance == 0){
            //z座標が同じ場合x座標のみ向き取得
            rotateDir = this.getDirection(xDistance, "x");
        }else{
            //どちらも差がある場合、ランダムで進む向き取得
            int rand = UnityEngine.Random.Range (0, 2);
            if(rand == 0){ 
                //z座標向き取得
                rotateDir = this.getDirection(zDistance, "z");
            }else{
                //x座標向き取得
                rotateDir = this.getDirection(xDistance, "x");
            }
        }

        //取得した方向にオブジェクトの向きを変える
        this.transform.rotation = Quaternion.Euler (0, rotateDir, 0);
    }

    //向きの角度を取得する
    int getDirection(int distance,string axis){
        //距離がプラスかマイナスかを取得
        int flag = distance > 0 ? 1 : 0;

        //角度を返却
        if(axis == "x"){
            return flag == 1 ? 270 : 90;
        }else{
            return flag == 1 ? 180 : 0 ;
        }
    }

    //指定されたタグの中で最も近いものを取得
    GameObject serchTag(GameObject nowObj,string tagName){
        float tmpDis = 0;           //距離用一時変数
        float nearDis = 0;          //最も近いオブジェクトの距離
        GameObject targetObj = null; //オブジェクト

        //タグ指定されたオブジェクトを配列で取得する
        foreach (GameObject obs in  GameObject.FindGameObjectsWithTag(tagName)){
            //自身と取得したオブジェクトの距離を取得
            tmpDis = Vector3.Distance(obs.transform.position, nowObj.transform.position);

            //オブジェクトの距離が近いか、距離0であればオブジェクト名を取得
            //一時変数に距離を格納
            if (nearDis == 0 || nearDis > tmpDis){
                nearDis = tmpDis;
                targetObj = obs;
            }
        }
        //最も近かったオブジェクトを返す
        return targetObj;
    }
}