見出し画像

「ヒグマだけど、鮭ぶっ飛ばしてみた」をGooglePlayとAppStoreにあげてみた【Unity】

↓前回の記事

Unity1Week「1ボタン」に投稿した「ヒグマだけど、鮭ぶっ飛ばしてみた」をスマホアプリにしてみました。

↓GooglePlay版

↓AppStore版

ちなみにUnity1Weekの結果は、どの項目も50位以内に入りませんでした…。12月にまたあるようなので、そこでまた頑張りたいと思います。

個人的にはUnity1Weekで作ったゲームをスマホアプリにするというのはオススメです。Unity1Week投稿がゴールではなく、通過点になるため、バグやアップデートに身が入るからです。

今回、スマホアプリにするにあたって、unityroom版からアップデートした点があります。また詰まった点もありますので、書いていきますね。


アップデートした点・実績機能の実装

ストア用のスクリーンショットなので、文字が入ってます。

前作の刑事・西門郷介ではガチャでアイテムを収集する機能を付けていましたが、今回は実績に応じて熊の情報が見ることができる機能を実装しました。

実装方法は以下の通りです。まず実績名が表示されているボタン(例えば「20
回ゲームをプレイした」ボタン)に以下のスクリプトをアタッチします。

using UnityEngine;
using UnityEngine.UI;

public class Achievements : MonoBehaviour{
    public string itemName; //アイテム名
    public Sprite itemImage; //アイテム画像
    [TextArea(3, 5)] //アイテムの説明入力エリア
    public string description; //アイテムの説明
    [SerializeField] private Text _text; //アイテム説明テキスト
    [SerializeField] private Text _name; //アイテム名テキスト
    [SerializeField] private Image _image; //クマ画像Image
    [SerializeField] private int _checkCount; //実績解除に使用する数値
    [SerializeField] private GameObject starMark;
    [SerializeField] private AudioClip[] bearVoice;
    private bool gotInfomation;
    [SerializeField]private Button _button;

    public enum AchievementStatus{
        None,
        RetryCount,
        BearButton,
        PlayCount,
        TenCombo,
        Score,
        JustMeetCount,
        OnePlayJustMeet,
    }

    [SerializeField] private AchievementStatus _achievementStatus;
    //「クマに注意!」ボタンを始めて押したという実績のStart関数は「実績と熊情報ボタン」に登録
    public void Start(){
        _button.interactable = false;
        switch (_achievementStatus){
            //_achievementStateには現在の状態が入っている
            case AchievementStatus.RetryCount:
                var a = ES3.Load<int>("RETRY", 0); //リトライ回数をロード
                if (a >= _checkCount){
                    gotInfomation = true;
                }
                break;
            case AchievementStatus.BearButton:
                var b = ES3.Load<int>("BEARBUTTON", 0); //熊注意ボタンを押した回数をロード
                if (b >= _checkCount){
                    gotInfomation = true;
                }
                break;
            case AchievementStatus.PlayCount:
                var c = ES3.Load<int>("PLAY", 0); //プレイ回数をロード
                if (c >= _checkCount){
                    gotInfomation = true;
                }
                break;
            case AchievementStatus.TenCombo:
                var d = ES3.Load<int>("TENCOMBO", 0); //10コンボ到達回数をロード
                if (d >= _checkCount){
                    gotInfomation = true;
                }
                break;
            case AchievementStatus.Score:
                var e = ES3.Load<int>("HIGHSCORE", 0); //ハイスコアをロード
                if (e >= _checkCount){
                    gotInfomation = true;
                }
                break;
            case AchievementStatus.JustMeetCount:
                var f = ES3.Load<int>("JUSTMEET", 0); //ジャストミート回数をロード
                if (f >= _checkCount){
                    gotInfomation = true;
                }
                break;
            case AchievementStatus.OnePlayJustMeet:
                var g = ES3.Load<int>("ONEPLAYJUSTMEET", 0); //1回のプレイでのジャストミート回数をロード
                if (g >= _checkCount){
                    gotInfomation = true;
                }
                break;
        }
        ButtonUpdate(); //画像更新
    }

    public void ButtonUpdate(){
        if (gotInfomation){
            //実績解除済
            starMark.SetActive(true);//スターマークを表示する
            _button.interactable = true;//ボタンを押せるようにする
        }
    }

    public void OnClick(){
        //実績ボタンがクリックされたとき
        if (gotInfomation){
            //実績解除済
            SoundManager.instance.RandomVoice(bearVoice);
            _text.text = description; //項目の説明を更新
            _name.text = itemName; //項目の名前を更新
            _image.sprite = itemImage; //項目のイメージを更新
        }
    }
}

すると、ボタンのインスペクタが以下のようになります。
インスペクタで設定した、項目名、写真、説明文が実績ボタンを押すと切り替わります。
CheckCountは実績解除に必要な数値です。以下の画像は列挙型AchievementStatusがPlay Countつまりプレイ回数なので、20回以上プレイすると実績解除となり、ボタンが押せるようになります。

ヒグマ怖すぎる

↓列挙型の参考記事

なお、Start関数がpublicの理由ですが、「クマに注意!ボタンを始めて押した」という実績は、「実績と熊情報」ウィンドウがあるのと同じTitleシーンで解除される実績なので、シーンの切り替わりタイミングで呼び出していては遅いからです(初回限定ですが)。
このため、「実績と熊情報」ボタンで呼び出せるようにpublic関数としています。

GameManagerは以下の通りで、プレイ回数等の実績を記録しています。

using System;
using UnityEngine;
using UnityEngine.UI;
using GoogleMobileAds.Api;//usingを追加

public class GameManager : MonoBehaviour{
    [SerializeField] private AudioClip startSe;//スタートSE
    [NonReorderable] public int highScore = 0;//ハイスコア
    [SerializeField] private Text scoreText;//スコアテキスト
    [Header("ハイスコアテキスト")] public Text highScoreText;
    [Header("ハイスコア更新テキスト")] public GameObject highScoreBroken;
    [Header("コンボテキスト")] public SuperTextMesh comboText;
    [Header("コンボテキスト")] public GameObject _comboText;
    [Header("サーモンカウント")] public Text salmonCount;
    [SerializeField] GameObject salmon;  //鮭
    private int playCount;//鮭が通過した回数
    private int gameCount;//ゲームをプレイした回数
    [NonSerialized] public int score;//得点
    [NonSerialized] public int comboCount;  // 現在のコンボ数
    [SerializeField] private GameObject _startManual;
    [Header("結果のスコアテキスト")] public Text resultScoreText;
    [Header("結果の鮭テキスト")] public Text resultSalmonText;
    [NonSerialized] public int flySalmon; //ぶっとばした鮭の数
    [SerializeField] public GameObject resultPanel;
    [NonSerialized] public int justmeetCount;//ジャストミートした回数


    public static GameManager instance;//どこからでもアクセスできるようにする
    void Awake() {
        CheckInstance();
    }
    void CheckInstance() {
        if (instance == null) {
            instance = this;
        } else {
            Destroy(gameObject);
        }
    }

    //マニュアル経由で開始した場合。マニュアルの開始ボタンにセット
    public void OnClickStart(){
        _startManual.SetActive(false);
        Time.timeScale = 1;//時を進める
        SoundManager.instance.PlaySe(startSe);
        ES3.Save<int>("Manual",1);//マニュアル表示済みフラグオン
    }
    
