見出し画像

【Unity】シューティングゲームを作ってみた【ゆっくり】

 シューテイングゲームの作成を行うことで、Unityの基礎力が上がるようなので、今回作成しました。実際効果はあったように思いますので、備忘録として記事を書きます。

↓BOOTH版(ダウンロード)

↓ゲームアツマール版(ブラウザ版、スマホ可)

↓unityroom版(ブラウザ版、スマホ不可)

↓GooglePlay版

↓参考にした教材(CodegeniusさんのDanmakuⅠとDanmakuⅡ)

↓参考にした動画3本

↓参考になりそうな記事

1.被弾したら点滅して、一定時間無敵にする

上記の記事を参考にしました。
プレイヤーにアタッチするコンポーネントに以下のコードを実装します。
絶対、もっとうまくかけるはず。何回もGetCompornentしているのが気に食わない…。

    float alpha_Sin;
    private Color _color;

    private void Update() {
   //Time.timeの後の数値を大きくすると点滅の間隔が短くなる
        alpha_Sin = Mathf.Sin(Time.time*30) / 2 + 0.5f;
    }

    void Retry() {
   //中略
        isMuteki = true;
        StartCoroutine("ColorCoroutine");
        Invoke("MutekiOff", 1.5f);

    }

    void MutekiOff() {
        isMuteki = false;
    //コルーチンをストップ
        StopCoroutine("ColorCoroutine");
    //プレーヤーのアルファ値を元に戻す
    Color _color = GetComponent<Renderer>().material.color;
        _color.a = 1;
        GetComponent<Renderer>().material.color = _color;
    }

    IEnumerator ColorCoroutine() {
        while (true) {
            yield return new WaitForEndOfFrame();
      Color _color = GetComponent<Renderer>().material.color;
            _color.a = alpha_Sin;
            GetComponent<Renderer>().material.color = _color;
        }
    }

2.円状のHPゲージを導入する

東方の原作に倣って、敵のHPを敵の周囲にワッカ上に表示することにしました。同じ大きさの色の違う輪の画像を用意します。自分は白と赤の画像を用意し、赤を前面に配置します。そして赤の輪っかが上部から時計回りに減っていき、全て白になる(前面の赤の輪っかが全て消える)と敵のHPは0になります。

↑こんなの

以下の記事を参考にします。

using UnityEngine;
using UnityEngine.UI;


public class EnemyHealth : MonoBehaviour {
    public GameObject effectPrefab;
    public AudioClip sound;
    public int enemyHP;
    public Slider hpSlider;
    // ★★追加(スコア)
    public int scoreValue;
    private ScoreManager sm;

    public GameObject image;
    //最大HP
    public float maxHP;
    //現在のHP
    public float currentHP;

    private void Start() {
        if (image) {
            currentHP = maxHP;
        }
        // 「ScoreLabel」オブジェクトについている「ScoreManager」スクリプトにアクセスして取得する(ポイント)
        sm = GameObject.Find("ScoreLabel").GetComponent<ScoreManager>();
    }


    private void OnTriggerEnter2D(Collider2D other) {
        // もしもぶつかった相手に「Missile」というタグ(Tag)がついていたら、
        if (other.gameObject.CompareTag("Missile")) {
            // 敵のHPを1ずつ減少させる
            if (image) {
                currentHP -= 1;
            }

            // ミサイルを破壊する
            Destroy(other.gameObject);
            if (image) {
                HPDown(currentHP, maxHP);
            }

            // 敵のHPが0になったら敵オブジェクトを破壊する。
            if (currentHP==0||enemyHP==0) {
                // 親オブジェクトを破壊する(ポイント;この使い方を覚えよう!)
                //Destroy(transform.root.gameObject);
                //親オブジェクトを破壊するのはキケン
                Destroy(this.gameObject);
                if (sound) {
                    // 効果音を出す
                    AudioSource.PlayClipAtPoint(sound, Camera.main.transform.position,0.8f);
                }
                if (effectPrefab) {
                    // エフェクトを発生させる
                    GameObject effect = Instantiate(effectPrefab, transform.position, Quaternion.identity);

                    // 0.5秒後にエフェクトを消す
                    Destroy(effect, 2.0f);
                }
                // ★★追加(スコア)
                // 敵を破壊した瞬間にスコアを加算するメソッドを呼び出す。
                // 引数には「scoreValue」を入れる。
                sm.AddScore(scoreValue);
            }
        }
    }

