【チュートリアル】uGUIとPhysics2Dでブロック崩しを作ってみる

チュートリアルとして試しに作ってみました。

比較的プログラムをあまりできない人向けにできる限り崩して書くようには気を付けます。

また、画像が用意できない人向けに画像リソースは一切使わないようにしてます。

※この記事はUnity5がインストールされていることが前提です。

私の環境はUnity5.3.f1です。これと違うと細かい差異があるかもしれません

が、大きくは変わらないと思われます。

  1. この記事で作るもの
  2. Unityのプロジェクトを作る
  3. フォルダの整理
  4. uGUIの設定をする
    1. Canvas作る
    2. Canvasの設定を変更
    3. ゲームの領域のGameObjectを作っておく
  5. Hierarchyの整理
  6. 壁を作る
    1. 左壁
    2. 右壁
    3. 上壁
  7. ボール作る
    1. ボールに物理演算機能を追加
    2. ボールを押すプログラム
  8. ボールを壁にぶつける
    1. 左壁にColliderをつける
    2. 右壁にColliderをつける
    3. 上壁にColliderをつける
  9. ボールを跳ね返らせる
    1. Physics Materialを作る
  10. 自機のバーを作る
    1. バーを移動させる
      1. 画面内のどこをクリックしたか判定用オブジェクト作る
    2. 判定オブジェクトクリック中の関数を用意する
    3. 判定オブジェクトにイベント時に呼ばれる関数を登録する
    4. ドラッグに合わせてバーを動かす処理を記述する
  11. ブロックを作る
    1. まず1つだけ作る
    2. ぶつかったらブロックが消えるようにする
    3. ブロックを量産する
      1. Prefabを作る
      2. Prefabをコピーする処理を記述する
  12. 下に落ちたらゲームオーバーにする
    1. 落下判定用オブジェクトを作る
    2. 落下判定オブジェクトに当たったときの処理を記述
  13. 課題
    1. ボールが毎回同じ挙動…
    2. バーがはみ出す…
    3. ブロックに耐久力
    4. 失敗できる回数を設ける
    5. スコアの導入
    6. アイテムの導入
    7. シーン遷移の導入
  14. canvasのlocalScaleを掛ける理由
    1. 画面の大きさでボールの速度が変わる
    2. 解決法
  15. CanvasがScreen Space Cameraの時のクリック位置の計算

この記事で作るもの

この記事は超簡単なブロック崩しを、スマートフォン向けを想定して作ります。

スマートフォンは縦持ち前提にして、画面の大きさは無難な横640x縦960で作っていきます。

記事自体は長いですが、していることは大したことはありませんし、慣れてなくても小一時間もあれば終わる内容だと思います。

結果はこれです

Unityのプロジェクトを作る

まずはUnityのプロジェクトがないことには始まらないのでプロジェクトを作ります。

File → New ProjectもしくはUnity起動時のプロジェクト選択画面からNEWを選択します。

※Unity起動時のプロジェクト選択画面はpreferenceで「Load Previous Project on Startup」のチェックが外れている場合にのみ表示されます。初期値ではチェックがついており、最後に開いたプロジェクトが開くようになっています。

createunityproj

Project Nameにはブロック崩しということでBlockCollapsingと入力します、。

Locationはどこでもいいですが、わかりやすい場所がいいと思います。

下に3D/2Dの選択がありますが、2Dを選んでおきます。

あとはCreate Projectでプロジェクト完成です。

フォルダの整理

プロジェクト作ってはじめにしておきたいのはフォルダの整理です。

Projectペイン内に事前に必要なフォルダを用意しておいてファイルの整理整頓をしやすくしておいた方が後々混乱が少ないです。

projectpain

このような感じで、ゲームに関する部分を置くGameフォルダを作り、その下にデータ、シーン、プログラムを置くフォルダを作っています。

