見出し画像

【Unity】汎用スクリプトとテンプレートを作る(2024年4月版)

Unityでよく使用しているスクリプトやUnityの基礎を理解するための記事を紹介します。


1.Prefabを理解する

2.アスペクト比と解像度を一定に保つ方法

3.よく使うエディタ拡張

4.SoundManager(サウンド管理クラス)

5.クレジット用スクロールパネル

6.PlayFab(オンラインランキング)

7.アニメーションするボタン・ウィンドウ

8.よく使うスクリプト集

シングルトンについて

以下のようなコードをMonoBehaviourを継承したスクリプトに書くと、GameManager.i.Hoge()と書くことで、プロジェクト内のあらゆるところからGameManager内のpublic関数が使用できます。
プロジェクト内にGameManagerが1つしか存在しないことが保証されるからです。
シーンに2つ以上GameManagerがある場合、2つ目以降はDestroyされるため、GameManager(を空オブジェクト等にアタッチしたオブジェクト)は、全てのシーンに置いても構いません。
なお、他の記事ではiではなく、 Instanceとしているのが一般的ですが、面倒なのでiとしています。
シーン遷移時に破棄されても構わない場合はDontDestroyOnLoad(gameObject);は不要です。

  public static GameManager(クラス名) i;    

  void Awake() {
        CheckInstance();
    }

    void CheckInstance() {
        if (i == null) {
            i = this;
            DontDestroyOnLoad(gameObject); //シーン遷移しても破棄されない
        } else {
            Destroy(gameObject);
        }
    }

  public void Hoge(){
        Debug.Log("OK");
  } 

(1)GameManager

GameManagerは使うことが多いので、当たり障りのない形でテンプレートとして入れておき、後から改良していきます。
SaveData.csと組み合わせて使うことを想定しています。
セーブ及びアセット「EasySave3」について詳しく知りたい方は以下の記事もご覧ください。

using System;
using Sirenix.OdinInspector;
using UnityEngine;

public class GameManager : SerializedMonoBehaviour {
    public static GameManager i; //どこからでもアクセスできるようにする
    private SaveData saveData; //ゲームデータ
    [Header("最高レベル")][SerializeField] private int maxLevel; // 最高レベル
    public event Action OnMoneyChanged; // 所持金が変更されたときのイベント
    public event Action OnLevelChanged; // レベルが変更されたときのイベント
    //public PlayableDirector openingTimeLine;//オープニングのタイムライン

    void Awake() {
        CheckInstance();
    }

    void CheckInstance() {
        if (i == null) {
            i = this;
            DontDestroyOnLoad(gameObject); //シーン遷移しても破棄されない
        } else {
            Destroy(gameObject);
        }
    }

    private void Start() {
        LoadGameData();
    }

    // データを読み込む
    public void LoadGameData()
    {
        if (ES3.KeyExists("gameData"))
        {
            saveData = ES3.Load<SaveData>("gameData");
        }
        else
        {
            //セーブデータがない場合
            saveData = new SaveData();
            SaveGameData();
            //セーブデータがない場合はオープニングのタイムラインを再生
            //openingTimeLine.Play();
        }
    }

    // データを保存する
    public void SaveGameData(){
        ES3.Save("gameData", saveData);
    }
    
    /*
    // 特定の服が所持されているかを確認するメソッド
    public bool IsOutfitOwned(Clothes outfit) {
        if (gameData.OwnedOutfits.ContainsKey(outfit)) {
            return gameData.OwnedOutfits[outfit];
        }
        return false;
    }

    // 服を所持リストに追加するメソッド
    public void AddOutfitToOwned(Clothes outfit) {
        if (!gameData.OwnedOutfits.ContainsKey(outfit)) {
            gameData.OwnedOutfits.Add(outfit, true);
        }else{
            gameData.OwnedOutfits[outfit] = true;
        }
        SaveGameData();
    }

    // 装備中の服を変更するメソッド
    public void ChangeEquippedOutfit(Clothes outfit) {
        gameData.EquippedOutfit = outfit;
        SaveGameData();
    }

    // 現在装備している服の種類を返すメソッド
    public Clothes GetEquippedOutfit() {
        return gameData.EquippedOutfit;
    }
    */

