Created
August 6, 2017 08:20
-
-
Save onionmk2/82cf6de7303aaf276ee2dde4916d43f8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using UnityEngine; | |
using Random = UnityEngine.Random; | |
public class Blink : MonoBehaviour | |
{ | |
///<summary>まばたきのmesh・blendShapeの名前</summary> | |
public BlinkShape blinkShape; | |
///<summary>このblendShapeが閾値を超えている場合、まばたきしない</summary> | |
public List<NoBlinkShape> noBlinkShapes; | |
///<summary>1秒あたり平均まばたき回数</summary> | |
public float blinkPerSec = 10f/60f; | |
///<summary><see cref="blinkShape"/>をクラス内部で使いやすくした形</summary> | |
private BlinkShapeInternal blinkShapeInternal; | |
///<summary><see cref="noBlinkShapes"/>をクラス内部で使いやすくした形</summary> | |
private List<NoBlinkShapeInternal> noBlinkShapeInternals; | |
///<summary>まばたきにかかる時間</summary> | |
private readonly float blinkDuration = 0.05f; | |
///<summary>まばたき間隔が、この値より大きいことを保証する。間隔保証不要なら0</summary> | |
private readonly float blinkMinInterval = 0; | |
///<summary><see cref="Phase"/>を参照</summary> | |
private Phase phase = Phase.Idle; | |
///<summary>まばたきの目閉じを開始したTime.time</summary> | |
private float startCloseTime; | |
///<summary>まばたきの目開きを開始したTime.time</summary> | |
private float startOpenTime; | |
private float lastBlinkFinishedTime; | |
private void Awake() | |
{ | |
var skinnedMeshRenders = GetComponentsInChildren<SkinnedMeshRenderer>(); | |
noBlinkShapeInternals = noBlinkShapes.Select(arg => | |
{ | |
var skinnedMeshRenderer = skinnedMeshRenders.FirstOrDefault(meshRenderer => meshRenderer.sharedMesh.name == arg.meshName); | |
if (skinnedMeshRenderer == null) | |
{ | |
throw new Exception(arg.meshName + "is not found"); | |
} | |
var index = skinnedMeshRenderer.sharedMesh.GetBlendShapeIndex(arg.blendShapeName); | |
var notFound = -1; // see https://docs.unity3d.com/ScriptReference/Mesh.GetBlendShapeIndex.html | |
if (index == notFound) | |
{ | |
throw new Exception(arg.blendShapeName + "is not found at " + skinnedMeshRenderer.sharedMesh.name); | |
} | |
return new NoBlinkShapeInternal | |
{ | |
renderer = skinnedMeshRenderer, | |
blendShapeIndex = index, | |
threshold = arg.threshold | |
}; | |
}).ToList(); | |
// assign blinkShapeInternal | |
{ | |
var skinnedMeshRenderer = skinnedMeshRenders.FirstOrDefault(meshRenderer => meshRenderer.sharedMesh.name == blinkShape.meshName); | |
if (skinnedMeshRenderer == null) | |
{ | |
throw new Exception(blinkShape.meshName + "is not found"); | |
} | |
var index = skinnedMeshRenderer.sharedMesh.GetBlendShapeIndex(blinkShape.blendShapeName); | |
var notFound = -1; // see https://docs.unity3d.com/ScriptReference/Mesh.GetBlendShapeIndex.html | |
if (index == notFound) | |
{ | |
throw new Exception(blinkShape.blendShapeName + "is not found at " + skinnedMeshRenderer.sharedMesh.name); | |
} | |
blinkShapeInternal = new BlinkShapeInternal | |
{ | |
renderer = skinnedMeshRenderer, | |
blendShapeIndex = index | |
}; | |
} | |
} | |
///<summary>まばたきを開始できるか否かをランダムに判定する。</summary> | |
private bool StartsByRandom() | |
{ | |
var framePerSec = 1 / Time.deltaTime; | |
var framePerBlink = Mathf.FloorToInt(framePerSec / blinkPerSec); | |
// 第2引数はexclusiveのため、最大framePerBlinkの乱数を得るにはframePerBlink + 1を割り当てる。 | |
return Random.Range(1, framePerBlink + 1) == framePerBlink; | |
} | |
private void LateUpdate() | |
{ | |
print(phase); | |
switch (phase) | |
{ | |
case Phase.Idle: | |
var intervalFinished = Time.time - lastBlinkFinishedTime > blinkMinInterval; | |
if (intervalFinished && StartsByRandom()) | |
{ | |
startCloseTime = Time.time; | |
phase = Phase.BlinkingOfClose; | |
} | |
break; | |
case Phase.BlinkingOfClose: | |
CloseEye(); | |
break; | |
case Phase.BlinkingOfOpen: | |
OpenEye(); | |
break; | |
default: | |
throw new Exception("ここにはこない"); | |
} | |
} | |
private void ResetWhenNoBlinkShape() | |
{ | |
if (noBlinkShapeInternals.Any(arg => arg.renderer.GetBlendShapeWeight(arg.blendShapeIndex) > arg.threshold)) | |
{ | |
blinkShapeInternal.renderer.SetBlendShapeWeight(blinkShapeInternal.blendShapeIndex, 0); | |
lastBlinkFinishedTime = 0; | |
phase = Phase.Idle; | |
} | |
} | |
private void CloseEye() | |
{ | |
var progressRatio = (Time.time - startCloseTime) / blinkDuration * 100; | |
var progressRatioClamped = Mathf.Clamp(progressRatio, 0, 100); | |
blinkShapeInternal.renderer.SetBlendShapeWeight(blinkShapeInternal.blendShapeIndex, progressRatioClamped); | |
var blinkWeight = blinkShapeInternal.renderer.GetBlendShapeWeight(blinkShapeInternal.blendShapeIndex); | |
if (Mathf.Approximately(blinkWeight, 100)) | |
{ | |
startOpenTime = Time.time; | |
phase = Phase.BlinkingOfOpen; | |
} | |
ResetWhenNoBlinkShape(); | |
} | |
private void OpenEye() | |
{ | |
var progressRatio = (Time.time - startOpenTime) / blinkDuration * 100; | |
var progressRatioClamped = 100 - Mathf.Clamp(progressRatio, 0, 100); | |
blinkShapeInternal.renderer.SetBlendShapeWeight(blinkShapeInternal.blendShapeIndex, progressRatioClamped); | |
var blinkWeight = blinkShapeInternal.renderer.GetBlendShapeWeight(blinkShapeInternal.blendShapeIndex); | |
if (Mathf.Approximately(blinkWeight, 0)) | |
{ | |
phase = Phase.Idle; | |
} | |
ResetWhenNoBlinkShape(); | |
} | |
///<summary>まばたきの状態</summary> | |
private enum Phase | |
{ | |
BlinkingOfClose, // まばたき中であり、なおかつ目閉じへ向かっている | |
BlinkingOfOpen,// まばたき中であり、なおかつ目開きへ向かっている | |
Idle // まばたき中ではない | |
} | |
[Serializable] | |
public class NoBlinkShape | |
{ | |
public string blendShapeName; | |
public string meshName; | |
public float threshold; | |
} | |
private class NoBlinkShapeInternal | |
{ | |
public int blendShapeIndex; | |
public SkinnedMeshRenderer renderer; | |
public float threshold; | |
} | |
[Serializable] | |
public class BlinkShape | |
{ | |
public string blendShapeName; | |
public string meshName; | |
} | |
private class BlinkShapeInternal | |
{ | |
public int blendShapeIndex; | |
public SkinnedMeshRenderer renderer; | |
} | |
} |
Author
onionmk2
commented
Aug 6, 2017
- coroutineでやると、Updateで上書きされるので、LateUpdate() でやっている。
- blinkだけの avator mask を作れば、coroutineでできる?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment