update terrain holder

This commit is contained in:
Le Duc Anh
2026-02-27 14:12:18 +07:00
parent 955276d54d
commit 47f9540424
10 changed files with 1176 additions and 889 deletions
@@ -15,7 +15,7 @@ MonoBehaviour:
m_DefaultGroup: 712e3991f28e549e7a56ee582a977810
m_currentHash:
serializedVersion: 2
Hash: 51bf3b9e2ab3f1e6b3ab9b39e73dcd30
Hash: 00000000000000000000000000000000
m_OptimizeCatalogSize: 0
m_BuildRemoteCatalog: 0
m_CatalogRequestsTimeout: 0
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,111 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &2883623149498973863
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 7046601733265648723}
- component: {fileID: 3280156353392080962}
- component: {fileID: 4477777324146565283}
- component: {fileID: 228556847582898674}
m_Layer: 6
m_Name: Terrain Block 16-19
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &7046601733265648723
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2883623149498973863}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &3280156353392080962
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2883623149498973863}
m_Mesh: {fileID: 4300000, guid: 299a52671c4c59240b12481d03cffe09, type: 2}
--- !u!23 &4477777324146565283
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2883623149498973863}
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_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 7320b29ef6acd4149b93b64aeefd87bd, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!64 &228556847582898674
MeshCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2883623149498973863}
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: 5
m_Convex: 0
m_CookingOptions: 30
m_Mesh: {fileID: 4300000, guid: 299a52671c4c59240b12481d03cffe09, type: 2}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 636146f88578b4e4697c4473e8bb0744
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: d0c06c588e2a6442488a3542551fb243
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -17,6 +17,8 @@ namespace CSNetwork.Protocols.RPCData
public int delete_time;
public int create_time;
public int lastlogin_time;
//TODO: Update user position before move to another world (scene).
public float posx;
public float posy;
public float posz;
@@ -86,7 +86,7 @@ namespace BrewMonster
[MenuItem("Tools/Addressable/Update Addressable Path For a61 Terrain")]
public static void UpdateAddressablePathFora61Terrain()
{
UpdateAddressableAddressesPath("a61_terrain", _terrainPathPrefix, "");
UpdateAddressableAddressesPath("a61-terrain", _terrainPathPrefix, ".prefab");
}
public static void UpdateAddressableAddressesPath(string groupName, string prefixToRemove, string subfix)
@@ -13,19 +13,26 @@ namespace BrewMonster
public string assetPath;
private GameObject _instance;
public bool _isLoading;
public bool _isLoaded;
private bool _isLoading;
private bool _isLoaded;
private int _loadRequestId;
public bool IsLoaded => _isLoaded;
public bool IsLoading => _isLoading;
// some object' position is base on the center of the mesh. So we don't need to update the position at start up.
public bool NeedToUpdatePositionAtStartUp;
public Vector3 ObjectPosition;
public Vector3 ObjectPositionOxz;
void Awake()
{
ObjectPosition = transform.position;
ObjectPositionOxz = new Vector3(ObjectPosition.x, 0, ObjectPosition.z);
if (NeedToUpdatePositionAtStartUp)
{
ObjectPosition = transform.position;
ObjectPositionOxz = new Vector3(ObjectPosition.x, 0, ObjectPosition.z);
}
}
public async UniTask LoadAsset()
@@ -86,7 +93,14 @@ namespace BrewMonster
if (_instance != null)
{
Destroy(_instance);
if (Application.isPlaying)
{
Destroy(_instance);
}
else
{
DestroyImmediate(_instance);
}
_instance = null;
}
else if (transform.childCount > 0)
@@ -94,7 +108,14 @@ namespace BrewMonster
// Backward-compatible cleanup if older versions instantiated children without tracking.
for (int i = transform.childCount - 1; i >= 0; i--)
{
Destroy(transform.GetChild(i).gameObject);
if (Application.isPlaying)
{
Destroy(transform.GetChild(i).gameObject);
}
else
{
DestroyImmediate(transform.GetChild(i).gameObject);
}
}
}
@@ -1,11 +1,14 @@
using UnityEngine;
using Cysharp.Threading.Tasks;
using System.Threading;
using BrewMonster.Network;
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
#endif
namespace BrewMonster
@@ -13,9 +16,190 @@ namespace BrewMonster
public class TerrainHolder : MonoBehaviour
{
[SerializeField] private AddressableObject[] _addressableObjects;
[SerializeField] private float _loadImmediateDistance = 150f;
[SerializeField] private float _unloadDistance = 300f;
[SerializeField] private float _minHostMoveToUpdate = 5f;
private List<AddressableObject> _candidatesForLoading = new List<AddressableObject>();
private List<AddressableObject> _objectsToUnload = new List<AddressableObject>();
private Vector3 _lastHostPosOxz;
private bool _hasLastHostPos = false;
private Vector3 _currentHostPosOxz;
private bool _hostPosReady = false;
private AddressableObject _currentObjectToCheck; // the object that we're currently checking for loading/unloading.
private bool _needToProcessLoadAndUnload = false;
private float _realTimeSinceStartUp;
private CancellationTokenSource _cts;
#region Unity Lifecycle
private void Awake()
{
_cts = new CancellationTokenSource();
StartStreamingProcess(_cts.Token);
}
private void Update()
{
UpdateGlobalDataForStreaming();
if (_needToProcessLoadAndUnload)
{
ProcessLoadAndUnload();
_needToProcessLoadAndUnload = false;
}
}
private void OnDisable()
{
// unload all the addressable objects.
foreach (var addressableObject in _addressableObjects)
{
if (addressableObject == null) continue;
addressableObject.UnloadAsset();
}
}
private void OnDestroy()
{
// cancel the streaming process.
if (_cts != null)
{
_cts.Cancel();
_cts.Dispose();
}
}
#endregion
#region private functions
// This function is expected to be called once at the beginning of a world (scene)
private void StartStreamingProcess(CancellationToken destroyToken)
{
if (destroyToken.IsCancellationRequested) return;
// Get the host player position from the UnityGameSession. This when user choose a role and enter a world (scene).
_currentHostPosOxz = new Vector3(UnityGameSession.Instance.GetRoleInfo().posx, 0f, UnityGameSession.Instance.GetRoleInfo().posz);
UniTask.RunOnThreadPool(async () =>
{
await StreamByDistanceLoop(destroyToken);
}).Forget();
}
/// <summary>
/// This is the main loop that keep checking the objects that are in the immediate loading range or padding range.
/// If the host moved too little, we will skip the streaming.
/// </summary>
/// <param name="destroyToken"></param>
/// <returns></returns>
private async UniTask StreamByDistanceLoop(CancellationToken destroyToken)
{
if (_addressableObjects == null || _addressableObjects.Length == 0)
return;
if (_unloadDistance < _loadImmediateDistance)
_unloadDistance = _loadImmediateDistance;
float immediateSqr = _loadImmediateDistance * _loadImmediateDistance;
float paddingSqr = _unloadDistance * _unloadDistance;
float minMoveSqr = _minHostMoveToUpdate * _minHostMoveToUpdate;
#if UNITY_EDITOR
while (!destroyToken.IsCancellationRequested)
{
if (_hasLastHostPos)
{
if ((_currentHostPosOxz - _lastHostPosOxz).sqrMagnitude < minMoveSqr)
{
await UniTask.Delay(10, cancellationToken: destroyToken);
continue;
}
}
_hasLastHostPos = true;
_lastHostPosOxz = _currentHostPosOxz;
TickStreaming(_currentHostPosOxz, immediateSqr, paddingSqr);
}
}
private void TickStreaming(Vector3 targetPos, float immediateSqr, float paddingSqr)
{
if (_addressableObjects == null || _addressableObjects.Length == 0)
return;
int count = _addressableObjects.Length;
lock (_objectsToUnload)
{
targetPos.y = 0f; // we only consider the Oxz plane.
float distanceSqr = 0f;
for (int i = 0; i < count; i++)
{
_currentObjectToCheck = _addressableObjects[i];
if (_currentObjectToCheck == null) continue;
distanceSqr = (_currentObjectToCheck.ObjectPositionOxz - targetPos).sqrMagnitude;
if (distanceSqr <= immediateSqr)
{
if (_currentObjectToCheck.IsLoaded || _currentObjectToCheck.IsLoading)
{
continue;
}
if (!_candidatesForLoading.Contains(_currentObjectToCheck))
{
_candidatesForLoading.Add(_currentObjectToCheck);
}
}
else if (distanceSqr > paddingSqr)
{
if (_currentObjectToCheck.IsLoaded || _currentObjectToCheck.IsLoading)
{
if (!_objectsToUnload.Contains(_currentObjectToCheck))
{
_objectsToUnload.Add(_currentObjectToCheck);
}
}
}
}
_needToProcessLoadAndUnload = true;
}
}
/// <summary>
/// process to call Load and Unload on each addressable object that we need to.
/// </summary>
private void ProcessLoadAndUnload()
{
for (int i = 0; i < _candidatesForLoading.Count; i++)
{
_candidatesForLoading[i].LoadAsset().Forget();
}
_candidatesForLoading.Clear();
for (int i = 0; i < _objectsToUnload.Count; i++)
{
_objectsToUnload[i].UnloadAsset();
}
_objectsToUnload.Clear();
}
private void UpdateGlobalDataForStreaming()
{
_realTimeSinceStartUp = Time.realtimeSinceStartup;
_currentHostPosOxz = CECGameRun.Instance.GetHostPlayer().GetPosVector3();
_currentHostPosOxz.y = 0f;
_hostPosReady = true;
}
#endregion
#if UNITY_EDITOR
private int MASK_TEXTURE_HASH = Shader.PropertyToID("_MaskTexture");
private int MASK_TEXTURE2_HASH = Shader.PropertyToID("_MaskTexture2");
private const string _terrainPathPrefix = "Assets/ModelRenderer/Art/Terrain";
@@ -146,98 +330,48 @@ namespace BrewMonster
// }
}
// private void PrepareAllMaskTextures()
// {
// if (_originalObjects == null || _originalObjects.Length == 0)
// {
// Debug.LogWarning("[TerrainHolder] _originalObjects is empty.");
// return;
// }
[ContextMenu("Load All Addressable Objects")]
private void LoadAllAddressableObjects()
{
for (int i = 0; i < _addressableObjects.Length; i++)
{
_addressableObjects[i].LoadAsset().Forget();
}
}
// if (string.IsNullOrWhiteSpace(_worldName))
// {
// Debug.LogError("[TerrainHolder] _worldName is empty. Please set it before running setup.");
// return;
// }
[ContextMenu("Unload All Addressable Objects")]
private void UnloadAllAddressableObjects()
{
for (int i = 0; i < _addressableObjects.Length; i++)
{
_addressableObjects[i].UnloadAsset();
}
}
// string worldFolder = $"{_terrainPathPrefix}/{_worldName}";
// EnsureFolderExists(worldFolder);
[ContextMenu("Update Addressable Objects Position Mesh Base")]
private void UpdateAddressableObjectsPositionMeshBase()
{
Mesh mesh;
for (int i = 0; i < _addressableObjects.Length; i++)
{
mesh = _addressableObjects[i].GetComponentInChildren<MeshFilter>().sharedMesh;
// var createdAddressableObjects = new List<AddressableObject>(_originalObjects.Length);
if (mesh == null) continue;
// for (int i = 0; i < _originalObjects.Length; i++)
// {
// GameObject currentTerrainObject = _originalObjects[i];
// if (currentTerrainObject == null)
// {
// continue;
// }
Vector3[] vertices = mesh.vertices;
// calculate the center of the mesh
Vector3 center = Vector3.zero;
for (int j = 0; j < vertices.Length; j++)
{
center += vertices[j];
}
center /= vertices.Length;
// Mesh mesh = GetFirstMesh(currentTerrainObject);
// Material material = currentTerrainObject.GetComponent<Renderer>().sharedMaterial;
// Texture2D maskTexture = material.GetTexture(MASK_TEXTURE_HASH) as Texture2D;
// Texture2D maskTexture2 = material.GetTexture(MASK_TEXTURE2_HASH) as Texture2D;
// if (mesh == null)
// {
// Debug.LogWarning($"[TerrainHolder] No Mesh found on '{currentTerrainObject.name}', skipping.");
// continue;
// }
// string safeName = MakeSafeFileName(currentTerrainObject.name);
// string maskTextureAssetPath = AssetDatabase.GenerateUniqueAssetPath($"{worldFolder}/{safeName}_MaskTexture.png");
// string maskTexture2AssetPath = AssetDatabase.GenerateUniqueAssetPath($"{worldFolder}/{safeName}_MaskTexture2.png");
// string maskTextureAbsolutePath = Path.Combine(Application.dataPath, maskTextureAssetPath.Substring(7));
// string maskTexture2AbsolutePath = Path.Combine(Application.dataPath, maskTexture2AssetPath.Substring(7));
// AssetDatabase.Refresh();
// AAssit.SaveTexture2DToPNG(maskTexture, maskTextureAbsolutePath);
// AAssit.SaveTexture2DToPNG(maskTexture2, maskTexture2AbsolutePath);
// // load mesh from asset
// var meshCopy = AssetDatabase.LoadAssetAtPath<Mesh>(meshAssetPath);
// var materialCopy = AssetDatabase.LoadAssetAtPath<Material>(materialAssetPath);
// var maskTextureCopy = AssetDatabase.LoadAssetAtPath<Texture2D>(maskTextureAssetPath);
// var maskTexture2Copy = AssetDatabase.LoadAssetAtPath<Texture2D>(maskTexture2AssetPath);
// if (maskTextureCopy != null)
// {
// materialCopy.SetTexture(MASK_TEXTURE_HASH, maskTextureCopy);
// }
// if (maskTexture2Copy != null)
// {
// materialCopy.SetTexture(MASK_TEXTURE2_HASH, maskTexture2Copy);
// }
// // set the mesh to the current terrain object
// currentTerrainObject.GetComponent<MeshFilter>().sharedMesh = meshCopy;
// currentTerrainObject.GetComponent<Renderer>().sharedMaterial = materialCopy;
// // Create the addressable anchor object with matching transform.
// var newGo = new GameObject(currentTerrainObject.name);
// Transform srcTr = currentTerrainObject.transform;
// Transform dstTr = newGo.transform;
// // dstTr.SetParent(srcTr.parent, true);
// dstTr.position = srcTr.position;
// dstTr.rotation = srcTr.rotation;
// dstTr.localScale = srcTr.localScale;
// var addressableObject = newGo.AddComponent<AddressableObject>();
// addressableObject.assetPath = meshAssetPath;
// createdAddressableObjects.Add(addressableObject);
// }
// // AssetDatabase.SaveAssets();
// // AssetDatabase.Refresh();
// _addressableObjects = createdAddressableObjects.ToArray();
// // EditorUtility.SetDirty(this);
// // if (gameObject.scene.IsValid())
// // {
// // EditorSceneManager.MarkSceneDirty(gameObject.scene);
// // }
// }
_addressableObjects[i].ObjectPosition = center;
_addressableObjects[i].ObjectPositionOxz = new Vector3(center.x, 0, center.z);
_addressableObjects[i].NeedToUpdatePositionAtStartUp = false;
}
}
private static Mesh GetFirstMesh(GameObject go)
{
@@ -293,6 +427,6 @@ namespace BrewMonster
}
return name.Trim();
}
#endif
#endif
}
}
+2 -2
View File
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fb4f2d427f091a6b43884ed151212e47fdd716d5ac9a633715e6133cd2b12b1c
size 5853337
oid sha256:5ccac29b57e33058978ba297bf08764f131ac73eea29fea04d218d252819c7d3
size 5839191