Browse Source

Some Logs from Andriis Trial

unknown 2 years ago
parent
commit
5d6db83715
34 changed files with 1520 additions and 417 deletions
  1. 79 20
      Assets/Coin/CoinCreation.cs
  2. 424 389
      Assets/Scenes/MainScene.unity
  3. 6 2
      Assets/Scripts/Controller/SensorBikeController.cs
  4. 26 3
      Assets/Scripts/Logging/Data/BikeGameObjectDataLogger.cs
  5. 24 1
      Assets/Scripts/Obstacles/PlayerStats.cs
  6. 1 2
      Assets/Scripts/Pools/CoinPool.cs
  7. 13 0
      Assets/schoenlogger/Condition.cs
  8. 11 0
      Assets/schoenlogger/Condition.cs.meta
  9. 64 0
      Assets/schoenlogger/CsvCompatible.cs
  10. 11 0
      Assets/schoenlogger/CsvCompatible.cs.meta
  11. 8 0
      Assets/schoenlogger/HOHCI.meta
  12. 26 0
      Assets/schoenlogger/HOHCI/HOHCICondition.cs
  13. 11 0
      Assets/schoenlogger/HOHCI/HOHCICondition.cs.meta
  14. 20 0
      Assets/schoenlogger/HOHCI/HOHCIEntry.cs
  15. 11 0
      Assets/schoenlogger/HOHCI/HOHCIEntry.cs.meta
  16. 6 0
      Assets/schoenlogger/HOHCI/HOHCILogger.cs
  17. 11 0
      Assets/schoenlogger/HOHCI/HOHCILogger.cs.meta
  18. 14 0
      Assets/schoenlogger/HOHCI/HOHCIStudyManager.cs
  19. 11 0
      Assets/schoenlogger/HOHCI/HOHCIStudyManager.cs.meta
  20. 10 0
      Assets/schoenlogger/LogEntry.cs
  21. 11 0
      Assets/schoenlogger/LogEntry.cs.meta
  22. 146 0
      Assets/schoenlogger/Logger.cs
  23. 11 0
      Assets/schoenlogger/Logger.cs.meta
  24. 8 0
      Assets/schoenlogger/Packages.meta
  25. 43 0
      Assets/schoenlogger/Packages/manifest.json
  26. 7 0
      Assets/schoenlogger/Packages/manifest.json.meta
  27. 338 0
      Assets/schoenlogger/Packages/packages-lock.json
  28. 7 0
      Assets/schoenlogger/Packages/packages-lock.json.meta
  29. 0 0
      Assets/schoenlogger/README.md
  30. 7 0
      Assets/schoenlogger/README.md.meta
  31. 20 0
      Assets/schoenlogger/Singleton.cs
  32. 11 0
      Assets/schoenlogger/Singleton.cs.meta
  33. 113 0
      Assets/schoenlogger/StudyManager.cs
  34. 11 0
      Assets/schoenlogger/StudyManager.cs.meta

+ 79 - 20
Assets/Coin/CoinCreation.cs

@@ -23,8 +23,12 @@ public class CoinCreation : MonoBehaviour
     // Parameters to determine stepsize.
     float route_lenght;
     [SerializeField] public int Coin_Number;
+    [SerializeField] public float Distance_Cross; // Test for cross coin drawing
+    [SerializeField] public float Distance_Slalom;
+    //[SerializeField] public bool Draw_Cross; // Maybe draw on Line
     public float stepsize; // public float stepsize = 10; //TODO welcher Wert ist hier passend?
     List<float> differeces = new List<float>();
+    int counter = 0; // How many Coins were drawn on the road
 
     public CoinPool coinPool;
     public List<RoutePoint> points = new List<RoutePoint>();
@@ -42,6 +46,7 @@ public class CoinCreation : MonoBehaviour
 
     int points_size = 30; //needs to be changed if Route changes!
 
