原カバンは鞄のお店ではありません。

Unityを使ったゲーム制作のあれこれを綴っていきます。

次回作のお知らせを・・・

雑感だけを書こうと思った

コンサートを観にいって、前後の列は女性だらけなのに何故か自分席の周りだけ奇人変人大集合みたいな人間に取り囲まれるとか、そんな経験をした昨今、いや、「女子率高いよ」との言葉に騙されて鼻の下を伸ばしながらアイドルコンサートに出かけた私が悪いんです。特攻服とか久しぶりに見た。

 

で、CutieCircuitのiOS版が容量の問題で頓挫しているので、最近ちょっと話題になった下のリンク先の話について雑感を書いてみたら、結構クサくなってバッサリ削りました。どうせ誰も読まないし。

 

www.gamecast-blog.com

次回作を作っているよ

なので、現在制作中のゲーム画面を載せることにしました。

 

f:id:Karvan:20180801005528g:plain

 

みんな大好きエンドレスランゲームです。既にジャンルとして確立しているので制作側も作りやすいですしね。


ありきたりな感じもしますが、キャラをバイクにして、後はエフェクトいっぱいつけて誤魔化すかなぁ・・・

 

 

C# JobSystemを使ってみよう2

賢人の言葉

宮沢賢治氏の代表作「雨ニモマケズ」では「雨ニモマケズ風ニモマケズ・・」の次に「雪ニモ夏ノ暑サニモマケヌ・・・」という言葉がありますが、ここ最近はさすがの賢治氏もその節を曲げそうな暑さが続いていおり、私の場合は負けっぱなしでエアコン全開の日々が続いています。もう部屋から出られんし。

 

夏休みシーズンに入ったこともあり私の周りでも、フェスだ!、花火だ!、お祭りだ!、みたいなワードが飛び交っていますが、私としてはこの酷暑の中、一歩も外に出る気はありません。そもそも誘われない、という話は悲しくなるので置いといて、この夏は部屋から出ることなくゲーム制作に専念したいと思っています、うん、だから泣いてません。

 

C# JobSystemの続き

前回の記事でUnityのJobSystemについて取り上げましたが、今回はその続きです。他にネタがなかったとか、そういうわけでは決してありません、うん、たぶん、きっとそう。

 

前回では複数のモデルに公転と自転の運動をさせていましたが、今回は公転運動に収縮の動きを加えてみようと思います。つまり公転半径が周期的に変わるってやつ。

f:id:Karvan:20180724225803p:plain

 

公転運動に収縮運動を加える

公転運動のロジックに収縮分の計算を加えれば出来そうな気もしますが、今回は公転運動を計算した後、収縮運動を計算してオブジェクトのTransformに反映しようと思います。これにより前回作成した公転運動のロジック部分には一切手を入れなくて済みます。

 

f:id:Karvan:20180724225856p:plain

 

収縮運動の移動

収縮運動の移動量にはAnimationCurveを使用しました。Main側でカーブの位置を取得して、変数「SpreadPower」に渡して収縮運動計算で使用しています。

Main側

    // Update is called once per frame
    void Update () {
        // 公転用JOB領域設定
        RevolutionMotionUpdate revolutionJob = new RevolutionMotionUpdate()
        {
            Accessor = this._revolutionStructs,
            DeltaTime = Time.deltaTime,
        };

        // 自転用JOB領域設定
        RotationMotionUpdate rotationJob = new RotationMotionUpdate()
        {
            Accessor = this._rotationStructs,
            DeltaTime = Time.deltaTime,
        };

        //カーブ位置取得
        if (_curveRate == 1f)
        {
            _curveRate = 0;
        }
        else
        {
            _curveRate = Mathf.Clamp(_curveRate + _spreadPower, 0f, 1f);
        }

        // 収縮運動用JOB領域設定
        ContractionMotionUpdate contractionJob = new ContractionMotionUpdate()
        {
            Accessor = this._spreadStructs,
            SpreadPower = contPower * _jumpCurve.Evaluate(_curveRate)
        };

        this._jobRevolutionHandle.Complete();
        this._jobRotatioHandle.Complete();
        this._jobSpreadHandle.Complete();

        this._jobRotatioHandle = rotationJob.Schedule(this._planetTransformAccessArray);
        this._jobRevolutionHandle = revolutionJob.Schedule(this._planetTransformAccessArray);
        this._jobSpreadHandle = contractionJob.Schedule(this._planetTransformAccessArray, _jobRevolutionHandle);
        JobHandle.ScheduleBatchedJobs();
    }

 