    //お金を追加するメソッド
    public void AddMoney(long amount) {
        saveData.Money += amount;
        if (saveData.Money >= 9999999999){//99億9999万9999円より多くはならない
            saveData.Money = 9999999999;
        }
        OnMoneyChanged?.Invoke(); // 所持金が変更されたときにイベントを発火
        SaveGameData();
    }

    //お金を使用するメソッド
    public void SpendMoney(long amount) {
        if (saveData.Money >= amount) {
            saveData.Money -= amount;
            OnMoneyChanged?.Invoke(); // 所持金が変更されたときにイベントを発火
            SaveGameData();
        }
    }

    //所持金を取得するメソッド
    public long GetMoney() {
        return saveData.Money;
    }

    [Button("レベルアップ")]
    //レベルをアップするメソッド
    public void LevelUp() {
        // 最高レベルより小さい場合
        if (saveData.Level < maxLevel) {
            saveData.Level++;
            OnLevelChanged?.Invoke(); // レベルが変更されたときにイベントを発火
            SaveGameData();
        }
    }
    
    // レベルを取得するメソッド
    public int GetLevel() {
        return saveData.Level;
    }

    // デバッグ用に所持金を増やすメソッド
    public void AddDebugMoney() {
        AddMoney(100000); // 100,000円を追加
    }
}

(2)GameData

MonoBehaviourを継承しないことで、ヒエラルキー上にオブジェクトとして配置せずに、プロジェクト内のどこからでもアクセス可能になります。

using System;
using System.Collections.Generic;

[Serializable]
public class SaveData {
    public int Level;//レベル
    public long Money; // 所持金
    // public Dictionary<Clothes, bool> OwnedOutfits;//服の所持状況
    // public Clothes EquippedOutfit;//装備中の服

    public SaveData() {
        // OwnedOutfits = new Dictionary<Clothes, bool>();
        // //服を全て所有していない状況にする
        // foreach (Clothes outfit in Enum.GetValues(typeof(Clothes))) {
        //     OwnedOutfits.Add(outfit, false);
        // }
        
        Level = 1;//初期レベル
        // OwnedOutfits[Clothes.アーマー] = true;//初期所有服
        // EquippedOutfit = Clothes.アーマー;//初期装備
    }
}

(3)PauseScript

ポーズ(一時停止)に使用できます。
SoundManager必須なので、記事を参考にして導入してください。
コード中盤の「※最重要※ポーズ画面になる条件」の下のコードを変えれば、マウス、キーボードの入力からでもポーズできます。
ポーズ中に消したい、映したいオブジェクトも指定できます。

using UnityEngine;

//一時停止ボタン、参考 https://www.youtube.com/watch?v=w10_AXiGYuY
public class PauseScript : MonoBehaviour{
    //違うボタンから呼び出す場合、PauseScriptは共通でなくてはならない
    //(1ボタンと2ボタンから呼び出すなら、どちらも1ボタンのPauseScriptから呼び出す必要がある)
    [Header("ポーズ中に表示するパネル等")]public GameObject [] pauseEffects;
    [Header("ポーズ中隠したいオブジェクト")] public GameObject [] hideObjects;
    [Header("ポーズ画面に入る効果音")]public AudioClip pauseOnSE;
    [Header("ポーズ画面から抜ける効果音")] public AudioClip pauseOffSE;
    [Header("BGMも停止するか")] public bool bgmStop;
    //public GameManager gameManager;//ステージクリア後かどうか判定するためにGameManagerを登録。ゲーム内容によっては不要。
    [Header("ポーズ画面に入るキー")]public KeyCode pauseKey = KeyCode.Mouse1;//ポーズ画面に入るキー
    [System.NonSerialized] public bool isPaused;//ポーズ中かどうか
    
    //マウス、キーボード、コントローラー等のボタンを押した場合
    private void Update() {
        //////////////////※最重要※ポーズ画面になる条件///////////////////
        if (Input.GetKeyDown(pauseKey)/* &&!gameManager.isEnd*/) {
            TogglePause();
        }
    }