プロジェクトの内容によっては更に細分化して管理しやすくしておきます。(例えば画像を置くフォルダ、音声を置くフォルダをDatasの下に作っておく等々)

今回は大したことない内容なのでこの程度で大丈夫です。

uGUIの設定をする

フォルダを設定したら早速シーンを作っていきます。

File → New Sceneを選択し新規シーンを作ります。新規シーンが出来たらさっそく保存しておきます。

File → Save Sceneを選択します。保存場所を聞かれますので、Game → Scenesを選びファイル名をGameにします。

savescene

以後、いざという時の為に定期的にSave Sceneをする事をお勧めします。(なんだったらバージョン管理も)

最初はMain Cameraだけがある青い画面です。これにuGUIの基本設定を施していきます。

Canvas作る

HierarchyのcreateからUI → Canvasを選択します。

CreateCanvas

これだけでCanvasが出来ます。

Canvasの設定を変更

640×960に合わせる為設定を変えます。

uGUIの基本の基本
uGUIの独自の描画処理なんかの記事を書きましたが、改めてuGUIの基本を書きたいと思います。 といってももっといい記事がその辺にいろいろありそうですが… uGUIでUI作るための準備 スマートフォン向けのUIを作る前提で準備...

こちらのuGUIの基本設定の話も参考にしてみてください。

スマートフォンは解像度も縦横の比率もいろいろあるのでレターボックスで設定します。(ゲーム画面の縦横の比率は保ったままたりないところは帯が表示される形式)

HierarchyでCanvasを選択し、UI Scale ModeScale With Screen Sizeへ変更します。

SettingCanvas1

Reference Resolution640960へ変更します。

Screen Match ModeExpandへ変更します。

settingscaler

これでCanvasの基本設定は完了です。

ゲームの領域のGameObjectを作っておく

画面の端等に便利なので、ゲームの範囲一杯のオブジェクトを作っておきます。

Canvasを右クリックしてCreate Emptyします。

setgamearea

名前はわかりやすくGameAreaにし、WidthとHeightを画面一杯の640と960に変更します。

それ以外は変更無しです。

Hierarchyの整理

projectのフォルダを整理したようにHierarchyのGameObjectも整理しておくとやりやすいです。

ブロック崩しなので、構成物は壁・ブロック・ボール・自機バーなので、これ用のGameObjectを用意しておきます。

inithierarchy

Hierarchyで下にあるやつが前面に表示されるので、下から一番手前に表示されてほしい順に並べています。(バーが一番奥、ボールが一番手前に出るように)

この4つのRectTransformはStrechにしてLeft等はすべて0にしておきます。

inithierarchy2

この様な感じです。Hierarchyで4つ選択した状態で変更すれば全部まとめて変更出来ます。

壁を作る

では壁を作ります。

先ほど作ったWallsに左右と上の壁を作ります。

左壁

Wallsを右クリックしてUI → Imageを選択します。

名前はLeftにしておきます。setleft

RectTransformでLeft Strechに設定し、Widthに40、Pos Xに20(Widthの半分)を設定します。縦幅は上下いっぱいにしたいので、TopとBottomを0にします。

他は特に弄る必要はなしです。Colorはお好みで自由な色にしてください。

setleft2

この様な感じでゲーム領域(内側の四角)の左端に四角が表示されると思います。

※外側の四角は実際の画面の大きさです。

同様にして上と右も作っていきます。

右壁

先ほどと同じようにWallsを右クリックしてUI → Imageを作ります。

こちらは右端に寄せるのでRight Strechで設定します。setright

上壁

これも同じようにWallsを右クリックしてUI → Imageを作ります。

上に付けたいのでTop Strechにし、こちらは横長なのでLeftとRightを0にし、Heightの方を40にします。

Pos Yは-20です。(Heightの半分)

settop

これでこのようになっていればOKです。

setwall

ボール作る

次にボールを作ります。

これもBallsを右クリックしUI → Imageを選択します。

