見出し画像

【備忘録】放置ゲームのスクリプト・「転生女騎士アパートに住む【放置ゲーム】」の作り方(4)【Unity】

2024/2/20リリースしました!

↓iOS版

↓GooglePlay版

↓YouTube(PV)

この記事では今回のゲームで使用したスクリプトを紹介します。殆どChatGPTが提出してきたものを修正して使用しています。途中からは有料記事とさせていただきます。

なお今回のプロジェクトには、アセットのEasySave3とOdin Inspector and Serializerを使用しています。


1.単機能系スクリプト

他のプロジェクトでも使える単純なスクリプトです。

(1)TimelineSkipper

タイムラインを最後までスキップします。
長いムービー等をタイムラインで実装する場合は必須だと思います。
ボタンなどに割り当てましょう。
今回のスクリプトではスキップ後にも、セリフが再生されてしまったため、セリフを停止する処理を書いています。

using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.UI;

public class TimelineSkipper : MonoBehaviour
{
    public PlayableDirector director; // タイムラインを制御するPlayableDirector
    
    void Start()
    {
        // ボタンにクリックイベントリスナーを追加
        GetComponent<Button>().onClick.AddListener(SkipToEnd);
    }

    void SkipToEnd()
    {
        // タイムラインの総再生時間を取得
        double duration = director.duration;

        // タイムラインの最後に移動
        director.time = duration;

        //最後のセリフを停止
        SoundManager.i.StopVoice();
    }
}

(2)BlinkText

Textを点滅させます。複雑なことをしたいわけでなければこれで十分かと。

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class BlinkText : MonoBehaviour
{
    private Text textToBlink; // 点滅させたいテキスト
    public float blinkTime = 0.5f; // 点滅の間隔(秒)

    private void Start()
    {
        textToBlink = GetComponent<Text>();
        StartCoroutine(Blink());
    }

    private IEnumerator Blink()
    {
        while (true)
        {
            textToBlink.enabled = false;
            yield return new WaitForSeconds(blinkTime);
            textToBlink.enabled = true;
            yield return new WaitForSeconds(blinkTime);
        }
    }
}

(3)LevelCheker

ゲームの進行度によって表示を切り替えるオブジェクトにアタッチして使います。例えば今作のテレビの上の猫のぬいぐるみはレベル3になると表示されます。これはデフォルトではオブジェクトを表示しておき、レベル2以下のときにアプリを起動すると、このスクリプトで非表示にしています。

using UnityEngine;

public class LevelChecker : MonoBehaviour
{
    // Start is called before the first frame update
    public int level;
    void Start()
    {
        //レベル以下なら非表示
        if (level > GameMgr.i.GetLevel())
        {
            gameObject.SetActive(false);
        }
    }
}

(4)ManualVoice

 今回はマニュアル1ページごとに説明用ボイスが付いています。SoundManagerからボイスを再生すると、ボイス再生中に次のページに移動したときに、ボイスが被って再生されてしまったため、マニュアル1ページごとにAudioSourceと以下のManualVoiceをアタッチしました。
ページがSetActive(true)になるとセリフが再生されますが、次ページに行くときにSetActive(false)になり、AudioSourceも無効になるため、セリフの再生が中断されます。

using UnityEngine;

public class ManualVoice : MonoBehaviour
{
    [Header("表示されたら再生される音声")]public AudioClip voice;
    private AudioSource audioSource;
    private void OnEnable()
    {
        audioSource = GetComponent<AudioSource>();
        audioSource.PlayOneShot(voice);
    }
}

(5)ScrollResetter

スクロールビューのスクロールバーの位置を初期位置に戻すスクリプト。スクロールビューを開くボタンに割り当てて使用。

using UnityEngine;
using UnityEngine.UI;

public class ScrollResetter : MonoBehaviour{
    [Header("位置をリセットするスクロールビューを登録")]public ScrollRect scrollRect;
    //クリックするとスクロールビューが開くボタン等にセット。
    //スクロールビューが開くと同時にSetActiveAndResetScrollを実行。

    void Start()
    {
        GetComponent<Button>().onClick.AddListener(ResetScrollToTop);
    }
    
    public void ResetScrollToTop(){
        // ScrollRectのcontentの位置をリセット
        if (scrollRect != null){
            scrollRect.verticalNormalizedPosition = 1.0f;
        }
    }
}

(6)TimeMoneyFormatter

お金と時間を数値で受け取り、変換して文字列で返します。
例えば、long変数の「100000000」をFormatMoneyで変換すると、文字列の「1億」が返ってきます。
高額のお金を取り扱う、桃鉄のようなゲームで使用するイメージです。

public static class TimeMoneyFormatter {
    public static string FormatMoney(long money) {
        if (money >= 100000000)
        {
            long oku = money / 100000000;
            long man = (money % 100000000) / 10000;
            long nokori = money % 10000;
            if (man == 0&&nokori ==0){
                return string.Format("{0}億", oku);
            }else if(man==0)
            {
                return string.Format("{0}億{1}", oku,nokori);
            }else if (nokori == 0)
            {
                {return string.Format("{0}億{1}万", oku, man);}
            }
            else
            {
                return string.Format("{0}億{1}万{2}", oku, man, nokori);
            }
        }
        else if (money >= 10000)
        {
            long man = money / 10000;
            long nokori = money % 10000;
            if (nokori == 0)
            {
                return string.Format("{0}万", man);
            }
            else
            {
                return string.Format("{0}万{1}", man, nokori);
            }
        }
        else
        {
            return money.ToString();
        }
    }

    public static string FormatWorkTime(int minutes) {
        if (minutes >= 60)
        {
            int hours = minutes / 60;
            int remainingMinutes = minutes % 60;
        
            if (remainingMinutes == 0)
                return $"{hours}時間";
            else
                return $"{hours}時間{remainingMinutes}分";
        }
        else
        {
            return $"{minutes}分";
        }
    }

    //タイマー更新用
    public static string FormatWorkSecond(float seconds)
    {
        // 残り時間のテキストを更新
        if (seconds > 3600) {
            // 時間と分で表示
            int hours = (int)(seconds / 3600);
            int minutes = (int)((seconds % 3600) / 60); 
            return $"{hours}時間{minutes}分";
        } else if (seconds > 60) {
            // 分と秒で表示
            int minutes = (int)(seconds / 60);
            int second = (int)(seconds % 60);
            return  $"{minutes}{second:0}秒";
        } else if (seconds > 1) {
            // 秒のみで表示
            return $"{seconds:0}秒";
        } else {
            return  "1秒";
        }
    }
}

(7)MoneyDisplay

所持金の増減があったときに、所持金の表示を変更するスクリプトです。

using UnityEngine;
using UnityEngine.UI;

public class MoneyDisplay : MonoBehaviour
{
    public Text moneyText; // 所持金を表示するText

    private void Start()
    {
        GameMgr.i.OnMoneyChanged += UpdateMoneyDisplay; // イベントにメソッドを登録
        UpdateMoneyDisplay(); // 初期表示
    }

    private void UpdateMoneyDisplay()
    {
        moneyText.text = TimeMoneyFormatter.FormatMoney(GameMgr.i.GetMoney()) + "円";
    }
}

(8)ParentObjectController

オブジェクトが非アクティブになったときに、配列内のオブジェクトを一緒に非アクティブにするスクリプト。服一覧ウィンドウとBGM一覧ウィンドウにアタッチし、それぞれ非アクティブにすると購入確認ウィンドウも非アクティブになります。

using UnityEngine;

public class ParentObjectController : MonoBehaviour
{
    // 非アクティブにしたいGameObjectの配列
    public GameObject[] panelsToDisable;

    private void OnDisable()
    {
        // 配列内の各GameObjectをループ処理
        foreach (GameObject panel in panelsToDisable)
        {
            if (panel != null)
            {
                panel.SetActive(false);
            }
        }
    }
}