    public void HPDown(float current, float max) {
        //ImageというコンポーネントのfillAmountを取得して操作する
        image.GetComponent<Image>().fillAmount = current / max;
    }
}
RemiriaのHPGageFullのImageの設定
↑Imageの塗りつぶし量とHPが連動。円状に塗りつぶし量を増減させたいときはRadial360を選びます。右回りにチェックを入れると時計回りに塗りつぶし量が減ります。FillOriginは円の切れ目の位置です。日本語版のUnityで申し訳ない……。

3.オブジェクトがDestoyされる瞬間にランダムで効果音を鳴らす

敵や自機がDestoyされるときにランダムで効果音を鳴らす場合、オーディオソースも一緒にDestoyされて効果音が鳴らないことを防ぐため、以下のようなコードを実装しました。
しかし、効果音が鳴るのがワンテンポ遅い気がしました…。

    [SerializeField] AudioClip[] clips; //ランダムで鳴らしたい音声の配列
    [SerializeField] AudioSource source;
    void PlayRandom() {
        source.clip = clips[UnityEngine.Random.Range(0, clips.Length)];
        source.Play();
    }

が、自己解決しました。どうやら上記のコードでは処理が重いらしいです。
以下のコードに書き直すことで、効果音の遅れがなくなりました。

private AudioClip destroySound; //private変数
[SerializeField] AudioClip[] clips;
//オーディオソースは不要

//任意のタイミングで以下の処理を実行
destroySound = GetRandom(clips);//空の変数に配列からランダムで要素を入れる
AudioSource.PlayClipAtPoint(destroySound, Camera.main.transform.position);

//配列からランダムで要素を取ってくる関数
internal static T GetRandom<T>(params T[] Params) {
        return Params[UnityEngine.Random.Range(0, Params.Length)];
    }

音が小さいと感じた時は、PlayClipAtPointの第二引数をカメラの座標にしましょう。

4.ランダムな弾を生成する

以下の記事を参照にスクリプトを書きました。

   public BossEnemyBullet [] bulletPrefabs;//GameObject型ではない場合、そのままコンポーネントを受け取ることができる。
   private int number; //ランダム情報を入れるための変数

~~中略~~

  void Shot(float angle,float speed)
    {
        number = UnityEngine.Random.Range(0, bulletPrefabs.Length);
        BossEnemyBullet bullet = Instantiate(bulletPrefabs[number], transform.position, transform.rotation);
        bullet.Setting(angle,speed);
    }

5.Destoy(transform.root.gameObject)の注意点

今回参考にしたCodeGeniusさんの敵のEnemyHealthスクリプトの中に以下の一文があります。

            // 親オブジェクトを破壊する(ポイント;この使い方を覚えよう!)
            Destroy(transform.root.gameObject);

EnemyHealthスクリプトはEnemyの子オブジェクトのbodyに付いており、bodyがDestoyされれば、親オブジェクトのEnemyオブジェクトもDestoyされます。
しかし、複数のEnemyをEnemiesフォルダにまとめている場合、bodyがDestoyされたとき、親オブジェクトのEnemyだけでなく、その親のEnemiesフォルダもDestoyされてしまいます。
このため、テキストのやり方から変えて、EnemyHealthスクリプトはじめbodyについていたコンポーネントは親のEnemyオブジェクトにアタッチし、DestroyはDestroy(this.gameObject);とし解決しました。

6.Time.deltaTimeとFixedUpdateについて

↓参考になる記事の数々

 コードジェネシスさんの講座を元にシューティングゲームを作りましたが、プレイ毎に、プレイヤーの動きが遅かったり、速かったりしました。その理由は、Update関数内でTime.deltatimeを使用せずに、プレイヤーや敵の動きを実装していたからでした。Update関数は、環境によって1フレームの長さが変わります。

