Compare commits

..

20 Commits

Author SHA1 Message Date
APEX FIGHT
cf607fa060 Merge branch 'main' of https://gitea.apexfight.net/apex/CSNC 2025-02-24 21:52:36 -05:00
APEX FIGHT
ef87716a8c added timeout to CSNC server 2025-02-24 21:52:30 -05:00
8459e7df21 Update README.md 2025-01-03 05:46:18 +00:00
9785da2e0d Update Client/Unity/README.md 2025-01-03 05:42:03 +00:00
e67060aaf7 Update README.md 2025-01-03 05:41:41 +00:00
a84cb21d8b Update README.md 2025-01-03 05:40:32 +00:00
aa1be3c082 Update README.md 2025-01-03 05:34:50 +00:00
APEX FIGHT
62a1d22528 added tick interval options (to reduce network load from 80tps lololol) 2025-01-03 00:33:48 -05:00
APEX FIGHT
ef941cd344 added uuid support for synchronization 2025-01-02 18:22:56 -05:00
APEX FIGHT
0810be6809 clearer documentation 2025-01-02 18:22:33 -05:00
APEX FIGHT
7d799457f6 clearer naming 2025-01-02 18:22:20 -05:00
APEX FIGHT
2a445343a2 meta 2025-01-02 18:22:03 -05:00
APEX FIGHT
af9e800716 gameobjectmanager 2025-01-02 18:21:58 -05:00
APEX FIGHT
72113dc2f2 prefab update 2025-01-02 18:21:48 -05:00
APEX FIGHT
a83220ea7c method call to sync objects 2025-01-02 18:21:37 -05:00
APEX FIGHT
3faaba9d0c added object uuid for synchronization 2025-01-02 18:20:54 -05:00
APEX FIGHT
3a30091559 made private public 2025-01-02 18:20:15 -05:00
APEX FIGHT
51132ceb5c updated csncobject class 2025-01-02 18:20:02 -05:00
APEX FIGHT
258f299fa0 default eztransform constructor 2025-01-02 18:19:24 -05:00
APEX FIGHT
a06db78d42 gameobjectmanager 2025-01-02 18:19:09 -05:00
9 changed files with 207 additions and 28 deletions

View File

@@ -6,9 +6,13 @@ using UnityEngine.Networking;
public class CSNC : MonoBehaviour public class CSNC : MonoBehaviour
{ {
public float tickInterval = 5f; // x times per sec
public string ip; public string ip;
private class EZtransform //ez consistent serialization of transforms public GameObjectManager manager;
private float lastPing = 0; //last time connected to server
public class EZtransform //ez consistent serialization of transforms
{ {
public List<float> position; public List<float> position;
public List<float> rotation; public List<float> rotation;
@@ -26,26 +30,38 @@ public class CSNC : MonoBehaviour
rotation.Add(transform.rotation.z); rotation.Add(transform.rotation.z);
rotation.Add(transform.rotation.w); rotation.Add(transform.rotation.w);
} }
public EZtransform()
{
position = new List<float>();
rotation = new List<float>();
}
} }
private class CSNCObject public class CSNCObject
{ {
public string id;
public string type; public string type;
public EZtransform transform; public EZtransform transform;
public CSNCObject()
public CSNCObject(string type, Transform trans)
{ {
id = "";
type = "";
transform = new EZtransform();
}
public CSNCObject(string type, Transform trans, string id)
{
this.id = id;
this.type = type; this.type = type;
this.transform = new EZtransform(trans); this.transform = new EZtransform(trans);
} }
public CSNCObject(string type, EZtransform trans) public CSNCObject(string type, EZtransform trans, string id)
{ {
this.id = id;
this.type = type; this.type = type;
this.transform = trans; this.transform = trans;
} }
} }
private class SendData public class SendData
{ {
public string uuid; public string uuid;
public List<CSNCObject> gameObjects; public List<CSNCObject> gameObjects;
@@ -64,8 +80,12 @@ public class CSNC : MonoBehaviour
void FixedUpdate() void FixedUpdate()
{ {
IEnumerator req = request(); if (Time.timeSinceLevelLoad - (1000f / tickInterval) > lastPing)
StartCoroutine(req); {
lastPing = Time.timeSinceLevelLoad;
IEnumerator req = request();
StartCoroutine(req);
}
} }
IEnumerator request() IEnumerator request()
{ {
@@ -74,7 +94,7 @@ public class CSNC : MonoBehaviour
foreach (SyncData obj in GameObjectRegistry.instance.registeredObjects) foreach (SyncData obj in GameObjectRegistry.instance.registeredObjects)
{ {
gameObjects.Add(new CSNCObject(obj.type, obj.transform)); gameObjects.Add(new CSNCObject(obj.type, obj.transform, obj.id));
} }
SendData sendData = new SendData(gameObjects); SendData sendData = new SendData(gameObjects);
@@ -90,9 +110,7 @@ public class CSNC : MonoBehaviour
} }
else else
{ {
manager.SyncObjects(JsonConvert.DeserializeObject<GameObjectManager.RecievedData>(req.downloadHandler.text));
Debug.Log(req.downloadHandler.text);
} }
} }
} }

View File

@@ -11,6 +11,7 @@ GameObject:
- component: {fileID: 7121321073155658785} - component: {fileID: 7121321073155658785}
- component: {fileID: 7629741995735410931} - component: {fileID: 7629741995735410931}
- component: {fileID: 5596437124550187285} - component: {fileID: 5596437124550187285}
- component: {fileID: 1768507704600374837}
m_Layer: 0 m_Layer: 0
m_Name: CSNC m_Name: CSNC
m_TagString: Untagged m_TagString: Untagged
@@ -45,6 +46,9 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 1c20e30fdef1b0841aa0fdb48748a6a6, type: 3} m_Script: {fileID: 11500000, guid: 1c20e30fdef1b0841aa0fdb48748a6a6, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
uuid:
gameObjects: []
ids: []
--- !u!114 &5596437124550187285 --- !u!114 &5596437124550187285
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -57,3 +61,17 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 6772f36d5b93fd5498e8f3f1d8155ff2, type: 3} m_Script: {fileID: 11500000, guid: 6772f36d5b93fd5498e8f3f1d8155ff2, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
ip:
manager: {fileID: 1768507704600374837}
--- !u!114 &1768507704600374837
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9009233966360587498}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 7f3dbff6bcfe57f4fb9ba717c6fecc0e, type: 3}
m_Name:
m_EditorClassIdentifier:

View File

@@ -0,0 +1,99 @@
using Newtonsoft.Json;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameObjectManager : MonoBehaviour
{
/*
This MonoBehaviour Synchronizes the position of All the game objects recieved from other clients
*/
// ID GAMEOBJECT+TYPE
Dictionary<string, GameObjectDataWrapper> inSceneGameObjects = new Dictionary<string, GameObjectDataWrapper>();
void Start()
{
}
public class RecievedData
{
/*
{
"gameObjects": //list of CSNCObjects
[
{
"id": "uuid here",
"type": "razorblade",
"transform": {
"position": [
0.0,
0.53,
0.0
],
"rotation": [
0.0,
0.0,
0.0,
1.0
]
}
},
]
}
*/
public RecievedData(List<CSNC.CSNCObject> gameObjects)
{
this.gameObjects = gameObjects;
}
public List<CSNC.CSNCObject> gameObjects;
}
public class GameObjectDataWrapper
{
public GameObject gameObject;
public string type;
public GameObjectDataWrapper(string type, GameObject gameObject)
{
this.type = type;
this.gameObject = gameObject;
}
}
public void SyncObjects(RecievedData data)
{
//Debug.Log(JsonConvert.SerializeObject(data));
foreach (CSNC.CSNCObject obj in data.gameObjects)
{
if (inSceneGameObjects.ContainsKey(obj.id)) //if in registry update
{
Quaternion rot = new Quaternion(obj.transform.rotation[0], obj.transform.rotation[1], obj.transform.rotation[2], obj.transform.rotation[3]);
Debug.Log(JsonConvert.SerializeObject(obj.transform.position));
Vector3 pos = new Vector3(obj.transform.position[0], obj.transform.position[1], obj.transform.position[2]);
IEnumerator cor = LerpGameObjectFromFixedUpdate(inSceneGameObjects[obj.id].gameObject, rot, pos);
StartCoroutine(cor);
} else //otherwise add to registry
{
inSceneGameObjects.Add(obj.id, new GameObjectDataWrapper(obj.type,Instantiate(GameObjectRegistry.instance.registry[obj.type]))); //Dubious ahh line of code
GameObject g = inSceneGameObjects[obj.id].gameObject;
g.SetActive(true);
g.transform.position = new Vector3(obj.transform.position[0], obj.transform.position[1], obj.transform.position[2]); //these 2 might be even more dubious than the last
g.transform.rotation = new Quaternion(obj.transform.rotation[0], obj.transform.rotation[1], obj.transform.rotation[2], obj.transform.rotation[3]);
}
}
}
IEnumerator LerpGameObjectFromFixedUpdate(GameObject obj, Quaternion rot, Vector3 pos)
{
obj.transform.position = pos;
obj.transform.rotation = rot;
yield return null;
}
}

View File

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

View File