収縮運動側

    /// <summary>
    /// 収縮運動のJOB
    /// </summary>
    struct ContractionMotionUpdate : IJobParallelForTransform
    {
        public NativeArray<RotCalStruct> Accessor;
        public float SpreadPower;

        // JobSystem側で実行する処理
        public void Execute(int index, TransformAccess transform)
        {
            RotCalStruct accessor = this.Accessor[index];
            transform.localPosition = Spread(accessor, transform);
            this.Accessor[index] = accessor;
        }

        // 収縮運動
        Vector3 Spread(RotCalStruct data, TransformAccess transform)
        {
            Vector3 nowPos = transform.localPosition;

            Vector3 radiusVec = new Vector3(nowPos.x - data.RevOri_X,
                                            nowPos.y - data.RevOri_Y,
                                            nowPos.z - data.RevOri_Z);

            Vector3 modVec = (SpreadPower + 1.0f) * data.RevRadius * radiusVec.normalized;

            Vector3 retPos = new Vector3(data.RevOri_X + modVec.x,
                                         data.RevOri_Y + modVec.y,
                                         data.RevOri_Z + modVec.z);

            return retPos;
        }
    }

 

公転運動後に収縮運動を行う

並列処理を行うためのJobSystemですが、今回の場合は公転運動⇒収縮運動の順に処理を行わなくてはいけません。これを実現させるためにJobSystemでは各Jobの依存関係を設定することができます。

 

上のソースのMain側で

       this._jobRevolutionHandle = revolutionJob.Schedule(this._planetTransformAccessArray);
        this._jobSpreadHandle = contractionJob.Schedule(this._planetTransformAccessArray, _jobRevolutionHandle);
        JobHandle.ScheduleBatchedJobs();

と記載している箇所があります。

収縮運動側のScheduleの第二引数に公転運動側のジョブハンドルを指定することで、公転運動の処理が完了したら収縮運動の処理が即時に実行させるようになります。

公転運動にも収縮運動にも同じtransformArrayを渡しているので、収縮運動の処理には公転運動分の移動が終ったtransformに収縮運動分の移動を反映させることができます。

 

実際の動作

これらの処理を使って実際に動作させた結果が以下の動画です。

どーん

f:id:Karvan:20180724231410g:plain

 

スクエア上に並んだ球体が自転+公転しながら収縮運動もしていることが分かります。

 

JobSystemは難しくない

前回も述べましたがJobSystemの実装自体は難しくありません。ただ、JobSystemでどのように実装するか、という設計の見極めは十分な考慮が必要かもしれません。今回の場合だと、自転と公転はそれぞれ並列処理でよいけど、収縮は公転⇒収縮の順じゃないと駄目、みたいな。まぁ、色々試してみるのが一番ですね。

 

 

Unity2018のC# JobSystemを使ってみよう

三連休

海の日だからって皆が海に行くとは思うなよ!ということで三連休は部屋に閉じこもる平常運転な休日だったのですが、連休中にやろうと思っていたCutieCircuitのiOS版のリリースはTextureを小さくしてもipaサイズの問題解消とはいかず、いよいよAssetBundleに手をだすしかないことが確定したので心折れて、現実逃避の意味も含めてちょっと違うことにチャレンジしました。

 

C# JobSystem

Unity 2018.1の目玉機能のひとつに「C# JobSystem」(以下 JobSystem)というものがあります。

 

JobSystemとは、ざっくり言うと「Unityで並列処理を可能とする機能」です。

 

もともとC#自体にはThread処理機能がありますが、JobSystemではUnity側が内部で使用しているWorkerThreadをC#の(ユーザ側の)スクリプト処理にも開放することで並列処理を可能としている機能なので、Unity側が使いやすいようにAPIを用意してくれていて、C#のThread処理を使うよりはロジックの実装がかなり楽ちん、らしいです。

 

ここら辺の話は以下のリンク先で詳しい解説が記載されています。

tsubakit1.hateblo.jp

せっかくだから使ってみよう

ロジックの実装がかなり楽、とはいえ、幾つか決まりがあります。

JobSystemを実装する場合は、この決まりに沿って処理を構築する必要があります。

 

  1. 並列処理で処理する関数(構造体)で使用する領域(メモリ)を事前に確保する
  2. 入力用の領域に並列処理用関数(構造体)へ渡すデータを設定する
  3. Sceduleを実行して、JobSystemにジョブを発行依頼
  4. Completeで処理が終了まで待つ
  5. 処理結果を出力用の領域から取得する
  6. 1.で確保した領域を解放する

 