通常は1秒間に60フレームや30フレームですが、ロースペックの環境でプレイすれば1フレーム=1秒ということもありうるということです。

 だから、毎回毎回キャラクターの動き方が違ったわけです。1フレームをある程度補正して、一定間隔にしてくれるのが、Time.deltatimeということみたいです。FixedUpdateはフレームとは関係なく、一定間隔で呼ばれます。じゃあ「Update関数をFixedUpdate関数に全部置き換えればええやん!」となりましたが、実際はそう簡単なモノではないようです。

 まずFixedUpdate関数は、入力処理と相性が悪いです。入力したタイミングがFixedUpdate関数の呼び出されるタイミングと合わず、入力から遅れて動いたり、動かなかったりすることがあるようです。

 2つ目に、Time.timeScale = 0の状態では、呼び出されなくなります。ポーズ状態は、Time.timeScale = 0 (ゲーム内時間停止)とTime.deltatime=1(ゲーム内時間通常)を切り替えて実装するため、ポーズ中に、ポーズ中のFixedUpdate関数内の処理は一切行わないことになります。

 他にも色々あるようですが、FixedUpdateを利用するより、Update関数とTime.deltatimeを組み合わせたほうが、無難な気がしました。Update関数内のポーズ中に行ってほしくない部分はif(Time.timeScale = 1){}で囲めばよいです。

 他の講座や教材にも言えることですが、盲信しすぎるのは危険です。特に初心者は、重複する部分があったとしても、色々な人の考え方に触れることをおすすめします。どんなに素晴らしい教材でも「ここの部分は他の人が言っているやり方のほうが効率的だな」ということが多々あります。誤解のないように申し上げますと、コードジェネシスさんの講座ですが、全体的に素晴らしく、間違いなく今回のゲームの基礎となっています。

↓FireMissile(プレイヤーのショットを管理するスクリプト。教材ではTime.deltatimeではなく固定値になっていた。)

using UnityEngine;
using UnityEngine.UI;

public class FireMissile : MonoBehaviour {
    // 変数の定義(データを入れるための箱を作成する。)
    public GameObject missilePrefab;
    public float missileSpeed;
    public AudioSource sfxSource;
    [Header("おならSE")] public AudioClip[] onaraSE;
    //private float timeCount;
    [Header("発射間隔")]public float interval; // 何秒間隔で撃つか
    private float timer = 0.0f; // 時間カウント用のタイマー

    //参考 https://teratail.com/questions/304496

    public float shotBarRecoveryTime;

    // ★追加(弾切れ発生)
    // ★改良(ショットパワーの全回復)
    // アクセス修飾子を「public」に変更する。
    public float maxPower = 2600;
    public float shotPower;

    // ★追加(パワー残量の表示)
    public Slider powerSlider;

    public bool buttondown = false;

    private void Start() {
        shotPower = maxPower;

        // ★追加(パワー残量の表示)
        powerSlider.maxValue = maxPower;
        powerSlider.value = shotPower;
    }


    void Update() {
        // ★改良(長押し連射)
        //timeCount += Time.deltaTime;
        //Debug.Log(timeCount);

        if (timer > 0.0f) {
            timer -= Time.deltaTime;
        }

        if (Time.timeScale == 0) {
            if (Input.GetKeyDown(KeyCode.A) || Input.GetKeyDown(KeyCode.D)) {
                return;
            }
        }


        // ★改良(長押し連射)
        // 「GetButtonDown」を「GetButton」に変更する(ポイント)
        // 「GetButton」は「押している間」という意味
        if (Input.GetButton("Jump") || buttondown) {
            if (Time.timeScale == 1) {//これがないとポーズ中にショットできる
                                      // ★追加(弾切れ発生)
                                      // ここのロジックをよく復習すること(重要ポイント)
                if (shotPower <= 0) {
                    return;
                }

                // ★追加(弾切れ発生)
                shotPower -=  500 * Time.deltaTime;

                // ★追加(パワー残量の表示)
                powerSlider.value = shotPower;

                // ★改良(長押し連射)
                // 「5」の部分の数字を変えると「連射の間隔」を変更することができます(ポイント)
                // 「%」と「==」の意味合いを復習しましょう。
                if (timer <= 0.0f) {
                    // プレハブからミサイルオブジェクトを作成し、それをmissileという名前の箱に入れる。
                    GameObject missile = Instantiate(missilePrefab, transform.position, Quaternion.identity);

                    Rigidbody2D rigidbody2D = missile.GetComponent<Rigidbody2D>();

                    rigidbody2D.velocity = transform.up * missileSpeed;

                    RandomizeSfx(onaraSE);

                    timer = interval; // 間隔をセット

                    // 発射したミサイルを2秒後に破壊(削除)する。
                    Destroy(missile, 0.6f);

                }
            }
        }
        // ★追加(ショットパワーの回復)
        else {
            if (Time.timeScale == 1) {//これがないとポーズ中にショットパワーが回復する
                shotPower += shotBarRecoveryTime * Time.deltaTime;

                if (shotPower > maxPower) {
                    shotPower = maxPower;
                }

                powerSlider.value = shotPower;
            } 
        }
    }

