節電鯖民の遊び場
http://w.atwiki.jp/mcforum/
節電鯖民の遊び場
ja
2022-10-23T00:15:39+09:00
1666451739
-
Unity:クリッカーゲームを作ってみたい/11-Windows向け or WEB GL 向けにビルド、公開する
https://w.atwiki.jp/mcforum/pages/103.html
***目次
#contents(,fromhere=true)
* できたもの
プレイアブル:Github Page 公開ページ
https://ecolight15.github.io/akane_clicker_web/
&youtube(https://youtu.be/e4gqqadQAyI){425,350}
*前提
-[[Unity:クリッカーゲームを作ってみたい/10-もっと音を増やしたい]]の続き
*Windows向けビルド
#region(remember,詳細を開く)
** プロジェクト設定
- Edit → Project Settings... でプロジェクト設定ウインドウを開く
&ref(11_01_project_settings.png)
- Player から Company Name, Product Name, Version 等設定する
- Resolution and Presentation
-- 必要に応じて Fullscreen Mode は解除し、Windowed に変更する。
-- 解像度設定も入力する
-- ウインドウ枠を掴んで任意のサイズに変更可能にする場合には Resizable Window のチェックを入れる
-- Alt + Enter 等でのフルスクリーン切り替えを許可する場合は「Allow Fullscreen Switch」のチェックを入れる
&ref(11_02_windows_settings.png,,width=800)
- Splash Image
-- ゲーム開始時に表示されるメーカーロゴ等の設定を実施する
-- Preview ボタンを押すと動作を確認できる
-- Logos で任意のロゴが追加できる。
--- Unity のロゴは、無償版では削除不可
** ビルドする
- File → Build Settings... でビルド設定を開く
&ref(11_03_windows_build.png,,width=300)
- PC, Mac & Linux Standalone にユニティアイコンが付いていることをチェック
-- ついていない場合は、右下にSwitch Platform ボタンが表示されているので押して切り替える
- 上部のシーン一覧で必要なシーンを全て選択
- Build And Run ボタンでフォルダ選択ダイアログが表示されるので、一式が格納される空ディレクトリを選択する
-- アプリケーションの作成と実行確認ができる
&ref(11_04_windows_build_run.png,,width=300)
#endregion()
*WebGL向けビルド
#region(remember,詳細を開く)
** プロジェクト設定
- 概ねWindowsアプリビルドの方法と同じなので省略
- Project Settings の WebGL のタブで WebGL 向けの設定画実施できる
-- Publish Settings
--- Compression Format で圧縮形式の変更ができるが、もしGithub Page で公開する場合、BrotliやGZipに対応していないため Disable にすること。
&ref(11_05_webgl_settings.png,,width=300)
- WebGLのビルド画面でビルドできる
-- Code Optimization で成果物 Size 優先でビルドするかビルド Speed 優先にするか選択できる。
--- プロジェクトの内容によっては Size のほうが速度もサイズも早くて小さくなることもあるとのこと。
&ref(11_06_webgl_build_run.png,,width=300)
#endregion()
*公開方法
- UnityRoom での公開
-- https://unityroom.com/
--- 要ユーザー登録
- Github Page での公開
-- Github にコミットpushするだけで、WEB UI上からプレイできる形で公開が可能
--- Github Page の使い方の詳細はGoogleで検索すること
-- 前述の Project Settings で Compression Format を Disable にしないと動作しなかったのを確認済み。
-- プロジェクトのディレクトリ名は保持したまま、ディレクトリごとアップロードする必要がある
-- index ページの内容はWEB GLプレイヤーの記述を保持すれば書き換えは可能
2022-10-23T00:15:39+09:00
1666451739
-
Unity:クリッカーゲームを作ってみたい/10-もっと音を増やしたい
https://w.atwiki.jp/mcforum/pages/101.html
***目次
#contents(,fromhere=true)
* できたもの
&youtube(https://youtu.be/e4gqqadQAyI){425,350}
*前提
-[[Unity:クリッカーゲームを作ってみたい/09-エンディングを作成する]]の続き
*素材集め
- 今回音声を増やしていくことにする。
- 手持ちの VOICEROID2 琴葉 茜・葵 で作成
-- ライセンス: https://aivoice.jp/character/kotonoha/
-- VOICEROID個人向け商用ライセンス https://www.ah-soft.com/licensee/voice_individual.html
--- 商用ライセンスの要否あたりは参考になるので必読
*ゲームの開始時に茜ちゃんに喋ってもらう
- GameController についていた AudioSource を共用することにする
-- AudioSource のボリュームは 1 に戻しておく
- Click スクリプトに public メンバで ClickSound と StartSound を用意してそれぞれの音声を設定する。
&ref(10_01_start.png,,width=500)
** スクリプトで制御する
- Start関数で起動時にStartSoundを再生する。
- 既存の Click はAudioSourceにつけていた音声ではなく、public 変数のものを使用する。
-- 必要に応じて、PlayOneShot の際にパラメタを増やし volumeScale を指定してボリュームを下げて再生する。
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
using UnityEngine;
public class Click : MonoBehaviour
{
public AudioClip clickSound;
public AudioClip startSound;
GameController gameCtrl;
AudioSource clickAudio;
void Start() {
gameCtrl = GameObject.FindObjectOfType<GameController>();
clickAudio = gameObject.GetComponent<AudioSource>();
clickAudio.PlayOneShot(startSound);
}
void Update() {
if (!gameCtrl.clickable) {
return;
}
if (Input.GetMouseButtonDown(0)) {
// クリック数をインクリメントし、ゲームコントローラー経由で全体に通知する
gameCtrl.IncrementCount();
clickAudio.PlayOneShot(clickSound, 0.5f);
}
}
}
}}
#endregion()
*レベルアップ時に茜ちゃんに喋ってもらう
- AkaneController に SoundSource 及び、喋らせる音声クリップを登録する。
&ref(10_02_levelup.png,,width=500)
** スクリプトで制御する
- エンディングのとき以外のレベルアップ時に PlayOneShot(akaneAudio.clip) でレベルアップ音声を再生する。
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
using UnityEngine;
public class AkaneController : MonoBehaviour, ICounterReceiver
{
public GameObject akane;
Animator anime;
AudioSource akaneAudio;
int prevLevel;
void Start()
{
anime = akane.GetComponent<Animator>();
akaneAudio = gameObject.GetComponent<AudioSource>();
prevLevel = 0;
}
public void UpdateCounter(int count) {
// 数値変動に応じてアニメーションする
anime.SetTrigger("click_cancel");
anime.SetTrigger("click");
int currentLevel = count / 100;
if (prevLevel < currentLevel) {
// LevelUp
anime.SetTrigger("levelup");
prevLevel = currentLevel;
// レベルアップ音声を流します。ただしエンディングの際には重複するのでスキップします。
if (count != 1000) {
akaneAudio.PlayOneShot(akaneAudio.clip);
}
} else if (prevLevel < currentLevel) {
// LevelDown
anime.SetTrigger("leveldown");
prevLevel = currentLevel;
}
}
}
}}
#endregion()
*エンディングを迎えらた茜ちゃんに喋ってもらう
- EndingController に SoundSource 及び、喋らせる音声クリップを登録する。
&ref(10_03_endhing.png,,width=500)
** スクリプトで制御する
- エンディングのとき以外のレベルアップ時に PlayOneShot(akaneAudio.clip) でレベルアップ音声を再生する。
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
using System;
using System.Collections;
using UnityEngine;
public class EndingController : MonoBehaviour, ICounterReceiver
{
public GameObject canvasObject;
Canvas canvas;
Animator anime;
AudioSource endAudio;
void Start()
{
~省略~
endAudio = gameObject.GetComponent<AudioSource>();
}
public void UpdateCounter(int count) {
// 数値変動に応じてアニメーションする
if (count == 1000) {
canvasObject.SetActive(true);
anime.SetTrigger("gameEnd");
endAudio.PlayOneShot(endAudio.clip); // エンディングのアニメーション開始と同時に再生
// 10 秒後に close Ending を再生しエンディング画面を消す
~省略~
}
}}
#endregion()
* カウンタが 5, 15, 25, 35 と1の桁が 5 の場合に、葵ちゃんにランダムな内容を喋ってもらう
- AoiController に AoiVoiceList と AudioSource を持たせ、ランダムで喋らせたい音声クリップを全て登録する。
&ref(10_04_aoi.png,,width=500)
** スクリプトで制御する
- カウンタ更新時に (カウンタ + 5) % 10 == 0 の場合にリストからランダムな音声を再生する。
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AoiController : MonoBehaviour, ICounterReceiver
{
~省略~
GameController gameCntl;
AudioSource aoiAudio;
private void Start() {
gameCntl = GameController.FindObjectOfType<GameController>();
aoiAudio = gameObject.GetComponent<AudioSource>();
}
public void UpdateCounter(int count) {
// 数値変動に応じて葵ちゃんの数を増減させる
AdjustAoiChan(count);
if ((count + 5) % 10 == 0) {
int index = UnityEngine.Random.Range(0, aoiVoiceList.Count);
// 葵ちゃんの登録音声をランダムに発声させる。
aoiAudio.PlayOneShot(aoiVoiceList[index]);
}
}
~省略~
}
}}
#endregion()
2022-10-22T21:43:21+09:00
1666442601
-
Unity:クリッカーゲームを作ってみたい/12-その他トラブルシューティング
https://w.atwiki.jp/mcforum/pages/102.html
***目次
#contents(,fromhere=true)
*表示系の異常
** カメラのSize変更時、またはウインドウサイズ変更時に消えるスプライトがある
- とりあえず、カメラの Clipping Planes を Near:-10000 Far:10000 に拡張して対処した。
-- おそらく本当は Z 座標値とカメラ:スプライトの前後関係あたりが原因な気がしている
** 葵ちゃんの位置がたまに茜ちゃんの前に来たり後ろに来たりする
- 新しく葵ちゃんを作成するときに、茜ちゃんの位置を設定しているのが原因
-- 一旦 Transform に取り出して、z 座標を常に 0 に設定するようにして回避
--- AoiController
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
~省略~
public class AoiController : MonoBehaviour, ICounterReceiver
{
~省略~
public void spawnAoi() {
// prefab からインスタンスを生成
Vector3 pos = posTarget.transform.position;
pos.z = 0;
GameObject aoi = Instantiate(prefab, pos, Quaternion.identity);
~省略~
}
}}
#endregion()
*処理速度の問題
** ロード直後が重い
- 物理演算の際に、大量に一気に増えたオブジェクト同士が干渉しあって計算量が増大している
-- 出現地点をある程度分散したらマシになるかもしれないと思い、x 座標を若干ずらすコードを追加
-- コライダーの有効化を遅延させて有効にすることで緩和(根本解決ではない)
--- あまり遅延が大きいと壁の判定を貫通していく。
--- ウインドウサイズ等壁との距離によっても壁到達までの時間が変わるため、カメラSizeが上がったら長めの遅延となるよう適当な計算式を入れている。
- 他にもインスタンスを予め生成しておき、画面外にプールしておく方法があるとのこと
- AoiController
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
~省略~
public class AoiController : MonoBehaviour, ICounterReceiver
{
~省略~
public void spawnAoi() {
// prefab からインスタンスを生成
Vector3 pos = posTarget.transform.position;
pos.z = 0;
pos.x += (UnityEngine.Random.value - 0.5f); // 若干出現地点を左右に分散させる
GameObject aoi = Instantiate(prefab, pos, Quaternion.identity);
~省略~
// 生み出した葵ちゃんに力を与える
aoiBody.AddForce(direction * prefabSpeed, ForceMode2D.Force);
aoiBody.AddTorque(torque * prefabTorque, ForceMode2D.Force);
// 生成時の負荷軽減のため、接触判定を0.2秒間保留する。
CapsuleCollider2D collider = aoi.GetComponent<CapsuleCollider2D>();
collider.enabled = false;
StartCoroutine(DelayCoroutineSeconds(0.1f * (gameCntl.GetCount() * 0.01f), () => {
collider.enabled = true;
}));
}
private IEnumerator DelayCoroutineSeconds(float interval, Action action)
{
yield return new WaitForSeconds(interval);
action?.Invoke();
}
~省略~
}
}}
#endregion()
2022-10-22T20:31:46+09:00
1666438306
-
Unity:クリッカーゲームを作ってみたい/09-エンディングを作成する
https://w.atwiki.jp/mcforum/pages/100.html
***目次
#contents(,fromhere=true)
* できたもの
&ref(end.gif)
*前提
-[[Unity:クリッカーゲームを作ってみたい/08-音楽や音を鳴らす]]の続き
*素材集め
#region(remember,詳細を開く)
- とりあえずの背景を用意
-- 和風背景 桜1 https://commons.nicovideo.jp/material/nc112046
--- 利用許可範囲:インターネット全体に許可
--- 営利利用:営利利用可能
#endregion()
* エンディング用のキャンバスとコントローラーのオブジェクトを用意する
#region(remember,詳細を開く)
** 画面の構成
- Ending画面を画像のような形式とした。
- この時、キャンバス自体はマウスの判定等の影響が出ないようにプレイ中は無効化しておく。
-- そのため、制御用の EndingController 空オブジェクトを作成し、その中にCanvasを配置した。
&ref(09_01_hierarchy.png,,width=800)
- Canvas 内にはそれぞれ、複数の文字と背景画像、茜ちゃんと葵ちゃんの画像を配置する。
- この時、文字については Canvas Group コンポーネントを付けた image オブジェクトの配下に設置している。
-- これにより、image の Canvas Group コンポーネントの Alpha 値制御により、全ての文字の透明度を変更できる。
&ref(09_02_image.png,,width=500)
** 設定
- Canvas(エンディング用 Canvas)
-- Canvas
--- Render Mode を Screen Space - Camera に設定することで、Canvas Scaler との組み合わせで画面にフィットするように
--- Render Camera にはメインカメラを設定
--- Order in Layer は、全ての手前に来るように他の Canvas や スプライト画像より大きい値に設定する。(これは文字列を表示する Canvas Group の表示オーダーになる)
-- Canvas Scaler
--- 背景画像をスクリーンにフィットさせるため UI Scale Mode を Scale With Screen Size に設定
- 背景画像
-- strech/strech のアンカー設定で、画面を覆う Scale に設定
-- Order in Layer は、他のCanvas や スプライト画像より大きく、かつエンディング用 Canvas の値(文字列用)より小さくする。
- 茜ちゃん/葵ちゃん
-- Order in Layer を一番大きくする。重なり具合によっては文字列より下げても良い。
-- 文字と重ならないようなアンカー設定にする
--- ここでは right/bottom として、どのサイズでも被りづらいように設定した
- Image(Canvas Group)
-- strech/strech の設定
- Image 配下の文字列
-- 左端の真ん中を維持してほしいため、それぞれ strech/middle 設定としている。
** 設定まで完了したら一旦透明化する
- Image は Canvas Group の Alpha を 0 に設定して透明化する
- 背景/茜ちゃん/葵ちゃんのスプライトは Sprite Renderer の Color の色設定から A の値(アルファ値)を 0 (透明)に設定する。
&ref(09_03_alpha.png)
#endregion()
* エンディング画面を表示するアニメーションを作成する
#region(remember,詳細を開く)
** 徐々に表示されたり、文字の位置を変動させるアニメーションを作成
- Canvas を選択して、Animation ウインドウを開いてアニメーションの作成を開始する。
- Ending 画面を徐々に表示するアニメーションを作成する。
-- Image : Canvas Group : Alpha
-- 背景画像/茜ちゃん/葵ちゃんそれぞれの Sprite Renderer : Color の Color.a
-- おまけで文字は少しスライドして動かすため Anchored Position もアニメーションさせている。
-- 今回は使用していないが、茜ちゃん/葵ちゃんを回転アニメーション等させたい場合もこのエンディングアニメーションか、別途回転アニメーションを作成して追加するなどしても良い。
&ref(09_04_anime.png,,width=800)
&ref(ending.gif,,width=600)
** アニメーション制御の設定をする
- アニメーションコントローラーに対して、Animator で Ending に遷移する制御を実施する。
-- 今回は表示後は、Canvas ごと無効化して表示を削除するため、アニメーションは表示のみとしている。
-- Trigger は gameEnd トリガーを作成して設定した。
&ref(09_05_animator.png,,width=600)
** ついでにエンディング画面を消すアニメーションも追加して、制御も追加しておく。
- 省略
-- アルファ値をどんどん上げるアニメーションにしておく
-- ここでは cleseEnding トリガーで消す動作となるようにした。
&ref(09_07_close_ending.png,,width=600)
#endregion()
* エンディングの制御を行う (表示 → 一定時間後に非表示)
#region(remember,詳細を開く)
- 1000 カウントでエンディング画面を 10 秒だけ表示して消す制御を実施する。
- EndingController オブジェクトに EndingController スクリプトを作成してアタッチする。
- EndingController オブジェクトは GameController の Counter Receivers リストに登録しておく。
#highlight(linenumber,csharp){{
using System;
using System.Collections;
using UnityEngine;
public class EndingController : MonoBehaviour, ICounterReceiver
{
public GameObject canvasObject;
Canvas canvas;
Animator anime;
AudioSource endAudio;
void Start()
{
canvas = canvasObject.GetComponent<Canvas>();
anime = canvas.gameObject.GetComponent<Animator>();
endAudio = gameObject.GetComponent<AudioSource>();
}
public void UpdateCounter(int count) {
// 数値変動に応じてアニメーションする
if (count == 1000) {
canvasObject.SetActive(true);
anime.SetTrigger("gameEnd");
// 10 秒後に close Ending を再生しエンディング画面を消す
StartCoroutine(DelayCoroutineSeconds(10f, () => {
anime.SetTrigger("closeEnding");
// その3秒後に canvas ごと無効化しておく
StartCoroutine(DelayCoroutineSeconds(3f, () => {
canvasObject.SetActive(false);
}));
}));
}
}
private IEnumerator DelayCoroutineSeconds(float interval, Action action)
{
yield return new WaitForSeconds(interval);
action?.Invoke();
}
}
}}
#endregion()
* 最後に、Canvas を無効化しておく
&ref(09_06_canvas_disable.png,,width=400)
2022-10-22T19:40:05+09:00
1666435205
-
Unity:クリッカーゲームを作ってみたい/08-音楽や音を鳴らす
https://w.atwiki.jp/mcforum/pages/99.html
***目次
#contents(,fromhere=true)
* できたもの
&youtube(https://youtu.be/TZ1uE9847tA)
*前提
-[[Unity:クリッカーゲームを作ってみたい/07-レベルアップする茜ちゃん]]の続き
*素材集め
#region(remember,詳細を開く)
- クリックした時の音
-- クリック https://commons.nicovideo.jp/material/nc44409
--- 利用許可範囲:インターネット全体に許可
--- 営利利用:営利利用可能
--- その他リンク希望の記述あり
#region(remember,引用文を開く)
HP・ブログ・SNSをお持ちの方は効果音を使用した際、リンクを貼っていただけると嬉しいです。
無料効果音で遊ぼう! https://taira-komori.jpn.org/
Audacityの音付きエフェクト解説・声の加工方法なんかもやってます。
#endregion()
- BGMに
-- 琴葉食品のうたoffvocal https://commons.nicovideo.jp/material/nc278791
--- 利用許可範囲:インターネット全体に許可
--- 営利利用:営利利用可能
- 適当なAssetsのディレクトリに格納しておく
&ref(08_01_sounds.png,,width=500)
#endregion()
*BGMを付ける
#region(remember,詳細を開く)
** 音源の登録
- BackgroundVideoPlayer に音源を持たせることにする
-- Assetsに格納されている音源ファイルを、ヒエラルキーのオブジェクトにドロップすることで、AudioSourceコンポーネントが追加され、Clipとして登録される
-- Play On Awake のチェックで、開始と同時に再生を開始する
-- Loop でBGMとしてなり続けるように設定
-- Volume は小さめで
&ref(08_02_bgm.png,,width=300)
#endregion()
*クリック音を付ける
#region(remember,詳細を開く)
** 音源の登録
- Clickスクリプトのついている GameController に音源を持たせることにする
-- Assetsに格納されている音源ファイルを、ヒエラルキーのオブジェクトにドロップすることで、AudioSourceコンポーネントが追加され、Clipとして登録される
-- Play On Awake のチェックは外すことで、開始時には鳴らないようにする。
-- Loop も外しておく。
-- Volume は半分ぐらい(お好み)
&ref(08_03_click.png,,width=500)
#endregion()
** スクリプトから再生
#region(remember,詳細を開く)
- AudioSource を追加し、クリック検知のタイミングで PlayOneShot を用いて自身についている clip を単発再生する。
#highlight(linenumber,csharp){{
using UnityEngine;
public class Click : MonoBehaviour
{
GameController gameCtrl;
AudioSource audio;
void Start() {
gameCtrl = GameObject.FindObjectOfType<GameController>();
audio = gameObject.GetComponent<AudioSource>();
}
void Update() {
if (!gameCtrl.clickable) {
return;
}
if (Input.GetMouseButtonDown(0)) {
// クリック数をインクリメントし、ゲームコントローラー経由で全体に通知する
gameCtrl.IncrementCount();
audio.PlayOneShot(audio.clip);
}
}
}
}}
#endregion()
2022-10-21T01:05:08+09:00
1666281908
-
Unity:クリッカーゲームを作ってみたい/07-レベルアップする茜ちゃん
https://w.atwiki.jp/mcforum/pages/98.html
***目次
#contents(,fromhere=true)
* できたもの
&ref(levelup_cam.gif)
*前提
-[[Unity:クリッカーゲームを作ってみたい/06-リファクタリング(ソースコードとオブジェクト関係の整理)]]の続き
*レベルアップ/ダウンする茜ちゃんのアニメーションを作る
#region(remember,詳細を開く)
- 茜ちゃんを選択して、Create New Clip.... から新しいアニメーションが追加できる。
&ref(07_01_levelup_anime.png,,width=300)
** レベルアップのアニメーション
&ref(levelup.gif,,width=300)
** レベルダウンのアニメーション
&ref(leveldown.gif,,width=300)
#endregion()
*アニメーションコントローラーに登録する
#region(remember,詳細を開く)
** レイヤーの追加
- 今回はクリックのアニメーションに加算して再生させる
- Animator の Layers を開いた際に表示される + ボタンから、新しいレイヤーを作成する。
-- ここでは LevelUp や LevelDown とする
&ref(07_02_layers.png,,width=500)
** レイヤーの設定
- レイヤーの右端の歯車を押して設定画面を開く
- Weight は 1 にする(ここは中途半端にするといい感じに合成してくれるかも。お好み)
- Blending は Additive を選択
&ref(07_03_layer_settings.png)
** トリガーの設定
- トリガーの設定をする
-- LevelUp時はlevelup、LevelDown時はleveldownトリガーを発生させるように
-- 矢印は該当アニメーションに向かう矢印にトリガーの設定をしたのみで、その他は未調整
&ref(07_04_trigger.png,,width=800)
#endregion()
*AkaneController でレベルアップアニメーションの制御をする
#region(remember,詳細を開く)
- 今回はカウントを 100 で割った値をレベルとし、上がった際にはLevelUpアニメーションを再生し、下がった場合にはLevelDownアニメーションを再生する。
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
using UnityEngine;
public class AkaneController : MonoBehaviour, ICounterReceiver
{
public GameObject akane;
Animator anime;
int prevLevel;
void Start()
{
anime = akane.GetComponent<Animator>();
prevLevel = 0;
}
public void UpdateCounter(int count) {
// 数値変動に応じてアニメーションする
anime.SetTrigger("click_cancel");
anime.SetTrigger("click");
int currentLevel = count / 100;
if (prevLevel < currentLevel) {
// LevelUp
anime.SetTrigger("levelup");
prevLevel = currentLevel;
} else if (prevLevel < currentLevel) {
// LevelDown
anime.SetTrigger("leveldown");
prevLevel = currentLevel;
}
}
}
}}
#endregion()
#endregion()
*概ねレベルアップに合わせて茜ちゃんを大きくする(厳密にはカメラのサイズを大きくする)
#region(remember,詳細を開く)
** 茜ちゃんのオブジェクトをCanvas上配置に変更
- まず、茜ちゃんは画面対比率を固定するため、Canvas 配下に配置するよう変更
&ref(07_05_akane.png)
** カメラ用のControllerスクリプトを追加
- 新しく CameraController を作成し、カメラにコンポーネントとして追加する。
-- CameraController で、カウントの更新に応じてカメラのSizeを変更する。
-- 暫定で適当な値までを設定
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
using System.Collections.Generic;
using UnityEngine;
public class CameraController : MonoBehaviour, ICounterReceiver
{
Camera cam;
Dictionary<int, float> camSizeMap = new Dictionary<int, float>() {
{0, 5.0f},
{25, 7.0f},
{50, 8.0f},
{100, 12.0f},
{200, 15.0f},
{300, 17.0f},
{400, 18.5f},
{500, 20.0f},
{600, 22.0f},
{700, 24.0f},
{800, 26.0f},
{900, 28.0f},
{1000, 30.0f},
{1100, 32.0f},
{1200, 34.0f},
};
float prevsize;
void Start()
{
cam = gameObject.GetComponent<Camera>();
prevsize = 0;
}
public void UpdateCounter(int count) {
float targetSize = 0;
foreach(KeyValuePair<int, float> pair in camSizeMap) {
if (count < pair.Key) {
break;
}
targetSize = pair.Value;
}
if (prevsize != targetSize) {
cam.orthographicSize = targetSize;
prevsize = targetSize;
}
}
}
}}
#endregion()
** MainCamera を GameController のリストにドロップして、カウンタ更新通知対象として登録する。
&ref(07_06_register_camera.png,,width=500)
#endregion()
2022-10-20T23:54:40+09:00
1666277680
-
Unity:クリッカーゲームを作ってみたい/06-リファクタリング(ソースコードとオブジェクト関係の整理)
https://w.atwiki.jp/mcforum/pages/97.html
***目次
#contents(,fromhere=true)
*前提
-[[Unity:クリッカーゲームを作ってみたい/05-マウスオーバーで表示されるメニューとSave&Load機能をつける]]の続き
*概要
&ref(06_01_refactoring.png,,width=800)
- 今後の拡張を見据えて、少し雑になっていたオブジェクト関係を見直し
- それに伴いスクリプトの内容も見直し
-- 茜ちゃん関連の操作は、トップレベルにいる AkaneController オブジェクトに実行させる
-- 葵ちゃん関連の操作は、トップレベルにいる AoiController オブジェクトに実行させる
-- AkaneController/AoiController は GameController からのカウント変更通知を受けて動作させる
*スクリプトを見直す
** ICounterReceiver
- 変更無し
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
public interface ICounterReceiver
{
public void UpdateCounter(int count);
}
}}
#endregion()
** Click
- クリックしたら GameController にインクリメントを依頼するだけの処理に変更
- 茜ちゃんの Click スクリプトコンポーネントは削除し、GameController オブジェクトに Click スクリプトを付け替え。
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
using UnityEngine;
public class Click : MonoBehaviour
{
GameController gameCtrl;
void Start() {
gameCtrl = GameObject.FindObjectOfType<GameController>();
}
void Update() {
if (!gameCtrl.clickable) {
return;
}
if (Input.GetMouseButtonDown(0)) {
// クリック数をインクリメントし、ゲームコントローラー経由で全体に通知する
gameCtrl.IncrementCount();
}
}
}
}}
#endregion()
** GameController
- 本クラスで実施していた葵ちゃん制御関連の関数は、AoiController に移動
- AoiController/AkaneController は本オブジェクトの counterReceivers にインスペクターから登録するよう変更
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
using System.Collections.Generic;
using UnityEngine;
public class GameController : MonoBehaviour
{
public static GameController instance;
// カウンタの通知を受けるオブジェクトのリスト(インスペクターより登録)
public List<GameObject> counterReceivers;
public bool clickable;
int clickCount;
Click click;
void Awake() {
// シーンを跨いでGameControllerがいた場合用にシングルトン実装
if (instance == null) { instance = this; }
else { Destroy(gameObject); }
}
void Start() {
// オブジェクトを永続化
DontDestroyOnLoad(gameObject);
clickable = true;
click = GameObject.FindObjectOfType<Click>();
}
void notifyCount(int count) {
// 指定されたカウント値を、登録オブジェクト全てに通知
foreach (GameObject obj in counterReceivers) {
ICounterReceiver receiver = obj.GetComponent<ICounterReceiver>();
if (receiver != null) {
receiver.UpdateCounter(count);
}
}
}
public void IncrementCount() {
// カウンタをインクリメントして、登録オブジェクト全てに通知
clickCount++;
notifyCount(clickCount);
}
public void SetCount(int count) {
// 設定されたカウンタ値を、登録オブジェクト全てに通知
clickCount = count;
notifyCount(clickCount);
}
public int GetCount() {
// カウンタ値を返却
return clickCount;
}
}
}}
#endregion()
** AkaneController
- 茜ちゃん制御用のクラスを新規作成
- Clickで実施していたアニメーションの制御を本クラスに移動し、UpdateCounter(カウンタ変更通知)で実施するよう変更
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
using UnityEngine;
public class AkaneController : MonoBehaviour, ICounterReceiver
{
public GameObject akane;
Animator anime;
void Start()
{
anime = akane.GetComponent<Animator>();
}
public void UpdateCounter(int count) {
// 数値変動に応じてアニメーションする
anime.SetTrigger("click_cancel");
anime.SetTrigger("click");
}
}
}}
#endregion()
** AoiController
- 葵ちゃんを生成する処理及び数の調整用関数を本クラスに移動
- それに伴い葵ちゃんを生成する位置は、外部から GameObject を登録する事で同座標に出現させるよう変更
- 葵ちゃんは AoiController 配下に生成されるよう変更し、検索も AoiController 配下のオブジェクトをカウントするよう変更
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
using UnityEngine;
public class AoiController : MonoBehaviour, ICounterReceiver
{
public GameObject prefab; // 生成するprefab を簡単に差し替えできるよう public で外部から変更できるようにしておく
public GameObject posTarget; // 葵ちゃんを出現させる地点をオブジェクトで指定する
// 速度は適切な速度を探れるように、publicで外部から変更できるようにしておく
public float prefabSpeed = 1000;
public float prefabTorque = 300;
public void UpdateCounter(int count) {
// 数値変動に応じて葵ちゃんの数を増減させる
AdjustAoiChan(count);
}
public void spawnAoi() {
// prefab からインスタンスを生成
GameObject aoi = Instantiate(prefab, posTarget.transform.position, Quaternion.identity);
Rigidbody2D aoiBody = aoi.GetComponent<Rigidbody2D>();
// 葵ちゃんの親を AoiController に設定する
aoi.transform.SetParent(gameObject.transform);
// 上方向のランダムな方向を決定
Vector2 direction = Random.insideUnitCircle.normalized;
direction.y = Mathf.Abs(direction.y);
// ランダムな回転速度を決定、整数にしたとき2で割り切れない場合は逆回転にする
float torque = Random.value;
if ((int)(torque*100) % 2 == 1) {
torque *= -1;
}
// 生み出した葵ちゃんに力を与える
aoiBody.AddForce(direction * prefabSpeed, ForceMode2D.Force);
aoiBody.AddTorque(torque * prefabTorque, ForceMode2D.Force);
}
void AdjustAoiChan(int target) {
// 今いる葵ちゃんの数をカウント
int now = transform.childCount;
// 今いる人数の方が多い場合は葵ちゃんには消えてもらう
foreach (Transform aoi in transform) {
if (now <= target) {
break;
}
Destroy(aoi.gameObject);
now--;
}
// 今いる人数の方が少ない場合は葵ちゃんを生み出す
while (now < target) {
spawnAoi();
now++;
}
}
}
}}
#endregion()
** CounterTextController
- 変更無し
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
using UnityEngine;
using TMPro;
public class CounterTextController : MonoBehaviour, ICounterReceiver
{
TextMeshProUGUI tmp;
void Start()
{
tmp = gameObject.GetComponent<TextMeshProUGUI>();
}
public void UpdateCounter(int count) {
tmp.text = count + " あおい~";
}
}
}}
#endregion()
** MenuMouseOver
- 変更無し
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
using UnityEngine;
public class MenuMouseOver : MonoBehaviour
{
Animator anime;
GameController gameCtrl;
private void Start() {
gameCtrl = GameObject.FindObjectOfType<GameController>();
anime = gameObject.GetComponent<Animator>();
}
void OnMouseEnter()
{
anime.SetTrigger("open");
gameCtrl.clickable = false;
}
void OnMouseExit()
{
anime.SetTrigger("close");
gameCtrl.clickable = true;
}
}
}}
#endregion()
** TriggerDestroy
- 葵ちゃん削除用の壁の名前を "Dead" で始まるように変更し、削除用の壁に触れたときのみ削除するよう判定を修正
-- これによりメニューパネルの Collider で IsTrigger 設定を有効(接触判定を無効)にしても誤削除がされなくなる
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
using UnityEngine;
public class TriggerDestroy : MonoBehaviour
{
void OnTriggerEnter2D(Collider2D other) {
if (other.name.StartsWith("Dead")) {
Destroy(gameObject);
}
}
}
}}
#endregion()
** Save
- 変更無し
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
using UnityEngine;
using TMPro;
public class Save : MonoBehaviour
{
GameController gameCtrl;
TextMeshProUGUI tmp;
void Start()
{
gameCtrl = GameObject.FindObjectOfType<GameController>();
tmp = GameObject.FindObjectOfType<CounterTextController>().gameObject.GetComponent<TextMeshProUGUI>();
}
public void OnClick() {
PlayerPrefs.SetInt("Count", gameCtrl.GetCount());
PlayerPrefs.Save();
tmp.text += "(せーぶしました)";
}
}
}}
#endregion()
** Load
- GameController に最新の数値を通知するのみの処理に変更 (GameController による全体へのカウンタ更新通知で反映される)
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
using UnityEngine;
using TMPro;
public class Load : MonoBehaviour
{
GameController gameCtrl;
TextMeshProUGUI tmp;
void Start()
{
gameCtrl = GameObject.FindObjectOfType<GameController>();
tmp = GameObject.FindObjectOfType<CounterTextController>().gameObject.GetComponent<TextMeshProUGUI>();
}
public void OnClick() {
// 葵ちゃんの数を調整する
int target = PlayerPrefs.GetInt("Count");
gameCtrl.SetCount(target);
tmp.text += "(ろーどしました)";
}
}
}}
#endregion()
** Reset
- GameController にカウンタ 0 を通知するのみの処理に変更 (GameController による全体へのカウンタ更新通知で反映される)
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
using UnityEngine;
using TMPro;
public class Reset : MonoBehaviour
{
GameController gameCtrl;
TextMeshProUGUI tmp;
void Start()
{
gameCtrl = GameObject.FindObjectOfType<GameController>();
tmp = GameObject.FindObjectOfType<CounterTextController>().gameObject.GetComponent<TextMeshProUGUI>();
}
public void OnClick() {
// 葵ちゃんの数をリセットする
gameCtrl.SetCount(0);
tmp.text += "(りせっとしました)";
}
}
}}
#endregion()
** Clear
- 変更無し
#region(remember,詳細を開く)
#highlight(linenumber,csharp){{
using UnityEngine;
using TMPro;
public class Clear : MonoBehaviour
{
TextMeshProUGUI tmp;
void Start()
{
tmp = GameObject.FindObjectOfType<CounterTextController>().gameObject.GetComponent<TextMeshProUGUI>();
}
public void OnClick() {
PlayerPrefs.DeleteKey("Count");
tmp.text += "(せーぶをクリアしました)";
}
}
}}
#endregion()
*オブジェクトの関連付け等を見直す
** オブジェクト名の変更と位置変更
- 葵ちゃんを削除する機能を持った壁類はDeadで始まるように変更
-- それ以外は任意
- AkaneController/AoiController を追加
- 茜オブジェクトは AkaneController 配下に変更 ※葵ちゃんもAoiController配下に作成される
#region(remember,詳細を開く)
&ref(06_02_object_name.png)
#endregion()
** 各種設定変更
*** パネルと葵ちゃんの接触判定を削除 + メニューマウスオーバーが葵ちゃんを誤検出しないよう修正
- IsTriggerの有効化
-- これによりメニューと葵ちゃんが接触しなくなるよう変更
- 葵ちゃんのPrefabを Ignore Raycast レイヤーに変更
-- これによりメニューと重なってもマウスオーバーを誤検出しなくなる
#region(remember,詳細を開く)
&ref(06_03_panel.png)
&ref(06_07_ignore_raycast.png)
#endregion()
*** GameController のスクリプト設定
- リストに AkaneController と AoiController のオブジェクトを追加
-- これにより茜ちゃん葵ちゃん関連の処理は、カウンタ値の更新通知で駆動するように変更
- 茜ちゃんに付けていた Click スクリプトを剥がして本オブジェクトに付け替え(茜ちゃんと言うより画面のクリックなので)
#region(remember,詳細を開く)
&ref(06_04_game_controller.png)
#endregion()
*** AoiController にクリック時に出現させる葵ちゃんのPrefabと出現位置を登録
#region(remember,詳細を開く)
&ref(06_05_aoi_controller.png)
#endregion()
*** AkaneController に茜ちゃんのオブジェクトを登録
- 配下にいるので検索できなくも無いが処理削減
#region(remember,詳細を開く)
&ref(06_06_akane_controller.png)
#endregion()
2022-10-20T22:38:37+09:00
1666273117
-
Unity:クリッカーゲームを作ってみたい/05-マウスオーバーで表示されるメニューとSave&Load機能をつける
https://w.atwiki.jp/mcforum/pages/96.html
***目次
#contents(,fromhere=true)
* できたもの
&ref(load.gif)
*前提
-[[Unity:クリッカーゲームを作ってみたい/04-葵ちゃんが堆積するようにする]]の続き
*素材集め
#region(remember,詳細を開く)
- メニューウインドウを作るにあたって、枠を付けてみる
-- 洋風フレーム・装飾 A1 https://commons.nicovideo.jp/material/nc121822
--- 利用許可範囲:インターネット全体に許可
--- 営利利用:営利利用可能
--- その他付加細則あり
--- 素材のフレームのうち、角の部分を切り抜きAssetsに追加しておく。
#endregion()
*マウスを載せたら展開するメニューを作る
#region(remember,詳細を開く)
** パネルの配置
- Canvas にウインドウになる Panel を配置する。
-- 色を変更する場合は、インスペクターから Image → Color で変更する
- 画面右側に少しだけ見えていて、マウスを載せたら展開するため左端に素材のフレームを載せる。
-- 一つは Sprite Renderer の設定でY軸反転して載せる。スケールをマイナス値にしても良い。
- 今後の拡張性を見越して、メニュー内部でスクロールができるよう UI → Scroll View を設置する
- Scroll View の Viewport → Content にスクロールさせたい文字やボタンを配置する
-- ここでは「めにゅー」の UI → Text (TextMeshPro) と UI → Button(TextMeshPro)
&ref(05_01_panel.png,,width=800)
** アンカーの設定はお好み
- Game 画面に見えている状態(パネルを展開した想定の位置)でウインドウサイズを変更してみて挙動を確認
- ここでの設定は以下
-- Panel は center/middle
-- 縁の素材は left/top と left/bottom
-- Scroll View / Viewport / Content は stretch/stretch
-- "めにゅー" の Text (TMP) は left/top
-- Save Button / Load Button と中の Text(TMP) は stretch/stretch
** 表示順序の制御
- カメラに映る表示順序は Canvas や素材 Sprite の Order in Layer の数値で調整する
** その他の設定
- 他にも細かい挙動をインスペクターで設定する
- Button テキストの Auto Size
- Scroll View の Scroll Rect → Movement Type でスクロール挙動の調整等
** 展開/格納アニメーションを作る
- デフォルトのパネルの位置は格納している状態にする
- Panel を対象に、まずは展開アニメーションを作成する
-- 具体的な操作は [[Unity:クリッカーゲームを作ってみたい/01-茜ちゃんをクリックしてアニメーションさせる]] の回を参照のこと
--- 展開アニメーションなので、始点は格納された位置、終点は展開した位置になるようなアニメーションにする。
--- ここでは右端から左方向に展開するので Rect Transform の Anchored Position の x 座標を変化させる
&ref(open.gif,,width=800)
- 続けて格納アニメーションを作成する
&ref(close.gif,,width=800)
- Panel の Animator で 初期状態を空のState (ここでは Close に名前を変更している) に繋ぎ、その後 展開アニメーション → 格納アニメーション → 展開…となるように矢印を繋ぐ。
-- アニメーションの箱は Assets からドロップで追加できる
- Close からパネルの展開アニメーションに向かう矢印の設定は、open トリガー契機で、Has Exit Time 及び Fixed Duration のチェックは外し、Transition Duration の値は 0.5 、Interruption Source は Next に設定。
&ref(05_02_animator_close_to_panel_open.png,,width=500)
- パネルの展開アニメーションからパネルの格納アニメーションに向かう矢印の設定は、close トリガー契機で、Has Exit Time 及び Fixed Duration のチェックは外し、Transition Duration の値は 0.5 、Interruption Source は Next に設定。
&ref(05_03_animator_panel_open_to_panel_close.png,,width=500)
- パネルの格納アニメーションからパネルの展開アニメーションに向かう矢印の設定は、open トリガー契機で、Has Exit Time 及び Fixed Duration のチェックは外し、Transition Duration の値は 0.5 、Interruption Source は Next に設定。
&ref(05_04_animator_panel_close_to_panel_open.png,,width=500)
** パネルにマウスを載せたら展開、パネルからマウスを外したら格納されるスクリプトを設定する
- パネルに Box Collider 2D を付け、コライダーのサイズをパネルと同じサイズに設定する。
- マウス検知制御用のスクリプトを付ける。
&ref(05_05_collider_script.png,,width=800)
*** マウス検知をしてトリガー制御するスクリプト
- OnMouseEnter と OnMouseExit を実装する。
-- Enter はマウスが乗ったとき、Exit はマウスが離れた時の動作を記述する。
#highlight(linenumber,csharp){{
using UnityEngine;
public class MenuMouseOver : MonoBehaviour
{
Animator anime;
private void Start() {
anime = gameObject.GetComponent<Animator>();
}
void OnMouseEnter()
{
anime.SetTrigger("open");
}
void OnMouseExit()
{
anime.SetTrigger("close");
}
}
}}
- 他にもRaycastを使用して検知する方法等も存在する
-- 検索キーワード &link2("unity マウスオーバー Raycast",https://www.google.co.jp/search?q=unity+%E3%83%9E%E3%82%A6%E3%82%B9%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC+Raycast,title="unity マウスオーバー Raycast",target=blank)
#endregion()
* セーブとロードのボタンを制御する
** メニュー操作をする際には葵ちゃんが増えないようにする
#region(remember,詳細を開く)
- セーブおよびロードをクリックした際に通常のクリック制御が実行されないようにフラグ制御する
- GameController に通常クリック可否のフラグを設ける
-- 単純なフラグ制御なので、public bool メンバにして直接設定/参照できるようにしておく
--- ここでは public bool clickable とし、初期値は true (クリック可) にする。
#highlight(linenumber,csharp){{
public class GameController : MonoBehaviour
{
public static GameController instance;
// カウンタの通知を受けるオブジェクトのリスト(インスペクターより登録)
public List<GameObject> counterReceivers;
int clickCount;
public bool clickable;
void Awake() {
~省略~
void Start() {
// オブジェクトを永続化
DontDestroyOnLoad(gameObject);
clickable = true;
}
~省略~
}}
- メニューを展開した場合には clickable を false に、格納したときは true に制御する。
#highlight(linenumber,csharp){{
public class MenuMouseOver : MonoBehaviour
{
Animator anime;
GameController gameCtrl;
private void Start() {
gameCtrl = GameObject.FindObjectOfType<GameController>();
anime = gameObject.GetComponent<Animator>();
}
void OnMouseEnter()
{
anime.SetTrigger("open");
gameCtrl.clickable = false;
}
void OnMouseExit()
{
anime.SetTrigger("close");
gameCtrl.clickable = true;
}
}
}}
- 画面クリック時に、clickable が false の場合には何もしないで return で終了するようにする。
-- これにより、メニューを展開している間は葵ちゃんが増えなくなる
#highlight(linenumber,csharp){{
public class Click : MonoBehaviour
{
~省略~
void Update() {
if (!controller.clickable) {
return;
}
if (Input.GetMouseButtonDown(0)) {
anime.SetTrigger("click_cancel");
anime.SetTrigger("click");
~省略~
}}
#endregion()
** カウンタ値を制御する共通処理を実装する
#region(remember,詳細を開く)
- GameController が保持するカウンタ値を設定/取得できる関数を追加する
-- 設定時にはカウンタ値を監視している機能に通知をする必要がある
--- 既存のカウンタ値をインクリメントする機能と共通化するため、通知処理はnotyfyCount関数として分離し、設定やインクリメント時に呼び出すように変更する。
#highlight(linenumber,csharp){{
~省略~
void Start() {
// オブジェクトを永続化
DontDestroyOnLoad(gameObject);
clickable = true;
}
void notifyCount(int count) {
// 指定されたカウント値を、登録オブジェクト全てに通知
foreach (GameObject obj in counterReceivers) {
ICounterReceiver receiver = obj.GetComponent<ICounterReceiver>();
if (receiver != null) {
receiver.UpdateCounter(count);
}
}
}
public void IncrementCount() {
// カウンタをインクリメントして、登録オブジェクト全てに通知
clickCount++;
notifyCount(clickCount);
}
public void SetCount(int count) {
// 設定されたカウンタ値を、登録オブジェクト全てに通知
clickCount = count;
notifyCount(clickCount);
}
public int GetCount() {
// カウンタ値を返却
return clickCount;
}
}
}}
#endregion()
** セーブの動作を実装する
#region(remember,詳細を開く)
- Save用のスクリプトを作成し、Save用の Button オブジェクトにコンポーネント追加する。
- Save用のスクリプトを記述する
-- ここでは、簡易な保存方法として PlayerPrefs への保存/読み込みを実施する。
-- セーブを実施した旨は、カウンタ表示用テキストに追記する
#highlight(linenumber,csharp){{
using UnityEngine;
using TMPro;
public class Save : MonoBehaviour
{
GameController gameCtrl;
TextMeshProUGUI tmp;
void Start()
{
gameCtrl = GameObject.FindObjectOfType<GameController>();
tmp = GameObject.FindObjectOfType<CounterTextController>().gameObject.GetComponent<TextMeshProUGUI>();
}
public void OnClick() {
PlayerPrefs.SetInt("Count", gameCtrl.GetCount());
PlayerPrefs.Save();
tmp.text += "(せーぶしました)";
}
}
}}
- ボタンクリックで OnClick() 関数が呼び出されるようにインスペクターで設定する。
-- ボタンのイベントを + ボタンで増やし、オブジェクトとしてヒエラルキーウインドウから Save用の Button をドラッグしてきてドロップする。
-- 呼び出される関数として、Save スクリプトの OnClick() を選択する。
&ref(05_06_button_event.png)
#endregion()
** ロードの動作を実装する
#region(remember,詳細を開く)
- 葵ちゃんの数を調整するのは、今後のことも考慮してGameControllerに実施してもらう。
-- GameController に Click クラスを保持するよう修正
-- public の AdjustAoiChan() 関数として葵ちゃんの数を調整する関数を作成
--- 葵ちゃんの数調整と合わせて登録クラスへの通知も実施する(表示も変更される)
#highlight(linenumber,csharp){{
using System.Collections.Generic;
using UnityEngine;
public class GameController : MonoBehaviour
{
public static GameController instance;
// カウンタの通知を受けるオブジェクトのリスト(インスペクターより登録)
public List<GameObject> counterReceivers;
public bool clickable;
int clickCount;
Click click;
~省略~
void Start() {
// オブジェクトを永続化
DontDestroyOnLoad(gameObject);
clickable = true;
click = GameObject.FindObjectOfType<Click>();
}
~省略~
public int GetCount() {
// カウンタ値を返却
return clickCount;
}
public void AdjustAoiChan(int target) {
// 今いる葵ちゃんの数をカウント
GameObject[] aois = GameObject.FindGameObjectsWithTag("Aoi");
int now = aois.Length;
if (now > target) {
// 今いる人数の方が多い場合
while (now > target) {
// 葵ちゃんには消えてもらう
Destroy(aois[now - 1]);
now--;
}
} else {
// 今いる人数の方が少ない場合
while (now < target) {
click.spawnAoi();
now++;
}
}
// 終わったら登録オブジェクトに通知
SetCount(target);
}
}
}}
- Load用のスクリプトを作成し、Load用の Button オブジェクトにコンポーネント追加する。
- Load用のスクリプトを記述する
-- PlayerPrefs に保存していた情報を読み込み、GameControllerへ葵ちゃんの数調整を依頼。
-- ロードを実施した旨は、カウンタ表示用テキストに追記する
#highlight(linenumber,csharp){{
using UnityEngine;
using TMPro;
public class Load : MonoBehaviour
{
GameController gameCtrl;
TextMeshProUGUI tmp;
void Start()
{
gameCtrl = GameObject.FindObjectOfType<GameController>();
tmp = GameObject.FindObjectOfType<CounterTextController>().gameObject.GetComponent<TextMeshProUGUI>();
}
public void OnClick() {
// 葵ちゃんの数を調整する
int target = PlayerPrefs.GetInt("Count");
gameCtrl.AdjustAoiChan(target);
tmp.text += "(ろーどしました)";
}
}
}}
#endregion()
** おまけ:リセットボタンとセーブのクリアボタンを実装する
#region(remember,詳細を開く)
- リセットボタンとセーブのクリアボタンを配置する。
-- 設置方法やスクリプトの付け方はセーブやロードと同じ。
&ref(05_07_reset_clear.png,,width=500)
- リセットボタン
#highlight(linenumber,csharp){{
using UnityEngine;
using TMPro;
public class Reset : MonoBehaviour
{
GameController gameCtrl;
TextMeshProUGUI tmp;
void Start()
{
gameCtrl = GameObject.FindObjectOfType<GameController>();
tmp = GameObject.FindObjectOfType<CounterTextController>().gameObject.GetComponent<TextMeshProUGUI>();
}
public void OnClick() {
// 葵ちゃんの数をリセットする
gameCtrl.AdjustAoiChan(0);
tmp.text += "(りせっとしました)";
}
}
}}
- セーブのクリアボタン
#highlight(linenumber,csharp){{
using UnityEngine;
using TMPro;
public class Clear : MonoBehaviour
{
TextMeshProUGUI tmp;
void Start()
{
tmp = GameObject.FindObjectOfType<CounterTextController>().gameObject.GetComponent<TextMeshProUGUI>();
}
public void OnClick() {
PlayerPrefs.DeleteKey("Count");
tmp.text += "(せーぶをクリアしました)";
}
}
}}
#endregion()
2022-10-19T02:11:23+09:00
1666113083
-
Unity:クリッカーゲームを作ってみたい/04-葵ちゃんが堆積するようにする
https://w.atwiki.jp/mcforum/pages/95.html
***目次
#contents(,fromhere=true)
* できたもの
&ref(wall.gif)
*前提
-[[Unity:クリッカーゲームを作ってみたい/03-背景とカウンタを用意する]]の続き
* 葵ちゃんが堆積する地面を用意する
#region(remember,詳細を開く)
- 画面は可変サイズとする
- そこで、画面にスナップしているCanvasを利用する
-- ヒエラルキーからCanvasに対して 2D Object の Sprites → Square を追加する
&ref(04_02_square.png,,width=500)
- Square を画面外下部少し離した位置に設置し、Scale を X=10000, Y=100 に設定し横に引き伸ばす
- 更に Polygon Collider 2D コンポーネントを追加する
- 画面下部との相対位置を維持するよう Rect Transform の Anchor Presets 設定で center/bottom を選択する
- 以上の設定で葵ちゃんが堆積するように
&ref(04_01_collider.png,,width=800)
#endregion()
* 葵ちゃんが積もるように壁を用意する
#region(remember,詳細を開く)
- 地面をコピーして Scale を X=100, Y=10000 にして、両側に配置する。
- 左側の壁の Anchor Presets は left/middle 設定にする
- 右側の壁の Anchor Presets は right/middle 設定にする
&ref(04_03_wall.png,,width=800)
#endregion()
* 負荷軽減のため壁を貫通または上限に達した葵ちゃんを送還する
#region(remember,詳細を開く)
- 白い壁と同じ要領で更に外側に赤い壁を作成し、それにぶつかった葵ちゃんは消えるようにする。
-- Sprite Renderer の Color でスプライト画像の色を変更し赤くしておく。
-- 衝突検知のため、壁の Polygon Collider 2D の Is Trigger のチェックを入れておく。
--- これにより、Is Trigger 設定された壁はモノがぶつかることは無くなる代わりにScriptから検知が可能になる。
-- 上側の天井は Anchor Presets を center/top にする
&ref(04_04_destroywall.png,,width=800)
- 葵ちゃんにTriggerを検知して、自身のインスタンスを削除するスクリプトをつける
-- Prefab を選択して作成したScriptをコンポーネント追加する。
--- Prefab のインスペクター表示した状態でスクリプトをドロップしても良い
&ref(04_05_prefab_update.png,,width=500)
#highlight(csharp,linenumber){{
using UnityEngine;
public class TriggerDestroy : MonoBehaviour
{
void OnTriggerEnter2D(Collider2D other) {
Destroy(gameObject);
}
}
}}
#endregion()
2022-10-18T01:42:47+09:00
1666024967
-
Unity:クリッカーゲームを作ってみたい/03-背景とカウンタを用意する
https://w.atwiki.jp/mcforum/pages/94.html
***目次
#contents(,fromhere=true)
* できたもの
&ref(counter.gif)
*前提
-[[Unity:クリッカーゲームを作ってみたい/02-茜ちゃんをクリックしたら葵ちゃんを散らせる]]の続き
*素材集め
#region(remember,詳細を開く)
- 権利を確認しながら素材を集める。
- とりあえず何らかの背景素材
-- 和風なループする背景(ピンクの花柄) https://commons.nicovideo.jp/material/nc149772
--- 利用許可範囲:インターネット全体に許可
--- 営利利用:営利利用可能
- あとカウンタ表示にデフォルトのフォントはイマイチな気がしたのでフォントを探してimportしておく。
-- Selected U3D Japanese Font
--- https://assetstore.unity.com/packages/2d/fonts/selected-u3d-japanese-font-337
--- License agreement: Standard Unity Asset Store EULA https://unity.com/legal/as-terms
--- Extension Asset: One license required for each individual user.
--- ベースはM+Fontでreadme/license文書が同梱 https://mplusfonts.github.io/
#endregion()
* 背景映像を貼りつける
#region(remember,詳細を開く)
-ヒエラルキーで VideoPlayer を追加
&ref(03_01_video_player.png,,width=300)
- 背景videoは適当にAssetsに入れてあるものとする
&ref(03_02_video.png)
- video playerに背景videoファイルを登録して各種設定を実施
-- Video Clip: Assetのvideoファイルをドロップして登録
-- Loop: ループ素材なのでチェックを入れる
-- Render Mode: 背景として流すので"Camera Far Plane" を選択
-- Camera: ヒエラルキーの Main Camera をドロップして登録する
-- Aspect Ratio: 画面の縦横サイズが変わっても背景映像以外が映らないよう"Fit Outside"にしておく。
-- Scene再生ボタンを押して映像が背景に流れていればOK
&ref(03_03_video_settings.png)
#endregion()
* クリックした回数を表示するカウンタを実装する
** 仕様
#region(remember,詳細を開く)
- カウンタは画面左上に配置するものとする。
- 画面サイズが変更されても同じ位置にあって欲しい。
- クリックしたらクリック回数の数値を更新表示する。
#endregion()
** キャンバスとテキストラベルを配置する
#region(remember,詳細を開く)
*** キャンバスの配置
- まずはヒエラルキーにCanvasを設置する
-- これにより、カメラ(初期設定ではdisplay)に追従するUIの設置場所ができる
&ref(03_04_canvas.png,,width=300)
- 初期設定のままでもUI設置は可能だが、設置UIがカメラ座標に追従するよう設定を変更する。
-- Canvas設定の Render Mode を "Screen Space - Camera" に変更する。
-- Render Camera に ヒエラルキーから Main Camera をドロップして登録する。
-- これにより設置UIにColliderをつけた場合にUI以外のオブジェクトとの接触判定が取れるようになる。
--- ※Canvas上でのColliderは次回利用する
&ref(03_05_canvas_settings.png,,width=800)
*** テキストラベルの配置
- クリック数カウントを表示するテキストオブジェクトをCanvasに設置する
-- ヒエラルキーから&bold(){Canvasを右クリック}してUIのTextMeshProを追加する。
--- 見た目を弄りたいだけなので、ただのTextでも良い
--- TextMeshPro使用する場合は TMP Importer が表示されるので、Import TMP Essentials ボタンでインポートする。
&ref(03_06_text_mesh_pro.png,,width=300)
*** TextMeshProを使用する場合の追加作業
- フォントを変換してTextMeshProで利用できる形式にする
-- UIで通常のTextを使う場合はこの作業は不要
#region(remember,詳細を開く)
- メニューから TMP の Font Asset Creator を開く
&ref(03_08_text_font_creator.png,,width=300)
- 表示されたCreatorで、変換したいフォントのttfファイルを選ぶ
- 英数字記号のみ変換する場合はそのまま Generate Font Atlas ボタンを押す
-- このとき元のフォントに含まれていない文字は変換に失敗する(詳細は下部のレポートに表示される)
- それ以外、例えば日本語等が必要な場合には Charactor Set を "Custom Characters" に変更し、表示されたテキストボックスに変換する文字を全て記入しておく。
-- Select Font Asset で適当なFont Assetを選択しておくと、既存の他のFontAssetで対応している文字がCustom Character Listにインポートされるので、それに追加で必要な文字を入れていく。なければ手動で必要な物を入れていく。
-- 変換は同様に Generate Font Atlas ボタンを押すこと。
- ウインドウサイズを広げると、生成後の文字列が表示される。
- 生成した文字がぼやけている場合は Atlas Resolution の数値を上げること。
- Saveを押すと保存ダイアログが表示されるので、Asset/Fonts 等適当なディレクトリを作成して保存する。
&ref(03_09_text_font_gen.png,,width=800)
- メモ
-- Canvas Scaler の UI Scale Mode を Scale With Screen Size 等に変更すると Canvas に配置した各アイテムが画面サイズに応じて自動でスケールする。
-- このとき Text Mesh Pro の文字の周りに文字色に近い色で枠ができてしまう事があるが、これはTMPの Face→Dilate を -1 に、Debug Settings の Sharpness を最大値(1)に設定すると改善するが、文字に対するエフェクトが掛からなくなってしまうので注意
--- Sharpness だけ 1 にして、Dilate を -0.5 ぐらいにすると妥協できるかも
#endregion()
*** テキストラベルの設定
- TextのインスペクターからRect TransformのAnchor Presetsを選択する。
-- 今回はCanvas(Camera)の左上に張り付かせたいのでtop/leftを選択する。
-- これで画面サイズを変更した場合も、テキスト座標は左上を基準に自動で調整される。
&ref(03_07_text_Anchor.png)
- 初期表示するテキストを配置する
-- テキストボックスに表示文字列を入力する
-- 文字数が増えて幅や高さが大きくなった場合、どの方向に拡張されるかはAlignmentの設定で調整すること。
-- Font Asset に Font Asset Creator で作成した Font Asset を指定する。
--- もし通常の Text の場合は Font の設定で ttf ファイル等を選択する
-- Vertex Colot (Textの場合は Color)でフォントの色を指定する。
--- 背景画像や映像を用いている場合は、それにあった色にする。
&ref(03_10_text_settings.png,,width=800)
#endregion()
** テキストを更新するスクリプトを実装する
#region(remember,詳細を開く)
*** 仕様
- 今回はカウンタ値を使用するのはテキストラベルだけであるが、今後カウンタ値を使用する機能が増える事を見越して以下の仕様で実装する。
-- Sceneを跨ぐ永続オブジェクトにゲーム全体で使用する値(今回のカウンタ値など)を持たせる。
--- GameController クラス/オブジェクトとし、ヒエラルキーのルートに配置する。
-- クリックした場合、GameController にカウンタ値のインクリメントを指示する
-- GameController へは public List を用いてカウンタ値を利用するクラスオブジェクトの登録を行う。
--- ※将来的に動的に登録する関数を設けるかどうかは検討する。
-- GameController はカウンタ値を利用するクラスオブジェクト全てに更新値を通知する。
--- ICounterReceiver インタフェースを作成して、GameControllerはリスト管理する。
--- ICounterReceiver インタフェースを持つクラスは 1 オブジェクト 1 クラスまでとする。(インスペクターのリスト表示がGameObjectで登録するため)
--- ICounterReceiver インタフェースは仮想関数として UpdateCounter 関数を持ち、継承先で実装する。
--- GameController は通知の際に、リスト保持する全てのオブジェクトの UpdateCounter 関数を呼び出す。
-- テキストラベルに持たせるクラスは ICounterReceiver インタフェースを実装する CounterTextController クラスとする。
&ref(03_11_class_relation.png,,width=800)
*** ICounterReceiver インタフェース
- UpdateCounter 関数定義を持つ。
#highlight(csharp,linenumber){{
public interface ICounterReceiver
{
public void UpdateCounter(int count);
}
}}
*** CounterTextController クラス
- テキストラベル(Canvasに作成した Text(TMP))にコンポーネントとして登録する。
#highlight(csharp,linenumber){{
using UnityEngine;
using TMPro;
public class CounterTextController : MonoBehaviour, ICounterReceiver
{
TextMeshProUGUI tmp;
void Start()
{
tmp = gameObject.GetComponent<TextMeshProUGUI>();
}
public void UpdateCounter(int count) {
tmp.text = count + " あおい~";
}
}
}}
*** GameController クラス
- ヒエラルキーの最上位の階層に空のオブジェクトを持たせ、本クラスをコンポーネントとして持たせる。
- 起動時に永続化オブジェクトに設定される
- シーンを跨いで2つ目が生成された場合には新しい方は破棄するシングルトン構造。
#highlight(csharp,linenumber){{
using System.Collections.Generic;
using UnityEngine;
public class GameController : MonoBehaviour
{
public static GameController instance;
// カウンタの通知を受けるオブジェクトのリスト(インスペクターより登録)
public List<GameObject> counterReceivers;
int clickCount;
void Awake() {
// シーンを跨いでGameControllerがいた場合用にシングルトン実装
if (instance == null) { instance = this; }
else { Destroy(gameObject); }
}
void Start() {
// オブジェクトを永続化
DontDestroyOnLoad(gameObject);
}
public void IncrementCount() {
// カウンタをインクリメントして、登録オブジェクト全てに通知
clickCount++;
foreach (GameObject obj in counterReceivers) {
ICounterReceiver receiver = obj.GetComponent<ICounterReceiver>();
if (receiver != null) {
receiver.UpdateCounter(clickCount);
}
}
}
}
}}
- GameControllerのインスペクターで、カウンタ値の通知先であるテキストラベルのオブジェクトをリストに登録する。
&ref(03_12_counter_receivers.png,,width=500)
*** Click クラスの変更点
- 前の記事からの増分は GameController の保持とインクリメント依頼の行のみ。
#highlight(csharp,linenumber){{
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Click : MonoBehaviour
{
// 生成するprefab を簡単に差し替えできるよう public で外部から変更できるようにしておく
public GameObject prefab;
// 速度は適切な速度を探れるように、publicで外部から変更できるようにしておく
public float prefabSpeed = 1000;
public float prefabTorque = 300;
Animator anime;
GameController controller;
void Start() {
anime = GetComponent<Animator>();
controller = GameObject.FindObjectOfType<GameController>();
}
void Update() {
if (Input.GetMouseButtonDown(0)) {
anime.SetTrigger("click_cancel");
anime.SetTrigger("click");
// prefab からインスタンスを生成
GameObject aoi = Instantiate(prefab, gameObject.transform.position, Quaternion.identity);
Rigidbody2D aoiBody = aoi.GetComponent<Rigidbody2D>();
// 上方向のランダムな方向を決定
Vector2 direction = Random.insideUnitCircle.normalized;
direction.y = Mathf.Abs(direction.y);
// ランダムな回転速度を決定、整数にしたとき2で割り切れない場合は逆回転にする
float torque = Random.value;
if ((int)(torque*100) % 2 == 1) {
torque *= -1;
}
// 生み出した葵ちゃんに力を与える
aoiBody.AddForce(direction * prefabSpeed, ForceMode2D.Force);
aoiBody.AddTorque(torque * prefabTorque, ForceMode2D.Force);
// クリック数をインクリメントする
controller.IncrementCount();
}
}
}
}}
#endregion()
2022-10-18T01:40:49+09:00
1666024849