YKpages

ロボット分野で勉強したことのまとめ

Unity ゲーム開発日記:当たり判定を作る

はじめに

今回は当たり判定を作っていきます

これができれば最低限のシューティングゲームのシステムができるはずです

Colliderの設定

衝突判定のためにオブジェクトの形状を定義します

Playerには「Box Collider」、Enemyには「Sphere Collider」にしました

いい感じに大きさを調節します

次にオブジェクトのレイヤーを分けます

何も設定していない場合、オブジェクトは全て衝突してしまいます

オブジェクトがどのオブジェクトと衝突するかは、レイヤーを設定することで簡単に決められるようです

Edit > Project Settings > Tags and Layers で以下のように設定

f:id:kato_robotics:20181104054416p:plain

Edit > Project Settings > Physics で以下のように設定

f:id:kato_robotics:20181104071912p:plain

Bullet どうしも衝突するようにしました

Particle System 作成

PlayerまたはEnemyが相手のBulletに衝突して消滅するときのエフェクトとしてParticle Systemを作成します

Create > Effects > Particle System を選択、名前は「enemyParticle」とします

設定は以下のように(好きなように)

f:id:kato_robotics:20181104055856p:plain

f:id:kato_robotics:20181104062043p:plain

f:id:kato_robotics:20181104055907p:plain

新しくマテリアルを作成してenemy用の画像を設定

Shader を Particle > Alpha Blended にする

作成したマテリアルをParticle Systemに設定

こんな感じになります

f:id:kato_robotics:20181104062139p:plain

これをEnemyが消滅したときに発生させます

同じようにPlayer用のParticle Systemも作ります

f:id:kato_robotics:20181104062523p:plain

Particle System のパーティクルに画像を設定

vivi.dyndns.org

衝突時のイベントを書く

衝突判定には方法が二つあるようです

qiita.com

レイヤーを設定するとTriggerを使えますが、 今回はOnCollisionEnter関数を使ってみます

各オブジェクトに「Tag」を設定します

「Tag」を利用して衝突したオブジェクトを判別します

f:id:kato_robotics:20181104081157p:plain

Particle System は Prefab 化してから Instantiate 関数を用いて発生させます

// PlayerDriver.csに追加
public GameObject playerParticleSystem;

// 衝突時のイベント
    void OnCollisionEnter(Collision col)
    {
        // 衝突したオブジェクトのTagが敵の弾かどうか
        if (col.gameObject.tag != "EnemyBullet") return;
        Debug.Log(col.gameObject.tag);
        Debug.Log(gameObject.name);

        // Particle System を起動
        GameObject pps = Instantiate(playerParticleSystem, transform.position, transform.rotation);
        Destroy(pps, 2f);
        // 弾を削除
        Destroy(col.gameObject);
        // Enemyオブジェクトを削除
        Destroy(gameObject);
    }
// EnemyDriver.csに追加
public GameObject enemyParticleSystem;

   // 衝突時のイベント
    void OnCollisionEnter(Collision col)
    {
        // 衝突したオブジェクトのTagがプレイヤーの弾かどうか
        if (col.gameObject.tag != "PlayerBullet") return;
        Debug.Log(col.gameObject.tag);
        Debug.Log(gameObject.name);

        // Particle System を起動
        GameObject eps = Instantiate(enemyParticleSystem, transform.position, transform.rotation);
        Destroy(eps, 2f);
        // 弾を削除
        Destroy(col.gameObject);
        // Enemyオブジェクトを削除
        Destroy(gameObject);
    }

当たり判定を取り入れたゲームはこんな感じになりました

f:id:kato_robotics:20181104081826p:plain

画像では分かりにくいですが、当たり判定がうまく働いています

弾が敵に当たるとParticleSystemを発生させた後、敵が消滅します

おわりに

無事に当たり判定を取り入れることができました

これでシューティングゲームの最低限のシステムができました

次はクリア条件などゲームとして遊べるようにしていきます

Unity ゲーム開発日記:敵を作る

