原カバンは鞄のお店ではありません。

Unityを使ったゲーム制作のあれこれを綴っていきます。

【Unity】ScreenToWorldPointで位置がずれる場合の対処

f:id:Karvan:20220315210249p:plain

制作者視点

そろそろYoutubeで「皆さん、エルデンリング楽しんでますか?一般の方はきっと無邪気に楽しんでらっしゃるかと思いますが、私みたいなゲーム制作者の視点から意見を言わせていただくと・・・」みたいな動画がアップされそうでワクワクしている皆さんこんにちは。どんな意見が聞けるのか今から楽しみですが、共感性羞恥心が発動して見れないかもしれない。

 

FlatFx

最近「FlatFx」というアセットを購入しました。

assetstore.unity.comこのアセットは2Dグラフィックのようなエフェクトが表示できるというアセットです。

www.youtube.com

 

動画を見るとUIのイメージアニメに見えますが、実体はScirptで指定した位置にRendermodeがBillboardのパーティクルが表示されるという仕組みなので、Z方向の座標は絶えず0となります

このため、マウスでクリックした位置をScreenToWorldPointで取得して、「FlatFx」のエフェクトを表示させようとすると下の動画のようにズレた位置に表示されてしまいます。(青い円がFlatFxのエフェクト)

f:id:Karvan:20220315211601g:plain

比較用に同じ位置に白いボールを移動させていますが、クリック位置が画面下の方に行くに従ってズレが大きくなるのがわかります。

ソースはこんな感じ、クリック位置はScreenToWorldPointで取得してFlatFxに渡しています。

f:id:Karvan:20220315211841p:plain

 

原因

この事象の原因はシーンを横から見るとわかります。
上記のソースではScreenToWorldPointにはクリック位置(X,Y)にカメラのZ方向の位置を指定して座標を変換していますが、メインカメラが傾いている場合、取得した位置はカメラの傾きと平行になる平面内の位置が取得されます。

f:id:Karvan:20220315212002g:plain

前述のようにFlatFxは「Z方向の座標は絶えず0」であるため、ScreenToWorldPointで取得した位置を指定しても「Z=0のXY平面」に投影された位置に表示される事になります。

f:id:Karvan:20220315212354p:plain

これをメインカメラ側の視点で見ると、傾き分Y方向がズレた位置にFlatFxが発生するように見える、というわけです。

f:id:Karvan:20220315212034g:plain

 

対処

「Z=0のXY平面」に指定する位置は、ScreenToWorldPointで取得した位置にメインカメラの傾きを考慮してY方向の位置を補正する必要があります。

f:id:Karvan:20220315212536p:plain

下の図のようにY方向の補正位置を計算して、「Z=0のXY平面」の位置を求めます。

f:id:Karvan:20220315212617p:plain

対応後のソース

f:id:Karvan:20220315212710p:plain

クリック位置に「FlatFx」のエフェクトが表示されるようになります。

f:id:Karvan:20220315212832g:plain

 

まとめ

ちょっとわかりづらいテーマでしたが、ポイントは

  • ScreenToWorldPointはカメラの傾きと平行になる位置が取得される
  • 特定のXY平面の位置を取得するにはカメラの傾きを考慮する必要がある

ということで、もし自プロジェクトでScreenToWorldPointの位置ずれに悩んでいる方は上記ポイントを見直してください。

【Unity】PC用ビルドのウィンドウサイズ指定

f:id:Karvan:20220308202850j:plain

寒いのか暖かいのか

近くの公園では梅の花も咲いて春の訪れが近いのに人生の春の訪れは未だ見えない皆さんこんにちは。
最近は大きな不幸事はないものの、小さい嫌がらせみたいな嫌な出来事が重なってやる気が全く出ません、エルデンリング以外。昨日は2時間かけてやっと漂流墓地のボスを倒しました。

 

GameBBQ

集英社のインディーゲーム支援企画「GameBBQ」の結果が発表されました。
私の企画は二次面接で箸にも棒にも掛からぬ結果となったので、残念ながら開発は中断となってしまったのですが良い経験になりました。
今回は実績が足らなかった事が落選の最大の原因だと思うので、もっとゲーム開発の実績を積んで機会があれば再度挑戦したいと思います。

 