    //ボタンを押した場合
    public void OnClick() {
        TogglePause();
    }

    //ポーズ画面の表示、非表示
    private void TogglePause() {
        if (isPaused) {
            ResumeGame();
        } else {
            PauseGame();
        }
    }

    //ポーズ画面に入る
    private void PauseGame() {
        SoundManager.i.PlaySe(pauseOnSE);//効果音を再生
        Time.timeScale = 0;//時間を止める
        isPaused = true;//ポーズ中
        if (bgmStop) {//BGMも停止する場合
            SoundManager.i.PauseBgm();
        }
        //ポーズ中に表示するパネル等を表示、ポーズ中隠したいオブジェクトを非表示に
        SetActiveObjects(pauseEffects, true);
        SetActiveObjects(hideObjects, false);
    }

    //ポーズ画面から抜ける
    private void ResumeGame() {
        SoundManager.i.PlaySe(pauseOffSE);//効果音を再生
        Time.timeScale = 1;//通常のスピードに戻す
        isPaused = false;//ポーズ中でない
        if (bgmStop) {//BGMも停止する場合
            SoundManager.i.PlayBgm();//
        }
        //ポーズ中に表示するパネル等を非表示、ポーズ中隠したいオブジェクトを表示に
        SetActiveObjects(pauseEffects, false);
        SetActiveObjects(hideObjects, true);
    }

    //オブジェクトの表示、非表示
    private void SetActiveObjects(GameObject[] objects, bool isActive) {
        //配列の中身を取り出して、それぞれに対してSetActiveを実行
        foreach (GameObject obj in objects) {
            obj.SetActive(isActive);
        }
    }
}

↓参考動画

(4)ConviniButton

ボタンにアタッチするスクリプト。
シーン遷移、Webページ表示(iOS、Android振り分け機能あり)、SE再生、オブジェクトの表示・非表示、TimeLineの再生
が可能です。連打防止機能もあります。
SoundManagerとの組み合わせ必須です。
またUniTaskの導入も必要です。
FadeControllerとの組み合わせは推奨しますが、必須ではありません。
Animancer Proというアセットを使用してアニメーションさせていますが、空でもエラーにならないようになっています。

using UnityEngine;
using UnityEngine.SceneManagement;
using System;
using System.Threading;
using UnityEngine.UI;
using Cysharp.Threading.Tasks;
using Sirenix.OdinInspector;
using Animancer;
using UnityEngine.Playables;

public class ConveniButton : SerializedMonoBehaviour {
    [Header("リトライボタンにする場合はチェック")][SerializeField] private bool retryEnabled = false; // リトライ機能の有効/無効
    [Header("表示したいWebページ")] public String url;
    [Header("表示したいWebページ(Android)")] public String androidUrl;
    [Header("表示したいWebページ(iOS)")] public String iosUrl;
    [Header("移動したいシーン")] public String scene;
    [Header("表示させたいゲームオブジェクト")] public GameObject[] showObject;
    [Header("消したいゲームオブジェクト")] public GameObject[] hideObject;
    [Header("Delayで遅らせたい時間")] public float delayTime;
    [Header("SEまたはボイス")] public AudioClip playSe;
    [Header("効果音ではなくボイスを使用する場合にチェックする")] public bool voiceUse;
    [Header("フェードコントローラー")] public FadeController fadeController;
    [Header("表示したいエフェクト用ゲームオブジェクト")] public GameObject effectObject;
    [Header("Animancer")][SerializeField]AnimancerComponent animancer;
    [Header("クリック時アニメーション")][SerializeField]AnimationClip pressed;
    [Header("非クリック時アニメーション")][SerializeField]AnimationClip notPressed;
    [Header("TimeScaleの影響を無視するか")] public bool unScaleTimeUse;  // TimeScaleの影響を無視するかどうか
    [Header("タイムラインを操作するためのPlayableDirector")] public PlayableDirector director;
    [NonSerialized] protected bool _dontSeBarrage;//連打防止用
    private Button _button;
    private CancellationTokenSource _cancellationTokenSource;//キャンセルトークン