    public void RandomizeSfx(params AudioClip[] clips) {
        var randomIndex = UnityEngine.Random.Range(0, clips.Length);
        sfxSource.PlayOneShot(clips[randomIndex]);
    }

    //仮想ボタンがクリックされたとき
    public void ButtonDown() {
        buttondown = true;
    }

    //仮想ボタンのクリックが終わったとき
    public void ButtonUp() {
        buttondown = false;
    }
}

7.static変数を使って、スコアの管理とノーコンティニュークリアの判定を行う

 static(静的)な変数やクラスは1つのプロジェクトに1つしか存在できません。今回はScoreManagerクラスの中にstatic int Score変数を用意しました。この変数はどのシーンの、どのスクリプトからでも

ScoreManager.Score += 40;

と入力すれば40点加点することができます。ゲームを終了したとしても、ScoreManager.Scoreが自動的に初期値に戻るわけではないので、タイトル画面を制御するTitleManager.csにScoreManager.Score = 0;と入力し、タイトル画面を起動するとScoreが0になるようにしています。また、本家東方に倣って、このゲームではノーコンティニュークリアを達成すると、エンディングの演出が変わるようになっています。ノーコンティニュークリアの判定を行うために、static bool noContinue変数を用意しました。Stage1開始時点でScoreManager.noContinue = trueとなり、ゲームオーバーになるとfalseになります。そして、エンディングでnoContinueがtrueかfalseを判定し、演出を変えています。

↓ScoreManagerスクリプト

using UnityEngine;
using UnityEngine.UI;

public class ScoreManager : MonoBehaviour {
    // 静的変数(ポイント)
    // public staticをつけることで、このScoreManagerスクリプトがついている他のオブジェクトと
    // scoreのデータを共有することができるようになります。
    [SerializeField] public static int score = 0;
    public static int oneUPscore = 500;
    public GameObject oneUPItem;
    public static string nowStage = "Stage1";//リトライ時に仕様。
    private Text scoreLabel;
    [SerializeField]public static int highScore;//ハイスコア記録
    [SerializeField]public static bool highScoreUpdate; //ハイスコア更新
    public static bool noContinue;//ノーコンティニューかどうか
    public bool highScoreHyoji;//タイトル画面のハイスコア表示のみチェック入れる
    public string nextStageName;

    void Start() {
        // 「Text」コンポーネントにアクセスして取得する
        scoreLabel = GetComponent<Text>();
        scoreLabel.text = "SCORE:" + score;
        highScore = PlayerPrefs.GetInt("SCORE", 0);
        if (highScoreHyoji) {
            scoreLabel.text = "HIGHSCORE:" + highScore;
        }
    }

    // スコアを加算するメソッド(命令ブロック)
    // 「public」をつけて外部からこのメソッドにアクセスできるようにする(重要ポイント)
    public void AddScore(int amount) {
        // 「amount」に入ってくる数値分を加算していく。
        score += amount;
        scoreLabel.text = "SCORE:" + score;

        if (score >= oneUPscore) {
            oneUPItem.SetActive(true);
            oneUPscore += 500;
        }
    } 
}

8.PlayerPrefsを使ってのハイスコアを記録する

今回のまりちゃのシューティングではハイスコアを記録する処理を実装しました。
その際に使用したのが、PlayerPrefsです。

これはfloat、int、string型の変数を保存できる機能であり、複雑なセーブデータの保存などには向いていません。

他にも色々問題があり、使用はあまり推奨されていないようです。

