gewoon_kiefer
gewoon_kiefer2w ago

how to sync scriptable objects

i have a scriptable object where i put stuff in in a random order (for a puzzle) and whenever a person joins the room they create a different order so i need to have a scriptable object sync or to make the script that puts the info in the scriptable object a "server" script instead of "client" script if it helps the code that i use
c#
public class SimonColorOrderSO : ScriptableObject
{
public List<SimonColorSo> simonOrder;
}
c#
public class SimonColorOrderSO : ScriptableObject
{
public List<SimonColorSo> simonOrder;
}
c#
public class SimonColorSo : ScriptableObject
{
public EColorName ColorName;
public Material color;
}

public enum EColorName
{
Red,
Yellow,
Green,
Blue
}
c#
public class SimonColorSo : ScriptableObject
{
public EColorName ColorName;
public Material color;
}

public enum EColorName
{
Red,
Yellow,
Green,
Blue
}
13 Replies
kapa.ai
kapa.ai2w ago
Hi @gewoon_kiefer. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- To synchronize your scriptable object data across clients in NormalVR, you'll need to use a different approach. Scriptable Objects are not directly supported for synchronization in Normcore. Instead, you should use a RealtimeComponent and RealtimeModel to synchronize the data. Here's how you can approach this: 1. Create a RealtimeModel to represent your data:
using Normal.Realtime;
using UnityEngine;
using System.Collections.Generic;

[RealtimeModel]
public partial class SimonColorOrderModel
{
[RealtimeProperty(1, true)]
private RealtimeList<int> _colorOrder = new RealtimeList<int>();
}
using Normal.Realtime;
using UnityEngine;
using System.Collections.Generic;

[RealtimeModel]
public partial class SimonColorOrderModel
{
[RealtimeProperty(1, true)]
private RealtimeList<int> _colorOrder = new RealtimeList<int>();
}
2. Create a RealtimeComponent to manage the synchronization:
using Normal.Realtime;
using UnityEngine;
using System.Collections.Generic;

public class SimonColorOrderSync : RealtimeComponent<SimonColorOrderModel>
{
public SimonColorOrderSO localColorOrder;
public List<SimonColorSo> availableColors;

protected override void OnRealtimeModelReplaced(SimonColorOrderModel previousModel, SimonColorOrderModel currentModel)
{
if (currentModel != null)
{
if (currentModel.isFreshModel)
{
// If this is a new model, generate the order and set it
GenerateColorOrder();
}
else
{
// If the model already exists, update the local scriptable object
UpdateLocalColorOrder();
}

// Subscribe to changes
currentModel.colorOrder.didInsertElement += ColorOrderDidChange;
currentModel.colorOrder.didRemoveElement += ColorOrderDidChange;
}
}

private void GenerateColorOrder()
{
// Clear existing order
model.colorOrder.Clear();
// Generate new random order
List<int> newOrder = new List<int>(availableColors.Count);
for (int i = 0; i < availableColors.Count; i++)
{
newOrder.Add(i);
}
for (int i = 0; i < newOrder.Count; i++)
{
int temp = newOrder[i];
int randomIndex = Random.Range(i, newOrder.Count);
newOrder[i] = newOrder[randomIndex];
newOrder[randomIndex] = temp;
}

// Set the new order in the model
foreach (int index in newOrder)
{
model.colorOrder.Add(index);
}
}

private void UpdateLocalColorOrder()
{
localColorOrder.simonOrder.Clear();
foreach (int index in model.colorOrder)
{
if (index >= 0 && index < availableColors.Count)
{
localColorOrder.simonOrder.Add(availableColors[index]);
}
}
}

private void ColorOrderDidChange(RealtimeList<int> list, int index, int value)
{
UpdateLocalColorOrder();
}
}
using Normal.Realtime;
using UnityEngine;
using System.Collections.Generic;

public class SimonColorOrderSync : RealtimeComponent<SimonColorOrderModel>
{
public SimonColorOrderSO localColorOrder;
public List<SimonColorSo> availableColors;

protected override void OnRealtimeModelReplaced(SimonColorOrderModel previousModel, SimonColorOrderModel currentModel)
{
if (currentModel != null)
{
if (currentModel.isFreshModel)
{
// If this is a new model, generate the order and set it
GenerateColorOrder();
}
else
{
// If the model already exists, update the local scriptable object
UpdateLocalColorOrder();
}

// Subscribe to changes
currentModel.colorOrder.didInsertElement += ColorOrderDidChange;
currentModel.colorOrder.didRemoveElement += ColorOrderDidChange;
}
}

private void GenerateColorOrder()
{
// Clear existing order
model.colorOrder.Clear();
// Generate new random order
List<int> newOrder = new List<int>(availableColors.Count);
for (int i = 0; i < availableColors.Count; i++)
{
newOrder.Add(i);
}
for (int i = 0; i < newOrder.Count; i++)
{
int temp = newOrder[i];
int randomIndex = Random.Range(i, newOrder.Count);
newOrder[i] = newOrder[randomIndex];
newOrder[randomIndex] = temp;
}

// Set the new order in the model
foreach (int index in newOrder)
{
model.colorOrder.Add(index);
}
}

private void UpdateLocalColorOrder()
{
localColorOrder.simonOrder.Clear();
foreach (int index in model.colorOrder)
{
if (index >= 0 && index < availableColors.Count)
{
localColorOrder.simonOrder.Add(availableColors[index]);
}
}
}

private void ColorOrderDidChange(RealtimeList<int> list, int index, int value)
{
UpdateLocalColorOrder();
}
}
3. Use this component in your scene: - Create an empty GameObject in your scene. - Add the SimonColorOrderSync component to it. - Assign your SimonColorOrderSO to the localColorOrder field. - Create a list of SimonColorSo objects and assign them to the availableColors field. This approach ensures that: 1. The color order is generated once when the first player joins the room. 2. Subsequent players receive the same order when they join. 3. Any changes to the order are synchronized across all clients. Remember to add a RealtimeView component to the GameObject that has the SimonColorOrderSync component, so Normcore can manage its lifecycle. This solution uses Normcore's RealtimeComponent and RealtimeModel system to synchronize the data, which is the recommended approach for custom data synchronization in NormalVR. Synchronizing Custom Data provides more details on creating custom RealtimeComponents and RealtimeModels in Normcore. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2w ago
<@899009432518803496> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Mechabit
Mechabit2w ago
how are you randomising the order? you can just sync the random seed and it will generate the same puzzle
gewoon_kiefer
gewoon_kieferOP2w ago
this is the script i use to randomize the order:
C#
public class SimonController : MonoBehaviour
{
#region Variables

[SerializeField] private List<SimonColorSo> simonColors = new List<SimonColorSo>();

[SerializeField] private GameObject displayLocation;
//[SerializeField] private GameObject displayPrefab;
public string displayName;
[SerializeField] private SimonColorOrderSO simonColorOrder;

[Range(4, 98)][SerializeField] private int ColorAmount;
[SerializeField] private int amountChancePerColor = 100;

private List<GameObject> visualColors = new List<GameObject>();
[SerializeField] private bool everyColorOnce = false;

bool spawnOnce = true;
#endregion

private void Update()
{
if (spawnOnce)
{
NewColorSet();
spawnOnce = false;
}
}

// make a new set of colors based on the variables maxRandomColorSize and minRandomColorSize
public void NewColorSet()
{
ResetVisualColors();

simonColorOrder.simonOrder.Clear();

List<SimonColorSo> gottenColors = new List<SimonColorSo>();
C#
public class SimonController : MonoBehaviour
{
#region Variables

[SerializeField] private List<SimonColorSo> simonColors = new List<SimonColorSo>();

[SerializeField] private GameObject displayLocation;
//[SerializeField] private GameObject displayPrefab;
public string displayName;
[SerializeField] private SimonColorOrderSO simonColorOrder;

[Range(4, 98)][SerializeField] private int ColorAmount;
[SerializeField] private int amountChancePerColor = 100;

private List<GameObject> visualColors = new List<GameObject>();
[SerializeField] private bool everyColorOnce = false;

bool spawnOnce = true;
#endregion

private void Update()
{
if (spawnOnce)
{
NewColorSet();
spawnOnce = false;
}
}

// make a new set of colors based on the variables maxRandomColorSize and minRandomColorSize
public void NewColorSet()
{
ResetVisualColors();

simonColorOrder.simonOrder.Clear();

List<SimonColorSo> gottenColors = new List<SimonColorSo>();
c#
// for the amount of ColorAmount create a new color and spawn the color in the world
for (int i = 0; i < ColorAmount; i++)
{
SimonColorSo color = null;

// change the way i create a random simonColorSo based on if the bool everyColorOnce is true or false
if (everyColorOnce)
{
color = CreateRandomColor(gottenColors);

gottenColors.Add(color);

if (gottenColors.Count == simonColors.Count)
{
gottenColors.Clear();
}
}
else
{
color = CreateRandomColor();
}

simonColorOrder.simonOrder.Add(color);

GameObject visualColor = Realtime.Instantiate(displayName, displayLocation.transform.position, Quaternion.identity);
visualColor.transform.parent = displayLocation.transform;
visualColor.GetComponent<SimonColorDisplay>().SetInfo(color);

visualColors.Add(visualColor);
}
}

// randomizes the color i get
SimonColorSo CreateRandomColor()
{
int maxOption = simonColors.Count * amountChancePerColor;
int result = Random.Range(1, maxOption);

return simonColors[Mathf.FloorToInt(result / amountChancePerColor)];
}

// checks if i the color i got is in the list
SimonColorSo CreateRandomColor(List<SimonColorSo> checkList)
{
SimonColorSo color = CreateRandomColor();

if(!checkList.Contains(color))
{
return color;
}
else
{
return CreateRandomColor(checkList);
}
}

void ResetVisualColors()
{
if(visualColors.Count != 0)
{
foreach (var obj in visualColors)
{
Destroy(obj);
}
visualColors.Clear();
}
}

}
c#
// for the amount of ColorAmount create a new color and spawn the color in the world
for (int i = 0; i < ColorAmount; i++)
{
SimonColorSo color = null;

// change the way i create a random simonColorSo based on if the bool everyColorOnce is true or false
if (everyColorOnce)
{
color = CreateRandomColor(gottenColors);

gottenColors.Add(color);

if (gottenColors.Count == simonColors.Count)
{
gottenColors.Clear();
}
}
else
{
color = CreateRandomColor();
}

simonColorOrder.simonOrder.Add(color);

GameObject visualColor = Realtime.Instantiate(displayName, displayLocation.transform.position, Quaternion.identity);
visualColor.transform.parent = displayLocation.transform;
visualColor.GetComponent<SimonColorDisplay>().SetInfo(color);

visualColors.Add(visualColor);
}
}

// randomizes the color i get
SimonColorSo CreateRandomColor()
{
int maxOption = simonColors.Count * amountChancePerColor;
int result = Random.Range(1, maxOption);

return simonColors[Mathf.FloorToInt(result / amountChancePerColor)];
}

// checks if i the color i got is in the list
SimonColorSo CreateRandomColor(List<SimonColorSo> checkList)
{
SimonColorSo color = CreateRandomColor();

if(!checkList.Contains(color))
{
return color;
}
else
{
return CreateRandomColor(checkList);
}
}

void ResetVisualColors()
{
if(visualColors.Count != 0)
{
foreach (var obj in visualColors)
{
Destroy(obj);
}
visualColors.Clear();
}
}

}
Mechabit
Mechabit2w ago
then Random calls will all be the same for clients who use the same seed
gewoon_kiefer
gewoon_kieferOP2w ago
how do i create a random seed that the server has then?
Mechabit
Mechabit2w ago
@kapa.ai how do I sync a random seed number in normcore
kapa.ai
kapa.ai2w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- To synchronize a random seed number in Normcore, you can use a RealtimeComponent and RealtimeModel approach. This method ensures that all clients use the same seed for random number generation, maintaining consistency across the network. Here's how you can implement it: 1. First, create a RealtimeModel to hold the seed:
using Normal.Realtime;