    void Start() {
        _dontSeBarrage = false;
        if (effectObject) {//エフェクト用ゲームオブジェクトがある場合は非表示にしておく
            effectObject.SetActive(false);
        }
        _button = GetComponent<Button>();//ボタンコンポーネントを取得
        _cancellationTokenSource = new CancellationTokenSource();//キャンセルトークンを生成
    }

    //このスクリプトが有効になったときにキャンセルトークンを生成する
    private void OnEnable() {
        Start();
    }

    //このスクリプトが無効になったときにキャンセルトークンをキャンセルする
    void OnDisable() {
        _cancellationTokenSource.Cancel();
    }
    
    //このスクリプトが破棄されたときにキャンセルトークンを破棄する
    void OnDestroy() {
        _cancellationTokenSource.Dispose();
    }

    private void AnimePlay() {
        if (animancer == null) return;
        var state = animancer.Play(pressed, 0.1f);
        state.Events.OnEnd = () => animancer.Play(notPressed, 0.1f);
    }
    
    //タイムラインを再生する
    public void PlayTimeline() {
        if (director == null) return;
        director.Play();
    }
    
    private async UniTask DelayPlayTimeline() {
        PlaySound();
        _dontSeBarrage = true;
        EffectGameObject();
        //delayTime秒待つ
        await UniTaskDelayHelper(TimeSpan.FromSeconds(delayTime));
        PlayTimeline();
        EffectGameObjectHide();
        _dontSeBarrage = false;
    }
    
    //タイムラインを遅れて再生する
    public void TriggerDelayPlayTimeline() {
        DelayPlayTimeline().Forget();
    }
    
    //タイムラインを停止する
    public void StopTimeline() {
        if (director == null) return;
        director.Stop();
    }
    
    //タイムラインを一時停止する
    public void PauseTimeline() {
        if (director == null) return;
        director.Pause();
    }
    
    private async UniTask DelayGoToScene() {//シーン遷移を遅らせる
        PlaySound();
        _dontSeBarrage = true;
        EffectGameObject();
        if (fadeController != null) {
            delayTime = fadeController.fadeOutDuration;//フェードアウトにかかる時間を遅延時間にする
            fadeController.isFadeOut = true;//フェードアウトを開始
        }
        //delayTime秒待つ
        await UniTaskDelayHelper(TimeSpan.FromSeconds(delayTime));
        GoToScene();
        EffectGameObjectHide();
        _dontSeBarrage = false;
    }

    private async UniTask DelayGoToWeb() {//Webページ遷移を遅らせる
        PlaySound();
        _dontSeBarrage = true;
        EffectGameObject();
        //delayTime秒待つ
        await UniTaskDelayHelper(TimeSpan.FromSeconds(delayTime));
        GoToWeb();
        EffectGameObjectHide();
        _dontSeBarrage = false;
    }

    public void GoToScene() {
        if (retryEnabled) {
            // リトライが有効の場合、現在のシーンをリロード
            SceneManager.LoadScene(SceneManager.GetActiveScene().name);
        } else {
            // そうでない場合は指定されたシーンに遷移
            SceneManager.LoadScene(scene);
        }
    }

    public void GoToWeb() {
        string platformUrl = GetPlatformSpecificUrl();
        if (!string.IsNullOrEmpty(platformUrl)) {
            Application.OpenURL(platformUrl);
        }
    }
    
    //プラットフォームによってURLを変更する
    private string GetPlatformSpecificUrl() {
#if UNITY_ANDROID
        return string.IsNullOrEmpty(androidUrl) ? url : androidUrl;
#elif UNITY_IOS
        return string.IsNullOrEmpty(iosUrl) ? url : iosUrl;
#else
        return url; // デフォルトURL
#endif
    }
    
    private async UniTask ShowAndHide() {//表示と非表示を遅らせる
        PlaySound();
        _dontSeBarrage = true;
        EffectGameObject();
        await UniTaskDelayHelper(TimeSpan.FromSeconds(delayTime));
        Show();
        Hide();
        EffectGameObjectHide();
        _dontSeBarrage = false;
    }