手順が多くて難しそうな印象を受けるかもしれませんが、一つ一つの作業はそれほど難しくありません。むしろ一回覚えれば簡単に作れてしまいます。

 

ただ、メモリの確保にはNativeContainerと呼ばれるコンテナを使う必要があることや、処理が終ったら必ずメモリを解放しなければいけないこと、メモリの確保の仕方が何種類かあること、などは慣れないと戸惑うかもしれませんが、最初のうちは「おまじないみたいなもの」として捉えて、とりあえず見よう見まねで実装しておけばよいです。

 

詳しいことは全て先ほどのリンク先に書かれているのでそちらを参考してもらうとして、このJobSystemを使って実際私が作ったものがこちら

 

ドーン 

f:id:Karvan:20180717232612g:plain

3つの球体がそれぞれ違う速度で自転と公転をしています。
JobSystemを使って自転と公転それぞれの回転をフレーム毎に並列で計算しています。

 

公転の計算と移動

各球体の回転と移動はIJobParallelForTransformを継承したジョブ実行用の構造体にて行います。

 

ジョブを実行すると各球体毎にIJobParallelForTransformのExecute関数が呼ばれます。
このExecute関数にはTransformAccessという引数が渡ってくるので、このTransformAccessを経由して球体を移動させます。


なので並列処理側の動作としては

  1. 1フレーム分の角度回転した移動量を計算する
  2. TransformAccessのpositionから現在位置を取得し、移動量分を加算する
  3. 加算した位置をTransformAccessのpositionに設定する

という流れになります。

ちなみに、IJobParallelForTransformではTransform.RotateAroundと言ったAPIを使うことができないようなので、行列計算を使い自力で公転の移動量を計算しています。

 

これは以下のリンクのロジックを参考にしました。

 

qiita.com

実際のソースはこんな感じ

/// <summary>
/// 公転運動
/// </summary>
struct RevolutionMotionUpdate : IJobParallelForTransform
{
    public NativeArray<RotCalStruct> Accessor;
	
    // JobSystem側で実行する処理
    void IJobParallelForTransform.Execute(int index, TransformAccess transform)
    {
        RotCalStruct accessor = this.Accessor[index];
		
        // 計算結果をTransformに反映
        ApplyPosition(accessor.Matrix, transform, accessor);
		
        // 結果保持
this.Accessor[index] = retAccessor; } /// <summary> /// 公転位置の設定 /// </summary> /// <param name="matrix"></param> /// <param name="transform"></param> /// <param name="data"></param> void ApplyPosition (Matrix4x4 matrix, TransformAccess transform, RotCalStruct data) { Vector3 RevOriPos = new Vector3(data.RevOri_X,data.RevOri_Y,data.RevOri_Z); Vector3 TargetPos = transform.position; // 原点位置からの差分ベクトル Vector3 TempPos = TargetPos - new Vector3(data.RevOri_X, data.RevOri_Y, data.RevOri_Z); // 行列計算による移動量算出 Vector3 CalResPos = matrix.MultiplyPoint3x4(TempPos); // 元の位置に移動量を加算して設定 transform.position = RevOriPos + CalResPos; } }

 

ここで、最初のほうに記述されている

     public NativeArray<RotCalStruct> Accessor;
この部分が入力・出力用の領域(メモリ)となります。


RotCalStructは自前の回転計算用データで、メイン側で1フレーム毎の回転角度や公転原点を設定し、並列処理側でそれを参照する形で使用しています。

 

自転の計算と移動

こちらも公転と同様にIJobParallelForTransformを継承したジョブ実行用の構造体にて行います。

/// <summary>
/// 自転運動
/// </summary>
struct RotationMotionUpdate : IJobParallelForTransform
{
    public NativeArray<RotCalStruct> Accessor;

