テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】LWRPの2D向けライト機能を試してみる

f:id:tsubaki_t1:20190708212813g:plain

今回はLWRPに追加された2Dライトを試してみます。

2Dにライト

最近の海外で2Dなゲームでは、2Dでもライティング…正しくは法線効果で光源の位置を示しているゲームをよく見ます。

そのやり方については下の記事で紹介していましたが、LWRPに標準機能として2Dライティングの機能が追加されました。

tsubakit1.hateblo.jp

大雑把に言えば、以下の機能が追加されています。

  • スプライトにノーマル等の補助用テクスチャ追加
  • 2DスプライトShaderがノーマル計算用に参照するライト

実際に使ってみます

導入手順

まず Unity 2019.2Light Weight 6.7.1 をインポートします。 インポートは何時も通り Package Managerで。

f:id:tsubaki_t1:20190708213553j:plain

導入後にレンダリングパイプラインをLWRPに変更し、2D用カスタムパイプラインを導入します。

  1. Assets>Createのメニューから、Pipeline Assets2D Renderer を作成
  2. Pipeline Assetsの Renderer Typeカスタム に変更して、2D Renderer を登録
  3. プロジェクト設定グラフィックススクリプタブルレンダーパイプライン設定 を、先程作成したパイプラインに設定

f:id:tsubaki_t1:20190708213801j:plain

f:id:tsubaki_t1:20190708214135j:plain

f:id:tsubaki_t1:20190708214143j:plain

これで準備完了です。

とりあえずライトを試す

とりあえず2Dライトを試してみます。

LWRPの2Dを導入すると、ライトに 2D の項目が追加されます。このライト設定はスプライトにも影響します。

光の見え方は Inner RadiusOuter Radius 、それと Fall off Intensity で大体決まります。

f:id:tsubaki_t1:20190708214725j:plain

f:id:tsubaki_t1:20190708214839j:plain

f:id:tsubaki_t1:20190708215231g:plain

ライトの形状は、円形だけでなくポリゴンの形状や、スプライトの切り抜き等が可能みたいです。例えば 「出口を光らせる」のような演出をしたい場合や、ユニークな発光現象を表現したい場合でも使えます。

f:id:tsubaki_t1:20190708220202j:plain
Freedom Lightで矩形の光源(右下の緑)

f:id:tsubaki_t1:20190708220232j:plain
Sprite Lightでスプライトの形状にくり抜く

ちなみにライトは加算だけでなく減算や他のブレンドも可能みたいです。ダークライト…普通に3Dでも欲しい*1

f:id:tsubaki_t1:20190708222326j:plain

陰影表現の追加

光源の向きを示すため、ノーマルマップを使用して陰影表現してみます。

f:id:tsubaki_t1:20190708221513j:plain

やることは単純で、スプライトにノーマル等のテクスチャを追加します。

  1. Sprite Editor を開く
  2. Secondary Texture を選択
  3. + ボタンで要素を増やして、名称を _NormalMap に設定
  4. _NormalMapにノーマルマップを追加

あとはライトで Use Normal Mapにチェックを入れます。

f:id:tsubaki_t1:20190708220805j:plain

f:id:tsubaki_t1:20190708221025j:plain

この手順はTilemapに使用する場合でも有効です。

f:id:tsubaki_t1:20190708222032j:plain

注意事項

  • 現状SpriteAtlasには対応していません。SpriteAtlasでパッキングすることでUVが変化してしまうので。
  • 対応しているのは「陰影」で、影には対応していません。
  • ライトのZの位置も影響します。Zが大きく外れていると、GameViewに反映しない事があります。
  • 最初に表示される最新のLWRPバージョンが6.7.1より下の場合、一覧表示すれば選択出来るようになります。
  • このバージョンのLWRPには、Pixel perfect Cameraが含まれます

f:id:tsubaki_t1:20190708222717j:plain

*1:暗黒の光とか言ってはいけない。いいね?

【Unity】AddressableでSprite ModeがMultipleなスプライトを使用する

AddressableでMultiSpriteを使用する方法についてです。

Unity 2019.1 Addressable 0.8.4 preview

追記:1.1.4で問題なく使えるようになりました

    async void Start()
    {
        var sprites = await Addressables.LoadAssetAsync<IList<Sprite>>("sprites").Task;

        foreach (var sprite in sprites)
        {
            Debug.Log(sprite.name);
        }
    }

