自動で徘徊するAIを作ってみる
過去にLookAtを使って敵をこちら側に向けさせるということをやりました
今回の趣旨としては同じなのですが、こちらは回転させるので反転させるとした場合にLookAtだと急激に反転するものがこちらでは回転させるのでクルッと振り向く事になります。
ターゲットへ視線を追随する場合はLookAtでもいいのですが、クルッと回転させたい場合に少しLookAtは不向きかと思います。
さっそくスクリプトをば
using UnityEngine;
using System.Collections;
public class hogehoge : MonoBehaviour {
private float speed = 1f;
private GameObject _cube;
private Vector3 targetPosition;
private float moveTime;
private void Start () {
_cube = GameObject.CreatePrimitive (PrimitiveType.Cube);
targetPosition = GetRandomPosition ();
moveTime = Random.Range (5, 10);
}
// Update is called once per frame
void Update () {
moveTime -= Time.deltaTime;
//moveTimeが0以上なら行動する
if (moveTime > 0) {
//正面に進む
_cube.transform.Translate (Vector3.forward * speed * Time.deltaTime);
Quaternion targetRotation = Quaternion.LookRotation (targetPosition - _cube.transform.position);
_cube.transform.rotation = Quaternion.Slerp (_cube.transform.rotation, targetRotation, Time.deltaTime / 2);
}
//moveTimeが0以下なら次のポイントを設定する
if (moveTime < 0) {
moveTime = MoveTime ();
targetPosition = GetRandomPosition ();
Debug.Log (targetPosition);
}
}
public Vector3 GetRandomPosition(){
float levelSize = 10f;
return new Vector3(Random.Range(-levelSize,levelSize),0,Random.Range(-levelSize,levelSize));
}
public float MoveTime(){
return Random.Range (5, 10);
}
}
speedが遅くてgifにすると長くなるのでスピードは3にしてます。
ターゲット向かって回転しながらfowordで前進していっています。
ランダムな敵の動きを作ろうと思ってこれに行きつきました。
ただ、これでは常に動き回っているので立ち止まったりする挙動も入れていきたいと思います。
ストップウォッチでプログラムの処理のスピードを計る
プログラムをガリガリ書いて動かしてみるとなんだか処理が重たい…でもどこを直せば早くなるのかわからないという事ありませんか?
先日記事にしたTranslateとtransform.positionで動かしたときにどちらが早く処理できるかという事を書いたのですが、若干の差が何千回、何万回ループをする事によって大きな差になってしまいます。
そこで、どのプログラムを書き換えると処理がどうなったかわかるようにストップウォッチみたいなものを使ってどれだけの時間がかかっているかを計測してみましょう。
//[時間を計測]
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
ストップウォッチとわかりやすいようにswという名前にしました。System.Diagnostics.Stopwatch
StopwatchクラスはSystem群の中のDiagnostics群の中にStopwatchクラスがあると考えて下さい。
一番最初に using System.Diagnostics; を書いておけば
Stopwatchだけで使用できます。
しかし、using System.Diagnostics;を最初に書いてしまうとusing UnityEngine;の中にもDebugが所属しており、System.DiagnosticsのDebugとUnityEngineのDebugがぶつかってしまうのでDebugを使うとエラーが出てしまいます。
なので長いのですが、System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();と記載しましょう。
sw.Start ();
Start()はそのままストップウォッチのカウントがスタートします。
sw.Stop ();
ストップももちろんストップします。
Debug.Log(sw.Elapsed);
計測したストップウォッチを確認する場合はElapsedです。Debug.Logでコンソールに表示させるようにして実際に確認してみましょう。
これを使うと処理がどれだけの時間をかけて処理されているか計測する事が可能です。重たくなるんだよなあ…という人は一度試しに自分のプログラムの何が重たいのかを計測してみてはどうでしょうか(´・ω・`)!
Quaternion.Eulerとtransform.Rotateの違い
この違い、昨日説明したtransform.positionとtransform.Translateと同じような関係でして指定した角度になるのか、指定した量だけ回転させるのかの違いになります。
回転させたいときとか、一気に反転させたいときとか、あれ、どっちを使えばいいんだっけ?回転ってEulerだっけな…Rotateだっけ…となってしまったりする事もあります。
慣れでいつも片方しか使わなかったりするのですが、ちゃんと分けて使えるのって出来る人っぽくていいですよね(笑)
そういえば昨日書き忘れてたなと思ったのでとりあえず書き足しましたが今日はこの辺で…(´・ω・`)
transform.positionとtransform.Translateの違いと処理速度
オブジェクトを移動させようとしたときにどっちを使おうかなー?といった感じで考えた事はないでしょうか。自分はあります。
両方ポジションを移動する事が出来るのですが、いったい何が違うのでしょうか?
transform.Translate
Translate()メソッドは移動したい位置のVector3型の数値を入れるpositionと違い移動量を表すVector3型のベクトルを指定します。
どう違うかと言えば下のgifを見てみましょう。
このスクリプトは
private GameObject _cube;
private Vector3 _directionCube;
void Start () {
_cube = GameObject.CreatePrimitive (PrimitiveType.Cube);
_directionCube = Vector3.right;
}
void Update () {
if (_cube.transform.position.x > 3)
_directionCube = Vector3.left;
if (_cube.transform.position.x < -3)
_directionCube = Vector3.right;
_cube.transform.Translate (_directionCube * 0.1f);
}
を使っています。Cubeオブジェクトのポジションがx = 3を超えたら_directionCubeの中は(-0.1,0.0,0.0)となり、逆にいけば(0.1,0.0,0.0)となります。
ここでpositionと違うのは移動量を入れる事によってマイフレーム指定されたベクトルへ移動する事になります。
_cube.transform.Translate (_directionCube * 0.1f);
最後の*0.1fはなくてもいいですが、ものすごい早く移動してしまうので今回は入れています。
transform.position
Translateとは違いtransform.positionは移動する先を指定してその位置に移動します。急激に大きな数字を入れればその位置に一瞬でワープする事も可能です。
今回は上のTranslateと同じように動くようにしました。
private GameObject _sphere;
private Vector3 _directionSphere;
bool _left;
void Start(){
_sphere = GameObject.CreatePrimitive (PrimitiveType.Sphere);
_directionSphere = Vector3.right;
}
void Update(){
if (_sphere.transform.position.x > 3){
_left = true;
} else if (_sphere.transform.position.x < -3){
_left = false;
}
if (_left){
_sphere.transform.position = (_directionSphere += Vector3.left)*0.1f;
} else {
_sphere.transform.position = (_directionSphere += Vector3.right)*0.1f;
}
}
Translateよりも長くなってしまってます。
positionだと毎回移動する先を指定しないといけないのと、boolを使って3以上か3以下に行ったかどうかの判断が必要になります。
if (_sphere.transform.position.x > 3){
_left = true;
} else if (_sphere.transform.position.x < -3){
_left = false;
}
の内側にtransform.positionをいれても内側に戻ってきたらこの中を通らなくなるので途中で動きが止まってしまいます。
左がtransform.Translateで右がtransform.position
処理速度ですが、Translateの方が実装は楽なのですが、transform.positionで同じ動きを作った場合はtransform.posiitonの方が処理は早く終わってしまいました。100周分なのでまだまだ足りないかもしれませんが...
という事で1000週してみました
それでも変わらずtransform.positionの方が若干処理は早いという事で決着がつきました。
コードがぐちゃぐちゃっとしてしまう事を考えると軽いゲームであればTranslateを使ってしまいたいですよね(´・ω・`)
数学関数をまとめる
Math関数とMathf関数がある事をしっている人も多いと思いますが、中々どうして忘れてしまうものです。という事で今回はこれらの関数を忘れたときの為に書き留めておこうと思います。
Mathf関数
Pow(x,y)
xのy乗になります。
Clamp(value, min, max);
minは最小maxは最大です。最小値より小さくなればminで設定した数値に戻りますし、最大を超えてればmaxの値になります。
Ceil(float x);
小数点切り上げますが型はfloatのままです。
CeilToInt(float x);
小数点切り上げてInt型にして返してきます。
Lerp(float from, float to, float t);
fromはtが0 toはtが1になっていて、tを0.5fにするとfromとtoの中間地点を返します。
PI
円周率を返してくれます。
Abs(x)
絶対値を返してくれます。
Approximately(float A,float B)
AとBの比較をして同じならtrue、違えばfalseを返します。
Mathf.Round(float x)
xの値に近い整数を返してくれます。1.2fは1を返してくれます。1.7fは2を返してくれます。
実際に動かしてコンソールを確認してみましょう(´・ω・`)!
// Update is called once per frame
void Start () {
//最大値最小値の指定
Debug.Log (Mathf.Pow(2,2));
//小数点切り上げ 型はfloatのまま
Debug.Log (Mathf.Ceil(1.00001f));
//小数点切り上げ 型はint型になる
Debug.Log (Mathf.CeilToInt(1.00001f));
//ftomからtoまでの中間地を取得 fromが0でtoが1 中間は0.5f
//xを1 yを0にして0.5にすると中間の0.5を取得
Debug.Log (Mathf.Lerp(this.transform.position.x,this.transform.position.y,0.5f));
//PI
//円周率
Debug.Log (Mathf.PI);
//Abs(x);
//絶対値を返す
Debug.Log (Mathf.Abs(-10));
//Approximately(float A,float B);
//AとB比較して同じならtrueを返す
Debug.Log(Mathf.Approximately(0.1f,0.2f));
//Round(float x);xの値に近い整数をint型で返す
Debug.Log(Mathf.Round(10.4f));
Debug.Log(Mathf.Round(10.7f));
}
サイン・コサイン・タンジェント
これらについてはぼちぼち長くなりそうなので、また別でまとめようと思います。
画面タッチ スクリーン座標 ワールド座標 ビューポート座標 Canvas座標
Input.mousePosition
マウスクリック時に取得されるInput.mousePositionですが、こちらはスクリーン座標となっており、320×480の場合は⇦が0で右が320になります。
Unityの世界の中ではオブジェクトを作った時の初期状態ではxが0yが0zが0でカメラの中心に存在すると思います。
それなのにスクリーン座標をもとにオブジェクトをクリックした位置に動かそうとするとかなり大変だという事がわかると思います。
そこで座標をわかりやすく変換できる関数をいくつか…
Camera.main.ScreenToWorldPoint
Camera.main.ScreenToWorldPoint(Vector3 position)
これはスクリーン座標をワールド座標に変換する事が出来ます。()の中にInput.mousePositionを入れるとInput.mousePositionのスクリーン座標を変換してくれるといいのですが…
Input.mousePositionはz座標を持っていませんのでそのままクリックするとMainCameraの座標をとってきたりします。なので、z座標をカメラよりも全面の位置に指定して上げて下さい。
void Update () {
if (Input.GetMouseButtonDown (0)) {
Debug.Log ("スクリーン座標" + Input.mousePosition);
Vector3 screen_point = Input.mousePosition;
screen_point.z = 2.0f;
Debug.Log ("ワールド座標" + Camera.main.ScreenToWorldPoint (screen_point));
}
}
こんな感じにして早速確認してみましょう。Cubeオブジェクトはx0y0z0の位置に配置しています。
お気づきかもしれませんが、CubeオブジェクトをクリックしてもCubeオブジェクトと同じxy座標にはなりません。若干ずれてしまいます。
その原因がこちら
3Dの世界では奥行きがあるので、その分ずれてしまいます。
このずれはProjectionを変更する事でひとまず解消してくれます。
こうする事でクリックした位置とコンソールに表示される座標の誤差はなくなったと思います。ただ、このカメラのモードだと3Dでは問題が出てきそうですが…
Camera.main.WorldToViewportPoint(Vector3 position)
つぎにこちら。これはワールド座標をビューポート座標に変換してくれます。
ビューポート座標は左下がx座標0y座標0になり、右上がx座標1y座標1となります。
Canvasの座標をとりたい
CanvasのRectTransformを動かしたいと思った時に座標をどうやってとればいいのか色々考えてたのですがスクリーン座標のままでもいけます。
ただ、Anchor Presetsを変更する必要があります。
左下に設定して下さい。
void Update () {
if (Input.GetMouseButtonDown (0)) {
Debug.Log ("Canvas座標" + Input.mousePosition);
}
}
そう、Input.mousePositionのスクリーン座標のままだとAnchorPresetをBottmLeftに設定すると同じ数値になりました。
RectTransform
左下 PosX0 PosY0
真ん中 PosX216 PosY212
右上 PosX431 PosY424
全てAnchorPresetはBottomLeftですが、Middle Centerにすると真ん中は0,0でTopRightにすると右上は0,0の位置に設定してますが、すべてBottomLeftでのPositionの数値になります。
これで動かしてクリックしてみましょう。
左下から真ん中右上の順番でクリックしてます。左下と右上は画面外にオブジェクトがはみ出ているので大きさは違って見えててなんだかわかりづらいですね…
オブジェクトの中心を押す能力が自分自身にないので誤差はありますが、ほぼ一緒ですね。Canvas上のRectTransformはどうやって取得したらいいのかとものすごい悩んでたのですが、以外にも簡単に終わってしまいました。
たしか昨日のJoystickも結構大変なコードになってたと思うのでその辺綺麗にしないと…
また間違えてたらちゃんと後日なおします!
JoyStickを自作してみたその2
前回に引き続きまたJoystickの方をば…
かなりむりやりな感じでしたが、少しずつ改善してゲームとして成り立つようにしたいと思い今日もごりごりやってました。
まず最初に今日の最終のJoystickの感じを…
タッチした位置でJoyStickが生成されて敵を発見するとタップすると攻撃を仕掛けます。
昨日まではJoystickの位置が同じ位置だったのですが、今回はどこでもいけるようにしました。
下の青い枠は特に今のところ意味がないのでとくに気にしないでください。
某有名ゲームのあのコントローラーを目指して作ってますが機能面を近づけられればいいので、たぶんプニプニはさせないと思います(笑)
一先ず明日あたりにでもコードを整理してきれいにまとめていきたいなと思います。。。