M1: build shared 2-tread bot; rewire TreadPart to seat-based gating
- Rewrote TreadPart: seat-based input gating (was mis-wired to NetworkObject IsOwner, which needs per-tread ownership that NGO forbids for nested NetworkObjects). Both treads now share the chassis NetworkObject; a client drives the tread whose SeatManager seat it holds, host re-validates OwnsSeat on a RequireOwnership=false RPC. Also swapped to Keyboard.current (New input). - Moved SeatManager OnGUI below the connectivity panel (was overlapping). - M1_Arena: in-scene Bot NetworkObject = chassis (Rigidbody 60kg + NetworkObject + NetworkTransform + NetworkRigidbody + SeatManager) + Tread_L/Tread_R children (TreadPart, world offset +/-1.2 X for differential drive). - Solo-verified: bot auto-spawns in-scene, seat claim + input gating wired. Controls: W/Up throttle, Shift pivot, A/D lean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f2039a43b0
commit
9f49d45465
4 changed files with 617 additions and 121 deletions
|
|
@ -338,6 +338,522 @@ Transform:
|
||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &781174154
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 781174160}
|
||||||
|
- component: {fileID: 781174159}
|
||||||
|
- component: {fileID: 781174158}
|
||||||
|
- component: {fileID: 781174157}
|
||||||
|
- component: {fileID: 781174156}
|
||||||
|
- component: {fileID: 781174155}
|
||||||
|
- component: {fileID: 781174163}
|
||||||
|
- component: {fileID: 781174162}
|
||||||
|
- component: {fileID: 781174161}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Bot
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &781174155
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 781174154}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject
|
||||||
|
GlobalObjectIdHash: 1509253913
|
||||||
|
InScenePlacedSourceGlobalObjectIdHash: 0
|
||||||
|
DeferredDespawnTick: 0
|
||||||
|
Ownership: 1
|
||||||
|
AlwaysReplicateAsRoot: 0
|
||||||
|
SynchronizeTransform: 1
|
||||||
|
<InScenePlaced>k__BackingField: 0
|
||||||
|
ActiveSceneSynchronization: 0
|
||||||
|
SceneMigrationSynchronization: 0
|
||||||
|
SpawnWithObservers: 1
|
||||||
|
DontDestroyWithOwner: 0
|
||||||
|
AutoObjectParentSync: 1
|
||||||
|
SyncOwnerTransformWhenParented: 1
|
||||||
|
AllowOwnerToParent: 0
|
||||||
|
--- !u!54 &781174156
|
||||||
|
Rigidbody:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 781174154}
|
||||||
|
serializedVersion: 5
|
||||||
|
m_Mass: 1
|
||||||
|
m_LinearDamping: 0
|
||||||
|
m_AngularDamping: 0.05
|
||||||
|
m_CenterOfMass: {x: 0, y: 0, z: 0}
|
||||||
|
m_InertiaTensor: {x: 1, y: 1, z: 1}
|
||||||
|
m_InertiaRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_IncludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_ExcludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_ImplicitCom: 1
|
||||||
|
m_ImplicitTensor: 1
|
||||||
|
m_UseGravity: 1
|
||||||
|
m_IsKinematic: 0
|
||||||
|
m_Interpolate: 0
|
||||||
|
m_Constraints: 0
|
||||||
|
m_CollisionDetection: 0
|
||||||
|
--- !u!23 &781174157
|
||||||
|
MeshRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 781174154}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_CastShadows: 1
|
||||||
|
m_ReceiveShadows: 1
|
||||||
|
m_DynamicOccludee: 1
|
||||||
|
m_StaticShadowCaster: 0
|
||||||
|
m_MotionVectors: 1
|
||||||
|
m_LightProbeUsage: 1
|
||||||
|
m_ReflectionProbeUsage: 1
|
||||||
|
m_RayTracingMode: 2
|
||||||
|
m_RayTraceProcedural: 0
|
||||||
|
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||||
|
m_RayTracingAccelStructBuildFlags: 1
|
||||||
|
m_SmallMeshCulling: 1
|
||||||
|
m_ForceMeshLod: -1
|
||||||
|
m_MeshLodSelectionBias: 0
|
||||||
|
m_RenderingLayerMask: 1
|
||||||
|
m_RendererPriority: 0
|
||||||
|
m_Materials:
|
||||||
|
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, 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: 1
|
||||||
|
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_GlobalIlluminationMeshLod: 0
|
||||||
|
m_SortingLayerID: 0
|
||||||
|
m_SortingLayer: 0
|
||||||
|
m_SortingOrder: 0
|
||||||
|
m_MaskInteraction: 0
|
||||||
|
m_AdditionalVertexStreams: {fileID: 0}
|
||||||
|
--- !u!65 &781174158
|
||||||
|
BoxCollider:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 781174154}
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_IncludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_ExcludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_LayerOverridePriority: 0
|
||||||
|
m_IsTrigger: 0
|
||||||
|
m_ProvidesContacts: 0
|
||||||
|
m_Enabled: 1
|
||||||
|
serializedVersion: 3
|
||||||
|
m_Size: {x: 1, y: 1, z: 1}
|
||||||
|
m_Center: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!33 &781174159
|
||||||
|
MeshFilter:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 781174154}
|
||||||
|
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
--- !u!4 &781174160
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 781174154}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 1, z: 0}
|
||||||
|
m_LocalScale: {x: 2, y: 1, z: 3}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children:
|
||||||
|
- {fileID: 1147751743}
|
||||||
|
- {fileID: 1190106215}
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!114 &781174161
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 781174154}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 3dd8122938f14804f9e4ef7e494fb2f6, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Assembly-CSharp::Jankenbots.Prototype.SeatManager
|
||||||
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
|
--- !u!114 &781174162
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 781174154}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: f6c0be61502bb534f922ebb746851216, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.Components.NetworkRigidbody
|
||||||
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
|
NetworkRigidbodyBaseExpanded: 0
|
||||||
|
UseRigidBodyForMotion: 0
|
||||||
|
AutoUpdateKinematicState: 1
|
||||||
|
AutoSetKinematicOnDespawn: 1
|
||||||
|
--- !u!114 &781174163
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 781174154}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: e96cb6065543e43c4a752faaa1468eb1, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.Components.NetworkTransform
|
||||||
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
|
NetworkTransformExpanded: 0
|
||||||
|
AutoOwnerAuthorityTickOffset: 1
|
||||||
|
PositionInterpolationType: 0
|
||||||
|
RotationInterpolationType: 0
|
||||||
|
ScaleInterpolationType: 0
|
||||||
|
PositionLerpSmoothing: 1
|
||||||
|
PositionMaxInterpolationTime: 0.1
|
||||||
|
RotationLerpSmoothing: 1
|
||||||
|
RotationMaxInterpolationTime: 0.1
|
||||||
|
ScaleLerpSmoothing: 1
|
||||||
|
ScaleMaxInterpolationTime: 0.1
|
||||||
|
AuthorityMode: 0
|
||||||
|
TickSyncChildren: 0
|
||||||
|
UseUnreliableDeltas: 0
|
||||||
|
SyncPositionX: 1
|
||||||
|
SyncPositionY: 1
|
||||||
|
SyncPositionZ: 1
|
||||||
|
SyncRotAngleX: 1
|
||||||
|
SyncRotAngleY: 1
|
||||||
|
SyncRotAngleZ: 1
|
||||||
|
SyncScaleX: 1
|
||||||
|
SyncScaleY: 1
|
||||||
|
SyncScaleZ: 1
|
||||||
|
PositionThreshold: 0.001
|
||||||
|
RotAngleThreshold: 0.01
|
||||||
|
ScaleThreshold: 0.01
|
||||||
|
UseQuaternionSynchronization: 0
|
||||||
|
UseQuaternionCompression: 0
|
||||||
|
UseHalfFloatPrecision: 0
|
||||||
|
InLocalSpace: 0
|
||||||
|
SwitchTransformSpaceWhenParented: 0
|
||||||
|
Interpolate: 1
|
||||||
|
SlerpPosition: 0
|
||||||
|
--- !u!1 &1147751742
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 1147751743}
|
||||||
|
- component: {fileID: 1147751747}
|
||||||
|
- component: {fileID: 1147751746}
|
||||||
|
- component: {fileID: 1147751745}
|
||||||
|
- component: {fileID: 1147751744}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Tread_L
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!4 &1147751743
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1147751742}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
|
m_LocalPosition: {x: -0.6, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 0.2, y: 0.7, z: 0.9}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 781174160}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!114 &1147751744
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1147751742}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: a07e6377a09865c4e8c2b59a637e7dd1, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Assembly-CSharp::Jankenbots.Prototype.TreadPart
|
||||||
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
|
side: 0
|
||||||
|
chassis: {fileID: 0}
|
||||||
|
cruiseForce: 1200
|
||||||
|
throttleDeadzone: 0.08
|
||||||
|
pivotAnchorStrength: 2500
|
||||||
|
pivotAngularResistance: 400
|
||||||
|
leanTorque: 800
|
||||||
|
leanDeadzone: 0.08
|
||||||
|
--- !u!23 &1147751745
|
||||||
|
MeshRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1147751742}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_CastShadows: 1
|
||||||
|
m_ReceiveShadows: 1
|
||||||
|
m_DynamicOccludee: 1
|
||||||
|
m_StaticShadowCaster: 0
|
||||||
|
m_MotionVectors: 1
|
||||||
|
m_LightProbeUsage: 1
|
||||||
|
m_ReflectionProbeUsage: 1
|
||||||
|
m_RayTracingMode: 2
|
||||||
|
m_RayTraceProcedural: 0
|
||||||
|
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||||
|
m_RayTracingAccelStructBuildFlags: 1
|
||||||
|
m_SmallMeshCulling: 1
|
||||||
|
m_ForceMeshLod: -1
|
||||||
|
m_MeshLodSelectionBias: 0
|
||||||
|
m_RenderingLayerMask: 1
|
||||||
|
m_RendererPriority: 0
|
||||||
|
m_Materials:
|
||||||
|
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, 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: 1
|
||||||
|
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_GlobalIlluminationMeshLod: 0
|
||||||
|
m_SortingLayerID: 0
|
||||||
|
m_SortingLayer: 0
|
||||||
|
m_SortingOrder: 0
|
||||||
|
m_MaskInteraction: 0
|
||||||
|
m_AdditionalVertexStreams: {fileID: 0}
|
||||||
|
--- !u!65 &1147751746
|
||||||
|
BoxCollider:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1147751742}
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_IncludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_ExcludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_LayerOverridePriority: 0
|
||||||
|
m_IsTrigger: 0
|
||||||
|
m_ProvidesContacts: 0
|
||||||
|
m_Enabled: 1
|
||||||
|
serializedVersion: 3
|
||||||
|
m_Size: {x: 1, y: 1, z: 1}
|
||||||
|
m_Center: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!33 &1147751747
|
||||||
|
MeshFilter:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1147751742}
|
||||||
|
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
--- !u!1 &1190106214
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 1190106215}
|
||||||
|
- component: {fileID: 1190106219}
|
||||||
|
- component: {fileID: 1190106218}
|
||||||
|
- component: {fileID: 1190106217}
|
||||||
|
- component: {fileID: 1190106216}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Tread_R
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!4 &1190106215
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1190106214}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0.6, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 0.2, y: 0.7, z: 0.9}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 781174160}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!114 &1190106216
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1190106214}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: a07e6377a09865c4e8c2b59a637e7dd1, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Assembly-CSharp::Jankenbots.Prototype.TreadPart
|
||||||
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
|
side: 1
|
||||||
|
chassis: {fileID: 0}
|
||||||
|
cruiseForce: 1200
|
||||||
|
throttleDeadzone: 0.08
|
||||||
|
pivotAnchorStrength: 2500
|
||||||
|
pivotAngularResistance: 400
|
||||||
|
leanTorque: 800
|
||||||
|
leanDeadzone: 0.08
|
||||||
|
--- !u!23 &1190106217
|
||||||
|
MeshRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1190106214}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_CastShadows: 1
|
||||||
|
m_ReceiveShadows: 1
|
||||||
|
m_DynamicOccludee: 1
|
||||||
|
m_StaticShadowCaster: 0
|
||||||
|
m_MotionVectors: 1
|
||||||
|
m_LightProbeUsage: 1
|
||||||
|
m_ReflectionProbeUsage: 1
|
||||||
|
m_RayTracingMode: 2
|
||||||
|
m_RayTraceProcedural: 0
|
||||||
|
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||||
|
m_RayTracingAccelStructBuildFlags: 1
|
||||||
|
m_SmallMeshCulling: 1
|
||||||
|
m_ForceMeshLod: -1
|
||||||
|
m_MeshLodSelectionBias: 0
|
||||||
|
m_RenderingLayerMask: 1
|
||||||
|
m_RendererPriority: 0
|
||||||
|
m_Materials:
|
||||||
|
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, 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: 1
|
||||||
|
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_GlobalIlluminationMeshLod: 0
|
||||||
|
m_SortingLayerID: 0
|
||||||
|
m_SortingLayer: 0
|
||||||
|
m_SortingOrder: 0
|
||||||
|
m_MaskInteraction: 0
|
||||||
|
m_AdditionalVertexStreams: {fileID: 0}
|
||||||
|
--- !u!65 &1190106218
|
||||||
|
BoxCollider:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1190106214}
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_IncludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_ExcludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_LayerOverridePriority: 0
|
||||||
|
m_IsTrigger: 0
|
||||||
|
m_ProvidesContacts: 0
|
||||||
|
m_Enabled: 1
|
||||||
|
serializedVersion: 3
|
||||||
|
m_Size: {x: 1, y: 1, z: 1}
|
||||||
|
m_Center: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!33 &1190106219
|
||||||
|
MeshFilter:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1190106214}
|
||||||
|
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
--- !u!1 &1336178141
|
--- !u!1 &1336178141
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
|
@ -580,3 +1096,4 @@ SceneRoots:
|
||||||
- {fileID: 562137770}
|
- {fileID: 562137770}
|
||||||
- {fileID: 1336178145}
|
- {fileID: 1336178145}
|
||||||
- {fileID: 1571629442}
|
- {fileID: 1571629442}
|
||||||
|
- {fileID: 781174160}
|
||||||
|
|
|
||||||
|
|
@ -161,19 +161,21 @@ namespace Jankenbots.Prototype
|
||||||
Seat? mine = LocalSeat();
|
Seat? mine = LocalSeat();
|
||||||
string held = mine.HasValue ? mine.Value.ToString() : "none (spectator)";
|
string held = mine.HasValue ? mine.Value.ToString() : "none (spectator)";
|
||||||
|
|
||||||
const int w = 260, h = 78;
|
// Sits BELOW the NetworkBootstrap connectivity panel (which occupies the
|
||||||
GUI.Box(new Rect(10, 10, w, h), "JANKENBOTS · seat");
|
// top-left ~260px) so the Claim button isn't hidden behind it.
|
||||||
GUI.Label(new Rect(20, 32, w - 20, 20), $"You hold: {held}");
|
const int x = 10, y = 300, w = 260, h = 78;
|
||||||
GUI.Label(new Rect(20, 52, w - 20, 20),
|
GUI.Box(new Rect(x, y, w, h), "JANKENBOTS · seat");
|
||||||
|
GUI.Label(new Rect(x + 10, y + 22, w - 20, 20), $"You hold: {held}");
|
||||||
|
GUI.Label(new Rect(x + 10, y + 42, w - 20, 20),
|
||||||
$"Left: {SeatLabel(LeftPilot.Value)} Right: {SeatLabel(RightPilot.Value)}");
|
$"Left: {SeatLabel(LeftPilot.Value)} Right: {SeatLabel(RightPilot.Value)}");
|
||||||
|
|
||||||
// Client-side ask/release buttons (they just fire the ServerRpc).
|
// Client-side ask/release buttons (they just fire the ServerRpc).
|
||||||
if (!mine.HasValue)
|
if (!mine.HasValue)
|
||||||
{
|
{
|
||||||
if (GUI.Button(new Rect(w - 70, 30, 55, 20), "Claim"))
|
if (GUI.Button(new Rect(x + w - 70, y + 20, 55, 20), "Claim"))
|
||||||
ClaimSeatRpc();
|
ClaimSeatRpc();
|
||||||
}
|
}
|
||||||
else if (GUI.Button(new Rect(w - 70, 30, 55, 20), "Leave"))
|
else if (GUI.Button(new Rect(x + w - 70, y + 20, 55, 20), "Leave"))
|
||||||
{
|
{
|
||||||
ReleaseSeatRpc();
|
ReleaseSeatRpc();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using Unity.Netcode;
|
using Unity.Netcode;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.InputSystem;
|
||||||
|
|
||||||
namespace Jankenbots.Prototype
|
namespace Jankenbots.Prototype
|
||||||
{
|
{
|
||||||
|
|
@ -11,31 +12,28 @@ namespace Jankenbots.Prototype
|
||||||
/// clumsy body and try to make it go where they want together?
|
/// clumsy body and try to make it go where they want together?
|
||||||
///
|
///
|
||||||
/// ARCHITECTURE (host-authoritative, no prediction — see the M1 cheat-sheet):
|
/// ARCHITECTURE (host-authoritative, no prediction — see the M1 cheat-sheet):
|
||||||
/// * There is ONE simulated Rigidbody: the CHASSIS. Both treads are just
|
/// * There is ONE simulated Rigidbody + ONE NetworkObject: the CHASSIS. Both
|
||||||
/// force-emitters that push on that shared body. No tread has its own
|
/// treads are plain CHILD GameObjects (no Rigidbody, no NetworkObject of their
|
||||||
/// Rigidbody. Differential drive "emerges" because the two treads apply
|
/// own); each TreadPart is a NetworkBehaviour bound to the chassis' shared
|
||||||
/// their forces at different WORLD POSITIONS (left vs right of centre).
|
/// NetworkObject. (NGO forbids nested NetworkObjects in a spawned prefab, so
|
||||||
/// * The owning pilot's client only READS input and ships {throttle, pivotHeld,
|
/// per-tread ownership is impossible — we gate on the SEAT instead.)
|
||||||
/// lean} to the host via an RPC. It applies NOTHING locally.
|
/// * Differential drive "emerges" because the two treads apply their forces at
|
||||||
/// * The host (IsServer) caches the latest input per tread and applies ALL
|
/// different WORLD POSITIONS (left vs right of centre).
|
||||||
/// forces in FixedUpdate. NGO's NetworkRigidbody/NetworkTransform on the
|
/// * SEAT-BASED input gating (via <see cref="SeatManager"/>): a client only reads
|
||||||
|
/// & sends input for a tread whose seat it currently holds. The host re-checks
|
||||||
|
/// seat ownership before honoring any input RPC, so a client can't drive a tread
|
||||||
|
/// it doesn't pilot.
|
||||||
|
/// * The host (IsServer) caches the latest validated input per tread and applies
|
||||||
|
/// ALL forces in FixedUpdate. NGO's NetworkRigidbody/NetworkTransform on the
|
||||||
/// chassis replicates the resulting motion back to everyone.
|
/// chassis replicates the resulting motion back to everyone.
|
||||||
///
|
///
|
||||||
/// FEEL MODEL v0.1 — three deliberately "janky" verbs, each a literal force:
|
/// FEEL MODEL v0.1 — three deliberately "janky" verbs, each a literal force:
|
||||||
/// (a) SNAP-TO-CRUISE THROTTLE — a tread does not have an analog gas pedal.
|
/// (a) SNAP-TO-CRUISE THROTTLE — slam forward → a fixed CRUISE force; partial is
|
||||||
/// Push the stick fully forward and it commits to a fixed CRUISE force
|
/// proportional but the intent is over-commitment (that's the comedy).
|
||||||
/// (chunky, momentum-y, a bit out of your hands). Partial stick is
|
/// (b) LOCK-PIVOT — hold a button and THIS tread plants as an anchor; the bot
|
||||||
/// proportional so you CAN feather it, but the intent is "slam it to
|
/// swings about it like a pinned foot.
|
||||||
/// cruise and live with the consequences" — that shared over-commitment
|
/// (c) PASSIVE LEAN — sideways stick dumps a ballast torque to counter-roll the
|
||||||
/// is where the comedy of coordination comes from.
|
/// top-heavy bot; a constant shared balancing chore.
|
||||||
/// (b) LOCK-PIVOT — hold a button and THIS tread plants itself as an anchor.
|
|
||||||
/// Its drive contribution is cancelled and we actively fight the chassis'
|
|
||||||
/// motion AT the tread's position, so the whole bot swings/rotates about
|
|
||||||
/// this tread like a pinned foot. Two pilots learn "you plant, I drive"
|
|
||||||
/// to turn on the spot.
|
|
||||||
/// (c) PASSIVE LEAN — this tall bot WANTS to tip over. Nudging the stick
|
|
||||||
/// sideways dumps a ballast torque along the drive axis to counter-roll,
|
|
||||||
/// a constant low-key balancing chore shared between pilots.
|
|
||||||
///
|
///
|
||||||
/// Tuning fields are public so we can dial the feel live in the inspector while
|
/// Tuning fields are public so we can dial the feel live in the inspector while
|
||||||
/// friends are playing. Numbers here are only sane starting points.
|
/// friends are playing. Numbers here are only sane starting points.
|
||||||
|
|
@ -43,14 +41,13 @@ namespace Jankenbots.Prototype
|
||||||
[DisallowMultipleComponent]
|
[DisallowMultipleComponent]
|
||||||
public class TreadPart : NetworkBehaviour
|
public class TreadPart : NetworkBehaviour
|
||||||
{
|
{
|
||||||
// Which side of the bot this tread is. Purely descriptive for M1 (the actual
|
// Which side of the bot this tread is. Drives the SEAT it maps to (Left/Right)
|
||||||
// left/right behaviour comes from where the tread SITS on the chassis, not
|
// AND, because the tread physically SITS left/right of centre, the differential
|
||||||
// from this enum) — but it's handy for seat-assignment logs and inspector
|
// drive. Set per-tread in the inspector.
|
||||||
// sanity, and lets us bias per-side tuning later if we want asymmetry.
|
|
||||||
public enum Side { Left, Right }
|
public enum Side { Left, Right }
|
||||||
|
|
||||||
[Header("Identity")]
|
[Header("Identity")]
|
||||||
[Tooltip("Which tread this is. Descriptive — real behaviour comes from world position on the chassis.")]
|
[Tooltip("Which tread this is → which SeatManager seat it maps to, and (via its world position) which side of the chassis it pushes.")]
|
||||||
public Side side = Side.Left;
|
public Side side = Side.Left;
|
||||||
|
|
||||||
[Header("Shared body")]
|
[Header("Shared body")]
|
||||||
|
|
@ -59,75 +56,84 @@ namespace Jankenbots.Prototype
|
||||||
|
|
||||||
// ---- (a) SNAP-TO-CRUISE THROTTLE tuning ----------------------------------
|
// ---- (a) SNAP-TO-CRUISE THROTTLE tuning ----------------------------------
|
||||||
[Header("(a) Throttle — snap-to-cruise")]
|
[Header("(a) Throttle — snap-to-cruise")]
|
||||||
[Tooltip("Force (Newtons) applied at full-forward stick. This is the 'cruise' the tread snaps to. Bigger = the bot lurches harder and is twice as hard to coordinate.")]
|
[Tooltip("Force (Newtons) applied at full-forward stick. This is the 'cruise' the tread snaps to.")]
|
||||||
public float cruiseForce = 1200f;
|
public float cruiseForce = 1200f;
|
||||||
|
|
||||||
[Tooltip("Below this stick magnitude we treat throttle as zero — kills drift/noise so a resting stick doesn't creep the bot.")]
|
[Tooltip("Below this stick magnitude we treat throttle as zero.")]
|
||||||
[Range(0f, 0.5f)]
|
[Range(0f, 0.5f)]
|
||||||
public float throttleDeadzone = 0.08f;
|
public float throttleDeadzone = 0.08f;
|
||||||
|
|
||||||
// ---- (b) LOCK-PIVOT tuning -----------------------------------------------
|
// ---- (b) LOCK-PIVOT tuning -----------------------------------------------
|
||||||
[Header("(b) Lock-pivot — plant this tread as an anchor")]
|
[Header("(b) Lock-pivot — plant this tread as an anchor")]
|
||||||
[Tooltip("How hard the planted tread resists the chassis sliding at its position. Higher = a crisper, more locked pivot; too high = the whole bot snaps rigidly and feels un-janky.")]
|
[Tooltip("How hard the planted tread resists the chassis sliding at its position.")]
|
||||||
public float pivotAnchorStrength = 2500f;
|
public float pivotAnchorStrength = 2500f;
|
||||||
|
|
||||||
[Tooltip("Extra angular damping (torque opposing spin) while planting. Keeps the pivot from becoming a wild spin — the planted foot should feel 'stuck', not greasy.")]
|
[Tooltip("Extra angular damping (torque opposing spin) while planting.")]
|
||||||
public float pivotAngularResistance = 400f;
|
public float pivotAngularResistance = 400f;
|
||||||
|
|
||||||
// ---- (c) PASSIVE LEAN tuning ---------------------------------------------
|
// ---- (c) PASSIVE LEAN tuning ---------------------------------------------
|
||||||
[Header("(c) Passive lean — anti-tip ballast")]
|
[Header("(c) Passive lean — anti-tip ballast")]
|
||||||
[Tooltip("Ballast torque (N·m) at full sideways stick, applied along the drive (forward) axis to counter-roll the tall bot. Tune vs how tippy the chassis is.")]
|
[Tooltip("Ballast torque (N·m) at full sideways stick, applied along the drive (forward) axis.")]
|
||||||
public float leanTorque = 800f;
|
public float leanTorque = 800f;
|
||||||
|
|
||||||
[Tooltip("Below this sideways magnitude, no lean torque — resting stick = no ballast.")]
|
[Tooltip("Below this sideways magnitude, no lean torque.")]
|
||||||
[Range(0f, 0.5f)]
|
[Range(0f, 0.5f)]
|
||||||
public float leanDeadzone = 0.08f;
|
public float leanDeadzone = 0.08f;
|
||||||
|
|
||||||
|
// The seat this tread maps to, resolved once from `side`.
|
||||||
|
SeatManager.Seat MySeat => side == Side.Left ? SeatManager.Seat.Left : SeatManager.Seat.Right;
|
||||||
|
|
||||||
|
// Source of truth for who pilots which tread. Found on a parent (the chassis).
|
||||||
|
SeatManager _seats;
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
// HOST-SIDE cached input. These are written ONLY by the RPC (which only runs
|
// HOST-SIDE cached input. Written ONLY by the (validated) RPC on the server and
|
||||||
// on the server) and read ONLY in FixedUpdate (also gated to server). We never
|
// read ONLY in FixedUpdate (also server-gated). No sync primitive needed — the
|
||||||
// touch them on a non-owning client, so no sync primitive is needed — the
|
|
||||||
// authoritative simulation is entirely host-local.
|
// authoritative simulation is entirely host-local.
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
float _throttle; // 0..1, already deadzoned/clamped by the sender
|
float _throttle; // 0..1
|
||||||
bool _pivotHeld; // is the pilot planting this tread right now?
|
bool _pivotHeld;
|
||||||
float _lean; // -1..1 sideways ballast request
|
float _lean; // -1..1
|
||||||
|
|
||||||
void Awake()
|
void Awake()
|
||||||
{
|
{
|
||||||
// Convenience: if nobody wired the chassis in the inspector, grab the
|
|
||||||
// Rigidbody off a parent. All treads should end up pointing at the SAME
|
|
||||||
// chassis Rigidbody — that shared reference is what makes it one bot.
|
|
||||||
if (chassis == null)
|
if (chassis == null)
|
||||||
chassis = GetComponentInParent<Rigidbody>();
|
chassis = GetComponentInParent<Rigidbody>();
|
||||||
|
_seats = GetComponentInParent<SeatManager>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// CLIENT: read local input, ship it to the host. Nothing is applied locally.
|
// CLIENT: if the LOCAL player holds this tread's seat, read input and ship it
|
||||||
// Runs every frame on the owning pilot only.
|
// to the host. Nothing is applied locally. Seat ownership — not NetworkObject
|
||||||
|
// ownership — is the gate, so each client self-selects the tread it pilots.
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
void Update()
|
void Update()
|
||||||
{
|
{
|
||||||
if (!IsOwner) return;
|
if (!IsSpawned || _seats == null) return;
|
||||||
|
|
||||||
|
ulong me = NetworkManager.Singleton.LocalClientId;
|
||||||
|
if (_seats.PilotOf(MySeat) != me) return; // I don't drive this tread
|
||||||
|
|
||||||
float throttle = ReadThrottle(); // 0..1
|
float throttle = ReadThrottle(); // 0..1
|
||||||
bool pivot = ReadPivotHeld();
|
bool pivot = ReadPivotHeld();
|
||||||
float lean = ReadLean(); // -1..1
|
float lean = ReadLean(); // -1..1
|
||||||
|
|
||||||
// One tiny packet per frame to the host. The host simulates; we watch the
|
|
||||||
// replicated chassis move. That round-trip "lag between my stick and the
|
|
||||||
// bot lurching" is itself part of the janky feel we're testing.
|
|
||||||
SubmitTreadInputRpc(throttle, pivot, lean);
|
SubmitTreadInputRpc(throttle, pivot, lean);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Owning client → host. Named *Rpc + [Rpc(SendTo.Server)] per NGO 2.x.
|
/// Owning pilot's client → host. RequireOwnership is FALSE (the bot's
|
||||||
/// RequireOwnership stays true (default): only the pilot who owns this tread
|
/// NetworkObject is owned by the server, not the pilot) — instead the host
|
||||||
/// may drive it. The host just caches; forces are applied in FixedUpdate.
|
/// re-validates the SEAT so a client can only drive the tread it actually
|
||||||
|
/// pilots. The host just caches; forces are applied in FixedUpdate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Rpc(SendTo.Server)]
|
[Rpc(SendTo.Server, RequireOwnership = false)]
|
||||||
void SubmitTreadInputRpc(float throttle, bool pivotHeld, float lean, RpcParams _ = default)
|
void SubmitTreadInputRpc(float throttle, bool pivotHeld, float lean, RpcParams rpcParams = default)
|
||||||
{
|
{
|
||||||
|
// Anti-spoof: only honor input from the client that currently holds this seat.
|
||||||
|
if (_seats == null || !_seats.OwnsSeat(MySeat, rpcParams.Receive.SenderClientId))
|
||||||
|
return;
|
||||||
|
|
||||||
_throttle = Mathf.Clamp01(throttle);
|
_throttle = Mathf.Clamp01(throttle);
|
||||||
_pivotHeld = pivotHeld;
|
_pivotHeld = pivotHeld;
|
||||||
_lean = Mathf.Clamp(lean, -1f, 1f);
|
_lean = Mathf.Clamp(lean, -1f, 1f);
|
||||||
|
|
@ -135,47 +141,38 @@ namespace Jankenbots.Prototype
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// HOST ONLY: turn the cached input into literal forces on the SHARED chassis.
|
// HOST ONLY: turn the cached input into literal forces on the SHARED chassis.
|
||||||
// This is the entire physics of the bot. Two TreadParts running this in the
|
// Two TreadParts running this in the same FixedUpdate, pushing at their two
|
||||||
// same FixedUpdate, pushing at their two different world positions, ARE the
|
// different world positions, ARE the differential drive.
|
||||||
// differential drive.
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
void FixedUpdate()
|
void FixedUpdate()
|
||||||
{
|
{
|
||||||
if (!IsServer) return; // authority guard — clients never simulate
|
if (!IsServer) return; // authority guard — clients never simulate
|
||||||
if (chassis == null) return;
|
if (chassis == null) return;
|
||||||
|
|
||||||
|
// Un-piloted tread goes limp: no pilot → no cached force. (Also clears any
|
||||||
|
// stale input the instant a pilot leaves, so the bot doesn't coast on it.)
|
||||||
|
if (_seats == null || _seats.PilotOf(MySeat) == SeatManager.NoPilot)
|
||||||
|
{
|
||||||
|
_throttle = 0f; _pivotHeld = false; _lean = 0f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Vector3 treadPos = transform.position; // where THIS tread pushes from
|
Vector3 treadPos = transform.position; // where THIS tread pushes from
|
||||||
|
|
||||||
if (_pivotHeld)
|
if (_pivotHeld)
|
||||||
{
|
{
|
||||||
// ---- (b) LOCK-PIVOT --------------------------------------------
|
// ---- (b) LOCK-PIVOT --------------------------------------------
|
||||||
// The pilot has planted this tread. We do NOT drive with it; instead
|
// Plant this tread: cancel linear slip at its position and bleed spin
|
||||||
// we make the chassis behave as if it's pinned at this tread's
|
// so the OTHER tread's thrust swings the bot about this point.
|
||||||
// position, so the OTHER tread's thrust swings the whole bot around
|
|
||||||
// this point like a pivoting foot.
|
|
||||||
//
|
|
||||||
// 1) Cancel the sideways/linear slip AT the tread position by pushing
|
|
||||||
// back against the local velocity there. GetPointVelocity gives the
|
|
||||||
// chassis' velocity at this world point (includes rotation), so
|
|
||||||
// opposing it plants the point in space.
|
|
||||||
Vector3 pointVel = chassis.GetPointVelocity(treadPos);
|
Vector3 pointVel = chassis.GetPointVelocity(treadPos);
|
||||||
chassis.AddForceAtPosition(-pointVel * pivotAnchorStrength, treadPos, ForceMode.Force);
|
chassis.AddForceAtPosition(-pointVel * pivotAnchorStrength, treadPos, ForceMode.Force);
|
||||||
|
|
||||||
// 2) Bleed off raw spin so the pivot feels 'stuck', not greasy. This
|
|
||||||
// is a soft angular brake, NOT a hard lock — we still want jank.
|
|
||||||
chassis.AddTorque(-chassis.angularVelocity * pivotAngularResistance, ForceMode.Force);
|
chassis.AddTorque(-chassis.angularVelocity * pivotAngularResistance, ForceMode.Force);
|
||||||
|
return; // a planted tread is an anchor, not a motor
|
||||||
// NOTE: no throttle drive while planting — a planted tread is an
|
|
||||||
// anchor, not a motor. (Lean is also skipped: you're busy pivoting.)
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- (a) SNAP-TO-CRUISE THROTTLE -----------------------------------
|
// ---- (a) SNAP-TO-CRUISE THROTTLE -----------------------------------
|
||||||
// throttle is 0..1. Full stick == full cruiseForce (the "snap to cruise"
|
// Full stick == full cruiseForce; partial scales down. Applied AT the
|
||||||
// commitment); partial stick scales it down so feathering is possible but
|
// tread's world position along its own forward → left/right offset =
|
||||||
// not the point. Push along the tread's own forward so a mis-aligned /
|
|
||||||
// knocked-askew tread pushes the bot in a wonky direction — jank on
|
|
||||||
// purpose. Applied AT the tread's world position → left+right offset =
|
|
||||||
// differential drive (asymmetric throttle turns the bot).
|
// differential drive (asymmetric throttle turns the bot).
|
||||||
if (_throttle > throttleDeadzone)
|
if (_throttle > throttleDeadzone)
|
||||||
{
|
{
|
||||||
|
|
@ -184,9 +181,6 @@ namespace Jankenbots.Prototype
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- (c) PASSIVE LEAN — anti-tip ballast ---------------------------
|
// ---- (c) PASSIVE LEAN — anti-tip ballast ---------------------------
|
||||||
// Sideways stick shovels ballast torque along the drive (forward) axis to
|
|
||||||
// counter-roll the top-heavy bot. It's a constant balancing chore the
|
|
||||||
// pilots share; it does NOT steer (that's the throttle differential).
|
|
||||||
if (Mathf.Abs(_lean) > leanDeadzone)
|
if (Mathf.Abs(_lean) > leanDeadzone)
|
||||||
{
|
{
|
||||||
chassis.AddTorque(transform.forward * (_lean * leanTorque), ForceMode.Force);
|
chassis.AddTorque(transform.forward * (_lean * leanTorque), ForceMode.Force);
|
||||||
|
|
@ -194,57 +188,38 @@ namespace Jankenbots.Prototype
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// INPUT READERS — placeholder wiring for M1. Swap for real Input System
|
// INPUT READERS — new Input System (project is Input-System-New-only, so legacy
|
||||||
// actions once seats are assigned; kept trivial so the physics is testable
|
// UnityEngine.Input would throw). Same keys for both seats; the SEAT gate in
|
||||||
// immediately. Only ever called on the owning client (inside Update's guard).
|
// Update() decides which tread a given client actually drives. Only called for
|
||||||
|
// the tread the local player pilots. Swap for per-seat gamepad actions later.
|
||||||
|
// Throttle = W / Up · Pivot = Left Shift · Lean = A/D or Left/Right
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|
||||||
/// <summary>0..1 throttle. Vertical axis, forward only (no reverse in v0.1).</summary>
|
/// <summary>0..1 throttle. Forward only (no reverse in v0.1).</summary>
|
||||||
float ReadThrottle()
|
float ReadThrottle()
|
||||||
{
|
{
|
||||||
// Forward-only: negative stick = 0 throttle (reverse is a later feel test).
|
var kb = Keyboard.current;
|
||||||
float v = Mathf.Max(0f, Input.GetAxisRaw("Vertical"));
|
if (kb == null) return 0f;
|
||||||
|
float v = (kb.wKey.isPressed || kb.upArrowKey.isPressed) ? 1f : 0f;
|
||||||
return v < throttleDeadzone ? 0f : v;
|
return v < throttleDeadzone ? 0f : v;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Is the plant-pivot button held?</summary>
|
/// <summary>Is the plant-pivot button held?</summary>
|
||||||
bool ReadPivotHeld()
|
bool ReadPivotHeld()
|
||||||
{
|
{
|
||||||
// Placeholder: left shift = plant. Real build: per-seat gamepad button.
|
var kb = Keyboard.current;
|
||||||
return Input.GetKey(KeyCode.LeftShift);
|
return kb != null && (kb.leftShiftKey.isPressed || kb.rightShiftKey.isPressed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>-1..1 sideways ballast request.</summary>
|
/// <summary>-1..1 sideways ballast request.</summary>
|
||||||
float ReadLean()
|
float ReadLean()
|
||||||
{
|
{
|
||||||
float h = Input.GetAxisRaw("Horizontal");
|
var kb = Keyboard.current;
|
||||||
|
if (kb == null) return 0f;
|
||||||
|
float h = 0f;
|
||||||
|
if (kb.aKey.isPressed || kb.leftArrowKey.isPressed) h -= 1f;
|
||||||
|
if (kb.dKey.isPressed || kb.rightArrowKey.isPressed) h += 1f;
|
||||||
return Mathf.Abs(h) < leanDeadzone ? 0f : h;
|
return Mathf.Abs(h) < leanDeadzone ? 0f : h;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================================================
|
|
||||||
// Seat lifecycle. The bot spawns owned by the server; the seat manager grants
|
|
||||||
// a tread to a pilot via NetworkObject.ChangeOwnership(clientId) (server-only,
|
|
||||||
// see cheat-sheet §8). These hooks just log so we can see claims land while
|
|
||||||
// testing with friends, and clear stale input if a pilot leaves.
|
|
||||||
// ==========================================================================
|
|
||||||
public override void OnGainedOwnership()
|
|
||||||
{
|
|
||||||
base.OnGainedOwnership();
|
|
||||||
if (IsOwner)
|
|
||||||
Debug.Log($"[TreadPart] {side} tread claimed by local pilot (client {OwnerClientId}).");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnLostOwnership()
|
|
||||||
{
|
|
||||||
base.OnLostOwnership();
|
|
||||||
// On the host, wipe cached input so an un-piloted tread goes limp instead
|
|
||||||
// of coasting on the last pilot's stick.
|
|
||||||
if (IsServer)
|
|
||||||
{
|
|
||||||
_throttle = 0f;
|
|
||||||
_pivotHeld = false;
|
|
||||||
_lean = 0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ Durable memory for this project. Read at session start, update before session en
|
||||||
## Open questions / TODOs
|
## Open questions / TODOs
|
||||||
|
|
||||||
- [ ] **▶ M1 IN PROGRESS** (see `docs/prototype-plan.md`). Done so far (2026-07-10, all compiling clean + pushed): **packages** (NGO 2.13.0 + `com.unity.services.multiplayer` 2.2.4 — the *unified* SDK that supersedes the deprecated standalone relay/lobby packages; pulls transport 6.5.0 + core/auth), **4 scripts** in `Assets/Scripts/{Net,Bot}/`, **scene** `Assets/Scenes/M1_Arena.unity` (NetworkManager+UnityTransport+NetworkBootstrap, ground/cam/light, set as build scene), **replicated Player capsule prefab** (`Assets/Prefabs/Player.prefab`, registered as `NetworkConfig.PlayerPrefab`).
|
- [ ] **▶ M1 IN PROGRESS** (see `docs/prototype-plan.md`). Done so far (2026-07-10, all compiling clean + pushed): **packages** (NGO 2.13.0 + `com.unity.services.multiplayer` 2.2.4 — the *unified* SDK that supersedes the deprecated standalone relay/lobby packages; pulls transport 6.5.0 + core/auth), **4 scripts** in `Assets/Scripts/{Net,Bot}/`, **scene** `Assets/Scenes/M1_Arena.unity` (NetworkManager+UnityTransport+NetworkBootstrap, ground/cam/light, set as build scene), **replicated Player capsule prefab** (`Assets/Prefabs/Player.prefab`, registered as `NetworkConfig.PlayerPrefab`).
|
||||||
|
- **✅ CAPSULE REPLICATION VERIFIED (2026-07-11) — M1 netcode thesis proven.** Two clients over Relay (host = original editor via MCP, client = ParrelSync clone), both capsules spawned + drove independently + replicated **bidirectionally, smooth**. The `input→ServerRpc→host-physics→NetworkTransform` loop works over real Relay. **ParrelSync 1.5.2** installed for local 2-client testing (clone at `../jankenbots_clone_0`, symlinks Assets+ProjectSettings; open it via `ClonesManager.OpenProject` or the ParrelSync menu; its MCP loses the port fight so only the original is MCP-controllable).
|
||||||
|
- **✅ SHARED BOT BUILT (2026-07-11), solo-smoke-verified; awaiting 2-pilot drive test.** In-scene `Bot` NetworkObject in M1_Arena: chassis Cube (Rigidbody 60kg + NetworkObject + NetworkTransform + NetworkRigidbody + SeatManager) with two child treads `Tread_L`/`Tread_R` (TreadPart, side Left/Right) at world ±1.2 X. In-scene bot auto-spawns on host; seat claim + gating verified solo. **DESIGN FIX (important):** the two drafts were mis-wired — TreadPart gated on NetworkObject `IsOwner` (needs per-tread ownership → impossible, NGO forbids nested NetworkObjects in a spawned prefab) while SeatManager's `OwnsSeat/PilotOf` API sat unused. Rewrote TreadPart to **seat-based gating**: both treads share the chassis NetworkObject; a client drives the tread whose seat it holds (`PilotOf(side)==LocalClientId`), host re-validates `OwnsSeat` on the RequireOwnership=false RPC. Also swapped TreadPart to `Keyboard.current` (New-only input). Controls: **W/↑ throttle · Shift pivot · A/D lean**. Moved SeatManager OnGUI below the bootstrap panel (was overlapping).
|
||||||
- **✅ UGS GATE CLEARED + HOST PATH VERIFIED (2026-07-10).** Cloud project **jankenbots** (`cloudProjectId a0d222ae-c79b-4120-bc0d-947bfd7600cb`, org `megaproxy`) is linked (has been in ProjectSettings since project creation) AND **Relay is now activated in the dashboard** (the missing step — done via cloud.unity.com ▸ Development ▸ Products ▸ Relay; there is NO enable toggle, opening the product page activates it). Verified by an in-editor Play-mode smoke test: `UnityServices.State=Initialized`, anonymous auth signed in, `HostGame()` created a real Relay allocation + join code, host's Player capsule auto-spawned (`IsSpawned`, owner=0). **The host half of M1-step-2 works end-to-end.** Still unverified: a *remote client joining* + peer-to-peer replication (needs a 2nd instance — standalone build via `manage_build` or a ParrelSync clone).
|
- **✅ UGS GATE CLEARED + HOST PATH VERIFIED (2026-07-10).** Cloud project **jankenbots** (`cloudProjectId a0d222ae-c79b-4120-bc0d-947bfd7600cb`, org `megaproxy`) is linked (has been in ProjectSettings since project creation) AND **Relay is now activated in the dashboard** (the missing step — done via cloud.unity.com ▸ Development ▸ Products ▸ Relay; there is NO enable toggle, opening the product page activates it). Verified by an in-editor Play-mode smoke test: `UnityServices.State=Initialized`, anonymous auth signed in, `HostGame()` created a real Relay allocation + join code, host's Player capsule auto-spawned (`IsSpawned`, owner=0). **The host half of M1-step-2 works end-to-end.** Still unverified: a *remote client joining* + peer-to-peer replication (needs a 2nd instance — standalone build via `manage_build` or a ParrelSync clone).
|
||||||
- **Next build steps:** (5) shared bot = chassis Rigidbody + 2 `TreadPart` treads + `SeatManager` (⚠ `TreadPart` still reads legacy `Input.GetAxis` — swap to `Keyboard.current` like PlayerDriver was, project is Input-System-**New-only**); (6) shove ball + follow cam; (7) 2-friends feel test over Relay (needs a standalone build via `manage_build` or a ParrelSync clone for the 2nd client).
|
- **Next build steps:** (5) shared bot = chassis Rigidbody + 2 `TreadPart` treads + `SeatManager` (⚠ `TreadPart` still reads legacy `Input.GetAxis` — swap to `Keyboard.current` like PlayerDriver was, project is Input-System-**New-only**); (6) shove ball + follow cam; (7) 2-friends feel test over Relay (needs a standalone build via `manage_build` or a ParrelSync clone for the 2nd client).
|
||||||
- **Verified Relay API gotcha (baked into NetworkBootstrap):** with the unified SDK, `new RelayServerData(allocation, "dtls")` does NOT exist — use `AllocationUtils.ToRelayServerData(allocation, "dtls")` (`AllocationUtils` is in `Unity.Services.Relay.Models`). Corrected via live editor reflection.
|
- **Verified Relay API gotcha (baked into NetworkBootstrap):** with the unified SDK, `new RelayServerData(allocation, "dtls")` does NOT exist — use `AllocationUtils.ToRelayServerData(allocation, "dtls")` (`AllocationUtils` is in `Unity.Services.Relay.Models`). Corrected via live editor reflection.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue