- 投稿日:2020-07-26T23:14:41+09:00
シンプルなサウンドマネージャー (Unity)
前提
- Unity 2019.4.5f1
- 使用に際して、C#でスクリプトを書く必要があります。
- C#に触りたくない場合は、Boltでも大丈夫です。
- Bolt Visual Scripting 1.4.12 で確認済み
- UIの操作音など、音源の位置が画面に固定されている場合に適しています。
できること
- BGMとSEの番号を指定して再生できます。
- BGM
- 最大同時再生数は2で固定です。(クロスフェード用)
- あらかじめ、インスペクタで、フェードイン、フェードアウト、インターバルの時間を指定します。
- 再生はループします。
- SE
- あらかじめ、インスペクタで、最大同時再生数を指定します。
- 再生時に、「同じ音でも重ねて鳴らす」、「同じ音が鳴っていたら止めてから鳴らす」、「同じ音が鳴っていない場合だけ鳴らす」ことが選択可能です。
- 再生はループしません。
- BGMとSEの音量を独立して設定できます。
- 全体の一時的なミュートが可能です。
アセットの入手 (GitHub)
ダウンロード ⇒ SoundManager.unitypackage
ソースはこちらです。導入と設定
- プロジェクトにアセットをインポートしてください。
- シーンの適当なオブジェクトに、スクリプト
Sound.cs
をアタッチしてください。- インスペクタで、
Sound Effect Clip
とSound Music Clip
のSizeを必要なだけ増やし、オーディオクリップを設定してください。- 必要に応じてパラメータを調整してください。
項目 説明 初期値 Sound Effect Max SE同時再生数 5 Sound Effect Initial Volume SE初期音量 0.5 Sound Music Initial Volume BGM初期音量 0.5 Sound Music Fade In Time BGMフェードイン時間 0 Sound Music Fade Out Time BGMフェードアウト時間 3 Sound Music Interval Time BGMインターバル時間 (フェードアウト時間が0でなく負数なら重なる) 0 Sound Effect Clip SEオーディオクリップ - Sound Music Clip BGMオーディオクリップ - 使い方
SEを再生する
同じSEでも重ねて鳴らす
Sound.Effect = number;
- 空きのチャネル(AudioSource)で再生します。
- 空きがない場合は、最も古くに再生開始されたチャネルの再生を止めて使います。
同じSEが鳴っていたら止めてから鳴らす
Sound.StopAndEffect = number;
- 以下と同じ処理です。
Sound.EffectStop = number; Sound.Effect = number;同じSEが鳴っていない場合だけ鳴らす
Sound.EffectIfNot = number;
- 既に同じ音を再生中であれば、新たに再生しません。
最後に再生中の音番号を得る
var number = Sound.Effect;
- 何も再生されていない場合は、
Sound.Silent
が得られます。指定したSEを止める
Sound.EffectStop = number;
- 複数チャネルで再生している場合は、最も古い再生チャネルだけが止まります。
全てのSEを止める
Sound.Effect = Sound.Silent; // or Sound.EffectStop = Sound.Silent;SE音量を設定する
Sound.EffectVolume = volume;
- 正規化された値(0~1f)を設定します。取得もできます。
登録されているSE数を得る
var count = Sound.EffectCount;BGMを再生する
Sound.Music = number;
- インスペクタで設定されたフェードイン、フェードアウト、インターバルの時間(秒)を勘案して曲を再生します。
- 既に再生中の曲の場合は、単に再生を継続します。
- 別の曲を再生中の場合は、まず、再生中の曲がフェードアウトします。
- フェードアウト時間が0なら即座に止まります。
- 負値は指定できません。
- 次に、インターバル時間だけ、次の再生開始を待機します。
- 負値の場合は、フェードアウト中に遡って待機を終えます。
- 待機を終えると、指定された曲のフェードインを開始します。
- フェードイン時間が0なら即座に既定音量で再生されます。
- 負値は指定できません。
- 再生中でない場合は、即座にフェードインが開始されます。
- クロスフェード中に新たな再生指示があった場合は、以下の特例処理を行います。
- 前の(フェードアウト中の)曲が再生指示された場合は、2曲のフェード方向が切り替わります。
- フェードアウト・イン中のどちらとも異なる第3の曲が再生指示された場合は、再生中だった2曲の内で音量の大きい方をフェードアウトさせ、音量の小さい方は即座に停止して次の曲の開始シーケンスに移行します。
- 例
- 全ての時間が0なら、即座に切り替わります。
- 全ての時間が1なら、前の曲が1秒でフェードアウト、1秒無音で、次の曲が開始され1秒でフェードインします。
- フェードインとフェードアウトが3でインターバルが-1だと、前の曲が3秒でフェードアウトし、その終了1秒前に次の曲が再生を開始して3秒でフェードインします。フェードアウト開始からフェードイン終了までは5秒になります。
再生中の曲番号を得る
var number = Sound.Music;
- 再生されていない場合は、
Sound.Silent
が得られます。BGM音量を設定する
Sound.MusicVolume = 0.5f;
- 正規化された値(0~1f)を設定します。取得もできます。
登録されているBGM数を得る
var count = Sound.MusicCount;BGM再生を止める
Sound.Music = Sound.Silent;
- フェードアウト時間の設定が0でない場合は、フェードアウトします。
- フェードアウト時間が0の場合は即座に止まります。
一時的に全ての音を消す、戻す
Sound.Mute = true; // 一時的に音を消す Sound.Mute = false; // 音を戻す
- 音が出ないだけで、既存の再生は継続しますし、新たな再生も有効です。
以下の素材を使わせていただきました。
どうもありがとうございました。
- SoundEffects: ©効果音ラボ https://soundeffect-lab.info/
- Music: ©魔王魂 https://maoudamashii.jokersounds.com/
- 投稿日:2020-07-26T23:14:41+09:00
シンプルなサウンドマネージャー (unity)
前提
- Unity 2019.4.5f1
- 使用に際して、C#でスクリプトを書く必要があります。
- UIの操作音など、音源の位置が画面に固定されている場合に適しています。
できること
- BGMとSEの番号を指定して再生できます。
- BGM
- 最大同時再生数は2で固定です。(クロスフェード用)
- あらかじめ、インスペクタで、フェードイン、フェードアウト、インターバルの時間を指定します。
- 再生はループします。
- SE
- あらかじめ、インスペクタで、最大同時再生数を指定します。
- 再生時に、「同じ音でも重ねて鳴らす」、「同じ音が鳴っていたら止めてから鳴らす」、「同じ音が鳴っていない場合だけ鳴らす」ことが選択可能です。
- 再生はループしません。
- BGMとSEの音量を独立して設定できます。
- 全体の一時的なミュートが可能です。
アセットの入手 (GitHub)
ダウンロード ⇒ SoundManager.unitypackage
ソースはこちらです。使い方
準備
- プロジェクトにアセットをインポートしてください。
- シーンの適当なオブジェクトに、スクリプト
Sound.cs
をアタッチしてください。- インスペクタで、
Sound Effect Clip
とSound Music Clip
のSizeを必要なだけ増やし、オーディオクリップを設定してください。- 必要に応じてパラメータを調整してください。
項目 説明 初期値 Sound Effect Max SE同時再生数 5 Sound Effect Initial Volume SE初期音量 0.5 Sound Music Initial Volume BGM初期音量 0.5 Sound Music Fade In Time BGMフェードイン時間 0 Sound Music Fade Out Time BGMフェードアウト時間 3 Sound Music Interval Time BGMインターバル時間 (フェードアウト時間が 0
でなく負数なら重なる)0 Sound Effect Clip SEオーディオクリップ - Sound Music Clip BGMオーディオクリップ - SEを再生する
同じSEでも重ねて鳴らす
Sound.Effect = number;同じSEが鳴っていたら止めてから鳴らす
Sound.StopAndEffect = number;同じSEが鳴っていない場合だけ鳴らす
Sound.EffectIfNot = number;最後に再生中の音番号を得る
var number = Sound.Effect;指定したSEを止める
Sound.EffectStop = number;全てのSEを止める
Sound.Effect = Sound.Silent;SE音量を設定する
Sound.EffectVolume = volume;登録されているSE数を得る
var count = Sound.EffectCount;BGMを再生する
Sound.Music = number;再生中の曲番号を得る
var number = Sound.Music; // (Sound.Music == Sound.Silent) であれば何も再生していないBGM音量を設定する
Sound.MusicVolume = 0.5f;登録されているBGM数を得る
var count = Sound.MusicCount;BGM再生を止める
Sound.Music = Sound.Silent;一時的に全ての音を消す、戻す
Sound.Mute = true; // 一時的に音を消す Sound.Mute = false; // 音を戻す
以下の素材を使わせていただきました。
どうもありがとうございました。
- SoundEffects: ©効果音ラボ https://soundeffect-lab.info/
- Music: ©魔王魂 https://maoudamashii.jokersounds.com/
- 投稿日:2020-07-26T17:47:05+09:00
プログラミング初心者がUnityでゲームをつくってみる
みなさんこんちは。お母さんです。
今回、前々から使ってみたいと思っていた「Unity」を使って、ゲーム作りに初めて挑戦してみたいと思います!
プログラミングは・・・全くの初心者です。
scratchを少し触ってみたことはあるけれど、まともな作品を作ったことはまだありません。
こんな自分でもUnityを使って何かが作れるのか、頑張ってみようと思います!まずは、Unityのダウンロード、そしてインストールです。
Unityの公式サイトへ行くと、
「Unityplus」「UnityHub」のどちらをダウンロードするか聞かれます。[undefined]()
まだ一度もUnityを使ったことがない私は、両方をダウンロードしました。
「UnityHub」をインストールし、セットアップウィザードに従って進めていき、UnityIDを作成します。
ライセンス登録の際には、企業で使うか、個人で使うか、企業で使う場合の企業規模はどのくらいか、などを選ぶ箇所がありますが、私は個人で使うを選びました。
つぎに、マイクロゲームを4種類から選択します。
動画で解説してくれますが、ゲームの種類がよくわからないので、3Dを選択しました。早速何か描いてみます。
「GameObject」から、->3D->cubeと選択すると、
右側にウィンドウが開きました。ここのTransformのX・Y・Zに値を入れると、3Dの四角いオブジェクトを作ることができました。
今日はこれでおしまい。
- 投稿日:2020-07-26T17:47:05+09:00
プログラミング初心者がUnityでゲームをつくってみる その(1)
みなさんこんちは。お母さんです。
今回、前々から使ってみたいと思っていた「Unity」を使って、ゲーム作りに初めて挑戦してみたいと思います!
プログラミングは・・・全くの初心者です。
scratchを少し触ってみたことはあるけれど、まともな作品を作ったことはまだありません。
こんな自分でもUnityを使って何かが作れるのか、頑張ってみようと思います!まずは、Unityのダウンロード、そしてインストールです。
Unityの公式サイトへ行くと、
「Unityplus」「UnityHub」のどちらをダウンロードするか聞かれます。[undefined]()
まだ一度もUnityを使ったことがない私は、両方をダウンロードしました。
「UnityHub」をインストールし、セットアップウィザードに従って進めていき、UnityIDを作成します。
ライセンス登録の際には、企業で使うか、個人で使うか、企業で使う場合の企業規模はどのくらいか、などを選ぶ箇所がありますが、私は個人で使うを選びました。
つぎに、マイクロゲームを4種類から選択します。
動画で解説してくれますが、ゲームの種類がよくわからないので、3Dを選択しました。早速何か描いてみます。
「GameObject」から、->3D->cubeと選択すると、
右側にウィンドウが開きました。ここのTransformのX・Y・Zに値を入れると、3Dの四角いオブジェクトを作ることができました。
今日はこれでおしまい。
- 投稿日:2020-07-26T17:33:09+09:00
Unity スクロール領域の端が表示されているかを検出する
概要
今回はUnityのScrollRectにおいて、スクロール領域の4辺いずれかが画面に表示されているかを実装しました。
分かりやすくするため以下の例では、端が表示されているかどうかを検出し、対応する辺を黄色くしています。画面の上や左端に達したとき黄色く表示されているのが確認できると思います。
プロジェクト全体はこちらから
https://github.com/Arihide/scroll-edge-detection説明
今回作成したソースコードは以下の通りです。
using UnityEngine; using UnityEngine.UI; public class EdgeDetect : MonoBehaviour { [SerializeField] private ScrollRect scroll = null; [SerializeField] private Image top = null; [SerializeField] private Image bottom = null; [SerializeField] private Image left = null; [SerializeField] private Image right = null; private void Update() { Bounds contentBound = RectTransformUtility.CalculateRelativeRectTransformBounds(scroll.viewport, scroll.content); Rect viewportRect = scroll.viewport.rect; top.enabled = viewportRect.max.y >= contentBound.max.y; // 上までスクロールされているか? bottom.enabled = viewportRect.min.y <= contentBound.min.y; left.enabled = viewportRect.min.x <= contentBound.min.x; right.enabled = viewportRect.max.x >= contentBound.max.x; } }一番重要な箇所は
RectTransformUtility.CalculateRelativeRectTransformBounds
で、viewportからみたcontentの領域を計算している点ですね。
あとは、viewportとcontentの最大値や最小値を比較してあげれば良いです。もしかすると、Scrollbar.valueを使えば良いんじゃないかと思われる方もいらっしゃると思います。
ただこの場合だと、スクロール全体の領域が表示領域より小さい場合は4辺すべてが表示されることになりますが、それを検出することができないので、このような方法を用いています。基本的なロジックは以上の通りなのですが、このままの条件で使用すると、しばしば端にあるのに正しく判定されないことがあります。その場合は以下のように小さい値を加えて条件を緩くすると良いです。
using UnityEngine; using UnityEngine.UI; public class EdgeDetect : MonoBehaviour { [SerializeField] private ScrollRect scroll = null; [SerializeField] private Image top = null; [SerializeField] private Image bottom = null; [SerializeField] private Image left = null; [SerializeField] private Image right = null; private const float eps = 0.1f; private void Update() { Bounds contentBound = RectTransformUtility.CalculateRelativeRectTransformBounds(scroll.viewport, scroll.content); Rect viewportRect = scroll.viewport.rect; top.enabled = viewportRect.max.y >= contentBound.max.y - eps; bottom.enabled = viewportRect.min.y <= contentBound.min.y + eps; left.enabled = viewportRect.min.x <= contentBound.min.x + eps; right.enabled = viewportRect.max.x >= contentBound.max.x - eps; } }スワイプによる画像切り替えなどを実装するときに役に立つと思います。
- 投稿日:2020-07-26T17:24:41+09:00
Unity TIPS カードを生成する方法
unityでよく使う
オブジェクトを生成するやり方を見ていきます。
unityのvrは、 2019.4 LTSを使用しています。
では今回はカードを生成していきます。
まず 用意するものは カードのオブジェクト
今回はトランプのクラブを使っています。
ちなみに トランプのカードはアセットストアで無料であります。
そしてヒエラルキーで UIから キャンバス、パネルを入れて
カードのオブジェクト【プレハブにしておく】を入れて
親子関係にしておいてください。
キャンバスの下にパネルを入れて
パネルの下にオブジェクトを入れていきます。
次にスクリプトを作って、 以下のコードを記入します。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class CardIn : MonoBehaviour { public GameObject Club; public GameObject Panel; void Start() { for(int i =0; i<5; i++) { GameObject cardObj = Instantiate(Club); cardObj.transform.SetParent(Panel.transform); } } // Update is called once per frame void Update() { } }クラス名は CardInという名前にしています。
updateは使わないので消しても大丈夫です。今回は Clubのカードを生成したいので Clubという名前です。
for文では 5回としていますが、 ここは好きな数字でOKです。
これによって、パネルの上にカードを5枚
生成することができます。
次にこのスクリプトを カードオブジェクトにアタッチして
インスペクターで
Clubの所に カードのオブジェクトをドラッグ
パネルの所に パネルをドラッグして入れてください。
これらを入れたら ゲームを再生してみましょう。
そしたら こんな感じにカードが生成されていると思います。
ヒエラルキーを確認して クローンが5枚 できていればOKです。
PS
今度新しいunity2D講座をリリースする予定です。
今度出す講座では、 boltの使い方も入れていくので
まだ当分はかかるけどね。
講座作っている最中に vr変わったり、新しい機能とか出てくると
ほんと困るのよね。 とりあえず、boltは無料で使えるようになりましたから
これは入れていきまっせ。
- 投稿日:2020-07-26T16:29:01+09:00
Unity初心者がゼロからルービックキューブを作りたい
- 投稿日:2020-07-26T03:17:56+09:00
Unityでプレイヤーを移動させるときは、本当にAddForceが良いのか
Unityでプレイヤーを動かすときに何を使うべきか
2Dと3Dどちらもプレイヤーに相当するオブジェクトを何で動かすべきなのか悩みます。
Qiitaの他の詳しく解説されている記事がありますので参考にしてもらいたいのですが、
https://qiita.com/JunShimura/items/ab243cbd29e63e4f27c5
AddForceとは結局なんなのかと、問題の回避方法を検証していきます。
本題は AddForceなのですが、それ以外の方法も一応書いておきます。
テトリスなどブロックの操作など物理演算をしなくてもいい場合や空間転移する場合
transform.position
そもそも物理演算をしない(RigidBodyを使わない)ときは、
transform.position += new Vector3(0,1,2)
のように動かします。
初めのうちは物理演算を使う場合でも使ってしまう気持ちもわかりますが、すり抜けたり、オブジェクトが埋まってしまったりします。
なのでやはりおすすめしません。
ただし、何もないところに移動することや、物理法則を無視しても問題ない場合はこれで移動しても良いと思います。
rigidbody.MovePosition
https://qiita.com/yuji_yasuhara/items/6f50ecdd5d59e83aac99
rigidbody.MovePosition(rigidbody.position + new Vector3(0, 1, 2));こちらも、座標に対して移動する方法ですが、動かす方も当たる方も補間の設定(「補間」にするなど)をすると、途中にある物体にちゃんと当たり判定がされ、高速で衝突します。
private Rigidbody rigidbody; private bool isSpace = false; void Start() { rigidbody = GetComponent<Rigidbody>(); } private void FixedUpdate() { if (isSpace) { rigidbody.MovePosition(new Vector3(0, 100, 0)); isSpace = false; } } // Update is called once per frame void Update() { if (Input.GetKey(KeyCode.Space)) { isSpace = true; } }例えばこれで実行すると、空中にある (0,10,0)の箱が 押されて (0,110,0)に移動します。
これらについて
ここにわかりやすい検証ページがありますので紹介します。https://yowabi.blogspot.com/2017/12/unity-positionmoveposition-rigidbody.html
ちなみに、 rigidbody.MovePosition を
FixedUpdate
で呼び出していますが、僕はこのような瞬間的な変更をする場合はUpdateでも良いという認識です。ただし、Updateの場合は次のゲームフレームまでに物理演算が行われていない可能性がありますので、フレーム単位で物理的な処理がある前提の処理をしたい場合や、キーを押している間中動くといった場合、 例えばフレームレートを固定していても、Updateは1秒間で呼び出される回数が不安定なので、
FixedUpdate
のほうが正確に呼びされますのでその場合は、FixedUpdate
で処理したほうが良いです。そもそも AddForce とは??
さてようやく本題ですが、よく「力を加える」と言う説明がされていますが、「力を加える」とはなんでしょうか。
Unityの教科書を見ても、「力を加える」、「適当に係数を設定する」みたいな書き方になっています。
(説明がわかっても実際は、摩擦などがあるため適当に設定することになるのと思うのですが)僕も実は、この記事を見るまであやふやだったのですが、この記事を見てスッキリしました。
https://www.f-sp.com/entry/2016/08/16/211214この記事から引用させてもらうと
rigidbody.AddForce(Vector3.forward * 0.1f, ForceMode.Force);は
rigidbody.velocity += (Vector3.forward * 0.1f) * Time.fixedDeltaTime / rigidbody.mass;と等価というものです。
(もしかしたら、Unityのエンジン内で別に何かあるかもしれませんので、知ってる方はコメントください)ちなみに、AddForceの第2引数を省略したら、ForceMode.Forceを指定されているのと同じです。
AddForceで変わるものを計算してみる
private Rigidbody rigidbody; void Start() { rigidbody = GetComponent<Rigidbody>(); } private bool isSpace = false; private bool isLog = false; private void FixedUpdate() { if (isLog) { // AddForce後に Updateの前にFixedが呼ばれたら出力 Debug.Log("Fixed"); } if (isSpace) { isSpace = false; rigidbody.AddForce(new Vector3(0, 100, 0)); Debug.Break(); isLog = true; } } // Update is called once per frame void Update() { isLog = false; if (Input.GetKeyDown(KeyCode.Space)) { isSpace = true; } }このようなコードを書いてみました。
ちなみに、rigidbody.AddForce 後に Updateがすぐ来る場合はうまく計算できるが、
もう一度FixedUpdateが呼ばれる可能性もあり、その場合、計算が少しずれるのでその対策のため、Fixedをログに出力しています。
(Fixedが出なかったら実験としては成功)
Debug.Break()
は次のゲームフレームでUnityが一時停止してくれます。ここで重要なのは座標ではなく Rigidbodyの Info内にある Velocity のYです
1.8038
となっています。なぜ1.8038なのでしょうか。
rigidbody.AddForce(new Vector3(0, 100, 0));
は
rigidbody.velocity += new Vector3(0, 100, 0) * Time.fixedDeltaTime / rigidbody.mass
と等価でした。
ここで
Time.fixedDeltaTime
は プロジェクト設定の「固定時間ステップ」なので 0.02です。
rigidbody.mass
は質量なので1です。よって、
rigidbody.velocity += new Vector3(0, 100, 0) * 0.02f / 1f
なのですが、ここで実は重力の影響があり
実は、重力は常時
AddForce(0 , -9.81f , 0)
されているのと同じなのです。1固定時間ステップ分影響があるため
rigidbody.velocity += new Vector3(0, 100, 0) * 0.02f/ 1f - new Vector3(0 , -9.81f , 0) * 0.02f
`y軸だけ見て
100 * 0.02f - 9.81f * 0.02f = 1.8038
と計算通りになりました。
では ForceMode.Impulse は?
ForceMode.Impulse は 力積|Wikipeda のことで、瞬発的に力を加えると説明されていることがあり、実際にそうなんですが、疑問がわきます。
ボタン押した瞬間にForceMode.ImpulseでAddForceを呼び出す場合でも ForceMode.Forceでも 1回しか呼ばれないのに
何が違うんだろうか。先程のコードでAddForceの引数に ForceMode.Impulseを指定しました。
private Rigidbody rigidbody; void Start() { rigidbody = GetComponent<Rigidbody>(); } private bool isSpace = false; private bool isLog = false; private void FixedUpdate() { if (isLog) { // AddForce後に Updateの前にFixedが呼ばれたら出力 Debug.Log("Fixed"); } if (isSpace) { isSpace = false; rigidbody.AddForce(new Vector3(0, 100, 0), ForceMode.Impulse); Debug.Break(); isLog = true; } } // Update is called once per frame void Update() { isLog = false; if (Input.GetKeyDown(KeyCode.Space)) { isSpace = true; } }先程のサイトによると
rigidbody.AddForce(new Vector3(0, 100, 0), ForceMode.Impulse);は
rigidbody.velocity += new Vector3(0, 100, 0) / rigidbody.mass;と等価である。
ここでも重力の影響が 1固定時間ステップ分影響があり
100-9.81*0.02 = 99.8038
となります。ちなみに、質量を2に変えると その分 速度が1/2になりました。
ForceMode.Impulseを指定すると、 ForceMode.Forceに比べて速度が一気に50倍(固定時間ステップが0.02の場合)
されることがわかりました。逆に言うとそれくらいの違いしかないのはと想像されます。
また
ForceMode.Acceleration
はForceMode.Force
でのrigidbody.mass
が必ず1として処理され
ForceMode.VelocityChange
はForceMode.Impulse
でのrigidbody.mass
が 必ず1として処理されるものです。なので、質量が違っても同じ速度として動かす場合はこれらを使うと良いです。
(同じ力でも質量によって動きを変えたかったら
ForceMode.Force
かForceMode.Impulse
)AddForceとはなんだったのか
AddForceは力を設定することであり、高校物理で習う
F = ma
なので加速度
がでてきますが、 Unityでの加速度というパラメータはなくa = \frac{\Delta v}{\Delta t}より
\Delta v = \frac{F}{m \Delta t}微小時間に速度の差分を計算することがわかります。
ここで 微小時間は固定時間ステップの0.02秒のことで(正確に言うとリアル時間ではなく、ゲーム内時間)
FixedUpdate
が呼ばれそこで速度に加算されます。つまり、AddForceは
RigidBody
内の速度パラメータ(velocity)を変えているだけ(と思われます)で、AddForceを使わずに自分でvelocityを変更してもいいわけですし、
ForceMode.Impulse
などは、ForceMode.Force
とは、倍率が違うだけと見ることもできます。(ただし、
ForceMode.Impulse
は文字通りに瞬間的なものに使うほうがプログラムの見やすさ的にも良いです)ということですので原理的にはAddForceを使わなくても自分で計算し、velocityに加算すれば良いはずです。
ただ、毎回重力なども含めすべての力を計算するのも大変なので AddForceが準備されているので、やはりAddForceを使うのが良いのかなと思います。
プレイヤーをAddForceで動かすときにハマること
さて、本題のAddForceを使ってプレイヤーを動かす事を考えます。
よくある教科書などにも書いてあるので、AddForce使って動かすんだなと思うわけなんですが、
private void FixedUpdate() { var hori = Input.GetAxis("Horizontal"); var vert = Input.GetAxis("Vertical"); rigidbody.AddForce(new Vector3(hori, 0, vert) * 5); }こんな感じで動かしてみたらあれ?と思うことがあると思います。
- 速度が徐々に増える感じになる
- 最大速度を超えてしまう
- 最大速度を超えたら力を加えないようにしても速度が一定ではない
- ドリフト現象になる (ボタンを離したのに滑る)
車のゲームを作る場合は、もちろんこれらでも良いのですが、
人のキャラクターを操作する場合は、違和感が出てしまいます。改めて考えてみるとキーボードを押したときに、プレイヤーはどうなるべきなんでしょうか。
- その方向に位置を移動させる
- その方向に速度を加える (その方向以外は速度を0にする)
- その方向に力を加える
どれも同じように見えますが別の概念の話になります。
(積分をすれば関係がある概念ですが)実際どれで実装してもよく、ゲームの相性にあったものをすると良いと思います。
どれもメリット・デメリットがあるのでそれを理解して実装しましょう。さらにあとから変えられるようにしてあるとベストです。
(そのゲームに慣性があってもよいのかや、リアルの動きを求めたいのかなど)位置を移動させる
private void FixedUpdate() { var hori = Input.GetAxis("Horizontal"); var vert = Input.GetAxis("Vertical"); rigidbody.MovePosition(rigidbody.position + new Vector3(hori, 0, vert)); }ボタンを押したらその分移動するし、離したらすぐ止まります。
これはこれで良いと思います、ただ慣性がまったくないのでそこは違和感が出るかなと思います、以下
GetAxis
でやっていますがGetAxisRaw
でやってもほぼ同様な結果になると思います。ちなみに
GetAxis
は[-1,1]
の範囲の値を取り、キーボードでキーを入力したときに アナログスティックで入力したような補正がかかり、単純に押したから 1 とはならないようです。アナログスティックの場合は、そのままの傾きです。
GetAxisRaw
は アナログスティックの場合は同じく[-1,1]
の範囲 ですが、キーボードでキーを入力したときに押した場合は補正がかからず -1,0,1 に限定されます。速度を変える
private void FixedUpdate() { var hori = Input.GetAxis("Horizontal"); var vert = Input.GetAxis("Vertical"); rigidbody.velocity = new Vector3(hori, 0, vert) * 10; }実は、この方法を使うと
- 速度が徐々に増える感じになる
- 最大速度を超えてしまう
- 最大速度をあたりでなるべく速度を一定化したい
- ドリフト現象になるときがある
が一気に改善できます。
なので、そこまでリアルさを求めないカジュアルなゲームでは十分ありなんだと思います。
ただ、
rigidbody.velocity
を書き換えるのは良くないのではという意見もあります。確かに公式ページを見ても
https://docs.unity3d.com/ScriptReference/Rigidbody-velocity.html
In most cases you should not modify the velocity directly, as this can result in unrealistic behaviour
とありますが、「リアルな動きにならないから使わないで」ということなので、
ゲームはリアルな動きを求めないほうがゲームとして良い場合もあるので(ジャンプ中に動けるなど)
そういう意味でそこまでリアルを求めずに速度を直接変えてもいいのではという気がします。だた、欠点としていきなり急ブレーキをかけた感じになることや、摩擦力が違う事がある場合に実装が難しいです。
氷のステージなど滑ってほしいのに直接速度を変えてしまうと滑らなくなります。
(もちろん、自力で判定して、別で処理することもできます。)やっぱり、地形ごとに合った動きもさせたいし、リアルな動きをさせたいからAddForceを使いたい。なおかつ上の問題も解決していきたいとは思います。
AddForceを使ったときのハマりどころを改善する
いきなり最高速にする・最高速を超えないようにする・最大速度あたりでもなるべく速度を一定化したい
AddForceの欠点としては、ただ使うだけだと徐々に速度が上がる動きになります。
また最高速度という概念がないので教科書でもよく見られますが
ある速度未満のときだけ力を加えるみたいな実装が見受けられます。
if (rigidbody.velocity.magnitude < 5) { rigidbody.AddForce(new Vector3(hori,0,vert)*3000); }ちなみにせっかくなので人形のキャラクターとしてUnityChan を使っています。
誤りというわけではなくこれはこれで良いのですが、力を加えたときに最高速を超えてしまい、Colliderの設定にもよりますが、この場合でも最高5.2くらいまで行きました。
(気にしなければ、これはこれで良いと思います。)次に、キーボードの入力の場合 Vector3(1,0,1) というのがありうるため、
(アナログスティックなら問題ない)
移動ベクトルの大きさが1より多かった場合は 1に正規化する処理をこのようにしました。var hori = Input.GetAxis("Horizontal"); var vert = Input.GetAxis("Vertical"); var moveVector = new Vector3(hori, 0, vert); if (moveVector.magnitude > 1) { // 大きさが1より大きかったら1に正規化する(主にキーボードのため) moveVector.Normalize(); }最大速を5にしたいがアナログスティックの傾きの量を単純に掛けたのを最大速にするとする。
今の速度からその最高速になる分だけ力を加えれば良く、その速度は
(5 * moveVector.magnitude - rigidbody.velocity.magnitude)
です。これを1固定時間ステップで与えるため 加速度としては fixedDeltaTimeで割る値になります。
(5 * moveVector.magnitude - rigidbody.velocity.magnitude) / Time.fixedDeltaTime
これを係数として 入力のベクトルを1に正規化したものに掛けて与えます。
private void FixedUpdate() { var hori = Input.GetAxisRaw("Horizontal"); var vert = Input.GetAxisRaw("Vertical"); var moveVector = new Vector3(hori, 0, vert); if (moveVector.magnitude > 1) { // 大きさが1より大きかったら1に正規化する(主にキーボードのため) moveVector.Normalize(); } // 以後 moveVector.magnitudeの大きさは最大でも1 // 最大速はアナログスティックを倒した量分の倍率とし、最大の速度までの力を計算する var factor = (5 * moveVector.magnitude - rigidbody.velocity.magnitude) / Time.fixedDeltaTime; rigidbody.AddForce(moveVector * factor); transform.localRotation = Quaternion.Lerp(transform.localRotation, Quaternion.LookRotation(moveVector), 20.0f * Time.deltaTime); }InfoのSpeedがほぼ5になっていると思います。
(摩擦があるため少し下がります。ここを常時5にするのは難しいですし、気になるかもしれませんが微小なので気にする必要もないと思います。)これで、「いきなり最高速にする」・「最高速を超えないようにする」・「最大速度あたりでもなるべく速度を一定化したい」が解決できます。
ドリフトする挙動を改善する
上の動画では速度はいい感じになったのですが、実は同時押しはしていなくて特に右に動いたあとに下ボタンしか押してないのにを右にひっぱられているような感じになっております。
これを直していきたいわけですが、なぜこうなるかというと
Z方向(下)に力を入れたとしても X方向(右)の速度を0にしていないので
X方向の速度が残ってしまっているというのが原因です。
(間違いやすいのですが、力が残っているわけではありません)車やボールのゲームなら自然ですが、人のキャラクターだと違和感があります。
自作されているゲームではドリフトみたいな挙動にならないという方は、それはそれで良くてこの問題を考えなくても良いと思います。
ちなみに、なぜX方向(右) の速度が0になるかというと、地面とキャラクターの摩擦の物理演算で速度が少し経つと0になるからです。
摩擦と質量と速度の設定次第では、すぐに0になるのでドリフト現象が出ないかもしれません。
ドリフト現象をどうなってなくすか
結局は、キーを離したときに摩擦で0になりきらず、少し滑るのが原因なので、色々調査しましたが、手っ取り早いのは 地面かキャラクターにつけるColliderのMaterialを「Dynamic Friction」 を1よりもかなり大きな値にすることです。
これは実は、最大値1のように見えて、最大値は無限らしいです。
推奨は 0〜1 のようですが、結局リアルを求めてない挙動なのでいいかなと思います。1にしてもすぐ止まる摩擦というわけでもないので大きく設定しても良いかなと思いますし、
この摩擦係数を変えると滑りやすい地面も実装できるので良いかなと思います。
参考
https://ekulabo.com/physic-material#outline__3_1
なお、摩擦を1より大きく設定したくないという方は このような力にしても良いと思います、FixedUpdateで速度を打ち消す力をいれます。
rigidbody.AddForce(-rigidbody.velocity / Time.fixedDeltaTime);摩擦力0の地形では
さてここまできて、地形で摩擦力0にしたらこのような動きになります。
スケート選手みたいな動きになるので、ゲームとしては、滑る地形のときはもうひと工夫しないといけないのかなと思います。
(現実を考えると、滑っている最中に他の方向に力を入れられないので、速度が0になったときだけ動けるなどの実装が必要なのかなと思います)
ちなみに、こういう地形でもvelocityに値を直接入れる実装の場合は、
private void FixedUpdate() { var hori = Input.GetAxis("Horizontal"); var vert = Input.GetAxis("Vertical"); rigidbody.velocity = new Vector3(hori, 0, vert) * 10; }速度が上書きされるので滑らなくなります。
ただ結局、摩擦が0で、スケート選手みたいな動きではなく、速度が0になったときだけ動けるみたいな実装をするのなら
velocity
を使うのもAddForce
を使うのも同じ様な実装になると思うので
結局はrigidbody.velocity
に直接入れても良いのではという気になってきました..ここまで来てですが、プレイヤーを動かすのに
AddForce
を使うと良い場面がありましたらぜひ教えて下さい。?まとめ
結局は、この記事通りにするのではなく、自分のゲームにあった方法に工夫してほしいが、
AddForce
を理解することによってよくわからないけど使うみたいな怖さを減らして欲しいと思います。結局の所理解しないまま
AddForce
を使うとハマることも多いので
物理シミュレーションならともかく、ゲームならrigidbody.velocity
の値を変えてもよいのではと思います。
AddForce
はrigidbody.velocity
(速度)の"変化"を記述しているとみなせる- 実質的には
AddForce
のモードの種類は、係数が違うだけとみなせるAddForce
を使うなら速度管理が大変- 重力の影響などを考えると
AddForce
を使うのが無難- AddForceを使わずにrigidbody.velocityを直接変更しても良いかもしれない
- 投稿日:2020-07-26T00:31:20+09:00
Unityで配管工風3Dアクションゲームを作った知見
はじめに
1か月ほどで某配管工のような3Dアクションゲームを作りました。
以下のサイトにて遊べます。
SpaceJumpGame | unityroom今後のゲーム制作にも役立ちそうな知見をまとめました。
(参考にならない)コードはGitHubにあげてます
https://github.com/Papyrustaro/Unity3DActionGameバージョン
Unity2019.4.0f1
Cinemachineを使ったカメラの追従
プレイヤーが常に画面の中心になるようにカメラを移動すると、どの方向にプレイヤーが進んでいるのかわかりにくい。
ワンテンポ遅れて追従するようなカメラをCinemachineを利用することで簡単に実装できる。
CinemachineVirtualCameraのFollowにプレイヤーの(中心の)Transformを代入する。
あとはBodyとAimのタイプと、各種パラメータを設定するだけでそれっぽくなる。今回はBodyをFraming Transposer、AimをDo nothingと置き、カメラの回転は別途スクリプトで書いた。
特に操作したパラメータは、画面のどこに対象物が映るようにするのか(Screen X/Y)、対象物とカメラの距離(Camera Distance)Cinemachineを使った特定経路のカメラの移動
今回メニュー画面で、カメラがステージの周りを回り続けるようにしている。
これもCinemachineを利用して簡単に実装している。
CinemachineSmoothPathでカメラの通過点を設定。
スクリプトからCinemachineTrackedDollyのm_PathPositionの値を変えることで通路上のどこにいるのかを変えている。エディタ上から特定の処理を呼ぶ
今回はNaughtyAttributesというエディタ拡張を利用した。
https://github.com/dbrizov/NaughtyAttributesこれによって手作業でやっていたInspectorの操作などを大幅に削減できる。
以下はシーンに存在するスイッチと、スイッチで切り替わるステージを代入するスクリプトとInspector上の見え方。[SerializeField, ReadOnly] private List<SwitchOnOffStageInHitHead> switches; [SerializeField, ReadOnly] private List<SwitchOnOffStage> switchStages; /*~~~~~~~~~~*/ /// <summary> /// シーンに存在するswitch、onOffStageを代入・初期化。Inspector上から /// </summary> [Button(enabledMode: EButtonEnableMode.Editor)] private void SetAllSwitchesAndOnOffStages() { this.switches = new List<SwitchOnOffStageInHitHead>(); foreach(GameObject obj in GameObject.FindGameObjectsWithTag("SwitchOnOff")) { this.switches.Add(obj.GetComponent<SwitchOnOffStageInHitHead>()); } this.switchStages = new List<SwitchOnOffStage>(); foreach(GameObject obj in GameObject.FindGameObjectsWithTag("SwitchOnOffStage")) { this.switchStages.Add(obj.GetComponent<SwitchOnOffStage>()); } }自前で用意したい場合はこちらの記事などを参考にしてみては。
https://kan-kikuchi.hatenablog.com/entry/CustomEditor_ButtonDoTweenはいいぞ(いいぞ)
今回初めてDoTweenを利用してみましたが、とにかく便利。
Unity全利用者使ってみるべき。入門ブログを以前書きましたが、簡単なUIの処理にも重宝した。
これからもかなりお世話になりそう。
https://qiita.com/papyrustaro/items/2435369d13373417e1fdWebGLでのJoy-Con操作
Unity上とWebGLビルド上ではどうもJoy-Conの入力が違うらしい。
アナログスティックは検知できない、という記事も見かけたが、全入力検知できた。
正直全く詳しくないので、環境次第では上手くいかないかもしれない。以下のサイトにて、unityroomでのパッド入力の確認ができるようにしています。
https://unityroom.com/games/inputchecker衝突したColliderの法線ベクトルが知りたい
まず、調べた限りTrigger(OnTrigger...)ではできない。
OnCollision...の中で、collision.contacts[0].normalでできる(1つでいいなら)https://docs.unity3d.com/ja/current/ScriptReference/Collision-contacts.html
https://docs.unity3d.com/ja/current/ScriptReference/Collision.htmlスクリプトからEventTriggerを設定する
ButtonなどのGUIでは、選択されたときやカーソルが上に乗ったときに特定の処理をさせたいことが頻繁に起こる。
そんなときにEventTriggerを利用しているのだが、Inspector上で毎回処理を代入するのが非常に面倒。
スクリプトでawakeなどで代入するのが楽。
https://light11.hatenadiary.com/entry/2018/01/31/234716しかし、この場合だと処理を登録してもInspector上から見えないのが欠点。
見えないが登録はされている。
このせいで、予めスクリプトによる代入ができないのが悩みどころ。床の移動にあわせてプレイヤーも移動させる
Rigidbodyによる摩擦計算を利用すれば、乗っている床が移動してもプレイヤーは乗ったままだが、
CharacterControllerを利用してプレイヤーを動かしていた場合、そうはいかない。
何もしないと、プレイヤーの座標はそのままで床だけ移動してしまう(しまいには落ちる)一番手っ取り早い方法はプレイヤーgameObjectを移動床の子オブジェクトにすることだが、できることが制限されるので今回は別の方法を検討。
今回は移動する可能性のある床に接地したら、床のTransformを記憶し、前フレームからの移動分プレイヤーも移動させることで、移動リフトの上に乗ることを実装した。
移動リフトはいろんなゲームで登場するのでもっと賢い実装がありそうだ...その他
壁キック実装
ブログを別で書きました。
https://papyrustaro.hatenablog.jp/entry/2020/06/17/121100アニメーションによって、gameObjectが移動しないようにする
(怒涛のURL貼り付け)
http://tsubakit1.hateblo.jp/entry/2017/11/03/235913
http://aluminum-pepe.hatenablog.com/entry/2019/01/07/064816
https://gametukurikata.com/program/scriptisgroundedアニメーションを瞬時に変える
has exit time をfalseに
transration duration を0に時にはanimationclip自体の編集も
CharacterControllerを使用したときにキャラが地面から浮く
SkinWidthが原因。リファレンスではradisの10%。
https://docs.unity3d.com/ja/2018.4/Manual/class-CharacterController.html
ライティング
これ以上わかりやすい資料があるでしょうか、というほどわかりやすい資料
https://learning.unity3d.jp/2224/線を描く
LineRendererを使う。
Inspector上からメソッドを呼ぶことで、特定のVector配列で線を引くことも可能移動リフトの線を引く例
/// <summary> /// Inspectorからリフトの軌道上の線を作成 /// </summary> [ContextMenu("GenerateLineOfOrbit")] public void GenericLineOfOrbit() { List<Vector3> paths = new List<Vector3>(); paths.Add(this.transform.position); for(int i = 0; i < this.path.Length; i++) { paths.Add(this.path[i] + paths[i]); } LineRenderer lineRenderer = this.gameObject.AddComponent<LineRenderer>(); lineRenderer.positionCount = paths.Count; lineRenderer.SetPositions(paths.ToArray()); lineRenderer.startWidth = 0.1f; lineRenderer.endWidth = 0.1f; }プロパティをinspector上から変更する
https://baba-s.hatenablog.com/entry/2018/12/19/085000
自動でいい感じのColliderを作成する
https://kan-kikuchi.hatenablog.com/entry/SAColliderBuilder
SDユニティちゃんにライトが当たらない
shaderのせいみたいだが、変えると見栄えが...おとなしく初期設定に戻した。
Particleが場所や観る角度によって見え方が変わる
影がついているのが原因
ShaderをParticles/Standard Surfaceなどから、Particles/Standard Unlitなどに変えることで解決(壁張り付き時の煙)