How to use the Timestamp data from JSON file in Unity - json

I'm currently working with a folder of JSON files which are collected through a tracking experiment with a drone. The data contains position, rotation and timestamp of the drone while it's moving and levitating inside the tracking system.
What I'm currently doing is trying to simulate the movement of the drone inside Unity using those data. So far, I've managed to parse the position and rotation from the data to an object inside Unity and extract the timestamp to System.DateTime in Unity.
However, I don't how to work with the timestamp. I want to use the timestamp to match the position and rotation of the object (i.e: at this timestamp, the drone should be at this position(x,y,z) and has the rotation(x,y,z,w)). Can someone help me with this problem, really appreciate your help :D Here is my current code:
void Update()
{
if (loaded)
{
for(int i = 0; i <= pos_data.Count; i+= 10)
{
Cube.transform.position = pos_data[i];
Cube.transform.rotation = rot_data[i];
}
}
else
{
LoadJson();
//startTime = datetime[0];
loaded = true;
}
}
public void LoadJson()
{
string HeadPath = #Application.dataPath + "/Data/" + "drone_data_1.json";
string HeadJsonhold = File.ReadAllText(HeadPath);
var data_ = JSON.Parse(HeadJsonhold);
for (int rows = 0; rows <= data_.Count; rows += 10)
{
pos_data.Add(new Vector3(data_[rows]["location"]["x"].AsFloat, data_[rows]["location"]["y"].AsFloat, data_[rows]["location"]["z"].AsFloat));
rot_data.Add(new Quaternion(data_[rows]["rotation"]["x"].AsFloat, data_[rows]["rotation"]["y"].AsFloat, data_[rows]["rotation"]["z"].AsFloat, data_[rows]["rotation"]["w"].AsFloat));
Time = System.DateTime.ParseExact(data_[rows]["Timestamp"], "yyyyMMddHHmmss",null);
//Debug.Log(Time);
}
}