WidthとHeightを20に設定し他は特に変更なしです。色は好きなものを設定してください。

ボールは出来ましたがこれでは点があるだけなので、これを動くようにします。

ボールに物理演算機能を追加

ボールにUnityの物理演算の機能を設定します。

ボールのInspectorのAdd ComponentからPhysics2D → Rigidbody 2DPhysics2D → Circle Collider 2Dを選択します。

addphysics2d

Rigidbodyが物理演算の挙動を行うためのものです。これがついたものは壁にぶつかって跳ね返ったりと言った挙動が自動で行われる様になります。

Circle Colliderがあたり判定用です。CircleのColliderなのでそのまま円形のあたり判定が設定出来ます。

setballphysics

Gravity Scaleには重力の影響を受けたくないので0を設定します。

Sleeping ModeはNever Sleepが推奨なのでそれに変更します。

Circle ColliderのRadiusは円の半径なので幅の半分の10を設定します。

setballphysics2

緑の円がCircle Colliderの範囲です。

物理演算コンポーネントの設定はこれで終わりです。

ただ、このままでもまだ点が真ん中に出るままです。

というのもいくら物理演算しようが止まっているものが勝手に動くことは無いからです。

というわけで押してやるプログラムを用意します。

ボールを押すプログラム

まずソースファイルを用意します。ProjectでScriptsを右クリックしCreate → C# Scriptを選択します。

newcsscript

名前はBallにしておきます。

Startでボールに初速を設定してやります。

using UnityEngine;

/// <summary>
/// ボールの処理を記述
/// </summary>
public class Ball : MonoBehaviour
{
    /// <summary>
    /// このスクリプトが設定されているGameObjectが初期化されるタイミングで行われる処理を記述する
    /// </summary>
    void Start()
    {
        // このGameObjectに付与されている物理演算コンポネントを取得
        var body = gameObject.GetComponent<Rigidbody2D>();
        // 画面描画用のCanvasを取得
        var canvas = GetComponentInParent<Canvas>();

        // ボールを動かす向き:とりあえず右上
        // normalized(正規化)で大きさを1のベクトルに
        var direction = new Vector2( 1, 1 ).normalized;

        // velocity(速度)を設定する。
        // この速さは秒速。上でdirectionの大きさは1にしているので1*640=640が速さになる
        body.velocity = direction * 640 * canvas.transform.localScale.x;
    }
}

ソースはこの通りです。

void Start()はGameObjectの初期化時に1度だけ呼ばれます。ここで初速を設定してやります。

速度はRigidbody2Dのvelocityですので、それを変更しています。

エラーが出てないことを確認したら、BallのAdd ComponentからScripts → Ballを選択します。Ball.csがボールに付与されたので実行してみましょう。

実行は画面上の横三角を押すとできます。

右上にシューンと飛んでいって壁もすりぬけそのまま画面外に飛んでいきます…

localScaleを掛ける理由は後述します。

ボールを壁にぶつける

とりあえず動いたものの壁をすり抜けられては困るのでぶつかるようにします。

やり方は簡単で壁にColliderをつけるだけです。

左壁にColliderをつける

左壁を選択し、Add ComponentからPhysics 2D → Box Collider 2Dを選択します。これは長方形なあたり判定が設定できます。

setwallcollider

Sizeには壁の大きさである40と960をそのまま設定します。

右壁にColliderをつける

左と全く同じです。Box Colliderを追加し、Sizeに40と960を設定します。

上壁にColliderをつける

これも同様です。Box Colliderを追加し、Sizeに640と40を設定します。

 

これであたり判定がつきました、では実行してみましょう。

ちゃんと壁にボールがぶつかり画面外に飛んでいかなくなりました。

しかし、今度は跳ね返らずに壁をずっていきます。

ボールを跳ね返らせる

3Dのゲームでプレイヤーが壁に向かって走っているところを想像してもらえるとわかりますが、プレイヤーは壁にぶつかっても跳ね返らずに押し出される程度で壁にそってずって行きます。

