
【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;
}
}

↑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については、ブラウザ上で再度テストプレイしたほうがよさそうですね。