わかゲームスタジオ

ゲーム制作初心者専門学生の備忘録

Unity 構造体の作り方

Unityでは構造体ではなくClassで管理することが多いです。

非常に便利なので覚えておくと良いですね。

>|C#|

[System.Serializable]

public class Data

{

public string name;

public int id;

public int score;

public float time;

}

public Data[] data;

||<

大事なのは[System.Serializable]の部分です。

これがないと使えないので注意ですね。

 

こうするとinspector上では

f:id:WakaiGames:20201017222721p:plain

ちゃんと表示されていますね。

 

これを配列にすると

f:id:WakaiGames:20201017222806p:plain

より多くの情報を扱うことができるようになります。

 

以上です。

これすっごく便利。

Unity RigidbodyとRootMotionの共存

現在制作中のゲームで攻撃の踏み込みをRootMotionで、移動や回避をRigidbodyで実装したいと考えていたのです。
しかしどうしてもうまくいかず、試行錯誤の結果RigidbodyとRootMotionは共存できないという結論に至りました。

否、解決策あった。

以下のサイト様を参考(書いてあることまんま)にすると。
qiita.com

    void OnAnimatorMove()
    {
        transform.position = GetComponent<Animator>().rootPosition;
    }

このコード書くだけで良かったんだって。
これは便利!

Unity プレイヤーの回転をなめらかにしたい

瞬時に入力方向を向いても良いのですが、なめらかに向いても良いと思うのです。
というわけで今回はなめらかな振り返りを実装しようと思います。

↓今回できること↓

前回までの記事を先に読んでください。
wakagamestudio.hatenablog.jp


Unity ver.2019.3.4f1

①変数を追加

    [Header("回転速度")]
    [SerializeField]
    private float rotateSpeed;
  • 回転速度を決める変数です。

②前回書いた分を削除

    //===向き切り替え===//
    if (inputVector != Vector2.zero)
    {
        // キャラクターの向きを進行方向に
        transform.rotation = Quaternion.LookRotation(moveVector);
    }
  • 回転部分を書き換えます。

③削除部分にコードを追加

    Vector3 dirMoveVector = (moveVector + transform.position) - gameObject.transform.position;
    Vector3 cross = Vector3.Cross(transform.forward, moveVector);
    if (hori != 0 || ver != 0)
    {
        if (cross.y <= 0)
        {
            transform.rotation = Quaternion.Euler(0, transform.eulerAngles.y - rotateSpeed, 0);
        }
        else
        {
            transform.rotation = Quaternion.Euler(0, transform.eulerAngles.y + rotateSpeed, 0);
        }
    }
  • 向いている方向と向きたい方向で外積を取ります。

すると、


以上です。
操作性が極端に悪くならない限りはこれのほうが滑らかでよいかもしれませんね。

これまでの全コード

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

public class PlayerMovement : MonoBehaviour
{
    [Header("リジッドボディ")]
    [SerializeField]
    private Rigidbody rigidbody;

    [Header("移動速度")]
    [SerializeField]
    private float speed = 15;

    [Header("入力制限")]
    [SerializeField]
    [Range(0, 1)]
    private float inputLimit = 0.1f;

    [Header("回転速度")]
    [SerializeField]
    private float rotateSpeed;

    //入力値代入用
    private Vector2 inputVector;
    //移動方向決定
    private Vector3 moveVector;

    void Update()
    {
        //入力値を代入
        float hori = Input.GetAxis("Horizontal") >= -inputLimit && Input.GetAxis("Horizontal") <= inputLimit ? 0 : Input.GetAxis("Horizontal");
        float ver = Input.GetAxis("Vertical") >= -inputLimit && Input.GetAxis("Vertical") <= inputLimit ? 0 : Input.GetAxis("Vertical");
        inputVector = new Vector2( hori, ver);

        //カメラの向きから移動方向を決定
        moveVector = (Vector3.Scale(Camera.main.transform.forward, new Vector3(1, 0, 1)).normalized * inputVector.y + Camera.main.transform.right * inputVector.x) * speed;

        Vector3 dirMoveVector = (moveVector + transform.position) - gameObject.transform.position;
        Vector3 cross = Vector3.Cross(transform.forward, moveVector);
        if (hori != 0 || ver != 0)
        {
            if (cross.y <= 0)
            {
                transform.rotation = Quaternion.Euler(0, transform.eulerAngles.y - rotateSpeed, 0);
            }
            else
            {
                transform.rotation = Quaternion.Euler(0, transform.eulerAngles.y + rotateSpeed, 0);
            }
        }
    }