しかし、導入するのが簡単なので、ハイスコア程度の簡単な情報の保存を行いたい人(特に初心者)には向いていると思います。
まずタイトル画面でScoreManagerのhighScore変数にハイスコアをセットします。ハイスコアがない場合は0をセットします。

// Score というキー名のデータをロード.キーが存在しなかったら 0 を返す.
int highscore = PlayerPrefs.GetInt("Score", 0);

 GameOverになったとき、またはステージ3(ラストステージ)をクリアしたときにscoreがhighScoreを超えていると、ScoreMangagerのhighScoreUpdateがtrueになり、HighScoreスクリプトでboolを判定し、画面に「HighScore!」の文字が点滅します。それと同時にPlayerPrefs.SetInt("Score", ScoreManager.score);とし、ハイスコアが記録されます。

  //ゲームオーバー時とクリア時に判定
   void GameOver() {
        if (ScoreManager.score > ScoreManager.highScore) { //ハイスコアを更新していた場合
            ScoreManager.highScore = ScoreManager.score;
            ScoreManager.highScoreUpdate = true;
        }
        SceneManager.LoadScene("GameOver");
    }
using UnityEngine;
using UnityEngine.UI;

public class HighScore : MonoBehaviour
{
    public GameObject highScoreText;
    public float speed = 1.0f;
    private Text text;
    private float time;
    void Start()
    {
        text = this.gameObject.GetComponent<Text>();
        if (ScoreManager.highScoreUpdate) {
            highScoreText.SetActive(true);//highScoreが更新されていたら表示
            PlayerPrefs.SetInt("SCORE", ScoreManager.highScore);//highscoreをPrefsにセーブ
            PlayerPrefs.Save();//https://futabazemi.net/unity/high_score/
        } else {
            highScoreText.SetActive(false);
        }
    }

    void Update()
    {
        //参考https://goodlucknetlife.com/unity-2daction-blinker/
        if (highScoreText) {
            text.color = GetAlphaColor(text.color);
        }
    }

    Color GetAlphaColor(Color color) {
        time += Time.deltaTime * 5.0f * speed;
        color.a = Mathf.Sin(time);
        return color;
    }
}

9.様々なアスペクト比に対応する魔法のスクリプト(再掲載)

 どんなアスペクト比の端末で再生しても、一定のアスペクト比を保つ、超優秀スクリプトを以前紹介し、その後の改善版も紹介しましたが、改めて紹介いたします。今回は縦型のアスペクト比でしたが、何も問題なく使用できました。あと画面を枠上に囲う帯は必ず設定しましょう。シーンが変わると前のシーンのゴミのようなものが映りこんでしまいます。

10.弾幕を作る

 弾幕自体を作成するスクリプトについては、参考資料を見ていただくとして、今回ボスについてはコルーチン処理を使用し、弾幕発射装置AとBを時間によってSetActiveのtrueとfalseを入れ替えたり、残HPによって切り替えたりすることで、ボスが同じ弾幕を撃ち続けて単調化することを防ぎました。弾幕のグラフィックは原作っぽいものを自作しました。

あと弾幕の実装は本当に複雑で面倒くさいので、アセットを使った方が効率的だと思います。

↓このアセットなんか日本人の方が開発されているようですし、よさそうです。セールになったら購入しようかと思います。

↓れみりゃの行動パターンをコルーチン処理したスクリプト。HPと時間によってSetActiveを切り替える。

    IEnumerator CPU() {

        //特定の位置まで移動する
        while (transform.position.y > 2.5f) {
                transform.position -= new Vector3(0, 3, 0) * Time.deltaTime;
                yield return null;//1フレーム待つ
        }

        AudioSource.PlayClipAtPoint(uh, Camera.main.transform.position);

        //whileがtrueならループ
        while (true) {
            //レミリアのHPが最大HPの2/3以上の場合
            if (remiriaHealth.currentHP > remiriaHealth.maxHP * 2 / 3) {
                bulletType = BulletType.Big;
                yield return WaveNShotM(8, 16);
                yield return new WaitForSeconds(1f);
                bulletType = BulletType.BlueSword;
                yield return WaveNPlayerAimShot(10, 5);
                yield return new WaitForSeconds(1.8f);
            //レミリアのHPが最大HPの1/3以上の場合
            } else if (remiriaHealth.currentHP > remiriaHealth.maxHP * 1 / 3) {
                nWay.SetActive(true);
                spiral.SetActive(false);
                yield return new WaitForSeconds(1.0f);
                spiral.SetActive(true);
                nWay.SetActive(false);
                yield return new WaitForSeconds(8f);
            } else {
                bulletType = BulletType.Big;
                nWay.SetActive(false);
                spiral.SetActive(false);
                yield return WaveNPlayerAimShot(6, 7);
                yield return new WaitForSeconds(1.5f);
            }
            //yield return new WaitForSeconds(1.5f);//これが最後に入らない状態でwhileを回すと無限ループになる
        }
    }