    // JobSystem側で実行する処理
    public void Execute(int index, TransformAccess transform)
    {
        RotCalStruct accessor = this.Accessor[index];
        
        // 角度を加算更新
        accessor.CurrentRot += accessor.CurrentAngle;
        
        // オブジェクトの向きを更新
        transform.rotation = Quaternion.AngleAxis(accessor.CurrentRot, Vector3.up);
        
        // 更新したデータをメイン側へ返却する
        this.Accessor[index] = accessor;
    }
}

こちらの場合は、Executeが呼ばれる度にCurrentRotを加算更新しメイン側へ返却、メイン側はそれを保持して次回のコール時に使用して球体を回転させています。

 

メイン側の処理

メイン側では前述した回転角度や公転原点を設定のほかに、TransformAccessArrayの配列を定義しています。


そこに動作対象となる3つの球体のtransformを設定することで3つの球体それぞれの並列処理(IJobParallelForTransform)が呼ばれる仕組みとなっています。

/// <summary>
/// 回転計算用データ
/// </summary>
struct RotCalStruct
{
    // 1フレーム毎の回転角度
    public float CurrentAngle;
    // 現在の回転角度
    public float CurrentRot;
    // 算出した回転情報を保持
    public Matrix4x4 Matrix;
    // 公転原点
    public float RevOri_X;
    public float RevOri_Y;
    public float RevOri_Z;

    public RotCalStruct(float currentAngle)
    {
        this.CurrentAngle = currentAngle;
        this.CurrentRot = 0f;
        this.Matrix = new Matrix4x4();
        this.RevOri_X = 0;
        this.RevOri_Y = 0;
        this.RevOri_Z = 0;
    }
}


// Jobの終了待ち等を行うHandle
JobHandle _jobRevolutionHandle;	// 公転運動
JobHandle _jobRotatioHandle;	// 自転運動

// Job用の回転計算用データ
NativeArray<RotCalStruct> _revolutionStructs;
NativeArray<RotCalStruct> _rotationStructs;

// JobSystem側で実行する際に用いるTransfromの配列
TransformAccessArray _planetTransformAccessArray;

void Start()
{
    // 回転計算用データのメモリ確保
    this._revolutionStructs = new NativeArray<RotCalStruct>(planetArray.Length, 
                                                            Allocator.Persistent);

    this._rotationStructs = new NativeArray<RotCalStruct>(planetArray.Length, 
                                                          Allocator.Persistent);

    for (int i = 0; i < planetArray.Length; i++)
    {
        //-------------------//
        // 公転用情報設定      //
        //-------------------//
        // 公転用情報生成
        RotCalStruct acsess = new RotCalStruct(jobSetting[i].revolAngle);

        // 公転Matrixの生成
        acsess.Matrix = Rotate(acsess);

        // 公転原点
        acsess.RevOri_X = originPlanet.transform.position.x;
        acsess.RevOri_Y = originPlanet.transform.position.y;
        acsess.RevOri_Z = originPlanet.transform.position.z;

        // 公転用Acesserに設定
        this._revolutionStructs[i] = acsess;

        //-------------------//
        // 自転用情報設定      //
        //-------------------//
        // 自転用情報生成
        RotCalStruct acsess2 = new RotCalStruct(jobSetting[i].rotatAngle);

        // 自転軸設定
        acsess2.RodAxis = (int)jobSetting[i].RotAxis;

        // 軸の傾き設定
        acsess2.AxisAngle = jobSetting[i].RotAxisAngle;

        this._rotationStructs[i] = acsess2;
    }

    // IJobParallelForTransform用データ
    this._planetTransformAccessArray = new TransformAccessArray(planetArray.Length);
    this._planetTrses = new Transform[planetArray.Length];
    for (int i = 0; i < planetArray.Length; ++i)
    {
        var trs = planetArray[i].transform;
        this._planetTransformAccessArray.Add(trs);
        this._planetTrses[i] = trs;
    }
}

/// <summary>
/// 回転マトリックスの取得
/// </summary>
/// <param name="deltaTime"></param>
/// <param name="data"></param>
/// <returns></returns>
static Matrix4x4 Rotate(RotCalStruct data)
{
    // マトリックスの初期化
    Matrix4x4 m = Matrix4x4.identity;
    float x = 0f, y = 0f, z = 0f;
    m.SetTRS(new Vector3(x, y, z), Quaternion.identity, Vector3.one);

    // 原点設定
    float oriX = data.RevOri_X;
    float oriY = data.RevOri_Y;
    float oriZ = data.RevOri_Z;

    // 回転角度
    float rot = data.CurrentAngle * Mathf.Deg2Rad;
    float sin = Mathf.Sin(rot);
    float cos = Mathf.Cos(rot);

    // 任意の原点周りにY軸回転を行う
    m.m00 = cos;
    m.m01 = -1 * (oriX * cos) + oriX - oriZ * sin;
    m.m02 = sin;
    m.m20 = -1 * sin;
    m.m21 = oriX * sin + oriZ - oriZ * cos;
    m.m22 = cos;

    return m;
}

void Update()
{
    RevolutionMotionUpdate revolutionJob = new RevolutionMotionUpdate()
    {
        Accessor = this._revolutionStructs,
    };
    
    RotationMotionUpdate rotationJob = new RotationMotionUpdate()
    {
        Accessor = this._rotationStructs,
    };
    
    this._jobRevolutionHandle.Complete();
    this._jobRotatioHandle.Complete();

    this._jobRevolutionHandle = revolutionJob.Schedule(this._planetTransformAccessArray);
    this._jobRotatioHandle = rotationJob.Schedule(this._planetTransformAccessArray);
    JobHandle.ScheduleBatchedJobs();
}

void OnDestroy()
{
    // 解放/破棄など
    this._jobRevolutionHandle.Complete();
    this._jobRotatioHandle.Complete();
    this._revolutionStructs.Dispose();
    this._rotationStructs.Dispose();
    this._planetTransformAccessArray.Dispose();
}

 

Update()内にて公転のJob、自転のJobをそれぞれ発行し、Completeで待ち合わせています。
各JobはScheduleで発行しますが、実際の動作開始はScheduleBatchedJobsになります。

 

ちなみにUpdateの先頭でCompleteを行っているのは、JOB発行⇒完了待ち(同じフレーム)、よりも JOB発行⇒次フレーム⇒完了待ち、のほうがより高速化するからです。(・・・と資料に書いてあった)

 

実際に作ってみて

JobSystemの実装自体は難しいものではありませんでした、どちらかといえばY軸周りの公転用マトリックスを作るのに時間が掛かったぐらいです。(久しぶりに自力で行列計算した)

 

JobSystemを使えば弾幕系シューティングのように大量のオブジェクトを動かすときなどに効果を発揮すると思います。


ただ、JobSystemではUnityAPが殆ど使えないので、複雑な動きをさせたい場合などは今回のように自力でロジックを組む必要がありそうで、使いどころの見極めが重要になると感じました。

 

最後に動かす球体を70個ぐらいに増やしたバージョンも作ったので見てやってください。

 

ド、ドーン

 

 

f:id:Karvan:20180718000756g:plain

 

 

 

 

近況報告と言い訳

大雨と猛暑

先週末は大雨が降ったと思えば今週は一転、猛暑の日々が続いて目まぐるしい気候の変化に驚いてばかりですが、最近なにより驚いたことと言えば、大雨の降った日の朝、普段は糞みたいに厳しい上司から電話が鳴り、何事かと思えば「おい、今日は大雨で大変だから無理して出社しなくていいぞ」という連絡。おお、なんて優しい配慮、ありがとうございます。感謝します。とか、言うべきだったんですが、なんせ普段の態度からは想像もつかない突然の言葉に驚いて、その時の私は返事に窮しました。ええ、だって既に出社してたらね。

 

その日は上司の言葉の通り、JRもバスも止まって大変でした。帰れないかと思った。

 