+    // Determine route length
     public void CalcRoute()
     {
         route_lenght = 0;
@@ -102,17 +107,33 @@ public class CoinCreation : MonoBehaviour
 
                 Vector3 rotationVector = new Vector3(0f, 0f, 0f);
 
-                // Zwischenlösung um Coins nicht in Kreuzungdatstellen zu müssen
-                int test = 1;
-                stepsize = stepsize - 1;
+                int idx = 1;
+                bool slalom = false;
+                bool slalom_x_dir = true;
 
-                // if (start_point.slalom || (start_point.slalom && end_point.slalom))
                 if (start_point.slalom && end_point.slalom)
                 {
-                    //TODO Was muss beim schiefen verteilen für den Slalom angepasst werden? z.B. stepsize?
                     Debug.Log("Coin-Creation: Place Coins for Slalom.");
-                    test = 0;
-                    stepsize = stepsize + 1;
+                    idx = 0;
+
+                    // Check if slalom along x-axis (slalom_x_dir) or along z-axis (!slalom_x_dir)
+                    if (!points[pointIndex - 2].slalom)
+                    {
+                        if (start_point.x - points[pointIndex - 2].x == 0)
+                        {
+                            slalom_x_dir = false;
+                        }
+                        else
+                        {
+                            slalom_x_dir = true;
+                        }
+                    }
+                    else
+                    {
+                        slalom = true;
+                    }
+
+
                 }
                 if (distance_x > 0) // wenn entlang der x Richung verteilt wird
                 {
@@ -127,27 +148,65 @@ public class CoinCreation : MonoBehaviour
                     rotationVector = new Vector3(0f, 90f, 90f);
                 }
 
-                for (int i = test; i < stepsize; ++i)
+                for (int i = idx; i < System.Math.Round(stepsize); ++i)
                 {
                     //Coin-Position berechnen
-                    if (start_point.x - end_point.x > 0)
+                    if (!slalom || (slalom && i != 0))
                     {
-                        coin_position.x = start_point.x - i * space_x;
+                        if (start_point.x - end_point.x > 0)
+                        {
+                            coin_position.x = start_point.x - i * space_x;
+                        }
+                        if (start_point.x - end_point.x <= 0)
+                        {
+                            coin_position.x = start_point.x + i * space_x;
+                        }
+                        if (start_point.z - end_point.z > 0)
+                        {
+                            coin_position.z = start_point.z - i * space_z;
+                        }
+                        if (start_point.z - end_point.z <= 0)
+                        {
+                            coin_position.z = start_point.z + i * space_z;
+                        }
                     }
-                    if (start_point.x - end_point.x <= 0)
+                    else
                     {
-                        coin_position.x = start_point.x + i * space_x;
+                        if (slalom_x_dir)
+                        {
+                            coin_position.x = start_point.x;
+                            if (start_point.z - end_point.z > 0)
+                            {
+                                coin_position.z = start_point.z - Distance_Slalom;
+                            }
+                            if (start_point.z - end_point.z <= 0)
+                            {
+                                coin_position.z = start_point.z + Distance_Slalom;
+                            }
+                        }
+                        else
+                        {
+                            if (start_point.x - end_point.x > 0)
+                            {
+                                coin_position.x = start_point.x - Distance_Slalom;
+                            }
+                            if (start_point.x - end_point.x <= 0)
+                            {
+                                coin_position.x = start_point.x + Distance_Slalom;
+                            }
+                            coin_position.z = start_point.z + Distance_Slalom;
+                        }
                     }
-                    if (start_point.z - end_point.z > 0)
-                    {
-                        coin_position.z = start_point.z - i * space_z;
-                    }
-                    if (start_point.z - end_point.z <= 0)
+
+                    coin_position.y = 0.5f;
+
+                    if ((distance_x - Distance_Cross < System.Math.Abs(coin_position.x - start_point.x) && distance_x != 0 && !end_point.slalom)
+                        || (distance_z - Distance_Cross < System.Math.Abs(coin_position.z - start_point.z) && distance_z != 0 && !end_point.slalom))
                     {
-                        coin_position.z = start_point.z + i * space_z;
+                        break;
                     }
 
-                    coin_position.y = 0.5f;
+                    counter++;
 
                     //Coin erstellen
                     coin = coinPool.GetItem();
@@ -155,7 +214,7 @@ public class CoinCreation : MonoBehaviour
                     coin.transform.rotation = Quaternion.Euler(rotationVector);
                     if (((i + 1) == stepsize) && (coin != null)) { Debug.Log("CoinCreation: Coin placement finished."); }
                 }
-
+                Debug.Log(counter);
             } while (end_point.slalom); //Da diese Funktion nur von Kreuzungen und nicht von Slalom-Referenzpunkten aufgerufen wird
             // muss hier eine Schleife über die einzelnen Slalom-Abschnitte gemacht werden
         }

File diff suppressed because it is too large
+ 424 - 389
Assets/Scenes/MainScene.unity


+ 6 - 2
Assets/Scripts/Controller/SensorBikeController.cs

@@ -6,6 +6,9 @@ using Sensors.ANT;
 using Tracking;
 using UnityEngine;
 