    private void FixedUpdate()
    {
        //移動方向に重力分を足す
        rigidbody.velocity = moveVector + new Vector3(0, rigidbody.velocity.y, 0);
    }
}

Unity 爆弾を実装する

以前のゲームジャムで爆弾を実装するときに参考になるサイトが見つからなかったので残しておきます。

Unity ver 2019.3.4f1

↓今回できること↓




①エネミーを作成

f:id:WakaiGames:20200922164348p:plain

  • Enemyタグを追加、設定

②爆弾用オブジェクト作成

f:id:WakaiGames:20200922164802p:plainf:id:WakaiGames:20200922164017p:plain

  • 爆発範囲用コライダー追加
  • 爆弾自体の当たり判定は子構造にしておきます。

③爆弾側にスクリプトを追加

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

public class BombAction : MonoBehaviour
{
    //範囲内にいるEnemyを保存するためのリスト
    private List<GameObject> enemys;

    private void Start()
    {
        //リストを初期化
        enemys = new List<GameObject>();
    }

    private void OnTriggerEnter(Collider other)
    {
        //進入オブジェクトがEnemyタグを持っているかつ、リスト外
        if (other.transform.tag == "Enemy" && !enemys.Contains(other.gameObject))
        {
            enemys.Add(other.gameObject);
        }
    }

    private void OnTriggerExit(Collider other)
    {
        //出て行ったオブジェクトがEnemyタグを持っている
        if (other.transform.tag == "Enemy")
        {
            enemys.Remove(other.gameObject);
        }
    }

    //爆発処理
    private void Explosion()
    {
        print("Bomb");
        foreach (GameObject item in enemys)
        {
            //リスト内のオブジェクトを破壊
            Destroy(item);
        }
        //爆弾自身も破壊
        Destroy(gameObject);
    }
}

④爆発関数を実行

    IEnumerator Exp()
    {
        yield return new WaitForSeconds(2.5f);
        Explosion();
    }
  • 先のままでは爆発しないので関数呼び出し用コルーチンを書いておきます。
  • これをStartで呼び出します。

すると、

以上です。
foreach文内でEnemyにアタッチしたクラスをGetして、ダメージ処理を書いておくと即死ではなくダメージを与えることができます。
ゲームに応じて使い分けてみてください。

Unity プレイヤーの向きを変える

今回はプレイヤーの向きを変更したいと思います。
先に前回の記事をご覧ください。
wakagamestudio.hatenablog.jp

Unity ver.2019.3.4f1

①Update文を変更する

    //移動方向決定
    private Vector3 moveVector;

 void Update()
    {
        //入力値を代入
        float hori = Input.GetAxis("Horizontal") >= -inputLimit && Input.GetAxis("Horizontal") <= inputLimit ? 0 : Input.GetAxis("Horizontal");
        float ver = Input.GetAxis("Vertical") >= -inputLimit && Input.GetAxis("Vertical") <= inputLimit ? 0 : Input.GetAxis("Vertical");
        inputVector = new Vector2( hori, ver);

        //カメラの向きから移動方向を決定
        moveVector = (Vector3.Scale(Camera.main.transform.forward, new Vector3(1, 0, 1)).normalized * inputVector.y + Camera.main.transform.right * inputVector.x) * speed;

        //===向き切り替え===//
        if (inputVector != Vector2.zero)
        {
            // キャラクターの向きを進行方向に
            transform.rotation = Quaternion.LookRotation(moveVector);
        }
    }
  • 前回FixedUpdateに書いた内容をUpdateに追加します。

以上です。
たったこれだけで回転するようになりました。
前回記事に書いてしまえばよかった。

前回までの全コード

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

public class PlayerMovement : MonoBehaviour
{
    [Header("リジッドボディ")]
    [SerializeField]
    private Rigidbody rigidbody;

    [Header("移動速度")]
    [SerializeField]
    private float speed = 15;

    [Header("入力制限")]
    [SerializeField]
    [Range(0, 1)]
    private float inputLimit = 0.1f;

    //入力値代入用
    private Vector2 inputVector;
    //移動方向決定
    private Vector3 moveVector;

    void Update()
    {
        //入力値を代入
        float hori = Input.GetAxis("Horizontal") >= -inputLimit && Input.GetAxis("Horizontal") <= inputLimit ? 0 : Input.GetAxis("Horizontal");
        float ver = Input.GetAxis("Vertical") >= -inputLimit && Input.GetAxis("Vertical") <= inputLimit ? 0 : Input.GetAxis("Vertical");
        inputVector = new Vector2( hori, ver);

        //カメラの向きから移動方向を決定
        moveVector = (Vector3.Scale(Camera.main.transform.forward, new Vector3(1, 0, 1)).normalized * inputVector.y + Camera.main.transform.right * inputVector.x) * speed;

        //===向き切り替え===//
        if (inputVector != Vector2.zero)
        {
            // キャラクターの向きを進行方向に
            transform.rotation = Quaternion.LookRotation(moveVector);
        }
    }

    private void FixedUpdate()
    {
        //移動方向に重力分を足す
        rigidbody.velocity = moveVector + new Vector3(0, rigidbody.velocity.y, 0);
    }
}

Unity マウスでカメラを制御してみる

前回までカメラの操作を実装してきたので、今回はそれをよくあるマウス制御にも対応していきたいと思います。
先に前回までの記事をご覧ください。
wakagamestudio.hatenablog.jp
wakagamestudio.hatenablog.jp

UnityVar.2019.3.4f1

①変数を追加

    [Header("マウス感度")]
    [SerializeField]
    private Vector2 mouseSpeed;
  • マウス感度(マウス操作によるカメラの移動速度を設定します)

②入力値を設定

        //キーボードかコントローラか判断
        float rx = 0;
        float ry = 0;
        if (Input.GetJoystickNames().Length != 0 && Input.GetJoystickNames()[Input.GetJoystickNames().Length - 1] != "")
        {
            rx = Input.GetAxis("HorizontalR");
            ry = Input.GetAxis("VerticalR");
        }
        else
        {
            rx = Input.GetAxis("Mouse X") * mouseSpeed.x;
            ry = Input.GetAxis("Mouse Y") * mouseSpeed.y;
        }
  • 入力値変数を使いまわすため、現在ゲームパッドが接続されているか判定します。

③前々回記述部分を編集

    Input.GetAxis("HorizontalR") ->rx
    Input.GetAxis("VerticalR") -> ry
  • ジョイスティック入力の取得部分をすべて②で作成した変数におきかえます。

これで動くようになるはずです。
①で作成した変数に値を入れるのを忘れないでくださいね。

これまでの全コード

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

public class CameraAction : MonoBehaviour
{
    [Header("カメラが向くターゲット")]
    [SerializeField]
    private GameObject target; //ターゲット

    [Header("マウス感度")]
    [SerializeField]
    private Vector2 mouseSpeed;
    [Header("回転するスピード")]
    [SerializeField]
    private Vector2 rotateSpeed;
    [Header("回転できる上限")]
    [SerializeField]
    private float rotateCeiling;//回転できる上限
    [Header("回転できる下限")]
    [SerializeField]
    private float rotateLower;  //回転できる下限
    [Header("スティックの入力制限")]
    [SerializeField]
    private float inputLimit;   //スティックの入力制限

    [Header("左右反転")]
    public bool LROpposite; //trueで反転
    [Header("上下反転")]
    public bool UDOpposite; //trueで反転

    [Header("カメラ位置リセットする遷移時間")]
    [SerializeField]
    private float camReset;     //カメラ位置リセットする遷移時間

    private bool reset;     //カメラリセット
    private float resetTime;
    private float resetAngle;
    private float nowTime;
    private float[] camnum;
    private float[] before;
    private Vector2 initRotate; //初期角度X

    private Vector3 beforePos;  //ターゲットの前位置

    private void Start()
    {
        //beforePosを初期化
        beforePos = target.transform.position;

        initRotate.x = transform.localEulerAngles.x;
        initRotate.y = transform.localEulerAngles.y;
        reset = false;
        resetTime = camReset;
        resetAngle = initRotate.x;
        camnum = new float[2];
        before = new float[2];
    }

    private void Update()
    {
        //ターゲットが動いた分移動
        transform.position += target.transform.position - beforePos;

        //入力値を判定
        //キーボードかコントローラか判断
        float rx = 0;
        float ry = 0;
        if (Input.GetJoystickNames().Length != 0 && Input.GetJoystickNames()[Input.GetJoystickNames().Length - 1] != "")
        {
            rx = Input.GetAxis("HorizontalR");
            ry = Input.GetAxis("VerticalR");
        }
        else
        {
            rx = Input.GetAxis("Mouse X") * mouseSpeed.x;
            ry = Input.GetAxis("Mouse Y") * mouseSpeed.y;
        }
        Vector3 angle = new Vector3(rx * rotateSpeed.x, ry * rotateSpeed.y, 0) * Time.deltaTime;

        //カメラを回転させる
        //横回転
        if (LROpposite)
        {
            transform.RotateAround(target.transform.position, Vector3.down, angle.x);
        }
        else
        {
            transform.RotateAround(target.transform.position, Vector3.up, angle.x);
        }

        //縦回転
        if (UDOpposite)
        {
            if ((transform.localEulerAngles.x <= rotateCeiling || transform.localEulerAngles.x >= 360 + rotateLower)
                || (ry >= inputLimit && transform.localEulerAngles.x < 360 + rotateLower && 180 <= transform.localEulerAngles.x)
                || (ry <= -inputLimit && transform.localEulerAngles.x < 180 && rotateCeiling < transform.localEulerAngles.x))
            {
                transform.RotateAround(target.transform.position, transform.right, angle.y);
            }
        }
        else
        {
            if ((transform.localEulerAngles.x <= rotateCeiling || transform.localEulerAngles.x >= 360 + rotateLower)
                || (ry <= -inputLimit && transform.localEulerAngles.x < 360 + rotateLower && 180 <= transform.localEulerAngles.x)
                || (ry >= inputLimit && transform.localEulerAngles.x < 180 && rotateCeiling < transform.localEulerAngles.x))
            {
                transform.RotateAround(target.transform.position, -transform.right, angle.y);
            }
        }

        //beforePosを更新
        beforePos = target.transform.position;

        //カメラ位置リセット中
        if (reset && nowTime <= resetTime)
        {
            nowTime += Time.deltaTime;
            transform.RotateAround(target.transform.position, Vector3.down, ResetCalc(0));
            transform.RotateAround(target.transform.position, transform.right, ResetCalc(1));
        }
        else
        {
            reset = false;
        }

        //カメラ位置リセット開始
        if (Input.GetButtonDown("JoyR") && !reset)
        {
            resetTime = camReset;
            resetAngle = initRotate.x;
            ResetStart();
        }
    }

    private void ResetStart()
    {
        camnum[0] = transform.localEulerAngles.y;
        if (camnum[0] >= 180)
        {
            camnum[0] -= 360;
        }
        float pnum = target.transform.localEulerAngles.y;
        if (pnum >= 180)
        {
            pnum -= 360;
        }
        camnum[0] -= pnum;
        if (camnum[0] >= 180)
        {
            camnum[0] -= 360;
        }
        else if (camnum[0] < -180)
        {
            camnum[0] += 360;
        }
        camnum[1] = -transform.localEulerAngles.x + resetAngle;
        if (camnum[1] < 0)
        {
            camnum[1] += 360;
        }
        if (camnum[1] >= 180)
        {
            camnum[1] -= 360;
        }
        nowTime = 0;
        before[0] = 0;
        before[1] = 0;
        reset = true;
    }

    private float ResetCalc(int sel)
    {
        float Percentage = nowTime / resetTime;
        float[] num = new float[2];
        float[] pos = new float[2];

        num[sel] = camnum[sel] * Percentage;
        pos[sel] = num[sel] - before[sel];
        before[sel] = num[sel];
        return pos[sel];
    }
}