[RealtimeModel]
public partial class RandomSeedModel
{
[RealtimeProperty(1, true)]
private int _seed;
}
using Normal.Realtime;

[RealtimeModel]
public partial class RandomSeedModel
{
[RealtimeProperty(1, true)]
private int _seed;
}
2. Then, create a RealtimeComponent to manage the seed synchronization:
using Normal.Realtime;
using UnityEngine;

public class RandomSeedSync : RealtimeComponent<RandomSeedModel>
{
private System.Random _random;

protected override void OnRealtimeModelReplaced(RandomSeedModel previousModel, RandomSeedModel currentModel)
{
if (currentModel != null)
{
if (currentModel.isFreshModel)
{
// Generate a new seed if this is a fresh model
currentModel.seed = Random.Range(int.MinValue, int.MaxValue);
}

// Initialize the random number generator with the synchronized seed
_random = new System.Random(currentModel.seed);
}
}

// Use this method to get random numbers that will be the same across all clients
public int GetRandomNumber(int minValue, int maxValue)
{
return _random.Next(minValue, maxValue);
}
}
using Normal.Realtime;
using UnityEngine;

public class RandomSeedSync : RealtimeComponent<RandomSeedModel>
{
private System.Random _random;

protected override void OnRealtimeModelReplaced(RandomSeedModel previousModel, RandomSeedModel currentModel)
{
if (currentModel != null)
{
if (currentModel.isFreshModel)
{
// Generate a new seed if this is a fresh model
currentModel.seed = Random.Range(int.MinValue, int.MaxValue);
}

// Initialize the random number generator with the synchronized seed
_random = new System.Random(currentModel.seed);
}
}

// Use this method to get random numbers that will be the same across all clients
public int GetRandomNumber(int minValue, int maxValue)
{
return _random.Next(minValue, maxValue);
}
}
3. Use this component in your scene: - Create an empty GameObject in your scene. - Add the RandomSeedSync component to it. - Make sure to add a RealtimeView component to the same GameObject. Now, whenever you need a random number that should be the same across all clients, you can use the GetRandomNumber method of the RandomSeedSync component. This approach ensures that: 1. The seed is generated once when the first player joins the room. 2. All clients use the same seed for random number generation. 3. The seed is synchronized across all clients. Remember that this method synchronizes the seed, not individual random numbers. All clients will generate the same sequence of random numbers based on this seed. For more information on creating custom RealtimeComponents and RealtimeModels in Normcore, you can refer to the Synchronizing Custom Data guide. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2w ago
<@162940911964258305> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
gewoon_kiefer
gewoon_kieferOP2w ago
oke thx @kapa.ai how to use the RandomSeedSync script and make a new random seed
kapa.ai
kapa.ai2w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- To use the RandomSeedSync script and generate a new random seed in Normcore, you can follow these steps: 1. First, make sure you have the RandomSeedSync script attached to a GameObject in your scene, along with a RealtimeView component. 2. To access the RandomSeedSync component and use it to generate random numbers, you can do the following:
public class YourGameScript : MonoBehaviour
{
private RandomSeedSync randomSeedSync;

void Start()
{
// Find the RandomSeedSync component in the scene
randomSeedSync = FindObjectOfType<RandomSeedSync>();

if (randomSeedSync != null)
{
// Use the synchronized random number generator
int randomNumber = randomSeedSync.GetRandomNumber(0, 100);
Debug.Log("Synchronized random number: " + randomNumber);
}
}
}
public class YourGameScript : MonoBehaviour
{
private RandomSeedSync randomSeedSync;

void Start()
{
// Find the RandomSeedSync component in the scene
randomSeedSync = FindObjectOfType<RandomSeedSync>();

if (randomSeedSync != null)
{
// Use the synchronized random number generator
int randomNumber = randomSeedSync.GetRandomNumber(0, 100);
Debug.Log("Synchronized random number: " + randomNumber);
}
}
}
3. To generate a new random seed, you would typically do this when starting a new game or round. You can modify the RandomSeedSync script to include a method for generating a new seed:
public class RandomSeedSync : RealtimeComponent<RandomSeedModel>
{
private System.Random _random;

protected override void OnRealtimeModelReplaced(RandomSeedModel previousModel, RandomSeedModel currentModel)
{
if (currentModel != null)
{
if (currentModel.isFreshModel)
{
GenerateNewSeed();
}

// Initialize the random number generator with the synchronized seed
_random = new System.Random(currentModel.seed);
}
}

public void GenerateNewSeed()
{
// Only the owner should generate a new seed
if (isOwnedLocally)
{
model.seed = Random.Range(int.MinValue, int.MaxValue);
_random = new System.Random(model.seed);
}
}
public int GetRandomNumber(int minValue, int maxValue)
{
return _random.Next(minValue, maxValue);
}
}
public class RandomSeedSync : RealtimeComponent<RandomSeedModel>
{
private System.Random _random;

protected override void OnRealtimeModelReplaced(RandomSeedModel previousModel, RandomSeedModel currentModel)
{
if (currentModel != null)
{
if (currentModel.isFreshModel)
{
GenerateNewSeed();
}

// Initialize the random number generator with the synchronized seed
_random = new System.Random(currentModel.seed);
}
}

public void GenerateNewSeed()
{
// Only the owner should generate a new seed
if (isOwnedLocally)
{
model.seed = Random.Range(int.MinValue, int.MaxValue);
_random = new System.Random(model.seed);
}
}
public int GetRandomNumber(int minValue, int maxValue)
{
return _random.Next(minValue, maxValue);
}
}
4. Then, you can call the GenerateNewSeed method when you want to create a new random seed:
public class YourGameManager : MonoBehaviour
{
private RandomSeedSync randomSeedSync;

void Start()
{
randomSeedSync = FindObjectOfType<RandomSeedSync>();
}

public void StartNewGame()
{
if (randomSeedSync != null)
{
randomSeedSync.GenerateNewSeed();
Debug.Log("New random seed generated");
}
}
}
public class YourGameManager : MonoBehaviour
{
private RandomSeedSync randomSeedSync;

void Start()
{
randomSeedSync = FindObjectOfType<RandomSeedSync>();
}

public void StartNewGame()
{
if (randomSeedSync != null)
{
randomSeedSync.GenerateNewSeed();
Debug.Log("New random seed generated");
}
}
}
Remember that only one client should generate the new seed (typically the room owner or the game host). The isOwnedLocally check in the GenerateNewSeed method ensures that only the owner of the RandomSeedSync object can generate a new seed. This approach uses Normcore's RealtimeComponent system to synchronize the random seed across all clients, ensuring that all players in the room will generate the same sequence of random numbers. Synchronizing Custom Data provides more details on creating and using custom RealtimeComponents in Normcore. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai2w ago
<@899009432518803496> Kapa.ai is still learning and improving, please let me know how I did by reacting below.

Did you find this page helpful?