如何精确采样频率为60Hz的数据?

时间:2016-11-21 19:17:54

标签: c# unity3d sampling data-science gameobject

实际上,我使用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获得更高的精确度?

1 个答案:

答案 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.xTransform类。 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
}