    private void Start(){
        //iOSでフルスクリーン広告が表示されている間、Unityアプリを一時停止
        MobileAds.SetiOSAppPauseOnBackground(true);
        // ゲームの初期化
        score = 0;//スコアを0にセット
        Time.timeScale = 0;//StartTimeLineが開始しないように時間を止める
        highScore = ES3.Load<int>("HIGHSCORE",0);//ハイスコアをロード
        highScoreText.text = highScore.ToString("d5");//ハイスコアテキスト更新
        highScoreBroken.SetActive(false);
        comboCount = 0;  // コンボ数をリセット
        justmeetCount = 0; //ジャストミート数をリセット
        flySalmon = 0;
        _comboText.SetActive(false);
        int flag = ES3.Load<int>("Manual", defaultValue: 0);//マニュアル表示フラグを呼び出す
        _startManual.SetActive(true);//マニュアルを表示
        if (flag == 1){//マニュアル表示済みなら
            _startManual.SetActive(false);//マニュアルを非表示に
            Time.timeScale = 1;//時を進める
        }
        //プレイ回数をロードして、1回足して、セーブする。
        gameCount = ES3.Load<int>("PLAY", 0); //プレイ回数をロード
        gameCount++;
        ES3.Save<int>("PLAY",gameCount);
    }
    
    //TimeLineのレシーバーから呼ばれる
    //鮭が通り過ぎたら、コンボカウントリセット
    public void ComboReset(){
        _comboText.SetActive(false);
        comboCount = 0;
    }

    //TimeLineのレシーバーから呼ばれる
    public void SalmonEnable(){
        salmonCount.text = "鮭残り"+ (10 - playCount) + "匹";
        if (playCount >= 10){//鮭が10匹遡上していたら
            GameEnd();   
        }else{
            salmon.SetActive(true);
            playCount++;
        }
    }

    public void ScoreComboDisplay(){
        scoreText.text = score.ToString("d5");
        if (comboCount >= 2){
            comboText.text = comboCount + "連続";
            _comboText.SetActive(true);
        }
    }
    
    private void GameEnd(){
        
        // Type == Number の場合
        naichilab.RankingLoader.Instance.SendScoreAndShowRanking(score);
        //現在のスコアがハイスコアより高いなら
        if (score > highScore){
            //現在のスコアをハイスコアとして記録
            ES3.Save<int>("HIGHSCORE",score);
            highScoreText.text = score.ToString("d5");//ハイスコアテキスト更新
            highScoreBroken.SetActive(true);//ハイスコア更新テキスト表示
        }

        //コンボカウントが10なら実績解除
        if (comboCount == 10){
            ES3.Save<int>("TENCOMBO",1);
        }

        //ジャストミート回数が1回以上なら
        if (justmeetCount >= 1){
            //1プレイの最高ジャストミート回数をロードし、比較
            var a = ES3.Load<int>("ONEPLAYJUSTMEET", 0); 
            //今回のほうが回数が多いなら、1プレイの最高ジャストミート回数として記録
            if(justmeetCount > a) {
                ES3.Save<int>("ONEPLAYJUSTMEET", justmeetCount);
            }
            
            //通算ジャストミート回数をロードして、今回のジャストミート回数を足して、セーブする。
            var b = ES3.Load<int>("JUSTMEET", 0); //プレイ回数をロード
            b += justmeetCount;
            ES3.Save<int>("JUSTMEET", b);
        }

        resultScoreText.text = score.ToString() + "点";
        resultSalmonText.text = flySalmon.ToString() + "匹";
        resultPanel.SetActive(true);
    }
}

リトライ機能は以下の通りです。
リトライ回数を記録するのと同時に、リトライ回数が3の倍数のときにインタースティシャル広告を表示します。
流石に毎回広告が表示されるのは鬱陶しいと思うので、このようにしました。

    public void Retry() {
        //リトライ回数をロードして、1回足して、セーブする。
        var a = ES3.Load<int>("RETRY", 0); //リトライ回数をロード
        a++;
        ES3.Save<int>("RETRY",a);
     //リトライ回数が3の倍数の場合は広告表示
        if (a % 3 == 0){
            AdInterstitial();
        }
        scene = SceneManager.GetActiveScene().name;
        GoToScene();
    }

【追記】【重要】iOSでインタースティシャル広告表示中にもゲームが進んでしまう現象の対処

Apple君またすか…。
Androidではインタースティシャル広告表示中は、ゲームが停止するので、それが当たり前だと思っていましたが、iOSではそうではないらしいです(下の記事参照)。