iGiインキュベーション

これで各出版社の企画には全て落ちてしまったのですが、懲りずに今度はiGiインキュベーションに応募してみました。
こちらの応募にはプレイ可能なデモ版が必要、とのことで、現在制作中の3D脱出ゲームの方で応募しています。
ゲーム内容的に各出版社に応募したものと比べてインパクトも新規性もないので二期メンバーには選ばれる事はないと思うのですが、選考の過程で寸評程度でも貰えれば開発の参考になるなぁ、という淡い期待で応募しています。

 

フルスクリーン表示

前述のようにiGiインキュベーションではプレイアブルデモが必要なのでWindows-PC用としてビルドしたのですが、どうもデフォルトはフルスクリーン設定なっているらしくビルド後に起動してみるとちょっとビックリします。
ビルドしたアプリをウィンドウモードで起動したい場合はビルド設定を変える必要があります。

 

File > Build Settings

で表示される画面でPlayer Settingsボタンを押下

f:id:Karvan:20220308203728p:plain

 

Player > Resolution and Presentationに進むとFullScreenModeの設定があるので、これを「windowed」に変更します。

f:id:Karvan:20220308203748p:plain

これで再ビルドするとウィドウモードで表示されます。

 

f:id:Karvan:20220308203830p:plain

ちょっと分かりづらいけど

【Unity】繰り返し使うと重くなるDOTweenTMPAnimatorへの対処

f:id:Karvan:20220301202656p:plain

腕組しながら語る

話題のエルデンリングについて、その面白さよりも騎馬戦や召喚魔法等の救済措置の多さを取り上げて「SEKIROに比べるとフロムも随分丸くなったなぁ」とか気持ち悪い感想をつぶやいてる皆さんこんにちは。
とにかく今作はボリュームがありすぎて、15時間プレイしても未だ噂のマルギットさんとお会いしてません。ちょっと歩くと中ボスやらドラゴンやらダンジョンがあるし。

 

DOTweenTMPAnimator

みんな大好きDOTweenのPro版にはTextMeshに対するTweenのメソッドが用意されています。
それらはText全体に対するアニメ動作を実装するものと、Textを1文字ずつ個別にアニメーションさせるものがあります。

Textを1文字ずつ個別に動作させる場合はDOTweenTMPAnimatorクラスを使用しますが、これはTextMeshをラップしたDOTweenTMPAnimatorクラスを生成し、それに対してTweenを行う方式で実装します。

現在作成中の「W.T.」でもこのクラスを利用して下の動画のようなテキストアニメーションを実装しています。

f:id:Karvan:20220228000244g:plain

一文字ずつ左に移動させながらフェードアウトするアニメ
ソースはこんな感じ

DOTweenTMPAnimator textAnim2 = new DOTweenTMPAnimator(MyTextMesh);
for (int iCnt = 0; iCnt < textAnim2.textInfo.characterCount; iCnt++)
{
    textAnim2.DOOffsetChar(iCnt, Vector3.left * 30.0f, 1.5f).SetDelay(0.1f * iCnt);
    textAnim2.DOFadeChar(iCnt, 0, 2.5f).SetDelay(0.1f * iCnt);
}

 

問題点

上述のように非常に便利なDOTweenTMPAnimatorクラスですが、同じTextMeshに対して文字列を入れ替えてテキストアニメーションさせようとした場合、
当然ながらその度にDOTweenTMPAnimatorクラスを再生成(new)する必要があります。

f:id:Karvan:20220301203704g:plain

この場合、数回なら問題ないのですが、それ以上になるとテキストアニメーションの開始に遅延が発生し、下手するとゲーム全体が一時停止したような状態になります
表示するテキスト毎にそれぞれTextMeshを定義すれば大丈夫なのでしょうが、そうすると1シーン内に大量にTextMeshが必要になったりしてあまり現実的な対応ではありません。

 

解決法

