# LIVE2D Practice
## 匯入SDK
https://www.live2d.com/en/sdk/download/unity/
https://docs.live2d.com/cubism-sdk-tutorials/getting-started/
## 匯入人物
https://booth.pm/en/search/live2d%E3%83%A2%E3%83%87%E3%83%AB?max_price=0
https://booth.pm/en/items/6235947
# 11/18
## 視線追蹤
### 效果

### 人物Root放一個CubismLookController

### ParamAngleY放一個CubismLookParameter

## 看向鼠標
```csharp=
public class LookMouse : MonoBehaviour
{
public Transform target; //取得Target
// Update is called once per frame
void Update()
{
//獲得鼠標位置
print($"mouse position : {Input.mousePosition}");//(0,0)~(1920,1080)
var pos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
print($"world position : {pos}");
//Target設成鼠標位置
target.position = pos;
}
}
```
## 自動眨眼

### 人物Root加入CubismEyeBlinkController跟CubismAutoEyeBlinkInput

### 眼睛的Parameter加入CubismEyeBlinkParameter

:::info
若MODEL有照標準命名
眨眼會是ParamEyeLOpen、ParamEyeROpen

來源:https://docs.live2d.com/zh-CHS/cubism-editor-manual/standard-parameter-list/
:::
## 循環動作

## 表情熱鍵
```charp
using System;
using System.Collections;
using System.Collections.Generic;
using Live2D.Cubism.Framework.Expression;
using UnityEngine;
public class ExpressionHotkey : MonoBehaviour
{
public CubismExpressionController _controller;
[Serializable]
public struct ExpressionMapping //熱鍵設定
{
public KeyCode key; //鍵位
public int expressionIndex; //表情號碼
public string name; //表情名子
}
public List<ExpressionMapping> _mappings; // 熱鍵列表
void Start()
{
for (int i = 0; i < _controller.ExpressionsList.CubismExpressionObjects.Length; i++)
{
print($"{i} : {_controller.ExpressionsList.CubismExpressionObjects[i].name}");
}
//Keycode -> expressionIndex(int)
_mappings = new List<ExpressionMapping>();
int expressionLength = _controller.ExpressionsList.CubismExpressionObjects.Length;
for (int i = 0; i < expressionLength; i++)
{
KeyCode keycode = KeyCode.Alpha0 + i; //Alpha0, Alpha1, Alpha2......
_mappings.Add(new ExpressionMapping()
{
key = keycode, //Alpha0, Alpha1, Alpha2......
expressionIndex = i, //表情號碼0, 1, 2......
name = _controller.ExpressionsList.CubismExpressionObjects[i].name
});
}
}
// Update is called once per frame
void Update()
{
for (int i = 0; i < _mappings.Count; i++)
{
if (Input.GetKey(_mappings[i].key))
{
_controller.CurrentExpressionIndex = _mappings[i].expressionIndex;
}
}
}
}
```
# 11/25
## UI播動畫
```csharp
using Live2D.Cubism.Core;
using Live2D.Cubism.Framework.Motion;
using Live2D.Cubism.Framework.MotionFade;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MotionHotkey : MonoBehaviour
{
private CubismModel model;
public CubismParameter[] parameters;
public CubismFadeController fadeController;
public Dictionary<string, CubismParameter> lookupTable = new();
[Serializable]
public struct Motion
{
public CubismParameter[] Parameters;
public AnimationCurve[] Curves;
}
public List<Motion> motionList;
private void Start()
{
if (TryGetComponent(out model))
{
parameters = model.Parameters;
foreach (var para in parameters)//建立查找表
{
lookupTable.Add(para.Id, para);//查找表填入每個ID對應的Parameter
}
motionList = new List<Motion>();
int length = fadeController.CubismFadeMotionList.CubismFadeMotionObjects.Length;//取得動畫個數
for (int i = 0; i < length; i++)//巡迴每個動畫
{
CubismFadeMotionData data = fadeController.CubismFadeMotionList.CubismFadeMotionObjects[i];//動畫資料
int paramerterCount = data.ParameterIds.Length;//動畫資料裡用到ID的總數
Motion motion = new Motion();
motion.Parameters = new CubismParameter[paramerterCount];//建立跟總數一樣長度的Array
motion.Curves = new AnimationCurve[paramerterCount];//建立跟總數一樣長度的Array
for (int j = 0; j < paramerterCount; j++)//巡迴每個ID
{
string id = data.ParameterIds[j];//拿到ID
AnimationCurve curve = data.ParameterCurves[j];//拿到動畫曲線
if (lookupTable.TryGetValue(id, out CubismParameter para) && para != null)//如果查找表裡知的有叫這個ID的參數
{
motion.Parameters[j] = para;//紀錄參數
motion.Curves[j] = curve;//紀錄對應的動畫曲線
}
motionList.Add(motion);//加到動畫清單裡
}
}
}
}
public void PlayMotion(int index)
{
if (index >= 0 && index < motionList.Count)
{
StopAllCoroutines();
Motion motion = motionList[index];
for (int i = 0; i < motion.Parameters.Length; i++)
{
StartCoroutine(PlayMotion(motion.Parameters[i], motion.Curves[i]));
}
}
}
public IEnumerator PlayMotion(CubismParameter parameter,AnimationCurve curve)
{
float t = 0;
while (t <= curve.length)
{
float value = curve.Evaluate(t);
parameter.Value = value;
print($"{parameter.Id}:{value}");
t += Time.deltaTime;
yield return null;
}
}
}
```
# 12/02
## 物件跟隨
1. 人物所有部位(ArtMesh)加上CubismRaycastable

