Unity ゲーム開発日記:当たり判定を作る
はじめに
今回は当たり判定を作っていきます
これができれば最低限のシューティングゲームのシステムができるはずです
Colliderの設定
衝突判定のためにオブジェクトの形状を定義します
Playerには「Box Collider」、Enemyには「Sphere Collider」にしました
いい感じに大きさを調節します
次にオブジェクトのレイヤーを分けます
何も設定していない場合、オブジェクトは全て衝突してしまいます
オブジェクトがどのオブジェクトと衝突するかは、レイヤーを設定することで簡単に決められるようです
Edit > Project Settings > Tags and Layers で以下のように設定
Edit > Project Settings > Physics で以下のように設定
Bullet どうしも衝突するようにしました
Particle System 作成
PlayerまたはEnemyが相手のBulletに衝突して消滅するときのエフェクトとしてParticle Systemを作成します
Create > Effects > Particle System を選択、名前は「enemyParticle」とします
設定は以下のように(好きなように)
新しくマテリアルを作成してenemy用の画像を設定
Shader を Particle > Alpha Blended にする
作成したマテリアルをParticle Systemに設定
こんな感じになります
これをEnemyが消滅したときに発生させます
同じようにPlayer用のParticle Systemも作ります
Particle System のパーティクルに画像を設定
衝突時のイベントを書く
衝突判定には方法が二つあるようです
レイヤーを設定するとTriggerを使えますが、 今回はOnCollisionEnter関数を使ってみます
各オブジェクトに「Tag」を設定します
「Tag」を利用して衝突したオブジェクトを判別します
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); }
当たり判定を取り入れたゲームはこんな感じになりました
画像では分かりにくいですが、当たり判定がうまく働いています
弾が敵に当たるとParticleSystemを発生させた後、敵が消滅します
おわりに
無事に当たり判定を取り入れることができました
これでシューティングゲームの最低限のシステムができました
次はクリア条件などゲームとして遊べるようにしていきます
Unity ゲーム開発日記:敵を作る
はじめに
今回は敵を作っていきます
まずイメージを作成
プレイヤーは四角形にしたので、 敵は丸にします
ゲームウインドウではこんな感じです
敵(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とかぶってしまうので遠くに置いています
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の進行方向を決定する処理のために参考にしました
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」の座標系に従って真っすぐにとんでいきます
おわりに
敵を作成しました
次は当たり判定を付けていきます
これができればシューティングゲームにおける最低限のシステムを作ったことになります
Unity ゲーム開発日記:弾を発射
はじめに
今回はプレイヤーから弾を発射します
その前に
以下のようなプレイヤーの画像を作成しました
ゲーム画面ではこんな感じです(まだ全然ロケットぽくない)
弾のオブジェクトを作成
まず空のオブジェクトを作成して名前を「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がどれか分かりづらいですね)
おわりに
今回はPlayerから弾を発射しました
次は敵を作成したいと思います
Unity ゲーム開発日記:プレイヤーを動かす
はじめに
2Dシューティングゲーム「オシャレなロケット:Oshare-Na-Rocket」のシステムを作っていきます。
今回はプレイヤーを動かすシステムを作ります。
参考
主に公式のチュートリアルを参考にします。
チュートリアルでは2Dモードで作成していますが、 私は3Dモードで作成します(ゲーム自体は基本的に2Dです)。
以下の記事はPlayerを進行方向へ向けるときに参考にさせていただきました
背景を黒に
Main Camera の "Clear Flag" を "Solid Color" にして色を黒にします
背景はとりあえずこれで良しとします
プレイヤーのオブジェクトを作成
Create > 2D Objects > Sprite を選択
名前は "Player" にします
Player の Sprite に "Knob" を選択すると、黒い背景に白い点が現れます
そしてPlayerに "Rigidbody" をアタッチ
Rigidbodyをアタッチすると、そのオブジェクトを物理的に制御できます
タッチ(マウス)でプレイヤーを操作
タッチ、またはマウスでプレイヤーを動かします
本来はスマートフォンのタッチ操作で動かすことになりますが、 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のエラーについて
今回書いたコード
おわりに
今回はプレイヤーを動かすシステムを作成しました
次は弾を発射するシステムを作っていきます
Unity ゲーム開発日記:プロジェクト作成と GitHub
はじめに
これまで二年以上Unityを使って色々してきましたが、 一つのゲームを完成させたことはありませんでした。
今回は思い切って、すぐ完成させられそうなシンプルなゲームを作成し、 Google Play Store などにリリースしてみようと思います。
作成するゲームは2Dのシューティングゲームです。
できる限りシンプルでオシャレなゲームにする予定です。
タイトルは「オシャレなロケット Oshare-Na-Rocket」
流れ
- Unityプロジェクト作成、GitHubで管理
- ゲームシステム作成
- 素材作成(イラスト、BGM)
- リリース
環境
- Windows10
- Unity 2018.2.14f1
参考
Unity公式チュートリアル
こちらも
プロジェクト作成
現在の最新バージョン Unity 2018.2.14f1 をインストール
Unity のバージョン管理は公式の「Unity Hub」を使うと便利です
インストールが終わったら新規プロジェクトを作成
GitHub で管理
Git を利用するとプロジェクトのバージョン管理が簡単にできます
GitHub の利用方法が分からない人は各自でググってください(私は良く分かっていません笑)
UnityのプロジェクトをGitHubにあげる場合はきちんと「.gitignore」を設定すると楽になります
GitHub で Repository を作成するときに .gitgnore を選択できるのでそこで「Unity」を選択するだけで済みます。
一応、方法は以下にまとめておきます
以下が私のGitHubのページです
おわりに
この記事ではUnityのプロジェクトを作成してGitHubにあげました。
次はゲームのシステムを作っていきます。山場です。
Unity と ROS の通信 ( ROS# : ros-sharp )
はじめに
UnityアプリとROSの間でデータをやり取りしたい
ROS#というライブラリを使うと簡単に実現できるようです
公式の情報を見ればだいたい分かりますが、 一応メモを残しておきます
参考
バージョン1.3以降の場合
環境
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からもインポートできるようです
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を入力
次にCubeを作成します。ここではこのCubeのPoseを配信してみます。
HierarchyでCreate > 3D Object > Cube で作成
作成したCubeに RosSharp > Scripts > MessageHandling > PoseProvider.cs をアタッチ
PoseProviderのframeId
は、特に何もなければUnity
と入力
最後にROSConnectorに RosSharp > Scripts > RosCommunication > UnityTimePublisher.cs をアタッチ
Topic
に/pose
と入力
Message Provider
はCube
を選択
ROS側の準備
すでにROSがインストールされているUbuntuを用意
以下のコマンドでrosbridge-serverをインストール
sudo apt-get install ros-kinetic-rosbridge-server
rosbridge-serverはROSとROSではないソフトウェアで簡単に通信を行えるようにするものです
以下のコマンドで実行
roslaunch rosbridge_server rosbridge_websocket.launch
やってみる
準備が整ったらUnityのプレイボタンを押します
UnityとROSの接続が成功するとubuntu側の端末にClient connected. 1 clients total.
と表示されます
接続が成功したら以下のコマンドを打ってPoseが配信されているかを確認します
rostopic list
次に以下のコマンドを打ってメッセージの内容を確認します
rostopic echo /pose
/poseトピックのメッセージが確認できると思います
ここでUnity側のCubeのPositionやRotationを変更するとメッセージの内容も変わることが確認できます
座標系について
ちなみにですが、UnityとROSでは座標系が異なっています
そのことについてまとめたので確認してみてください
通信が上手くいかない場合
考えられる点
- 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追記)
以下情報
まとめ
ROS#を利用してUnityとROSでデータをやり取り
おわりに
今回はUnityからROSへデータを送りましたが、 もちろんROSからUnityへデータを送ることもできます
またROS#の強みは、簡単であることと、URDFも扱えるということみたいです
ROS#の存在はありがたい
おまけ
ROS と Unity における座標系の違いについて
はじめに
ROS と Unity でロボットの座標データをやり取りするときに、 座標系の違いを調べる必要があったのでまとめておきます
座標系について
- ROS : 右手系
- Unity : 左手系
変換方法
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)
おわりに
メモしておかないと忘れます