This commit is contained in:
2025-06-16 15:14:23 +02:00
commit 074e590073
3174 changed files with 428263 additions and 0 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a3db73e3af7b04b51864106cf8b38b24
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,42 @@
// script to handle the table's pocket collisions for resets / destruction.
// predicted objects sometimes have their rigidbodies moved out of them.
// which is why we handle collisions in the table itself, not per-object.
// because here we can check who the rigidbody belongs to more easily.
// ... that's just the best practice at the moment, maybe we can make this
// easier in the future ...
using UnityEngine;
namespace Mirror.Examples.BilliardsPredicted
{
public class Pockets : MonoBehaviour
{
void OnTriggerEnter(Collider other)
{
if (!NetworkServer.active) return;
// the collider may be on a predicted object or on its ghost object.
// find the source first.
if (PredictedRigidbody.IsPredicted(other, out PredictedRigidbody predicted))
{
// is it a white ball?
if (predicted.TryGetComponent(out WhiteBallPredicted white))
{
Rigidbody rigidBody = predicted.predictedRigidbody;
rigidBody.position = white.startPosition;
#if UNITY_6000_0_OR_NEWER
rigidBody.linearVelocity = Vector3.zero;
#else
rigidBody.velocity = Vector3.zero;
#endif
}
// is it a read ball?
if (predicted.GetComponent<RedBallPredicted>())
{
// destroy when entering a pocket.
NetworkServer.Destroy(predicted.gameObject);
}
}
}
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: f17c923d118b941fb90a834d87e9ff27
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Examples/BilliardsPredicted/Ball/Pockets.cs
uploadId: 736421

View File

@ -0,0 +1,80 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Red
m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
m_ValidKeywords: []
m_InvalidKeywords: []
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: 1
- _Glossiness: 0.92
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _UVSec: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 0.2971698, b: 0.2971698, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []

View File

@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: 919ef8af6341a428380e25d2f2ced7f9
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Examples/BilliardsPredicted/Ball/Red.mat
uploadId: 736421

View File

@ -0,0 +1,22 @@
using UnityEngine;
namespace Mirror.Examples.BilliardsPredicted
{
// keep the empty script so we can find out what type of ball we collided with.
public class RedBallPredicted : NetworkBehaviour
{
/* ball<->pocket collisions are handled by Pockets.cs for now.
because predicted object's rigidbodies are sometimes moved out of them.
which means this script here wouldn't get the collision info while predicting.
which means it's easier to check collisions from the table perspective.
// destroy when entering a pocket.
// there's only one trigger in the scene (the pocket).
[ServerCallback]
void OnTriggerEnter(Collider other)
{
NetworkServer.Destroy(gameObject);
}
*/
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3c3384f4b0aaa417abfc3335d2d873c0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Examples/BilliardsPredicted/Ball/RedBallPredicted.cs
uploadId: 736421

View File

@ -0,0 +1,184 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3429911415116987808
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3429911415116987812}
- component: {fileID: 3429911415116987813}
- component: {fileID: 3429911415116987810}
- component: {fileID: 6723567693459418947}
- component: {fileID: 3429911415116987811}
- component: {fileID: -177125271246800426}
- component: {fileID: 5308121378143249733}
- component: {fileID: -8731861437394929722}
m_Layer: 0
m_Name: RedPredicted
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3429911415116987812
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3429911415116987808}
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 &3429911415116987813
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3429911415116987808}
m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &3429911415116987810
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3429911415116987808}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 919ef8af6341a428380e25d2f2ced7f9, 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
--- !u!114 &6723567693459418947
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3429911415116987808}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9b91ecbcc199f4492b9a91e820070131, type: 3}
m_Name:
m_EditorClassIdentifier:
sceneId: 0
_assetId: 4171706805
serverOnly: 0
visibility: 0
hasSpawned: 0
--- !u!135 &3429911415116987811
SphereCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3429911415116987808}
m_Material: {fileID: 13400000, guid: b70c4b1c659c647859a8491848d4b145, type: 2}
m_IsTrigger: 0
m_Enabled: 1
serializedVersion: 2
m_Radius: 0.5
m_Center: {x: 0, y: 0, z: 0}
--- !u!54 &-177125271246800426
Rigidbody:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3429911415116987808}
serializedVersion: 2
m_Mass: 0.5
m_Drag: 0.5
m_AngularDrag: 0.05
m_UseGravity: 1
m_IsKinematic: 0
m_Interpolate: 1
m_Constraints: 4
m_CollisionDetection: 2
--- !u!114 &5308121378143249733
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3429911415116987808}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 3c3384f4b0aaa417abfc3335d2d873c0, type: 3}
m_Name:
m_EditorClassIdentifier:
syncDirection: 0
syncMode: 0
syncInterval: 0
--- !u!114 &-8731861437394929722
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3429911415116987808}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d38927cdc6024b9682b5fe9778b9ef99, type: 3}
m_Name:
m_EditorClassIdentifier:
syncDirection: 0
syncMode: 0
syncInterval: 0
predictedRigidbody: {fileID: -177125271246800426}
mode: 1
motionSmoothingVelocityThreshold: 0.1
motionSmoothingAngularVelocityThreshold: 5
motionSmoothingTimeTolerance: 0.5
stateHistoryLimit: 32
recordInterval: 0.05
onlyRecordChanges: 1
compareLastFirst: 1
positionCorrectionThreshold: 0.1
rotationCorrectionThreshold: 5
oneFrameAhead: 1
snapThreshold: 2
showGhost: 1
ghostVelocityThreshold: 0.1
localGhostMaterial: {fileID: 2100000, guid: 411a48b4a197d4924bec3e3809bc9320, type: 2}
remoteGhostMaterial: {fileID: 2100000, guid: 04f0b2088c857414393bab3b80356776, type: 2}
checkGhostsEveryNthFrame: 4
positionInterpolationSpeed: 15
rotationInterpolationSpeed: 10
teleportDistanceMultiplier: 10
reduceSendsWhileIdle: 1

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: ecbb35e2a01c0427ebb0b4b6ed146c70
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Examples/BilliardsPredicted/Ball/RedPredicted.prefab
uploadId: 736421