    public void PlaySound() {
        if (!_dontSeBarrage) {
            AnimePlay();
            if (playSe) {
                if (voiceUse) {
                    SoundManager.i.PlayVoice(playSe);
                } else {
                    SoundManager.i.PlaySe(playSe);
                }
            }
        }
    }

    protected void EffectGameObject() {//エフェクト用ゲームオブジェクトを表示する
        if (!effectObject) return;
        effectObject.SetActive(true);
        _button.interactable = false;
    }

    protected void EffectGameObjectHide() {//エフェクト用ゲームオブジェクトを非表示にする
        if (!effectObject) return;
        effectObject.SetActive(false);
        _button.interactable = true;
    }

    private void Show() {
        if(showObject.Length==0) return;
        foreach (var obj in showObject) {
            obj.SetActive(true);
        }
    }

    private void Hide() {
        if(hideObject.Length ==0) return;
        foreach (var obj in hideObject) {
            obj.SetActive(false);
        }
    }
    
    //UniTaskのDelayを使って遅延させる
    private async UniTask UniTaskDelayHelper(TimeSpan delay) {
        if (unScaleTimeUse) {
            await UniTask.Delay(delay, ignoreTimeScale: true, cancellationToken: _cancellationTokenSource.Token);
        } else {
            await UniTask.Delay(delay, cancellationToken: _cancellationTokenSource.Token);
        }
    }
    
    public void TriggerDelayGoToScene() {
        DelayGoToScene().Forget();  // Forgetを使って非同期タスクをファイアー&フォゲットする
    }

    public void TriggerDelayGoToWeb() {
        DelayGoToWeb().Forget();
    }

    public void TriggerShowAndHide() {
        ShowAndHide().Forget();
    }
}

(5)FadeController

Imageにアタッチし、他のScriptからisFadeOut、isFadeInをtrueにすると、Panelの透明度が変わり、フェードイン、フェードアウトが実行されます。
シーン開始時のフェードインについては設置するだけでも実行可能です。フェードインに使用する場合でも、Imageの透明度は0でOKです。startBlackがTrueになっている場合、シーン開始時にパネルの透明度が255になります。これでゲームビューが見づらいということはなくなります。

using System;
using UnityEngine;
using UnityEngine.UI;

public class FadeController : MonoBehaviour {
    [Header("フェードインにかかる時間")] public float fadeInDuration = 1.0f;  // フェードインにかかる時間(秒)
    [Header("フェードアウトにかかる時間")] public float fadeOutDuration = 1.0f; // フェードアウトにかかる時間(秒)
    [Header("TimeScaleの影響を無視するか")] public bool unScaleTimeUse = false;  // TimeScaleの影響を無視するかどうか

    [Header("シーン開始時に完全に黒からフェードインさせる時にチェック")]
    public bool startBlack = false;
    [NonSerialized] public bool isFadeOut = false;
    [Header("フェードインさせる時にチェック")]
    public bool isFadeIn = false;
    private Image fadeImage;
    private float fadeTimer = 0.0f; // フェード処理のタイマー

    void Awake() {
        fadeImage = GetComponent<Image>();
        if (startBlack) {
            SetAlpha(1.0f);  // 開始時に完全に黒
        } else {
            SetAlpha(0.0f);  // 完全に透明
        }
    }

    void Update() {
        float deltaTime = unScaleTimeUse ? Time.unscaledDeltaTime : Time.deltaTime;

        if (isFadeIn) {
            FadeIn(deltaTime);
        }
        if (isFadeOut) {
            FadeOut(deltaTime);
        }
    }

    private void FadeIn(float deltaTime) {
        fadeTimer += deltaTime;
        float alpha = Mathf.Clamp01(fadeTimer / fadeInDuration);  // 経過時間からアルファ値を計算
        SetAlpha(1 - alpha);  // 1から0へ
        if (fadeTimer >= fadeInDuration) {
            isFadeIn = false;
            fadeTimer = 0;
            fadeImage.enabled = false;
        }
    }

    private void FadeOut(float deltaTime) {
        fadeTimer += deltaTime;
        float alpha = Mathf.Clamp01(fadeTimer / fadeOutDuration);  // 経過時間からアルファ値を計算
        SetAlpha(alpha);  // 0から1へ
        if (fadeTimer >= fadeOutDuration) {
            isFadeOut = false;
            fadeTimer = 0;
        }
    }

