[Unity] 3Dキャラに武器を持たせる(非エンジニア)

unity上で刀オブジェクトを3Dのキャラに装備させてその刀を抜刀した時に刀の親を鞘から手に切り替える備忘録
Blenderのコンストレイントでトランスフォームコピーの影響値にキーフレームを入れて切り替えようしたがunity上では意図通りにならなかったのでunity内で解決することにした

Unity 2019.4.18f1
Blender 2.83.2

1、武器とキャラを別々のfbxでエクスポートする

位置と回転はunity上で調整できるがBlender内で位置を合わせておけば調整しなくて済む

Blender内の武器の位置

アニメーションを作るときはキャラと武器を一緒に作ってトランスフォームコピーでくっつけておく
unity上でのボーンの座標はヘッドの位置になるのでトランスフォームコピーのヘッド/テールを0にしておく 使用空間はワールド空間↔︎ワールド空間

原点が武器の座標になる

トランスフォームコピー用のボーンを原点(0,0,0)に置いて作成しメッシュの位置を合わせておけばunity上で位置を合わせなくてすむはず

キャラと武器を分ける

武器とキャラを別々のコレクションに分ける
エクスポート時に不要なコレクションをオフにする

いらないものはエクスポートしない

武器fbxにはボーンは必要ないのでメッシュのみエクスポートする
キャラfbxは武器のコレクションを消してエクスポートする

エクスポート設定

▼内容
メッシュとアーマチュアを選択(武器はメッシュのみ)
▼トランスフォーム
!実験的機能!トランスフォーム適用にチェック
▼ジオメトリ
特になし
▼アーマチュア
デフォームボーンのみにチェック
リーフボーン追加のチェックを外す
▼アニメーションをベイク
アニメーションをアクションでエクスポートしたい場合は全アクションにチェックをする
ストリップでエクスポートしたい場合はNLAストリップにチェックをする

2、Unityにインポートする

unityのプロジェクトを開いてProjectウィンドウにfbxをドラッグ&ドロップしてインポートする

インポートしたキャラと武器をHierarchyウィンドウにドラッグ&ドロップする

3、武器をプレファブ化

Resourcesフォルダ作成

Projectフォルダで右クリック >> Create >> Folder からフォルダを作成し名前をResourcesにする(このフォルダ名は重要なので変更しないように)

Prefabフォルダ作成

Resourcesフォルダで右クリック >> Create >> Folder からフォルダを作成し名前をPrefabにする(このフォルダ名は変更しても良いが後で記述するScriptで指定している箇所があるので変更した場合はそちらも書き換える)

武器をプレファブ化

武器をHierarchyウィンドウからProjectウィンドウへドラッグ&ドロップするとプレファブ化できる(プレファブ名はここではSwordとしておく変更する際は後で記述するScriptで指定している箇所があるのでそちらも書き換える)
作成されたプレファブを選択しInspectorウィンドウでPosition(0,0,0) Rotation(0,0,0) Scale (1,1,1)にしておく
プレファブ化の際にOriginal Prefab か Prefab Variant のどちらにするかウィンドウが出る どちらでも良いがここではOriginal Prefabにしておく
(Prefab Variantは差分を作るためのものらしいがよくわからない)

4、くっつける用のGameObjectを作成

からのGameObject作成

メニューの GameObject >> Create Empty から
(Hierarchyウィンドウで右クリック >> Create Emptyでも良い)
空のGameObjectを必要な数だけ作成する(この場合は手と鞘で一つずつ 計二つ)
※ボーンの中に直接GameObjectを作成した場合はScaleが合わない可能性があるので注意

からのGameObjectに名前をつける

わかりやすい名前にする
ここではHandPos(手のPosition)とSheathPos(鞘のPosition)としておく

からのGameObjectに武器プレファブを入れる

作成した空のGameObjectのHandPosとSheathPosにProjectウィンドウの武器のプレファブをドラッグ&ドロップで入れる
このときプレファブはPosition(0,0,0) Rotation(0,0,0) Scale (1,1,1)にしておく
GameObjectはScale (1,1,1)にしておく