(9)DelayedFadeOutAndDisable

オブジェクトがSetActive(true)になったときから、delayTime秒表示され、fadeOutTIme秒間かけて消えていくスクリプトです。リワード広告が準備できていないときに、「ご馳走する!」ボタンをクリックすると「広告が準備できていないため、ご馳走できません。」と表示されるのですが、そのオブジェクト(Imageと子オブジェクトのText)にアタッチされています。

ご馳走するボタンをクリックしたタイミングで、リワード広告のリクエストもしています。
using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class DelayedFadeOutAndDisable : MonoBehaviour
{
    public float delayTime = 2.0f; // アルファ値の減少を開始するまでの遅延時間(秒)
    public float fadeOutTime = 2.0f; // アルファ値を0にするのにかかる時間(秒)
    private Image imageComponent;
    private Text textComponent;

    void OnEnable()
    {
        // ImageコンポーネントとTextコンポーネントを取得
        imageComponent = GetComponent<Image>();
        textComponent = GetComponentInChildren<Text>();

        // アルファ値の初期値をセット
        SetAlpha(1.0f);

        // フェードアウト処理を遅延後に開始
        StartCoroutine(DelayedFadeOutCoroutine());
    }

    void SetAlpha(float alpha)
    {
        if (imageComponent != null)
        {
            Color imageColor = imageComponent.color;
            imageColor.a = alpha;
            imageComponent.color = imageColor;
        }

        if (textComponent != null)
        {
            Color textColor = textComponent.color;
            textColor.a = alpha;
            textComponent.color = textColor;
        }
    }

    IEnumerator DelayedFadeOutCoroutine()
    {
        // 指定された遅延時間を待つ
        yield return new WaitForSeconds(delayTime);

        float elapsedTime = 0;

        while (elapsedTime < fadeOutTime)
        {
            elapsedTime += Time.deltaTime;
            float newAlpha = Mathf.Lerp(1.0f, 0.0f, elapsedTime / fadeOutTime);
            SetAlpha(newAlpha);
            yield return null;
        }

        // アルファ値が0になったらGameObjectを非アクティブにする
        gameObject.SetActive(false);
    }
}

2.データ系スクリプト

(1)DataResetButton

EasySave3で作成したデータをオールクリアするボタン。一度警告用のウィンドウが表示される仕様。

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class DataResetButton : MonoBehaviour {
    public GameObject confirmWindow; // 確認ウィンドウ
    public Button yesButton; // はいボタン
    public Button noButton; // いいえボタン
    public AudioClip buzzer;//警告音
    public AudioClip ClickSe;//クリック音

    private void Start() {
        // ボタンにリスナーを設定
        GetComponent<Button>().onClick.AddListener(ShowConfirmWindow);
        yesButton.onClick.AddListener(ResetDataAndReload);
        noButton.onClick.AddListener(HideConfirmWindow);
    }

    // 確認ウィンドウを表示
    private void ShowConfirmWindow() {
        SoundManager.i.PlaySe(buzzer);
        confirmWindow.SetActive(true);
    }

    // データをリセットしてシーンを再読み込み
    private void ResetDataAndReload() {
        ES3.DeleteFile();

        // 現在のシーンを再読み込み
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    }

    // 確認ウィンドウを非表示に
    private void HideConfirmWindow() {
        SoundManager.i.StopSe();
        SoundManager.i.PlaySe(ClickSe);
        confirmWindow.SetActive(false);
    }
}

(2)Clothes(enum 列挙型データ)

以下の書き方で、プロジェクトのどこからでもアクセスできます。これだけでは意味を持ちませんが、その他のスクリプトと組み合わせることで、非常に便利になります。enumごとにデータを分けて作成することもできます。

public enum Clothes {
    アーマー,
    パーカー,
    警備員の制服,
    ビジネススーツ,
    レオタード,
    バイクスーツ,
    バニースーツ,
    水着,
    陸上のユニホーム,
    体操着,
    野球のユニホーム,
    パジャマ,
    ゴスロリ服,
    ピンクジャージ,
    オーバーオール,
    学生服,
    作業服,
    ウエイトレスの制服,
    カフェのエプロン,
    チアリーダーの服,
    軍服,
    メイド服,
    チャイナドレス,
}

public enum Bgm {
    落ち着いたジャズ,
    ノスタルジックシティポップ,
    ディスコファンク,
    大草原をイメージした民族音楽,
    クールなシティポップ,
    洗練されたハウスミュージック,
    RPGの村のようなBGM,
    アップテンポジャズ,
    アコースティックギタージャズ,
    トロピカルボサノヴァ,
    ファンキーハウスミュージック,
    マジカルトレイン,
    チルアウトローファイミュージック,
    和風テクノ,
    中国宮廷風音楽,
    エレクトロダンス,
    洋楽ポップス,
    幻想的な女性コーラス,
    アコースティックギターシティポップ,
    エレクトリックシティポップ,
}

public enum SecondJudge
{
    なし,
    服,
    時間,
    月,
    日付,
    食べ物
}

public enum Food
{
    チョコバナナ,
    チョコレート,
    クロワッサン,
    カレーライス,
    フライドポテト,
    ハンバーガー,
    ホットケーキ,
    唐揚げ,
    チョココロネ,
    メロンパン,
    肉まん,
    オムライス,
    桃,
    プリン,
    ラーメン,
    ショートケーキ,
    スパゲッティミートソース,
    寿司,
    うな丼,
    焼きいも,
    なし,
}

(3)FoodData

enum列挙型で作成したFoodにSprite、String、intを紐づけたデータです。

using UnityEngine;
[System.Serializable] // シリアライズ可能にすることで、Unity エディタのインスペクターに表示されるようになります。
public class FoodData
{
    public Food FoodType;//列挙型
    public Sprite Image;//食べ物のイメージ
    public string Name;//食べ物の名前
    public int Chance;//報酬の倍率

    // コンストラクタは必須ではありませんが、必要に応じて追加できます。
    public FoodData(Food foodType, Sprite image, string name, int chance){
        FoodType = foodType;
        Image = image;
        Name = name;
        Chance = chance;
    }
}

(4)GameData

レベル、所持金、服の所持状況、BGMの所持状況、装備中の服、再生中のBGMを一つにまとめたスクリプトです。変更があった際には、他のスクリプトから変数を変更し、保存します。このようにまとめておけば、レベルはLEVELキー、所持金はMONEYキーのように、バラバラなキーを使って保存しなくても済みます(後述)。

using System;
using System.Collections.Generic;

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

    public GameData() {
        OwnedOutfits = new Dictionary<Clothes, bool>();
        OwnedBGMs = new Dictionary<Bgm, bool>();
        //服を全て所有していない状況にする
        foreach (Clothes outfit in Enum.GetValues(typeof(Clothes))) {
            OwnedOutfits.Add(outfit, false);
        }
        
        //BGMを全て所有していない状況にする
        foreach (Bgm bgm in Enum.GetValues(typeof(Bgm))) {
            OwnedBGMs.Add(bgm, false);
            //OwnedBGMs.Add(bgm, true);//テスト用(曲全部所有にする)
        }
        
        Level = 1;//初期レベル
        OwnedOutfits[Clothes.アーマー] = true;//初期所有服
        OwnedBGMs[Bgm.落ち着いたジャズ] = true;//初期所有BGM
        OwnedBGMs[Bgm.ノスタルジックシティポップ] = true;//初期所有BGM
        PlayingBGM = Bgm.落ち着いたジャズ;//初期再生
        EquippedOutfit = Clothes.アーマー;//初期装備
    }
}

(5)GameMgr

GameDataのセーブ、ロードを行うスクリプト。
DataManagerという名前のほうが良かったかもしれないです。
初回やセーブデータ消去後はGameDataがないため、GameDataを作成しています。その際にオープニング用のタイムラインを再生します。

using System;
using UnityEngine;
using UnityEngine.Playables;