    private void SetAlpha(float alpha) {
        fadeImage.color = new Color(fadeImage.color.r, fadeImage.color.g, fadeImage.color.b, alpha);
    }

    public void StartFadeIn() {
        isFadeIn = true;
        isFadeOut = false; // 状態をリセットして競合を防ぐ
        fadeImage.enabled = true;
        fadeTimer = 0; // タイマーをリセット
    }

    public void StartFadeOut() {
        isFadeOut = true;
        isFadeIn = false; // 状態をリセットして競合を防ぐ
        fadeImage.enabled = true;
        fadeTimer = 0; // タイマーをリセット
    }
}

(6)ScrollResetter

スクロールビューのScrollRectを一番上に戻すスクリプトです。
スクロールビューを開くボタンにアタッチしてください。

using System;
using Animancer.Examples.Basics;
using UnityEngine;
using UnityEngine.UI;
public class ScrollResetter : MonoBehaviour{
    [Header("位置をリセットするツマミを登録")]public ScrollRect scrollRect;
    //クリックするとスクロールビューが開くボタン等にセット。
    //スクロールビューが開くと同時にSetActiveAndResetScrollを実行。
    private Button button;

    //このスクリプトが有効になったときにボタンを取得
    private void OnEnable() {
        button = GetComponent<Button>();
        // ボタンがあればイベントリスナーを追加
        if (button != null){
            button.onClick.AddListener(SetActiveAndResetScroll);
        }
    }

    private void ResetScrollToTop(){
        // ScrollRectのcontentの位置をリセット
        if (scrollRect != null){
            scrollRect.verticalNormalizedPosition = 1.0f;
        }
    }
    
    public void SetActiveAndResetScroll(){
        ResetScrollToTop();
    }
}

(7)FolderStructureCreator

お気に入りのフォルダ構成を一瞬で作れるスクリプトです。
ビルド時にエラーになることがあるので、フォルダを作ったら、プロジェクトから消してしまったほうが無難です。

using UnityEngine;
using UnityEditor;
using System.IO;


//上部メニューのツールからフォルダを作成可能
public class FolderStructureCreator
{
    [MenuItem("Tools/Create Basic Folder Structure")]
    private static void CreateFolderStructure()
    {
        // 階層を含むフォルダリスト
        string[] folders = new string[]
        {
            "Assets/000Scripts",
            "Assets/000Scripts/000Manager",
            "Assets/000Scripts/001UI",
            "Assets/000Scripts/002SoundSys",
            "Assets/000Scripts/003ScreenEffect",
            "Assets/000Scripts/004AspectLock",
            "Assets/000Scripts/005Admob",
            "Assets/001Prefabs",
            "Assets/001Prefabs/000UIImportant",
            "Assets/001Prefabs/001SoundSystem",
            "Assets/001Prefabs/002Admob",
            "Assets/002Scenes",
            "Assets/003Sprites",
            "Assets/003Sprites/001Icons",
            "Assets/004Scenes",
            "Assets/005TimeLines",
            "Assets/006Audio",
            "Assets/006Audio/000BGM",
            "Assets/006Audio/001EventSE",
            "Assets/006Audio/002SystemSE",
            "Assets/006Audio/003Voice",
            "Assets/007Animations",
            "Assets/007Animations/001UI",
            "Assets/008Materials",
            "Assets/009Textures",
            "Assets/010ImportAssetsMaterial",
            "Assets/011ImportAssetsSystem",
            "Assets/Fonts",
            // 必要に応じて更に追加
        };

        foreach (string folder in folders)
        {
            // Directory.CreateDirectoryは、必要なすべてのサブディレクトリとともに指定されたパスにディレクトリを作成します。
            // 既に存在する場合はスキップされます。
            if (!Directory.Exists(folder))
            {
                Directory.CreateDirectory(folder);
                Debug.Log("Created folder: " + folder);
            }
        }

        // アセットデータベースを更新して、エディタが新しいフォルダを認識できるようにします。
        AssetDatabase.Refresh();
    }
}