この時点でキャラと武器のサイズが合ってない場合はキャラか武器かGameObjectいずれかのScaleが(1,1,1)になっていない可能性があるので合わせる

GameObjectをくっつけるボーンの子にする

Hierarchyウィンドウにあるキャラの▼を押して開く くっつける予定のボーンまで開いておく
GameObjectをくっつけたいボーンにドラッグ&ドロップする
HandPosとSheathPosのGameObject両方をドラッグ&ドロップしたら
武器の位置と回転とサイズを確認する

位置と回転とサイズの調整

Blenderで位置と回転とサイズを調整している場合は
上記のGameObjectを選択し InspectorウィンドウでPosition(0,0,0) Rotation(0,0,0)にする Scaleはそのままで良い

Blenderで位置と回転とサイズを調整していない場合は
ここでGameObjectの位置と回転とサイズを調整する

unityで調整するときはSceneウィンドウの左上あたりにあるボタンでオブジェクトの操作のモードをPivotにしておくのが良い ここがCenterになっているとオブジェクトが原点の位置ではなくオブジェクトの中心を基点に回転することになる

画像2

GameObjectをからにする

調整が済んだら武器のプレファブは削除して空のGameObjectのみにする

5、Scriptファイルを用意する

Scriptファイル作成

Projectウィンドウで右クリック >> Create >> C# Script からScriptファイルを作成しEquipというファイル名にする
(ファイル名を変更する場合は以下のScriptのclass名も変更する public class Equip のところ)

Scriptを記述

Scriptファイルをダブルクリックして開き以下を記述する

using System.Collections;
using UnityEngine;

public class Equip : MonoBehaviour    //class名
{
	//インスタンス化したPrefabの親となるGameObjectを指定するための変数 Inspectorウィンドウで指定する
	public GameObject Hand;    //HandPos用の変数の宣言
	public GameObject Sheath;    //SheathPos用の変数の宣言

	private GameObject obj;    //インスタンス用の変数の宣言
	private string path = "Prefab/Sword";  //プレファブの階層とデータ名用の変数の宣言

	void Start (){
        //インスタンス化
		obj = Instantiate(Resources.Load<GameObject>(this.path),Sheath.transform);
        //インスタンスの親をSheathにする
		obj.transform.SetParent(Sheath.transform);
	}
	void Update (){
		if (Input.GetKeyDown(KeyCode.RightArrow)){
            //親をHandに切り替える
			obj.transform.SetParent(Hand.transform);
			obj.transform.position = Hand.transform.position;
			obj.transform.rotation = Hand.transform.rotation;
		}
		if (Input.GetKeyDown(KeyCode.LeftArrow)){
            //親をSheathに切り替える
			obj.transform.SetParent(Sheath.transform);
			obj.transform.position = Sheath.transform.position;
			obj.transform.rotation = Sheath.transform.rotation;
		}
	}
}

obj = Instantiate(Resources.Load<GameObject>(this.path),Sheath.transform);の意味

Resources.Loadの部分の意味
Resourcesフォルダ以下にあるデータを読み込む
ResourcesフォルダはAssetsフォルダ内のどの階層にあっても良い
this.pathの部分でResourcesフォルダ内の階層とデータ名を指定している
ここでの階層とデータ名というのは"Prefab/Sword"の部分で
ResourcesフォルダのPrefabフォルダにあるSwordプレファブという意味
Resourcesフォルダは名前を変えることはできないがPrefabフォルダとSwordプレファブは任意の名前にできる 階層を増やしても良い
(Resourcesフォルダを別の名前にするとプレファブを読み込めなくなる)

this.pathの部分
ここでは変数を使って指定しているが直接ここに"Prefab/Sword"と記述しても良い

Instantiateの部分の意味
Resources.Loadで読み込んだデータをインスタンス(実体)化する

Sheath.transformの部分の意味
変数SheathのGameObjectのtransform(回転とか座標)にInstantiateしたGameObjectのtransformを合わせている
Sheath.transformの部分を以下のように記述すれば値を入力することもできる 左の数値が座標 右の数値が回転
new Vector3(0.0f,0.0f,0.0f), Quaternion.Euler(0.0f, 0.0f, 0.0f)

