2016-11-21 7 views
1

На самом деле, я использую метод InvokeRepeating для вызова другого метода каждые 1/x секунд. Проблема в том, что точность задержки между вызовом и полученными мной данными не очень хороша.Как точно сэмплировать данные с частотой 60 Гц?

Как я могу точно выбрать transform.position с частотой 60 Гц.

Вот мой код:

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?

+3

Eh ... просто разместив это там, но вы, вероятно, захотите использовать что-то другое, кроме Unity, если вам нужны точно рассчитанные/имитируемые значения за определенные промежутки времени. Как игровой движок он делает много оптимизаций, используя аппроксимирующие вещи. – Serlite

+0

Вы абсолютно не можете. Это так просто. Unity не имеет никакого отношения - вообще - к этому типу техники. (Обратите внимание, что «Invoke» просто не имеет никакого соединения - ВСЕ ВСЕ - к тому, что вы делаете.) – Fattie

ответ

2

Это сложно. Хотя, я бы не сказал, что это невозможно делать в Unity. Это можно сделать точно, но не с InvokeRepeating.

У вас есть еще два способа сделать это.

. 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)); 
} 

. Thread (рекомендуется)

Это, я рекомендовал вам использовать, так как вы полностью избегаете замедления частоты кадров основной темой Unity. Выполняйте операцию таймера в другом Thread.

Проблема в том, что вы не будет иметь возможность использовать transform.position.x или Transform класс в другую тему. Единство мешает вам это сделать. Вам нужно будет выполнить dataList.Add(new Data(Time.time, transform.position.x... в главном Thread. Другой вариант - хранить transform.position в глобальной переменной Vector3, а затем использовать ее у другого Thread.

Вы не можете также использовать Time.time в другом Thread и должен получить его в главном Thread, а затем сохранить его в локальной переменной, которая будет использоваться в Thread функции

Вы также должны использовать ключевое слово lock, чтобы сделать его Thread-safe. Обратите внимание, что использование ключевого слова lock замедлит вашу игру немного бит. Он удалит по крайней мере 2 или 3 кадра из вашей игры, но он необходим и стоит того преимущества и защиты, которые он предоставляет.

Код ниже сделает все, что я только что сказал. Для целей тестирования частота дискретизации устанавливается равной 2 раз в секунду. Вы можете изменить chage на 60 Гц в переменной samplingRate, если вы считаете, что это работает.

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 
} 
+0

Благодарим вас за ответ. Корутин лучше. Точность вызова составляет + - 2 мс для очень простого приложения. Этого может быть достаточно, я думаю. Я по-прежнему рассматриваю использование C++ для большей точности, если это необходимо. – probitaille

+0

Я программист на C++, но я не знаю, как использование C++ улучшит точность. Рад, что версия coroutine вам помогла. – Programmer

 Смежные вопросы

  • Нет связанных вопросов^_^