我們平常可能會希望可以回顧該使用者的動作或是一些姿態的轉動方向,這時候就需要不斷的在一段時間內紀錄受試者的動作。所以今天我希望能夠跟大家說明並且以簡單的方式記錄在一段時間中該一個物件的Position移動的位置, 並且能夠讓我們進行回顧一下其在這段時間中移動的方向。今天的程式碼部分需求量比較大。
首先我設計一個 Car 物件,這個 Car 物件包含了 Trail Renderer,因為我希望之後能讓觀眾可以看到該位移的變化。場景如下:
以下這邊我也新增一個 CarMovement.cs 的腳本,目的是要讓Car 能夠根據我鍵盤的 w,a,s,d 縱向與橫向的位移。程式碼非常的簡單,如以下結果:
我們撰寫程式碼。該就是取得 Mouse 移動的位置,並且更改 Car 的位置。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CarMovement : MonoBehaviour
{
public float sideForce = 50f;
public float forwardForce = 80f;
void Update()
{
float dx = Input.GetAxis("Horizontal");
float dz = Input.GetAxis("Vertical");
transform.position += new Vector3(dx * sideForce * Time.deltaTime, 0f, dz * forwardForce * Time.deltaTime);
}
}
[Serializable]
public class MovementData
{
[Serializable]
public class MovePosition
{
}
}
[NonSerializable]
public class MovePosition
{
[NonSerialized]
public List<Vector3> Movements = new List<Vector3>();
public List<string> MovementsStr = new List<string>();
public List<float> DirectionForward = new List<float>();
public void positionToString()
{
if(Movements.Count > 0)
{
MovementsStr.Clear();
}
foreach(Vector3 movement in Movements)
{
string str = movement.x.ToString() + ", " + movement.y.ToString() + ", " + movement.z.ToString();
MovementsStr.Add(str);
}
}
public void StringToPosition()
{
if(MovementsStr.Count > 0)
Movements.Clear();
foreach(string str in MovementsStr)
{
string[] arr = str.Split(',');
Vector3 vec = new Vector3(float.Parse(arr[0]), float.Parse(arr[1]), float.Parse(arr[2]));
Movements.Add(vec);
}
}
}
每個 MovePosition 物件都會需要存入傳進來的 Vector3 List的資料。
public List<Vector3> Movements = new List<Vector3>()
接下來是我們可能會希望顯示List 中的 Vector3 的 Position 這邊新增兩種方法
public List<string> MovementsStr = new List<string>();
public void PositionToString()
{
if(Movements.Count > 0)
MovementsStr.Clear();
foreach(Vector3 movement in Movements)
{
string str = movement.x.ToString() + "," + movement.y.ToString() + "," + movement.z.ToString();
MovementsStr.Add(str);
}
}
public void StringToPosition()
{
if(MovementsStr.Count > 0)
Movements.Clear();
foreach(string s in MovementsStr)
{
string[] str = s.Split(',');
Vector3 vec = new Vector3(float.Parse(str[0]), float.Parse(str[1]), float.Parse(str[2]));
Movements.Add(vec);
}
}
這邊使用了兩種新的方法:
public List<MovePosition> MovesPositionObjList = new List<MovePosition>();
這邊就是透過 List 儲存 MovesPosition 的物件,這個物件可以使用關於 MovePosition物件中的 List 儲存 vector3 。所以說要一個能引入List 以 Vector3型態,並且透過MovePosition 物件來轉換該物件下面的 Movements List設為 傳入的 _movesList。
public void InsertMoves(List<Vector3> _movesList)
{
MovePosition moves = new MovePosition();
moves.Movements = _movesList;
this.MovesPositionObjList.Add(moves);
}
public void InsertMoves(List<Vector3> _movesList, List<float> _directionForward)
{
MovePosition moves = new MovePosition();
moves.Movements = _movesList;
moves.DirectionForward = _directionForward;
this.MovesPositionObjList.Add(moves);
}
public void SaveMoves()
{
foreach(MovePosition mo in MovesPositionObjList)
{
mo.PositionToString();
}
}
public void loadMoves()
{
foreach(MovePosition mo in MovesPositionObjList)
{
mo.stringToPosition();
}
}
public void CleanMovements()
{
MovesPositionObjList.Clear();
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
[Serializable]
public class MovementData
{
public string name;
public List<MovePosition> MovesPositionObjList = new List<MovePosition>();
[Serializable]
public class MovePosition
{
[NonSerialized]
public List<Vector3> Movements = new List<Vector3>();
public List<string> MovementsStr = new List<string>();
public List<float> DirectionForward = new List<float>();
public void PositionToString()
{
if(Movements.Count > 0)
{
MovementsStr.Clear();
}
foreach(Vector3 movement in Movements)
{
string str = movement.x.ToString() + ", " + movement.y.ToString() + ", " + movement.z.ToString();
MovementsStr.Add(str);
}
}
public void StringToPosition()
{
if(MovementsStr.Count > 0)
Movements.Clear();
foreach(string str in MovementsStr)
{
string[] arr = str.Split(',');
Vector3 vec = new Vector3(float.Parse(arr[0]), float.Parse(arr[1]), float.Parse(arr[2]));
Movements.Add(vec);
}
}
}
public void InsertMoves(List<Vector3> _movesList)
{
MovePosition moves = new MovePosition();
moves.Movements = _movesList; // store the list of the Vector3 _moves
this.MovesPositionObjList.Add(moves); // Moves is the list of the object, then stroe the objects
}
public void InsertMoves(List<Vector3> _movesList, List<float> _directionForward)
{
MovePosition moves = new MovePosition();
moves.Movements = _movesList;
moves.DirectionForward = _directionForward;
this.MovesPositionObjList.Add(moves);
}
public void SaveMoves()
{
foreach(MovePosition mo in MovesPositionObjList)
{
mo.PositionToString(); // mo is the List of Moves, and mo is an object from MovePosition so here use object to do that the position from float to string
}
}
public void loadMoves()
{
foreach(MovePosition mo in MovesPositionObjList)
{
mo.StringToPosition();
}
}
public override string ToString()
{
string str = this.name + ", Len(Moves): " + MovesPositionObjList.Count.ToString();
return str;
}
public void CleanMovements()
{
MovesPositionObjList.Clear();
}
}
// UI Text
public Text recordTimeText;
public Text startTimeText;
回到 Unity 設計一下我們的 Text
// record Object, main Object
public GameObject MainObj;
public GameObject RecordObj;
回到 Unity 新增兩個Car 物件,紅色是我主要移動的物件,綠色為該紀錄物件的動作並回顧剛剛主物件的動作。當然用方塊取代也可以。
// control start time
int startCount = 3; // ready to start time
int recordPlayObjCount = 0; // show the recored time
const int RecordCount = 5;
int recordCount = RecordCount; // record the object move time
bool recoredStart = false; // check ready to start
bool isRecordPlay = false; // check ready to record
moveData = new MovementData();
recordCount = RecordCount;
startCount = 3;
startTimeText.text = startCount.ToString();
這邊要注意到 InvokeRepeating 是負責喚醒一個 void Function,後面的兩個變數就是設定該Function 每次執行的秒數與重複執行的時間區隔。
OnRecord( ) 完整的程式:
void OnRecord()
{
moveData = new MovementData();
recordCount = RecordCount;
startTimeText.text = startCount.ToString();
startCount = 3; // need to restart the time
// start to minus time and ready to record
InvokeRepeating("StartRecord", 1, 1);
}
// here is to control time and set the bool to record movement
void StartRecord()
{
if(!recoredStart)
{
startCount--;
startTimeText.text = startCount.ToString();
if(startCount == 0)
{
recoredStart = true; // start to record mainObj movement
startTimeText.text = "===Start to Record!===";
}
}else{
recordCount--;
if(recordCount >= 0)
{
recordTimeText.text = recordCount.ToString();
}
if(recordCount == -1){
recoredStart = false; // stop record!
CancelInvoke("StartRecord"); // stop to Invoke the void function
Debug.Log("Success to record movement!");
}
}
}
void RecoredObjMovement()
{
if(recoredStart)
{
//print("Record Object Position....");
List<Vector3> positionList = new List<Vector3>();
positionList.Add(MainObj.transform.position);
moveData.InsertPosition(positionList);
}
}
void RecordedObjPlay()
{
if(isRecordPlay)
{
//print("Play Record...");
if(recordPlayObjCount < moveData.movePositionsObjList.Count)
{
MovementData.MovePosition movePosObj = moveData.movePositionsObjList[recordPlayObjCount++];
foreach(Vector3 vec in movePosObj.MoveVector3List)
{
RecordObj.transform.position = vec;
}
}else{
// reset the variables
isRecordPlay = false;
recordPlayObjCount = 0;
}
}
}
以下就是去控制是否要開始回顧剛剛的動作。加上控制去執行。
void PlayRecord()
{
if(moveData != null)
{
isRecordPlay = true;
}
}
void Update()
{
if(Input.GetKeyDown("p"))
{
Debug.Log("Start to record movement!");
OnRecord();
}
if(Input.GetKeyDown("l"))
{
Debug.Log("Start to play movement!");
print("Start to play movement!");
PlayRecord();
}
// check record and play record
RecoredObjMovement();
RecordedObjPlay();
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class RecordManager : MonoBehaviour
{
// UI Text
public Text recordTimeText;
public Text startTimeText;
// record Object, main Object
public GameObject MainObj;
public GameObject RecordObj;
private MovementData moveData;
// control start time
int startCount = 3; // ready to start time
int recordPlayObjCount = 0; // show the recored time
const int RecordCount = 5;
int recordCount = RecordCount; // record the object move time
bool recoredStart = false; // check ready to start
bool isRecordPlay = false; // check ready to record
void Update()
{
if(Input.GetKeyDown("p"))
{
Debug.Log("Start to record movement!");
OnRecord();
}
if(Input.GetKeyDown("l"))
{
Debug.Log("Start to play movement!");
print("Start to play movement!");
PlayRecord();
}
// check record and play record
RecoredObjMovement();
RecordedObjPlay();
}
// step 1, record the object
void OnRecord()
{
moveData = new MovementData();
recordCount = RecordCount;
startTimeText.text = startCount.ToString();
startCount = 3; // need to restart the time
// start to minus time and ready to record
InvokeRepeating("StartRecord", 1, 1);
}
// here is to control time and set the bool to record movement
void StartRecord()
{
if(!recoredStart)
{
startCount--;
startTimeText.text = startCount.ToString();
if(startCount == 0)
{
recoredStart = true;
startTimeText.text = "===Start to Record!===";
}
}else{
recordCount--;
if(recordCount >= 0)
{
recordTimeText.text = recordCount.ToString();
}
if(recordCount == -1){
recoredStart = false;
CancelInvoke("StartRecord"); // stop to Invoke the void function
Debug.Log("Success to record movement!");
}
}
}
void PlayRecord()
{
if(moveData != null)
{
isRecordPlay = true;
}
}
void RecoredObjMovement()
{
if(recoredStart)
{
//print("Record Object Position....");
List<Vector3> positionList = new List<Vector3>();
positionList.Add(MainObj.transform.position);
moveData.InsertPosition(positionList);
}
}
void RecordedObjPlay()
{
if(isRecordPlay)
{
//print("Play Record...");
if(recordPlayObjCount < moveData.movePositionsObjList.Count)
{
MovementData.MovePosition movePosObj = moveData.movePositionsObjList[recordPlayObjCount++];
foreach(Vector3 vec in movePosObj.MoveVector3List)
{
RecordObj.transform.position = vec;
}
}else{
// reset the variables
isRecordPlay = false;
recordPlayObjCount = 0;
}
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
[System.Serializable]
public class MovementData
{
public List<MovePosition> movePositionsObjList = new List<MovePosition>();
[System.Serializable]
public class MovePosition
{
[NonSerialized]
public List<Vector3> MoveVector3List = new List<Vector3>();
public List<string> MovePosStringList = new List<string>();
public List<float> MovePosFloatList = new List<float>();
// let the position float change to string
public void positionToString()
{
if(MoveVector3List.Count > 0)
{
MovePosStringList.Clear(); // clean the list
}
foreach(Vector3 movement in MoveVector3List)
{
string str = movement.x.ToString() + ", " + movement.y.ToString() + ", " + movement.z.ToString();
MovePosStringList.Add(str);
}
}
public void StringToPosition()
{
if(MovePosStringList.Count > 0)
{
MovePosStringList.Clear();
}
foreach(string str in MovePosStringList)
{
string[] splitStr = str.Split(',');
float getstrX = float.Parse(splitStr[0]);
float getstrY = float.Parse(splitStr[1]);
float getstrZ = float.Parse(splitStr[2]);
Vector3 newVec = new Vector3(getstrX, getstrY, getstrZ);
MoveVector3List.Add(newVec);
}
}
}
// Insert the Object Position List
public void InsertPosition(List<Vector3> _moveList)
{
MovePosition moves = new MovePosition();
moves.MoveVector3List = _moveList;
movePositionsObjList.Add(moves);
}
// let the position string value change to float value
public void StringToPos()
{
foreach(MovePosition movesObj in movePositionsObjList)
{
movesObj.StringToPosition();
}
}
// let the position float value change to string value
public void PosToString()
{
foreach(MovePosition movesObj in movePositionsObjList)
{
movesObj.positionToString();
}
}
public void ClearObj()
{
movePositionsObjList.Clear();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CarMovement : MonoBehaviour
{
public float sideForce = 50f;
public float forwardForce = 80f;
void Update()
{
float dx = Input.GetAxis("Horizontal");
float dz = Input.GetAxis("Vertical");
transform.position += new Vector3(dx * sideForce * Time.deltaTime, 0f, dz * forwardForce * Time.deltaTime);
}
}
首先確認我們的 UI Text 是否都對應好了
執行開始的時候不會有任何變化
點選 P 會開始倒數3秒讓使用者準備好。
接下來是給受試者紀錄物件移動的時間,這時候可以開始移動物件。
錄製完物件的動作後,點選 L 就會回顧剛剛物件移動的方式,很有趣完全一樣的路徑。