分けて記述する場合
ちなみに読み込み(Resources.Load)とインスタンス化(Instantiate)を分けて記述したい場合は以下のようにする(先に読み込んでおいて後でインスタンス化する時に分けて記述する)

obj = Resources.Load<GameObject>(this.path);
obj = Instantiate<GameObject>(obj, Sheath.transform);

以下のように記述しても同じ(書き方がなぜ2つあるのかはわからない)

obj = (GameObject)Resources.Load(this.path);
obj = (GameObject)Instantiate(obj, Sheath.transform);

obj.transform.SetParent(Hand.transform);の意味
objというインスタンスに変数HandのGameObjectをSetParent(親として設定)する
これで親を切り替える

obj.transform.position = Hand.transform.position;の意味
objというGameObjectのposition(座標)をHand(HandPos)のpositionと同じにする
これで位置を合わせる

obj.transform.rotation = Hand.transform.rotation;の意味
objというGameObjectのrotation(回転)をHand(HandPos)のrotationと同じにする
これで回転を合わせる

もし武器を削除する場合は以下のように記述する
Destroy(obj);

if (Input.GetKeyDown(KeyCode.RightArrow)){ }の意味
「→」キーが押された時に{ }内の処理を実行する
ここでは処理の確認のためキー操作で親を切り替えるようにしているが実際には抜刀または納刀のアニメーション再生処理が記述された箇所でSetParentを記述する

KeyCodeのリファレンス

アニメーションの途中で切り替えたい場合はAnimationClipにAnimationEventを入れる

AnimationEvent

AnimationClipを編集する際はfbxから分離する必要がある
分離はAnimationClipを選択して(Windows Ctrl + D)(Mac Command + D)
1、Animationウィンドウを開く Window >> Animation >> Animation
2、AnimationClipを選択しAnimationウィンドウの該当のタイミングで右クリックしてAdd Animation Eventを選択

画像3

3、Animation Eventを選択しInspectorウィンドウのfunctionに任意の名前を入れる 仮にaaaとする
4、Script内に以下のように記述する
※これが記述されたScriptはアニメーションが再生されているキャラにAddComponentされている必要がある

private void aaa ()
{
    // 処理
}

publicで宣言された変数はInspectorウィンドウで表示される
Inspectorウィンドウで変数に値を入れたい場合はpublicで宣言
Script内のみで使用したい変数はprivateで宣言する
何も書かなければprivateとして宣言される
publicにしたくないがInspectorウィンドウには表示したいという場合は以下のように記述する(privateは省略できる)

[SerializeField] private GameObject Hand;

6、Scriptをキャラに設定する

キャラにScriptをドラッグ&ドロップ

作成したProjectウィンドウにあるScriptファイルをHierarchyウィンドウのキャラにドラッグ&ドロップするとキャラのInspectorウィンドウにScriptが追加される

変数に武器プレファブの親となるGameObjectを指定

InspectorウィンドウにHandとSheathの項目があるはずなのでそれぞれに4で作成した空のGameObjectのHandPosとSheathPosを指定する

画像1

7、▶︎実行を押して確認

エラーが出たらConsoleで確認して修正する
Window >> General >> Console

Scriptの理解はまだ十分ではないので説明は間違っている可能性があります自分でも調べてみてください

参考元

2018/5/29
【Unity入門】動的生成の基本!Resources.Loadを覚えよう!
https://www.sejuku.net/blog/55260

2021.01.24
【Unity】TransformのSetParentで親子関係を設定して表示を整理する 
https://ekulabo.com/unity-transform-set-parent

2019-06-02
 Unityちゃんにスマホ持たせてあげた #23
https://www.lovehatetubaki.work/entry/Unity-hand-1

2018/5/22
【Unity入門】Destroyを完全攻略!初心者必見の便利テクも一覧まとめ
https://www.sejuku.net/blog/53555


この記事が気に入ったらサポートをしてみませんか?