はじめに

今回は敵を作っていきます

まずイメージを作成

プレイヤーは四角形にしたので、 敵は丸にします

f:id:kato_robotics:20181103222143p:plain

ゲームウインドウではこんな感じです

f:id:kato_robotics:20181103222246p:plain

敵(Enemy)を作成

Create > 2D Objects > Sprite を選択して、名前は「Enemy」とします

Sprite のイメージには先ほど作成した Enemy 用のイメージを選択

Playerと同じようにRigidbodyをアタッチします

EnemyManagerを作成

まず、空のオブジェクトを作成して名前を「EnemyManager」とします

新規のC#スクリプトを作成して、「EnemyManager」とします

     void Start () 
    {
        for(int i = 0; i < 10; i++)
        {
            Instantiate(enemy, new Vector3(10f, 10f, 0f), Quaternion.identity);
        }
    }

とりあえずStart関数では、Enemyを10体作成するようにしました

原点に置くとPlayerとかぶってしまうので遠くに置いています

f:id:kato_robotics:20181104015229p:plain

Enemyを動かす

EnemyDriver.csというスクリプトを作成します

public class EnemyDriver : MonoBehaviour {

    public float enemySpeed = 1.0f;
    Rigidbody enemyRigidbody;

    // スクリーンの大きさ
    Vector3 min;
    Vector3 max;

    // 進行方向
    Vector3 enemyDirection;

    void Start () {
        enemyRigidbody = GetComponent<Rigidbody>();
        // スクリーンの大きさを取得
        min = Camera.main.ScreenToWorldPoint(new Vector3(0, 0, 10f));
        max = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, 10f));
        // ランダムに座標を決める
        Vector3 enemyPosition = new Vector3(0, 0, 0);
        enemyPosition.x = Random.Range(min.x, max.x);
        enemyPosition.y = Random.Range(min.y, max.y);
        if(enemyPosition.x > -2f && enemyPosition.x < 2f && enemyPosition.y > -2f && enemyPosition.y < 2f)
        {
            enemyPosition.x += Random.Range(-5f, 5f);
            enemyPosition.y += Random.Range(-5f, 5f);
        }
        transform.position = enemyPosition;
        // ランダムに進行方向を定める
        Vector3 angle = new Vector3(0, 0, Random.Range(0, 360));
        enemyDirection = Quaternion.Euler(angle) * Vector3.up;
        enemyRigidbody.velocity = enemyDirection * enemySpeed;
    }
    
    void Update () {
        WallCollision();
        EnemyLookAt();
    }

    void EnemyLookAt()
    {
        // 進行方向に向かせる(2Dであることに気をつける)
        float angle = GetAngle(enemyDirection);
        transform.eulerAngles = new Vector3(0, 0, angle - 90f);
    }

    float GetAngle(Vector3 v)
    {
        float rad = Mathf.Atan2(v.y, v.x);
        return rad * Mathf.Rad2Deg;
    }

    void WallCollision()
    {
        if (transform.position.x < min.x)
        {
            Rebound(1);
        }
        if (transform.position.x > max.x)
        {
            Rebound(2);
        }
        if (transform.position.y < min.y)
        {
            Rebound(3);
        }
        if (transform.position.y > max.y)
        {
            Rebound(4);
        }
    }

    // enemyが壁に当たって跳ね返る
    void Rebound(int flag)
    {
        // 壁に当たったら位置を戻す
        Vector3 pos = transform.position;
        if (flag == 1) pos.x = min.x;
        else if (flag == 2) pos.x = max.x;
        else if (flag == 3) pos.y = min.y;
        else if (flag == 4) pos.y = max.y;
        transform.position = pos;
        // XまたはY方向の速度を逆にする
        if(flag == 1 || flag == 2) enemyDirection.x = -enemyDirection.x;
        else if(flag == 3 || flag == 4) enemyDirection.y = -enemyDirection.y;
        // 速度を更新
        enemyRigidbody.velocity = enemyDirection * enemySpeed;
    }
}