マルチスプライト

Unityで2Dゲームを作るとき、2Dスプライトをパッキングすることがあります。 このとき、SpriteAtlasを使うか、パッキングしたテクスチャをSpriteEditorでスライスするかを選択出来ます。

f:id:tsubaki_t1:20190617230856j:plain

今回はSpriteをSpriteEditorでパッキングした場合の、Addressable対応についてです。

AddressableはSubAssetを直接ロード出来ない

結論から言えば、パッキングしたスプライトをロードすることは出来ません。
正確に言えば、SubAssetというカテゴリに当たるファイルを、直接指定してロードすることが出来ません。

これはTextureからSprite一覧を取り出したり、FBXからAnimationClipを抽出したり等です。

厄介な事にFastModeでは動いてしまうので一見問題が無いように見えますが、Packed Modeにすると動かない事が確認出来ます。例えば下のコードはPackedだと動きません。

public class LoadSprite : MonoBehaviour
{
    [SerializeField] int id;
    
    async void Start()
    {
        var sprites = await Addressables.LoadAssetAsync<Sprite[]>("sprite").Task; // packed だとエラー
       GetComponent<SpriteRenderer>().sprite = sprites[id];
    }
}

これはSpriteAnimationといった「アセットを使用しているだけ」のケースでは問題無いのですが、キャラクターアイコン等の「スプライトをケースバイケースで差し替える」といった場合に問題になるかもしれませn

解決方法

解決方法は、SubAssetでなくす事です。アプローチは幾つかあります。

アイディア1: スプライトを取り出す

テクスチャからSpriteを取り出します。例えばCtrl + Dで複製する等。実際にはエディター拡張でやるべきですが、まぁそれはそれ。

Spriteは「メッシュ&UV&テクスチャへの 参照 」の情報なので、複製して通常のアセットにしてやれば、普通にロードが可能です。

f:id:tsubaki_t1:20190617234443j:plain

アイディア2:SpriteAtlasに格納する

SpriteAtlasに格納する方法もあります。

SpriteAtlasにスプライトを設定すると、スプライトは再パッキングされます。そのかわり spriteAtlas.GetSprite(str) でスプライトを取得出来ます。

UVが既存のテクスチャから変化する点に注意です。またパック元になるテクスチャは必ず無圧縮にします。

f:id:tsubaki_t1:20190618000513j:plain

アイディア3:適当なScriptableObjectに参照させておく

UV等の情報を維持したままスプライトをロードしたい場合、適当なScriptableObjectから参照しておき、それをロードさせるというアイディアもあります。

例えば下のようなScriptableObjectを用意しておき、スプライトをすべて登録しておきます。

using UnityEngine;

[CreateAssetMenu(menuName ="SpriteRefs")]
public class Sprites : ScriptableObject
{
    public Sprite[] value;
}

f:id:tsubaki_t1:20190617235535j:plain

あとは普通にScriptableObjectをロードすれば、そのScriptableObjectが参照するSpriteを取得出来ます。

public class LoadSprite : MonoBehaviour
{
    [SerializeField] int id;
    
    async void Start()
    {
        var sprites = await Addressables.LoadAssetAsync<Sprites>("sprites").Task;
       GetComponent<SpriteRenderer>().sprite = sprites.value[id];
    }
}

感想

SubAssetがロード出来ないという問題があるので少々面倒ですが、一応回避方法はあるという事で。

関連

tsubakit1.hateblo.jp

tsubakit1.hateblo.jp

【Unity】TextMeshProのテキストに表示するスプライトをAddressableから取得する

TextMeshPro はスプライトをAssetBundleから取得してみます。

検証環境

addressable 0.8.6 unity 2019.3.0 a3

TextMeshProのスプライトをAddressableに格納する

下の記事を参考に、テクスチャからSpriteAssetを生成します。

www.snoopopo.com

作成したSpriteAssetにアドレス spritesheet を設定しておきます。

f:id:tsubaki_t1:20190530013425p:plain

TextMeshProのスプライトをテキストに適応する

スプライトをロードして、実際にテキストに反映させます。

コード的にはTextMeshProUGUI.spriteAssetにロードしたスプライトを設定するだけで良いです。

using UnityEngine;
using TMPro;
using UnityEngine.AddressableAssets;