 心折れる

大変なことと言えば、CutieCircutのiOS版のリリース作業。

毎度のごとく「iOS版はもうすぐリリースできそう」とかのたまっていて、前回は「審査が通れば」なんてことも言ってましたが、もういい加減心が折れそう。
なぜなら審査以前の問題が発覚したからです。

 

iOS版に取り掛かる前まではアプリの中身はとっくに出来上がっていて、Andoroid版ではAPKのサイズもギリギリ100MB以下に収まっていたので、XCodeビルドと実機での動作確認さえできればリリース可能だと簡単に考えていました。

 

XCodeビルドと実機での動作確認は色々ありながもなんとかクリアして、もうこれで作業完了したも同然の気分でいたり。

f:id:Karvan:20180710233824p:plain

<なんだかんだ余裕だったおwww、もう天才おwww

 

見たいな感じ、でもね、でもさぁ・・・

f:id:Karvan:20180626225740p:plain

<ipaのファイルサイズが300MBオーバーだお!!


審査のためにXCodeからiTunes Connectにアップロードしたら警告がでたので何事かと思えばipaのファイルサイズが制限越え。
前述のようにAndoroid版ではギリギリ100MB以下に収まっていたので150MB制限のAppStoreでは余裕かと思っていたのですがとんでもなかった。

 

調べてみるとiOSの場合、iPhoneとiPadに対応させたら対応するためのファイルがそれぞれ必要で、かつ32Bit版(armv7)と64Bit版(arm64)それぞれで必要なビルドを行うのでアプリの容量が膨らむらしい。

 

なので、リリース対象を絞ってiPhoneのみ+64Bit版のみとして再ビルドしてみても・・・

 

f:id:Karvan:20180710234123p:plain

<268MB・・・・

 

こうなると、素材の容量を削減するか、AssetBundleに手を出すしかない状況に。
AssetBundleは未着手の領域のなので、今はなるだけ手を出したくない。
なので、まずは素材(Texture)の容量(サイズ)自体を削減することにしました。

 

素材の容量を削減

「素材の容量を削減する」とは前回やったようなUnityのInspectorでTextureのMAXサイズを変更する、というのではなく、Texture自体のサイズを小さくする、という意味です。

 

つまり、Textureの画像サイズを一つ一つGimpみたいなツールを使って小さくする⇒Unityにインポートする⇒Materialに反映する、という作業を繰り返す、という意味で、つまりは・・・

終らない

 

無駄にいろんなアセットのいろんな素材を使いまくったせいで縮小対象のTextureが多すぎ=単純作業の繰り返し、となるわけで、画像を縮小して、Unityにインポートして、そのTextureを使っているMatrialを変更して、画像を縮小して、Unityにインポートして・・・・

 

f:id:Karvan:20180710234329p:plain


もう、すっとそんな気分ですよ。夏休みも始まってないのに。

 

とりあえず頑張る

心折れかけなんですが、とりあえず頑張ります。ちなみに上の対策でだめな場合は、AssetBundleに手を出す羽目になるんですが、そうなるといよいよ終らないと思います。。。

 

Unity製アプリのメモリリーク対応について

IT系勤務とはいうものの

IT系の職業に就いていたり休日は携帯用ゲームを作っていたりしますが、だからいってPC(=ハード)に興味があるわけではなくって、例えばTVショッピングとかで「お手ごろです!!」と宣伝されているノートPCを見ると素直に「これはお得かも」とか思っちゃうぐらいの情弱な人間なのです。

 

つまりは、床屋とかで職業を問われてIT系だと答えると十中八九「お勧めのPCはなんですか?」とか聞かれて困る、ということなんです。そんなことはヨドバシの店員にでも聞いてください。

 

まぁ、気が弱い人間なのでそんな質問でも拒絶できずに「う~ん、、、今はメーカものよりも自作の方がいいかもですね~」とか適当に答えるんですが、稀に「そうですよね~、私も自作で組んでて~」とか言われてマザーボードの型番とかを持ち出されたり、ごめんなさい、ごめんなさい、全然わかりません。
そんなこともあります。

 

意外とあっさりと

そんな感じで冴えない日々が続いているのですが、ゲーム制作の方は以外と進捗があって、前回の記事でCutieCircutのiOS版について『アプリが音もなく落ちる』と伝えていた問題は意外にあっさりと解決しました。

 

元々メモリ管理には厳しめのiOSで、そんなのお構いなしにプログラムを組んでいた私が悪いのですが、なにせ前述のようにPCハードとかアーキテクチャとかあまり興味がないので、メモリリーク?なにそれ美味しいの?状態。何をどうしたらよいか思案に明け暮れていたのですが、Google先生を駆使して色々調べたらあれよいう間に解決したので今回はそのお話

 

解決方法

前回の記事でも書いたように今回のメモリリークは初期処理でキャラクタの走るステージを作る際に、一旦プレハブを読み込んでInstantiateでオブジェクト化して、要が済んだらそのオブジェクトをDestroyする、といった処理を繰り返しているところで発生しているみたい。

 

「Destroyする」とは勝手に「メモリ上から破棄する」と考えていたのですが、調べてみると「生成されたオブジェクトのRendererが持っているMaterial(Clone)が、Rendererが破棄されたあとも残り続けている」ことがあるみたい。要はゴミが残っていることがあるってことです。

なのでそれらのゴミを一掃すべく以下の方法を使いました。

 

1.Destroy時にMaterialを解放する。

オブジェクトをDestroyするとそのGameObjectにOnDestroyというイベントが発生するので、そのイベント内でGameObjectが使用しているMaterialを明示的に破棄するようにしました。

 

