Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 14 Next capture
2021 2022 2023
2 captures
13 Jun 22 - 14 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
1
Question by justinenayat · Oct 01, 2021 at 12:59 AM · scripting beginnersavesave dataracing gamesave-to-file

Racing Game: Saving and loading replay data?

I've got a racing game and a replay system set up already. I couldn't go the input-based route because the precise mouse movements in the game are too crucial.


I have a bool that checks if it should be recording or playing. When '`isRecord`', the script records 3 lists [Time (float), Position (vector3), Rotation (quaternion)] to a scriptable object. When '`isReplay`', it gets 2 indexes and lerps/slerps between them.


I'd like to record it to a file (because next I want to figure out how to make the replay data retrievable from the cloud), but the binary formatter doesn't seem to like the data. I'm not sure if it's because of the list, vector3, or quaternions, but it just doesn't work. This is probably where I need the most help. Then I'd like it to be able to load a replay file made this way to the scriptable object so the run can be replayed.


Any input is extremely appreciated. Thanks!

Comment
Add comment · Show 4
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Eno-Khaon · Oct 01, 2021 at 09:54 AM 0
Share

Are you just trying to use complex structs (i.e. Vector3 and Quaternion) with a BinaryFormatter as-is? From what I've seen, it's not quite that simple and requires organizing the data for both reading and writing.

avatar image justinenayat Eno-Khaon · Oct 01, 2021 at 04:42 PM 0
Share

Yeah I read a bit about that. Do you know if there are alternative methods of saving and loading stuff? For example it would be great to be able to export the state of the scriptable object to be loaded back later. Not sure if that's possible

avatar image Eno-Khaon justinenayat · Oct 01, 2021 at 08:55 PM 1
Share

I can't say that I'm already familiar with any approach to this that's just available as-is. Overall, it should be straightforward enough to compile the data down as-is. Basically, the way you want to approach it will dictate the way you can utilize it.

For example, let's say that instead of basing a replay on inputs and current states, you based it exclusively on position and rotation on every physics update (50-per-second by default). No compression, just raw, linear data with no gaps between anything.

If you took the position (Vector3, 12 bytes) and rotation (Quaternion, 16 bytes), 50 times per second (physics default rate), converted those into bytes and placed them end-to-end:

 28 bytes * 50-per-second = 1400
 1400 bytes * 60 seconds = 84000 bytes (~84kb)
 84000 * 5 $$anonymous$$utes (long race by Mario Kart standards) = 420000 (~420kb)


Basically, in the worst-case scenario of utilizing no velocities or input states and with nothing really planned out for a self-verifying replay, you'd still wind up with a few hundred kilobytes of data in a maximally-inefficient file: Small by today's standards of file size, but still technically huge for the simple data it represents.

Really, what it all comes down to in the end is figuring out what data you want to store and how you want it organized. There's not really any "standardized" way of doing this sort of thing, since the types and number of Component scripts would always vary on a game-by-game basis.

Essentially, something like: Are there powerups? Should they be accurately portrayed during a replay? That would require at least a starting time/state for when it's activated, and/or a separate time/state for when it's acquired if it doesn't automatically activate.

That alone would be a rather confounding factor in deter$$anonymous$$ing what should go into replay data, so there's not really any reasonable way to genericize a state-saving system to support it without it being bloated with a huge amount of data that's irrelevant a majority of the time.

Show more comments

1 Reply

· Add your reply
  • Sort: 
avatar image
0
Best Answer

Answer by justinenayat · Oct 02, 2021 at 12:30 PM

I figured it out! For the sake of anyone with a similar problem I'm going to list out my codes:

Here's how I call the save/load methods

 public Ghost ghost;
 
 void YourFunction()
 {
 //To Load
 ghost.LoadNow(ghost);
 //To Save
 ghost.SaveNow(ghost);
 }

GhostRecord.cs

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 using System.IO;
 
 public class GhostRecord : MonoBehaviour
 {
     public Ghost ghost;
     private float timer;
     private float timeValue;
 
     private void Awake()
     {
         if (ghost.isRecord)
         {
             ghost.ResetData();
             timeValue = 0;
             timer = 0;
         }
     }
 
     void Update()
     {
         if (GameControl.runStarted == true)
         {
             timer += Time.unscaledDeltaTime;
             timeValue += Time.unscaledDeltaTime;
 
             if (ghost.isRecord & timer >= 1 / ghost.recordFrequency)
             {
                 ghost.timeStamp.Add(timeValue);
                 ghost.position.Add(this.transform.position);
                 ghost.rotation.Add(this.transform.rotation);
 
                 timer = 0;
             }
         }
     }
 }