スクリーン内をランダムに動き回るようにしました

かなり力技になってしまいましたがこれで良しとしておきます(勉強します)

参考

Enemyの進行方向を決定する処理のために参考にしました

qiita.com

Enemyから弾を撃つ

以前に作ったPlayerが弾を撃つ関数を改良しました

PlayerとEnemyで共通の関数を利用することにします

public class ShotBullet : MonoBehaviour {

    public bool isShoot = true;
    public GameObject shotBullet;
    public GameObject bullet;

    IEnumerator Start()
    {
        while (isShoot)
        {
            // 弾をプレイヤーと同じ姿勢でインスタンス化
            GameObject sb = Instantiate(shotBullet, transform.position, transform.rotation);
            GameObject b = Instantiate(bullet, sb.transform);
            b.transform.position = sb.transform.position;
            b.transform.rotation = sb.transform.rotation;
            // 0.3待つ
            yield return new WaitForSeconds(0.3f);
        }
    }
}
public class Bullet : MonoBehaviour {

    public float bulletSpeed = 4f;

    void Start () {
        GetComponent<Rigidbody>().velocity = transform.up.normalized * bulletSpeed;
    }
    
    // Update is called once per frame
    void Update () {
        
    }
}

まず、空のオブジェクト「ShotBullet」をPlayerまたはEnemyの姿勢に合わせて作成

次に「Bullet」オブジェクトを作成して「ShotBullet」の"子"とします

「Bullet」は「ShotBullet」の座標系に従って真っすぐにとんでいきます

f:id:kato_robotics:20181104042530p:plain

おわりに

敵を作成しました

次は当たり判定を付けていきます

これができればシューティングゲームにおける最低限のシステムを作ったことになります

Unity ゲーム開発日記:弾を発射

はじめに

今回はプレイヤーから弾を発射します

その前に

以下のようなプレイヤーの画像を作成しました

f:id:kato_robotics:20181103194947p:plain

ゲーム画面ではこんな感じです(まだ全然ロケットぽくない)

f:id:kato_robotics:20181103195154p:plain

弾のオブジェクトを作成

まず空のオブジェクトを作成して名前を「PlayerBullet」とします

この空のオブジェクトにはスクリプトなどは一切アタッチしません(後で付けます)

次に 2D Sprite を作成して名前を「Bullet」とします

そして「Bullet」を「PlayerBullet」の子にします

  • 親:PlayerBullet(空のオブジェクト)
  • 子:Bullet(2D Sprite)

オブジェクトを親子関係にすると、子のオブジェクトは親のオブジェクトの座標系に従います

これにより「Bullet」の動きを簡単に書くことができます

Bullet.cs を書く

    void Start () 
    {
        GetComponent<Rigidbody>().velocity = transform.up.normalized * bulletSpeed;
    }

このコードでは上方向へ速度を与えます

先ほど書いたとおり、「Bullet」は「PlayerBullet」の座標系に従います

以下のコードのように「PlayerBullet」をPlayerの姿勢に合わせてインスタンス化すれば「Bullet」はPlayerが向いている方向へ発射されます

    IEnumerator Start()
    {
        while(isShoot)
        {
            // 弾をプレイヤーと同じ姿勢でインスタンス化
            Instantiate(playerBullet, transform.position, transform.rotation);
            // 0.3待つ
            yield return new WaitForSeconds(0.3f);
        }
    }

弾を撃っているときのゲーム画面はこのようになりました(Playerがどれか分かりづらいですね)

f:id:kato_robotics:20181103210125p:plain

おわりに

今回はPlayerから弾を発射しました

次は敵を作成したいと思います

Unity ゲーム開発日記:プレイヤーを動かす

はじめに

2Dシューティングゲーム「オシャレなロケット:Oshare-Na-Rocket」のシステムを作っていきます。

今回はプレイヤーを動かすシステムを作ります。

参考

主に公式のチュートリアルを参考にします。