public class GameMgr : MonoBehaviour {
    public static GameMgr i; //どこからでもアクセスできるようにする
    private GameData gameData;//ゲームデータ
    public event Action OnMoneyChanged; // 所持金が変更されたときのイベント
    public event Action OnLevelChanged; // レベルが変更されたときのイベント
    public PlayableDirector openingTimeLine;//オープニングのタイムライン
    
    void Awake() {
        CheckInstance();
    }
    void CheckInstance() {
        if (i == null) {
            i = this;
        } else {
            Destroy(gameObject);
        }
    }
    
    private void Start(){
        LoadGameData();
    }

    // データを読み込む
    public void LoadGameData(){
        if (ES3.KeyExists("gameData")) {
            gameData = ES3.Load<GameData>("gameData");
        } else {//セーブデータがない場合
            gameData = new GameData();
            SaveGameData();
            LocalPushNotification.RegisterChannel("channelId", "PushTest", "通知の説明");//プッシュ通知の登録
            //セーブデータがない場合はオープニングのタイムラインを再生
            openingTimeLine.Play();
        }
    }
    
    // データを保存する
    public void SaveGameData() {
        ES3.Save("gameData", gameData);
    }
    
    // 特定の服が所持されているかを確認するメソッド
    public bool IsOutfitOwned(Clothes outfit){
        if (gameData.OwnedOutfits.ContainsKey(outfit)){
            return gameData.OwnedOutfits[outfit];
        }
        return false;
    }

    // 特定のBGMが所持されているかを確認するメソッド
    public bool IsBgmOwned(Bgm bgm){
        if (gameData.OwnedBGMs.ContainsKey(bgm)){
            return gameData.OwnedBGMs[bgm];
        }
        return false;
    }

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

    // BGMを所持リストに追加するメソッド
    public void AddBgmToOwned(Bgm bgm) {
        if (!gameData.OwnedBGMs.ContainsKey(bgm)) {
            gameData.OwnedBGMs.Add(bgm, true);
        }else{
            gameData.OwnedBGMs[bgm] = true;
        }
        SaveGameData();
    }

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

    // 再生中のBGMを変更するメソッド
    public void ChangePlayingBGM(Bgm bgm) {
        gameData.PlayingBGM = bgm;
        SaveGameData();
    }
    
    // 現在装備している服の種類を返すメソッド
    public Clothes GetEquippedOutfit() {
        return gameData.EquippedOutfit;
    }
    
    // 現在再生中のBGMを取得するメソッド
    public Bgm GetPlayingBGM() {
        return gameData.PlayingBGM;
    }
    
    //お金を追加するメソッド
    public void AddMoney(long amount) {
        gameData.Money += amount;
        if (gameData.Money >=9999999999)//99億9999万9999円より多くはならない
        {
            gameData.Money = 9999999999;
        }
        OnMoneyChanged?.Invoke(); // 所持金が変更されたときにイベントを発火
        SaveGameData();
    }

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

    //所持金を取得するメソッド
    public long GetMoney() {
        return gameData.Money;
    }
    
    //レベルをアップするメソッド
    public void LevelUp(){
        if (gameData.Level < 20) // 最高レベルを20に設定{
            gameData.Level++;
            OnLevelChanged?.Invoke(); // レベルが変更されたときにイベントを発火
            SaveGameData();
        }
    }

    // レベルを取得するメソッド
    public int GetLevel() {
        return gameData.Level;
    }
    
    // デバッグ用に所持金を増やすメソッド
    public void AddDebugMoney() {
        AddMoney(100000); // 100,000円を追加
    }
    
    // デバッグ用に所持金を増やすメソッド
    public void AddDebugMoreMoney() {
        AddMoney(100000000); // 100,000円を追加
    }
    
    // デバッグ用にレベルアップするメソッド
    public void DebugLevelUp() {
        LevelUp(); // 既存のレベルアップメソッドを呼び出す
    }
}

3.セリフ関係スクリプト

ここから先は

39,091字

¥ 300

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