内容提交
This commit is contained in:
8
Assets/Gaia User Data/Sessions.meta
Normal file
8
Assets/Gaia User Data/Sessions.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d3a120cd69656f468963a7b399a2f3b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
35
Assets/Gaia User Data/Sessions/GS-20260228 - 171840.asset
Normal file
35
Assets/Gaia User Data/Sessions/GS-20260228 - 171840.asset
Normal file
@@ -0,0 +1,35 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: e251860fcd970734c9f14ebd98a146c3, type: 3}
|
||||
m_Name: GS-20260228 - 171840
|
||||
m_EditorClassIdentifier: GaiaCore::Gaia.GaiaSession
|
||||
m_name: Session 20260228-171840
|
||||
m_description: 'Rocking out at Creativity Central!
|
||||
|
||||
|
||||
If you like Gaia please
|
||||
consider rating it :)'
|
||||
m_previewImage: {fileID: 0}
|
||||
m_dateCreated: 2026/2/28 17:18:40
|
||||
m_terrainWidth: 1024
|
||||
m_terrainDepth: 1024
|
||||
m_terrainHeight: 1024
|
||||
m_seaLevel: 25
|
||||
m_spawnDensity: 0.8
|
||||
m_isLocked: 0
|
||||
m_previewImageBytes:
|
||||
m_previewImageWidth: 0
|
||||
m_previewImageHeight: 0
|
||||
m_operations: []
|
||||
m_terrainMinMaxCache: []
|
||||
m_bakedMaskCacheEntries: []
|
||||
m_worldBiomeMaskSettings: {fileID: 0}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8eda7ecde0aa9d4994993b532ba21ae
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Gaia User Data/Sessions/GS-20260228 - 171840.meta
Normal file
8
Assets/Gaia User Data/Sessions/GS-20260228 - 171840.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 743e8db08f1a57f49b2057d04785e337
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,63 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!850595691 &4890085278179872738
|
||||
LightingSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: Gaia Lighting Settings
|
||||
serializedVersion: 9
|
||||
m_EnableBakedLightmaps: 0
|
||||
m_EnableRealtimeLightmaps: 0
|
||||
m_RealtimeEnvironmentLighting: 1
|
||||
m_BounceScale: 1
|
||||
m_AlbedoBoost: 1
|
||||
m_IndirectOutputScale: 1
|
||||
m_UsingShadowmask: 1
|
||||
m_BakeBackend: 1
|
||||
m_LightmapMaxSize: 1024
|
||||
m_LightmapSizeFixed: 0
|
||||
m_UseMipmapLimits: 1
|
||||
m_BakeResolution: 40
|
||||
m_Padding: 2
|
||||
m_LightmapCompression: 3
|
||||
m_AO: 0
|
||||
m_AOMaxDistance: 1
|
||||
m_CompAOExponent: 1
|
||||
m_CompAOExponentDirect: 0
|
||||
m_ExtractAO: 0
|
||||
m_MixedBakeMode: 2
|
||||
m_LightmapsBakeMode: 1
|
||||
m_FilterMode: 1
|
||||
m_LightmapParameters: {fileID: 15204, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_ExportTrainingData: 0
|
||||
m_EnableWorkerProcessBaking: 1
|
||||
m_TrainingDataDestination: TrainingData
|
||||
m_RealtimeResolution: 2
|
||||
m_ForceWhiteAlbedo: 0
|
||||
m_ForceUpdates: 0
|
||||
m_PVRCulling: 1
|
||||
m_PVRSampling: 1
|
||||
m_PVRDirectSampleCount: 32
|
||||
m_PVRSampleCount: 512
|
||||
m_PVREnvironmentSampleCount: 256
|
||||
m_PVREnvironmentReferencePointCount: 2048
|
||||
m_LightProbeSampleCountMultiplier: 4
|
||||
m_PVRBounces: 2
|
||||
m_PVRMinBounces: 2
|
||||
m_PVREnvironmentImportanceSampling: 1
|
||||
m_PVRFilteringMode: 1
|
||||
m_PVRDenoiserTypeDirect: 1
|
||||
m_PVRDenoiserTypeIndirect: 1
|
||||
m_PVRDenoiserTypeAO: 1
|
||||
m_PVRFilterTypeDirect: 0
|
||||
m_PVRFilterTypeIndirect: 0
|
||||
m_PVRFilterTypeAO: 0
|
||||
m_PVRFilteringGaussRadiusDirect: 1
|
||||
m_PVRFilteringGaussRadiusIndirect: 1
|
||||
m_PVRFilteringGaussRadiusAO: 1
|
||||
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||
m_RespectSceneVisibilityWhenBakingGI: 0
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f2a358d6ae67644f8c199d940dadd27
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 4890085278179872738
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f56f90b57af5a9448a3fb8ca3f1f2a6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,38 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: da50733ca13d24f45a2980dcd5a56ed5, type: 3}
|
||||
m_Name: TerrainScenes
|
||||
m_EditorClassIdentifier: GaiaCore::Gaia.TerrainSceneStorage
|
||||
m_terrainLoadingEnabled: 1
|
||||
m_showTerrainLoadingDisabledWarning: 1
|
||||
m_useAddressables: 0
|
||||
m_preloadAddressablesWithImpostors: 1
|
||||
m_colliderOnlyLoading: 0
|
||||
m_terrainTilesX: 1
|
||||
m_terrainTilesZ: 1
|
||||
m_terrainTilesSize: 0
|
||||
m_useFloatingPointFix: 0
|
||||
m_hasWorldMap: 0
|
||||
m_worldMaprelativeSize: 0.5
|
||||
m_worldMapRelativeHeightmapPixels: 1
|
||||
m_worldMapPreviewHeightmapResolution: 2049
|
||||
m_worldMapPreviewRange: 1024
|
||||
m_worldMapPreviewTerrainHeight: 1024
|
||||
m_terrainScenes: []
|
||||
m_deactivateRuntimePlayer: 0
|
||||
m_deactivateRuntimeLighting: 0
|
||||
m_deactivateRuntimeAudio: 0
|
||||
m_deactivateRuntimeWeather: 0
|
||||
m_deactivateRuntimeWater: 0
|
||||
m_deactivateRuntimeScreenShotter: 0
|
||||
m_pos00X: -1.7976931348623157e+308
|
||||
m_pos00Z: -1.7976931348623157e+308
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e2428c1e05a106448aeb4c7e59def5d
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Gaia User Data/Stamps.meta
Normal file
8
Assets/Gaia User Data/Stamps.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed3d8d94bfd55054cbdd0c355641f6ee
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -26,7 +26,7 @@ MonoBehaviour:
|
||||
m_unloadTerrainScenes: 0
|
||||
m_floatingPointFix: 0
|
||||
m_targeSizePreset: 3
|
||||
m_creationWorkflow: 2
|
||||
m_creationWorkflow: 1
|
||||
m_defaultStampSpawnSettings: {fileID: 11400000, guid: 0e1586b8863570048942e9493106d5e8, type: 2}
|
||||
m_defaultBiomeMaskSettings: {fileID: 0}
|
||||
m_spawnSimulateComputeShader: {fileID: 7200000, guid: ab31bdb5aecea3744951fb814c0d268b, type: 3}
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
391
Assets/Scenes/GaiaTest.unity
Normal file
391
Assets/Scenes/GaiaTest.unity
Normal file
@@ -0,0 +1,391 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!29 &1
|
||||
OcclusionCullingSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_OcclusionBakeSettings:
|
||||
smallestOccluder: 5
|
||||
smallestHole: 0.25
|
||||
backfaceThreshold: 100
|
||||
m_SceneGUID: 00000000000000000000000000000000
|
||||
m_OcclusionCullingData: {fileID: 0}
|
||||
--- !u!104 &2
|
||||
RenderSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 10
|
||||
m_Fog: 0
|
||||
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||
m_FogMode: 3
|
||||
m_FogDensity: 0.01
|
||||
m_LinearFogStart: 0
|
||||
m_LinearFogEnd: 300
|
||||
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
||||
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
||||
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
||||
m_AmbientIntensity: 1
|
||||
m_AmbientMode: 0
|
||||
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
||||
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_HaloStrength: 0.5
|
||||
m_FlareStrength: 1
|
||||
m_FlareFadeSpeed: 3
|
||||
m_HaloTexture: {fileID: 0}
|
||||
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_DefaultReflectionMode: 0
|
||||
m_DefaultReflectionResolution: 128
|
||||
m_ReflectionBounces: 1
|
||||
m_ReflectionIntensity: 1
|
||||
m_CustomReflection: {fileID: 0}
|
||||
m_Sun: {fileID: 0}
|
||||
m_UseRadianceAmbientProbe: 0
|
||||
--- !u!157 &3
|
||||
LightmapSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 13
|
||||
m_BakeOnSceneLoad: 0
|
||||
m_GISettings:
|
||||
serializedVersion: 2
|
||||
m_BounceScale: 1
|
||||
m_IndirectOutputScale: 1
|
||||
m_AlbedoBoost: 1
|
||||
m_EnvironmentLightingMode: 0
|
||||
m_EnableBakedLightmaps: 1
|
||||
m_EnableRealtimeLightmaps: 0
|
||||
m_LightmapEditorSettings:
|
||||
serializedVersion: 12
|
||||
m_Resolution: 2
|
||||
m_BakeResolution: 40
|
||||
m_AtlasSize: 1024
|
||||
m_AO: 0
|
||||
m_AOMaxDistance: 1
|
||||
m_CompAOExponent: 1
|
||||
m_CompAOExponentDirect: 0
|
||||
m_ExtractAmbientOcclusion: 0
|
||||
m_Padding: 2
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_LightmapsBakeMode: 1
|
||||
m_TextureCompression: 1
|
||||
m_ReflectionCompression: 2
|
||||
m_MixedBakeMode: 2
|
||||
m_BakeBackend: 1
|
||||
m_PVRSampling: 1
|
||||
m_PVRDirectSampleCount: 32
|
||||
m_PVRSampleCount: 512
|
||||
m_PVRBounces: 2
|
||||
m_PVREnvironmentSampleCount: 256
|
||||
m_PVREnvironmentReferencePointCount: 2048
|
||||
m_PVRFilteringMode: 1
|
||||
m_PVRDenoiserTypeDirect: 1
|
||||
m_PVRDenoiserTypeIndirect: 1
|
||||
m_PVRDenoiserTypeAO: 1
|
||||
m_PVRFilterTypeDirect: 0
|
||||
m_PVRFilterTypeIndirect: 0
|
||||
m_PVRFilterTypeAO: 0
|
||||
m_PVREnvironmentMIS: 1
|
||||
m_PVRCulling: 1
|
||||
m_PVRFilteringGaussRadiusDirect: 1
|
||||
m_PVRFilteringGaussRadiusIndirect: 5
|
||||
m_PVRFilteringGaussRadiusAO: 2
|
||||
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||
m_ExportTrainingData: 0
|
||||
m_TrainingDataDestination: TrainingData
|
||||
m_LightProbeSampleCountMultiplier: 4
|
||||
m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_LightingSettings: {fileID: 0}
|
||||
--- !u!196 &4
|
||||
NavMeshSettings:
|
||||
serializedVersion: 2
|
||||
m_ObjectHideFlags: 0
|
||||
m_BuildSettings:
|
||||
serializedVersion: 3
|
||||
agentTypeID: 0
|
||||
agentRadius: 0.5
|
||||
agentHeight: 2
|
||||
agentSlope: 45
|
||||
agentClimb: 0.4
|
||||
ledgeDropHeight: 0
|
||||
maxJumpAcrossDistance: 0
|
||||
minRegionArea: 2
|
||||
manualCellSize: 0
|
||||
cellSize: 0.16666667
|
||||
manualTileSize: 0
|
||||
tileSize: 256
|
||||
buildHeightMesh: 0
|
||||
maxJobWorkers: 0
|
||||
preserveTilesOutsideBounds: 0
|
||||
debug:
|
||||
m_Flags: 0
|
||||
m_NavMeshData: {fileID: 0}
|
||||
--- !u!1 &203844586
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 203844589}
|
||||
- component: {fileID: 203844588}
|
||||
- component: {fileID: 203844587}
|
||||
m_Layer: 0
|
||||
m_Name: Directional Light
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &203844587
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 203844586}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_UsePipelineSettings: 1
|
||||
m_AdditionalLightsShadowResolutionTier: 2
|
||||
m_CustomShadowLayers: 0
|
||||
m_LightCookieSize: {x: 1, y: 1}
|
||||
m_LightCookieOffset: {x: 0, y: 0}
|
||||
m_SoftShadowQuality: 0
|
||||
m_RenderingLayersMask:
|
||||
serializedVersion: 0
|
||||
m_Bits: 1
|
||||
m_ShadowRenderingLayersMask:
|
||||
serializedVersion: 0
|
||||
m_Bits: 1
|
||||
m_Version: 4
|
||||
m_LightLayerMask: 1
|
||||
m_ShadowLayerMask: 1
|
||||
m_RenderingLayers: 1
|
||||
m_ShadowRenderingLayers: 1
|
||||
--- !u!108 &203844588
|
||||
Light:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 203844586}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 11
|
||||
m_Type: 1
|
||||
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
|
||||
m_Intensity: 1
|
||||
m_Range: 10
|
||||
m_SpotAngle: 30
|
||||
m_InnerSpotAngle: 21.80208
|
||||
m_CookieSize: 10
|
||||
m_Shadows:
|
||||
m_Type: 2
|
||||
m_Resolution: -1
|
||||
m_CustomResolution: -1
|
||||
m_Strength: 1
|
||||
m_Bias: 0.05
|
||||
m_NormalBias: 0.4
|
||||
m_NearPlane: 0.2
|
||||
m_CullingMatrixOverride:
|
||||
e00: 1
|
||||
e01: 0
|
||||
e02: 0
|
||||
e03: 0
|
||||
e10: 0
|
||||
e11: 1
|
||||
e12: 0
|
||||
e13: 0
|
||||
e20: 0
|
||||
e21: 0
|
||||
e22: 1
|
||||
e23: 0
|
||||
e30: 0
|
||||
e31: 0
|
||||
e32: 0
|
||||
e33: 1
|
||||
m_UseCullingMatrixOverride: 0
|
||||
m_Cookie: {fileID: 0}
|
||||
m_DrawHalo: 0
|
||||
m_Flare: {fileID: 0}
|
||||
m_RenderMode: 0
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingLayerMask: 1
|
||||
m_Lightmapping: 4
|
||||
m_LightShadowCasterMode: 0
|
||||
m_AreaSize: {x: 1, y: 1}
|
||||
m_BounceIntensity: 1
|
||||
m_ColorTemperature: 6570
|
||||
m_UseColorTemperature: 0
|
||||
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_UseBoundingSphereOverride: 0
|
||||
m_UseViewFrustumForShadowCasterCull: 1
|
||||
m_ForceVisible: 0
|
||||
m_ShadowRadius: 0
|
||||
m_ShadowAngle: 0
|
||||
m_LightUnit: 1
|
||||
m_LuxAtDistance: 1
|
||||
m_EnableSpotReflector: 1
|
||||
--- !u!4 &203844589
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 203844586}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
|
||||
m_LocalPosition: {x: 0, y: 3, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
|
||||
--- !u!1 &961739749
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 961739753}
|
||||
- component: {fileID: 961739752}
|
||||
- component: {fileID: 961739751}
|
||||
- component: {fileID: 961739750}
|
||||
m_Layer: 0
|
||||
m_Name: Main Camera
|
||||
m_TagString: MainCamera
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &961739750
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 961739749}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_RenderShadows: 1
|
||||
m_RequiresDepthTextureOption: 2
|
||||
m_RequiresOpaqueTextureOption: 2
|
||||
m_CameraType: 0
|
||||
m_Cameras: []
|
||||
m_RendererIndex: -1
|
||||
m_VolumeLayerMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 1
|
||||
m_VolumeTrigger: {fileID: 0}
|
||||
m_VolumeFrameworkUpdateModeOption: 2
|
||||
m_RenderPostProcessing: 0
|
||||
m_Antialiasing: 0
|
||||
m_AntialiasingQuality: 2
|
||||
m_StopNaN: 0
|
||||
m_Dithering: 0
|
||||
m_ClearDepth: 1
|
||||
m_AllowXRRendering: 1
|
||||
m_AllowHDROutput: 1
|
||||
m_UseScreenCoordOverride: 0
|
||||
m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_RequiresDepthTexture: 0
|
||||
m_RequiresColorTexture: 0
|
||||
m_TaaSettings:
|
||||
m_Quality: 3
|
||||
m_FrameInfluence: 0.1
|
||||
m_JitterScale: 1
|
||||
m_MipBias: 0
|
||||
m_VarianceClampScale: 0.9
|
||||
m_ContrastAdaptiveSharpening: 0
|
||||
m_Version: 2
|
||||
--- !u!81 &961739751
|
||||
AudioListener:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 961739749}
|
||||
m_Enabled: 1
|
||||
--- !u!20 &961739752
|
||||
Camera:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 961739749}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
m_ClearFlags: 1
|
||||
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
|
||||
m_projectionMatrixMode: 1
|
||||
m_GateFitMode: 2
|
||||
m_FOVAxisMode: 0
|
||||
m_Iso: 200
|
||||
m_ShutterSpeed: 0.005
|
||||
m_Aperture: 16
|
||||
m_FocusDistance: 10
|
||||
m_FocalLength: 50
|
||||
m_BladeCount: 5
|
||||
m_Curvature: {x: 2, y: 11}
|
||||
m_BarrelClipping: 0.25
|
||||
m_Anamorphism: 0
|
||||
m_SensorSize: {x: 36, y: 24}
|
||||
m_LensShift: {x: 0, y: 0}
|
||||
m_NormalizedViewPortRect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 1
|
||||
height: 1
|
||||
near clip plane: 0.3
|
||||
far clip plane: 1000
|
||||
field of view: 60
|
||||
orthographic: 0
|
||||
orthographic size: 5
|
||||
m_Depth: -1
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingPath: -1
|
||||
m_TargetTexture: {fileID: 0}
|
||||
m_TargetDisplay: 0
|
||||
m_TargetEye: 3
|
||||
m_HDR: 1
|
||||
m_AllowMSAA: 1
|
||||
m_AllowDynamicResolution: 0
|
||||
m_ForceIntoRT: 0
|
||||
m_OcclusionCulling: 1
|
||||
m_StereoConvergence: 10
|
||||
m_StereoSeparation: 0.022
|
||||
--- !u!4 &961739753
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 961739749}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 1, z: -10}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1660057539 &9223372036854775807
|
||||
SceneRoots:
|
||||
m_ObjectHideFlags: 0
|
||||
m_Roots:
|
||||
- {fileID: 961739753}
|
||||
- {fileID: 203844589}
|
||||
7
Assets/Scenes/GaiaTest.unity.meta
Normal file
7
Assets/Scenes/GaiaTest.unity.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 57e93090c20de5540b06fbb7d4da5d3b
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
130
Assets/Scripts/Test/BobberBuoyancyStable.cs
Normal file
130
Assets/Scripts/Test/BobberBuoyancyStable.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using UnityEngine;
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
[RequireComponent(typeof(CapsuleCollider))]
|
||||
public class BobberBuoyancyStable : MonoBehaviour
|
||||
{
|
||||
[Header("Water")]
|
||||
public float waterLevelY = 0f;
|
||||
|
||||
[Tooltip("必须至少浸入这么深才开始产生浮力(防止还没入水就被顶)")]
|
||||
public float enterWaterDepth = 0.003f; // 3mm(按你的尺度改)
|
||||
|
||||
[Tooltip("在这个深度范围内做平滑过渡(越大越软)")]
|
||||
public float smoothDepth = 0.02f;
|
||||
|
||||
[Header("Buoyancy Spring")]
|
||||
public float buoyancySpring = 30f;
|
||||
public float buoyancyDamping = 8f;
|
||||
|
||||
[Tooltip("最大上浮加速度限制(0=不限制)")]
|
||||
public float maxUpAcceleration = 0f;
|
||||
|
||||
[Header("Water Drag")]
|
||||
public float extraLinearDampingInWater = 2f;
|
||||
public float extraAngularDampingInWater = 2f;
|
||||
|
||||
[Header("Center Of Mass")]
|
||||
public bool driveCenterOfMassFromCapsule = true;
|
||||
public Vector3 extraCenterOfMassOffset = new Vector3(0f, -0.01f, 0f);
|
||||
|
||||
[Header("Righting")]
|
||||
public float rightingTorque = 1.5f;
|
||||
public float rightingDamping = 0.5f;
|
||||
|
||||
Rigidbody rb;
|
||||
CapsuleCollider cap;
|
||||
|
||||
float airLinearDamping;
|
||||
float airAngularDamping;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
rb = GetComponent<Rigidbody>();
|
||||
cap = GetComponent<CapsuleCollider>();
|
||||
rb.useGravity = true;
|
||||
|
||||
airLinearDamping = rb.linearDamping;
|
||||
airAngularDamping = rb.angularDamping;
|
||||
|
||||
ApplyCenterOfMass();
|
||||
rb.maxAngularVelocity = 50f;
|
||||
}
|
||||
|
||||
void FixedUpdate()
|
||||
{
|
||||
ApplyCenterOfMass();
|
||||
|
||||
Bounds b = cap.bounds;
|
||||
float bottomY = b.min.y;
|
||||
float topY = b.max.y;
|
||||
|
||||
// 用“底部点”判定是否真正入水(必须超过阈值)
|
||||
float bottomSubmersion = waterLevelY - bottomY; // >0 表示底部在水下
|
||||
if (bottomSubmersion <= enterWaterDepth)
|
||||
{
|
||||
// 认为未入水:不施加浮力,恢复空气阻尼
|
||||
rb.linearDamping = airLinearDamping;
|
||||
rb.angularDamping = airAngularDamping;
|
||||
return;
|
||||
}
|
||||
|
||||
// 进入水中:阻尼随浸入增强
|
||||
// 这里用一个0~1的平滑权重,避免刚入水就“猛顶”
|
||||
float w = Smooth01((bottomSubmersion - enterWaterDepth) / Mathf.Max(1e-4f, smoothDepth));
|
||||
|
||||
rb.linearDamping = airLinearDamping + extraLinearDampingInWater * w;
|
||||
rb.angularDamping = airAngularDamping + extraAngularDampingInWater * w;
|
||||
|
||||
// 垂直速度(用刚体自身速度就够稳定)
|
||||
float vY = rb.linearVelocity.y;
|
||||
|
||||
// 弹簧+阻尼浮力(仅向上)
|
||||
float forceY = buoyancySpring * bottomSubmersion - buoyancyDamping * vY;
|
||||
if (forceY < 0f) forceY = 0f;
|
||||
|
||||
// 平滑权重:刚入水时逐渐接管
|
||||
forceY *= w;
|
||||
|
||||
// 限制最大上浮加速度(可选)
|
||||
if (maxUpAcceleration > 0f)
|
||||
{
|
||||
float maxForce = rb.mass * maxUpAcceleration;
|
||||
if (forceY > maxForce) forceY = maxForce;
|
||||
}
|
||||
|
||||
// 浮力作用点:必须放在水面下(否则会出现奇怪力矩)
|
||||
float buoyY = Mathf.Min(waterLevelY - 0.001f, topY); // 强制在水面下1mm
|
||||
buoyY = Mathf.Max(buoyY, bottomY); // 不低于底部
|
||||
Vector3 buoyPoint = new Vector3(b.center.x, buoyY, b.center.z);
|
||||
|
||||
rb.AddForceAtPosition(Vector3.up * forceY, buoyPoint, ForceMode.Force);
|
||||
|
||||
// 归正扭矩(只在水里生效)
|
||||
Vector3 up = transform.up;
|
||||
Vector3 axis = Vector3.Cross(up, Vector3.up);
|
||||
float mag = axis.magnitude;
|
||||
if (mag > 1e-4f)
|
||||
{
|
||||
axis /= mag;
|
||||
float angle = Mathf.Asin(Mathf.Clamp(mag, -1f, 1f));
|
||||
float angVelOnAxis = Vector3.Dot(rb.angularVelocity, axis);
|
||||
float torque = (rightingTorque * angle - rightingDamping * angVelOnAxis) * w;
|
||||
rb.AddTorque(axis * torque, ForceMode.Acceleration);
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyCenterOfMass()
|
||||
{
|
||||
if (!driveCenterOfMassFromCapsule) return;
|
||||
rb.centerOfMass = cap.center + extraCenterOfMassOffset;
|
||||
}
|
||||
|
||||
static float Smooth01(float t)
|
||||
{
|
||||
t = Mathf.Clamp01(t);
|
||||
// smoothstep
|
||||
return t * t * (3f - 2f * t);
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Test/BobberBuoyancyStable.cs.meta
Normal file
3
Assets/Scripts/Test/BobberBuoyancyStable.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 976f4f103cc04f34a8a81bd4abba2244
|
||||
timeCreated: 1772269304
|
||||
@@ -1,126 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest;
|
||||
|
||||
namespace NBF
|
||||
{
|
||||
/// <summary>
|
||||
/// 小型浮漂/小物体:基于 Crest 的浮力(自动从多个 CapsuleCollider 计算尺寸与探针)
|
||||
/// - 自动收集自身与子物体上的 CapsuleCollider(可多个)
|
||||
/// - 计算整体长轴、长度、最大直径、底部高度
|
||||
/// - 自动设置 Crest 查询尺度 _ObjectWidth(与直径同量级)
|
||||
/// - 自动生成 5 个探针:底部四周 + 底部中心(更稳)
|
||||
/// - 浮力:弹簧(k) + 阻尼(c),ForceMode.Force(质量参与)
|
||||
/// </summary>
|
||||
public sealed class BobberFloating : MonoBehaviour
|
||||
{
|
||||
[Header("Crest")]
|
||||
public WaterRenderer _water;
|
||||
|
||||
[SerializeField] Rigidbody _RigidBody;
|
||||
|
||||
[Tooltip("要瞄准哪一层水的碰撞层。")]
|
||||
[SerializeField] CollisionLayer _Layer = CollisionLayer.AfterAnimatedWaves;
|
||||
[Tooltip("要瞄准哪一层水的碰撞层。")] [SerializeField]
|
||||
CollisionLayer _Layer = CollisionLayer.AfterAnimatedWaves;
|
||||
|
||||
// -----------------------------
|
||||
// 自动从 CapsuleCollider 计算
|
||||
// -----------------------------
|
||||
[Header("Auto from CapsuleColliders")]
|
||||
[Tooltip("自动扫描自己与子物体的 CapsuleCollider,并计算尺寸/探针/采样尺度。")]
|
||||
[SerializeField] bool _AutoFromCapsules = true;
|
||||
[Header("浮力")]
|
||||
[Header("力强度")]
|
||||
[Tooltip("对于探测器而言,大致为 100 比 1 的质量与力的比例,以使质心保持在表面附近。对于“对齐法线”,默认值适用于具有默认刚体的默认球体。")]
|
||||
[SerializeField]
|
||||
float _BuoyancyForceStrength = 10f;
|
||||
|
||||
[Tooltip("仅使用 enabled 的 CapsuleCollider。")]
|
||||
[SerializeField] bool _OnlyEnabledCapsules = true;
|
||||
[Header("扭矩强度")] [Tooltip("使船体方向与水的法线方向一致时所施加扭矩的大小。")] [SerializeField]
|
||||
float _BuoyancyTorqueStrength = 8f;
|
||||
|
||||
[Tooltip("运行时也会每隔一定时间重建(应对浮漂缩放/更换碰撞体)。0 表示仅 Start/OnValidate 时重建。")]
|
||||
[SerializeField] float _RuntimeRebuildInterval = 0f;
|
||||
[Header("最大力矩")] [Tooltip("将浮力值固定在此数值上。\n\n适用于处理完全浸没的物体。")] [SerializeField]
|
||||
float _MaximumBuoyancyForce = 100f;
|
||||
|
||||
[Tooltip("给估算出来的直径乘一个系数,用于更稳的 Crest 查询与阻尼。通常 1.0~2.0。")]
|
||||
[Range(0.5f, 3f)]
|
||||
[SerializeField] float _WidthMultiplier = 1.2f;
|
||||
[Header("高度偏移")] [Tooltip("从变换中心到船体底部的高度偏移(如果存在)。\n\n默认值适用于默认球体。该值无需精确测量从中心到底部的距离。")] [SerializeField]
|
||||
float _CenterToBottomOffset = -1f;
|
||||
|
||||
[Tooltip("探针半径比例(相对估算半径)。越大越“撑开”,越抗翻滚,但也更容易被浪抬。建议 0.6~0.9")]
|
||||
[Range(0.1f, 1.2f)]
|
||||
[SerializeField] float _ProbeRadiusRatio = 0.75f;
|
||||
[Tooltip("顺着波浪 “冲浪” 的近似流体动力学效果。")] [Range(0, 1)] [SerializeField]
|
||||
float _AccelerateDownhill;
|
||||
|
||||
[Tooltip("探针在底部之上的抬高(米)。用于避免探针刚好在最底点导致抖动。建议:半径的 5%~20%。")]
|
||||
[SerializeField] float _ProbeLiftMeters = 0.0f;
|
||||
|
||||
[Tooltip("探针权重分配:四周总权重(其余给中心)。建议 0.6~0.9")]
|
||||
[Range(0f, 1f)]
|
||||
[SerializeField] float _RingWeight = 0.75f;
|
||||
[Header("拖拽")] [Tooltip("在水中时使用拖拽功能。\n将此属性添加到刚体所声明的拖拽力上。")] [SerializeField]
|
||||
Vector3 _Drag = new(2f, 3f, 1f);
|
||||
|
||||
// -----------------------------
|
||||
// 物理参数(吃水可控)
|
||||
// -----------------------------
|
||||
[Header("Buoyancy (controllable submergence)")]
|
||||
[Tooltip("目标吃水比例:以“直径”为尺度。\n例:0.30 表示目标吃水深度约 = 直径*0.30。\n对小浮漂非常好调:想更浮(露更多)-> 降低;想更沉 -> 提高。")]
|
||||
[Range(0.05f, 1.2f)]
|
||||
[SerializeField] float _TargetSubmergenceRatio = 0.30f;
|
||||
[Tooltip("在水中会产生旋转阻力。\n\n将此阻力添加到刚体上已声明的旋转阻力值之上。")] [SerializeField]
|
||||
float _AngularDrag = 0.2f;
|
||||
|
||||
[Tooltip("浮力阻尼比(0~2 常用)。越大越不抖,但会显得黏。推荐 0.7~1.2")]
|
||||
[Range(0f, 2f)]
|
||||
[SerializeField] float _DampingRatio = 0.95f;
|
||||
[Tooltip("施加拉力的位置的垂直偏移量。")] [SerializeField]
|
||||
float _ForceHeightOffset;
|
||||
|
||||
[Tooltip("最大总浮力(N)保护。若为 Infinity 则不限制。")]
|
||||
[SerializeField] float _MaximumBuoyancyForce = 50f;
|
||||
|
||||
[Tooltip("吃水偏移(米)。用于校准 pivot/碰撞体中心误差。\n>0 更容易浮起(等效更浅),<0 更沉。")]
|
||||
[SerializeField] float _SubmergenceOffset = 0f;
|
||||
[Header("波响应")] [Tooltip("用于物理计算的物体宽度。\n\n此值越大,波响应的滤波效果和平滑程度就越高。如果无法对较大波长进行滤波,则应增加 LOD 级别。")] [SerializeField]
|
||||
float _ObjectWidth = 3f;
|
||||
|
||||
[Header("Water drag (relative to water surface velocity/flow)")]
|
||||
[Tooltip("相对水的线性阻力(N per m/s)。小浮漂建议从 (0.2, 0.6, 0.2) 起。")]
|
||||
[SerializeField] Vector3 _Drag = new(0.2f, 0.6f, 0.2f);
|
||||
|
||||
[Tooltip("角阻力(N*m per rad/s)。小浮漂建议 0.01~0.05。")]
|
||||
[SerializeField] float _AngularDrag = 0.03f;
|
||||
|
||||
[Header("Optional downhill acceleration")]
|
||||
[Range(0, 1)]
|
||||
[SerializeField] float _AccelerateDownhill = 0f;
|
||||
|
||||
[Header("Force application height offset")]
|
||||
[SerializeField] float _ForceHeightOffset = 0f;
|
||||
|
||||
// -----------------------------
|
||||
// 自动生成的“几何结果”
|
||||
// -----------------------------
|
||||
[Header("Computed (read-only)")]
|
||||
[SerializeField] float _ComputedLength = 0.1f;
|
||||
[SerializeField] float _ComputedDiameter = 0.04f;
|
||||
[SerializeField] Vector3 _ComputedAxisLocal = Vector3.up; // 刚体局部坐标中的长轴(单位向量)
|
||||
[SerializeField] float _ComputedBottomLocalY = -0.02f; // 刚体局部坐标中“整体最低点”的 y(沿长轴方向投影不是y;这里是按 rb local Y,仅用于Debug显示)
|
||||
|
||||
[Header("Wave response / query scale")]
|
||||
[Tooltip("用于 Crest 查询的物体宽度(米)。会自动从 Capsule 直径推算。")]
|
||||
[SerializeField] float _ObjectWidth = 0.04f;
|
||||
|
||||
// -----------------------------
|
||||
// Debug
|
||||
// -----------------------------
|
||||
[Space(10)]
|
||||
[SerializeField] DebugFields _Debug = new();
|
||||
[Space(10)] [SerializeField] DebugFields _Debug = new();
|
||||
|
||||
[Serializable]
|
||||
[System.Serializable]
|
||||
sealed class DebugFields
|
||||
{
|
||||
[SerializeField] internal bool _DrawProbes = false;
|
||||
[SerializeField] internal bool _DrawForces = false;
|
||||
[SerializeField] internal bool _DrawQueries = false;
|
||||
}
|
||||
|
||||
/// <summary>是否有任意探针入水</summary>
|
||||
internal const string k_FixedUpdateMarker = "Crest.FloatingObject.FixedUpdate";
|
||||
|
||||
static Unity.Profiling.ProfilerMarker s_FixedUpdateMarker = new(k_FixedUpdateMarker);
|
||||
|
||||
/// <summary>
|
||||
/// 这个物体的任何部分是否浸泡在水中?
|
||||
/// </summary>
|
||||
public bool InWater { get; private set; }
|
||||
|
||||
// 探针(刚体局部空间,相对 worldCenterOfMass)
|
||||
struct Probe
|
||||
{
|
||||
public Vector3 localOffsetFromCOM;
|
||||
public float weight;
|
||||
}
|
||||
|
||||
Probe[] _Probes = Array.Empty<Probe>();
|
||||
|
||||
readonly SampleFlowHelper _SampleFlowHelper = new();
|
||||
|
||||
Vector3[] _QueryPoints;
|
||||
@@ -128,436 +70,152 @@ namespace NBF
|
||||
Vector3[] _QueryResultVelocities;
|
||||
Vector3[] _QueryResultNormal;
|
||||
|
||||
float _NextRebuildTime = 0f;
|
||||
// internal FloatingObjectProbe[] _Probe = new FloatingObjectProbe[] { new() { _Weight = 1f } };
|
||||
|
||||
void Reset()
|
||||
{
|
||||
if (_RigidBody == null) TryGetComponent(out _RigidBody);
|
||||
}
|
||||
public FloatingObjectProbe[] Probe = new FloatingObjectProbe[] { new() { _Weight = 1f } };
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
_ObjectWidth = Mathf.Max(0.001f, _ObjectWidth);
|
||||
_RuntimeRebuildInterval = Mathf.Max(0f, _RuntimeRebuildInterval);
|
||||
_ProbeLiftMeters = Mathf.Max(0f, _ProbeLiftMeters);
|
||||
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
if (_AutoFromCapsules)
|
||||
{
|
||||
TryRebuildFromCapsules(editor: true);
|
||||
}
|
||||
AllocateQueryArrays();
|
||||
}
|
||||
}
|
||||
|
||||
void Start()
|
||||
private void Start()
|
||||
{
|
||||
if (_RigidBody == null) TryGetComponent(out _RigidBody);
|
||||
|
||||
if (_AutoFromCapsules)
|
||||
{
|
||||
TryRebuildFromCapsules(editor: false);
|
||||
}
|
||||
|
||||
AllocateQueryArrays();
|
||||
var points = Probe;
|
||||
// Advanced 还需要为中心增设一个位置。
|
||||
var length = points.Length;
|
||||
_QueryPoints = new Vector3[length];
|
||||
_QueryResultDisplacements = new Vector3[length];
|
||||
_QueryResultVelocities = new Vector3[length];
|
||||
_QueryResultNormal = new Vector3[length];
|
||||
}
|
||||
|
||||
void FixedUpdate()
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (_water == null || _RigidBody == null) return;
|
||||
s_FixedUpdateMarker.Begin(this);
|
||||
|
||||
if (_AutoFromCapsules && _RuntimeRebuildInterval > 0f && Time.time >= _NextRebuildTime)
|
||||
{
|
||||
_NextRebuildTime = Time.time + _RuntimeRebuildInterval;
|
||||
TryRebuildFromCapsules(editor: false);
|
||||
AllocateQueryArrays();
|
||||
}
|
||||
|
||||
if (_Probes == null || _Probes.Length == 0) return;
|
||||
|
||||
// Crest provider
|
||||
var points = Probe;
|
||||
|
||||
// 查询
|
||||
var collisions = _water.AnimatedWavesLod.Provider;
|
||||
|
||||
int probeCount = _Probes.Length;
|
||||
int centerIndex = probeCount; // 最后一个点用于中心采样(表面速度/法线)
|
||||
|
||||
Vector3 comWorld = _RigidBody.worldCenterOfMass;
|
||||
|
||||
// Build query points
|
||||
for (int i = 0; i < probeCount; i++)
|
||||
|
||||
// 更新查询点。
|
||||
for (var i = 0; i < points.Length; i++)
|
||||
{
|
||||
_QueryPoints[i] = comWorld + transform.TransformVector(_Probes[i].localOffsetFromCOM);
|
||||
var point = points[i];
|
||||
_QueryPoints[i] =
|
||||
transform.TransformPoint(point._Position + new Vector3(0, _RigidBody.centerOfMass.y, 0));
|
||||
}
|
||||
_QueryPoints[centerIndex] = comWorld;
|
||||
|
||||
// Query waves
|
||||
collisions.Query(
|
||||
GetHashCode(),
|
||||
_ObjectWidth,
|
||||
_QueryPoints,
|
||||
_QueryResultDisplacements,
|
||||
_QueryResultNormal,
|
||||
_QueryResultVelocities,
|
||||
_Layer
|
||||
);
|
||||
|
||||
// Surface velocity + flow
|
||||
Vector3 surfaceVelocity = _QueryResultVelocities[centerIndex];
|
||||
|
||||
_QueryPoints[^1] = transform.position + new Vector3(0, _RigidBody.centerOfMass.y, 0);
|
||||
|
||||
collisions.Query(GetHashCode(), _ObjectWidth, _QueryPoints, _QueryResultDisplacements,
|
||||
_QueryResultNormal, _QueryResultVelocities, _Layer);
|
||||
|
||||
|
||||
//我们可以将表面速度过滤为最近两帧中的较小值。
|
||||
//存在一种极端情况:当波长被开启 / 关闭时,会产生单帧速度尖峰——
|
||||
//因为此时水面确实会发生极快的运动。
|
||||
var surfaceVelocity = _QueryResultVelocities[^1];
|
||||
_SampleFlowHelper.Sample(transform.position, out var surfaceFlow, minimumLength: _ObjectWidth);
|
||||
surfaceVelocity += new Vector3(surfaceFlow.x, 0f, surfaceFlow.y);
|
||||
|
||||
// Compute buoyancy coefficients
|
||||
float g = Mathf.Abs(Physics.gravity.y);
|
||||
|
||||
// 目标吃水深度(米):按直径比例
|
||||
float targetSubmergence = Mathf.Max(0.0005f, _ComputedDiameter * _TargetSubmergenceRatio);
|
||||
|
||||
// 总弹簧系数:k ≈ m*g / targetSubmergence
|
||||
float kTotal = (_RigidBody.mass * g) / targetSubmergence;
|
||||
|
||||
// 总阻尼:c = 2*sqrt(k*m)*dampingRatio
|
||||
float cTotal = 2f * Mathf.Sqrt(Mathf.Max(0.0001f, kTotal * _RigidBody.mass)) * _DampingRatio;
|
||||
|
||||
InWater = false;
|
||||
float totalBuoyMag = 0f;
|
||||
|
||||
// Apply distributed buoyancy
|
||||
for (int i = 0; i < probeCount; i++)
|
||||
surfaceVelocity += new Vector3(surfaceFlow.x, 0, surfaceFlow.y);
|
||||
|
||||
if (_Debug._DrawQueries)
|
||||
{
|
||||
Vector3 p = _QueryPoints[i];
|
||||
float waterHeight = _QueryResultDisplacements[i].y + _water.SeaLevel;
|
||||
|
||||
// depth: positive means submerged
|
||||
float depth = (waterHeight - p.y) + _SubmergenceOffset;
|
||||
if (depth <= 0f) continue;
|
||||
|
||||
InWater = true;
|
||||
|
||||
float w = Mathf.Max(0.0001f, _Probes[i].weight);
|
||||
|
||||
// spring
|
||||
float FiSpring = (kTotal * w) * depth;
|
||||
|
||||
// damping (vertical relative velocity)
|
||||
Vector3 pointVel = _RigidBody.GetPointVelocity(p);
|
||||
float vRel = Vector3.Dot(pointVel - surfaceVelocity, Vector3.up);
|
||||
float FiDamp = -(cTotal * w) * vRel;
|
||||
|
||||
float Fi = FiSpring + FiDamp;
|
||||
if (Fi < 0f) Fi = 0f;
|
||||
|
||||
totalBuoyMag += Fi;
|
||||
|
||||
Vector3 force = Fi * Vector3.up;
|
||||
_RigidBody.AddForceAtPosition(force, p, ForceMode.Force);
|
||||
|
||||
if (_Debug._DrawForces)
|
||||
{
|
||||
Debug.DrawLine(p, p + force * 0.02f, Color.cyan);
|
||||
}
|
||||
Debug.DrawLine(transform.position + 5f * Vector3.up,
|
||||
transform.position + 5f * Vector3.up + surfaceVelocity, new(1, 1, 1, 0.6f));
|
||||
}
|
||||
|
||||
if (!InWater) return;
|
||||
|
||||
// Clamp buoyancy (simple safety)
|
||||
if (_MaximumBuoyancyForce < Mathf.Infinity && totalBuoyMag > _MaximumBuoyancyForce)
|
||||
var height = _QueryResultDisplacements[0].y + _water.SeaLevel;
|
||||
var bottomDepth = height - transform.position.y - _CenterToBottomOffset;
|
||||
var normal = _QueryResultNormal[0];
|
||||
|
||||
if (_Debug._DrawQueries)
|
||||
{
|
||||
float excess = totalBuoyMag - _MaximumBuoyancyForce;
|
||||
_RigidBody.AddForce(-excess * Vector3.up, ForceMode.Force);
|
||||
var surfPos = transform.position;
|
||||
surfPos.y = height;
|
||||
DebugUtility.DrawCross(Debug.DrawLine, surfPos, normal, 1f, Color.red);
|
||||
}
|
||||
|
||||
// Optional downhill acceleration (usually 0 for bobber)
|
||||
if (_AccelerateDownhill > 0f)
|
||||
InWater = bottomDepth > 0f;
|
||||
if (!InWater)
|
||||
{
|
||||
Vector3 normal = _QueryResultNormal[centerIndex];
|
||||
_RigidBody.AddForce(_AccelerateDownhill * g * new Vector3(normal.x, 0f, normal.z), ForceMode.Force);
|
||||
s_FixedUpdateMarker.End();
|
||||
return;
|
||||
}
|
||||
|
||||
// Angular drag
|
||||
if (_AngularDrag > 0f)
|
||||
var buoyancy = _BuoyancyForceStrength * bottomDepth * bottomDepth * bottomDepth *
|
||||
-Physics.gravity.normalized;
|
||||
if (_MaximumBuoyancyForce < Mathf.Infinity)
|
||||
{
|
||||
_RigidBody.AddTorque(-_AngularDrag * _RigidBody.angularVelocity, ForceMode.Force);
|
||||
buoyancy = Vector3.ClampMagnitude(buoyancy, _MaximumBuoyancyForce);
|
||||
}
|
||||
|
||||
// Linear drag relative to water
|
||||
_RigidBody.AddForce(buoyancy, ForceMode.Acceleration);
|
||||
|
||||
// // 在水面上滑行的近似流体动力学
|
||||
// if (_AccelerateDownhill > 0f)
|
||||
// {
|
||||
// _RigidBody.AddForce(_AccelerateDownhill * -Physics.gravity.y * new Vector3(normal.x, 0f, normal.z),
|
||||
// ForceMode.Acceleration);
|
||||
// }
|
||||
//
|
||||
//
|
||||
// // 朝向
|
||||
// // 与水面垂直。默认使用一个垂直方向,但也可以使用单独的垂直方向。
|
||||
// // 根据船的长度与宽度的比例。这会根据船只的不同而产生不同的旋转效果。
|
||||
// // dimensions.
|
||||
// {
|
||||
// var normalLatitudinal = normal;
|
||||
//
|
||||
// if (_Debug._DrawQueries)
|
||||
// Debug.DrawLine(transform.position, transform.position + 5f * normalLatitudinal, Color.green);
|
||||
//
|
||||
// var torqueWidth = Vector3.Cross(transform.up, normalLatitudinal);
|
||||
// _RigidBody.AddTorque(torqueWidth * _BuoyancyTorqueStrength, ForceMode.Acceleration);
|
||||
// _RigidBody.AddTorque(-_AngularDrag * _RigidBody.angularVelocity);
|
||||
// }
|
||||
//
|
||||
//
|
||||
// 相对于水进行拖拽操作
|
||||
if (_Drag != Vector3.zero)
|
||||
{
|
||||
Vector3 velocityRelativeToWater = _RigidBody.linearVelocity - surfaceVelocity;
|
||||
Vector3 forcePosition = _RigidBody.worldCenterOfMass + _ForceHeightOffset * Vector3.up;
|
||||
|
||||
var velocityRelativeToWater = _RigidBody.linearVelocity - surfaceVelocity;
|
||||
|
||||
var forcePosition = _RigidBody.worldCenterOfMass + _ForceHeightOffset * Vector3.up;
|
||||
_RigidBody.AddForceAtPosition(
|
||||
_Drag.x * Vector3.Dot(transform.right, -velocityRelativeToWater) * transform.right,
|
||||
forcePosition,
|
||||
ForceMode.Force);
|
||||
|
||||
_RigidBody.AddForceAtPosition(
|
||||
_Drag.y * Vector3.Dot(Vector3.up, -velocityRelativeToWater) * Vector3.up,
|
||||
forcePosition,
|
||||
ForceMode.Force);
|
||||
|
||||
_Drag.x * Vector3.Dot(transform.right, -velocityRelativeToWater) * transform.right, forcePosition,
|
||||
ForceMode.Acceleration);
|
||||
_RigidBody.AddForceAtPosition(_Drag.y * Vector3.Dot(Vector3.up, -velocityRelativeToWater) * Vector3.up,
|
||||
forcePosition, ForceMode.Acceleration);
|
||||
_RigidBody.AddForceAtPosition(
|
||||
_Drag.z * Vector3.Dot(transform.forward, -velocityRelativeToWater) * transform.forward,
|
||||
forcePosition,
|
||||
ForceMode.Force);
|
||||
forcePosition, ForceMode.Acceleration);
|
||||
}
|
||||
|
||||
if (_Debug._DrawProbes)
|
||||
{
|
||||
for (int i = 0; i < probeCount; i++)
|
||||
{
|
||||
Debug.DrawLine(_QueryPoints[i], _QueryPoints[i] + Vector3.up * 0.05f, Color.yellow);
|
||||
}
|
||||
}
|
||||
s_FixedUpdateMarker.End();
|
||||
}
|
||||
}
|
||||
|
||||
static class DebugUtility
|
||||
{
|
||||
public delegate void DrawLine(Vector3 position, Vector3 up, Color color, float duration);
|
||||
|
||||
public static void DrawCross(DrawLine draw, Vector3 position, float r, Color color, float duration = 0f)
|
||||
{
|
||||
draw(position - Vector3.up * r, position + Vector3.up * r, color, duration);
|
||||
draw(position - Vector3.right * r, position + Vector3.right * r, color, duration);
|
||||
draw(position - Vector3.forward * r, position + Vector3.forward * r, color, duration);
|
||||
}
|
||||
|
||||
void AllocateQueryArrays()
|
||||
public static void DrawCross(DrawLine draw, Vector3 position, Vector3 up, float r, Color color,
|
||||
float duration = 0f)
|
||||
{
|
||||
int probeCount = _Probes?.Length ?? 0;
|
||||
int n = probeCount + 1; // +1 center sample
|
||||
if (n <= 1) n = 2;
|
||||
|
||||
if (_QueryPoints != null && _QueryPoints.Length == n) return;
|
||||
|
||||
_QueryPoints = new Vector3[n];
|
||||
_QueryResultDisplacements = new Vector3[n];
|
||||
_QueryResultVelocities = new Vector3[n];
|
||||
_QueryResultNormal = new Vector3[n];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 公开按钮:手动重建(你也可以在 Inspector 右键脚本 -> Reset,然后 Play)
|
||||
/// </summary>
|
||||
[ContextMenu("Rebuild From CapsuleColliders")]
|
||||
public void RebuildFromCapsules()
|
||||
{
|
||||
TryRebuildFromCapsules(editor: Application.isEditor && !Application.isPlaying);
|
||||
AllocateQueryArrays();
|
||||
}
|
||||
|
||||
bool TryRebuildFromCapsules(bool editor)
|
||||
{
|
||||
var capsules = GetComponentsInChildren<CapsuleCollider>(includeInactive: true);
|
||||
if (capsules == null || capsules.Length == 0)
|
||||
{
|
||||
// 没胶囊就不改
|
||||
return false;
|
||||
}
|
||||
|
||||
// 收集点(在刚体局部空间)
|
||||
List<Vector3> ptsLocal = new List<Vector3>(capsules.Length * 8);
|
||||
|
||||
// 同时估算“长轴”:取所有胶囊在 rb local 的端点云,计算 AABB 最大跨度轴
|
||||
// 这比“看某个 Capsule.direction”更鲁棒(因为你说可能多个、分布复杂)
|
||||
foreach (var cap in capsules)
|
||||
{
|
||||
if (cap == null) continue;
|
||||
if (_OnlyEnabledCapsules && !cap.enabled) continue;
|
||||
|
||||
AddCapsuleSamplePointsInRbLocal(cap, ptsLocal);
|
||||
}
|
||||
|
||||
if (ptsLocal.Count < 2) return false;
|
||||
|
||||
// AABB in rb local
|
||||
Vector3 min = ptsLocal[0];
|
||||
Vector3 max = ptsLocal[0];
|
||||
for (int i = 1; i < ptsLocal.Count; i++)
|
||||
{
|
||||
min = Vector3.Min(min, ptsLocal[i]);
|
||||
max = Vector3.Max(max, ptsLocal[i]);
|
||||
}
|
||||
|
||||
Vector3 size = max - min;
|
||||
|
||||
// 估算长轴:取跨度最大的轴
|
||||
// 注意:这是 rb local 坐标的轴(x/y/z),足够用于“尺寸”和“探针布局”
|
||||
int axis = 0;
|
||||
float spanX = size.x;
|
||||
float spanY = size.y;
|
||||
float spanZ = size.z;
|
||||
if (spanY >= spanX && spanY >= spanZ) axis = 1;
|
||||
else if (spanZ >= spanX && spanZ >= spanY) axis = 2;
|
||||
else axis = 0;
|
||||
|
||||
Vector3 axisLocal = axis == 0 ? Vector3.right : axis == 1 ? Vector3.up : Vector3.forward;
|
||||
|
||||
// length along that axis
|
||||
float length = axis == 0 ? spanX : axis == 1 ? spanY : spanZ;
|
||||
|
||||
// diameter:取另外两轴的最大跨度(更保守)
|
||||
float dia;
|
||||
if (axis == 0) dia = Mathf.Max(spanY, spanZ);
|
||||
else if (axis == 1) dia = Mathf.Max(spanX, spanZ);
|
||||
else dia = Mathf.Max(spanX, spanY);
|
||||
|
||||
dia = Mathf.Max(0.001f, dia);
|
||||
|
||||
_ComputedLength = length;
|
||||
_ComputedDiameter = dia;
|
||||
_ComputedAxisLocal = axisLocal;
|
||||
|
||||
// Crest 查询尺度:直径同量级 * multiplier,且给下限
|
||||
_ObjectWidth = Mathf.Max(0.002f, _ComputedDiameter * _WidthMultiplier);
|
||||
|
||||
// 估算“底部位置”(用于探针 y)
|
||||
// 我们按“长轴”方向找最小投影的点作为底部
|
||||
float minProj = float.PositiveInfinity;
|
||||
Vector3 bottom = ptsLocal[0];
|
||||
for (int i = 0; i < ptsLocal.Count; i++)
|
||||
{
|
||||
float proj = Vector3.Dot(ptsLocal[i], axisLocal);
|
||||
if (proj < minProj)
|
||||
{
|
||||
minProj = proj;
|
||||
bottom = ptsLocal[i];
|
||||
}
|
||||
}
|
||||
|
||||
// 这个值只是给你在 Inspector 看一眼,不参与计算
|
||||
_ComputedBottomLocalY = bottom.y;
|
||||
|
||||
// 生成探针:以“底部附近”一圈 + 中心
|
||||
BuildDefaultProbes(axisLocal, bottom, _ComputedDiameter);
|
||||
|
||||
if (!editor)
|
||||
{
|
||||
// 运行时也确保 query arrays 充足
|
||||
AllocateQueryArrays();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BuildDefaultProbes(Vector3 axisLocal, Vector3 bottomLocal, float diameter)
|
||||
{
|
||||
// 以刚体局部空间为准,探针 offset 是“相对 COM 的局部偏移”
|
||||
// 我们把探针放在 “底部投影点附近”,并沿“横向平面”撑开
|
||||
float radius = diameter * 0.5f;
|
||||
float ringR = radius * _ProbeRadiusRatio;
|
||||
|
||||
// 底部沿长轴方向略抬高,避免探针刚好在最底点导致 jitter
|
||||
// 这里用“长轴”方向抬高,而不是简单的 localY
|
||||
float lift = _ProbeLiftMeters;
|
||||
if (lift <= 0f)
|
||||
{
|
||||
// 默认:半径的 10%
|
||||
lift = radius * 0.10f;
|
||||
}
|
||||
|
||||
Vector3 basePoint = bottomLocal + axisLocal * lift;
|
||||
|
||||
// 需要在“横向平面”找两条正交方向(在 rb local 坐标中)
|
||||
// 任意取一个与 axisLocal 不平行的向量,构造正交基
|
||||
Vector3 t1 = Vector3.Cross(axisLocal, Vector3.up);
|
||||
if (t1.sqrMagnitude < 1e-6f) t1 = Vector3.Cross(axisLocal, Vector3.right);
|
||||
t1.Normalize();
|
||||
Vector3 t2 = Vector3.Cross(axisLocal, t1).normalized;
|
||||
|
||||
// 权重分配
|
||||
float ringTotal = Mathf.Clamp01(_RingWeight);
|
||||
float centerW = 1f - ringTotal;
|
||||
float eachRingW = ringTotal / 4f;
|
||||
|
||||
// 探针点(rb local),最后转成 “相对 COM 的 local offset”
|
||||
// 注意:我们使用 worldCenterOfMass 作为基准,所以这里要把 basePoint(rb local)转换成 offset-from-COM
|
||||
Vector3 comLocal = transform.InverseTransformPoint(_RigidBody.worldCenterOfMass);
|
||||
|
||||
var probes = new Probe[5];
|
||||
|
||||
// 四周
|
||||
probes[0] = new Probe { localOffsetFromCOM = (basePoint + t1 * ringR) - comLocal, weight = eachRingW };
|
||||
probes[1] = new Probe { localOffsetFromCOM = (basePoint - t1 * ringR) - comLocal, weight = eachRingW };
|
||||
probes[2] = new Probe { localOffsetFromCOM = (basePoint + t2 * ringR) - comLocal, weight = eachRingW };
|
||||
probes[3] = new Probe { localOffsetFromCOM = (basePoint - t2 * ringR) - comLocal, weight = eachRingW };
|
||||
|
||||
// 底部中心
|
||||
probes[4] = new Probe { localOffsetFromCOM = basePoint - comLocal, weight = Mathf.Max(0.0001f, centerW) };
|
||||
|
||||
_Probes = probes;
|
||||
}
|
||||
|
||||
void AddCapsuleSamplePointsInRbLocal(CapsuleCollider cap, List<Vector3> outPtsRbLocal)
|
||||
{
|
||||
// 计算胶囊端点(世界) + 若干“半径点”,再转换到 rb local
|
||||
// 胶囊在 cap.transform local:center, direction(0=x 1=y 2=z), height, radius
|
||||
Transform ct = cap.transform;
|
||||
|
||||
Vector3 centerW = ct.TransformPoint(cap.center);
|
||||
|
||||
// direction axis in world
|
||||
Vector3 axisW =
|
||||
cap.direction == 0 ? ct.right :
|
||||
cap.direction == 1 ? ct.up :
|
||||
ct.forward;
|
||||
|
||||
axisW.Normalize();
|
||||
|
||||
// 处理缩放:radius 取垂直于轴的最大缩放分量(保守)
|
||||
Vector3 s = ct.lossyScale;
|
||||
float sx = Mathf.Abs(s.x);
|
||||
float sy = Mathf.Abs(s.y);
|
||||
float sz = Mathf.Abs(s.z);
|
||||
|
||||
float radiusScale;
|
||||
float heightScale;
|
||||
|
||||
if (cap.direction == 0)
|
||||
{
|
||||
heightScale = sx;
|
||||
radiusScale = Mathf.Max(sy, sz);
|
||||
}
|
||||
else if (cap.direction == 1)
|
||||
{
|
||||
heightScale = sy;
|
||||
radiusScale = Mathf.Max(sx, sz);
|
||||
}
|
||||
else
|
||||
{
|
||||
heightScale = sz;
|
||||
radiusScale = Mathf.Max(sx, sy);
|
||||
}
|
||||
|
||||
float r = Mathf.Max(0.0005f, cap.radius * radiusScale);
|
||||
float h = Mathf.Max(0.001f, cap.height * heightScale);
|
||||
|
||||
// 圆柱部分半长(端点到端点的距离为 h-2r)
|
||||
float half = Mathf.Max(0f, (h * 0.5f) - r);
|
||||
|
||||
Vector3 p0W = centerW + axisW * half;
|
||||
Vector3 p1W = centerW - axisW * half;
|
||||
|
||||
// 在世界空间构造两个与 axisW 正交的方向
|
||||
Vector3 n1 = Vector3.Cross(axisW, Vector3.up);
|
||||
if (n1.sqrMagnitude < 1e-6f) n1 = Vector3.Cross(axisW, Vector3.right);
|
||||
n1.Normalize();
|
||||
Vector3 n2 = Vector3.Cross(axisW, n1).normalized;
|
||||
|
||||
// 采样点:两个端点 + 端点的四周半径点 + 中心四周半径点(足够稳定估算整体 AABB)
|
||||
AddWorldPoint(p0W, outPtsRbLocal);
|
||||
AddWorldPoint(p1W, outPtsRbLocal);
|
||||
|
||||
AddWorldPoint(p0W + n1 * r, outPtsRbLocal);
|
||||
AddWorldPoint(p0W - n1 * r, outPtsRbLocal);
|
||||
AddWorldPoint(p0W + n2 * r, outPtsRbLocal);
|
||||
AddWorldPoint(p0W - n2 * r, outPtsRbLocal);
|
||||
|
||||
AddWorldPoint(p1W + n1 * r, outPtsRbLocal);
|
||||
AddWorldPoint(p1W - n1 * r, outPtsRbLocal);
|
||||
AddWorldPoint(p1W + n2 * r, outPtsRbLocal);
|
||||
AddWorldPoint(p1W - n2 * r, outPtsRbLocal);
|
||||
|
||||
AddWorldPoint(centerW + n1 * r, outPtsRbLocal);
|
||||
AddWorldPoint(centerW - n1 * r, outPtsRbLocal);
|
||||
AddWorldPoint(centerW + n2 * r, outPtsRbLocal);
|
||||
AddWorldPoint(centerW - n2 * r, outPtsRbLocal);
|
||||
}
|
||||
|
||||
void AddWorldPoint(Vector3 world, List<Vector3> outPtsRbLocal)
|
||||
{
|
||||
// rb local(即本脚本 transform 的 local)
|
||||
// 注意:这里假设脚本挂在 Rigidbody 所在物体上(通常是这样)
|
||||
outPtsRbLocal.Add(transform.InverseTransformPoint(world));
|
||||
up.Normalize();
|
||||
var right = Vector3.Normalize(Vector3.Cross(up, Vector3.forward));
|
||||
var forward = Vector3.Cross(up, right);
|
||||
draw(position - up * r, position + up * r, color, duration);
|
||||
draw(position - right * r, position + right * r, color, duration);
|
||||
draw(position - forward * r, position + forward * r, color, duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Assets/Scripts/Test/BobberFloatingTest.cs
Normal file
9
Assets/Scripts/Test/BobberFloatingTest.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace NBF
|
||||
{
|
||||
public class BobberFloatingTest : MonoBehaviour
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Test/BobberFloatingTest.cs.meta
Normal file
3
Assets/Scripts/Test/BobberFloatingTest.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75d9ef57b5894116b65988fda7760fc6
|
||||
timeCreated: 1772260103
|
||||
317
Assets/Scripts/Test/CentimeterBuoyancy.cs
Normal file
317
Assets/Scripts/Test/CentimeterBuoyancy.cs
Normal file
@@ -0,0 +1,317 @@
|
||||
using UnityEngine;
|
||||
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
[RequireComponent(typeof(Collider))]
|
||||
public class CentimeterBuoyancy : MonoBehaviour
|
||||
{
|
||||
[Header("浮力设置")]
|
||||
[SerializeField] private float waterDensity = 1000f; // 水的密度 kg/m³
|
||||
[SerializeField] private float buoyancyScale = 1f; // 浮力缩放系数
|
||||
[SerializeField] private float dragScale = 1f; // 水中阻力系数
|
||||
|
||||
[Header("水面设置")]
|
||||
[SerializeField] private float waterLevel = 0f; // 水面高度
|
||||
[SerializeField] private LayerMask waterLayer; // 水体层级
|
||||
|
||||
[Header("调试")]
|
||||
[SerializeField] private bool showDebugInfo = true;
|
||||
[SerializeField] private Color debugColor = Color.cyan;
|
||||
|
||||
private Rigidbody rb;
|
||||
private Collider objCollider;
|
||||
private float volumeInCm;
|
||||
private Vector3[] samplePoints;
|
||||
private Bounds localBounds;
|
||||
|
||||
void Start()
|
||||
{
|
||||
rb = GetComponent<Rigidbody>();
|
||||
objCollider = GetComponent<Collider>();
|
||||
|
||||
// 计算物体体积(立方厘米)
|
||||
volumeInCm = CalculateVolumeInCm();
|
||||
|
||||
// 计算局部空间的包围盒
|
||||
CalculateLocalBounds();
|
||||
|
||||
if (showDebugInfo)
|
||||
{
|
||||
Debug.Log($"物体体积: {volumeInCm:F2} cm³,质量: {rb.mass * 1000:F2} g");
|
||||
Debug.Log($"局部包围盒: 中心 {localBounds.center}, 大小 {localBounds.size}");
|
||||
}
|
||||
|
||||
// 初始化采样点
|
||||
InitializeSamplePoints();
|
||||
}
|
||||
|
||||
void FixedUpdate()
|
||||
{
|
||||
ApplyBuoyancy();
|
||||
ApplyWaterDrag();
|
||||
}
|
||||
|
||||
// 计算局部空间的包围盒
|
||||
void CalculateLocalBounds()
|
||||
{
|
||||
// 获取碰撞器的局部空间边界
|
||||
if (objCollider is BoxCollider box)
|
||||
{
|
||||
localBounds = new Bounds(box.center, box.size);
|
||||
}
|
||||
else if (objCollider is SphereCollider sphere)
|
||||
{
|
||||
Vector3 size = Vector3.one * sphere.radius * 2;
|
||||
localBounds = new Bounds(sphere.center, size);
|
||||
}
|
||||
else if (objCollider is CapsuleCollider capsule)
|
||||
{
|
||||
float radius = capsule.radius;
|
||||
float height = capsule.height;
|
||||
Vector3 size;
|
||||
|
||||
// 根据胶囊方向确定尺寸
|
||||
switch (capsule.direction)
|
||||
{
|
||||
case 0: // X轴
|
||||
size = new Vector3(height, radius * 2, radius * 2);
|
||||
break;
|
||||
case 1: // Y轴
|
||||
size = new Vector3(radius * 2, height, radius * 2);
|
||||
break;
|
||||
case 2: // Z轴
|
||||
size = new Vector3(radius * 2, radius * 2, height);
|
||||
break;
|
||||
default:
|
||||
size = new Vector3(radius * 2, height, radius * 2);
|
||||
break;
|
||||
}
|
||||
localBounds = new Bounds(capsule.center, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 对于复杂碰撞器,使用世界边界转换到局部空间
|
||||
Bounds worldBounds = objCollider.bounds;
|
||||
localBounds = new Bounds(
|
||||
transform.InverseTransformPoint(worldBounds.center),
|
||||
transform.InverseTransformVector(worldBounds.size)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 计算体积(立方厘米)
|
||||
float CalculateVolumeInCm()
|
||||
{
|
||||
Bounds bounds = objCollider.bounds;
|
||||
Vector3 sizeInCm = bounds.size * 100f; // 转换为厘米
|
||||
|
||||
// 根据不同碰撞器类型估算体积
|
||||
if (objCollider is BoxCollider)
|
||||
{
|
||||
return sizeInCm.x * sizeInCm.y * sizeInCm.z;
|
||||
}
|
||||
else if (objCollider is SphereCollider)
|
||||
{
|
||||
float radiusInCm = bounds.extents.x * 100f;
|
||||
return (4f / 3f) * Mathf.PI * Mathf.Pow(radiusInCm, 3);
|
||||
}
|
||||
else if (objCollider is CapsuleCollider)
|
||||
{
|
||||
CapsuleCollider capsule = objCollider as CapsuleCollider;
|
||||
float radiusInCm = capsule.radius * 100f;
|
||||
float heightInCm = capsule.height * 100f;
|
||||
return Mathf.PI * radiusInCm * radiusInCm * heightInCm;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 对于复杂碰撞器,使用边界框估算
|
||||
return sizeInCm.x * sizeInCm.y * sizeInCm.z * 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化采样点
|
||||
void InitializeSamplePoints()
|
||||
{
|
||||
int sampleCount = 3; // 每个维度采样点数(减少采样点以提高性能)
|
||||
|
||||
// 创建网格采样点用于检测浸入深度
|
||||
samplePoints = new Vector3[sampleCount * sampleCount * sampleCount];
|
||||
|
||||
int index = 0;
|
||||
for (int x = 0; x < sampleCount; x++)
|
||||
{
|
||||
for (int y = 0; y < sampleCount; y++)
|
||||
{
|
||||
for (int z = 0; z < sampleCount; z++)
|
||||
{
|
||||
// 生成 -0.5 到 0.5 范围内的归一化坐标
|
||||
float nx = (float)x / (sampleCount - 1) - 0.5f;
|
||||
float ny = (float)y / (sampleCount - 1) - 0.5f;
|
||||
float nz = (float)z / (sampleCount - 1) - 0.5f;
|
||||
|
||||
samplePoints[index] = new Vector3(nx, ny, nz);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyBuoyancy()
|
||||
{
|
||||
float immersedVolume = 0f;
|
||||
float totalImmersedDepth = 0f;
|
||||
int immersedPoints = 0;
|
||||
Vector3 buoyancyCenter = Vector3.zero;
|
||||
|
||||
// 计算浸入体积
|
||||
foreach (Vector3 normalizedPoint in samplePoints)
|
||||
{
|
||||
// 将归一化坐标转换为局部空间坐标
|
||||
Vector3 localPoint = new Vector3(
|
||||
localBounds.center.x + normalizedPoint.x * localBounds.size.x,
|
||||
localBounds.center.y + normalizedPoint.y * localBounds.size.y,
|
||||
localBounds.center.z + normalizedPoint.z * localBounds.size.z
|
||||
);
|
||||
|
||||
// 转换到世界空间
|
||||
Vector3 worldPoint = transform.TransformPoint(localPoint);
|
||||
|
||||
// 调试:打印第一个点的坐标
|
||||
if (showDebugInfo && immersedPoints == 0 && samplePoints.Length > 0)
|
||||
{
|
||||
Debug.DrawLine(worldPoint, worldPoint + Vector3.up * 0.01f, Color.red, 0.1f);
|
||||
}
|
||||
|
||||
if (worldPoint.y < waterLevel)
|
||||
{
|
||||
immersedVolume += 1f;
|
||||
totalImmersedDepth += (waterLevel - worldPoint.y);
|
||||
buoyancyCenter += worldPoint;
|
||||
immersedPoints++;
|
||||
}
|
||||
}
|
||||
|
||||
// 计算浸入比例
|
||||
float immersedRatio = (float)immersedPoints / samplePoints.Length;
|
||||
|
||||
if (immersedRatio < 0.01f)
|
||||
return; // 几乎没有浸入,不施加浮力
|
||||
|
||||
// 计算平均浸入深度
|
||||
float averageImmersedDepth = immersedPoints > 0 ? totalImmersedDepth / immersedPoints : 0;
|
||||
|
||||
// 计算浮力中心
|
||||
if (immersedPoints > 0)
|
||||
{
|
||||
buoyancyCenter /= immersedPoints;
|
||||
}
|
||||
|
||||
// 计算实际浸入体积(立方厘米)
|
||||
float actualImmersedVolumeCm = volumeInCm * immersedRatio;
|
||||
|
||||
// 转换为立方米(1 m³ = 1,000,000 cm³)
|
||||
float actualImmersedVolumeM = actualImmersedVolumeCm / 1000000f;
|
||||
|
||||
// 计算浮力 = 水的密度 * 重力加速度 * 浸入体积
|
||||
// 水的密度默认 1000 kg/m³,重力加速度 9.8 m/s²
|
||||
float buoyancyForceMagnitude = waterDensity * Physics.gravity.magnitude * actualImmersedVolumeM * buoyancyScale;
|
||||
|
||||
// 根据浸入深度调整浮力(更深的地方浮力更大)
|
||||
float depthFactor = Mathf.Clamp01(averageImmersedDepth * 10f); // 每10cm深度增加一倍
|
||||
buoyancyForceMagnitude *= (1f + depthFactor);
|
||||
|
||||
// 施加浮力
|
||||
Vector3 buoyancyForce = Vector3.up * buoyancyForceMagnitude;
|
||||
rb.AddForceAtPosition(buoyancyForce, buoyancyCenter, ForceMode.Force);
|
||||
|
||||
// 施加浮力产生的扭矩(使物体自然上浮)
|
||||
Vector3 torque = Vector3.Cross(buoyancyCenter - rb.worldCenterOfMass, buoyancyForce);
|
||||
rb.AddTorque(torque * 0.1f);
|
||||
|
||||
if (showDebugInfo)
|
||||
{
|
||||
// 绘制浮力线
|
||||
Debug.DrawLine(buoyancyCenter, buoyancyCenter + buoyancyForce * 0.001f, debugColor, 0.1f);
|
||||
|
||||
// 绘制浸没点
|
||||
foreach (Vector3 normalizedPoint in samplePoints)
|
||||
{
|
||||
Vector3 localPoint = new Vector3(
|
||||
localBounds.center.x + normalizedPoint.x * localBounds.size.x,
|
||||
localBounds.center.y + normalizedPoint.y * localBounds.size.y,
|
||||
localBounds.center.z + normalizedPoint.z * localBounds.size.z
|
||||
);
|
||||
Vector3 worldPoint = transform.TransformPoint(localPoint);
|
||||
|
||||
if (worldPoint.y < waterLevel)
|
||||
{
|
||||
Debug.DrawLine(worldPoint, worldPoint + Vector3.up * 0.005f, debugColor, 0.1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyWaterDrag()
|
||||
{
|
||||
float immersedRatio = CalculateImmersedRatio();
|
||||
|
||||
if (immersedRatio > 0.01f)
|
||||
{
|
||||
// 线性阻力
|
||||
Vector3 dragForce = -rb.linearVelocity * dragScale * immersedRatio * 10f;
|
||||
rb.AddForce(dragForce, ForceMode.Force);
|
||||
|
||||
// 角阻力
|
||||
Vector3 angularDrag = -rb.angularVelocity * dragScale * immersedRatio * 5f;
|
||||
rb.AddTorque(angularDrag, ForceMode.Force);
|
||||
}
|
||||
}
|
||||
|
||||
float CalculateImmersedRatio()
|
||||
{
|
||||
int immersedPoints = 0;
|
||||
|
||||
foreach (Vector3 normalizedPoint in samplePoints)
|
||||
{
|
||||
Vector3 localPoint = new Vector3(
|
||||
localBounds.center.x + normalizedPoint.x * localBounds.size.x,
|
||||
localBounds.center.y + normalizedPoint.y * localBounds.size.y,
|
||||
localBounds.center.z + normalizedPoint.z * localBounds.size.z
|
||||
);
|
||||
Vector3 worldPoint = transform.TransformPoint(localPoint);
|
||||
|
||||
if (worldPoint.y < waterLevel)
|
||||
{
|
||||
immersedPoints++;
|
||||
}
|
||||
}
|
||||
|
||||
return (float)immersedPoints / samplePoints.Length;
|
||||
}
|
||||
|
||||
void OnDrawGizmosSelected()
|
||||
{
|
||||
if (!showDebugInfo || !Application.isPlaying) return;
|
||||
|
||||
// 绘制局部包围盒
|
||||
Gizmos.color = Color.yellow;
|
||||
Vector3[] corners = new Vector3[8];
|
||||
|
||||
// 计算局部包围盒的8个角在世界空间的位置
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
Vector3 localCorner = localBounds.center;
|
||||
localCorner.x += (i & 1) == 0 ? -localBounds.extents.x : localBounds.extents.x;
|
||||
localCorner.y += (i & 2) == 0 ? -localBounds.extents.y : localBounds.extents.y;
|
||||
localCorner.z += (i & 4) == 0 ? -localBounds.extents.z : localBounds.extents.z;
|
||||
|
||||
corners[i] = transform.TransformPoint(localCorner);
|
||||
}
|
||||
|
||||
// 绘制包围盒的边
|
||||
int[] edges = new int[] { 0,1, 1,3, 3,2, 2,0, 4,5, 5,7, 7,6, 6,4, 0,4, 1,5, 2,6, 3,7 };
|
||||
for (int i = 0; i < edges.Length; i += 2)
|
||||
{
|
||||
Gizmos.DrawLine(corners[edges[i]], corners[edges[i + 1]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Test/CentimeterBuoyancy.cs.meta
Normal file
3
Assets/Scripts/Test/CentimeterBuoyancy.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3481fc2889d14a00a9de205cc39d9c33
|
||||
timeCreated: 1772262032
|
||||
105
Assets/Scripts/Test/FloatBobberController.cs
Normal file
105
Assets/Scripts/Test/FloatBobberController.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class FloatBobberController : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Rigidbody _rigidbody;
|
||||
[Header("水属性")] public float waterLevel = 0f;
|
||||
|
||||
[Header("浮漂最大浮力")] public float bobberVolume = 30f; // 浮漂最大浮力 (cm³)
|
||||
|
||||
public float bobberHeight = 0.25f; // 浮漂长度,用来决定躺漂角度
|
||||
|
||||
[Header("配件重量")] public float sinkerWeight = 2f;
|
||||
public float baitWeight = 0.5f;
|
||||
public float hookWeight = 0.2f;
|
||||
|
||||
[Header("Behaviour")] public float fallSpeed = 8f;
|
||||
public float riseSpeed = 3f;
|
||||
// public float smoothDamping = 8f; // 插值平滑
|
||||
|
||||
[Header("Noise")] public float noiseAmp = 0.015f;
|
||||
public float noiseFreq = 1.5f;
|
||||
|
||||
float impulseForce = 0f;
|
||||
float impulseDecay = 4f;
|
||||
|
||||
void FixedUpdate()
|
||||
{
|
||||
SimulateBobber();
|
||||
}
|
||||
|
||||
void SimulateBobber()
|
||||
{
|
||||
if (!_rigidbody.isKinematic) return;
|
||||
float totalDownwardWeight = sinkerWeight + baitWeight + hookWeight;
|
||||
|
||||
float maxBuoyancy = bobberVolume; // 最大浮力 = 体积
|
||||
float netBuoyancy = maxBuoyancy - totalDownwardWeight;
|
||||
|
||||
float targetY;
|
||||
|
||||
// -------------------------
|
||||
// 1. 判断浮漂应该沉多少(吃水深度)
|
||||
// -------------------------
|
||||
if (netBuoyancy > 0)
|
||||
{
|
||||
float buoyPercent = Mathf.Clamp01(netBuoyancy / maxBuoyancy);
|
||||
float rise = buoyPercent * 0.1f; // 浮漂露出水面的高度
|
||||
|
||||
targetY = waterLevel + rise;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 净浮力为负 → 说明浮漂整体被拉下,沉入水中
|
||||
float sinkDistance = Mathf.Abs(netBuoyancy) * 0.03f;
|
||||
targetY = waterLevel - sinkDistance;
|
||||
}
|
||||
|
||||
targetY += Mathf.Sin(Time.time * noiseFreq) * noiseAmp; // 微扰模拟波浪
|
||||
|
||||
// 顿口/顶漂力
|
||||
if (impulseForce != 0f)
|
||||
{
|
||||
targetY += impulseForce * Time.deltaTime;
|
||||
impulseForce = Mathf.Lerp(impulseForce, 0, Time.deltaTime * impulseDecay);
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// ③ 上浮 / 下沉差速
|
||||
// -----------------------------
|
||||
float y = transform.position.y;
|
||||
float diff = targetY - y;
|
||||
|
||||
if (diff > 0) // 上浮
|
||||
y += diff * Time.deltaTime * riseSpeed;
|
||||
else
|
||||
y += diff * Time.deltaTime * fallSpeed;
|
||||
|
||||
transform.position = new Vector3(transform.position.x, y, transform.position.z);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// 外部控制接口
|
||||
// ----------------------------------------
|
||||
|
||||
public void TriggerDownPulse(float s = 0.8f)
|
||||
{
|
||||
impulseForce -= Mathf.Abs(s);
|
||||
}
|
||||
|
||||
public void TriggerUpPulse(float s = 0.8f)
|
||||
{
|
||||
impulseForce += Mathf.Abs(s);
|
||||
}
|
||||
|
||||
public void AddFishPull(float v)
|
||||
{
|
||||
sinkerWeight += v;
|
||||
}
|
||||
|
||||
public void ReleaseFishPull(float v)
|
||||
{
|
||||
sinkerWeight -= v;
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/Test/FloatBobberController.cs.meta
Normal file
3
Assets/Scripts/Test/FloatBobberController.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f32660b4549141fbb754dfef9a523eda
|
||||
timeCreated: 1772264277
|
||||
Reference in New Issue
Block a user