物理演算の初期設定ではこのような挙動になるようになっています。

これではブロック崩しにならないので、材質の設定で跳ね返るようにします。

Physics Materialを作る

その材質の設定がPhysics Materialです。

これに摩擦係数(Friction)と反射係数(Bounciness)を設定することができ、これによって壁にあたった時の挙動を変える事ができます。

ProjectのDatasを右クリックし、Create → Physics2D Materialを選択します。

名前はBallPhysicsMaterialにしておきましょう。

createphysicsmaterial

マテリアルの設定でFriction(摩擦係数)を0にBounciness(反射係数)を1に変更します。

 

setballphysicsmaterial

これで摩擦(何かに触れている時の減速)がなくなり、反射が最大になり壁ずりしなくなります。

最後にマテリアルは装備しないと意味が無いので、Ballのコライダーに設定してやります。

setballphysicsmaterial2

これで実行すると壁でちゃんと跳ね返ってそれっぽくなりました。

自機のバーを作る

ここまで来たら自機を作ってそれに跳ね返らせるようにします。

跳ね返る部分は壁でやったので簡単です。

HierarchyのBarsを右クリックてUI → Imageを選択し作ります。

setbart

下側に表示するのでBottom Centerを選択し、位置や大きさは適当にそれっぽい場所に設定しました。

これにもBox Collider2Dをつけて、Sizeには150と20(Rect Transformの大きさと一緒)にします。

これでボールがバーで跳ね返るようになっているはずです。

ためしにPos Xを調整してボールに当たるところにおいてみてください。

バーを移動させる

このままではゲームにならないのでクリック(タッチ)されている場所に移動するようにします。

画面内のどこをクリックしたか判定用オブジェクト作る

画面一杯にあたり判定を持ったオブジェクトを作ってそれでクリック場所を判定できるようにします。

Canvasを右クリックし、UI → Imageを選択します。名前はTouchAreaなんかがわかりやすいと思います。

settoucharea

ストレッチでLeft等は全部0にして画面一杯に設定します。

ColorのAlphaも0にして見えなくします。

settoucharea2

これで見えない画面一杯の当たり判定が出来上がりました。

判定オブジェクトクリック中の関数を用意する

では自機に画面を押されているときの処理を記述します。

Bar.csをBallと同様に作ります。(Scriptsで右クリック → Create → C# Script)

using UnityEngine;
using UnityEngine.EventSystems;

public class Bar
    : MonoBehaviour
{
    public void OnTouch( BaseEventData arg )
    {
        // EventTriggerでは汎用の為引数はBaseEventDataですが、ドラッグのイベントでは実態はPointerEventDataなのでキャストしてあげる
        PointerEventData e = arg as PointerEventData;
        // ここに押されているときの処理を記述する
    }
}

これはEventTriggerというのを使って実装する予定なので、

public void 関数名( BaseEventData )

の形式で定義する必要があります。

エラーが出てないのを確認したら、自機のGameObjectにAdd Componentしておきます。(Scripts → Bar)

判定オブジェクトにイベント時に呼ばれる関数を登録する

判定オブジェクトにEventTriggerをつけてやります。

画面一杯に作ったTouchAreaを選択し、Add ComponentからEvent → EventTriggerを選択します。

addeventtrigger

EventTriggerを追加したらAdd New Event TypeからDragを選択します。

addneweventtypedrag

次いで右下の+を押下します。

addeventhandler

ここにGameObjectと関数を設定するとドラッグ時のイベントが処理できるようになります。

seteventhandler

Hierarchyから赤四角のところにドラッグ&ドロップで設定できます。(赤四角の右の⦿を押すと一覧が表示されて、そこから選択することもできます。)

GameObjectを登録したら右側No Functionとなっているところを選択し、先ほど作ったOnTouchを設定します。

これで先ほどの関数がドラッグ時に呼ばれるようになりました。

using UnityEngine;
using UnityEngine.EventSystems;

public class Bar
    : MonoBehaviour
{
    public void OnTouch( BaseEventData arg )
    {
        // EventTriggerでは汎用の為BaseEventDataですが、ドラッグのイベントでは実態はPointerEventDataなのでキャストしてあげる
        PointerEventData e = arg as PointerEventData;
        // ドラッグされている場所をログに出す
        Debug.Log( e.position );
    }
}

試しにこのようにして見るとドラッグする(クリックしたまま動かす)とログが表示されると思います。

ログが出ない場合は何か間違っていますので、EventTriggerにちゃんと登録されているか等見なおしてみてください。

ドラッグに合わせてバーを動かす処理を記述する

とても簡単です。

using UnityEngine;
using UnityEngine.EventSystems;

public class Bar
    : MonoBehaviour
{
    public void OnTouch( BaseEventData arg )
    {
        // EventTriggerでは汎用の為BaseEventDataですが、ドラッグのイベントでは実態はPointerEventDataなのでキャストしてあげる
        PointerEventData e = arg as PointerEventData;
        var transform = GetComponent<RectTransform>();
        var position = transform.position; // 今のtransformの座標を取得
        position.x = e.position.x;         // ドラッグされている場所を代入
        transform.position = position;     // transformに座標を反映
    }
}

transformのpositionのxにイベントのpositionのx(ドラッグされている座標)を入れてやるだけです。

CanvasがOverlayの場合です。Screen Space Cameraの場合はまた少し違った処理が必要です。(後述します)

実行してみると、クリックしたまま動かすとそれに追従してバーが動くようになりました。

これで大分ブロック崩しっぽくなってきました。

ブロックを作る

ここまで来たら本命のブロックを作ります。

まず1つだけ作る

HierarchyでBlocksを右クリックしCreateからImageを選択します。

blockcoord

Top Centerで座標と大きさは目分量で決めました。色も含めお好みで調整して良いです。

次に当たり判定をつけます。

自機と同じようにAdd ComponentからPhysics 2D → Box Collider 2Dを選択します。

setblockcollider

サイズをRectTransformのWidthとHeightと一緒にします。

実行してみるとブロックに跳ね返るようになりました。

ぶつかったらブロックが消えるようにする

このままだといつまでたっても崩れないのでボールが当たったら消えるようにします。

ProjectのScriptsに新たにC# ScriptのBlock.csを作ります。

using UnityEngine;

/// <summary>
/// ブロックに関する処理
/// </summary>
public class Block : MonoBehaviour
{
    /// <summary>
    /// ボールがぶつかったときの処理
    /// </summary>
    void OnCollisionExit2D()
    {
        // ブロックのGameObjectを消す
        Destroy( gameObject );
    }
}

OnCollisionExit2DがPhysics2Dで何かが衝突したときに自動的に行われる処理です。

1字でも間違いがあると処理が実行されませんので注意してください。

ソースが出来たらBlockのGameObjectにAdd Componentするのを忘れないように。

実行するとぶつかるとブロックが消えるようになりました。

ブロックを量産する

1つだとブロック崩しっぽさが足りないのでこれを量産します。

Editorでコピペしてもいいのですが、それだと変更時に手間がかかるのでスクリプトで自動生成するようにしましょう。

Prefabを作る

Hierarchyの先ほど作ったBlockをProjectのDatasのところにドラッグ&ドロップしてください。

これだけでPrefabが完成です。UnityではPrefabをスクリプト上でコピーし量産する事が出来ます。

Prefabが出来たらGameObjectの方はもういらないので、HierarchyのBlockは削除してください。(Projectの方のBlockはPrefabなので消さないでください。)

Prefabをコピーする処理を記述する

上述のとおりPrefabはスクリプト上からコピペすることが出来ます。

ScriptsにC# ScriptのGame.csを作ります。

using UnityEngine;
using System.Collections;

public class Game : MonoBehaviour
{
    /// <summary>
    /// ブロックを追加するGameObject
    /// </summary>
    [SerializeField]
    private RectTransform blocks_;

    /// <summary>
    /// ブロックの元になるPrefab
    /// </summary>
    [SerializeField]
    private GameObject blockPrefab_;

    /// <summary>
    /// 開始時に一度だけ呼ばれる処理
    /// </summary>
    void Start()
    {
    }
}

SerializeFieldでEditorから設定できるようにします。

設定する項目はPrefabと、Prefabのコピーを追加するHerarchy上のGameObjectです。

これをGameAreaにAdd Componentします。

setbcgame

Editor上でPrefabと、ブロックを追加していく場所のBlocksを設定します。

スクリプト上ではこのPrefabをコピーしてBlocksに追加します。

PrefabのコピーにはInstantiateを、Blocksへの追加にはSetParentを利用します。

using UnityEngine;

public class Game : MonoBehaviour
{
    /// <summary>
    /// ブロックを追加するGameObject
    /// </summary>
    [SerializeField]
    private RectTransform blocks_;

    /// <summary>
    /// ブロックの元になるPrefab
    /// </summary>
    [SerializeField]
    private GameObject blockPrefab_;

    /// <summary>
    /// 開始時に一度だけ呼ばれる処理
    /// </summary>
    void Start()
    {
        // InstantiateでPrefabをコピーしたGameObjectを作る
        var block = Instantiate<GameObject>( blockPrefab_ );
        // 新規GameObjectの親を設定されたGameObjectにする
        block.transform.SetParent( blocks_, false );
    }
}

Prefabをコピーする処理です。

これで1つだけコピーされて、先ほどと同じ画面になります。

次にこれを1つだけから、任意の個数コピーするようにします。

必要な数だけスクリプトをコピペする事も出来ますが、手間ですし変更があった際に困るので繰り返し構文を使います。

using UnityEngine;

public class Game : MonoBehaviour
{
    /// <summary>
    /// ブロックを追加するGameObject
    /// </summary>
    [SerializeField]
    private RectTransform blocks_;

    /// <summary>
    /// ブロックの元になるPrefab
    /// </summary>
    [SerializeField]
    private GameObject blockPrefab_;

    /// <summary>
    /// 開始時に一度だけ呼ばれる処理
    /// </summary>
    void Start()
    {
        // 横5個なので左に2個分ずらして中央に合わせる
        int centering = 5 / 2;

        // 5x5個ブロックを用意する
        for ( int y = 0; y < 5; ++y ) {
            for ( int x = 0; x < 5; ++x ) {
                // InstantiateでPrefabをコピーしたGameObjectを作る
                var block = Instantiate<GameObject>( blockPrefab_ );
                // 新規GameObjectの親を設定されたGameObjectにする
                block.transform.SetParent( blocks_, false );

                // 座標を調整する
                var transform = block.GetComponent<RectTransform>();
                var rect = transform.rect;      // ブロックの大きさ

                // xは0が真ん中
                //  -1*widthでブロック1つ分左、1*widthでブロック1つ分右に
                // yは0が一番上、0だと壁にめり込むので60下に下げる
                //  そこから-width毎に下にずれる
                transform.anchoredPosition = new Vector2(
                        (x - centering) * rect.width,
                        -y * rect.height - 60 );
            }
        }
    }
}

先ほどと同じようにInstantiateしてSetParentしますが、今回はfor文の中で行っています。

for ( 初期化; 継続条件; カウンタの更新 ) { 繰り返し処理 }

継続条件を満たす限り繰り返し処理が何度も行われます。

カウンタの更新の処理は繰り返し処理が終わる毎に1度呼ばれます。

流れとしては「初期化」→「継続条件判定」→「繰り返し処理」→「カウンタ更新」→「継続条件判定」→ 「繰り返し処理」→「カウンタ更新」→「継続条件判定」・・・と続きます。

今回はそのforが二重になっています。

その為

y = 0 → y < 5の判定 → {x = 0 → x < 5の判定 → prefabのコピペ処理 → ++x → x < 5の判定 ・・・} → ++y → y < 5の判定 → { x = 0 → ・・・} → ++y・・・

という風に内側のforが外側のforの繰り返しの数だけ処理されます。( {}の中が内側のfor)

結果prefabのコピー処理は内側のforの回数(5) * 外側のforの回数(5) = 25回行われます。

数を増減させたいときはこのforを繰り返す回数を変更するだけでよくなりました。

ブロックの位置に関しては現在の繰り返しの回数(xとy)から計算しています。

blockcimage

ブロックが増えました。ちゃんと跳ね返って当たればブロックが消えました。

下に落ちたらゲームオーバーにする

現状は下に落ちたらそのまま延々と下に落ち続けるので、ゲームオーバーとして再度初めから始まるようにします。

落下判定用オブジェクトを作る

Wallsを右クリックし、Create Emptyを選択します。このオブジェクトを落下判定用のオブジェクトとして設定します。

setbottom

ぎりぎりにするとすり抜けが怖いので、少し余裕をもってこのように設定しました。

次はこれに当たり判定の為にBox Collider 2Dをつけます。

setbottomphysics

大きさをRectTransformと同じにし、Is Triggerにチェックを付けます。

Is Triggerは当たってるかの判定はするが、跳ね返ったりの処理はしたくないものに付けます。

地面に当たって跳ね返るのは困るのでチェックをつけます。(といっても即初めからに戻すなら関係ないですが・・・)

落下判定オブジェクトに当たったときの処理を記述

ScriptsにField.csを用意します。

using UnityEngine;
using UnityEngine.SceneManagement;

public class Field : MonoBehaviour
{
    /// <summary>
    /// ボールがこの領域から出た時の処理
    /// </summary>
    public void OnTriggerExit2D()
    {
        // 今のシーンを終わらせてGameシーンを読み込む
        SceneManager.LoadScene( "Game" );
    }
}

OnCollisionExit2Dと同様にOnTriggerExit2Dを実装します。

CollisionとTriggerの違いは文字通り、ColliderのIs Triggerにチェックをつけているかどうかです。

これでボールが落ちるとパッと最初に戻ります。

ひとまず、最低限度のブロック崩しにはなりました。

課題

出来たと言っても最低限です。いろいろゲームとしては微妙です。

以下にパッと思いついた課題を列挙しますので、後学の為にも一度自力で実装してみてください。

【チュートリアル2】uGUIとPhysics2Dでブロック崩しを作ってみる 続き
この記事の続きです。 最後に課題として投げていたものを順次実装してみます。 ボールが毎回同じ挙動 毎度同じようにはねて面白味もなにもないので、バーの当たる場所で跳ね返る角度を若干変えるようにします。 ボールに角度を変える...

上記記事に課題の実装を書きました。

ボールが毎回同じ挙動…

毎回同じように跳ね返って同じようにブロックが削れていきます。

巷のブロック崩しによくあるバーの当てる場所によって跳ね返る角度が変わるような処理を入れると解決できそうです。

バーがはみ出す…

横長な画面だとバーがフィールドからはみ出ます。なので、枠が出ないような処理を入れてみてください。

ブロックに耐久力

難易度が上がると数回当てないと壊れないブロックが出てきたりするのは良くあります。

失敗できる回数を設ける

いわゆる残機です。何度か失敗したらその時にゲームオーバーなるようにしてみてください。

スコアの導入

ブロックを壊した個数でスコアを得るシステムもこういうゲームでは必須です。

アイテムの導入

ボールの速度が上がる、残機が増える等のアイテムの実装を挑戦してみてください。

シーン遷移の導入

タイトル → ゲーム画面 → ゲームオーバー画面 と言ったシーンの切り替わりを入れると結構それっぽくなります。

canvasのlocalScaleを掛ける理由

ボールの初速を与える処理の時canvasのlocalScaleを掛けました。もちろんなんとなく掛けているわけではなく理由があります。

画面の大きさでボールの速度が変わる

これを作っている時に気になったのが表題のこれです。画面が小さければボールが速く、大きければ遅くなります。※localScaleの掛け算を消してみるとわかります。

というのもPhysicsの計算がワールド座標系(uGUIだと画面そのもの)で行われており、Cavas内のローカル座標系ではされていないようです。

その為Canvasの設定がどうであれ、速度640で画面幅が640なら画面端から端まで横切るのに1秒。画面幅が320なら横切るのに0.5秒ということになってしまいます。

これに対応するために画面が小さければ遅く、画面が大きければ速くしなければいけません。

解決法

そして、これと同様な処理をしているものがあります。それがCanvasです。

Canvasでは画面が小さければGameObjectを縮小して、大きければ拡大して、どのような端末でも同じように見えるように設定されています。

つまり、このCanvasの拡縮の値(localScale)をそのままPhysicsの速度に掛けてやれば画面の大きさの違いを勝手に吸収してくれます。

CanvasがScreen Space Cameraの時のクリック位置の計算

バーの位置を計算する際にこれはOverlay用で、Cameraの時は違うと書きました。

Overlayの場合は画面領域とGameObjectの位置・大きさが必ず一致するのでpositionをそのまま代入するだけで事足りました。

しかし、Cameraの場合はそうとは限らないため、画面上の座標からTransformの座標空間へ変換してやる必要が出てきます。

using UnityEngine;
using UnityEngine.EventSystems;

public class Bar
    : MonoBehaviour
{
    public void OnTouch( BaseEventData arg )
    {
        PointerEventData e = arg as PointerEventData;
        // バーの位置をタッチされた位置へ
        Camera camera = GameObject.Find( "Main Camera" ).GetComponent<Camera>();

        // 押された画面上の座標がタッチ範囲のGameObjectのローカル座標ではどこなのかを計算する
        var transform = GetComponent<RectTransform>();
        var area = e.pointerCurrentRaycast.gameObject.GetComponent<RectTransform>();
        var anchored = transform.anchoredPosition;
        if ( RectTransformUtility.ScreenPointToLocalPointInRectangle( area, e.pointerCurrentRaycast.screenPosition, camera, out anchored ) ) {
            anchored.y = transform.anchoredPosition.y;
            transform.anchoredPosition = anchored;
        }
    }
}

これはCanvasに設定されたカメラがMain Cameraという名前前提のものです。

Findは処理が遅い上に打ち間違い等で不具合の元になるのでメンバ変数に持つ等工夫したほうがいいです。

pointerCurrentRaycastにはタッチされているオブジェクトが含まれています。タッチされている画面上の座標をこのオブジェクトの座標空間へ変換します。

変換にはRectTransformUtility.ScreenPointToLocalPointInRectangleを使います。

もう名前がそのものな感じがしますが、画面上の座標を指定のRectTransformの座標系へと変換してくれる便利関数です。

RectTransformUtility.ScreenPointToLocalPointInRectangle( area, e.pointerCurrentRaycast.screenPosition, camera, out anchored )

e.pointerCurrentRaycast.screenPositionが画面上の座標です。あとはRectTransformとCameraを指定してやれば最後の引数に計算結果が代入されます。

数値が不正等で計算出来なかったり、指定座標がRectTransformの外にある場合は返り値がfalseに。

コメント

  1. […] 【チュートリアル】uGUIとPhysics2Dでブロック崩しを作ってみる […]

  2. […] 【チュートリアル】uGUIとPhysics2Dでブロック崩しを作ってみる […]

タイトルとURLをコピーしました