 これは以下のサイトに詳しく書かれていて、自動解放用のスクリプトも掲載されています。

qiita.com

 

2.アセットのアンロードを実施する。

Resource.UnloadUnusedAssetsを使用すると「強制的に」不使用なアセットを解放してくるようです。併せてSystem.GC.Collectをコールすればガベージコレクションを実施してくれるのでメモリの最適化も行ってくれます。


ただ、この処理自体が結構重たいのでDestroyの度に行うとその度にゲームが止まってしまうので、ある程度のところで一気に行います。

 

3.使用するテクスチャの最大サイズを下げる。

メモリリークには関係ないのですが、テクスチャの最大サイズを下げることでメモリ使用量自体を少なくしました。


やり方はAndroid版と同様にテクスチャをクリックしてInspectorに表示される

「Override for iOS」

のチェックをONにして、MaxSizeを小さいサイズに下げます。

f:id:Karvan:20180703205307p:plain

 

それらの結果

前回大騒ぎした割にはあっさり解決してちょっと拍子抜けですが、無事にiOS上でも動作確認が取れて一安心です。

 

現在はAppStoreへ申請中なので申請が通れば無事にCutieCircuitがAppStoreに並ぶことになります。申請が通れば。

 

プロモーション動画

iOS版リリースを先取りしてプロモーション動画を作成しました。

Android版リリースの時に作っとけよ、って話ですが、色々課題が山積みだったのでそこまで時間が取れなかったんですよね。

 

今回はゲームの流れがわかるようなゲーム実況風の紹介動画となっています。

つたない編集ですが見てやってください。

 


Cutie Promotion【ちょっぴり高画質版】

 

次回は新作の話ができたらいいなぁ・・・

八方塞がりな報告

違う違う、そうじゃそうじゃない

最近、何故か会社で「グループ交流会」なる謎の飲み会が開催されるはめになり、私も半強制的に参加させられたのですが、そのちっとも楽しくない飲み会で、ちっとも可愛くない新入社員の女の子と互いに興味のない会話をしなくてはならなくなり、とはいえ、話題も続かないので互いに携帯に保存してある画像を見せあったりしたんです。「最近、こんな痛車をみつけて~」みたいな感じで。

 

そうしたら不意に、かなり前の社員旅行で撮った写真が現れて、それが何故か私とデブスの女性社員(とっくの昔に退職した)とのツーショットみたいになっている。
おそらく同僚がふざけて撮ったものが私の携帯に送られてきたのだと思うのですが、それを見た新入社員が一言、「・・・へぇ、綺麗な人ですね~・・・」

 

違う違う、そうじゃそうじゃない
ただ偶然にツーショットになっただけですから!この人がカバンさんの・・・とかうがった見方は止めてください!
そんなモビルスーツでいえばアッガイみたいな女性と私が関係があるとか心外です!プン!プン!
とか思ったのですが、新入社員はその一言以外何も言わなかったので反論することなく終了、ああ、もう謎の飲み会とか参加するべきじゃなかった。

 

進捗報告

そんな感じで不意な誤解と不利益を被ってばかりな日々が続いています。
お気に入りのズボンが破けて破棄するとか、携帯のパケットが制限一杯になったのでTwitterもできくなくなったとか、新入社員たちの間では私が守備範囲の広い人になっているとか、違う違うそうじゃない。

 

CutiteCircuitで言えば、つい先日までは楽観的な予想をツラツラと述べていましたが、今の所その予想に反することが多くて八方塞りな状況です。
つまり、「iOS版は近々にリリースできると思います」とか言ってましたが、あれはウソだ!ってことです。

 

一体なにが問題なのかというと、iOS版のリリースについて当初の計画では

1.UnityでAndroid版を完成させる。
2.完成したAndroid版のプロジェクトをiOS版のプラットフォームへ変更する。
3.iOS版へ変更したプロジェクトをMacBookでビルドする
4.MacBookでビルドしたものを実機で動作確認
5.AppleStoreへリリース

 

となるはずで、UnityでAndroid版を完成させてプラットフォームをiOS版へ変更するまでは順調だったんです。なのに・・・

 

問題続出

問題その1:iOS版へ変更したプロジェクトをMacBookでビルドする

 

f:id:Karvan:20180626225555p:plain


>Unityビルドは無事に完了、あとはXCodeでビルドだお

 

f:id:Karvan:20180626225455p:plain
>ld: library not found for -lPods-Unity-iPhone ってエラーになったお

 

f:id:Karvan:20180626225648p:plain

>Xcodeの General設定⇒Linked Frameworks and Libraries から
>「libPods-Unity-iPhone.a」 を 削除すればいいみたいだお!

 

f:id:Karvan:20180626225740p:plain

>余計にエラーが増えたお!!

 

■対処
・「libPods-Unity-iPhone.a」 を 削除する
・使用しているアセットを最新にする。

 

問題その2:MacBookでビルドしたものを実機で動作確認

 

f:id:Karvan:20180626225827p:plain

>Xcodeでビルドが通ったから実機確認だお

 

f:id:Karvan:20180626225859p:plain
>Android版で完成しているから問題ないはずだお

 

f:id:Karvan:20180626225740p:plain


>アプリが音もなく落ちたお!

 

 

そうだった、そうだった思い出した

そうです。iOSってメモリ管理が厳しくってメモリ不足とかメモリリークとかでよく落ちるんです。
CutieCircuitの場合、メイン画面でキャラクターの走るコースとか周囲のオブジェクトとかをPrefabから読み込んだり、
別のオブジェクトに差し替えてDestroyとかしているのですが、そのあたりでメモリ不足、メモリリークが発生しているっぽい。

 

つまり、その辺の面倒くさい処理を見直す必要がある=すぐには終らない、というわけで。。。

 

f:id:Karvan:20180626230255p:plain

自分の技術不足に歯軋りしながら対処方法を思案中、という訳です・・・

 

お知らせのブログ

カッタデー

日本勝ちましたね!!

まさか勝つとは思わなかったので、21時過ぎに帰宅したらリードしていてビックリ。一夜漬けみたいなチーム作りで三戦全敗を予想していました。御免なさい(土下座)。

 

デキタデー

この勢いに任せて、というわけではありませんが、当初の計画ではとうの昔にリリースを終えているはずのCutiteCircuitのアップデート、長い苦労の末ようやく完成しました!パチ!パチ!

 

f:id:Karvan:20180328195705p:plain

f:id:Karvan:20180326204448p:plain

 