チュートリアルでは2Dモードで作成していますが、 私は3Dモードで作成します(ゲーム自体は基本的に2Dです)。

unity3d.com

以下の記事はPlayerを進行方向へ向けるときに参考にさせていただきました

qiita.com

背景を黒に

Main Camera の "Clear Flag" を "Solid Color" にして色を黒にします

背景はとりあえずこれで良しとします

プレイヤーのオブジェクトを作成

Create > 2D Objects > Sprite を選択

名前は "Player" にします

Player の Sprite に "Knob" を選択すると、黒い背景に白い点が現れます

そしてPlayerに "Rigidbody" をアタッチ

Rigidbodyをアタッチすると、そのオブジェクトを物理的に制御できます

docs.unity3d.com

タッチ(マウス)でプレイヤーを操作

タッチ、またはマウスでプレイヤーを動かします

本来はスマートフォンのタッチ操作で動かすことになりますが、 Unityエディタ上ではマウスで動かします

方法

  • タッチを始めた座標(スクリーン座標)を取得
  • 指をスライドさせた後の座標を取得
  • 1と2からプレイヤーが移動する方向を計算
  • プレイヤー移動

タッチ操作のための関数を作成

// タッチを始めた座標
Vector2 TouchPositionFrist;
     void PlayerTouch ()
    {
        // タッチ数をカウント、0より大きい場合(つまりタッチがある場合)
        if (Input.touchCount > 0)
        {
            Touch touch = Input.GetTouch(0);

            // タッチを始めた時
            if (touch.phase == TouchPhase.Began)
            {
                //タッチを始めた座標を取得
                TouchPositionFrist = touch.position;
            }
            // 指をスライドさせている時
            else if (touch.phase == TouchPhase.Moved)
            {
                // プレイヤーを移動させる関数へ座標を渡す
                PlayerMove(TouchPositionFrist, touch.position);
            }
            // 指を離した時
            if (touch.phase == TouchPhase.Ended)
            {
                // プレイヤーの速度を0にする
                PlayerRigidbody.velocity = new Vector3(0, 0, 0);
            }
        }
    }

プレイヤーを動かす関数

    void Start () 
    {
        // プレイヤーのRigidbodyコンポーネントを取得
        PlayerRigidbody = GetComponent<Rigidbody>();
    }
    void PlayerMove (Vector3 v1, Vector3 v2)
    {
        // 二つのベクトルから指をスライドさせた方向を求める
        Vector3 direction = (v2 - v1).normalized;
        // 指をスライドさせた方向とは逆向きにプレイヤーを移動
        PlayerRigidbody.velocity = -direction * PlayerSpeed;
    }

プレイヤーに進行方向を向かせる

PlayerMove関数に追加

    void PlayerMove (Vector3 v1, Vector3 v2)
    {
        Vector3 direction = (v2 - v1).normalized;
        PlayerRigidbody.velocity = direction * PlayerSpeed;
        // 進行方向に向かせる(2Dであることに気をつける)
        float angle = GetAngle(direction);
        transform.eulerAngles = new Vector3(0, 0, angle);
    }

アークタンジェントを使ってZ軸の向きを求める

    float GetAngle(Vector3 v)
    {
        float rad = Mathf.Atan2(v.y, v.x);
        return rad * Mathf.Rad2Deg;
    }

移動制限を設ける

Start関数でスクリーンの大きさを取得します

プレイヤーをスクリーン内で動かすようにするためです

Camera.main.ScreenToWorlePointでカメラがNullだよみたいなエラーが出た場合、 CameraのTagが設定されていない可能性があります。CameraのTagを"MainCamera"にすると直ります。

    void Start () 
    {
        Debug.Log("!!START!!");
        playerRigidbody = GetComponent<Rigidbody>();
        // スクリーンの大きさを取得
        min = Camera.main.ScreenToWorldPoint(new Vector3(0, 0, 10f));
        max = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, 10f));
    }

そしてPlayerMove関数を以下のように変更します。

Mathf.Clamp関数は定めた範囲から出た場合、内側に戻してくれます。

marginは余裕を持たせるために設定しました。

    void PlayerMove (Vector3 v1, Vector3 v2)
    {
        // 指をスライドさせた方向に移動
        Vector3 direction = (v2 - v1).normalized;
        playerRigidbody.velocity = direction * playerSpeed;
        // 進行方向に向かせる(2Dであることに気をつける)
        float angle = GetAngle(direction);
        transform.eulerAngles = new Vector3(0, 0, angle - 90f);
        // 移動制限を設ける
        Vector3 playerPosition = transform.position;
        float margin_x = max.x / 20f;
        float margin_y = max.y / 20f;
        playerPosition.x = Mathf.Clamp(playerPosition.x, min.x + margin_x, max.x - margin_x);
        playerPosition.y = Mathf.Clamp(playerPosition.y, min.y + margin_y, max.y - margin_y);
        transform.position = playerPosition;
    }

Camera.main.ScreenToWorlePointのエラーについて

blog.be-style.jpn.com

今回書いたコード

github.com

おわりに

今回はプレイヤーを動かすシステムを作成しました

次は弾を発射するシステムを作っていきます

Unity ゲーム開発日記:プロジェクト作成と GitHub

はじめに

これまで二年以上Unityを使って色々してきましたが、 一つのゲームを完成させたことはありませんでした。

今回は思い切って、すぐ完成させられそうなシンプルなゲームを作成し、 Google Play Store などにリリースしてみようと思います。

作成するゲームは2Dのシューティングゲームです。

できる限りシンプルでオシャレなゲームにする予定です。

タイトルは「オシャレなロケット Oshare-Na-Rocket」

流れ

  • Unityプロジェクト作成、GitHubで管理
  • ゲームシステム作成
  • 素材作成(イラスト、BGM)
  • リリース

環境

  • Windows10
  • Unity 2018.2.14f1

参考

Unity公式チュートリアル

unity3d.com

こちらも

baba-s.hatenablog.com

プロジェクト作成

現在の最新バージョン Unity 2018.2.14f1 をインストール

Unity のバージョン管理は公式の「Unity Hub」を使うと便利です

インストールが終わったら新規プロジェクトを作成

GitHub で管理

Git を利用するとプロジェクトのバージョン管理が簡単にできます

今回は GitHub というwebサービスを利用します

GitHub の利用方法が分からない人は各自でググってください(私は良く分かっていません笑)

UnityのプロジェクトをGitHubにあげる場合はきちんと「.gitignore」を設定すると楽になります

GitHub で Repository を作成するときに .gitgnore を選択できるのでそこで「Unity」を選択するだけで済みます。

一応、方法は以下にまとめておきます

kato-robotics.hatenablog.com

以下が私のGitHubのページです

github.com

おわりに

この記事ではUnityのプロジェクトを作成してGitHubにあげました。

次はゲームのシステムを作っていきます。山場です。

Unity と ROS の通信 ( ROS# : ros-sharp )

はじめに

UnityアプリとROSの間でデータをやり取りしたい

ROS#というライブラリを使うと簡単に実現できるようです

公式の情報を見ればだいたい分かりますが、 一応メモを残しておきます

github.com

参考

qiita.com

バージョン1.3以降の場合

kuwamai.hatenablog.com

環境

Unity側

  • Windows10
  • Unity 2018.2.8f1
  • ROS# v1.0 (バージョン:v1.3 → v1.0)

ROS側

  • Ubuntu 16.04 LTS
  • ROS Kinetic

ROS#をインポート

GitHubページから最新版UnityPackageをダウンロードしてインポート

UnityのAsset Storeからもインポートできるようです

github.com

ros-sharpパッケージ内にサンプルシーンが入っているのでそれを見るとだいたい使い方が分かります

(Assets > RosSharp > Scenes)

オブジェクトのPoseを配信してみる

Unity側の準備

Hierarchyで空(Empty)のオブジェクトを作成(名前:ROSConnector)

Create>Create Enpty で作成

そのオブジェクトに Assets > RosSharp > Script > RosCommunication > RosConnector.cs をアタッチ

(もしくはRosSharp > Prefabs > RosConnectorから作成)

InspectorでROS側PCのURLを入力

f:id:kato_robotics:20181030160707p:plain

次にCubeを作成します。ここではこのCubeのPoseを配信してみます。

HierarchyでCreate > 3D Object > Cube で作成

作成したCubeに RosSharp > Scripts > MessageHandling > PoseProvider.cs をアタッチ

PoseProviderのframeIdは、特に何もなければUnityと入力

f:id:kato_robotics:20181030162029p:plain

最後にROSConnectorに RosSharp > Scripts > RosCommunication > UnityTimePublisher.cs をアタッチ

Topic/poseと入力

Message ProviderCubeを選択

f:id:kato_robotics:20181030162535p:plain

ROS側の準備

すでにROSがインストールされているUbuntuを用意

以下のコマンドでrosbridge-serverをインストール

sudo apt-get install ros-kinetic-rosbridge-server

rosbridge_suite - ROS Wiki

rosbridge-serverはROSとROSではないソフトウェアで簡単に通信を行えるようにするものです

以下のコマンドで実行

roslaunch rosbridge_server rosbridge_websocket.launch

やってみる

準備が整ったらUnityのプレイボタンを押します

UnityとROSの接続が成功するとubuntu側の端末にClient connected. 1 clients total.と表示されます

f:id:kato_robotics:20181030163537p:plain

接続が成功したら以下のコマンドを打ってPoseが配信されているかを確認します

rostopic list

f:id:kato_robotics:20181030163905p:plain

次に以下のコマンドを打ってメッセージの内容を確認します

rostopic echo /pose

/poseトピックのメッセージが確認できると思います

ここでUnity側のCubeのPositionやRotationを変更するとメッセージの内容も変わることが確認できます

f:id:kato_robotics:20181030164347p:plain

座標系について

ちなみにですが、UnityとROSでは座標系が異なっています

そのことについてまとめたので確認してみてください

kato-robotics.hatenablog.com

通信が上手くいかない場合

考えられる点

  • ROSConnectorで入力するROS側PCのURLが間違っている
  • Unity側PCとROS側PCが同じネットワークにいない
  • rosbridge-serverを起動していない
  • Unity側の準備が足りていない

など

配信されているPoseの可視化について

ROSには可視化ツールとしてRvizというソフトウェアがあります

このrvizで今回Unityから配信したPoseデータを可視化してみるとおもしろいかと思います

以下のコマンドで実行できます

rosrun rviz rviz

使い方は調べれば出てきます

rvizで可視化する場合、Unity上でCubeにアタッチしたPoseProviderのFrameIdを設定する必要があります

気になること

ROS#がiOS端末上では上手く動かない問題

(2019/04/17追記)

以下情報

github.com

まとめ

ROS#を利用してUnityとROSでデータをやり取り

おわりに

今回はUnityからROSへデータを送りましたが、 もちろんROSからUnityへデータを送ることもできます

またROS#の強みは、簡単であることと、URDFも扱えるということみたいです

ROS#の存在はありがたい

おまけ

kato-robotics.hatenablog.com

kato-robotics.hatenablog.com

ROS と Unity における座標系の違いについて

はじめに

ROS と Unity でロボットの座標データをやり取りするときに、 座標系の違いを調べる必要があったのでまとめておきます

座標系について

  • ROS : 右手系
  • Unity : 左手系

f:id:kato_robotics:20181024194534j:plain
Unity と ROS の座標系について

変換方法

Unity -> ROS

Position: Unity(x,y,z) -> ROS(z,-x,y)
Quaternion: Unity(x,y,z,w) -> ROS(z,-x,y,-w)

ROS -> Unity

Position: ROS(x,y,z) -> Unity(-y,z,x)
Quaternion: ROS(x,y,z,w) -> Unity(-y,z,x,-w)

おわりに

メモしておかないと忘れます