unity1week(2019/03/11~3/17)
◆作業記録の目次
1. ゲームの内容を考える
2. 開発環境セットアップ
3. オブジェクト配置
4. 光を飛ばす
5. 汎用性を高くする
6. ゴールデバッグ機能
7. 動かしてるオブジェクトに注目させる
https://gyazo.com/ea38e8d40023a4d62a667463b859b1d4
1. ゲームの内容を考える
◆ お題
今回のお題は「つながる」
お題は着想のためのきっかけくらいの意味合いぽい
◆ 過去のunity1weekのゲームを偵察
Porin.icon1プレイが手軽にできる&リアルタイム性があってハラハラするものが楽しい印象
◆ 考える
▼ 「つながる」から連想
チェイン
鎖
/icons/done.icon光
光を鏡で反射して繋げる
紙を焼く
影を消す
敵を倒す
リレー形式と合わせられるかも?
文字
文字の中を探検?
「繋」「ツナグ」
リレー形式
スイッチを押したら別のキャラが先に進めるようになるのを交互に
Porin.icon「光」が楽しそうな気がする
▼ コンセプト
複数キャラで光の反射を連鎖させるゲーム
GitHubとかUnityのプロジェクト名とかは「Chain Of Reflection」とかにしよ
ゲームのゴールは?
/icons/done.icon塞がってる道を開く?
敵を倒す?
リアルタイム性をどうやって出す?
/icons/done.icon光源が動く
/icons/done.icon足場が動く
壁とかの反射鏡が動く
/icons/done.icon光が出る回数OR時間に制限がある
▼ ゲームの流れ
チュートリアルとかは余裕があったら入れよう...
Tap To Start的なのを入れる
①方向を決める
みんゴルみたいなイメージ
1)は角度を、2)3)は位置を決める
②明かりをつけてゴール判定
①が終わったら「明かりをつけるORやり直す」が選べる
ゴールに光が当たったら道が開ける的な
明かりは光源の電池的に1回しかつかえない
イメージ
https://gyazo.com/4874cb3542b58680d18ffcce8ec01fe7
https://gyazo.com/ea38e8d40023a4d62a667463b859b1d4
2. 開発環境セットアップ
◆ GitHub
リポジトリ名は「reflection-game」
ゲームですよって書かないと他のWeb系のものと混じるため
.gitignoreはUnityのものをつける
いつものようにghqでcloneする
◆ 開発タスク整理
いつもお世話になっているProjectを作成
最初の2つだけタスクを作成して、作りながらtodoを整理していく
https://gyazo.com/9ff293ae861f042b56d9827a123f5311
◆ Unityプロジェクトセットアップ
▼ Unity Hubでバージョンを指定
UnityRoomは2018.3~ にも対応してるぽいので2018.3.6f1で
https://gyazo.com/c44f917a75bf90e5f5d39d19ec8c0212
▼ ゲームウィンドウ
https://gyazo.com/ae2ac324f20118fed046b4483f06569f
▼ DOTweenを入れる
アセットを入れてセットアップ
DOTween用に自分で作ったモジュールぽいものも入れる
これ一通り作ったらGitHubにアップして使いまわせるようにしよ
code:cs
using DG.Tweening;
public class AppUtil : MonoBehaviour
{
public static void InitTween(){
DOTween.useSmoothDeltaTime = true;
}
public static void DOSequence(Tween[] tweenArray, float prependInterval, float appendInterval){
Sequence sequence = DOTween.Sequence();
sequence.PrependInterval(prependInterval);
sequence.AppendInterval(appendInterval);
foreach(Tween tween in tweenArray){
sequence.Append(tween);
}
}
public static IEnumerator WaitForTween(Tween tween){
yield return tween.WaitForCompletion();
}
public static Tween ShowRect(RectTransform rect, string changeValue, float startValue, float duration, string ease, float delay=0){
/* changeValue:"x", "y", "full" */
Ease easeType = (Ease)Enum.Parse(typeof(Ease), ease);
rect.gameObject.SetActive(true);
Vector2 targetScale = rect.localScale;
switch(changeValue){
case "x":
rect.localScale = new Vector2(rect.localScale.x * startValue, rect.localScale.y);
break;
case "y":
rect.localScale = new Vector2(rect.localScale.x, rect.localScale.y * startValue);
break;
case "full":
rect.localScale = new Vector2(rect.localScale.x * startValue, rect.localScale.y * startValue);
break;
}
Tween tween = rect.DOScale(targetScale, duration).SetEase(easeType).SetDelay(delay);
return tween;
}
public static Tween HideRect(RectTransform rect, string changeValue, float duration, string ease, float delay=0f){
/* changeValue:"x", "y", "full" */
Ease easeType = (Ease)Enum.Parse(typeof(Ease), ease);
rect.gameObject.SetActive(true);
Vector2 targetScale = Vector2.zero;
switch(changeValue){
case "x":
targetScale = new Vector2(0f, rect.localScale.y);
break;
case "y":
targetScale = new Vector2(rect.localScale.y, 0f);
break;
}
Tween tween = rect.DOScale(targetScale, duration).SetEase(easeType).SetDelay(delay);
return tween;
}
public static Tween MoveRect(RectTransform rect, string from, Vector2 startAnchoredPos, Vector2 endAnchoredPos, float duration, string ease, float delay=0f){
/* from:"上" */
Ease easeType = (Ease)Enum.Parse(typeof(Ease), ease);
rect.anchoredPosition = startAnchoredPos;
Vector2 targetAnchoredPos = endAnchoredPos;
Tween tween = rect.DOAnchorPos(targetAnchoredPos, duration).SetEase(easeType).SetDelay(delay);
return tween;
}
}
https://gyazo.com/ea38e8d40023a4d62a667463b859b1d4
3. オブジェクト配置
◆ TODO
https://gyazo.com/dfda85df7dee8cbe109becb9b0575cc1
◆ ひとまずこんな感じに
デザインはほら...後回し...
UIも後回し
https://gyazo.com/523135bf7fc72d82da339db38c2e61d9
https://gyazo.com/ea38e8d40023a4d62a667463b859b1d4
4. 光を飛ばす
Porin.icon全然わからん...しらべる
◆ 調べる
なんかここら辺で向きとか長さとかは計算できそう
ここらへんで光飛ばせる&反射させられるんでね?
◆ 一つ目の光実装
クリックでライトの方向を決定
ライトの動きはDOTweenのSequenceを使用
use world spaceにチェック入れないとめんどかった...気づくのにだいぶ時間かかった
code:LightLine.cs
public class LightLine : MonoBehaviour
{
private DG.Tweening.Sequence lightSequence;
Transform lightObject;
private bool isLightingMode = false;
private float firstAngle;
private LineRenderer lr;
private void Start()
{
lr = GetComponent<LineRenderer>();
lr.generateLightingData = true;
lightObject = transform.parent;
lightSequence = AppUtil.DOSequence(
new DG.Tweening.Tween[] {
AppUtil.Rotate(lightObject, new Vector3(0f, 0f, 90f), 2f),
AppUtil.Rotate(lightObject, new Vector3(0f, 0f, -90f), 2f)
},
0f,
0f,
-1
);
AppUtil.SetOnKillCallback(lightSequence, ()=> isLightingMode = true);
}
private void Update(){
if(Input.GetMouseButtonDown(0)){
AppUtil.KillDO(lightSequence, false);
}
// Rayを飛ばす
if(isLightingMode){
firstAngle = lightObject.localEulerAngles.z;
RaycastHit2D hit = Physics2D.Raycast(transform.position, new Vector2(Mathf.Cos(firstAngle*Mathf.Deg2Rad), Mathf.Sin(firstAngle*Mathf.Deg2Rad)));
if(hit){
Debug.DrawRay(transform.position, new Vector2(Mathf.Cos(firstAngle*Mathf.Deg2Rad), Mathf.Sin(firstAngle*Mathf.Deg2Rad)) * 100, Color.blue, 1);
lr.positionCount = 2;
lr.SetPosition(0, transform.position);
lr.SetPosition(1, hit.point);
}
}
}
}
DOTween用のコールバックとかKillとかを設定する用のメソッドを追加したりなど
code:AppUtil.cs
public static void KillDO(Tween tween, bool complete=true){
tween.Kill(complete);
}
public static void KillDO(Sequence sequence, bool complete=true){
sequence.Kill(complete);
}
public static void SetOnKillCallback(Tween tween, TweenCallback action){
tween.OnKill(action);
}
public static void SetOnKillCallback(Sequence sequence, TweenCallback action){
sequence.OnKill(action);
}
public static Tween Rotate(Transform target, Vector3 endValue, float duration, string ease="OutQuad", float delay=0f)
{
Ease easeType = (Ease)Enum.Parse(typeof(Ease), ease);
Tween tween = target.DORotate(endValue, duration).SetEase(easeType).SetDelay(delay);
return tween;
}
◆ 全ての光を生成
値を調整しながら角度計算
鏡のデフォルト向きによって入力された角度を計算式に当てはめるための調整しなきゃでめんどくさかった...
code:LightLine.cs
private void FixedUpdate(){
// Rayを飛ばす
hit1 = DrawRaycast(transform.position, Angle.firstAngle); // デフォルト右向き
if(hit1){
float x = transform.position.x+(hit1.point.x-transform.position.x)*2;
float y = hit1.point.y - transform.position.y;
float hit1Angle = Mathf.Atan2(y, x) * Mathf.Rad2Deg;
hit1Angle = hit1Angle - 180f + hit1Angle;
hit2 = DrawRaycast(hit1.transform.position, hit1Angle); // デフォルト下向き
}
if(hit2){
hit3 = DrawRaycast(hit2.transform.position, -Angle.secondAngle + 180f); // デフォルト左向き
}
if(hit3){
hit4 = DrawRaycast(hit3.transform.position, Angle.thirdAngle); // デフォルト右向き
}
}
private RaycastHit2D DrawRaycast(Vector2 from, float angle){
if(angle != 360) Debug.DrawRay(from, new Vector2(Mathf.Cos(angle*Mathf.Deg2Rad), Mathf.Sin(angle*Mathf.Deg2Rad)) * 100, Color.blue, 1);
return Physics2D.Raycast(from, new Vector2(Mathf.Cos(angle*Mathf.Deg2Rad), Mathf.Sin(angle*Mathf.Deg2Rad)));
}
反射光を生成する
code:LightLine.cs
public IEnumerator LightUp(){
Debug.Log(hit1.collider.name);
Debug.Log(hit2.collider.name);
Debug.Log(hit3.collider.name);
Debug.Log(hit4.collider.name);
lr.positionCount = 2;
lr.SetPosition(0, transform.position);
lr.SetPosition(1, hit1.point);
if(hit1.collider.name != "Mirror") yield break;
yield return new WaitForSeconds(1f);
lr.positionCount = 3;
lr.endWidth += 0.2f;
lr.SetPosition(2, hit2.point);
if(hit2.collider.name != "Mirror1") yield break;
lr.positionCount = 4;
lr.endWidth += 0.2f;
lr.SetPosition(3, hit3.point);
if(hit3.collider.name != "Mirror2") yield break;
lr.positionCount = 5;
lr.endWidth += 0.2f;
lr.SetPosition(4, hit4.point);
if(hit4.collider.name != "HitPlace") yield break;
Debug.Log("goal");
yield break;
}
◆ 光にアニメーションをつける
参考
code:LightLine.cs
public IEnumerator LightUp(){
Debug.Log(hit1.collider.name);
Debug.Log(hit2.collider.name);
Debug.Log(hit3.collider.name);
Debug.Log(hit4.collider.name);
int hitCount = 0;
if(hit1.collider.name == "Mirror") hitCount += 1;
if(hit2.collider.name == "Mirror1") hitCount += 1;
if(hit3.collider.name == "Mirror2") hitCount += 1;
if(hit4.collider.name == "HitPlace") hitCount += 1;
// 徐々に光が伸びていく
Vector3[] linePointArray = new Vector3[]
{
transform.position,
hit1.point,
hit2.point,
hit3.point,
hit4.point
};
DG.Tweening.Tween[] tweenArray = new DG.Tweening.TweenhitCount+1; int lineNum = 0;
for (int i = 0; i <= hitCount; i++){
tweenArrayi = AppUtil.DOTO( v =>
{
lr.SetPosition(lineNum, linePointArrayi-1); },
0.5f,
"OutExpo",
0.8f
);
if(i < linePointArray.Length-1){
AppUtil.SetOnCompleteCallback(tweenArrayi, ()=>{ lineNum++;
lr.positionCount+=1;
lr.SetPosition(lineNum, linePointArrayi-1); lr.endWidth += 0.3f;
});
}
}
AppUtil.DOSequence(tweenArray, 0f, 0f, 1);
if(lineNum == 4) Debug.Log("goal");
yield break;
}
Vector3を変化させていくメソッドを作成
code:AppUtil.cs
public static Tween DOTO(DG.Tweening.Core.DOGetter<Vector3> getter, DG.Tweening.Core.DOSetter<Vector3> setter, Vector3 endValue, float duration, string ease="OutQuad", float delay=0f){
Ease easeType = (Ease)Enum.Parse(typeof(Ease), ease);
Tween tween = DOTween.To(getter, setter, endValue, duration).SetEase(easeType).SetDelay(delay);
return tween;
}
◆ バランス調整
コライダとかの位置やら判定の値やらアニメーションのパラメータやらを調整
https://gyazo.com/ea38e8d40023a4d62a667463b859b1d4
5. 汎用性を高くする
◆ 全体の情報
▼ だいたいの役割分担
LightLine.cs:Rayを飛ばす、光を出現させる
SceneBase.cs:角度計算やら位置決定、デバッグモードの判定などいろいろ
▼ ゲーム動作の流れ(本番モード)
データ読み込み
必要なオブジェクト生成
クリックして光源や鏡の角度や位置を決定していく
光源や鏡の角度や位置に対応してRayを飛ばす
Rayを元に光を生成
鏡に当たった場合は反射
最終的にゴールに光が当たったらクリア
◆ 実装
▼ データを作る
CSV形式のテキストデータを作成
code:TextData.txt
オブジェクト名,向き,動かす対象
Light,右,本体
Mirror1,下,なし
Mirror2,左,本体、足場
Mirror3,右,本体
データはジャグ配列の形で読み込む
code:SceneBase.cs
private string[][] LoadData(){
var data =
from x in Resources.Load("TextData").ToString().Split('\n').Skip(1)
select x.Split(',');
return data.ToArray();
}
これに沿ってスクリプトはほぼ書き直したので流れに沿って全部載せよ...
▼ シーン読み込み開始
基本
データ読み込み
動かすオブジェクトの数だけ、角度の値を入れる配列を作成
DOTweenの設定を初期化
デバッグじゃないときはDesideAngleAndPosコルーチンを呼び出す
code:SceneBase.cs
private void Awake(){
moveDataArray = LoadData();
lightLine = FindObjectOfType<LightLine>();
if(isGoalDebugMode){
SetClearRoot();
}
AppUtil.InitTween();
}
private void Start(){
if(!isGoalDebugMode){
for(int i=0; i<angleValueArray.Length; ++i){
}
StartCoroutine(DesideAngleAndPos());
}
}
ライト用
もろもろを生成
Rayに当たったやつを入れるRaycastHit2Dの配列を動かすオブジェクトの数だけ生成
code:LightLine.cs
private void Awake(){
lr = GetComponent<LineRenderer>();
lr.generateLightingData = true;
}
private void Start(){
}
▼ 角度や位置を決定
データを参照しながら頑張る
色々終えたら光を呼び出す
code:SceneBase.cs
private IEnumerator DesideAngleAndPos(){
foreach(string[] dataArray in moveDataArray){
string name = dataArray0; string[] moveTarget = dataArray2.Split('、'); if(moveTarget.Contains("なし")) continue; // 動かす対象がないものはスキップ
if(moveTarget.Contains("足場")){ // 足場を動かしたあとに鏡を回転
SlidePos(GameObject.Find(name));
yield return new WaitUntil(()=> Input.GetMouseButtonDown(0));
AppUtil.KillDO(angleSequence, false);
yield return new WaitForSeconds(0.1f);
RotateAngle(GameObject.Find(name));
} else
{
RotateAngle(GameObject.Find(name));
}
yield return new WaitUntil(()=> Input.GetMouseButtonDown(0));
AppUtil.KillDO(angleSequence, false);
yield return new WaitForSeconds(0.1f); // Kill終了まで待機
}
SetAngles(); // 全てのオブジェクトの角度決定後に角度を取得する
Invoke("StartLighting", 3f); // LightLineのhitに値が入るのを待機
}
private void StartLighting(){
StartCoroutine(lightLine.LightUp());
}
光源回転用メソッド
code:SceneBase.cs
private void RotateAngle(GameObject moveObject){
angleSequence = AppUtil.DOSequence(
new DG.Tweening.Tween[] {
AppUtil.Rotate(moveObject.transform, new Vector3(0f, 0f, 70f), 2f, "OutQuart"),
AppUtil.Rotate(moveObject.transform, new Vector3(0f, 0f, -70f), 2f, "OutQuart")
},
0f,
0f,
-1
);
}
足場移動用メソッド
光源の親要素を動かす
code:SceneBase.cs
private void SlidePos(GameObject moveObject){
moveObject = moveObject.transform.parent.gameObject;
Vector3 originPos = moveObject.transform.position;
Vector3 targetPos = new Vector3(originPos.x-10f, originPos.y, originPos.z);
angleSequence = AppUtil.DOSequence(
new DG.Tweening.Tween[] {
AppUtil.Move(moveObject.transform, targetPos, 3f),
AppUtil.Move(moveObject.transform, originPos, 3f),
},
0f,
0f,
-1
);
}
角度計算用メソッド
code:SceneBase.cs
private void SetAngles(){
foreach(string[] dataArray in moveDataArray){
int index = Array.IndexOf(moveDataArray, dataArray);
string name = dataArray0; string direction = dataArray1; string[] moveTarget = dataArray2.Split('、'); float targetAngle = GameObject.Find(name).transform.eulerAngles.z;
switch(direction){
case "右":
angleValueArrayindex = targetAngle; break;
case "左":
angleValueArrayindex = targetAngle+180f; break;
case "下":
// 入射角から反射角を計算
Vector3 prevPos = GameObject.Find(moveDataArrayindex-10).transform.position; Vector3 currentPos = GameObject.Find(name).transform.position;
float x = prevPos.x+(currentPos.x-prevPos.x)*2;
float y = currentPos.y - prevPos.y;
float hitAngle = Mathf.Atan2(y, x) * Mathf.Rad2Deg;
angleValueArrayindex = hitAngle - 180f + hitAngle; break;
}
}
}
◆ 光を生成
▼ Rayを飛ばす
決定された角度や位置をもとにRaycastを飛ばす
コライダーに当たったら次のRaycastを生成する
hitArrayの要素は動かすオブジェクトの数だけ生成されているのでそれを元に繰り返し処理
code:LightLine.cs
private void FixedUpdate(){
// Rayを飛ばす
for(int i=0; i<hitArray.Length; ++i){
if(i == 0) {
hitArrayi = DrawRaycast(transform.position, SceneBase.angleValueArrayi); } else
{
if(hitArrayi-1) hitArrayi = DrawRaycast(hitArrayi-1.transform.position, SceneBase.angleValueArrayi); }
}
}
Raycastを飛ばすときはデバッグでシーンビュー線を引くようにする
code:LightLine.cs
private RaycastHit2D DrawRaycast(Vector2 from, float angle){
if(angle != 360) Debug.DrawRay(from, new Vector2(Mathf.Cos(angle*Mathf.Deg2Rad), Mathf.Sin(angle*Mathf.Deg2Rad)) * 100, Color.blue, 1);
return Physics2D.Raycast(from, new Vector2(Mathf.Cos(angle*Mathf.Deg2Rad), Mathf.Sin(angle*Mathf.Deg2Rad)));
}
▼ 光を生成
鏡にヒットした回数を取得
光を伸ばす場所をVector3配列で生成
鏡に当たった時はその鏡の位置
それ以外はRayがコライダーに当たった位置
光を伸ばし終わったあとゴールに達しているか判定
code:LightLine.cs
public IEnumerator LightUp(){
// 対象オブジェクトにヒットした回数を取得
int hitCount = 0;
for(int i=0; i<hitArray.Length-1; ++i){
string hitName = hitArrayi.collider.name; string targetName = SceneBase.moveDataArrayi+10; Debug.Log(hitName);
if(hitName == targetName && hitCount >= i) hitCount += 1;
}
// 光を伸ばす場所をRayCastから取得
var data =
from x in SceneBase.moveDataArray
string[] nameArray = data.ToArray();
for(int i=0; i<linePointArray.Length; ++i){
if(i==0){
linePointArrayi = transform.position; } else
{
if(nameArray.Contains(hitArrayi-1.collider.name)){ linePointArrayi = hitArrayi-1.transform.position; } else
{
linePointArrayi = hitArrayi-1.point; }
}
}
// 徐々に光を伸ばしていく
DG.Tweening.Tween[] tweenArray = new DG.Tweening.TweenhitCount+1; lr.SetPosition(0, linePointArray0); // 初期設定 lr.positionCount += 1;
int lineNum = 1;
lr.SetPosition(lineNum, linePointArray0); for (int i = 0; i < tweenArray.Length; ++i){ // 線を伸ばすためのTweenの配列を作成
int index = i; // なぜかtweenの中ではインクリメントが使用できなかったので新しくint型変数を作成
tweenArrayi = AppUtil.DOTO( () => linePointArrayindex, v =>
{
lr.SetPosition(lineNum, linePointArrayindex); },
0.5f,
"OutExpo",
0.5f
);
if(i < tweenArray.Length-1){ // 次のラインのセッティング
AppUtil.SetOnCompleteCallback(tweenArrayi, ()=>{ lineNum++;
lr.positionCount+=1;
lr.SetPosition(lineNum, linePointArrayindex+1); lr.endWidth += 0.3f;
});
}
}
AppUtil.SetOnCompleteCallback(AppUtil.DOSequence(tweenArray, 0f, 0f, 1), ()=>{ // 線を伸ばす&ゴール判定
});
yield break;
}
https://gyazo.com/ea38e8d40023a4d62a667463b859b1d4
6. ゴールデバッグ機能
◆ 流れ(クリア状態を作る)
クリア位置に光源や鏡の角度や位置を移動
光を飛ばす(光を伸ばす数の指定も可能)
◆ 実装
▼ デバッグ判定
code:SceneBase.cs
// ゴールデバッグ用
private bool _isGoalDebugMode = false;
public static bool isGoalDebugMode;
private int _debugLightNum = 0;
public static int debugLightNum;
private void Awake(){
isGoalDebugMode = _isGoalDebugMode;
debugLightNum = _debugLightNum;
}
private void Start(){
if(isGoalDebugMode){
SetClearRoot();
} else
{
for(int i=0; i<angleValueArray.Length; ++i){
}
StartCoroutine(DesideAngleAndPos());
}
}
▼ 光を伸ばす数を設定
code:LightLine.cs
private void Start(){
}
▼ クリア位置に移動
クリア位置に光源や鏡の角度や位置を移動
そして光を伸ばすコルーチンを呼び出す
code:SceneBase.cs
private void SetClearRoot(){
int angleIndexCount = 0;
int posIndexCount = 0;
foreach(string[] dataArray in moveDataArray){
if(dataArray2.Split('、').Contains("本体")){ // 角度回転あり GameObject.Find(dataArray0).transform.eulerAngles = new Vector3(0f, 0f, clearAngleArrayangleIndexCount); angleIndexCount += 1;
}
if(dataArray2.Split('、').Contains("足場")){ // 足場移動あり GameObject moveObject = GameObject.Find(dataArray0).transform.parent.gameObject; moveObject.transform.position = new Vector3(clearPosArrayposIndexCount, moveObject.transform.position.y, moveObject.transform.position.z); posIndexCount += 1;
}
}
SetAngles();
Invoke("StartLighting", 3f); // LightLineのhitに値が入るのを待機
}
private void StartLighting(){
StartCoroutine(lightLine.LightUp());
}
▼ ゲームやり直し
ついでにSpaceボタン押したらゲームやりなおせるようにした
code:SceneBase.cs
private void Update(){
if(Input.GetKey(KeyCode.Space)){
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
}
◆ こんな感じに
ゴールデバッグ状態
https://gyazo.com/49302ac2b27ec9319e2fb897cf21bd51
普通に操作した状態
何度やりすぎて1発でクリアできるようになってしまった
https://gyazo.com/a27e4ee24ed60550e212b06e444becac
こっからはステージ増やしたり、演出入れたり、デザイン作り直したりって感じになりそう
鏡の後ろに光が残っちゃうのもなんとかしたい
https://gyazo.com/ea38e8d40023a4d62a667463b859b1d4
7. 動かしてるオブジェクトに注目させる
◆ やること
最初は、もやぽいUI画像ををかけた上にシェーダーで穴をあけるみたいなのをやりたかったのだけどうまくできなくて断念
で、パーティクルをいじってたらいい感じの円を作ることができたのでそれを使うことにした
Porin.iconシェーダーはどこかでまとめてやりたいな〜
◆ パーティクル作成
こんな感じのパラメータに
まじで適当にいじってただけなのでアレ
これをPrefab化する
https://gyazo.com/42f516872f1a82b1fd4c7ed8a66e28ed
https://gyazo.com/ce50f657e96f667bbeed2395064ef15f
Size Over Lifetime
https://gyazo.com/95395e21a44419c58fcf50a74dfc9cdb
https://gyazo.com/c5693a7996e2540acdc1447841d04be6
◆ 実装
足場移動するときもオブジェクトと一緒に動かしたかったから以下のようにした
1つ目のオブジェクト上でパーティクルオブジェクトをアクティブに
1つ目のオブジェクト操作終了後、パーティクルを次のオブジェクトに移動
操作していたパーティクルを非アクティブに
移動先オブジェクトの子要素のパーティクルをアクティブに
もしかしたら足場移動のときに一緒に動くようにすればいいだけだったかもだけど...
code:SceneBase.cs
private IEnumerator DesideAngleAndPos(){
Transform focusParticle = GameObject.Find(moveDataArray00).transform.Find("SelectedParticle"); foreach(string[] dataArray in moveDataArray){
string name = dataArray0; string[] moveTarget = dataArray2.Split('、'); GameObject reflection = GameObject.Find(name);
if(moveTarget.Contains("なし")) continue; // 動かす対象がないものはスキップ
// 注目切り替え
yield return AppUtil.WaitDO(AppUtil.Move(focusParticle, focusParticle.position, reflection.transform.position, 1f, "OutQuart"));
focusParticle.gameObject.SetActive(false);
focusParticle = reflection.transform.Find("SelectedParticle");
focusParticle.gameObject.SetActive(true);
yield return new WaitForSeconds(0.8f);
// 動かす
if(moveTarget.Contains("足場")){ // 足場を動かしたあとに鏡を回転
SlidePos(reflection);
yield return new WaitUntil(()=> Input.GetMouseButtonDown(0));
AppUtil.KillDO(angleSequence, false);
yield return new WaitForSeconds(0.1f);
RotateAngle(reflection);
} else
{
RotateAngle(reflection);
}
yield return new WaitUntil(()=> Input.GetMouseButtonDown(0));
AppUtil.KillDO(angleSequence, false);
yield return new WaitForSeconds(0.5f); // Kill終了まで待機
}
focusParticle.gameObject.SetActive(false);
SetAngles(); // 全てのオブジェクトの角度決定後に角度を取得する
Invoke("StartLighting", 2f); // LightLineのhitに値が入るのを待機
}
https://gyazo.com/02021072a65ce3c942a6ad96b17a99d6
https://gyazo.com/ea38e8d40023a4d62a667463b859b1d4
8. スタート演出
◆ パラメータ調整
パラメーター
ライト降下
ライト向き変更
タイトル表示
Tap to start 表示
https://gyazo.com/f1e4b657a0d43a0181ada9e3577bb478
◆ 実装
code:cs
private IEnumerator PlayOpening(){
opGroup.gameObject.SetActive(true);
float delay = opParameter.Find(x=> x.UseTarget=="ライト降下").Delay;
float value = opParameter.Find(x=> x.UseTarget=="ライト降下").Value;
float duration = opParameter.Find(x=> x.UseTarget=="ライト降下").Duration;
string easeType = opParameter.Find(x=> x.UseTarget=="ライト降下").EaseType;
yield return AppUtil.WaitDO(AppUtil.Move(opLight, new Vector2(opLight.anchoredPosition.x, value), opLight.anchoredPosition, duration, easeType, delay));
delay = opParameter.Find(x=> x.UseTarget=="ライト向き変更").Delay;
value = opParameter.Find(x=> x.UseTarget=="ライト向き変更").Value;
duration = opParameter.Find(x=> x.UseTarget=="ライト向き変更").Duration;
easeType = opParameter.Find(x=> x.UseTarget=="ライト向き変更").EaseType;
yield return AppUtil.WaitDO(AppUtil.Scale(opLight, new Vector2(value, 1), duration, easeType, delay));
delay = opParameter.Find(x=> x.UseTarget=="タイトル表示").Delay;
value = opParameter.Find(x=> x.UseTarget=="タイトル表示").Value;
duration = opParameter.Find(x=> x.UseTarget=="タイトル表示").Duration;
easeType = opParameter.Find(x=> x.UseTarget=="タイトル表示").EaseType;
yield return AppUtil.WaitDO(AppUtil.Move(opCover, opCover.anchoredPosition, new Vector2(value, opCover.anchoredPosition.y), duration, easeType, delay));
delay = opParameter.Find(x=> x.UseTarget=="Tap to start表示暗").Delay;
value = opParameter.Find(x=> x.UseTarget=="Tap to start表示暗").Value;
duration = opParameter.Find(x=> x.UseTarget=="Tap to start表示暗").Duration;
easeType = opParameter.Find(x=> x.UseTarget=="Tap to start表示暗").EaseType;
float delay_2 = opParameter.Find(x=> x.UseTarget=="Tap to start表示明").Delay;
float value_2 = opParameter.Find(x=> x.UseTarget=="Tap to start表示明").Value;
float duration_2 = opParameter.Find(x=> x.UseTarget=="Tap to start表示明").Duration;
string easeType_2 = opParameter.Find(x=> x.UseTarget=="Tap to start表示明").EaseType;
yield return new WaitForSeconds(delay);
AppUtil.Blink(opStartMessage, 50, value, value_2, duration, duration_2, easeType, easeType_2, delay_2);
yield return new WaitUntil(()=> Input.GetMouseButtonDown(0));
delay = opParameter.Find(x=> x.UseTarget=="スタート画面非表示").Delay;
value = opParameter.Find(x=> x.UseTarget=="スタート画面非表示").Value;
duration = opParameter.Find(x=> x.UseTarget=="スタート画面非表示").Duration;
easeType = opParameter.Find(x=> x.UseTarget=="スタート画面非表示").EaseType;
yield return AppUtil.WaitDO(AppUtil.FadeOut(opGroup, value, duration, easeType, delay));
opGroup.gameObject.SetActive(false);
}
https://gyazo.com/ea38e8d40023a4d62a667463b859b1d4
9. Text Mexh Pro試す
◆ 参考
◆ 試してみた
アセットストアでインストール
サンプルとかも一通り見て試してみた
これはテクスチャーとか揃えたくなるやつだ...
こんな感じに(画像素材は適当)
フォントとかもっとこだわれば色々できそう
キランって感じの演出も入れてみた
https://gyazo.com/5de6f2ac2acf38113e426d7cba196f3c
サンプルで見たこれがすごく楽しそうだった
どうやってるのかはよくわかってないけど...
これを使ってるぽいな...
あ、一文字ずつメッシュの頂点情報を取得→書き換え→TextMesh Proに反映 ってやってるぽいな...途方も無い...
https://gyazo.com/7de0d4d7e35af89a4635fd7a0451eb9f
code:VertexJitter.cs
using UnityEngine;
using System.Collections;
namespace TMPro.Examples
{
public class VertexJitter : MonoBehaviour
{
public float AngleMultiplier = 1.0f;
public float SpeedMultiplier = 1.0f;
public float CurveScale = 1.0f;
private TMP_Text m_TextComponent;
private bool hasTextChanged;
/// <summary>
/// Structure to hold pre-computed animation data.
/// </summary>
private struct VertexAnim
{
public float angleRange;
public float angle;
public float speed;
}
void Awake()
{
m_TextComponent = GetComponent<TMP_Text>();
}
void OnEnable()
{
// Subscribe to event fired when text object has been regenerated.
TMPro_EventManager.TEXT_CHANGED_EVENT.Add(ON_TEXT_CHANGED);
}
void OnDisable()
{
TMPro_EventManager.TEXT_CHANGED_EVENT.Remove(ON_TEXT_CHANGED);
}
void Start()
{
StartCoroutine(AnimateVertexColors());
}
void ON_TEXT_CHANGED(Object obj)
{
if (obj == m_TextComponent)
hasTextChanged = true;
}
/// <summary>
/// Method to animate vertex colors of a TMP Text object.
/// </summary>
/// <returns></returns>
IEnumerator AnimateVertexColors()
{
// We force an update of the text object since it would only be updated at the end of the frame. Ie. before this code is executed on the first frame.
// Alternatively, we could yield and wait until the end of the frame when the text object will be generated.
m_TextComponent.ForceMeshUpdate();
TMP_TextInfo textInfo = m_TextComponent.textInfo;
Matrix4x4 matrix;
int loopCount = 0;
hasTextChanged = true;
// Create an Array which contains pre-computed Angle Ranges and Speeds for a bunch of characters.
VertexAnim[] vertexAnim = new VertexAnim1024; for (int i = 0; i < 1024; i++)
{
vertexAnimi.angleRange = Random.Range(10f, 25f); vertexAnimi.speed = Random.Range(1f, 3f); }
// Cache the vertex data of the text object as the Jitter FX is applied to the original position of the characters.
TMP_MeshInfo[] cachedMeshInfo = textInfo.CopyMeshInfoVertexData();
while (true)
{
// Get new copy of vertex data if the text has changed.
if (hasTextChanged)
{
// Update the copy of the vertex data for the text object.
cachedMeshInfo = textInfo.CopyMeshInfoVertexData();
hasTextChanged = false;
}
int characterCount = textInfo.characterCount;
// If No Characters then just yield and wait for some text to be added
if (characterCount == 0)
{
yield return new WaitForSeconds(0.25f);
continue;
}
for (int i = 0; i < characterCount; i++)
{
TMP_CharacterInfo charInfo = textInfo.characterInfoi; // Skip characters that are not visible and thus have no geometry to manipulate.
if (!charInfo.isVisible)
continue;
// Retrieve the pre-computed animation data for the given character.
VertexAnim vertAnim = vertexAnimi; // Get the index of the material used by the current character.
int materialIndex = textInfo.characterInfoi.materialReferenceIndex; // Get the index of the first vertex used by this text element.
int vertexIndex = textInfo.characterInfoi.vertexIndex; // Get the cached vertices of the mesh used by this text element (character or sprite).
// Determine the center point of each character at the baseline.
// Determine the center point of each character.
// Need to translate all 4 vertices of each quad to aligned with middle of character / baseline.
// This is needed so the matrix TRS is applied at the origin for each character.
Vector3 offset = charMidBasline;
Vector3[] destinationVertices = textInfo.meshInfomaterialIndex.vertices; vertAnim.angle = Mathf.SmoothStep(-vertAnim.angleRange, vertAnim.angleRange, Mathf.PingPong(loopCount / 25f * vertAnim.speed, 1f));
Vector3 jitterOffset = new Vector3(Random.Range(-.25f, .25f), Random.Range(-.25f, .25f), 0);
matrix = Matrix4x4.TRS(jitterOffset * CurveScale, Quaternion.Euler(0, 0, Random.Range(-5f, 5f) * AngleMultiplier), Vector3.one);
}
// Push changes into meshes
for (int i = 0; i < textInfo.meshInfo.Length; i++)
{
textInfo.meshInfoi.mesh.vertices = textInfo.meshInfoi.vertices; m_TextComponent.UpdateGeometry(textInfo.meshInfoi.mesh, i); }
loopCount += 1;
yield return new WaitForSeconds(0.1f);
}
}
}
}
https://gyazo.com/ea38e8d40023a4d62a667463b859b1d4
10. LineRendererを綺麗に(できなかった)
◆ XRTrailRenderer
ドキュメントがあまりなくて微妙だった
◆ Volumetric Lines