using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; public class GameState { private readonly UniQuake uq; private GameObject worldGameObject; private readonly Dictionary entities = new Dictionary(); public GameState(UniQuake uniQuake) { uq = uniQuake; } public void Destroy() { DestroyEntities(); DestroyWorld(); } public void NewMap(BrushModel worldModel) { Destroy(); Resources.UnloadUnusedAssets(); worldGameObject = new GameObject(worldModel.Name) { layer = (int)uq.GameLayer }; // The first sub-model contains all of the static geometry var subModel = worldModel.GetSubModel(0); var subModelGO = CreateWorldBrushObject(subModel); subModelGO.transform.SetParent(worldGameObject.transform); } private GameObject CreateWorldBrushObject(BrushModel.SubModel subModel) { var subModelGO = new GameObject(subModel.Name) { layer = (int)uq.GameLayer }; foreach (var surfaceMesh in subModel.SurfaceMeshes) { var meshGO = new GameObject(surfaceMesh.Mesh.name) { layer = (int)uq.GameLayer }; meshGO.transform.SetParent(subModelGO.transform); var meshFilter = meshGO.AddComponent(); meshFilter.sharedMesh = surfaceMesh.Mesh; var meshRenderer = meshGO.AddComponent(); uq.CurrentStyle.SetupWorldRenderer(meshRenderer); meshRenderer.material = uq.CurrentStyle.CreateWorldMaterial(surfaceMesh.Flags); // TODO FIXME this currently leaks Materials uint texNum = surfaceMesh.TextureNum; if (uq.GameAssets.TryGetTexture(texNum, out var texture)) { uint fbNum = surfaceMesh.FullBrightNum; uq.GameAssets.TryGetTexture(fbNum, out var fullBright); uq.GameAssets.TryGetLightmap(surfaceMesh.Lightmap, out var lightmap); uq.CurrentStyle.SetWorldTextures(meshRenderer.material, texture, fullBright, lightmap); } } return subModelGO; } private GameObject CreateEntityBrushObject(BrushModel.SubModel subModel) { var subModelGO = new GameObject(subModel.Name) { layer = (int)uq.GameLayer }; foreach (var surfaceMesh in subModel.SurfaceMeshes) { var meshGO = new GameObject(surfaceMesh.Mesh.name) { layer = (int)uq.GameLayer }; meshGO.transform.SetParent(subModelGO.transform); var meshFilter = meshGO.AddComponent(); meshFilter.sharedMesh = surfaceMesh.Mesh; var meshRenderer = meshGO.AddComponent(); uq.CurrentStyle.SetupEntityRenderer(meshRenderer); meshRenderer.material = uq.CurrentStyle.CreateEntityMaterial(false); // TODO FIXME this currently leaks Materials uint texNum = surfaceMesh.TextureNum; if (uq.GameAssets.TryGetTexture(texNum, out var texture)) { uint fbNum = surfaceMesh.FullBrightNum; uq.GameAssets.TryGetTexture(fbNum, out var fullBright); uq.CurrentStyle.SetEntityTextures(meshRenderer.material, texture, fullBright); } } return subModelGO; } private void DestroyWorld() { if (worldGameObject != null) { Object.Destroy(worldGameObject); worldGameObject = null; } } public Entity GetEntity(int entityNum) { if (!entities.TryGetValue(entityNum, out var entity)) { entity = new Entity(entityNum, uq.CurrentStyle, entityNum == 0 ? uq.ViewModelLayer : uq.GameLayer); entities.Add(entityNum, entity); } return entity; } public void SetEntityAliasModel(int entityNum, string modelName) { var entity = GetEntity(entityNum); if (!uq.GameAssets.TryGetAliasModel(modelName, out var aliasModel)) { Debug.LogWarning($"Unknown alias model name: {modelName}"); return; } entity.SetAliasModel(aliasModel); } public void SetEntityBrushModel(int entityNum, string modelName) { var entity = GetEntity(entityNum); if (!uq.GameAssets.TryGetBrushModel(modelName, out var brushModel)) { Debug.LogWarning($"Unknown brush model name: {modelName}"); return; } var brushModelGO = new GameObject(brushModel.Name) { layer = (int)Layers.Game1 }; for (int i = 0; i < brushModel.SubModelCount; ++i) { var subModelGO = CreateEntityBrushObject(brushModel.GetSubModel(i)); subModelGO.transform.SetParent(brushModelGO.transform); } entity.SetBrushModel(brushModelGO); } public void SetEntityWorldModel(int entityNum, int subModelNum) { var entity = GetEntity(entityNum); if (!uq.GameAssets.TryGetWorldSubModel(subModelNum, out var subModel)) { Debug.LogWarning($"Invalid world sub-model number: {subModelNum}"); return; } // TODO: these relatively complex world game objects are going to get destroyed and re-created all the time // as the player moves through the map and moves in and out of range of these entities. This can and should // be done more efficiently by creating the game objects only once and enabling/disabling them on demand. var worldModelGO = CreateWorldBrushObject(subModel); entity.SetWorldModel(worldModelGO); } public void RemoveEntity(int entityNum) { if (entities.TryGetValue(entityNum, out var entity)) { entity.Destroy(); entities.Remove(entityNum); } } private void DestroyEntities() { foreach (var entity in entities.Values) { entity.Destroy(); } entities.Clear(); } }