(8)ScreenshotToSpecifyRange

指定範囲のスクリーンショットを撮影できるスクリプトです。

「ぷるぷる☆ゆっくり☆すいかげーむ」に導入した機能ですが、他のプロジェクトで使用したことがないので、今後検証します。

using System.IO;
using Cysharp.Threading.Tasks; // UniTaskを使用するために必要
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;

public class ScreenshotToSpecifyRange : MonoBehaviour{
    [Header("どの解像度でも一定の範囲をスクリーンショットする場合はチェック")]
    [SerializeField] private bool useRectMask2D;
    [Header("スクリーンショットを反映させるRawImage")]
    public GameObject targetImage;
    [Header("スクリーンショットで写したい幅")]
    [SerializeField] private int ssWidth;
    [Header("スクリーンショットで写したい高さ")]
    [SerializeField] private int ssHeight;
    [Header("スクリーンショットで写したくないモノ")]
    [SerializeField] private GameObject[] notScreenShot;
    private bool isNotScreenShotListEmpty;//notScreenShotが空かどうか
    private string screenshotPath;//スクリーンショットの保存先
    public static ScreenshotToSpecifyRange i;

    void Awake(){
        if (i == null){
            i = this;
        } else{
            Destroy(gameObject);
        }
    }

    private void Start(){
        //notScreenShotが空かどうか
        isNotScreenShotListEmpty = notScreenShot.Length == 0;
        //スクリーンショットの保存先
        screenshotPath = Path.Combine(Application.persistentDataPath, "ss.png");
    }

    //スクリーンショットを撮る
    public async UniTask ScreenShot(){
        if(!useRectMask2D){//RectMask2Dを使わない場合
            ScreenCapture.CaptureScreenshot(screenshotPath); 
        }else{//RectMask2Dを使う場合
            await CaptureScreenshotAsync(ssWidth, ssHeight);
        }
    }
    
    private async UniTask CaptureScreenshotAsync(int width, int height)
    {
        UIStateChange(true);//スクリーンショットを撮る前にUIを非表示にする
        await UniTask.WaitForEndOfFrame();//1フレーム待つ
        Texture2D tex = ScreenCapture.CaptureScreenshotAsTexture();//スクリーンショットを撮る
        int x = (tex.width - width) / 2;//スクリーンショットで写したい範囲の左上のX座標
        int y = (tex.height - height) / 2;//スクリーンショットで写したい範囲の左上のY座標
        Color[] colors = tex.GetPixels(x, y, width, height);//スクリーンショットで写したい範囲の色情報を取得
        Texture2D saveTex = new Texture2D(width, height, TextureFormat.ARGB32, false);//スクリーンショットで写したい範囲の色情報を保存するためのTexture2D
        saveTex.SetPixels(colors);//色情報をTexture2Dにセット
        saveTex.Apply();//変更を適用
        File.WriteAllBytes(screenshotPath, saveTex.EncodeToPNG());//スクリーンショットを保存
        Object.Destroy(tex);//不要になったTexture2Dを破棄
        Object.Destroy(saveTex);//不要になったTexture2Dを破棄
        UIStateChange(false);//スクリーンショットを撮った後にUIを表示する
        ShowSSImage();//スクリーンショットを表示
    }
    
    public void ShowSSImage(){
        //スクリーンショットを表示
        byte[] image = File.ReadAllBytes(screenshotPath);
        //Texture2Dを作成してスクリーンショットを読み込む
        Texture2D tex = new Texture2D(0, 0);
        //スクリーンショットをTexture2Dに読み込む
        tex.LoadImage(image);
        //スクリーンショットを表示するRawImageを取得
        RawImage target = targetImage.GetComponent<RawImage>();
        //スクリーンショットを表示
        target.texture = tex;
    }
    
    //UIの表示状態を変更
    private void UIStateChange(bool hide){
        if (isNotScreenShotListEmpty){
            return;
        }
        foreach (GameObject obj in notScreenShot){
            obj.SetActive(!hide);
        }
    }
}