色々試した結果、以下の二点の対応を行うと解消されることが分かりました。

  • DOTweenTMPAnimatorクラスをメンバ変数として使いまわす。
  • Tweenが完了したらDOTweenTMPAnimatorクラスのResetメソッドをコールする。

特にDOTweenTMPAnimatorクラスをメンバ変数として使いまわす事は重要で、一つの関数内のローカル変数として毎度生成させていると3,4回目ぐらいで激重状態になります。
また、DOTweenTMPAnimatorクラスでも完了時のコールバックは指定可能なので、最後の文字のTweenが完了したらResetメソッドをコールするようにしましょう。

 

対処後のソース

DOTweenTMPAnimator TextAnim;

public void TextFadeColor()
{
    for (int iCnt = 0; iCnt < TextAnim.textInfo.characterCount; iCnt++)
    {
        TextAnim.DOOffsetChar(iCnt, Vector3.left * 30.0f, 1.0f).SetDelay(0.1f * iCnt);
        if (iCnt == TextAnim.textInfo.characterCount - 1)
        {
            TextAnim.DOFadeChar(iCnt, 0, 1.5f).SetDelay(0.1f * iCnt).OnComplete(() => OnComplete_FadeOut());
        }
        else
        {
            TextAnim.DOFadeChar(iCnt, 0, 1.5f).SetDelay(0.1f * iCnt);
        }
    }
}

public void OnComplete_FadeOut()
{
    TextAnim.Reset();
}

 

【雑記】進捗報告いろいろ

f:id:Karvan:20220215193111p:plain

SNS

Twitter等のSNSで一人称が「俺」の人は何となく苦手な皆さんこんにちは。10代、20代ならともかく30も過ぎて「俺」呼びの方とは距離を置いて過ごしたい日々です。

 

講談社ゲームクリエーターズラボ

二期候補生を審査中ですが、私の企画は残念ながら一次面接で不合格となってしまいました。

 

creatorslab.kodansha.co.jp面接中は相手の方からかなり持ち上げられて浮かれていたのですが、ちょっと考えが甘かったようですね。

これまで大した実績が無いのもマイナスポイントだったのかもしれません。
もし次があれば今回の企画をブラッシュアップしたもので挑みたいと思います。

 

ゲーム制作進捗

そんなわけで年末に浮かれていたのが嘘のように真っ暗闇に落ち込んでいるのでブログの新しネタを書く気も起きない状況です。

代わりに現在制作中の脱出ゲームについて、出来上がったところまでの紹介動画を作成したので、これでご勘弁ください。

youtu.be

とりあえず完成まで漕ぎ着けるようにこれからも頑張ります。

 

 

【Unity】Linecastを利用して遮蔽物を判定する

f:id:Karvan:20220201213224p:plain

冬季オリンピック

いつの間にか冬季オリンピックが始まっていてビックリしている皆さんこんにちは。ここ最近の報道がコロナ禍一色でそれほどオリンピックの話題が無かったし、個人的にはブレスオブザワイルドの剣の試練にドハマりしていたこともあって全く意識にありませんでした。週末の新聞の一面がオリンピックの話題で埋まっていてビックリ。

 

表か裏か

現在開発を進めている3D脱出ゲームにて、プレイヤーの視点の先にあるアイテムについて説明文を表示するという機能を作成しました。

f:id:Karvan:20220208194516p:plain

 

仕組みとしては簡単で、まずアイテム側にプレイヤーの視点を検知する為のColliderを取り付けて、

f:id:Karvan:20220208194615p:plain

このCollider内にプレイヤー側の視点(camera)の視界を表すCollider(下の図の青いBoxの部分)が接触(OnTriggerEnter)したら説明文を表示するという仕組みですが、

f:id:Karvan:20220208194657p:plain

例えば上図のPCモニターのようにアイテムに表と裏がある場合、プレイヤーがPCモニターの裏面側に回ったとしてもColliderの接触判定は有効なので説明文が表示されてしまいます

f:id:Karvan:20220208194749p:plain

 

なので、この問題を回避するためにLinecastを利用してプレイヤーが裏側に回った際の遮蔽物を判定するように対応しました。

 

裏側を判定する

まずPCモニターの裏側に、裏側だと判定するためのColliderを設置します。下の図の紫の箇所がそのColliderです。
このColliderには「PlayerRayIsBlocked」のタグが設定してあります。

f:id:Karvan:20220208195445p:plain

 

そして、PCモニター側のOnTriggerEnter内でプレイヤー側の視点Colliderを検知した際、プレイヤーとPCモニターの間に「PlayerRayIsBlocked」のタグが付いたColliderが存在すればプレイヤーは裏側にいると判定することができます。
この「PlayerRayIsBlocked」Colliderの存在有無を判定するためにLinecastを使用します。

 

Linecast

Linecastは始点と終点を設定してそこに線を張り、その範囲内でヒットしたCollider情報を取得する事ができます。
Colliderが複数接触してる時、始点に近いCollider情報を取得できるようです。
こういった手法を取る場合、主にRaycastを使用する事が多いと思いますが、今回のケースでは明確に始点と終点が分かるのでLinecastを使用することにしました。

public void OnTriggerEnter(Collider other)
{
    Vector3 startPos = this.transform.position;
    Vector3 endPos = other.gameObject.transform.position;
    RaycastHit hit;

    if (Physics.Linecast(startPos, endPos, out hit, myLayerMask))
    {
        if (hit.transform.gameObject.CompareTag("PlayerRayIsBlocked"))
        {
            return;
        }
    }
}

上記のソースでは始点(PCモニター)と終点(Player視点)の間にLinecastで線を張り、ヒットしたColldierのタグ名が「PlayerRayIsBlocked」であるかどうか判定しています。

この対応によって

f:id:Karvan:20220206154351g:plain

プレイヤーがPCモニターの裏側にいる場合、説明文は表示されなくなり、表に回ると表示されるようになりました。

【Unity】トゥーンシェーダーとポストエフェクトで漫画風な画面を作る

f:id:Karvan:20220201211803p:plain

誰得

ここ最近、Twitterのタイムラインに頻繁に出没する謎のマス目だけの投稿に苛立ちを覚えて"wordle"をミュートワードに登録した皆さんこんにちは。投稿した本人以外意味不明なので勘弁してほしい。unity1weekでも偶に「●●さんがスコア××を獲得しました!」的なつぶやきをTwitterに自動投稿するゲームが見受けられますが、アレのおかげで閲覧数が伸びても評価には繋がらないので辞めた方が良いと思います。

 

漫画風な画面を作りたい

特に需要があるわけではないのですが、自作ゲームをちょっとインパクトのある画面にしたい場合、漫画風な画面にする、という選択肢も有りだと思っています。

「漫画風」とは、色々な意見があるかと思いますが、個人的には以下のような要素を含んだ画面だと考えています。

  • 基本的に画面は二色or三色で構成される
  • オブジェクトに対して明暗の境界がくっきりと分かれる
  • オブジェクト、背景に輪郭線が描かれる

特にUnit製のゲームの場合、巷で「Unity臭がある」と陰口を囁かれるケースを目にするので、それを感じさせないような絵作りの一つとして漫画風な画面造りもチャレンジするだけの価値はあると思います。

 

オブジェクトに対する設定

まず最初にオブジェクトに使用するシェーダーをトゥーンシェーダーに変更します。
今回使用するモデルはStandardシェーダーだとこんな感じ

f:id:Karvan:20220201212104p:plain

これを前々回に紹介したFlatKitToonシェーダーに変更します。

assetstore.unity.com

Colorのみ指定し、Cell Shading ModeでNone(なし)とすると

f:id:Karvan:20220201212210p:plain

ここからCell Shading Modeを「Step」に変更

f:id:Karvan:20220201212356p:plain

明暗の境界がくっきりと分かれて表示されるようになります。

f:id:Karvan:20220201212457p:plain

 

次に後で付与するアウトラインを補強するために「Rim」の設定を追加します。

f:id:Karvan:20220201212727p:plain

