はじめに
普段は企画職を中心に学んでいる私が、株式会社GENEROSITY様での臨地実務実習で行わせていただいた、XR技術を使ったサービスの企画発案から価値検証モデル(Minimum Viable Product, 通称:MVP)の開発内容を全3回に分けて記事にさせていただきます。
最後の第3回では、スコアを管理するマネージャーの作成と第1回と第2回に作成したものを改良し、シーンごとに連続性を持たせて一つの作品にしたいと思います。
目次
概要
今回のゴール
環境
実装
準備
スクリプトの作成
実行
まとめ
概要
私は「他人に言いにくい本音を抱えているが、誰にも見られたくない」といった課題を解決するために、本音を一人で解消するためのスマホ向けアプリ「本音バルーン」を作成いたしました。
視覚的なカタルシス(爽快感やストレスの解消)を体験した人がより感じるようにするため、ARを活用したゲーミフィケーション要素を企画に取り入れました。
私が開発したMVPの全体の流れを動画にしたので、ぜひご覧ください!(0.75倍速推奨)
リリースまでは至っておらず、あくまでもMVPであることをご了承ください。
今回のゴール
ScoreManagerスクリプトを作成し、第1回のシーンと第2回のシーンを繋げます。
文字数をスコアカウントに変換し、スコア量の風船を生成します。
環境
OS
Windows11
Unityバージョン
Unity - 2022.3.49fi
テストデバイス
Google Pixel 6(Android 12)
Unity上のSimulatorは実装されていないのでGoogle Pixel 5で行っています
実装
準備
第1回で作成したシーンと第2回で作成したシーンとスクリプトを全て同じプロジェクト内に保存します。
以前までのスクリプトを残しておきたい人は新しくスクリプトを作成してください。(記事では同名で行います。)
スクリプトの作成
ScoreManagerスクリプト
文字数に応じたスコアを加算して保存しておくためのスクリプトです。シングルトン化していることで、シーンを跨いでも削除されることがありません。また、同シーン内で2つ以上存在しないようにするため、既にシーンがスクリプトを保持している場合は削除されるようになっています。
using TMPro; // TextMeshProを使用するために追加
using UnityEngine;
using UnityEngine.Events;
public class ScoreManager : MonoBehaviour
{
public static ScoreManager instance;
private int totalBalloonsCount = 0; // 取得したオブジェクトの総数
private int score;
private void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject); // シーンが切り替わっても破棄しない
OnScoreReset = new UnityEvent(); // イベントを初期化
}
else
{
Destroy(gameObject);
}
}
private void Start()
{
// 初期表示のための更新を行う
UpdateScoreDisplay();
}
private void Update()
{
UpdateScoreDisplay();
}
public void AddScore(int amount)
{
score += amount;
totalBalloonsCount += amount;
PlayerPrefs.SetInt("PlayerScore", score); // スコアを保存
UpdateScoreDisplay(); // スコア追加時に表示を更新
}
public int GetScore()
{
return score;
}
SceneControllerスクリプト
シーンを移動する処理を追加するため、using UnityEngine.SceneManagement;
を追加しました。
using System.Collections;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement; //追加必須
using UnityScreenNavigator.Runtime.Core.Modal;
public class SceneController : MonoBehaviour
{
[SerializeField]
private ModalContainer _modalContainer = null;
public GameObject inputGameObject;
public Button button;
public TMP_InputField inputField;
// テキストの文字数を保存する変数
private int textLength;
// シングルトンインスタンスの設定
public static SceneController Instance { get; private set; }
private void Awake()
{
// インスタンスのセットアップ。既にある場合は削除
if (Instance == null) Instance = this;
else Destroy(gameObject);
}
}
文字数を管理するメソッドにオブジェクト数を指定するメソッドを追加します。InputField
には100字まで入力が可能なので、「~文字ならa~b個」と範囲を区切ることでオブジェクトの生成数をランダムに取得します。
public int CalculateObjectCount(int textLength)
{
if (textLength < 6) // 6文字未満
return 0;
else if (textLength <= 10) // 6~10文字
return Random.Range(10, 15); // 10〜14の範囲
else if (textLength >= 11 && textLength <= 15) // 11~15文字
return Random.Range(15, 20); // 15〜19の範囲
else if (textLength >= 16 && textLength <= 20) // 16~20文字
return Random.Range(20, 25); // 20〜24の範囲
else if (textLength >= 21 && textLength <= 30) // 21~30文字
return Random.Range(25, 30); // 25〜30の範囲
else if (textLength >= 31 && textLength <= 75) // 31~75文字
return Random.Range(30, 35); // 25〜30の範囲
else // 75文字以上
return Random.Range(35, 41); // 25〜30の範囲
}
元シーンのボタンUIメソッドに下記のコードを追加します。先ほど取得したオブジェクトの生成数を計算し、その数だけスコアを増加させます。
private IEnumerator NextButton()
{
//追加
// テキスト量に基づいたオブジェクト数を計算
int objectCount = CalculateObjectCount(textLength);
// ScoreManagerのインスタンスを取得
ScoreManager scoreManager = ScoreManager.instance;
// ObjectCountの数だけスコアを増加させる
if (scoreManager != null)
{
scoreManager.AddScore(objectCount); // ObjectCountの数だけスコアを追加
}
}
OptionSelectorModalスクリプト
スクリプト自体に修正はありませんが、OKボタンにARカメラのシーンを指定します。
ここまでで文字入力のシーンからARカメラのシーンへの移動が行えると思います。
次にARカメラシーンのスクリプトを改良します。
RandomObejectPlacement
ScoreManagerから先ほどカウントした生成数を受け取り、最大生成数内の数だけAR空間に生成します。生成数を受け取っていない場合、デフォルト数だけオブジェクトを生成します。
public class RandomObjectPlacement : MonoBehaviour
{
private const int maxObjects = 30; // 最大オブジェクト数
public void PlaceObjects()
{
// ScoreManagerからスコアを取得してオブジェクト数を設定
ScoreManager scoreManager = ScoreManager.instance;
if (scoreManager != null && scoreManager.GetScore() > 0)
{
int targetObjectCount = Mathf.Min(scoreManager.GetScore(), maxObjects);
int additionalObjects = targetObjectCount - placedObjectPositions.Count; // 追加するオブジェクト数
if (additionalObjects > 0)
{
PlaceObjectsRandomly(additionalObjects);
}
}
else
{
// スコアがない場合はデフォルトで指定された数のオブジェクトを生成
PlaceDefaultObjects();
}
}
public void PlaceDefaultObjects()
{
// デフォルトのオブジェクト数を生成
int additionalObjects = defaultObjectCount - placedObjectPositions.Count;
if (additionalObjects > 0)
{
PlaceObjectsRandomly(additionalObjects);
}
}
ObjectManagerスクリプト
変更なし。
ObjectInteractionスクリプト
オブジェクトが削除されたら、スコアを1減らすコードを追加しました。
public class ObjectInteraction : MonoBehaviour
{
private void HandleObjectInteraction(GameObject target)
{
// effectPrefabsとeffectPrefabs1のそれぞれからランダムにエフェクトを選択し、同時に生成する
int randomIndex = Random.Range(0, effectPrefabs.Count);
int randomIndex1 = Random.Range(0, effectPrefabs1.Count);
GameObject effectPrefab = effectPrefabs[randomIndex];
GameObject effectPrefab1 = effectPrefabs1[randomIndex1];
// エフェクトを生成し、一定時間後に削除
GameObject effectInstance = Instantiate(effectPrefab, target.transform.position, Quaternion.identity);
GameObject effectInstance1 = Instantiate(effectPrefab1, target.transform.position, Quaternion.identity);
Destroy(effectInstance, 2f);
Destroy(effectInstance1, 2f);
// オブジェクトを削除とリスト管理
DestroyObject(target);
ScoreManager.instance.SubtractScore(1); //追加
// ObjectManagerのリストからオブジェクトを削除
objectManager?.RemoveObjectFromList(target);
// ヒットカウントの更新と削除確認
RemoveObjectCount();
}
}
実行
正しく実行されたなら動画のように文字数に応じた数のオブジェクトがARシーン上に生成されると思います。
これにより、今回のゴールであった2つのシーンに連続性を持たせることが出来ました。
まとめ
全回に渡って私の開発したMVPの要素を説明させていただきました。実物はこれらの要素に+α(UXの向上)を加えた、もう少しクオリティが担保された物となっています。(概要の動画をご覧ください)
他にも、オブジェクトが無限に生成される中ひたすら風船を割り続けるモードもボーナス要素で付け加えたり、偽のローディング処理を挟んで実際のアプリっぽさを増したり、特定のフラグを踏むことで現れるモーダルなんかも追加しました。(運営の遊び心を表現したかった)
実習自体はこの開発で終わりとなってしまいましたが、私の行ったことがこのブログとして誰かの目に留まって少しでも役に立つものになれば嬉しいです。