Ghost.cs

 using System.Collections;
 using System.Collections.Generic;
 using System;
 using UnityEngine;
 using System.IO;
 
 [CreateAssetMenu]
 public class Ghost : ScriptableObject
 {
     public bool isRecord;
     public bool isReplay;
     public float recordFrequency;
 
     public List<float> timeStamp;
     public List<Vector3> position;
     public List<Quaternion> rotation;
 
     public void ResetData()
     {
         timeStamp.Clear();
         position.Clear();
         rotation.Clear();
     }
 
     public void SaveNow(Ghost a_Ghost)
     {
         SaveJsonData(a_Ghost);
     }
 
     private static void SaveJsonData(Ghost a_Ghost)
     {
         ReplayData rd = new ReplayData();
         a_Ghost.PopulateReplayData(rd);
 
         if (FileManager.WriteToFile("replaydata.replay", rd.ToJson()))
         {
             Debug.Log("Save Complete");
         }
     }
 
     public void PopulateReplayData(ReplayData a_ReplayData)
     {
         a_ReplayData.d_timeStamp = timeStamp;
         a_ReplayData.d_position = position;
         a_ReplayData.d_rotation = rotation;
     }
 
     public void LoadNow(Ghost a_Ghost)
     {
         LoadJsonData(a_Ghost);
     }
 
     private static void LoadJsonData(Ghost a_Ghost)
     {
         if (FileManager.LoadFromFile("replaydata.replay", out var json))
         {
             ReplayData rd = new ReplayData();
             rd.LoadFromJson(json);
 
             a_Ghost.LoadFromReplayData(rd);
             Debug.Log("Load Complete");
         }
     }
 
     public void LoadFromReplayData(ReplayData a_ReplayData)
     {
         timeStamp = a_ReplayData.d_timeStamp;
         position = a_ReplayData.d_position;
         rotation = a_ReplayData.d_rotation;
     }
 }

ReplayData.cs

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 
 [System.Serializable]
 public class ReplayData
 {
     public List<float> d_timeStamp;
     public List<Vector3> d_position;
     public List<Quaternion> d_rotation;
 
     public string ToJson()
     {
         return JsonUtility.ToJson(this);
     }
 
     public void LoadFromJson(string a_Json)
     {
         JsonUtility.FromJsonOverwrite(a_Json, this);
     }
 }
 
 public interface ISaveable
 {
     void PopulateReplayData(ReplayData a_ReplayData);
     void LoadFromReplayData(ReplayData a_ReplayData);
 }

GhostPlayer.cs

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 
 public class GhostPlayer : MonoBehaviour
 {
     public Ghost ghost;
     private float timeValue;
     private int index1;
     private int index2;
 
     private void Awake()
     {
         timeValue = 0;
         ghost.LoadNow(ghost);
     }
 
     void Update()
     {
         if (GameControl.runStarted == true)
         {
             timeValue += Time.unscaledDeltaTime;
 
             if (ghost.isReplay)
             {
                 GetIndex();
                 SetTransform();
             }
         }
     }
 
     private void GetIndex()
     {
         for (int i = 0; i < ghost.timeStamp.Count - 2; i++)
         {
             if (ghost.timeStamp[i] == timeValue)
             {
                 index1 = i;
                 index2 = i;
                 return;
             }
             else if (ghost.timeStamp[i] < timeValue & timeValue < ghost.timeStamp[i + 1])
             {
                 index1 = i;
                 index2 = i + 1;
                 return;
             }
         }
         index1 = ghost.timeStamp.Count - 1;
         index2 = ghost.timeStamp.Count - 1;
     }
 
     private void SetTransform()
     {
         if (index1 == index2)
         {
             this.transform.position = ghost.position[index1];
             this.transform.rotation = ghost.rotation[index1];
         }
         else
         {
             float interpolationFactor = (timeValue - ghost.timeStamp[index1]) / (ghost.timeStamp[index2] - ghost.timeStamp[index1]);
 
             this.transform.position = Vector3.Lerp(ghost.position[index1], ghost.position[index2], interpolationFactor);
             this.transform.rotation = Quaternion.Slerp(ghost.rotation[index1], ghost.rotation[index2], interpolationFactor);
         }
     }
 }


Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

142 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Making a less complicated save system. 2 Answers

How To Make Settings File Over XML? 1 Answer

PlayerPrefs not saving in build 1 Answer

Future compatible saveClass? 1 Answer

Chest contents persistence between scenes 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges