如何精确采样频率为60Hz的数据?
How to precisely sample data with frequency of 60Hz?
实际上,我使用 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)的重复位置,最糟糕的是,我可以在不同的位置(给出无限加速度)。
事实上,分析的游戏对象在时间上以线性函数移动,在 20 秒内沿 X-Y 轴做圆。
因此,此代码无法为我提供良好的科学分析精度。
如何使用 Unity3D 获得更高的精度?
这很复杂。虽然,我不会说在 Unity 中不可能。它可以精确地完成,但不能用 InvokeRepeating
.
您还有其他两种方法可以做到这一点。
1.Coroutine
如果InvokeRepeating
太慢,可能是使用了反射调用函数,也可能是实现不完善
您可以通过使用协程进行直接函数调用并等待 WaitForSecondsRealtime
instead of WaitForSeconds
. WaitForSecondsRealtime
is new in Unity and does not depend on the frame-rate to wait. 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
class。 Unity 会阻止您这样做。您必须在 Main Thread
中执行 dataList.Add(new Data(Time.time, transform.position.x...
。另一种选择是将 transform.position
存储在全局 Vector3
变量中,然后从另一个 Thread
访问它。
你不能也在另一个Thread
中使用Time.time
并且必须在主Thread
中获取它,然后将它存储在将在 Thread
函数
中使用的局部变量
您还必须使用 lock
关键字使其 线程安全 。请注意,使用 lock
关键字会使您的游戏变慢 一点 位。它会从您的游戏中移除至少 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
}
实际上,我使用 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)的重复位置,最糟糕的是,我可以在不同的位置(给出无限加速度)。
事实上,分析的游戏对象在时间上以线性函数移动,在 20 秒内沿 X-Y 轴做圆。
因此,此代码无法为我提供良好的科学分析精度。
如何使用 Unity3D 获得更高的精度?
这很复杂。虽然,我不会说在 Unity 中不可能。它可以精确地完成,但不能用 InvokeRepeating
.
您还有其他两种方法可以做到这一点。
1.Coroutine
如果InvokeRepeating
太慢,可能是使用了反射调用函数,也可能是实现不完善
您可以通过使用协程进行直接函数调用并等待 WaitForSecondsRealtime
instead of WaitForSeconds
. WaitForSecondsRealtime
is new in Unity and does not depend on the frame-rate to wait. 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
class。 Unity 会阻止您这样做。您必须在 Main Thread
中执行 dataList.Add(new Data(Time.time, transform.position.x...
。另一种选择是将 transform.position
存储在全局 Vector3
变量中,然后从另一个 Thread
访问它。
你不能也在另一个Thread
中使用Time.time
并且必须在主Thread
中获取它,然后将它存储在将在 Thread
函数
您还必须使用 lock
关键字使其 线程安全 。请注意,使用 lock
关键字会使您的游戏变慢 一点 位。它会从您的游戏中移除至少 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
}