以上です。
次回はプレイヤー自体の回転について書こうと思います。

Unity カメラの位置リセットを追加する

前回スティックでカメラを動かしたので、今回はカメラの位置リセットを設定しようと思います。
↓前回↓
wakagamestudio.hatenablog.jp

↓今回できること↓

Unity var2019.3.4f1

①InputManagerを編集する

f:id:WakaiGames:20200904174149p:plain

  • カメラ向きリセット用のインプットを設定しておきます。

②変数を宣言する

    [Header("カメラ位置リセットする遷移時間")]
    [SerializeField]
    private float camReset;     //カメラ位置リセットする遷移時間

    private bool reset;     //カメラリセット
    private float resetTime;
    private float resetAngle;
    private float nowTime;
    private float[] camnum;
    private float[] before;
    private Vector2 initRotate; //初期角度X
  • 向きリセットに使う変数たちです。

③Startで初期化する

        initRotate.x = transform.localEulerAngles.x;
        initRotate.y = transform.localEulerAngles.y;
        reset = false;
        resetTime = camReset;
        resetAngle = initRotate.x;
        camnum = new float[2];
        before = new float[2];
  • Startで初期化しておきます。

④Updateに処理を追加する

        //カメラ位置リセット中
        if (reset && nowTime <= resetTime)
        {
            nowTime += Time.deltaTime;
            transform.RotateAround(target.transform.position, Vector3.down, ResetCalc(0));
            transform.RotateAround(target.transform.position, transform.right, ResetCalc(1));
        }
        else
        {
            reset = false;
        }

        //カメラ位置リセット開始
        if (Input.GetButtonDown("JoyR") && !reset)
        {
            resetTime = camReset;
            resetAngle = initRotate.x;
            ResetStart();
        }
  • Updateでリセット回転中の動きを書きます。

⑤関数を追加する

    private void ResetStart()
    {
        camnum[0] = transform.localEulerAngles.y;
        if (camnum[0] >= 180)
        {
            camnum[0] -= 360;
        }
        float pnum = target.transform.localEulerAngles.y;
        if (pnum >= 180)
        {
            pnum -= 360;
        }
        camnum[0] -= pnum;
        if (camnum[0] >= 180)
        {
            camnum[0] -= 360;
        }
        else if (camnum[0] < -180)
        {
            camnum[0] += 360;
        }
        camnum[1] = -transform.localEulerAngles.x + resetAngle;
        if (camnum[1] < 0)
        {
            camnum[1] += 360;
        }
        if (camnum[1] >= 180)
        {
            camnum[1] -= 360;
        }
        nowTime = 0;
        before[0] = 0;
        before[1] = 0;
        reset = true;
    }

    private float ResetCalc(int sel)
    {
        float Percentage = nowTime / resetTime;
        float[] num = new float[2];
        float[] pos = new float[2];

        num[sel] = camnum[sel] * Percentage;
        pos[sel] = num[sel] - before[sel];
        before[sel] = num[sel];
        return pos[sel];
    }
  • リセット開始時に初期化する関数と、向きを360°に変換する用の計算関数です。

前回分も含む全コード

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

public class CameraAction : MonoBehaviour
{
    [Header("カメラが向くターゲット")]
    [SerializeField]
    private GameObject target; //ターゲット

    [Header("回転するスピード")]
    [SerializeField]
    private Vector2 rotateSpeed;
    [Header("回転できる上限")]
    [SerializeField]
    private float rotateCeiling;//回転できる上限
    [Header("回転できる下限")]
    [SerializeField]
    private float rotateLower;  //回転できる下限
    [Header("スティックの入力制限")]
    [SerializeField]
    private float inputLimit;   //スティックの入力制限

    [Header("左右反転")]
    public bool LROpposite; //trueで反転
    [Header("上下反転")]
    public bool UDOpposite; //trueで反転

    [Header("カメラ位置リセットする遷移時間")]
    [SerializeField]
    private float camReset;     //カメラ位置リセットする遷移時間

    private bool reset;     //カメラリセット
    private float resetTime;
    private float resetAngle;
    private float nowTime;
    private float[] camnum;
    private float[] before;
    private Vector2 initRotate; //初期角度X