#if UNITY_EDITOR
//スクリプトの説明をインスペクターに表示
[CustomEditor(typeof(ScreenshotToSpecifyRange))]

//スクリプトの説明をインスペクターに表示
public class ScreenshotToSpecifyRangeEditor : Editor {
    //Inspectorに説明を表示
    public override void OnInspectorGUI() {
        //デフォルトのInspector部分を表示
        base.OnInspectorGUI();
        EditorGUILayout.HelpBox("useRectMask2DをFalseでスクリーンショットを撮り、その後TrueにしてssWidthとssHeightを設定する。", MessageType.Info);
    }
}
#endif

(9)ScrollBack

縦横に自動スクロールする背景のスクリプト「ScrollBack」を作成しましたが、下の記事の方法が遥かに優れています。斜めもいけますし、導入方法も簡単です。

一応、何かに使えるかもしれないので残してはおきます。

using UnityEngine;
using System;

//背景スクロール画像をスクロールさせるスクリプト
//スクロール用画像は2枚同じものを用意
//1枚はX=0,Y=0に配置
//もう1枚は下方向スクロールの場合は、X=0,Y=+heightに配置
//上方向スクロールの場合は、X=0,Y=-heightに配置
//右方向スクロールの場合は、X=-width,Y=0に配置
//左方向スクロールの場合は、X=+width,Y=0に配置
public class ScrollBack : MonoBehaviour
{
    // スクロール方向の選択肢を定義する列挙型
    public enum ScrollDirection { 下, 上, 左, 右 }

    // インスペクターからスクロール方向を選択可能にする
    [Header("スクロール方向")]public ScrollDirection direction = ScrollDirection.下;

    // スクロールの速度
    [Header("スクロール速度")][SerializeField] private float speed = 100.0f;

    // スクロールに使用する RectTransform
    private RectTransform rectTransform;

    // スクロールがリセットされる境界値
    private float boundary;
    
    // スクロールの更新処理を格納するデリゲート
    private Action updateAction;

    private void Start()
    {
        // RectTransform コンポーネントを取得
        rectTransform = GetComponent<RectTransform>();

        // 選択された方向に基づいて境界値を設定
        if (rectTransform != null)
        {
            switch (direction) {
                case ScrollDirection.下:
                case ScrollDirection.上:
                    boundary = rectTransform.sizeDelta.y;
                    break;
                case ScrollDirection.左:
                case ScrollDirection.右:
                    boundary = rectTransform.sizeDelta.x;
                    break;
            }

            switch (direction) {
                case ScrollDirection.下:
                    updateAction = UpdateVerticalDown;
                    break;
                case ScrollDirection.上:
                    updateAction = UpdateVerticalUp;
                    break;
                case ScrollDirection.左:
                    updateAction = UpdateHorizontalLeft;
                    break;
                case ScrollDirection.右:
                    updateAction = UpdateHorizontalRight;
                    break;
            }
        }
    }

    // Update メソッドを使ってスクロール処理を行う
    private void Update() {
        updateAction?.Invoke();
    }
    
    private void UpdateVerticalDown() {
        Vector2 pos = rectTransform.anchoredPosition;
        pos.y -= speed * Time.deltaTime;
        if (pos.y < -boundary) pos.y += boundary * 2;
        rectTransform.anchoredPosition = pos;
    }
    
    private void UpdateVerticalUp() {
        Vector2 pos = rectTransform.anchoredPosition;
        pos.y += speed * Time.deltaTime;
        if (pos.y > boundary) pos.y -= boundary * 2;
        rectTransform.anchoredPosition = pos;
    }
    
    private void UpdateHorizontalLeft() {
        Vector2 pos = rectTransform.anchoredPosition;
        pos.x -= speed * Time.deltaTime;
        if (pos.x < -boundary) pos.x += boundary * 2;
        rectTransform.anchoredPosition = pos;
    }
    
    private void UpdateHorizontalRight() {
        Vector2 pos = rectTransform.anchoredPosition;
        pos.x += speed * Time.deltaTime;
        if (pos.x > boundary) pos.x -= boundary * 2;
        rectTransform.anchoredPosition = pos;
    }
}
 


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