实际上,我使用InvokeRepeating
方法每1 / x秒调用另一个方法。问题是调用和我得到的数据之间的延迟精度不好。
如何以60Hz的频率精确采样transform.position
。
这是我的代码:
public class Recorder : MonoBehaviour {
public float samplingRate = 60f; // sample rate in Hz
public string outputFilePath;
private StreamWriter _sw;
private List<Data> dataList = new List<Data>();
public void OnEnable()
{
InvokeRepeating("SampleNow", 0, 1 / samplingRate);
}
public void OnDisable()
{
_sw = System.IO.File.AppendText(outputFilePath);
for (int k=0; k< dataList.Count; k++)
{
_sw.WriteLine("t {0} x {1} y {2} z {3}",
dataList[k].time, dataList[k].x, dataList[k].y, dataList[k].z);
}
_sw.Close();
CancelInvoke();
}
public void SampleNow()
{
dataList.Add(new Data(Time.time, transform.position.x, transform.position.y, transform.position.z));
}
public class Data
{
public float time;
public float x;
public float y;
public float z;
public Data(float time, float x, float y, float z)
{
this.time = time;
this.x = x;
this.y = y;
this.z = z;
}
}
}
所以,有了这个,我可以得到这样的结果:
t 0 x 0 y 0 z 0
t 0.02 x 0.283776 y -0.76 z 0
t 0.04 x 0.599808 y -0.52 z 0
t 0.06 x 0.599808 y -0.52 z 0
t 0.08 x 0.599808 y -0.52 z 0
t 0.09999999 x 0.599808 y -0.52 z 0
t 0.12 x 0.599808 y -0.52 z 0
t 0.14 x 0.599808 y -0.52 z 0
t 0.16 x 0.599808 y -0.52 z 0
t 0.18 x 0.599808 y -0.52 z 0
t 0.2 x 0.599808 y -0.52 z 0
t 0.22 x 0.599808 y -0.52 z 0
t 0.24 x 0.599808 y -0.52 z 0
t 0.26 x 0.599808 y -0.52 z 0
t 0.28 x 0.599808 y -0.52 z 0
t 0.3 x 0.599808 y -0.52 z 0
t 0.32 x 0.599808 y -0.52 z 0
t 0.3338465 x 0.599808 y -0.52 z 0
t 0.3338465 x 0.2918357 y -0.7538424 z 0
t 0.34 x 0.2918357 y -0.7538424 z 0
t 0.3539519 x 0.2918357 y -0.7538424 z 0
t 0.3539519 x 0.6092016 y -0.5125771 z 0
t 0.3705226 x 0.6092016 y -0.5125771 z 0
t 0.3870888 x 0.8340279 y -0.3137283 z 0
t 0.4036556 x 0.9750986 y -0.114934 z 0
t 0.42 x 0.9865224 y 0.09031145 z 0
...
正如你在这个结果中看到的那样,我可以收集不同时间的重复位置(t = 0.04到t = 0.32),最糟糕的是,我可以得到不同位置的重复时间(t = 0.3539519)(给出无限加速度)
事实上,分析的游戏对象在时间上以线性方式移动,在X-Y轴上做了20秒的圆圈。
所以,这段代码并没有给我一个很好的科学分析精度。
如何使用Unity3D获得更高的精确度?
答案 0 :(得分:2)
这很复杂。虽然,我不会说在Unity中做 impossbile 。它可以精确地完成,但不能使用InvokeRepeating
。
您还有其他两种方法可以做到这一点。
<强> 1 即可。的 Coroutine
强>
如果InvokeRepeating
太慢,可能是因为它使用反射来调用函数,或者实现可能并不完美。
您可以通过使用协程进行直接函数调用并使用WaitForSecondsRealtime
代替WaitForSeconds
来完成此操作。 WaitForSecondsRealtime
是Unity中的新功能,并不依赖于等待的帧速率。 WaitForSeconds
确实如此。
public float samplingRate = 60f; // sample rate in Hz
void OnEnable()
{
StartCoroutine(startSampling());
}
IEnumerator startSampling()
{
WaitForSecondsRealtime waitTime = new WaitForSecondsRealtime(1f / samplingRate);
while (true)
{
SampleNow();
yield return waitTime;
}
}
public void SampleNow()
{
Debug.Log("Called");
dataList.Add(new Data(Time.time, transform.position.x, transform.position.y, transform.position.z));
}
2 。 Thread
(推荐)
这个,我建议你应该使用,因为你将完全避免Unity主线程的任何帧速率减慢。在另一个Thread
进行计时器操作。
问题在于赢了能够在另一个线程中使用transform.position.x
或Transform
类。 Unity阻止你这样做。您必须在主dataList.Add(new Data(Time.time, transform.position.x...
中执行Thread
。另一种选择是将transform.position
存储在全局Vector3
变量中,然后将其从另一个Thread
加入。
你不能也在另一个Time.time
中使用Thread
,并且必须在主Thread
中获取它,然后将其存储在一个本地变量中用于Thread
函数
您还必须使用lock
关键字使其线程安全。请注意,使用lock
关键字会使您的游戏减慢 little 位。它将从您的游戏中移除至少2或3帧,但它是必需的并且值得它提供的好处和保护。
下面的代码将完成我刚才所说的一切。为了测试目的,采样率设置为每秒2次。当您认为samplingRate
变量正常工作时,您可以将其变为60Hz。
private List<Data> dataList = new List<Data>();
Thread samplingThread;
const float samplingRate = 2f; // sample rate in Hz
Vector3 posInThread;
float TimetimeInThread;
private System.Object threadLocker = new System.Object();
void OnEnable()
{
startSampling();
}
void startSampling()
{
samplingThread = new Thread(OnSamplingData);
samplingThread.Start();
}
void Update()
{
lock (threadLocker)
{
//Update this values to be used in another Thread
posInThread = transform.position;
TimetimeInThread = Time.time;
}
}
void OnSamplingData()
{
long oldTime = DateTime.Now.Ticks;
long currentTime = oldTime;
const float waitTime = 1f / samplingRate;
float counter;
while (true)
{
currentTime = DateTime.Now.Ticks;
counter = (float)TimeSpan.FromTicks(currentTime - oldTime).TotalSeconds;
if (counter >= waitTime)
{
//Debug.Log("counter: " + counter + " waitTime: " + waitTime);
oldTime = currentTime; //Store current Time
SampleNow();
}
Thread.Sleep(0); //Make sure Unity does not freeze
}
}
public void SampleNow()
{
Debug.Log("Called");
lock (threadLocker)
{
dataList.Add(new Data(TimetimeInThread, posInThread.x, posInThread.y, posInThread.z));
}
}
void OnDisable()
{
if (samplingThread != null && samplingThread.IsAlive)
{
samplingThread.Abort();
}
samplingThread.Join(); //Wait for Thread to finish then exit
}