2. 人物Root加上加上CubismRaycaster

### 印出物件名稱
```csharp
using Live2D.Cubism.Core;
using Live2D.Cubism.Framework.Raycasting;
using UnityEngine;
public class ObjectFollow : MonoBehaviour
{
public CubismRaycaster raycaster;
public CubismDrawable selectedDrawable;
private void Update()
{
var ray = Camera.main.ScreenPointToRay(Input.mousePosition); //0,0~ 1920*1080
Debug.DrawRay(ray.origin, ray.direction * Camera.main.farClipPlane, Color.green, 1);
CubismRaycastHit[] result = new CubismRaycastHit[1];
if (raycaster.Raycast(ray.origin,ray.direction * Camera.main.farClipPlane,result) > 0)
{
print($"{result[0].Drawable.name} : {result[0].WorldPosition}");
}
}
}
```
:::info
結果:

:::
### 物件貼到座標
假設戴一頂帽子

帽子圖片放到新建的SpriteRenderer上

```charp
using Live2D.Cubism.Core;
using Live2D.Cubism.Framework.Raycasting;
using UnityEngine;
public class ObjectFollow : MonoBehaviour
{
public CubismRaycaster raycaster;
public CubismDrawable selectedDrawable;
public int closestPointIndex;
public Vector3 closestPoint;
public Transform Hat;
public Vector3 offset;
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
var ray = Camera.main.ScreenPointToRay(Input.mousePosition); //0,0~ 1920*1080
Debug.DrawRay(ray.origin, ray.direction * Camera.main.farClipPlane, Color.green, 1);
if (raycaster.Raycast(ray.origin,ray.direction * Camera.main.farClipPlane,result) > 0)
{
print($"{result[0].Drawable.name} : {result[0].WorldPosition}");
selectedDrawable = result[0].Drawable;//滑鼠選到的部位
float min = Mathf.Infinity;//先設無限大
for (int i = 0; i < selectedDrawable.VertexPositions.Length; i++)
{
float dis = Vector3.Distance(result[0].Drawable.VertexPositions[i],
result[0].WorldPosition);//計算滑鼠點擊座標跟每個模型polygon座標的距離
if (dis < min)//才會從無限大慢慢比較越來越小
{
closestPointIndex = i;//得到第幾個polygon是離滑鼠最近的
min = dis;
}
closestPoint = selectedDrawable.VertexPositions[closestPointIndex];//取得模型上距離最近polygon的座標
offset = result[0].WorldPosition - closestPoint;//offset回去滑鼠點到的座標
//強制把帽子Z軸設在相機前面
offset.z = Camera.main.transform.position.z + Camera.main.nearClipPlane;//相機起始點=相機Z軸座標+相機nearClipPlane(開始渲染範圍)
}
}
else
{
selectedDrawable = null;
}
}
if (selectedDrawable!=null)
{
closestPoint = selectedDrawable.VertexPositions[closestPointIndex]+transform.position;//取得模型上距離最近polygon的座標 + 人物在場景中的座標
Hat.position = closestPoint + offset;
}
}
```
記得把帽子放到Hat欄位