If I understand you correctly what you are getting are samples of a real-world drone that at some rate stores keyframes of its movement.
Now you already successfully load that json data but wonder how to animate the Unity object accordingly.
The timestamp itself you can't use at all! ^^
It most probably lies somewhere in the past ;) And you can't just assign something to Time.
What you can do, however, is take the timestamp of the first sample (I will just assume that your samples are all already ordered by the time) and calculate the difference to the next sample and so on.
Then you can use that difference in order to always interpolate between the current and next sample transforms using the given time delta.
Currently you are just doing all samples in one single frame so there won't be any animation at all.
Also just as sidenote:
for(int i = 0; i <= pos_data.Count; i+= 10)
is wrong twice:
a) you already skipped 10 samples when loading the data -> are you sure you now want to again skip 10 of these => In total every time skipping 100 samples?
b) since indices are 0 based the last accessible index would be pos_data.Count - 1 so in general when iterating Lists/arrays it should be i < pos_data.Count ;)
First of all I would suggest you use a better data structure and use one single list holding the information that belongs together instead of multiple parallel lists and rather load your json like e.g.
[Serializable]
public class Sample
{
public readonly Vector3 Position;
public readonly Quaternion Rotation;
public readonly float TimeDelta;
public Sample(Vector3 position, Quaternion rotation, float timeDelta)
{
Position = position;
Rotation = rotation;
TimeDelta = timeDelta;
}
}
And then
// Just making this serialized so you can immediately see in the Inspector
// if your data loaded correctly
[SerializeField] private readonly List<Sample> _samples = new List<Sample>();
public void LoadJson()
{
// start fresh
_samples.Clear();
// See https://learn.microsoft.com/dotnet/api/system.io.path.combine
var path = Path.Combine(Application.dataPath, "Data", "drone_data_1.json");
var json = File.ReadAllText(path);
var data = JSON.Parse(json);
DateTime lastTime = default;
for (var i = 0; i <= data.Count; i += 10)
{
// First I would pre-cache these values
var sample = data[i];
var sampleLocation = sample["location"];
var sampleRotation = sample["rotation"];
var sampleTime = sample["Timestamp"];
// Get your values as you did already
var position = new Vector3(sampleLocation["x"].AsFloat, sampleLocation["y"].AsFloat, sampleLocation["z"].AsFloat));
var rotation = new Quaternion(sampleRotation["x"].AsFloat, sampleRotation["y"].AsFloat, sampleRotation["z"].AsFloat, sampleRotation["w"].AsFloat));
var time = System.DateTime.ParseExact(sampleTime, "yyyyMMddHHmmss", null);
// Now for the first sample there is no deltaTime
// for all others calculate the difference in seconds between the
// last and current sample
// See https://learn.microsoft.com/dotnet/csharp/language-reference/operators/conditional-operator
var deltaTime = i == 0 ? 0f : GetDeltaSeconds(lastTime, time);
// and of course store it for the next iteration
lastTime = time;
// Now you can finally add the sample to the list of samples
// instead of having multiple parallel lists
_samples.Add(new Sample(position, rotation, deltaTime));
}
}
private float GetDeltaSeconds(DateTime first, DateTime second)
{
// See https://learn.microsoft.com/dotnet/api/system.datetime.op_subtraction#System_DateTime_op_Subtraction_System_DateTime_System_DateTime_
var deltaSpan = second - first;
// See https://learn.microsoft.com/dotnet/api/system.timespan.totalseconds#System_TimeSpan_TotalSeconds
return (float)deltaSpan.TotalSeconds;
}
So now what to do with this information?
You now have samples (still assuming ordered by time) holding all required information to be able to interpolate between them.
I would use Coroutines instead of Update, in my eyes they are easier to understand and maintain
// Do your loading **once** in Start
private void Start()
{
LoadJson();
// Then start the animation routine
// I just make it a method so you could also start it later e.g. via button etc
StartAnimation();
}
// A flag just in case to avoid concurrent animations
private bool alreadyAnimating;
// As said just making this a method so you could also remove it from Start
// and call it in any other moment you like
public void StartAnimation()
{
// Only start an animation if there isn't already one running
// See https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html
if(!alreadyAnimating) StartCoroutine(AnimationRoutine());
}
private IEnumerator AnimationRoutine()
{
// Just in case abort if there is already another animation running
if(alreadyAnimating) yield break;
// Block concurrent routine
alreadyAnimating = true;
// Initially set your object to the first sample
var lastSample = _samples[0];
Cube.transform.position = lastSample.Position;
Cube.transform.rotation = lastSample.Rotation;
// This tells Unity to "pause" the routine here, render this frame
// and continue from here in the next frame
yield return null;
// then iterate through the rest of samples
for(var i = 1; i < _samples.Count; i++)
{
var lastPosition = lastSample.Position;
var lastRottaion = lastSample.Rottaion;
var currentSample = _samples[i];
var targetPosition = sample.Position;
var targetRotation = sample.Rotation;
// How long this interpolation/animation will take
var duration = currentSample.TimeDelta;
// You never know ;)
// See https://docs.unity3d.com/ScriptReference/Mathf.Approximately.html
if(Mathf.Approximately(duration, 0f))
{
Cube.transform.position = targetPosition;
Cube.transform.rotation = targetRotation;
lastSample = currentSample;
continue;
}
// And this is where the animation magic happens
var timePassed = 0f;
while(timePassed < duration)
{
// this factor will be growing linear between 0 and 1
var factor = timePassed / duration;
// Interpolate between the "current" transforms (the ones it had at beginning of this iteration)
// towards the next sample target transforms using the factor between 0 and 1
// See https://docs.unity3d.com/ScriptReference/Vector3.Lerp.html
Cube.transform.position = Vector3.Lerp(lastPosition, targetPosition, factor);
// See https://docs.unity3d.com/ScriptReference/Quaternion.Slerp.html
Cube.transform.rotation = Quaternion.Slerp(lastRotation, targetRotation, factor);
// This tells Unity to "pause" the routine here, render this frame
// and continue from here in the next frame
yield return null;
// increase by the time passed since the last frame was rendered
timePassed += Time.deltaTime;
}
// just to be sure to end with clean values
Cube.transform.position = targetPosition;
Cube.transform.rotation = targetRotation;
lastSample = currentSample;
}
// Allow the next animation to start (or restart this one)
alreadyAnimating = false;
// Additional stuff to do once the animation is done
}

Related

Flash drop and catch game

I am doing a flash mini game that a character need to catch item drop down from top.
The problems i am facing are using timer for those 6 items in an array randomly chose and drop down with random x-axis.
var PointFood:Array = [Melon1,Honey1,Worm1,Clock1,Poison1,Sling1];
//this is the array of items to drop
time = new Timer(60);
//No idea the timer is with frame or second
time.addEventListener(TimerEvent.TIMER,GenerateItem);
time.start();
function GenerateItem(event:TimerEvent):void
{
axis = Math.round(Math.random()*709.7)+44.45;
//this is the random x axis with my scene width
PointFood[i].x = axis;
PointFood[i].y = 0;
if(PointFood[i].y < 600.95)
{
PointFood[i].y += 10;
}
}
//here assign the x and y axis
function ItemType(e:Event):Number
{
i = Math.round(Math.random()*5);
return i;
}
//this is the random for item in array
However the x-axis will be keep changing once the calculation done. Even the items that already exist on screen, their x-axis will also keep changing with the calculation.
Any Solution for this?
There are different ways of dealing with this.
I assume Melon1, Honey1, ... are instances of your own classes.
Add a public property positioned to each of them, like:
public var positioned:Boolean=false;
At the moment your ItemType function just returns a random integer based on the number of objects inside the PointFood array.
Let's integrate a check if the random object is already positioned using it's freshly added positioned property:
function ItemType(e:Event):Number
{
var found:Boolean;
do
{
found = true;
i = Math.round(Math.random() * 5);
if (PointFood[i].positioned)
{
found = false;
}
} while (!found);
return i;
}
Finally modify the GenerateItem function to take the positioned property into account - thus just randomize the position if it's false:
function GenerateItem(event:TimerEvent):void
{
if (PointFood[i].positioned == false)
{
axis = Math.round(Math.random() * 709.7) + 44.45;
//this is the random x axis with my scene width
PointFood[i].positioned = true;
PointFood[i].x = axis;
PointFood[i].y = 0;
}
if (PointFood[i].y < 600.95)
{
PointFood[i].y += 10;
}
}
As a side note:
time = new Timer(60);
means your timer fires every 60 milliseconds - is that the expected behaviour?
Also there might be a little logic problem with your code. As the name GenerateItem implies, I guess this function should just spawn a new object and initialize it's position. Unfortunately it looks like you're abusing this function to do your main game loop as well. I'd recommend splitting this up into two separate functions.

Implicit Coercion of a value of a type of text.flash:TextField to an unrelated Int