+// Declare outisde for public visibility
+public enum SteeringMode { frontWheel, Leaning, HMD };
+
 namespace Controller
 {
     [Serializable]
@@ -30,7 +33,6 @@ namespace Controller
     [RequireComponent(typeof(IBicycleController))]
     public class SensorBikeController : MonoBehaviour
     {
-        public enum SteeringMode { frontWheel, Leaning, HMD };
         public PolarRotationMapping polarRotationMapping;
         public FrontWheelTrackerConfig frontWheelTrackerConfig;
         public HMDTrackerConfig hmdTrackerConfig;
@@ -80,7 +82,9 @@ namespace Controller
                     var polarData = sensorData.BleData;
                     if (polarData != null)
                     {
-                        bicycleController.CurrentSteerAngle = (polarData.Value.Acc.y - polarRotationMapping.center) * leanFactor;
+                        bicycleController.CurrentLeaningAngle = (polarData.Value.Acc.y - polarRotationMapping.center) * leanFactor;
+                        // Activate below in case we also need the steering angle
+                        //bicycleController.CurrentSteerAngle = (polarData.Value.Acc.y - polarRotationMapping.center) * leanFactor;
                     }
                     break;
                 case SteeringMode.HMD:

+ 26 - 3
Assets/Scripts/Logging/Data/BikeGameObjectDataLogger.cs

@@ -19,9 +19,15 @@ namespace Logging.Data
         private readonly float velocityY;
         private readonly float velocityZ;
         private readonly int collisionCounter;
+        private readonly int coinCounter;
+        private readonly int hasFinished;
+        private readonly int hasFinishedSlalom1;
+        private readonly int hasFinishedSlalom2;
+        private readonly string condition;
 
         public BikeGameObjectDataLog(long timestamp, float positionX, float positionY, float positionZ, float rotationX,
-            float rotationY, float rotationZ, float velocityX, float velocityY, float velocityZ, int collisionCounter)
+            float rotationY, float rotationZ, float velocityX, float velocityY, float velocityZ, int collisionCounter, int coinCounter, 
+            int hasFinished, int hasFinishedSlalom1, int hasFinishedSlalom2, string condition)
         {
             this.timestamp = timestamp;
             this.positionX = positionX;
@@ -34,6 +40,11 @@ namespace Logging.Data
             this.velocityY = velocityY;
             this.velocityZ = velocityZ;
             this.collisionCounter = collisionCounter;
+            this.coinCounter = coinCounter;
+            this.hasFinished = hasFinished;
+            this.hasFinishedSlalom1 = hasFinishedSlalom1;
+            this.hasFinishedSlalom2 = hasFinishedSlalom2;
+            this.condition = condition;
         }
 
         public KeyValuePair<long, string[]> Serialize()
@@ -50,7 +61,12 @@ namespace Logging.Data
                     velocityX.ToString("F6", CultureInfo.InvariantCulture),
                     velocityY.ToString("F6", CultureInfo.InvariantCulture),
                     velocityZ.ToString("F6", CultureInfo.InvariantCulture),
-                    collisionCounter.ToString("F6", CultureInfo.InvariantCulture)
+                    collisionCounter.ToString("F6", CultureInfo.InvariantCulture),
+                    coinCounter.ToString("F6", CultureInfo.InvariantCulture),
+                    hasFinished.ToString("F6", CultureInfo.InvariantCulture),
+                    hasFinishedSlalom1.ToString("F6", CultureInfo.InvariantCulture),
+                    hasFinishedSlalom2.ToString("F6", CultureInfo.InvariantCulture),
+                    condition,
                 });
         }
     }
@@ -80,7 +96,14 @@ namespace Logging.Data
             Log(new BikeGameObjectDataLog(Helpers.RoundToLong(Time.time * 1000),
                 pos.x, pos.y, pos.z,
                 rot.x, rot.y, rot.z,
-                velocity.x, velocity.y, velocity.z, playerStats.collisionCounter));
+                velocity.x, velocity.y, velocity.z, 
+                playerStats.collisionCounter,
+                playerStats.coinCounter,
+                playerStats.hasFinished,
+                playerStats.hasFinishedSlalom1,
+                playerStats.hasFinishedSlalom2,
+                playerStats.condition
+            ));
         }
 
         public override IEnumerable<BikeGameObjectDataLog> ReadLog(IEnumerable<IEnumerable<string>> lines)

+ 24 - 1
Assets/Scripts/Obstacles/PlayerStats.cs