@@ -8,7 +8,8 @@ public class GameObjectRegistry : MonoBehaviour
public static GameObjectRegistry instance; //instance of gameobject registry in scene public static GameObjectRegistry instance; //instance of gameobject registry in scene
public string uuid; //unique identifier for this client public string uuid; //unique identifier for this client
public List<GameObject> gameObjects; //list to make it easy to register new gameobjects public List<GameObject> gameObjects; //list to make it easy to register new gameobjects
public List<string> ids; public List<string> types;
public Dictionary<string, GameObject> registry = new Dictionary<string, GameObject>(); //registry of game objects that can be synced (needs to be identical on both clients public Dictionary<string, GameObject> registry = new Dictionary<string, GameObject>(); //registry of game objects that can be synced (needs to be identical on both clients
public List<SyncData> registeredObjects = new List<SyncData>(); //game objcets that are currently being synced public List<SyncData> registeredObjects = new List<SyncData>(); //game objcets that are currently being synced
@@ -25,7 +26,7 @@ public class GameObjectRegistry : MonoBehaviour
void initializeRegistry() void initializeRegistry()
{ {
int i = 0; int i = 0;
foreach (string id in ids) foreach (string id in types)
{ {
registry.Add(id, gameObjects[i]); registry.Add(id, gameObjects[i]);
i++; i++;

View File

@@ -1,6 +1,6 @@
# Unity Setup # Unity Setup
First, set the ip of the csnc compatible server. First, set the ip of the csnc compatible server. \
(it needs to be formatted as such http://192.51.100.123:1234/ or http://mydomain.tld:1234/)
![image](./InstructionalImages/ipchange.png) ![image](./InstructionalImages/ipchange.png)
Then schedule the GameObjectRegistry script to be\ Then schedule the GameObjectRegistry script to be\

View File

@@ -6,9 +6,12 @@ public class SyncData //structure for gameobjects and data sent over to the serv
{ {
public SyncData(Transform pos, string type) public SyncData(Transform pos, string type)
{ {
this.id = System.Guid.NewGuid().ToString();
this.transform = pos; this.transform = pos;
this.type = type; this.type = type;
} }
public Transform transform; public Transform transform;
public string id;
public string type; //key in registry data that defines which gameobject to instantiate with transform: position public string type; //key in registry data that defines which gameobject to instantiate with transform: position
} }

View File

@@ -1,7 +1,16 @@
# CSNC # CSNC
Client-Sync-Net-Code for unity game objects Client-Sync-Net-Code for unity (but potentially not just unity) game objects
A set of very basic drop-in scripts for client-server games that allows 'p2p' style synchronization A set of very basic drop-in scripts for client-server games that allows 'p2p' style synchronization
great for synchronizing simple visual things that are mostly controlled by clients such as particles or thrown objects great for synchronizing simple visual things that are mostly controlled by clients such as particles or thrown objects
## Unity Setup
Please refer to : \
https://gitea.apexfight.net/apex/CSNC/src/branch/main/Client/Unity#unity-setup
## NodeJS Setup
Email me at vlrtch3571@gmail.com if you want me to make a tutorial or need help
this project is about 90% complete other than the godot and go implementations which will be done if i ever decide to make anything with either one

View File

@@ -1,9 +1,13 @@
const timeoutTickRate = 1; //timeout check every x seconds
const timeoutLength = 5000; //timeout length in ms
var gameObjectStore = {}; //object which stores CSNCgameobjects for each client var gameObjectStore = {}; //object which stores CSNCgameobjects for each client
//below is an example of the structure of gameobjectstore //below is an example of the structure of gameobjectstore
/** /**
* { * {
* "2c0a48e9-40c6-4139-9697-992397773b12": [ * "2c0a48e9-40c6-4139-9697-992397773b12":
* time: Date.now(),
* gameObjects: [
* { * {
"type": "razorblade", "type": "razorblade",
"transform": { "transform": {
@@ -42,6 +46,21 @@ var gameObjectStore = {}; //object which stores CSNCgameobjects for each client
* *
*/ */
/**
* @param {string} uuid
*/
function removePlayer(uuid) {
delete gameObjectStore[uuid];
}
function checkTimeout() {
for (let key in gameObjectStore) {
if (Date.now() - gameObjectStore[key].time > timeoutLength) {
removePlayer(key);
}
}
}
/** /**
* @param {http.ServerResponse<http.IncomingMessage>} res * @param {http.ServerResponse<http.IncomingMessage>} res
* @param {string} req * @param {string} req
@@ -53,9 +72,7 @@ async function handleResponse(res, req) {
var request = JSON.parse(req); var request = JSON.parse(req);
let gameObjects = request.gameObjects; let gameObjects = request.gameObjects;
gameObjectStore[request.uuid] = gameObjects; //store gameobjects to send to other players gameObjectStore[request.uuid] = {gameObjects: gameObjects, time: Date.now()}; //store gameobjects to send to other players
res.writeHead(200); res.writeHead(200);
res.write(JSON.stringify(responseObjectHelper(request.uuid))); res.write(JSON.stringify(responseObjectHelper(request.uuid)));
@@ -68,6 +85,7 @@ async function handleResponse(res, req) {
} }
} }
//preps and returns data to send back to client //preps and returns data to send back to client
function responseObjectHelper(uuid) { function responseObjectHelper(uuid) {
var responseObject = { var responseObject = {
@@ -76,9 +94,11 @@ function responseObjectHelper(uuid) {
for (let key in gameObjectStore) { for (let key in gameObjectStore) {
if (key == uuid) continue; //dont send back players own data if (key == uuid) continue; //dont send back players own data
responseObject.gameObjects = responseObject.gameObjects.concat(gameObjectStore[key]); responseObject.gameObjects = responseObject.gameObjects.concat(gameObjectStore[key].gameObjects);
} }
return responseObject; return responseObject;
} }
setInterval(checkTimeout, timeoutTickRate * 1000);
exports.handleResponse = handleResponse; exports.handleResponse = handleResponse;