Okay, so I'm trying to create a mobile flash game (I'm mostly an animator, a storyteller) and I'm having trouble with the input text for the player name. I've often read helpful tips on this site so I signed up to get some help. The code I use to load and save data is saveDataObject, But as far as I know input text has to be used with package code. I tried to convert it to function var, but then these errors occur. I am unsure how to use the Package class code, and everything I've read on it has been confusing. I am pretty much self taught for everything I know about code though tutorials and forums, so if it isn't explained in a way I can understand I wont be able to do it...
Here's the section of code if I wasn't clear(error lines separate):
var playerName:int;
init(); // this line goes directly beneath the variables
function f_1(init):void
{ // call once to set everything up
saveDataObject = SharedObject.getLocal("DataBattle/character/name"); // give the save data a location
-
playerName = txtPlayer;
-
function addName(e:TouchEvent):void
{
var myTextBox1:TextField = new TextField();
var txtPlayer:TextField = new TextField();
var myText:String = "Cyan";
function CaptureUserInput()
{
captureText();
}
function captureText():void
{
myTextBox1.type = TextFieldType.INPUT;
myTextBox1.background = true;
addChild(myTextBox1);
myTextBox1.text = myText;
myTextBox1.addEventListener(TextEvent.TEXT_INPUT, textInputCapture);
}
function textInputCapture(event:TextEvent):void
{
var str:String = myTextBox1.text;
createOutputBox(str);
}
function createOutputBox(str:String):void
{
txtPlayer.background = false;
txtPlayer.x = 200;
addChild(txtPlayer);
txtPlayer.text = str;
}
-
if(saveDataObject.data.characterName = playerName == null)
-
{ // checks if there is save data
trace("No Player data yet."); // if there isn't any data on the computer...
saveDataObject.data.characterName = playerName; // ...set the savedScore to 0
}
else
{
trace("Player data found."); // if we did find data...
loadData1(); // ...load the data
}
function loadData1():void
{
playerName = saveDataObject.data.characterName; // set the current score to the saved score
trace("Data Loaded!");
}
}
}
function saveData(e:TouchEvent):void
{
saveDataObject.data.characterName = playerName; // set the saved score to the current score
trace("Data Saved!");
saveDataObject.flush(); // immediately save to the local drive
trace(saveDataObject.size); // this will show the size of the save file, in bytes
}
Would be nice to have some more details about this error. Like which line is actually throwing this error.
However even without it there are few things that looks weird right away.
var playerName:int;
// And few lines below
playerName = txtPlayer;
// Few more lines below
var txtPlayer:TextField = new TextField();
Looks like you're declaring playerName as int so you want to store numeric values, but then txtPlayer is a TextField. So by playerName = txtPlayer; you're trying to store TextField as Int value, which is not gonna work.
I can also see
if(saveDataObject.data.characterName = playerName == null)
and it's like :O
I'm not even sure how this part is being compiled. You want to do playerName == null check (which would return true/false) and then assign it to saveDataObject.data.characterName ?

AS3 - Using a For Loop to Update Multiple Points and Their Values in an Array

I'm a bit new with AS3 (but not really with coding) so please forgive my ignorance. I'm creating a small function that will be called by a Main Function to update the position of 52 Pointers that have the x and y position of multiple point objects (empty movie clips). It will also then update two global arrays with those values (one array for the x and one for the y).
The problem is, as there are 52 of them, and they will probably grow in quantity, I'd like to be able to use a FOR function to do it, but I can't seem to be able to figure it out.
I get this error: Access of undefined property _point.
Here is a piece of the code that dream about:
function happyFunc():void
{
var avpointers:int = 52;
var vpointx:Array = new Array();
var vpointy:Array = new Array();
for (aa=0; aa<vpointers; aa++)
{
vpointx[aa] = _point[aa].x;
vpointy[aa] = _point[aa].y;
}
}
And this is the code that I'm stuck with...
function reallySadFunc():void
{
_point1 = localToGlobal(new Point(point1.x,point1.y));
//...
_point52 = localToGlobal(new Point(point52.x,point1.y));
vpointx[0] = _point1.x;
vpointx[1] = _point2.x;
//...
//oh god there are 104 lines of this why do I have to suffer
}
Thank you!
If I read your question correctly, I think this is what you need:
public static const CLIP_COUNT:int = 52;
// ...
private function happyFunc(parentClip:DisplayObjectContainer):void
{
var vpointx:Vector.<Number> = new Vector.<Number>(CLIP_COUNT, true);
var vpointy:Vector.<Number> = new Vector.<Number>(CLIP_COUNT, true);
var clipPoint:Point = new Point ();
var globalPoint:Point;
for (var i:int = 0; i < CLIP_COUNT; i++)
{
var childClip:DisplayObject = parentClip.getChildByName("point" +
(i + 1).toString());
clipPoint.x = childClip.x;
clipPoint.y = childClip.y;
globalPoint = parentClip.localToGlobal(clipPoint);
vpointx[i] = globalPoint.x;
vpointy[i] = globalPoint.y;
}
// do something with vpointx and vpointy - they're local variables
// and will go out of scope unless you declare them as class members
// or somehow return them from this function.
}
This function works by taking the parent display object (the one that contains the 52 movie clips - this could be the Stage) and iterates through them by getting each movie clip by name. The code assumes that your movie clips are called point1, point2, ..., point52.
To speed up the local-to-global coordinate conversion, two Point objects are created and then reused during the for loop to avoid creating many temporary Point instances.
Instead of using Array, use Vector.<Number> - this class has better performance than Array does.