@@ -6,17 +6,40 @@ public class PlayerStats : MonoBehaviour
 {
     public int collisionCounter;
     public int coinCounter;
+    public string condition;
+    public int hasFinished;
+    public int hasFinishedSlalom1;
+    public int hasFinishedSlalom2;
+
     // Start is called before the first frame update
     void Start()
     {
         collisionCounter = 0;
         coinCounter = 0;
+        hasFinished = -1;
+        hasFinishedSlalom1 = -1;
+        hasFinishedSlalom2 = -1;
+        condition = "";
     }
 
     // Update is called once per frame
     void Update()
     {
-        
+        Debug.Log("Before :" + condition); 
+        var bikePlayer = GameObject.Find("BikePlayer - RigidBody");
+        switch (bikePlayer.GetComponent<Controller.SensorBikeController>().steeringSelection)
+        {
+            case SteeringMode.frontWheel:
+                condition = "frontWheel";
+                break;
+            case SteeringMode.Leaning:
+                condition = "Leaning";
+                break;
+            case SteeringMode.HMD:
+                condition = "HMD";
+                break;
+        }
+        Debug.Log("After :" + condition); 
     }
 
     public void IncreaseCollisionCounter() 

+ 1 - 2
Assets/Scripts/Pools/CoinPool.cs

@@ -5,7 +5,7 @@ namespace Pools
 {
     public class CoinPool : MonoBehaviour
     {
-        public int initialSize = 100;
+        public int initialSize = 50;
         public int amountIncrease = 5;
         public GameObject prefab;
         private HashSet<GameObject> active;
@@ -55,7 +55,6 @@ namespace Pools
             {
                 item.SetActive(false);
                 inactive.Enqueue(item);
-                Debug.Log("CoinPool: Coin returned to Pool :)");
             }
         }
     }

+ 13 - 0
Assets/schoenlogger/Condition.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using UnityEngine;
+
+namespace SchoenLogger
+{
+    [Serializable]
+    public abstract class Condition : CsvCompatible
+    {
+    }
+}

+ 11 - 0
Assets/schoenlogger/Condition.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: fc5a53e9245dcb748a0aa7d090b7860c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 64 - 0
Assets/schoenlogger/CsvCompatible.cs

@@ -0,0 +1,64 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using UnityEngine;
+
+namespace SchoenLogger
+{
+    public class CsvCompatible
+    {
+        const BindingFlags Bindings = BindingFlags.Public |
+                                      BindingFlags.NonPublic |
+                                      BindingFlags.Instance;
+    
+        public virtual string ToCsv()
+        {
+            StringBuilder csvString = new StringBuilder();
+
+            FieldInfo[] fields = this.GetType()
+                .GetFields(Bindings)
+                .ToArray();
+
+            foreach (FieldInfo fieldInfo in fields)
+            {
+                if (Attribute.IsDefined(fieldInfo, typeof(SerializeField)))
+                {
+                    if (fieldInfo.FieldType == typeof(float))
+                    {
+                        csvString.Append(";" + ((float)fieldInfo.GetValue(this)).ToString("G", CultureInfo.InvariantCulture));
+                        continue;
+                    }
+                    
+                    if (fieldInfo.FieldType == typeof(double))
+                    {
+                        csvString.Append(";" + ((double)fieldInfo.GetValue(this)).ToString("G", CultureInfo.InvariantCulture));
+                        continue;
+                    }
+                    
+                    csvString.Append(";" + fieldInfo.GetValue(this).ToString());
+                }
+            }
+            
+            return csvString.ToString();
+        }
+
+        public static string GetCsvHeader<T>()
+        {
+            StringBuilder headerString = new StringBuilder();
+
+            FieldInfo[] fields = typeof(T)
+                .GetFields(Bindings)
+                .ToArray();
+
+            foreach (FieldInfo fieldInfo in fields)
+            {
+                if (Attribute.IsDefined(fieldInfo, typeof(SerializeField)))
+                    headerString.Append(";" + fieldInfo.Name);
+            }
+            
+            return headerString.ToString();
+        }
+    }
+}

+ 11 - 0
Assets/schoenlogger/CsvCompatible.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7528bd5c53490a14c8193f448b70b66a
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/schoenlogger/HOHCI.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 512d45aa59b0428949151a75687a22c6
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 26 - 0
Assets/schoenlogger/HOHCI/HOHCICondition.cs

@@ -0,0 +1,26 @@
+using System;
+using UnityEngine;
+
+namespace SchoenLogger.HOHCI
+{
+    [Serializable]
+    public class HOHCICondition : Condition
+    {
+        public enum SteeringType
+        {
+            handlebarSteering,
+            hmdSteering,
+            leanSteering
+        }
+        
+        [SerializeField]
+        public SteeringType steer;
+
+        public HOHCICondition() { }
+
+        public HOHCICondition(SteeringType steerType)
+        {
+            steer = steerType;
+        }
+    }   
+}

+ 11 - 0
Assets/schoenlogger/HOHCI/HOHCICondition.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 60b25dfd95fd7d68b812ce255e2e8b3e
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 20 - 0
Assets/schoenlogger/HOHCI/HOHCIEntry.cs

@@ -0,0 +1,20 @@
+using System;
+using UnityEngine;
+
+namespace SchoenLogger.HOHCI
+{
+    [Serializable]
+    public class HOHCIEntry : LogEntry
+    {
+        [SerializeField]
+        public float Time;
+
+        [SerializeField]
+        public int CoinCount;
+        [SerializeField]
+        public int CollisionCount;
+        [SerializeField]
+        public bool ReachedFinish;
+    }
+}
+

+ 11 - 0
Assets/schoenlogger/HOHCI/HOHCIEntry.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e6d61c2934c13ad54ab8f602c285bf27
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 6 - 0
Assets/schoenlogger/HOHCI/HOHCILogger.cs

@@ -0,0 +1,6 @@
+namespace SchoenLogger.HOHCI
+{
+    public class HOHCILogger : Logger<HOHCIEntry, HOHCICondition>
+    {
+    }
+}

+ 11 - 0
Assets/schoenlogger/HOHCI/HOHCILogger.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8b0f16f32355c1ba1bcb0036da00ce8c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 14 - 0
Assets/schoenlogger/HOHCI/HOHCIStudyManager.cs

@@ -0,0 +1,14 @@
+namespace SchoenLogger.HOHCI
+{
+    public class HOHCIStudyManager : StudyManager<HOHCICondition>
+    {
+        protected override void CreateConditions(ref HOHCICondition[] conditions)
+        {
+            conditions = new HOHCICondition[3];
+            conditions[0] = new HOHCICondition(HOHCICondition.SteeringType.handlebarSteering);
+            conditions[1] = new HOHCICondition(HOHCICondition.SteeringType.hmdSteering);
+            conditions[2] = new HOHCICondition(HOHCICondition.SteeringType.leanSteering);
+        }
+    }
+}
+

+ 11 - 0
Assets/schoenlogger/HOHCI/HOHCIStudyManager.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 83dc838f8c2a4c1b5b16347d8daf20f7
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 10 - 0
Assets/schoenlogger/LogEntry.cs

@@ -0,0 +1,10 @@
+using System;
+
+namespace SchoenLogger
+{
+    [Serializable]
+    public abstract class LogEntry : CsvCompatible
+    {
+    }
+}
+

+ 11 - 0
Assets/schoenlogger/LogEntry.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 007a0a3e892e9c448a58687c6888cfe6
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 146 - 0
Assets/schoenlogger/Logger.cs

@@ -0,0 +1,146 @@
+using UnityEngine;
+using System.IO;
+using System.Text;
+#if UNITY_EDITOR
+using UnityEditor;
+#endif
+
+namespace SchoenLogger
+{
+    public class Logger<TLogEntry, TCondition> : MonoBehaviour, ILogger where TLogEntry : LogEntry, new() where TCondition : Condition , new()
+    {
+        [Header("General Settings")]
+        public StudyManager<TCondition> studyManager;
+        [Tooltip("Keep empty to autofill to PersistentDataPath")]
+        public string LogPathOverride= "";
+        [Tooltip("Buffersize (in bytes) for the LogFileWriter. Larger means less writing cycles")]
+        public int BufferSize = 65536;
+        
+        private string LogPath = "";
+        private string LogFilePath = "";
+        private string LogFileName = "";
+        private FileStream LogFileStream;
+        private StreamWriter LogFileWriter;
+        private string CurrentConditionString = "";
+        private int CurrentParticipantId = -1;
+        
+        // Start is called before the first frame update
+        void Start()
+        {
+            StartLogFileStream();
+
+            if (studyManager == null)
+            {
+                studyManager.GetComponent<StudyManager<TCondition>>();
+                if (studyManager == null)
+                {
+                    Debug.LogErrorFormat("{0} has not set studyManager", this.gameObject.name);
+                    return;
+                }
+            }
+            studyManager.ChangeCondition.AddListener(OnConditionChanged);
+        }
+
+        private void OnConditionChanged(TCondition cond, int partId)
+        {
+            CurrentConditionString = cond.ToCsv();
+            CurrentParticipantId = partId;
+        }
+        
+        private void OnApplicationQuit()
+        {
+            LogFileStream.Flush();
+            LogFileWriter?.Dispose();
+            LogFileStream?.Dispose();
+            Debug.Log("Closed Logger FileStreams!");
+        }
+
+        protected void StartLogFileStream()
+        {
+            if(LogPathOverride == "")
+                LogPath = Application.persistentDataPath;
+            else
+                LogPath = LogPathOverride;
+
+            LogFileName = "log_" + typeof(TLogEntry).Name + ".csv";
+            LogFilePath = Path.Combine(LogPath, LogFileName);
+
+            if (!File.Exists(LogFilePath))
+            {
+                using (FileStream stream = File.Open(LogFilePath, FileMode.Create))
+                {
+                    using (StreamWriter writer = new StreamWriter(stream))
+                    {
+                        writer.WriteLine(GetLogFileHeader());
+                        writer.Flush();
+                    }
+                }
+                
+                Debug.LogFormat("Created new Logfile {0}", LogFileName);
+            }
+            
+            LogFileStream = File.Open(LogFilePath, FileMode.Append);
+            LogFileWriter = new StreamWriter(LogFileStream, Encoding.UTF8, BufferSize);
+            LogFileWriter.AutoFlush = true;
+        }
+
+        protected string GetLogFileHeader()
+        {
+            StringBuilder header = new StringBuilder("ParticipantID");
+            header.Append(Condition.GetCsvHeader<TCondition>());
+            header.Append(LogEntry.GetCsvHeader<TLogEntry>());
+            return header.ToString();
+        }
+
+        public void Log(TLogEntry entry)
+        {
+            string logEntry = CreateLogEntryCsvLine(entry);
+            LogFileWriter.WriteLine(CreateLogEntryCsvLine(entry));
+            Debug.Log(logEntry);
+        }
+
+        private string CreateLogEntryCsvLine(TLogEntry entry)
+        {
+            StringBuilder stringBuilder = new StringBuilder();
+            stringBuilder.Append(CurrentParticipantId);
+            stringBuilder.Append(CurrentConditionString);
+            stringBuilder.Append(entry.ToCsv());
+
+            return stringBuilder.ToString();
+        }
+
+        public string GetLogPath()
+        {
+            if (LogPath == "")
+                return Application.persistentDataPath;
+            
+            return LogPath;
+        }
+    }
+
+    public interface ILogger
+    {
+        string GetLogPath();
+    }
+
+#if UNITY_EDITOR
+    [CustomEditor(typeof(Logger<,>), true)]
+    public class LoggerEditor : Editor
+    {
+        public override void OnInspectorGUI()
+        {
+            DrawDefaultInspector();
+            ILogger Target = (ILogger)target;
+
+            EditorGUILayout.Space(10);
+            EditorGUILayout.LabelField("Info", EditorStyles.boldLabel);
+            EditorGUILayout.LabelField("Log Path: ", Target.GetLogPath());
+            if (GUILayout.Button("Show in Explorer"))
+            {
+                string itemPath = Target.GetLogPath().Replace(@"/", @"\");
+                System.Diagnostics.Process.Start("explorer.exe", "/select,"+itemPath);
+            }
+        }
+    }
+#endif
+}

+ 11 - 0
Assets/schoenlogger/Logger.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: bc73ad4520362b644910d908fad4d5f2
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/schoenlogger/Packages.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: effb2d34d175bcceb87f9d4764958637
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 43 - 0
Assets/schoenlogger/Packages/manifest.json

@@ -0,0 +1,43 @@
+{
+  "dependencies": {
+    "com.unity.collab-proxy": "1.3.9",
+    "com.unity.ide.rider": "2.0.7",
+    "com.unity.ide.visualstudio": "2.0.7",
+    "com.unity.ide.vscode": "1.2.3",
+    "com.unity.test-framework": "1.1.22",
+    "com.unity.textmeshpro": "3.0.1",
+    "com.unity.timeline": "1.4.6",
+    "com.unity.ugui": "1.0.0",
+    "com.unity.modules.ai": "1.0.0",
+    "com.unity.modules.androidjni": "1.0.0",
+    "com.unity.modules.animation": "1.0.0",
+    "com.unity.modules.assetbundle": "1.0.0",
+    "com.unity.modules.audio": "1.0.0",
+    "com.unity.modules.cloth": "1.0.0",
+    "com.unity.modules.director": "1.0.0",
+    "com.unity.modules.imageconversion": "1.0.0",
+    "com.unity.modules.imgui": "1.0.0",
+    "com.unity.modules.jsonserialize": "1.0.0",
+    "com.unity.modules.particlesystem": "1.0.0",
+    "com.unity.modules.physics": "1.0.0",
+    "com.unity.modules.physics2d": "1.0.0",
+    "com.unity.modules.screencapture": "1.0.0",
+    "com.unity.modules.terrain": "1.0.0",
+    "com.unity.modules.terrainphysics": "1.0.0",
+    "com.unity.modules.tilemap": "1.0.0",
+    "com.unity.modules.ui": "1.0.0",
+    "com.unity.modules.uielements": "1.0.0",
+    "com.unity.modules.umbra": "1.0.0",
+    "com.unity.modules.unityanalytics": "1.0.0",
+    "com.unity.modules.unitywebrequest": "1.0.0",
+    "com.unity.modules.unitywebrequestassetbundle": "1.0.0",
+    "com.unity.modules.unitywebrequestaudio": "1.0.0",
+    "com.unity.modules.unitywebrequesttexture": "1.0.0",
+    "com.unity.modules.unitywebrequestwww": "1.0.0",
+    "com.unity.modules.vehicles": "1.0.0",
+    "com.unity.modules.video": "1.0.0",
+    "com.unity.modules.vr": "1.0.0",
+    "com.unity.modules.wind": "1.0.0",
+    "com.unity.modules.xr": "1.0.0"
+  }
+}

+ 7 - 0
Assets/schoenlogger/Packages/manifest.json.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: a953395aa8de65314832e88aa3dfd199
+TextScriptImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 338 - 0
Assets/schoenlogger/Packages/packages-lock.json

@@ -0,0 +1,338 @@
+{
+  "dependencies": {
+    "com.unity.collab-proxy": {
+      "version": "1.3.9",
+      "depth": 0,
+      "source": "registry",
+      "dependencies": {},
+      "url": "https://packages.unity.com"
+    },
+    "com.unity.ext.nunit": {
+      "version": "1.0.6",
+      "depth": 1,
+      "source": "registry",
+      "dependencies": {},
+      "url": "https://packages.unity.com"
+    },
+    "com.unity.ide.rider": {
+      "version": "2.0.7",
+      "depth": 0,
+      "source": "registry",
+      "dependencies": {
+        "com.unity.test-framework": "1.1.1"
+      },
+      "url": "https://packages.unity.com"
+    },
+    "com.unity.ide.visualstudio": {
+      "version": "2.0.7",
+      "depth": 0,
+      "source": "registry",
+      "dependencies": {
+        "com.unity.test-framework": "1.1.9"
+      },
+      "url": "https://packages.unity.com"
+    },
+    "com.unity.ide.vscode": {
+      "version": "1.2.3",
+      "depth": 0,
+      "source": "registry",
+      "dependencies": {},
+      "url": "https://packages.unity.com"
+    },
+    "com.unity.test-framework": {
+      "version": "1.1.22",
+      "depth": 0,
+      "source": "registry",
+      "dependencies": {
+        "com.unity.ext.nunit": "1.0.6",
+        "com.unity.modules.imgui": "1.0.0",
+        "com.unity.modules.jsonserialize": "1.0.0"
+      },
+      "url": "https://packages.unity.com"
+    },
+    "com.unity.textmeshpro": {
+      "version": "3.0.1",
+      "depth": 0,
+      "source": "registry",
+      "dependencies": {
+        "com.unity.ugui": "1.0.0"
+      },
+      "url": "https://packages.unity.com"
+    },
+    "com.unity.timeline": {
+      "version": "1.4.6",
+      "depth": 0,
+      "source": "registry",
+      "dependencies": {
+        "com.unity.modules.director": "1.0.0",
+        "com.unity.modules.animation": "1.0.0",
+        "com.unity.modules.audio": "1.0.0",
+        "com.unity.modules.particlesystem": "1.0.0"
+      },
+      "url": "https://packages.unity.com"
+    },
+    "com.unity.ugui": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {
+        "com.unity.modules.ui": "1.0.0",
+        "com.unity.modules.imgui": "1.0.0"
+      }
+    },
+    "com.unity.modules.ai": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {}
+    },
+    "com.unity.modules.androidjni": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {}
+    },
+    "com.unity.modules.animation": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {}
+    },
+    "com.unity.modules.assetbundle": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {}
+    },
+    "com.unity.modules.audio": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {}
+    },
+    "com.unity.modules.cloth": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {
+        "com.unity.modules.physics": "1.0.0"
+      }
+    },
+    "com.unity.modules.director": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {
+        "com.unity.modules.audio": "1.0.0",
+        "com.unity.modules.animation": "1.0.0"
+      }
+    },
+    "com.unity.modules.imageconversion": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {}
+    },
+    "com.unity.modules.imgui": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {}
+    },
+    "com.unity.modules.jsonserialize": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {}
+    },
+    "com.unity.modules.particlesystem": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {}
+    },
+    "com.unity.modules.physics": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {}
+    },
+    "com.unity.modules.physics2d": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {}
+    },
+    "com.unity.modules.screencapture": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {
+        "com.unity.modules.imageconversion": "1.0.0"
+      }
+    },
+    "com.unity.modules.subsystems": {
+      "version": "1.0.0",
+      "depth": 1,
+      "source": "builtin",
+      "dependencies": {
+        "com.unity.modules.jsonserialize": "1.0.0"
+      }
+    },
+    "com.unity.modules.terrain": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {}
+    },
+    "com.unity.modules.terrainphysics": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {
+        "com.unity.modules.physics": "1.0.0",
+        "com.unity.modules.terrain": "1.0.0"
+      }
+    },
+    "com.unity.modules.tilemap": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {
+        "com.unity.modules.physics2d": "1.0.0"
+      }
+    },
+    "com.unity.modules.ui": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {}
+    },
+    "com.unity.modules.uielements": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {
+        "com.unity.modules.ui": "1.0.0",
+        "com.unity.modules.imgui": "1.0.0",
+        "com.unity.modules.jsonserialize": "1.0.0",
+        "com.unity.modules.uielementsnative": "1.0.0"
+      }
+    },
+    "com.unity.modules.uielementsnative": {
+      "version": "1.0.0",
+      "depth": 1,
+      "source": "builtin",
+      "dependencies": {
+        "com.unity.modules.ui": "1.0.0",
+        "com.unity.modules.imgui": "1.0.0",
+        "com.unity.modules.jsonserialize": "1.0.0"
+      }
+    },
+    "com.unity.modules.umbra": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {}
+    },
+    "com.unity.modules.unityanalytics": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {
+        "com.unity.modules.unitywebrequest": "1.0.0",
+        "com.unity.modules.jsonserialize": "1.0.0"
+      }
+    },
+    "com.unity.modules.unitywebrequest": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {}
+    },
+    "com.unity.modules.unitywebrequestassetbundle": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {
+        "com.unity.modules.assetbundle": "1.0.0",
+        "com.unity.modules.unitywebrequest": "1.0.0"
+      }
+    },
+    "com.unity.modules.unitywebrequestaudio": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {
+        "com.unity.modules.unitywebrequest": "1.0.0",
+        "com.unity.modules.audio": "1.0.0"
+      }
+    },
+    "com.unity.modules.unitywebrequesttexture": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {
+        "com.unity.modules.unitywebrequest": "1.0.0",
+        "com.unity.modules.imageconversion": "1.0.0"
+      }
+    },
+    "com.unity.modules.unitywebrequestwww": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {
+        "com.unity.modules.unitywebrequest": "1.0.0",
+        "com.unity.modules.unitywebrequestassetbundle": "1.0.0",
+        "com.unity.modules.unitywebrequestaudio": "1.0.0",
+        "com.unity.modules.audio": "1.0.0",
+        "com.unity.modules.assetbundle": "1.0.0",
+        "com.unity.modules.imageconversion": "1.0.0"
+      }
+    },
+    "com.unity.modules.vehicles": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {
+        "com.unity.modules.physics": "1.0.0"
+      }
+    },
+    "com.unity.modules.video": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {
+        "com.unity.modules.audio": "1.0.0",
+        "com.unity.modules.ui": "1.0.0",
+        "com.unity.modules.unitywebrequest": "1.0.0"
+      }
+    },
+    "com.unity.modules.vr": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {
+        "com.unity.modules.jsonserialize": "1.0.0",
+        "com.unity.modules.physics": "1.0.0",
+        "com.unity.modules.xr": "1.0.0"
+      }
+    },
+    "com.unity.modules.wind": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {}
+    },
+    "com.unity.modules.xr": {
+      "version": "1.0.0",
+      "depth": 0,
+      "source": "builtin",
+      "dependencies": {
+        "com.unity.modules.physics": "1.0.0",
+        "com.unity.modules.jsonserialize": "1.0.0",
+        "com.unity.modules.subsystems": "1.0.0"
+      }
+    }
+  }
+}

