【Unity, Teddy, 3Dモデリング】描いた絵をキャラクターに取り付ける方法

はじめに
この記事ではTeddyで描いた絵をモデルに取り付ける方法を紹介します。
TeddyRuntimeDemo.cs
using UnityEngine;
using UnityEngine.EventSystems;
using System;
using System.Linq;
using System.Collections;
//List用
using System.Collections.Generic;
using Teddy;
namespace TeddyDemo
{
public class TeddyRuntimeDemo : MonoBehaviour {
public enum OperationMode
{
Default,
Draw,
Move,
Cut
};
[SerializeField] new Camera camera;
[SerializeField] Shader lineShader;
[SerializeField] GameObject prefab;
[SerializeField] bool simulate;
Material lineMat;
OperationMode mode;
List<Vector2> screenpoints;
List<Vector3> points;
List<Puppet> puppets;
Puppet selected;
Vector3 origin;
Vector3 startPoint;
//手書きの円を表示する場所をずらすオフセット
const float zOffset = 10f;
//シーン開始時のカメラのベクトル
Vector3 initialCameraPosition;
//生成したオブジェクトを取り付ける親オブジェクト
GameObject parent;
void Start () {
//カメラが無いなら追加
if(camera == null) {
camera = Camera.main;
}
screenpoints = new List<Vector2>();
points = new List<Vector3>();
puppets = new List<Puppet>();
lineMat = new Material(lineShader);
simulate = false;
initialCameraPosition = camera.transform.position;
}
void Update () {
//ボタンなどのuGUIをクリックしたときは画面クリックを無視する
if (EventSystem.current.IsPointerOverGameObject()) return;
//マウスの座標を取得
var screen = Input.mousePosition;
switch (mode)
{
case OperationMode.Default:
if (Input.GetMouseButtonDown(0))
{
//Listを全て消去
points.Clear();
screenpoints.Clear();
// メインカメラからクリックしたポジションに向かってRayを撃つ。
var ray = camera.ScreenPointToRay(screen);
RaycastHit hit;
//Rayとオブジェクトが衝突していないかを調べる
if (Physics.Raycast(ray.origin, ray.direction, out hit, float.MaxValue))
{
//RayがあたったオブジェクトのPuppetクラスを取得
var puppet = hit.collider.GetComponent<Puppet>();
//RayがヒットしたオブジェクトにPuppetクラスがあったら
if(puppet != null)
{
startPoint = camera.ScreenToWorldPoint(screen);
//オブジェクトに物理演算を付与
selected = puppet;
selected.Select();//body.isKinematic = true;
//クリックしたスクリーン座標をRayがヒットしたオブジェクトのワールド座標にする
startPoint = hit.point;
//Rayの始点を、ヒットしたオブジェクトの座標へ移動
origin = selected.transform.position;
mode = OperationMode.Move;
}
else
{
mode = OperationMode.Draw;
}
}
//Rayの先にオブジェクトが無い場合
else
{
mode = OperationMode.Draw;
}
}
//右ボタンをクリックでカット
else if(Input.GetMouseButtonDown(1))
{
points.Clear();
screenpoints.Clear();
mode = OperationMode.Cut;
}
break;
//Game画面上で黒い手書きの線の表示
case OperationMode.Draw:
if (Input.GetMouseButtonUp(0))
{
Build();
mode = OperationMode.Default;
}
else
{
var point = camera.ScreenToWorldPoint(new Vector3(screen.x, screen.y, camera.nearClipPlane + zOffset));
points.Add(point);
screenpoints.Add(new Vector2(screen.x, screen.y));
}
break;
case OperationMode.Move:
if (Input.GetMouseButtonUp(0))
{
selected.Unselect();
Simulate(simulate);
selected = null;
mode = OperationMode.Default;
}
else
{
var currentPoint = camera.ScreenToWorldPoint(new Vector3(screen.x, screen.y, camera.nearClipPlane + zOffset));
var offset = currentPoint - startPoint;
selected.transform.position = origin + offset;
}
break;
case OperationMode.Cut:
if(Input.GetMouseButtonUp(1))
{
Cut();
mode = OperationMode.Default;
} else
{
var point = camera.ScreenToWorldPoint(new Vector3(screen.x, screen.y, camera.nearClipPlane + zOffset * 0.5f));
points.Add(point);
screenpoints.Add(new Vector2(screen.x, screen.y));
}
break;
}
}
//----------------------------------
//線を引く
//----------------------------------
void Build ()
{
if (screenpoints.Count > 5)
{
var go = Instantiate(prefab);
try
{
Create(go, screenpoints);
var puppet = go.GetComponent<Puppet>();
puppets.Add(puppet);
Simulate(simulate);
} catch(Exception)
{
Destroy(go);
}
}
}
void Cut()
{
for(int i = 0, n = screenpoints.Count; i < n; i++)
{
var screen = screenpoints[i];
var ray = camera.ScreenPointToRay(screen);
RaycastHit hit;
if (Physics.Raycast(ray.origin, ray.direction, out hit, float.MaxValue))
{
var teddy = hit.collider.GetComponent<Teddy.Teddy>();
if(teddy != null)
{
try
{
TeddyUtility.CutMesh(teddy, camera, screenpoints);
// Copy material for sub meshes
var filter = teddy.GetComponent<MeshFilter>();
var renderer = teddy.GetComponent<MeshRenderer>();
var mat = renderer.sharedMaterial;
int count = filter.sharedMesh.subMeshCount;
var materials = new Material[count];
for(int j = 0; j < count; j++)
{
materials[j] = mat;
}
renderer.sharedMaterials = materials;
} catch
{
}
break;
}
}
}
}
public void Reset()
{
Simulate(true);
puppets.ForEach(puppet => {
puppet.Ignore();
Destroy(puppet.gameObject, 10f);
});
puppets.Clear();
}
//-----------------------------------------
//手書きのオブジェクトを生成する
//------------------------------------------
void Create (GameObject go, List<Vector2> screens)
{
//カメラのRotateの角度を取得
var cameraQuaternion = camera.transform.rotation;
//(Closureクラスが見つからない。たぶんTeddyRuntime.dllの中に入っていると思われます)
var closure = Closure.GetClosure(screens);
if (closure.Count > 2)
{
closure.RemoveAt(0);
closure.RemoveAt(closure.Count - 1);
}
screens.AddRange(closure);
//screenの座標を引数にして、camera.ScreenToWorldPointの座標を返す
var world = screens.Select(screen => {
var makePos = camera.ScreenToWorldPoint(new Vector3(screen.x, screen.y, camera.nearClipPlane + zOffset));
//cameraが90°、または-90°のとき、手書きの円で作ったポイントをxy座標へ転写
if (cameraQuaternion.eulerAngles.y == 90f)
{
makePos = Quaternion.Euler(0f, 90f, 0f) * makePos;
}
else if(cameraQuaternion.eulerAngles.y == -90f || cameraQuaternion.eulerAngles.y == 270f)
{
makePos = Quaternion.Euler(0f, -90f, 0f) * makePos;
}
return makePos;
}).ToList();//ToList()で、screens全体にbody.isKinematic = true;を付与
//go オブジェクトをカメラの向きに合わせる
//go.transform.rotation = camera.transform.rotation;
var points = world.Select(p => new Vector2(p.x, p.y)).ToList();
// create lowpoly mesh for MeshCollider vertex count limit.(APIの部分)
var lowpoly = TeddyUtility.CreateMesh(points, 200, 0.85f);
var collider = go.GetComponent<MeshCollider>();
collider.sharedMesh = lowpoly;
TeddyUtility.CreateTeddy(go, points, 600, 0.85f);
//生成したオブジェクトの色を変更する
go.GetComponent<Renderer>().material.color = Color.red;
//カメラ移動した分のベクトルを取得
var offsetPos = camera.transform.position - initialCameraPosition;
//goをの位置ベクトルを取得
var goPos = go.transform.position;
//カメラがのy軸が0, 90, 180°, 270のとき
if(cameraQuaternion.eulerAngles.y == 0f)
{
goPos = new Vector3(0f, goPos.y, offsetPos.z);
}
else if(cameraQuaternion.eulerAngles.y == 180f)
{
//カメラを基準にx軸対象にPuppetを移動させる
offsetPos.z -= (offsetPos.z - camera.transform.position.z) * 2f;
goPos = new Vector3(0f, goPos.y, offsetPos.z);
}
else if(cameraQuaternion.eulerAngles.y == 90f)
{
go.transform.rotation = Quaternion.Euler(0f, -90f, 0f);
goPos = new Vector3(offsetPos.x + zOffset, goPos.y, 0f);
}
else if (cameraQuaternion.eulerAngles.y == -90f || cameraQuaternion.eulerAngles.y == 270f)
{
go.transform.rotation = Quaternion.Euler(0f, 90f, 0f);
goPos = new Vector3(offsetPos.x - zOffset, goPos.y, 0f);
}
//goを手書きの円の位置に移動
go.transform.position = goPos;
//goを指定したオブジェクトの子に配置
if(parent != null)
{
go.transform.SetParent(parent.transform, true);
}
else
{
Debug.Log("部位を選択してください。");
}
}
//-------------------------------
//生成したオブジェクトのisKinematicの切り替え
//-------------------------------
public void OnToggleSimulate(bool flag)
{
simulate = flag;
Simulate(simulate);
}
void Simulate (bool flag)
{
puppets.ForEach(puppet =>
{
puppet.body.isKinematic = !flag;
});
}
//----------------------------------
//ゲーム画面でドラッグした時に線を引く
//-------------------------------------
void OnRenderObject()
{
GL.PushMatrix();
lineMat.SetPass(0);
GL.Begin(GL.LINES);
GL.Color(Color.black);
for(int i = 0, n = points.Count; i < n - 1; i++)
{
GL.Vertex(points[i]);
GL.Vertex(points[i + 1]);
}
GL.End();
GL.PopMatrix();
}
//---------------------------------------------------
//生成したオブジェクトを取り付ける親オブジェクトの選択
//--------------------------------------------------
public void OnRightHand()
{
parent = GameObject.Find("unitychan/Character1_Reference/Character1_Hips/Character1_Spine/Character1_Spine1/Character1_Spine2/Character1_RightShoulder/Character1_RightArm/Character1_RightForeArm/Character1_RightHand").gameObject;
Debug.Log("a" + parent.name);
}
}
}
取り付けるモデルの配置
モデルは何でもいいので、今回はユニティーちゃんを使用しました。また、体の部位を選択するボタン「SelectBodyButton」を作成しました。これを押すと、選択した体の部位に、手書きのオブジェクトが子オブジェクトとしてセットされます。
では、TeddyRuntimeDemo.cs内に以下のコードを足します。
グローバル変数
//生成したオブジェクトを取り付ける親オブジェクト
GameObject parent;Create () メソッドの最下部
SetParent(parent.transform, true); でpearent オブジェクトの子にgoを配置できます。
//goを指定したオブジェクトの子に配置
if(parent != null)
{
go.transform.SetParent(parent.transform, true);
}
else
{
Debug.Log("部位を選択してください。");
}新規メソッド OnRightHand()
//---------------------------------------------------
//生成したオブジェクトを取り付ける親オブジェクトの選択
//--------------------------------------------------
public void OnRightHand()
{
parent = GameObject.Find("unitychan/Character1_Reference/Character1_Hips/Character1_Spine/Character1_Spine1/Character1_Spine2/Character1_RightShoulder/Character1_RightArm/Character1_RightForeArm/Character1_RightHand").gameObject;
Debug.Log(parent.name);
}ここではユニティちゃんの右手を取得しています。
(Optimize Transform Hierarchy を使えば見た目の階層を減らせます)
最後に、TeddyRutimeDemo.csのOnRightHand () をSelectBodyButtonに取り付けてください(※正確にはSelectBodyButton/RightHand のButtonコンポーネントです。SelectBodyButtonは空オブジェクトなので)。
これで準備完了です。実行すると、描いた絵がユニティーちゃんの右手の子オブジェクトとして張り付きます。