### 最終完整程式
```csharp
using Live2D.Cubism.Core;
using Live2D.Cubism.Framework.Raycasting;
using UnityEngine;
public class ObjectFollow : MonoBehaviour
{
public CubismRaycaster raycaster;
public CubismDrawable selectedDrawable;
public int closestPointIndex;
public int farestPointIndex;
public Vector3 closestPoint;
public Vector3 farestPoint;
public Vector3 startDirection;
public float angle;
public Transform Hat;
public Vector3 offset;
public CubismRaycastHit[] result = new CubismRaycastHit[1];
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
var ray = Camera.main.ScreenPointToRay(Input.mousePosition); //0,0~ 1920*1080
Debug.DrawRay(ray.origin, ray.direction * Camera.main.farClipPlane, Color.green, 1);
if (raycaster.Raycast(ray.origin,ray.direction * Camera.main.farClipPlane,result) > 0)
{
print($"{result[0].Drawable.name} : {result[0].WorldPosition}");
selectedDrawable = result[0].Drawable;//滑鼠選到的部位
float min = Mathf.Infinity;//先設無限大
float max = 0;//先設0
for (int i = 0; i < selectedDrawable.VertexPositions.Length; i++)
{
float dis = Vector3.Distance(result[0].Drawable.VertexPositions[i],
result[0].WorldPosition);//計算滑鼠點擊座標跟每個模型polygon座標的距離
if (dis < min)//才會從無限大慢慢越比較越變小
{
closestPointIndex = i;//得到第幾個polygon是離滑鼠最近的
min = dis;
}
if (dis > max)
{
farestPointIndex = i;
max = dis;
}
closestPoint = selectedDrawable.VertexPositions[closestPointIndex];//取得模型上距離最近polygon的座標
farestPoint = selectedDrawable.VertexPositions[farestPointIndex];
startDirection = farestPoint - closestPoint;
offset = result[0].WorldPosition - closestPoint;//offset回去滑鼠點到的座標
//強制把帽子Z軸設在相機前面
offset.z = Camera.main.transform.position.z + Camera.main.nearClipPlane;//相機起始點=相機Z軸座標+相機nearClipPlane(開始渲染範圍)
}
}
else
{
selectedDrawable = null;
}
}
if (selectedDrawable!=null)//如果有選到人物部位
{
closestPoint = selectedDrawable.VertexPositions[closestPointIndex] + transform.position;//取得模型上距離最近polygon的座標 + 人物在場景中的座標
farestPoint = selectedDrawable.VertexPositions[farestPointIndex] + transform.position;
angle = Vector3.SignedAngle(startDirection, (farestPoint - closestPoint), startDirection);//計算目前選到部位,最近跟最遠的Polygon方向跟一開始滑鼠點的時候差多少度了
Hat.position = closestPoint + offset;
Hat.rotation = Quaternion.Euler(0, 0,angle );//旋轉Z軸
}
}
}
```