+ 7 - 0
Assets/schoenlogger/Packages/packages-lock.json.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 6643e15a2f7a08105823fc8dbc0fd4ea
+TextScriptImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 0 - 0
Assets/schoenlogger/README.md


+ 7 - 0
Assets/schoenlogger/README.md.meta

@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 1e04b4d2d775d9e05a5adc3335c69396
+TextScriptImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 20 - 0
Assets/schoenlogger/Singleton.cs

@@ -0,0 +1,20 @@
+using System;
+using UnityEngine;
+
+namespace SchoenLogger
+{
+    public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour
+    {
+        private static readonly Lazy<T> LazyInstance = new Lazy<T>(CreateSingleton);
+
+        public static T Instance => LazyInstance.Value;
+
+        private static T CreateSingleton()
+        {
+            var ownerObject = new GameObject($"{typeof(T).Name} (singleton)");
+            var instance = ownerObject.AddComponent<T>();
+            DontDestroyOnLoad(ownerObject);
+            return instance;
+        }
+    }
+}

+ 11 - 0
Assets/schoenlogger/Singleton.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: eeae0510ceb621f499f1ab8ab9f12455
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 113 - 0
Assets/schoenlogger/StudyManager.cs

@@ -0,0 +1,113 @@
+using UnityEngine;
+using UnityEngine.Events;
+#if UNITY_EDITOR
+using UnityEditor;
+#endif
+
+namespace SchoenLogger
+{
+    public abstract class StudyManager<TCondition> : MonoBehaviour, IStudyManager where TCondition : Condition, new()
+    {
+        [Header("Study Settings")]
+        public int ParticipantId = -1;
+        [Header("Study Events")]
+        public UnityEvent<TCondition, int> ChangeCondition;
+        public UnityEvent<TCondition, int> StartCondition;
+        
+        [Header("Misc")]
+        [SerializeField]
+        protected bool EnableConsoleLogging = false;
+        protected TCondition[] Conditions;
+        protected int CurrentConditionIndex = -1;
+    
+        // Start is called before the first frame update
+        void Start()
+        {
+            CreateConditions(ref Conditions);
+        }
+
+        /// <summary>
+        /// Creates all possible Conditions
+        /// </summary>
+        /// <param name="conditions"></param>
+        protected abstract void CreateConditions(ref TCondition[] conditions);
+
+        public void RaiseNextCondition()
+        {
+            CurrentConditionIndex++;
+            if (CurrentConditionIndex >= Conditions.Length)
+                return;
+
+            ChangeCondition?.Invoke(Conditions[CurrentConditionIndex], ParticipantId);
+            
+            if(EnableConsoleLogging)
+                Debug.LogFormat("Changed Condition to {0}!", CurrentConditionIndex);
+        }
+
+        public void RaiseStartCondition()
+        {
+            StartCondition?.Invoke(Conditions[CurrentConditionIndex], ParticipantId);
+            
+            if(EnableConsoleLogging)
+                Debug.LogFormat("Started Condition! {0}", Conditions[CurrentConditionIndex].ToCsv());
+        }
+
+        public string GetConditionCountString()
+        {
+            if (Conditions == null || !Application.isPlaying)
+                return "Only available on play";
+
+            return Conditions.Length.ToString();
+        }
+
+        public int GetCurrentConditionIndex()
+        {
+            return CurrentConditionIndex;
+        }
+    }
+
+    public interface IStudyManager
+    {
+        void RaiseNextCondition();
+
+        void RaiseStartCondition();
+
+        string GetConditionCountString();
+
+        int GetCurrentConditionIndex();
+    }
+    
+#if UNITY_EDITOR
+    [CustomEditor(typeof(StudyManager<>), true)]
+    public class StudyManagerEditor : Editor
+    {
+        public override void OnInspectorGUI()
+        {
+            DrawDefaultInspector();
+            IStudyManager Target = (IStudyManager)target;
+
+            //EditorGUILayout.Space(10);
+            //EditorGUILayout.LabelField("Manage Conditions", EditorStyles.boldLabel);
+            EditorGUILayout.LabelField("Defined Conditions: ", Target.GetConditionCountString());
+            EditorGUILayout.LabelField("Current Condition: ", Target.GetCurrentConditionIndex().ToString());
+            EditorGUILayout.Space(5);
+            EditorGUILayout.LabelField("Controlls", EditorStyles.boldLabel);
+            GUILayout.BeginHorizontal();
+            if (GUILayout.Button("Setup next Condition"))
+            {
+                Target.RaiseNextCondition();
+            }
+            if (GUILayout.Button("Setup & start next Condition"))
+            {
+                Target.RaiseNextCondition();
+                Target.RaiseStartCondition();
+            }
+            GUILayout.EndHorizontal();
+            if (GUILayout.Button("StartCondition"))
+            {
+                Target.RaiseStartCondition();
+            }
+        }
+    }
+#endif
+}

+ 11 - 0
Assets/schoenlogger/StudyManager.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2db518c0b2a3f0441a4db7ed684db6d5
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

Some files were not shown because too many files changed in this diff