    private Vector3 beforePos;  //ターゲットの前位置

    private void Start()
    {
        //beforePosを初期化
        beforePos = target.transform.position;

        initRotate.x = transform.localEulerAngles.x;
        initRotate.y = transform.localEulerAngles.y;
        reset = false;
        resetTime = camReset;
        resetAngle = initRotate.x;
        camnum = new float[2];
        before = new float[2];
    }

    private void Update()
    {
        //ターゲットが動いた分移動
        transform.position += target.transform.position - beforePos;

        //入力値を判定
        Vector3 angle = new Vector3(Input.GetAxis("HorizontalR") * rotateSpeed.x, Input.GetAxis("VerticalR") * rotateSpeed.y, 0) * Time.deltaTime;

        //カメラを回転させる
        //横回転
        if (LROpposite)
        {
            transform.RotateAround(target.transform.position, Vector3.down, angle.x);
        }
        else
        {
            transform.RotateAround(target.transform.position, Vector3.up, angle.x);
        }

        //縦回転
        if (UDOpposite)
        {
            if ((transform.localEulerAngles.x <= rotateCeiling || transform.localEulerAngles.x >= 360 + rotateLower)
                || (Input.GetAxis("VerticalR") >= inputLimit && transform.localEulerAngles.x < 360 + rotateLower && 180 <= transform.localEulerAngles.x)
                || (Input.GetAxis("VerticalR") <= -inputLimit && transform.localEulerAngles.x < 180 && rotateCeiling < transform.localEulerAngles.x))
            {
                transform.RotateAround(target.transform.position, transform.right, angle.y);
            }
        }
        else
        {
            if ((transform.localEulerAngles.x <= rotateCeiling || transform.localEulerAngles.x >= 360 + rotateLower)
                || (Input.GetAxis("VerticalR") <= -inputLimit && transform.localEulerAngles.x < 360 + rotateLower && 180 <= transform.localEulerAngles.x)
                || (Input.GetAxis("VerticalR") >= inputLimit && transform.localEulerAngles.x < 180 && rotateCeiling < transform.localEulerAngles.x))
            {
                transform.RotateAround(target.transform.position, -transform.right, angle.y);
            }
        }

        //beforePosを更新
        beforePos = target.transform.position;

        //カメラ位置リセット中
        if (reset && nowTime <= resetTime)
        {
            nowTime += Time.deltaTime;
            transform.RotateAround(target.transform.position, Vector3.down, ResetCalc(0));
            transform.RotateAround(target.transform.position, transform.right, ResetCalc(1));
        }
        else
        {
            reset = false;
        }

        //カメラ位置リセット開始
        if (Input.GetButtonDown("JoyR") && !reset)
        {
            resetTime = camReset;
            resetAngle = initRotate.x;
            ResetStart();
        }
    }

    private void ResetStart()
    {
        camnum[0] = transform.localEulerAngles.y;
        if (camnum[0] >= 180)
        {
            camnum[0] -= 360;
        }
        float pnum = target.transform.localEulerAngles.y;
        if (pnum >= 180)
        {
            pnum -= 360;
        }
        camnum[0] -= pnum;
        if (camnum[0] >= 180)
        {
            camnum[0] -= 360;
        }
        else if (camnum[0] < -180)
        {
            camnum[0] += 360;
        }
        camnum[1] = -transform.localEulerAngles.x + resetAngle;
        if (camnum[1] < 0)
        {
            camnum[1] += 360;
        }
        if (camnum[1] >= 180)
        {
            camnum[1] -= 360;
        }
        nowTime = 0;
        before[0] = 0;
        before[1] = 0;
        reset = true;
    }

    private float ResetCalc(int sel)
    {
        float Percentage = nowTime / resetTime;
        float[] num = new float[2];
        float[] pos = new float[2];

        num[sel] = camnum[sel] * Percentage;
        pos[sel] = num[sel] - before[sel];
        before[sel] = num[sel];
        return pos[sel];
    }
}

以上です。
リセット中にもカメラを動かせるようになっているので、ゲームに合わせて制限する等してみてください。
次回はマウスでも動かせるようにしようと思います。