Commit 9d28856d authored by Maximilian Tomio's avatar Maximilian Tomio
Browse files

Implement FittsTest

parent 2f9d3b45
{
"version": "1.0",
"components": [
"Microsoft.VisualStudio.Workload.ManagedGame"
]
}
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: ActiveMaterial
m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
m_ShaderKeywords: _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _BumpScale: 1
- _Cutoff: 0.48
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 0
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _SmoothnessTextureChannel: 1
- _SpecularHighlights: 1
- _SrcBlend: 1
- _UVSec: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 0.49695027, b: 0, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []
using UnityEngine;
public class ClickableSphere : MonoBehaviour
{
private MeshRenderer Renderer;
[SerializeField]
private Material InActiveMaterial;
[SerializeField]
private Material ActiveMaterial;
// Start is called before the first frame update
void Start()
{
}
void Awake()
{
Renderer = GetComponent<MeshRenderer>();
if(InActiveMaterial != null)
{
Renderer.material = InActiveMaterial;
}
}
// Update is called once per frame
void Update()
{
}
public void SetActive(bool isActive)
{
if (isActive)
{
if (ActiveMaterial != null)
{
Renderer.material = ActiveMaterial;
}
}
else
{
if (InActiveMaterial != null)
{
Renderer.material = InActiveMaterial;
}
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Clicker : MonoBehaviour
{
[SerializeField]
private SphereHandler SphereHandler;
private Logger Logger;
private float LastClickTime = 0;
private int TotalClicks = 0;
private int MissClicks = 0;
// Start is called before the first frame update
void Start()
{
Logger = GameObject.Find("Logger").GetComponent<Logger>();
}
public void ResetClickTime()
{
LastClickTime = 0;
}
// Update is called once per frame
void Update()
{
LastClickTime += Time.deltaTime;
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast(ray, out RaycastHit hit, 100.0f))
{
if(hit.transform != null && hit.transform.GetComponent<ClickableSphere>() != null && hit.transform.GetComponent<ClickableSphere>() == SphereHandler.CurrentActive)
{
TotalClicks++;
float errorRate = (float)MissClicks / (float)TotalClicks;
ClickDataSet dataHit = new ClickDataSet(
LastClickTime,
Camera.main.WorldToScreenPoint(hit.point),
Camera.main.WorldToScreenPoint(hit.transform.position),
errorRate,
SphereHandler.DistancePixel,
SphereHandler.AmplitudePixel);
Logger.AddDataSet(dataHit);
SphereHandler.NextActive();
return;
}
TotalClicks++;
MissClicks++;
var active = SphereHandler.CurrentActive;
if(active != null)
{
float errorRate = (float)MissClicks / (float)TotalClicks;
ClickDataSet dataMiss = new ClickDataSet(
LastClickTime,
Input.mousePosition,
Camera.main.WorldToScreenPoint(active.transform.position),
errorRate,
SphereHandler.DistancePixel,
SphereHandler.AmplitudePixel);
Logger.AddDataSet(dataMiss);
}
}
LastClickTime = 0;
}
}
}
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 6
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: InActiveMaterial
m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
m_ShaderKeywords: _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BumpMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailAlbedoMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailMask:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _DetailNormalMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _EmissionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MetallicGlossMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _OcclusionMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _ParallaxMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- _BumpScale: 1
- _Cutoff: 0.5
- _DetailNormalMapScale: 1
- _DstBlend: 0
- _GlossMapScale: 0
- _Glossiness: 0.5
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _SmoothnessTextureChannel: 1
- _SpecularHighlights: 1
- _SrcBlend: 1
- _UVSec: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 0.25943398, g: 0.5922418, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;
public class Logger : MonoBehaviour
{
List<ClickDataSet> Data = new List<ClickDataSet>();
public string UserID = "TESTSUBJECT";
public bool UsesMouse = true;
// Start is called before the first frame update
void Start()
{
}
public void ToggleUsesMouse()
{
UsesMouse = !UsesMouse;
}
// Update is called once per frame
void Update()
{
}
public void AddDataSet(ClickDataSet NewSet)
{
Data.Add(NewSet);
}
public void SaveData(int difficultyIndex)
{
var fileName = UsesMouse ? $"{UserID}_Mouse_Diff_{difficultyIndex}" : $"{UserID}_Trackpad_Diff_{difficultyIndex}";
var path = Path.Combine(Application.dataPath, $"{fileName}.csv");
if (File.Exists(path))
{
File.Delete(path);
}
StringBuilder builder = new StringBuilder();
builder.AppendLine("User,A,W,ID,MT,EP,TP,Acc,Err");
foreach (var set in Data)
{
var endPoint = $"({set.EndPoint.x}/{set.EndPoint.y})";
var targetPoint = $"({set.TargetPoint.x}/{set.TargetPoint.y})";
builder.AppendLine($"{UserID},{set.Amplitude},{set.Width},{set.DifficultyIndex},{set.MoveTime},{endPoint},{targetPoint},{set.Accuracy},{set.ErrorRate}");
}
File.WriteAllText(path, builder.ToString());
}
public void ClearData()
{
Data.Clear();
}
}
public struct ClickDataSet
{
public readonly float MoveTime;
public readonly Vector2 EndPoint;
public readonly Vector2 TargetPoint;
public readonly float Accuracy;
public readonly float ErrorRate;
public readonly float Width;
public readonly float Amplitude;
public readonly float DifficultyIndex;
public ClickDataSet(float time, Vector2 ep, Vector2 tp, float errorRate, float width, float amplitude)
{
MoveTime = time;
EndPoint = ep;
TargetPoint = tp;
Accuracy = Vector2.Distance(ep, tp);
ErrorRate = errorRate;
Width = width;
Amplitude = amplitude;
DifficultyIndex = Mathf.Log((amplitude / width) + 1.0f, 2.0f);
}
}
\ No newline at end of file
This diff is collapsed.
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &316699081740425071
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 316699081740425074}
- component: {fileID: 316699081740425075}
- component: {fileID: 316699081740425068}
- component: {fileID: 316699081740425069}
- component: {fileID: 316699081740425070}
m_Layer: 0
m_Name: Sphere
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &316699081740425074
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 316699081740425071}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &316699081740425075
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 316699081740425071}
m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &316699081740425068
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 316699081740425071}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 0
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 271fa4994ae3c9449a0fe2e4b99300d2, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!135 &316699081740425069
SphereCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 316699081740425071}
m_Material: {fileID: 0}
m_IsTrigger: 0
m_Enabled: 1
serializedVersion: 2
m_Radius: 0.5
m_Center: {x: 0, y: 0, z: 0}
--- !u!114 &316699081740425070
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 316699081740425071}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 7ee219a5cbf45d141b4bb3b65a03280f, type: 3}
m_Name:
m_EditorClassIdentifier:
InActiveMaterial: {fileID: 2100000, guid: 271fa4994ae3c9449a0fe2e4b99300d2, type: 2}
ActiveMaterial: {fileID: 2100000, guid: e7073d1ffacc53f49975a7465f28d14f, type: 2}
using System;
using System.Collections.Generic;
using UnityEngine;
public class SphereHandler : MonoBehaviour
{
private List<ClickableSphere> Spheres = new List<ClickableSphere>();
public ClickableSphere CurrentActive { get; private set; }
[SerializeField]
private int SphereNumber = 13;
public int DifficultyIndex { get; private set; } = 0;
[SerializeField]
private List<Vector2> DistanceWidths = new List<Vector2>();
public float DistancePixel { get; private set; }
public float AmplitudePixel { get; private set; }
[SerializeField]
private int MaxSphereSwitches = 50;
private int CurrentSphereSwitches = 0;
[SerializeField]
private GameObject SphereTemplate;
public event EventHandler DifficultyFinished;
public event EventHandler TestFinished;
public void SpawnSpheres()
{
var DistWidth = DistanceWidths[DifficultyIndex];
for (var i = 0; i < SphereNumber; i++)
{
var angle = i * Mathf.PI * 2 / SphereNumber;
var pos = new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0) * DistWidth.x;
var sphere = Instantiate(SphereTemplate);
sphere.transform.localScale *= DistWidth.y;
var clickComp = sphere.GetComponent<ClickableSphere>();
Spheres.Add(clickComp);
sphere.transform.position = pos;
sphere.name += i;
}
var firstActive = Spheres[UnityEngine.Random.Range(0, SphereNumber - 1)];
firstActive.SetActive(true);
CurrentActive = firstActive;
CalcPixelDistances();
}
public void NextActive()
{
CurrentSphereSwitches++;
if(CurrentSphereSwitches >= MaxSphereSwitches)
{
DifficultyFinished?.Invoke(this, new EventArgs());
return;
}
var len = Spheres.Count;
var idx = Spheres.IndexOf(CurrentActive);
var newIdx = (idx + Mathf.CeilToInt(len * 0.5f)) % len;
ClickableSphere newActive = Spheres[newIdx];
if (CurrentActive != null)
{
CurrentActive.SetActive(false);
}
CurrentActive = newActive;
CurrentActive.SetActive(true);
}
public void NextDifficulty()
{
DifficultyIndex++;
if (DifficultyIndex >= DistanceWidths.Count)
{
TestFinished?.Invoke(this, new EventArgs());
return;
}
foreach (var sph in Spheres)
{
Destroy(sph.gameObject);
}
Spheres.Clear();
SpawnSpheres();
CurrentSphereSwitches = 0;
}
// Update is called once per frame
void Update()
{
}
private void CalcPixelDistances()
{
var DistWidth = DistanceWidths[DifficultyIndex];
var spherePos = CurrentActive.transform.position;
var spherePosRad = new Vector3(spherePos.x + DistWidth.y, spherePos.y, spherePos.z);
var pixelPosSphere