 3月の末に初版をリリースしてから思いのほか日数を要しましたが、一応これで完成です。バグは随時改修するとして、これ以上の大きな変更や追加はないと思います。まぁ、容量が100MBギリギリなのでこれ以上増やせない、というのが正直なところなんですが。

 

今回のバージョンの主な変更点は

  • 4ステージを追加、これで全16ステージに。
  • イベントを追加、ちょっとしたストーリー仕立てになります。
  • ポーズ処理を追加。
  • 全体的に難易度を簡単に。
  • 全ステージクリアでエンディングが表示されます。

こんなところでしょうか。難易度を下げたことで遊びやくなったと思います。

 

残りはiOS版のリリース!あとひと頑張りです!!

 

 

◇プライバシーポリシー

●個人情報の利用目的

当ブログでは、メールでのお問い合わせ、メールマガジンへの登録などの際に、名前(ハンドルネーム)、メールアドレス等の個人情報をご登録いただく場合がございます。

これらの個人情報は質問に対する回答や必要な情報を電子メールなどをでご連絡する場合に利用させていただくものであり、個人情報をご提供いただく際の目的以外では利用いたしません。

●個人情報の第三者への開示

当サイトでは、個人情報は適切に管理し、以下に該当する場合を除いて第三者に開示することはありません。

・本人のご了解がある場合
・法令等への協力のため、開示が必要となる場合

個人情報の開示、訂正、追加、削除、利用停止
ご本人からの個人データの開示、訂正、追加、削除、利用停止のご希望の場合には、ご本人であることを確認させていただいた上、速やかに対応させていただきます。

アクセス解析ツールについて

当サイトでは、Googleによるアクセス解析ツール「Googleアナリティクス」を利用しています。

このGoogleアナリティクスはトラフィックデータの収集のためにCookieを使用しています。このトラフィックデータは匿名で収集されており、個人を特定するものではありません。
この機能はCookieを無効にすることで収集を拒否することが出来ますので、お使いのブラウザの設定をご確認ください。

●免責事項

当サイトからリンクやバナーなどによって他のサイトに移動された場合、移動先サイトで提供される情報、サービス等について一切の責任を負いません。

当サイトのコンテンツ・情報につきまして、可能な限り正確な情報を掲載するよう努めておりますが、誤情報が入り込んだり、情報が古くなっていることもございます。

当サイトに掲載された内容によって生じた損害等の一切の責任を負いかねますのでご了承ください。

●プライバシーポリシーの変更について

当サイトは、個人情報に関して適用される日本の法令を遵守するとともに、本ポリシーの内容を適宜見直しその改善に努めます。

修正された最新のプライバシーポリシーは常に本ページにて開示されます。