「Rim」は本来、オブジェクトの後ろに光源がある場合の回り込む光の具合を表現するために使いますが、ここでは暗い色を指定して背景からオブジェクトの輪郭が浮かび上がるような形に仕上げます。

f:id:Karvan:20220201212747p:plain

 

最後に「Specular」を設定して明るい部分をさらに明るくして

f:id:Karvan:20220201212928p:plain

Rimで表現する暗い部分が大きくなり過ぎないように調整します。

f:id:Karvan:20220201212905p:plain

 

輪郭線を付ける

輪郭線はポストエフェクトで表示します。シェーダーで表示する手もありますが、それだとあまり綺麗な線が描画できないし、オブジェクト内部のエッジ部分に線が描かれないのでポストエフェクトを使用した方がベターだと思います。
今回はFlatKitToonに付属していたアウトラインエフェクトを使用しています。

f:id:Karvan:20220201213126p:plain

 

これでオブジェクトに対する設定が完了です。同様に設定した背景と合わせるとこんな感じの画面になります。

f:id:Karvan:20220201213224p:plain

 

課題

これで万事OK、というわけではなくて課題もあります。
ポストエフェクトで表示する輪郭線は主に深度情報と法線情報を使って抽出されますが、その抽出の為の設定が以外にシビアです。

特に深度情報を使って輪郭線を抽出する手法では、その閾値を小さくすると地平線のような遠方にある輪郭線が過大に抽出されて塗潰されてしまいます。

f:id:Karvan:20220201213347p:plain

逆に閾値を大きくすると小さな輪郭線が表示されなくなります。下の図だと手前の歩道と道路の段差が輪郭線として表示されません。

f:id:Karvan:20220201213415p:plain

 

下の動画ではカメラが切り替わる毎に輪郭線を抽出する閾値を変更して何とか課題を解決しようと試みていますが、漫画風の画面を作りこむには更に工夫が必要になってきそうです。

f:id:Karvan:20220131001509g:plain

 

【Unity】DOTweenのDOVirtualを使おう

f:id:Karvan:20220125195517p:plain

ストレッチ

リングフィットアドベンチャーは最初のストレッチの段階で疲労してしまう皆さんこんにちは。ストーリーモードをプレイするとリングコンをお腹に押し当てる「腹筋ガード」を異常にやらされるのでお臍の辺りが筋肉痛で辛いです。

 

インプレッション数

毎回ブログを更新するとTwitterの方に告知をするのですが、前回記事の告知ツイートが何故かリツイートされてインプレッション数がいつもとは桁違いでした。
最初は冒頭に載せているゲームの進捗画像が注目を浴びたのか、と喜んでいたのですが、良く調べるとブログの記事がアセット紹介だったのでUnityのアセットストア公式アカウントが記事に反応してリツイートしてくれたおかげでした。ありがとうございます。次は自力でバズれるように頑張ります。(本当にありがとうございます。)

 

DOTween

今更紹介するまでもないのですが「DOTween」というアセットはオブジェクトの移動や回転、拡大縮小を簡便に行えるアセットで、例えば「地点Aに1.5秒で移動する」といった動作(Tween)はAPIコール1つで実装することができます。

assetstore.unity.com


ただ、「1.5秒間でオブジェクトが拡大(縮小)し、最初の大きさに戻る」といった動きをしたい場合は、Sequenceというクラスに「拡大する動作(Tween)」と「もどの大きさに戻る動作(Tween)」をそれぞれ登録してSequenceを実行する(Playメソッドをコールする)必要がありました。

まぁ、そのような実装方式で作業を進めても問題ないのですが、「いったん大きくなって元に戻る」といった単純な動作にわざわざSequenceを使うまでもないかなぁと個人的に思っていて、そういったときはDOTweenのToメソッドを使った方式で実装していました。
この手法についてはかなり前に記事にして紹介しています。

www.karvan1230.com

 

アニメーションカーブを使う