View File

@ -0,0 +1,80 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: White
m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
m_ValidKeywords: []
m_InvalidKeywords: []
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: 1
- _Glossiness: 0.93
- _GlossyReflections: 1
- _Metallic: 0
- _Mode: 0
- _OcclusionStrength: 1
- _Parallax: 0.02
- _SmoothnessTextureChannel: 0
- _SpecularHighlights: 1
- _SrcBlend: 1
- _UVSec: 0
- _ZWrite: 1
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []

View File

@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: cc250a30997cc42318e82a6ec42171db
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Examples/BilliardsPredicted/Ball/White.mat
uploadId: 736421

View File

@ -0,0 +1,186 @@
using System;
using UnityEngine;
namespace Mirror.Examples.BilliardsPredicted
{
public class WhiteBallPredicted : NetworkBehaviour
{
public LineRenderer dragIndicator;
public float dragTolerance = 1.0f;
public Rigidbody rigidBody;
public float forceMultiplier = 2;
public float maxForce = 40;
// remember start position to reset to after entering a pocket
internal Vector3 startPosition;
bool draggingStartedOverObject;
// cast mouse position on screen to world position
bool MouseToWorld(out Vector3 position)
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Plane plane = new Plane(Vector3.up, transform.position);
if (plane.Raycast(ray, out float distance))
{
position = ray.GetPoint(distance);
return true;
}
position = default;
return false;
}
void Awake()
{
startPosition = transform.position;
}
[ClientCallback]
void Update()
{
// mouse down on the white ball?
if (Input.GetMouseButtonDown(0))
{
if (MouseToWorld(out Vector3 position))
{
// allow dragging if mouse is 'close enough'.
// balls are moving so we don't need to be exactly on it.
float distance = Vector3.Distance(position, transform.position);
if (distance <= dragTolerance)
{
// enable drag indicator
dragIndicator.SetPosition(0, transform.position);
dragIndicator.SetPosition(1, transform.position);
dragIndicator.gameObject.SetActive(true);
draggingStartedOverObject = true;
}
}
}
// mouse button dragging?
else if (Input.GetMouseButton(0))
{
// cast mouse position to world
if (draggingStartedOverObject && MouseToWorld(out Vector3 current))
{
// drag indicator
dragIndicator.SetPosition(0, transform.position);
dragIndicator.SetPosition(1, current);
}
}
// mouse button up?
else if (Input.GetMouseButtonUp(0))
{
// cast mouse position to world
if (draggingStartedOverObject && MouseToWorld(out Vector3 current))
{
// calculate delta from ball to mouse
// ball may have moved since we started dragging,
// so always use current ball position here.
Vector3 from = transform.position;
// debug drawing: only works if Gizmos are enabled!
Debug.DrawLine(from, current, Color.white, 2);
// calculate pending force delta
Vector3 delta = from - current;
Vector3 force = delta * forceMultiplier;
// there should be a maximum allowed force
force = Vector3.ClampMagnitude(force, maxForce);
// forward the event to the local player's object.
// the ball isn't part of the local player.
NetworkClient.localPlayer.GetComponent<PlayerPredicted>().OnDraggedBall(force);
// disable drag indicator
dragIndicator.gameObject.SetActive(false);
}
draggingStartedOverObject = false;
}
}
// OnMouse callbacks don't work for predicted objects because we need to
// move the collider out of the main object ocassionally.
// besides, having a drag tolerance and not having to click exactly on
// the white ball is nice.
/*
[ClientCallback]
void OnMouseDown()
{
// enable drag indicator
dragIndicator.SetPosition(0, transform.position);
dragIndicator.SetPosition(1, transform.position);
dragIndicator.gameObject.SetActive(true);
}
[ClientCallback]
void OnMouseDrag()
{
// cast mouse position to world
if (!MouseToWorld(out Vector3 current)) return;
// drag indicator
dragIndicator.SetPosition(0, transform.position);
dragIndicator.SetPosition(1, current);
}
[ClientCallback]
void OnMouseUp()
{
// cast mouse position to world
if (!MouseToWorld(out Vector3 current)) return;
// calculate delta from ball to mouse
// ball may have moved since we started dragging,
// so always use current ball position here.
Vector3 from = transform.position;
// debug drawing: only works if Gizmos are enabled!
Debug.DrawLine(from, current, Color.white, 2);
// calculate pending force delta
Vector3 delta = from - current;
Vector3 force = delta * forceMultiplier;
// there should be a maximum allowed force
force = Vector3.ClampMagnitude(force, maxForce);
// forward the event to the local player's object.
// the ball isn't part of the local player.
NetworkClient.localPlayer.GetComponent<PlayerPredicted>().OnDraggedBall(force);
// disable drag indicator
dragIndicator.gameObject.SetActive(false);
}
*/
/* ball<->pocket collisions are handled by Pockets.cs for now.
because predicted object's rigidbodies are sometimes moved out of them.
which means this script here wouldn't get the collision info while predicting.
which means it's easier to check collisions from the table perspective.
// reset position when entering a pocket.
// there's only one trigger in the scene (the pocket).
[ServerCallback]
void OnTriggerEnter(Collider other)
{
rigidBody.position = startPosition;
rigidBody.Sleep(); // reset forces
// GetComponent<NetworkRigidbodyUnreliable>().RpcTeleport(startPosition);
}
*/
[ClientCallback]
void OnGUI()
{
// have a button to reply exactly the same force in every hit for easier testing.
if (GUI.Button(new Rect(10, 150, 200, 20), "Hit!"))
{
// hit with a slight angle so the red balls spread out in all directions
Vector3 force = Vector3.ClampMagnitude(new Vector3(10, 0, 600), maxForce);
NetworkClient.localPlayer.GetComponent<PlayerPredicted>().OnDraggedBall(force);
}
}
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 8fb3f86b63cef4b2093db683f699a13c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Examples/BilliardsPredicted/Ball/WhiteBallPredicted.cs
uploadId: 736421

View File

@ -0,0 +1,318 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &982362981
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 982362983}
- component: {fileID: 982362982}
m_Layer: 0
m_Name: DragIndicator - Line
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 0
--- !u!4 &982362983
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 982362981}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 13}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 3429911415116987812}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!120 &982362982
LineRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 982362981}
m_Enabled: 1
m_CastShadows: 0
m_ReceiveShadows: 0
m_DynamicOccludee: 1
m_MotionVectors: 0
m_LightProbeUsage: 0
m_ReflectionProbeUsage: 0
m_RayTracingMode: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 10306, guid: 0000000000000000f000000000000000, type: 0}
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_Positions:
- {x: 0, y: 0, z: 0}
- {x: 0, y: 0, z: 1}
m_Parameters:
serializedVersion: 3
widthMultiplier: 0.1
widthCurve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 1
inSlope: 0
outSlope: 0
tangentMode: 0
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
colorGradient:
serializedVersion: 2
key0: {r: 1, g: 1, b: 1, a: 1}
key1: {r: 1, g: 1, b: 1, a: 0}
key2: {r: 0, g: 0, b: 0, a: 0}
key3: {r: 0, g: 0, b: 0, a: 0}
key4: {r: 0, g: 0, b: 0, a: 0}
key5: {r: 0, g: 0, b: 0, a: 0}
key6: {r: 0, g: 0, b: 0, a: 0}
key7: {r: 0, g: 0, b: 0, a: 0}
ctime0: 0
ctime1: 65535
ctime2: 0
ctime3: 0
ctime4: 0
ctime5: 0
ctime6: 0
ctime7: 0
atime0: 31418
atime1: 65535
atime2: 0
atime3: 0
atime4: 0
atime5: 0
atime6: 0
atime7: 0
m_Mode: 0
m_NumColorKeys: 2
m_NumAlphaKeys: 2
numCornerVertices: 0
numCapVertices: 0
alignment: 0
textureMode: 0
shadowBias: 0.5
generateLightingData: 0
m_UseWorldSpace: 1
m_Loop: 0
--- !u!1 &3429911415116987808
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3429911415116987812}
- component: {fileID: 3429911415116987813}
- component: {fileID: 3429911415116987810}
- component: {fileID: -1560774411725421365}
- component: {fileID: 3429911415116987811}
- component: {fileID: 1848203816128897140}
- component: {fileID: 6607303410184343467}
- component: {fileID: -2068612481632719751}
m_Layer: 0
m_Name: WhitePredicted
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3429911415116987812
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3429911415116987808}
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:
- {fileID: 982362983}
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &3429911415116987813
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3429911415116987808}
m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &3429911415116987810
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3429911415116987808}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: cc250a30997cc42318e82a6ec42171db, 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
--- !u!114 &-1560774411725421365
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3429911415116987808}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9b91ecbcc199f4492b9a91e820070131, type: 3}
m_Name:
m_EditorClassIdentifier:
sceneId: 0
_assetId: 1601738520
serverOnly: 0
visibility: 0
hasSpawned: 0
--- !u!135 &3429911415116987811
SphereCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3429911415116987808}
m_Material: {fileID: 13400000, guid: b70c4b1c659c647859a8491848d4b145, type: 2}
m_IsTrigger: 0
m_Enabled: 1
serializedVersion: 2
m_Radius: 0.5
m_Center: {x: 0, y: 0, z: 0}
--- !u!54 &1848203816128897140
Rigidbody:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3429911415116987808}
serializedVersion: 2
m_Mass: 0.5
m_Drag: 0.5
m_AngularDrag: 0.05
m_UseGravity: 1
m_IsKinematic: 0
m_Interpolate: 1
m_Constraints: 4
m_CollisionDetection: 2
--- !u!114 &6607303410184343467
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3429911415116987808}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8fb3f86b63cef4b2093db683f699a13c, type: 3}
m_Name:
m_EditorClassIdentifier:
syncDirection: 0
syncMode: 0
syncInterval: 0
dragIndicator: {fileID: 982362982}
dragTolerance: 1
rigidBody: {fileID: 1848203816128897140}
forceMultiplier: 2
maxForce: 40
--- !u!114 &-2068612481632719751
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3429911415116987808}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d38927cdc6024b9682b5fe9778b9ef99, type: 3}
m_Name:
m_EditorClassIdentifier:
syncDirection: 0
syncMode: 0
syncInterval: 0
predictedRigidbody: {fileID: 1848203816128897140}
mode: 1
motionSmoothingVelocityThreshold: 0.1
motionSmoothingAngularVelocityThreshold: 5
motionSmoothingTimeTolerance: 0.5
stateHistoryLimit: 32
recordInterval: 0.05
onlyRecordChanges: 1
compareLastFirst: 1
positionCorrectionThreshold: 0.1
rotationCorrectionThreshold: 5
oneFrameAhead: 1
snapThreshold: 2
showGhost: 1
ghostVelocityThreshold: 0.1
localGhostMaterial: {fileID: 2100000, guid: 411a48b4a197d4924bec3e3809bc9320, type: 2}
remoteGhostMaterial: {fileID: 2100000, guid: 04f0b2088c857414393bab3b80356776, type: 2}
checkGhostsEveryNthFrame: 4
positionInterpolationSpeed: 15
rotationInterpolationSpeed: 10
teleportDistanceMultiplier: 10
reduceSendsWhileIdle: 1

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 8b8303075efb94c0e9478abfb2e558df
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Examples/BilliardsPredicted/Ball/WhitePredicted.prefab
uploadId: 736421

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: b18c719cb0805482ca6fc7c2db38ab4b
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Examples/BilliardsPredicted/MirrorBilliardsPredicted.unity
uploadId: 736421

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4792af3825b5e47de945b1da8e0037c7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Mirror.Examples.BilliardsPredicted
{
// example input for this exact demo.
// not general purpose yet.
public struct PlayerInput
{
public double timestamp;
public Vector3 force;
public PlayerInput(double timestamp, Vector3 force)
{
this.timestamp = timestamp;
this.force = force;
}
}
public class PlayerPredicted : NetworkBehaviour
{
// white ball component
WhiteBallPredicted whiteBall;
void Awake()
{
// find the white ball once
#if UNITY_2022_2_OR_NEWER
whiteBall = FindAnyObjectByType<WhiteBallPredicted>();
#else
// Deprecated in Unity 2023.1
whiteBall = FindObjectOfType<WhiteBallPredicted>();
#endif
}
// apply force to white ball.
// common function to ensure we apply it the same way on server & client!
void ApplyForceToWhite(Vector3 force)
{
// https://docs.unity3d.com/2021.3/Documentation/ScriptReference/Rigidbody.AddForce.html
// this is buffered until the next FixedUpdate.
// get the white ball's Rigidbody.
// prediction sometimes moves this out of the object for a while,
// so we need to grab it this way:
Rigidbody rb = whiteBall.GetComponent<PredictedRigidbody>().predictedRigidbody;
// AddForce has different force modes, see this excellent diagram:
// https://www.reddit.com/r/Unity3D/comments/psukm1/know_the_difference_between_forcemodes_a_little/
// for prediction it's extremely important(!) to apply the correct mode:
// 'Force' makes server & client drift significantly here
// 'Impulse' is correct usage with significantly less drift
rb.AddForce(force, ForceMode.Impulse);
}
// called when the local player dragged the white ball.
// we reuse the white ball's OnMouseDrag and forward the event to here.
public void OnDraggedBall(Vector3 force)
{
// apply force locally immediately
ApplyForceToWhite(force);
// apply on server as well.
// not necessary in host mode, otherwise we would apply it twice.
if (!isServer) CmdApplyForce(force);
}
// while prediction is applied on clients immediately,
// we still want to validate every input on the server and reject it if necessary.
// this way we can latency free yet cheat safe movement.
// this should include a certain tolerance so players aren't hard corrected
// for their local movement all the time.
// TODO this should be on some kind of base class for reuse, but perhaps independent of parameters?
bool IsValidMove(Vector3 force) => true;
// TODO send over unreliable with ack, notify, etc. later
[Command]
void CmdApplyForce(Vector3 force)
{
if (!IsValidMove(force))
{
Debug.Log($"Server rejected move: {force}");
return;
}
// apply force
ApplyForceToWhite(force);
}
}
}

View File

@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 79c6ff7781e8c4174b07ea7e905f62b2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Examples/BilliardsPredicted/Player/PlayerPredicted.cs
uploadId: 736421

View File

@ -0,0 +1,66 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &7529115942715260948
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1534822232247476853}
- component: {fileID: 1279227879756014839}
- component: {fileID: -1702612885512365273}
m_Layer: 0
m_Name: PlayerPredicted
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1534822232247476853
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7529115942715260948}
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!114 &1279227879756014839
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7529115942715260948}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9b91ecbcc199f4492b9a91e820070131, type: 3}
m_Name:
m_EditorClassIdentifier:
sceneId: 0
_assetId: 4028108460
serverOnly: 0
visibility: 0
hasSpawned: 0
--- !u!114 &-1702612885512365273
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7529115942715260948}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 79c6ff7781e8c4174b07ea7e905f62b2, type: 3}
m_Name:
m_EditorClassIdentifier:
syncDirection: 0
syncMode: 0
syncInterval: 0

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: ffab5af8b8b354f8ca8eb7aeaf89dfac
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Examples/BilliardsPredicted/Player/PlayerPredicted.prefab
uploadId: 736421

View File

@ -0,0 +1,19 @@
Advanced multiplayer Billiards demo with Prediction.
Please read this first:
https://mirror-networking.gitbook.io/docs/manual/general/client-side-prediction
Mouse drag the white ball to apply force.
PredictedRigidbody syncInterval is intentionally set pretty high so we can see when it corrects.
If you are a beginner, start with the basic Billiards demo instead.
If you are advanced, this demo shows how to use Mirror's prediction features for physics / FPS games.
Billiards is a great example to try our Prediction algorithm, it works extremely well here!
=> We use 'Fast' Prediction mode for Billiards because we want to see exact collisions with balls/walls.
=> 'Smooth' mode would look too soft, with balls changing direction even before touching other balls/walls.
Notes:
- Red/White ball Rigidbody CollisionMode needs to be ContinousDynamic to avoid white flying through red sometimes.
even 'Continous' is not enough, we need ContinousDynamic.

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: e88568e61917a49d5957d51c4f8fb239
timeCreated: 1684039107
AssetOrigin:
serializedVersion: 1
productId: 129321
packageName: Mirror
packageVersion: 96.0.1
assetPath: Assets/Mirror/Examples/BilliardsPredicted/_Readme.txt
uploadId: 736421