public class LoadSpriteSheet : MonoBehaviour
{
    async void Start()
    {
        var spritesheet = await Addressables.LoadAssetAsync<TMP_SpriteAsset>("spritesheet").Task;
        GetComponent<TextMeshProUGUI>().spriteAsset = spritesheet;
    }
}

あとはリッチテキストでスプライトを表示させれば、スプライトが表示されます。例えばスプライト名が 8bit_1の場合、下のようなテキストで表示されます。 なお、これで設定したSpriteSheetは、TMP SettingsのDefault Sprite Assetで設定したスプライトより優先されます。

sprite <sprite name="8bit_1">test

f:id:tsubaki_t1:20190530013735j:plain

もしテキスト毎にSpriteSheetを追加するのが面倒くさいという場合、下のようなコードで追加しても良いかもしれません。

ただし下のコードはTMP_Settingsが上書きされる点に注意です。

        spritesheet = await Addressables.LoadAssetAsync<TMP_SpriteAsset>("spritesheet").Task;
        TMP_Settings.defaultSpriteAsset.fallbackSpriteAssets.Add(spritesheet);

【Unity】Timelineのアニメーションに微調整を加える(Humanoid編)

前回に引き続き、Timelineのアニメーションに微調整を加える方法です。

今回はHumanoid向け

HumanoidはTimelineでアニメーションを上書きしにくい

Humanoid Rigで動く部位の場合、Genericと同じ方法でアニメーションの微調整が出来ません。というのも、Timelineから生成するアニメーションは基本的にGenericになるので、Humanoidとは互換性がない為です。

(つまり髪や手首といった

なので、Humanoidのアニメーションを微調整したい場合、IK を使用するか、Constraint で少し動作に追加エフェクトをかける必要があります。

f:id:tsubaki_t1:20190523223841j:plain

IKを使用する場合

IKを使用する場合、AnimatorのIKを使用して手や足の位置を調整します。

IKの設定は普通に行います。例えば下のようなコンポーネントをIKを使用するAnimatorに設定します。

using UnityEngine;

[RequireComponent(typeof(Animator))]
public class CharacterIK : MonoBehaviour
{
    [SerializeField] AvatarIKGoal goal;  // どの部位のIKを使用するか
    [SerializeField] AvatarIKHint hint;  // goalと同じ部位のヒントを選択
    [SerializeField] Transform goalTransform;// 最終的な位置
    [SerializeField] Transform hintTransform;// 肘や膝の位置のヒント

    [SerializeField][Range(0, 1)] float weight, hintWeight;

    private Animator animator;

    void Start()
    {
        animator = GetComponent<Animator>();
    }

    private void OnAnimatorIK(int layerIndex)
    {
        animator.SetIKPosition(goal, goalTransform.position);
        animator.SetIKHintPosition(hint, hintTransform.position);

        animator.SetIKHintPositionWeight(hint, hintWeight);
        animator.SetIKPositionWeight(goal, weight);
    }
}

f:id:tsubaki_t1:20190523224340j:plain

あとはIKを動かせるようにする訳ですが、ここでのポイントがAnimatorControllerでIKを有効化する事です。Timelineで動かす場合もIKの設定は有効になるみたいです。

もし対象がカットシーン専用データ(AnimatorControllerを持っていない)場合は、空のAnimatorController(AnimationClipが一切登録されていない)でもOKみたいです。

f:id:tsubaki_t1:20190523224554j:plain

これでTimeline中でも視点や手の位置といった部位は調整出来ました。

個人的にはTimelineの仕組みを考えるとバグな気もしますが、動きます。

Constraintを使用する場合

IKが使用できないケース…例えば腕の向きを僅かに変更したい…つまりFK的なアプローチが必要な場合は、こちらで行います。

これは正直なところ、コードのLateUpdateで更新する方が理にかなっているような気もしますが、面倒くさいのでConstraintで云々してしまいます。例えば足を少し調整したい場合

  • Rotation Constraint を足のボーンに追加
  • Rotation Constraintに足のボーンとエフェクターを設定
  • 有効な方のウェイトを1にして、他を0にする

という感じで行けます。

f:id:tsubaki_t1:20190523230029j:plain

関連

今回のIKのアプローチを使用してボーンを動かすプロダクトです。

github.com