自分はGameManagerのStartに以下のコードを追加しました。

//iOSでフルスクリーン広告が表示されている間、Unityアプリを一時停止
MobileAds.SetiOSAppPauseOnBackground(true);

usingも忘れないようにしてください。

using GoogleMobileAds.Api;//usingを追加

小技・画像やボタンの角を簡単に丸くする

ImageをSimpleRoundedImageに差し替えるのが簡単でした。

Shapes2Dというアセットもあるみたいです。こちらも今度試してみようと思います。

AndroidBuildエラーについて

今回はAndroidビルド時に以下のようなエラーが出ました。

CommandInvokationFailure: Gradle build failed.J:\Unity\2021.3.24f1\Editor\Data\PlaybackEngines\AndroidPlayer\OpenJDK\bin\java.exe -classpath
"J:\Unity\2021.3.24f1\Editor\Data\PlaybackEngines\AndroidPlayer\Tools\gradle\lib\gradle-launcher-6.1.1.jar" org.gradle.launcher.GradleMain "-Dorg.gradle.jvmargs=-Xmx4096m" "assembleRelease"

Gradle関係のエラーです。解決方法は以下の通りです。

1.Plugin/Androidのaarやjarを全削除する

2.Play Services ResolverのAndroidResolverのResolveまたはForce Resolveを使う

今回はこの方法で行けました。次回はわかりません!

Xcodeビルドエラーについて

また君か、壊れるなぁ

UI/SplashScreen.h' file not found

西門郷介のときから環境変わってないのに何故?と思いましたが、一つ変わっていることがありました。今作はオンラインランキングを導入しているのです。
UnityやNCMBのバージョンを上げる等色々やりましたが、
結局、エラー文の意味を素直に読み取ることにしました。
「UI/SplashScreen.h' file not found」つまりSplashScreen.hファイルがないという意味です。
自分のストレージのどこかにないかと思い、EveryThingで検索をかけます。EveryThingは高速なファイル検索ソフトでWindowsPCの人は全員入れたほうがいいと思います。外付けHDDやNAS、オンラインストレージまで一瞬で検索できます。これがないと仕事も作業もできません。

すると、C(インストール先ドライブ):\Unity\2021.3.21f1\Editor\Data\PlaybackEngines\iOSSupport\Trampoline\Classes\UIにありました!
他のUnityのバージョンの同階層にはないのが謎ですね。
同様にSplashScreen.mmも他のバージョンにはありませんでした。

これをUnityでiOSビルドしたファイルのClasses\UIフォルダに直接入れます。

またXCodeを立ち上げるとき、Unity-iPhone.xcodeprojファイルではなく、Unity-iPhone.xcworkspaceファイルから起動しないとビルド成功しませんでした。
.xcworkspaceファイルはMacBook上でビルドすると出てくるようです(WindowsのUnityでiOSビルドしても出てこないっぽい)。

.xcworkspaceファイルが出てこないときは以下の記事を参考にしてください。

わからないときはteratailで質問するのも手です。今回は自己解決しましたが。

MacBook君のちょっとした手荒い歓迎(SSDやUSBメモリについて)

普段はWindowsのUnityで作業して、iOSビルドの時だけ、スティックSSDでUnityのデータをMacBookに持って行っていたので気づかなかったのですが、MacBook側からWindows側に持っていきたいデータがあって、スティックSSDにデータを入れようとすると、マホカンタで弾かれてしまいます。

解決方法は上の記事の通りです。面倒くさいですね。

iOS&Xcodeビルド手順早見表(自分用)

今回もXcodeビルドを30回くらいやって、その度に同じ設定をしていたので、自分用の手順書を以下に記します。人によって設定する箇所が変わってくる可能性はありますので、あくまで参考程度にとどめてください。
というかまるさんのブログ見ればわかりますよ!

ここから先は

1,029字 / 9画像

¥ 100

この記事が気に入ったらサポートをしてみませんか?