---
# System prepended metadata

title: LIVE2D Practice

---

# 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
## 視線追蹤
### 效果
![image](https://hackmd.io/_uploads/H1ERk7_zyl.png)
### 人物Root放一個CubismLookController
![image](https://hackmd.io/_uploads/H1GylQuMyx.png)
### ParamAngleY放一個CubismLookParameter
![image](https://hackmd.io/_uploads/HJDxgmdGkl.png)

## 看向鼠標
```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;
    }
}
```

## 自動眨眼
![image](https://hackmd.io/_uploads/Hkf49Qdzyx.png)
### 人物Root加入CubismEyeBlinkController跟CubismAutoEyeBlinkInput
![{F3C2F976-A5B5-4B52-8ACA-47ED4D8032CD}](https://hackmd.io/_uploads/B18_G6PMJl.png)
### 眼睛的Parameter加入CubismEyeBlinkParameter
![image](https://hackmd.io/_uploads/Hyweqm_Mke.png)
:::info
若MODEL有照標準命名
眨眼會是ParamEyeLOpen、ParamEyeROpen
![image](https://hackmd.io/_uploads/r1ghc7_G1x.png)
來源：https://docs.live2d.com/zh-CHS/cubism-editor-manual/standard-parameter-list/
:::
## 循環動作
![{D0C1508E-E43D-4040-94BD-F3D158198850}](https://hackmd.io/_uploads/BkImlTDz1l.png)

## 表情熱鍵
```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
![image](https://hackmd.io/_uploads/Sk3k0YqXyg.png)

2. 人物Root加上加上CubismRaycaster
![image](https://hackmd.io/_uploads/BkB-AFcQ1g.png)

### 印出物件名稱
```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
結果：
![image](https://hackmd.io/_uploads/BkEqb9qXJg.png)
:::

### 物件貼到座標
假設戴一頂帽子
![full-m2H7d3m2Z5H7N4b1](https://hackmd.io/_uploads/Hyg40cqQye.png)

帽子圖片放到新建的SpriteRenderer上
![image](https://hackmd.io/_uploads/B1N7RqcX1l.png)
```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欄位
![image](https://hackmd.io/_uploads/BkjqCqq7kl.png)

### 最終完整程式
```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軸
        }
    }
}
```
![image](https://hackmd.io/_uploads/HyMx8nq7Jl.png)