11.オブジェクトプーリングについて

 本ゲームのザコ的や弾幕については、多くの教材でも紹介されているとおり、Instantiate(生成)とDestroy(削除)を繰り返していますが、オノティーさんの動画によると、シーン開始時に必要最大限の数をinstantiateし、あとは必要に応じて、SetActiveをtrue、falseを切り替える方法のほうが、処理が軽く、望ましいそうです。これを「オブジェクトプーリング」といいます。

ザコ的や弾幕の生成は複雑な部分が多く、自分の現在の能力ではオブジェクトプーリングに置き換えるのは難しいと思ったので、今回は導入を見送りましたが、1UPアイテムについては、オブジェクトプーリングもどきを導入しました。

初めからステージにはSetActive=falseの状態で1UPアイテムを一つ用意します。

そしてScoreManager.1UP~が500点を超えるごとにSetActiveがtrueになり、OnEnable(SetActiveがtrueになったときに作動する)で、y座標は画面外上部で、X座標をランダムにします。あとは自然落下し、1UPアイテムをプレイヤーが取得するか、y座標が画面外下部以下になったところでSetActiveがfalseになります。表示されるX座標をランダムにすることで、取り損ねる確率が上がり、ゲームをスリリングなモノにします。

using UnityEngine;

public class Player1UPItem : Item // 「MonoBehaviour」を「Item」に変更する(これで「Itemクラスを承継」することができます。)
{
    private PlayerHealth ph;
    private int reward = 1;

    void Start() {
        ph = GameObject.Find("Player").GetComponent<PlayerHealth>();
    }

    //アクティブになるときx座標はランダムにする。
    private void OnEnable() {
        //https://www.sejuku.net/blog/51251
        Transform myTransform = this.transform;
        // 座標を取得
        Vector3 pos = myTransform.position;
        pos.x = Random.Range(-2.0f, 2.0f);
        pos.y =6f;
        myTransform.position = pos;  // 座標を設定
    }

    //取り損ねた時に消える。
    private void Update() {
        if(transform.position.y < -7) {
            this.gameObject.SetActive(false);
        }
    }

    //PlayerにRigidbody2Dを付けないと振れたことにならない
    private void OnTriggerEnter2D(Collider2D other) {
        if (other.gameObject.tag == "Player") {
            // (重要ポイント)ItemクラスのItemBaseメソッドを呼び出す。
            base.ItemBase(other.gameObject);

            if (ph != null) {
                // 自分で設定した分だけ自機が回復する。
                ph.Player1UP(reward);
            }
        }
    }
}

12.文字がぼやける場合はScaleの値を小さくして、フォントのサイズを大きくする

 これはそのままです。以下の参考記事を参照。

疑問点1.ポーズ中のスペースキーの挙動

以下のスクリプトでポーズ(一時停止)キーを導入したのですが、ボタンをマウスクリックして、ポーズした後は、ショットボタンであるスペースキーでもポーズ解除とポーズができてしまいました。

using UnityEngine;
using UnityEngine.UI;


//一時停止ボタン、参考 https://www.youtube.com/watch?v=w10_AXiGYuY
public class PauseScript : MonoBehaviour{

    public bool IsOnPause;
    public Sprite playButton;
    public Sprite pauseButton;
    public GameObject pauseEffect;
    public AudioClip pauseOnSE;
    public AudioClip pauseOffSE;
    public GameObject joyStick;

