【Unity】愛のゆっくりっかー【備忘録】
買っただけでやった気になっている教材を消化しようと思い、いたのくまんぼう氏の「Unityの寺子屋 定番スマホゲーム開発入門」を参考にクリッカーゲームを作りました。
クリッカーゲームを紹介している講座や書籍は少ないので、大変ありがたかったです(書籍内では放置ゲームの途中からクリッカーゲームに改造)。
今回、自分が作った「愛のゆっくりっかー」は東方Projectの二次創作ゲームで、「ゆっくり」をひたすらクリックして、可愛がるだけのゲームです。
ゆっくりのクリッカーゲームなので「ゆっくりっかー」という名前が良かったのですが「ゆっくりっかー」という「ゆっくり」への愛がないゲームがありましたので、「愛の」ゆっくりっかーという名前にしています。
教材内のゲームシステムの概要は以下のとおりです。
1.画面下部の木魚をクリックすると、「徳」と書かれた珠のインスタンス生成されます。
2.珠は、DoTweenを使用したアニメーションで、画面上部の寺まで飛んでいきます。
3.クリックする回数に応じて徳ゲージが溜まっていき、一定数を越えると寺のレベルがあがって、寺のグラフィックが変わります。
4.寺が最大レベルまであがった状態で、徳ゲージを限度まで貯めると、くす玉のグラフィックが表示されてゲームクリアです。
このゲームの木魚をゆっくりに、寺をゆっくりプレイス(ゆっくりの家)に、珠をハートに変えてゲームをつくることを思いつきましたが、あまりに味気ないので、今まで試したかった事を色々試すことにしました。以下その結果を記載していきます。
1.TextMeshProで文字をアニメーションさせた
レベルアップ時等にテキストを表示するだけでは盛り上がりに欠けると思い、文字のアニメーションとして、以下の記事を参考に「TextMeshProSimpleAnimator」を導入しました。
「Text」と違い、「TextMeshPro」は日本語のフォントを導入するのに一手間が必要です。以下の記事を参考に導入しましたが、導入できるフォントとできないフォントがありました(理由は不明でした)。
↓記事は次回参考にします。
なお、ノイズパターンを使用するアニメーションについては紹介記事だけでは導入できなかったので、以下の通りteratailに質問を投げて、回答を得ることができました。
TextMeshProの設定で詰まった点が2点ありましたので、以下画像を掲載します。
以下、TextMeshProについて参考になりそうな記事を載せておきます。
2.ハートをパーティクルで発生させるようにした
ゆっくりをクリックすると珠ではなく、ハートを発生させるようにしたかったので、以下の記事を参考にしました。
元のゲームではDoTweenを使用して、珠をアニメーションさせていましたが、パーティクルに置き換えました。
ゆっくりをクリックするとパーティクルシステムをプレイするようにしました。
またハートはピンク(ゆっくり度+1)、銀(+3)、金(+5)がランダムに発生するようにしたかったので、パーティクルシステムを3つ作成しました。
public AudioClip [] voices; //ボイス
public ParticleSystem pinkLikeEffect; //ピンクのハートが出るパーティクルシステム
public ParticleSystem silverLikeEffect; //銀のハートが出るパーティクルシステム
public ParticleSystem goldLikeEffect; //金のハートが出るパーティクルシステム
// オブジェクト参照
public GameObject gameManager; // ゲームマネージャー
SoundManager soundManager; //サウンドマネージャー
private void Start() {
GameObject gameObject = GameObject.FindGameObjectWithTag("SoundManager");
soundManager = gameObject.GetComponent<SoundManager>();
}
public void TapYukkuri() {
gameManager.GetComponent<GameManager>().CreateHeart();
soundManager.RandomizeSfx(voices);
//https://tech.pjin.jp/blog/2021/03/31/unity_howto_random/
int a = UnityEngine.Random.Range(0, 10);
if (a > 3) { //60%の確率
//ピンクのハートのパーティクル放出
pinkLikeEffect.Play();
gameManager.GetComponent<GameManager>().GetHeart(1);
}else if (a > 0) { //30%の確率
//銀のハートのパーティクル放出
silverLikeEffect.Play();
gameManager.GetComponent<GameManager>().GetHeart(3);
} else { //10%の確率
//金のハートのパーティクル放出
goldLikeEffect.Play();
gameManager.GetComponent<GameManager>().GetHeart(5);
}
}
3.画像とスコアつきツイートができるようにした
ゲームジャムのときによく見かけるプレイ画像とスコアが掲載されたツイートができるボタンを作りました。
クリックすると、ゆっくりプレイスのレベルとゆっくり度を、スクリーンショット画像付きでツイートします。導入方法は以下の記事の通りです。
記事は画像付きツイートの方法なので、スコア等もツイートさせたいときは以下のようなコードを付け加える必要があります。
private string tweetString; //ツイートする文章
public GameManager gameManager; //スコアが格納されているゲームマネージャー
public void OnClick() {
tweetString = "ゆっくりプレイスは"+gameManager.placeString[gameManager.placeLevel]+
"で、ゆっくり度が"+gameManager.score + "まで貯まったよ!";
StartCoroutine(TweetManager.TweetWithScreenShot(tweetString));
}
ただ、この画像付きツイートは縦持ちのゲームには向いてないです。
理由は以下の画像のように、サムネイルとして画面の中央3分の1くらいのみ表示されるからです。
4.スタート画面の背景をスクロールさせた
背景をスクロールする方法は以下の記事の3番目の方法が一般的で、過去に自分が通っていたオンラインスクールでも、その方法を習いました。
しかしRectTransformはanchordPositionでないと座標のワープが発生しなかったので、以下の記事を参考にコードを書き換えました。
public class BackScroll : MonoBehaviour {
public float speed;
[Header("デッドライン")]public float deadLine;
[Header("リスポーンライン")]public float respawnLine;
RectTransform rectTransform;
private void Start() {
rectTransform = GetComponent<RectTransform>();
}
//下から上に流れる
void FixedUpdate() {
rectTransform.Translate(0, speed, 0);
if (rectTransform.anchoredPosition.y > deadLine) {
rectTransform.anchoredPosition = new Vector3(0, respawnLine, 0);
}
}
}
なお、2枚並べている画像をスクロールさせる形だと、どうしても隙間ができてしまいましたが、3枚並べてほんの少しずつ重ねて配置すると隙間がなくなりました。
5.キャンバス内のソート順を自由に変更した
これは機能の導入ではないのですが、記述しないと確実に忘れるので記載します。
基本的にキャンバス内のオブジェクトはヒエラルキーの下にあるものが前に描画されますが、そうしたくない場合や下に配置しても前に描画されない場合はソート順を変えたいオブジェクトに「Canvas」をアタッチして、ソートをオーバライドにチェックすればソート順を自由に変えられます。
パネルを最前面に表示したくても、TextMeshProが前がどうしても前に表示されてしまったので、以下の記事を見つけ参考にしました。
ただし、「Canvas」をアタッチしたオブジェクトに「GraphicRaycaster」もアタッチしないと、子オブジェクトに設置したボタンが反応しないし、逆にオブジェクトの後ろに描画されているボタンやオブジェクトには反応してしまいます。
↓ボタンが反応しないランキング第6位「Canvas単位でGraphicRaycasterが必要」が該当しました。
6.EasySaveを導入した
今まではデータのセーブにplayerprefsを使用していましたが、今回からはEasySaveというアセットを使用しました。
これで放置アセットを、また一つ減らせました。
以下の記事を参考にすれば、すぐに導入できます。
playerprefsは色々問題のある機能なようなので、早く卒業したかったので、簡単に導入出来てホッとしました。
そしてplayerprefsでは保存できない型にも沢山対応しています。今回はbool値をそのままセーブ・ロードしました。
playerprefsだとfalseは0、trueは1のようにIntへの置き換えが必要なので、便利です。TransformやVectorにも対応しているので、今後も使用していきたいですね。
7.BGMとSEの音量を調節できるようにした
簡単そうに見えて、物凄い時間がかかりました。
以下の記事のとおり実装すれば、簡単に導入自体はできますが、スライダーを半分付近にすると、音量がほぼ0になります。
以下の記事を読むと、音量はSliderのValueの数値をそのまま当てはめるものではないようです(
var volume = Mathf.Clamp(Mathf.Log10(value) * 20f,-80f,0f);
の部分が該当)。
しかし、記事そのまま導入しても、シーン遷移やアプリ終了時に音量やスライダーの位置が元に戻ってしまいます。
スライダーと音量の大きさのズレやスライダーが動かなくなる現象に悩まされ2日、ようやく以下のコードをひねり出しました。
スライダーの位置をロード、セーブし、その位置をVolumeに変換して、オーディオミキサーに登録しています。
データのロード、セーブにはEasySaveを使用していますが、floatなのでplayerprefsでも問題ありません。
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.UI;
public class AudioVolume : MonoBehaviour {
public AudioMixer audioMixer;//オーディオミキサーを登録
public Slider bGMSlider;//BGMのスライダーを登録
public Slider sESlider;//SEのスライダーを登録
private void Start() {
//BGMとSEのスライダーの位置をロード。データがなければ最大値を入れる。
float bgmSliderPosition = ES3.Load<float>("BGM_SLIDER", 5.0f);
float seSliderPosition = ES3.Load<float>("SE_SLIDER", 5.0f);
//BGMとSEのセットメソッドを呼び出す
SetBGM(bgmSliderPosition);
SetSE(seSliderPosition);
}
public void SetBGM(float bgmSliderPosition) {
//スライダーの位置から相対量をdBに変換してvolumeに入れる
var volume = Mathf.Clamp(Mathf.Log10(bgmSliderPosition/5) * 20f, -80f, 0f);
//スライダーの位置のデータとビジュアルを合わせる
bGMSlider.value = bgmSliderPosition;
//オーディオミキサーにvolumeの値をセットする。
audioMixer.SetFloat("BGM", volume);
//スライダーの位置をセーブする。
ES3.Save<float>("BGM_SLIDER", bGMSlider.value);
}
public void SetSE(float seSliderPosition) {
var volume = Mathf.Clamp(Mathf.Log10(seSliderPosition/ 5) * 20f, -80f, 0f);
sESlider.value = seSliderPosition;
audioMixer.SetFloat("SE", volume);
ES3.Save<float>("SE_SLIDER", sESlider.value);
}
}
8.レベルアップ時に画面がフェードアウト→フェードインするようにした
教材ではレベルアップ時に爆発の画像を出していましたが、フェードアウト→フェードインに変更しました。以下の記事を参考に簡単に導入できました。
以下のように外部からisFadeOut、isFadeInをtrueにすることで、フェードイン、アウトを開始することができます。Invokeやコルーチンでタイミングを調節して使うとよいと思います。
fadeController.isFadeOut = true; //フェードアウト開始
Invoke("FadeIn", 3.5f); //3.5秒後にフェードインメソッド開始
void FadeIn() {
fadeController.isFadeIn = true;//フェードイン開始
}
9.クリア後もスコア(ゆっくり度)を上げられるようした
教材の中では寺レベルが最高になり、くす玉が割れてクリア状態になると「徳」を貯めることはできませんが、「愛のゆっくりっかー」ではクリア後も「ゆっくり度」を無限に(実際はintの最大値2,147,483,647まで)貯めれるようにしました。
クリア時にbool型のisClearがtrueになり、trueであれば、クリア前はnextScoreが表示される部分が∞になります。
// スコアテキスト更新
void RefreshScoreText () {
//未クリアの場合
if (!isClear) {
textScore.GetComponent<Text>().text =
"ゆっくり度:" + score + " / " + nextScore;
} else {
//クリア済の場合
textScore.GetComponent<Text>().text =
"ゆっくり度:" + score + " / ∞";
}
}
↓intの最大値について
10.ピボットを意識したアニメーションを作った
スタート画面でゆっくりれいむとゆっくりまりさがお互いに押し合ってプニプニしている感じのアニメーションを作りました。
このアニメーションを作成するときにゆっくりの画像のピボットを変更しています。通常はピボットが画像の中央なので、この状態で収縮させると両端から縮まってぷにぷにしている感じになりません。
画面端側をピボットにすることで、画像の画面中央側のみ縮まり、ぷにぷに押し合っているになりました。
11.アプリ起動時のMade With Unityロゴ画面を編集した
スプラッシュスクリーンという名前のようです。変えれることはおろか名前すら知りませんでした。
初めてGooglePlayにアップロードされた方がロゴ画面を変えていたので、そんなに複雑な手順を踏まずに変えられるのか?と思い、調べました。
結果記事のとおりやれば、3分かからず導入できました。
12.広告をいれた
広告は過去に入れていたことはあるのですが、今年Unityでのゲーム開発を再開してからは初となります。導入方法は以下の記事のとおりです。
ただし、テスト広告は表示成功したものの、Androidのビルドは今だ通っていませんで、もしかしたら間違っている点があるかもしれません。
ちなみにテストIDは以下の通りです(出展)。
バナー ca-app-pub-3940256099942544/6300978111
インタースティシャル ca-app-pub-3940256099942544/1033173712
インタースティシャル(動画) ca-app-pub-3940256099942544/8691691433
リワード ca-app-pub-3940256099942544/5224354917
上の記事をそのまま実装すればよいのですが、バナーはスマートバナー(画面の幅に合わせるバナー)の方がいいと思います。
//30行目の以下のコードを
this.bannerView = new BannerView(adUnitId, AdSize.Banner, AdPosition.Bottom);
//以下の通り、AdSize.SmartBannerに変える。
this.bannerView = new BannerView(adUnitId, AdSize.SmartBanner, AdPosition.Bottom);
インタースティシャル広告は、loadInterstitialAd();とshowInterstitialAd();両方必要です。実行された瞬間に実行されるので、ボイスや効果音を再生中だと不自然になるかもしれないので、実行するタイミングの調整が必要だと思います。
interstitial.loadInterstitialAd();//インタースティシャル広告を読み込む
interstitial.showInterstitialAd();//インタースティシャル広告を表示する
リワード広告は、見ると報酬があるタイプの広告です。
これは以下の2つの記事を参考にします。今回はリワード広告を見ると、お礼のメッセージが表示されます。バナー広告、インタースティシャル広告よりも導入には手間取りました。
あとスクリプト名を「Reward」にすると、他のスクリプトと干渉してエラーになりますので、他の名前を付けるようにしてください。
記事を読み解いた結果、以下のようなコードとコメントになりました。
using GoogleMobileAds.Api;
using System;
using UnityEngine.UI;
using UnityEngine;
public class RewardSystem : MonoBehaviour {
private RewardedAd rewardedAd = null;
public Text messegeText;
int count = 0;
bool isRewarded;
string[] messege = {
"応援、ありがとう。",
"励みになります。",
"感謝しています。",
"次回作にご期待ください。"
};
// テスト用広告ユニットID
private string adId = "ca-app-pub-3940256099942544/5224354917";
private bool rewardedAdRetry = false;
// Start is called before the first frame update
void Start() {
// Initialize the Google Mobile Ads SDK.
MobileAds.Initialize(initStatus => { });
LoadRewardAd();
}
//isRewarded=trueならShowRewardResult()を実行
void Update() {
if (isRewarded) {
isRewarded = false;
ShowRewardResult();
}
if (rewardedAdRetry) {
LoadRewardAd();
rewardedAdRetry = false;
}
}
//リワード広告を見た報酬としてメッセージを表示
public void ShowRewardResult() {
messegeText.text = messege[count];
count++;
if (count == 4) {
count = 0;
}
}
//ボタンクリックに登録
public void UserChoseToWatchAd() {
if (this.rewardedAd.IsLoaded()) {
this.rewardedAd.Show();
}
}
void LoadRewardAd() {
// Clean up banner ad before creating a new one.
if (rewardedAd != null) {
rewardedAd = null;
}
rewardedAd = new RewardedAd(adId);
// Register for ad events.
rewardedAd.OnAdLoaded += HandleRewardAdLoaded;
rewardedAd.OnAdFailedToLoad += HandleRewardAdFailedToLoad;
rewardedAd.OnAdOpening += HandleRewardedAdAdOpened;
rewardedAd.OnAdClosed += HandleRewardedAdAdClosed;
rewardedAd.OnUserEarnedReward += HandleUserEarnedReward;
rewardedAd.OnAdFailedToShow += HandleRewardedAdFailedToShow;
AdRequest adRequest = new AdRequest.Builder().Build();
this.rewardedAd.LoadAd(adRequest);
}
public void HandleRewardAdLoaded(object sender, EventArgs args) {
Debug.Log("HandleRewardAdLoaded event received with message: " + args);
rewardedAdRetry = false;
}
public void HandleRewardAdFailedToLoad(object sender, AdFailedToLoadEventArgs args) {
LoadAdError loadAdError = args.LoadAdError;
int code = loadAdError.GetCode();
string message = loadAdError.GetMessage();
Debug.Log("Load error string: " + loadAdError.ToString());
Debug.Log("code: " + code.ToString());
MonoBehaviour.print(
"HandleRewardedAdFailedToLoad event received with message: "
+ message);
if (code == 2 || code == 0) {
Debug.Log("error");
} else {
Debug.Log("error no fill");
}
rewardedAdRetry = true;
}
public void HandleRewardedAdAdOpened(object sender, EventArgs args) {
Debug.Log("HandleRewardedAdAdOpened event received");
}
public void HandleRewardedAdFailedToShow(object sender, AdErrorEventArgs args) {
MonoBehaviour.print(
"HandleRewardedAdFailedToShow event received with message: "
+ args.AdError.GetMessage());
}
//動画の視聴が完了したら実行される(途中で閉じられた場合は呼ばれない)
public void HandleUserEarnedReward(object sender, Reward args) {
string type = args.Type;
double amount = args.Amount;
MonoBehaviour.print(
"HandleRewardedAdRewarded event received for "
+ amount.ToString() + " " + type);
isRewarded = true;
}
public void HandleRewardedAdAdClosed(object sender, EventArgs args) {
Debug.Log("HandleRewardedAdClosed event received");
rewardedAdRetry = true;
}
}
広告を実装する場合は、プライバシーポリシー掲載が必要なのですが、その方法等は以下の記事が凄くまとまっています。
↓プライバシーポリシー掲載の例
困った点.Androidのビルドが通らなかった
2022/11/14記事掲載時点で、まだAndroidのビルドができてません。埒が明かないので、WebGL版とWindows版を先に公開しました。
解決・Androidビルド成功とその理由(2022/11/21)
藁にもすがる思いで以下のUdemyの講座を受けたところ、問題が解決し、無事広告付きのアプリのビルドに成功しました!
大きな原因はGoogle Adsのプラグインのバージョンを最新版のv7.3.1を使用していたことで、v6.1.2を使用するとビルドできました。コンソールのエラーメッセージには、「Gradle」と表示されていたのでGradleのバージョンは上げたり下げたりしましたが、プラグインのバージョンは盲点でした。
ビルドができただけでも満足でしたが、税金やプレスリリース、テスト端末の登録など、知らないことやためになる話もいっぱい入っている素晴らしい講座でした。広告実装についての事柄を体系的に学べるので本当におススメです!
復習1.アスペクト比を一定にする神スクリプトをさらに便利にした
以前から紹介している素晴らしいスクリプトなのですが、縦持ちアプリと横持ちアプリ用のPrefabを作って、新しいプロジェクトの際はそれをそのまま使用することにしました。
どうせ縦と横の2種類しかないし、解像度も1080×1920を基準にすれば問題はないと思います。
↓偉大なる元記事1
↓偉大なる元記事2
復習2.便利で簡単なボタン用スクリプトを作った
ボタン用の汎用性のあるスクリプトを作りました。
シーン遷移、ゲーム終了、Webページを開く、ActiveSelfを切り替えるの4つができます。シーン遷移とゲーム終了は開始までのディレイをかけることもできます。
効果音を鳴らす場合は、SoundManagerスクリプトも必要です。
using UnityEngine;
using UnityEngine.SceneManagement;
using System;
public class ConveniButton : MonoBehaviour {
[Header("表示したいWebページ")] public String url;
[Header("移動したいシーン")] public String scene;
[Header("表示したい・消したいゲームオブジェクト")] public GameObject gObject;
[Header("Delayで遅らせたい時間")] public float delayTime;
[Header("ウィンドウオープン時またはシーン遷移時になるSE")]public AudioClip OpenSE;
[Header("ウィンドウクローズ時またはゲーム終了時になるSE")]public AudioClip CloseSE;
bool dontSeBarrage;//DelayGoT0Scene,DelayQuitSceneのSEの連射防止
SoundManager soundManager;
public void Start() {
GameObject gameObject = GameObject.FindGameObjectWithTag("SoundManager");
soundManager = gameObject.GetComponent<SoundManager>();
dontSeBarrage = false;
}
/// <summary>
/// シーン遷移
/// </summary>
public void GoToScene() {
SceneManager.LoadScene(scene);
}
/// <summary>
/// delayTime分遅れて、シーン遷移
/// </summary>
public void DelayGoToScene() {
if (OpenSE&&!dontSeBarrage) {
soundManager.PlaySe(OpenSE);
dontSeBarrage = true;
}
Invoke("GoToScene", delayTime);
}
/// <summary>
/// ゲーム終了
/// </summary>
public void QuitGame() {
Application.Quit();
}
/// <summary>
/// delayTime分遅れて、ゲーム終了
/// </summary>
public void DelayQuitGame() {
if (CloseSE&&!dontSeBarrage) {
soundManager.PlaySe(CloseSE);
dontSeBarrage = true;
}
Invoke("QuitGame", delayTime);
}
/// <summary>
/// Webページを開く
/// </summary>
public void GoToWeb() {
if (OpenSE) {
soundManager.PlaySe(OpenSE);
}
Application.OpenURL(url);
}
/// <summary>
/// ActiveSelfを切り替える
/// </summary>
public void OpenOrClose() {
if (gObject.activeSelf) {
if (CloseSE) {
soundManager.PlaySe(CloseSE);
}
gObject.SetActive(false);
} else {
if (OpenSE) {
soundManager.PlaySe(OpenSE);
}
gObject.SetActive(true);
}
}
}
復習3.サウンドマネージャーを作った
上記の記事を参考にSoundManagerも作りました。
AudioMixerは必須なので作ってください(「7.BGMとSEの音量を調節できるようにした」を参照)。
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.SceneManagement;
public class SoundManager : MonoBehaviour
{
[SerializeField] AudioMixer audioMixer;
[SerializeField] AudioSource bgmAudioSource;
[SerializeField] AudioSource seAudioSource;
public static SoundManager instance;
void Awake() {
CheckInstance();
}
void CheckInstance() {
if (instance == null) {
instance = this;
} else {
Destroy(gameObject);
}
}
void Start() {
DontDestroyOnLoad(gameObject);//シーン遷移しても破棄されない
}
/// <summary>
/// BGMを再生
/// </summary>
/// <param name="clip"></param>
public void PlayBgm(AudioClip clip) {
bgmAudioSource.clip = clip;
bgmAudioSource.Play();
}
/// <summary>
/// BGMを停止
/// </summary>
public void StopBgm() {
bgmAudioSource.Stop();
}
/// <summary>
/// 効果音を再生
/// </summary>
/// <param name="clip"></param>
public void PlaySe(AudioClip clip) {
seAudioSource.PlayOneShot(clip);
}
/// <summary>
/// 引数のAudioClipの中からランダムに再生
/// </summary>
/// <param name="clips"></param>
public void RandomizeSfx(params AudioClip[] clips) {
var randomIndex = UnityEngine.Random.Range(0, clips.Length);
seAudioSource.PlayOneShot(clips[randomIndex]);
}
}
復習4.公開方法ごとのビルド設定等のメモ
unityroom
圧縮形式はGripにする。
データキャッシングと解凍フォールバックにチェックを入れない。
ゲームアツマール
データキャッシングと解凍フォールバックにチェックを入れる。
アイコン、スクリーンショットの大きさが100KBまでなので、大きい場合は圧縮等で容量を小さくする。
↓オンラインで画像圧縮できるサイト
GooglePlay
アイコンの大きさは512px×512px。アダプティブアイコンなので、前面用と背景用の画像を用意する。前面用は、中央付近に画像を置き、周りは透過する。
フィーチャーグラフィックと呼ばれる、アプリのヘッダー画像のようなものが必要。大きさは1024×500px。
次回の課題
以下の記事を参考にオリジナルボタンを作りたかったのですが、C# 9.0以上でないと有効でないコードらしく、プロジェクトの途中でバージョンアップするのは怖いので次回以降に持ち越すことにしました。
以下の記事も読みましたが、もうだいぶ作った後だったので、次回作に持ち越しです。
スクリプト上でプラットフォームごとの処理を分けるのも試してみたいですね。
実験的に下のようなコードを実装しました。
「ゲームをやめる」ボタンはWebGL版には不要なので、非表示にします。広告周りにも使えそうです。
#if UNITY_WEBGL
quitbutton.SetActive(false);
#endif
また、今までは2020.3.41f1を使用していましたが、2021.3.12f1が10月にLTS(安定版)リリースされたので、乗り換えようと思います。
ビルドや再生の待ち時間もかなり軽減されそうです。
この記事が気に入ったらサポートをしてみませんか?