ぐーるらいふ

しがないリーマンの記録。遊びのunityのメモ帳にしたい。

【Unity】第11回unity1week「つながる」オブジェクトプールについて

またunity1weekが開催されましたね。
これで11回目になります。すべて参加してまして、
「じょじょにUnityに慣れてきてるんだなぁ、ちょっとずつだけど出来る事が広がっているな」
と自分なりに感じる結果となっています。

f:id:ghoul_life:20190319214902p:plain:w150
(今回作ったアイコン。文字はあれですが、ユニティちゃんはちょっと力作です。)

サイドバーに今までunity1weekで作ったアプリを表示してますので、
良かったら…やっぱ見ないでいいです。

初期のものはコードも酷いし、unityわかってないし、絵も下手くそですごく恥ずかしいですが
恥を忍んで、公開したままにしてます。成長の証って事で…。

お題について

お題もそうなんですが、実は毎回自分に対して
「今回は○○について勉強すること」
という題目を決めて参加してます。

今回は「オブジェクトプーリング」「負荷対策」について
主に取り上げてみようかなと漠然と考えてました。

事件がおこる

なんだこれ。けもみみおーこくすげーな。って事件です。
この辺りではまだ勉強段階で、
「こうやったらこんなこと出来るのかなぁ」
という推測を元にスクラップ&ビルドを繰り返してました。

で、これをゲームにしようと思いました。

つながる要素

前回の最大の反省点は「難しすぎた事」でした。
僕は格闘ゲームとかもプレイしてるのですが、
基本的に「連打をしない」ということが染み付いていまして、
どんなゲームでもリズムよく(タイミングよくポンポンポンと)
押すのが普通になってしまっていました。

ぱふもどきさんの放送を見てたのですが、
凄い連打してたり、位置取りとかあんまり気にしてなかったりと
「ああ、これが普通の人だ。自分が間違っていた…」
とちょっとショックを受けました。

その反省を活かそうと、今回はなるべく複雑な操作、
システムは止めようと漠然と考えていて
コンボが繋がるとスコアがどんどん伸びていく感じがわかりやすいだろう
とシンプルな要素に落ち着かせました。

破壊について

ボロノイ図」とかでググってみてね。
voronoiのアルゴリズムを利用して作ってます。
自分なりにやっちゃってるので、なんちゃってボロノイかもしれませんので割愛。

最初のランダム点配置は Random.insideUnitSphere でやっちゃってます。
これオンラインで同期する場合はRandom.seedの共有も必要になるんだろうな…。

f:id:ghoul_life:20190320230648p:plain

オブジェクトプールと破片について

一ブロックにつき、10~20の破片にランダムで分解されるような仕様にしています。
この各破片は一つ一つがGameObjectになってます。
Rigidbodyで物理演算の影響も受けないとならないため、別々なオブジェクトにしないとなりませんでした。
そして以前から告知している通り、随時動的に計算して作っています。
単純に毎回Instantiateするとプチフリーズの嵐になってしまうので以下のようにプールを作りました。

PartsPool.cs

public void InitializePool()
{
    if (IsPopulated && parts.Length == maxPoolSize) return;

    // Clear old shard pool if one exists.
    if (parts != null)
    {
        foreach (var part in parts)
	{
	    if (part == null) continue;
	    DestroyImmediate(part.gameObject);
	}
    }

    parts = new Part[maxPoolSize];

    for (int i = 0; i < maxPoolSize; i++)
    {
	parts[i] = new GameObject("Part").AddComponent<Part>();
	parts[i].transform.parent = transform;
    }
}

このプールは破片プールです。
各破片はここにあるGameObjectを使いまわします。
800個から1000個ほど生成しておいていました。

Part.cs

void Initialize()
{
    if (GetComponent<Renderer>() == null) gameObject.AddComponent<MeshRenderer>();
    if (GetComponent<Rigidbody>() == null) gameObject.AddComponent<Rigidbody>();

    meshFilter = GetComponent<MeshFilter>() == null ? gameObject.AddComponent<MeshFilter>() : GetComponent<MeshFilter>();
    meshCollider = GetComponent<MeshCollider>() == null ? gameObject.AddComponent<MeshCollider>() : GetComponent<MeshCollider>();
            
    meshCollider.convex = true;
    meshFilter.sharedMesh = new Mesh();
    IsUse = false;
    gameObject.SetActive(false);
}

破片はゲーム内でInstatiateせず、Inspector上で800個用意して、Activeを切っておきます。
また各種必要なコンポーネントを変数に持っておきアクセスを容易にしときます。

Part.cs

public void Use(GameObject parent, Vector3[] newVertices, Vector3[] newNormals, int[] newTriangles, Vector2[] newUVs)
{
    part.name = "Fractured Parts";
    var mesh = new Mesh();
    part.Mesh = mesh;
    part.Mesh.vertices = newVertices;
    part.Mesh.normals = newNormals;
    part.Mesh.uv = newUVs;
    part.Mesh.triangles = newTriangles;
    part.meshCollider.sharedMesh = part.Mesh;

    part.gameObject.SetActive(true);
    part.IsUse = true;

    part.GetComponent<Renderer>().material = parent.GetComponent<Renderer>().material;

    if (parent.GetComponent<Rigidbody>())
    {
        part.GetComponent<Rigidbody>().mass = parent.GetComponent<Rigidbody>().mass;
        part.GetComponent<Rigidbody>().velocity = parent.GetComponent<Rigidbody>().velocity;
    }
}


使う時にActive化して、位置、大きさ、Meshをセットしてあげます。
また、この破片はparentとなる元オブジェクトが管理しており、
設定した時間後に削除されます。この削除は非アクティブ状態にして、Poolに戻るだけにしておきます。

PartsPool.cs

public static void BackPool(GameObject obj , float waitTime)
{
        var part = obj.GetComponent<Part>();
        if (part != null)
        {
            pool.StartCoroutine(pool._BackPool(part , waitTime));
        }
}

private System.Collections.IEnumerator _BackPool(Part part, float waitTime)
{
        yield return new WaitForSeconds(waitTime);

        part.IsUse = false;
        part.gameObject.SetActive(false);
        part.transform.parent = transform;
}

(一定時間後に削除する、というのをCoroutineで簡易的に実装してます。)

こうすることによって、poolingを使い回すことが可能です。
これでゲーム中はInstatiateとDestroyを使わずに進めることが可能です。
(IsUseがfalseのものをプールから取ってくればOKですね)

Effect , PopupScoreなども同様にInGame中にはInstantiateせず、Active切り替えで使いまわしてます。

CameraPlay

assetstore.unity.com

これ知らなかったんですが、こんな凄いAssetがあるとは…。
有料なんですが、カメラ演出は効果が大きいので、コストに見合う効果が出ます。

画面を揺らす、集中線を出すといったことが1行でかけます。
便利すぎる。最初はやりまくってたんですが、気持ち悪くならない程度にちょっと抑えました。

AudioManager

同時に複数のオブジェクトを破壊するので、音も大量に一度に鳴ります。
AudioSourceは最終的に10個用意していて、その中で使ってないAudioSourceを探して使うようにしてます。
15でも良かったかも。

NCMB

バカやって変な不具合出してた。別記事にまとめたので、そちらをどうぞ
自分のせいです。これもうちょっと作る必要あるなぁ…。通信中表示とかまだ甘い。

ghoul-life.hatenablog.com

その他色々

最後はほんとに不具合直すのと、違和感を削る作業(調整とも言う)に忙殺されますね。

ゲームプレイ > ここ変だな、これ欲しいな > 直す、作る

これを繰り返しまくる。土日はほとんどこれ。
ここでポップアップやらスコア動かしたり操作説明付けたりなど。
この辺りが今まで甘かったのだけど、少しずつ計算に入れて作業を進めることが
出来るようになってきたなと思う。

お絵かき

前回はバリバリ描きまくりで疲れましたが、
今回はほとんど要らなかったのでアイコンしか描けず。
文字は酷いので外したverを。

f:id:ghoul_life:20190320234606p:plain:w150

割とそれっぽく描けたかな…と思います。リリースギリギリに滑り込み。
元ネタは「何でも言うことを聞いてくれるアカネちゃん」です。

反省点

  • ステージとかもう一つぐらい作れば良かった
  • ボムアイテム作りたかった(どうしてもエフェクトが間に合わず…Blenderに慣れないと…)
  • 敵とか居たらもっと良かったかも。むしろ街中で怪獣とユニティちゃんが戦うゲームにするのはどうだろうか
  • 甘城ブリリアントパークが面白すぎた
  • SEKIROが楽しみすぎてそれどころじゃなかった
  • WebGLの日本語入力これか。時間があったらやってみよう

tsubakit1.hateblo.jp

次回

もちろん参加します。
今回はお絵描きがほとんど出来なかったので、
次はノベル要素強めとかやりたいなと思ってます。
xNode使ってエディタ拡張の勉強かなー。