    public void Update() {
       if (PlayerHealth.zanki > 0 && Input.GetKeyDown(KeyCode.P)) {//GetKeyDown関数にしないと何回も押せてしまう。Pキーでもポーズがかかるように改良         
            pauseTheGame();
        }
    }

    public void pauseTheGame(){
        if (IsOnPause && !Input.GetKeyDown(KeyCode.Space)) { //これがないとボタンをクリックした後にスペースキーでもポーズができてしまう。
            Time.timeScale = 1;
            IsOnPause = false;
            //押すとボタンの画像自体が変わる方式に変更 https://futabazemi.net/unity/photo_change_collider/
            this.gameObject.GetComponent<Image>().sprite = pauseButton;
            AudioSource.PlayClipAtPoint(pauseOnSE, Camera.main.transform.position);
            pauseEffect.SetActive(false);
            joyStick.SetActive(true);
        } else if(!Input.GetKeyDown(KeyCode.Space)) {//これがないとボタンをクリックした後にスペースキーでもポーズができてしまう。
            AudioSource.PlayClipAtPoint(pauseOffSE, Camera.main.transform.position);//時間を止めるより先に音を鳴らさないと音が鳴らない
            Time.timeScale = 0;
            IsOnPause = true;
            this.gameObject.GetComponent<Image>().sprite = playButton;
            pauseEffect.SetActive(true);
            joyStick.SetActive(false);
            var ab = joyStick.GetComponent<FixedJoystick>();
            ab.OnPointerUp(null);//ドラッグを終了したことにする
        }
    }
}

!Input.GetKeyDown(KeyCode.Space)をif文に入れて、スペースキーでは、このスクリプトが作動しないようにしたのですが、そもそも何故スペースキーが作動するかがわかりませんでした。

疑問点2.背景のスクロールが少しずれる

空の画像を使用して、スクロールする背景を作ったのですが、若干背景と背景の隙間が空いてきてしまうことがありました。これも原因はわかりませんでした。

using UnityEngine;
 
public class BackGround : MonoBehaviour {
    //スクロールスピード
    [SerializeField] float speed = 1;
    [SerializeField] float limitPosition;
    [SerializeField] float loopPosition;
    public bool yokoScroll = false;//チェックが入ってたら横スクロール

    private void Start() {
    }

    void Update() {
        if (!yokoScroll) {
            //下方向にスクロール
            transform.position -= new Vector3(0, Time.deltaTime * speed);

            //Yが-11まで来れば、yPositionまで移動する
            if (transform.position.y <= limitPosition) {
                transform.position = new Vector2(0, loopPosition);
            }
        } else {
            //左方向にスクロール
            transform.position -= new Vector3(Time.deltaTime * speed,0);

            //Xが-11まで来れば、xPositionまで移動する
            if (transform.position.x <= limitPosition) {
                transform.position = new Vector2(loopPosition,transform.position.y);
            }
        }
    }
}

ちなみに端と端がつながる様に作られているスクロール背景用画像でなくても、左右あるいは上下反転した画像を隣り合わせれば、スクロール背景として使えますよ!

7/3追記、どうやら背景スクロールはTime.deltatimeと相性が悪いようです?
以下の記事でも使っていません。

使っている方法を紹介しているところもあるので、確かなことは言えませんが、Time.deltatimeを使わなくなってから、私のゲームでは隙間はでなくなりました。

追記・WebGLビルドの注意点

WebGLビルドしたファイルをアップロードするゲームアツマールとunityroomでは進行不可能なバグが発生するとの報告がありました。

バグの理由はStageManagerの以下の部分です。

    private void Update() {
        if (enemy9 == null && boss != null && !isClear) {
            BossActive();
        }

        if (boss==null && !isClear) {
            isClear = true;
            StartCoroutine("StageClear");
        }
    }

~~中略~~

    void BossActive() {
            boss.SetActive(true);
    }

1つ目のが、バグのある状態ではif(enemy9 == null && !isClear)となっており、bossがnullになった後(Destoyされた後)も、BossActiveでbossをSetActiveをtrueにしようとしていて、ゲームが落ちてました。

WebGL環境でなければ、それでも動いていたのですが。
WebGLについては、ブラウザ上で再度テストプレイしたほうがよさそうですね。

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