上の記事では拡大縮小の計算にsin,cosの三角関数を使用していますが、もうちょっと拡大縮小の動きに自由度のある方法がないかと考えた時、アニメーションカーブを使う方法が良さそうに思います。
アニメーションカーブはスクリプト内にAnimationCurveクラスを定義するだけでInspector上にアニメーションカーブを編集できるエディタが表示され、任意の形のカーブを編集する事ができます。

public AnimationCurve _customEasing;

今回は「いったん大きくなって元に戻る」という変化を表すカーブを作りました。
縦軸が変化量で横軸が経過時間を表し、どちらも0~1の値をとります。

f:id:Karvan:20220125200304p:plain

縦軸の値は以下の関数から取得できるので、

/// <summary>
/// アニメーションカーブから値取得
/// </summary>
public float GetCurveValue(float time)
{
    return _customEasing.Evaluate(time);
}

timeの値をTweenさせたい時間で0~1に変化させればアニメーションカーブに応じた変化量が取得できます。

 

DOVirtual

上の記事のようにDOTweenのToメソッドを使ってtimeの値を変化させても良いのですが、その場合、変数timeはグローバル変数である必要がでてくるので、もっと手軽にローカル変数のみで実装したい場合は、DOVirtualを使用した方がコード的にもスッキリした形で実装できます。

DOVirtual.Float(開始の値, 終了の値, 時間, コールバック); 

DOVirtual.Floatに渡す第三引数がコールバック関数で、そこにアニメーションカーブに応じた変化量を渡して

/// <summary>
/// ジャンプ動作終了
/// </summary>
public void OnComplete_JumpMove()
{

    DOVirtual.Float(0.0f, // 開始の値
    		1.0f, // 終了の値
    		0.6f, // 時間
    		value => ChangeScale(GetCurveValue(value))); // コールバック

}

/// <summary>
/// スケールの変換
/// </summary>
/// <param name="value"></param>
public void ChangeScale(float value)
{
    this.transform.localScale = new Vector3(OriScale.x + value,
                                            OriScale.y - value,
                                            OriScale.z + value);

}

縦、横のスケールを変化させるようにします。
下の動画では各ペンギンのジャンプ動作が終わったタイミングで縦、横のスケールを変化させています。

f:id:Karvan:20220125200923g:plain

 

◇プライバシーポリシー

●個人情報の利用目的

当ブログでは、メールでのお問い合わせ、メールマガジンへの登録などの際に、名前(ハンドルネーム)、メールアドレス等の個人情報をご登録いただく場合がございます。

これらの個人情報は質問に対する回答や必要な情報を電子メールなどをでご連絡する場合に利用させていただくものであり、個人情報をご提供いただく際の目的以外では利用いたしません。

●個人情報の第三者への開示

当サイトでは、個人情報は適切に管理し、以下に該当する場合を除いて第三者に開示することはありません。

・本人のご了解がある場合
・法令等への協力のため、開示が必要となる場合

個人情報の開示、訂正、追加、削除、利用停止
ご本人からの個人データの開示、訂正、追加、削除、利用停止のご希望の場合には、ご本人であることを確認させていただいた上、速やかに対応させていただきます。

アクセス解析ツールについて

当サイトでは、Googleによるアクセス解析ツール「Googleアナリティクス」を利用しています。

このGoogleアナリティクスはトラフィックデータの収集のためにCookieを使用しています。このトラフィックデータは匿名で収集されており、個人を特定するものではありません。
この機能はCookieを無効にすることで収集を拒否することが出来ますので、お使いのブラウザの設定をご確認ください。

●免責事項

当サイトからリンクやバナーなどによって他のサイトに移動された場合、移動先サイトで提供される情報、サービス等について一切の責任を負いません。

当サイトのコンテンツ・情報につきまして、可能な限り正確な情報を掲載するよう努めておりますが、誤情報が入り込んだり、情報が古くなっていることもございます。

当サイトに掲載された内容によって生じた損害等の一切の責任を負いかねますのでご了承ください。

●プライバシーポリシーの変更について

当サイトは、個人情報に関して適用される日本の法令を遵守するとともに、本ポリシーの内容を適宜見直しその改善に努めます。

修正された最新のプライバシーポリシーは常に本ページにて開示されます。