AS3 Error #1009: Cannot access a property or method of a null object reference

Basically, I'm trying to make a randomly generated character follow a series of waypoints to get to where he needs to go without walking into walls etc on stage.
I'm doing this by passing an Array of Points from the Engine to the Character's followPath function (this will be on a loop, but I haven't gotten to that stage yet).
A part of this followPath function is to detect when the character is close enough to the waypoint and then move on to the next one. To do this, I'm trying to use Point.distance(p1,p2) to calculate the distance between the currently selected waypoint, and a point that represents the Character's current position.
This is where I'm running into this problem. Trying to update the current (x,y) point position of the Character is proving difficult. For some reason, the Point.setTo function does not seem to exist, despite it being in documentation. As a result, I'm using
currentPos.x = x;
currentPos.y = y;
//update current position point x and y
to try and do this, which is where my 1009 error is coming from.
Here is my full Character class so far:
package {
import flash.display.MovieClip;
import flash.geom.Point;
public class Character extends MovieClip {
public var charType:String;
private var charList:Array = ["oldguy","cloudhead","tvhead","fatguy","speakerhead"];
private var numChars:int = charList.length;
private var wpIndex:int = 0;
private var waypoint:Point;
private var currentPos:Point;
private var wpDist:Number;
private var moveSpeed:Number = 5;
//frame labels we will need: charType+["_walkingfront", "_walkingside", "_walkingback", "_touchon", "_touchonreaction", "_sitting"/"_idle", "_reaction1", "_reaction2", "_reaction3", "_reaction4"]
public function Character() {
trace("new character:");
charType = charList[Math.floor(Math.random()*(numChars))];
//chooses a random character type based on a random number from 0-'numchars'
trace("char type: " + charType);
gotoAndStop(charType+"_walkingfront");
x = 600;
y = 240;
}
public function followPath(path:Array):void {
if(wpIndex > path.length){ //if the path has been finished
gotoAndStop(charType+"_sitting"); //sit down
return;//quit
}
waypoint = path[wpIndex];
//choose the selected waypoint
currentPos.x = x;
currentPos.y = y;
//update current position point x and y
wpDist = Point.distance(currentPos,waypoint);
//calculate distance
if(wpDist < 3){ //if the character is close enough to the waypoint
wpIndex++; //go to the next waypoint
return; //stop for now
}
moveTo(waypoint);
}
public function moveTo(wp:Point):void {
if(wp.x > currentPos.x){
currentPos.x += moveSpeed;
} else if(wp.x < currentPos.x){
currentPos.x -= moveSpeed;
}
if(wp.y > currentPos.y){
currentPos.y += moveSpeed;
} else if(wp.y < currentPos.y){
currentPos.y -= moveSpeed;
}
}
}
}
Can anybody explain to me why this is happening? It's a roadblock that I haven't been able to overcome at this stage.
I'm also curious if anybody can provide information as to why I can't use the phantom Point.setTo method.
You're trying to assign x and y properties of a Point object that doesn't exist.
You have to create your Point:
currentPos = new Point ();
and then assign x and y
The problem is that your are not using the Point constructor first.
When you create a variable that is not a simple data type (Int, Number, String ...) you must call the constructor first and assign properties to the fields of the object only afterwards.
This is because you must initialize an instance of the class Point before accessing it's properties.
The same will be true with any other class.
http://en.wikipedia.org/wiki/Constructor_%28object-oriented_programming%29
"In object-oriented programming, a constructor (sometimes shortened to ctor) in a class is a special type of subroutine called at the creation of an object. It prepares the new object for use.."
Basically, you did not prepare a new Point object.
In this example, during the constructor (public function Character)
public function Character() {
//add these lines (you can omit the zeroes as the default value is zero)
//I added the zeroes to show that the constructor can set the initial values.
wayPoint = new Point(0, 0);
currentPos = new Point(0, 0);
trace("new character:");
charType = charList[Math.floor(Math.random()*(numChars))];
//chooses a random character type based on a random number from 0-'numchars'
trace("char type: " + charType);
gotoAndStop(charType+"_walkingfront");
x = 600;
y = 240;
}
remember every new object identifer references NULL (nothing) until you construct an object or do something like this
var pointA = pointB;
//where pointB is already not null
//You can also check this
if(currentPos != null)
{
currentPos.x = X;
currentPos.y = Y;
}
currentPos will not be null if you use a constructor first.
Good luck.

Find top 3 closest targets in Actionscript 3

I have an array of characters that are Points and I want to take any character and be able to loop through that array and find the top 3 closest (using Point.distance) neighbors. Could anyone give me an idea of how to do this?
This is a new and improved version of the code I posted last night. It's composed of two classes, the PointTester and the TestCase. This time around I was able to test it too!
We start with the TestCase.as
package {
import flash.geom.Point;
import flash.display.Sprite;
public class TestCase extends Sprite {
public function TestCase() {
// some data to test with
var pointList:Array = new Array();
pointList.push(new Point(0, 0));
pointList.push(new Point(0, 0));
pointList.push(new Point(0, 0));
pointList.push(new Point(1, 2));
pointList.push(new Point(9, 9));
// the point we want to test against
var referencePoint:Point = new Point(10, 10);
var resultPoints:Array = PointTester.findClosest(referencePoint, pointList, 3);
trace("referencePoint is at", referencePoint.x, referencePoint.y);
for each(var result:Object in resultPoints) {
trace("Point is at:", result.point.x, ", ", result.point.y, " that's ", result.distance, " units away");
}
}
}
}
And this would be PointTester.as
package {
import flash.geom.Point;
public class PointTester {
public static function findClosest(referencePoint:Point, pointList:Array, maxCount:uint = 3):Array{
// this array will hold the results
var resultList:Array = new Array();
// loop over each point in the test data
for each (var testPoint:Point in pointList) {
// we store the distance between the two in a temporary variable
var tempDistance:Number = getDistance(testPoint, referencePoint);
// if the list is shorter than the maximum length we don't need to do any distance checking
// if it's longer we compare the distance to the last point in the list, if it's closer we add it
if (resultList.length <= maxCount || tempDistance < resultList[resultList.length - 1].distance) {
// we store the testing point and it's distance to the reference point in an object
var tmpObject:Object = { distance : tempDistance, point : testPoint };
// and push that onto the array
resultList.push(tmpObject);
// then we sort the array, this way we don't need to compare the distance to any other point than
// the last one in the list
resultList.sortOn("distance", Array.NUMERIC );
// and we make sure the list is kept at at the proper number of entries
while (resultList.length > maxCount) resultList.pop();
}
}
return resultList;
}
public static function getDistance(point1:Point, point2:Point):Number {
var x:Number = point1.x - point2.x;
var y:Number = point1.y - point2.y;
return Math.sqrt(x * x + y * y);
}
}
}
It might be worth mentioning that, if the number of points is large enough for performance to be important, then the goal could be achieved more quickly by keeping two lists of points, one sorted by X and the other by Y. One could then find the closest 3 points in O(logn) time rather than O(n) time by looping through every point.
If you use grapefrukt's solution you can change the getDistance method to return x*x + y*y; instead of return Math.sqrt( x * x + y * y );