diff --git a/Assets/Materials/UI/MapAsteroid.mat b/Assets/Materials/UI/MapAsteroid.mat new file mode 100644 index 000000000..a0dbbdb1f --- /dev/null +++ b/Assets/Materials/UI/MapAsteroid.mat @@ -0,0 +1,31 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: MapAsteroid + m_Shader: {fileID: 4800000, guid: 22e2fc798f044a3aa2f6cd71f1df09a7, type: 3} + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 0 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: 3000 + stringTagMap: {} + disabledShaderPasses: [] + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _MainTex: + m_Texture: {fileID: 2800000, guid: 060aefd5a3d5ced4dabd9190a140e76e, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: [] + m_Colors: + - _Color: {r: 1, g: 1, b: 1, a: 1} + m_BuildTextureStacks: [] diff --git a/Assets/Prefabs/RTS/Asteroid Belt.prefab.meta b/Assets/Materials/UI/MapAsteroid.mat.meta similarity index 52% rename from Assets/Prefabs/RTS/Asteroid Belt.prefab.meta rename to Assets/Materials/UI/MapAsteroid.mat.meta index f1298ae38..2c044e9e9 100644 --- a/Assets/Prefabs/RTS/Asteroid Belt.prefab.meta +++ b/Assets/Materials/UI/MapAsteroid.mat.meta @@ -1,7 +1,8 @@ fileFormatVersion: 2 -guid: 1f1bfa2175a13b647888e2949e0cb138 -PrefabImporter: +guid: c1012d5466454651b7279be93ef9af88 +NativeFormatImporter: externalObjects: {} + mainObjectFileID: 2100000 userData: assetBundleName: assetBundleVariant: diff --git a/Assets/Prefabs/RTS/Asteroid Belt.prefab b/Assets/Prefabs/RTS/Asteroid Belt.prefab deleted file mode 100644 index 38df4e9e3..000000000 --- a/Assets/Prefabs/RTS/Asteroid Belt.prefab +++ /dev/null @@ -1,111 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!1 &992254348637625916 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 1779911639310264026} - - component: {fileID: 349597964696402918} - - component: {fileID: 2844878085845264008} - - component: {fileID: 917803079556930035} - - component: {fileID: 4397878771830533054} - m_Layer: 14 - m_Name: Asteroid Belt - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!4 &1779911639310264026 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 992254348637625916} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_Children: [] - m_Father: {fileID: 0} - m_RootOrder: 0 - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!33 &349597964696402918 -MeshFilter: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 992254348637625916} - m_Mesh: {fileID: 0} ---- !u!23 &2844878085845264008 -MeshRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 992254348637625916} - m_Enabled: 1 - m_CastShadows: 1 - m_ReceiveShadows: 1 - m_DynamicOccludee: 1 - m_MotionVectors: 1 - m_LightProbeUsage: 1 - m_ReflectionProbeUsage: 1 - m_RayTracingMode: 2 - m_RayTraceProcedural: 0 - m_RenderingLayerMask: 1 - m_RendererPriority: 0 - m_Materials: - - {fileID: 2100000, guid: c018d99199afeca448c64b9022ac7270, 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: 0 - 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 &917803079556930035 -MeshCollider: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 992254348637625916} - m_Material: {fileID: 0} - m_IsTrigger: 0 - m_Enabled: 1 - serializedVersion: 4 - m_Convex: 0 - m_CookingOptions: 30 - m_Mesh: {fileID: 0} ---- !u!114 &4397878771830533054 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 992254348637625916} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 52538a3309c36fc4583c964a1c0b705f, type: 3} - m_Name: - m_EditorClassIdentifier: diff --git a/Assets/Scenes/ARPG.unity b/Assets/Scenes/ARPG.unity index a7940de8e..0bee4642b 100644 --- a/Assets/Scenes/ARPG.unity +++ b/Assets/Scenes/ARPG.unity @@ -16421,8 +16421,7 @@ MonoBehaviour: TourFollowDistance: 100 TourHeightOffset: 50 TourFollowOffsetDegrees: 55 - AsteroidBeltUI: {fileID: 349597964696402918, guid: 1f1bfa2175a13b647888e2949e0cb138, - type: 3} + MapAsteroidMaterial: {fileID: 2100000, guid: c1012d5466454651b7279be93ef9af88, type: 2} Planet: {fileID: 8287217960984982033, guid: 88ace6073e3db1a46bd3aaf4f971acb9, type: 3} GasGiant: {fileID: 3250623180916197132, guid: 85753b4a7c762274385af384abed7a04, type: 3} diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index 5ee4b474a..eae1fc5bb 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -298,10 +298,6 @@ public class ActionGameManager : MonoBehaviour Settings.GameplaySettings, Debug.Log); ZoneRenderer.ItemManager = ItemManager; - // If hiding minimap asteroids, turn them off to start with - if (!PlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap) - ZoneRenderer.ShowAsteroidUI = false; - // TODO: Process Stories #region Input Handling diff --git a/Assets/Scripts/Gameplay/EntityInstance.cs b/Assets/Scripts/Gameplay/EntityInstance.cs index e263bac27..945661078 100644 --- a/Assets/Scripts/Gameplay/EntityInstance.cs +++ b/Assets/Scripts/Gameplay/EntityInstance.cs @@ -80,6 +80,8 @@ public class EntityInstance : MonoBehaviour var materials = new List<(Material material, List<(MeshRenderer renderer, int index)> submeshes)>(); foreach (var mesh in meshes) { + // The map icon is drawn by MapPresenter, not as part of the hull (SetEntity hands it over). + if (mesh == MapIcon) continue; _meshes.Add(mesh, mesh.sharedMaterials); for (var i = 0; i < mesh.sharedMaterials.Length; i++) { @@ -165,6 +167,9 @@ public class EntityInstance : MonoBehaviour ZoneRenderer = zoneRenderer; var hullData = entity.ItemManager.GetData(entity.Hull) as HullData; + zoneRenderer.Map.RegisterEntity(entity, MapIcon, transform); + MapIcon = null; + if(Shield) Shield.Entity = entity; @@ -187,7 +192,8 @@ public class EntityInstance : MonoBehaviour { var pingInstance = Instantiate(PingPrefab); var pingMesh = pingInstance.GetComponent(); - pingInstance.position = DrawAhead.Position(entity, ActionGameManager.Clock.Lead).ToUnity(); + var drawn = DrawAhead.Position(entity, ActionGameManager.Clock.Lead); + pingInstance.position = new Vector3(drawn.x, MapPresenter.PlaneY, drawn.z); _pingBrightness = pingMesh.material.GetFloat("_Depth"); _currentPing = (pingInstance, pingMesh); }; @@ -366,7 +372,10 @@ public class EntityInstance : MonoBehaviour OnFadedIn = null; ShowUnfadedElements(); } - foreach(var material in _fadeMaterials) material.SetFloat("_Fade", _fade); + { + foreach(var material in _fadeMaterials) material.SetFloat("_Fade", _fade); + ZoneRenderer.Map.SetFade(Entity, _fade); + } } else { @@ -381,7 +390,10 @@ public class EntityInstance : MonoBehaviour OnFadedOut?.Invoke(); OnFadedOut = null; } - foreach(var material in _fadeMaterials) material.SetFloat("_Fade", _fade); + { + foreach(var material in _fadeMaterials) material.SetFloat("_Fade", _fade); + ZoneRenderer.Map.SetFade(Entity, _fade); + } } } diff --git a/Assets/Scripts/ServerShared/PlayerSettings.cs b/Assets/Scripts/ServerShared/PlayerSettings.cs index f46bbad22..d99bc9287 100644 --- a/Assets/Scripts/ServerShared/PlayerSettings.cs +++ b/Assets/Scripts/ServerShared/PlayerSettings.cs @@ -63,7 +63,7 @@ public class PlayerGameplaySettings public class PlayerGraphicsSettings { [Key(0)] public Quality NebulaQuality = Quality.Normal; - [Key(1)] public bool ShowAsteroidsInMinimap; + // Key 1 is retired; an older player.cc that still carries it loads, and it is dropped. // How hard the camera shakes when the player's ship takes an impact: 0 off, 1 full (Feedback.Cue). [Key(2)] public float ScreenShake = 1f; } diff --git a/Assets/Scripts/UI/MainMenu.cs b/Assets/Scripts/UI/MainMenu.cs index bd019ab21..4e64b7569 100644 --- a/Assets/Scripts/UI/MainMenu.cs +++ b/Assets/Scripts/UI/MainMenu.cs @@ -296,9 +296,6 @@ public class MainMenu : MonoBehaviour CloudRenderer.quality = ActionGameManager.PlayerSettings.GraphicsSettings.NebulaQuality; }, Enum.GetNames(typeof(Quality))); - _nextMenu.panel.AddField("Show Asteroids in Minimap", - () => ActionGameManager.PlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap, - b => ActionGameManager.PlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap = b); _nextMenu.panel.AddField("Screen Shake", () => ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake, f => ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake = f, diff --git a/Assets/Scripts/UI/Menu/MapRenderer.cs b/Assets/Scripts/UI/Menu/MapRenderer.cs index e8b1d49bb..d6e585872 100644 --- a/Assets/Scripts/UI/Menu/MapRenderer.cs +++ b/Assets/Scripts/UI/Menu/MapRenderer.cs @@ -51,10 +51,7 @@ public class MapRenderer : MonoBehaviour TintCamera.gameObject.SetActive(true); InfluenceCamera.gameObject.SetActive(true); Title.text = $"Zone: {GameManager.Zone.GalaxyZone.Name}"; - - // If hiding minimap asteroids, turn them back on for the map screen - if (!ActionGameManager.PlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap) - ZoneRenderer.ShowAsteroidUI = true; + GameManager.ZoneRenderer.Map.OpenMapScreen(true); } private void OnDisable() @@ -67,10 +64,7 @@ public class MapRenderer : MonoBehaviour GravityCamera.gameObject.SetActive(false); TintCamera.gameObject.SetActive(false); InfluenceCamera.gameObject.SetActive(false); - - // If hiding minimap asteroids, turn them back off when leaving the map screen - if (!ActionGameManager.PlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap) - ZoneRenderer.ShowAsteroidUI = false; + GameManager.ZoneRenderer.Map.OpenMapScreen(false); } void ReleaseTextures() @@ -129,6 +123,6 @@ public class MapRenderer : MonoBehaviour InfluenceCamera.transform.position = pos; InfluenceCamera.orthographicSize = _size.y * Scale * .5f; - GameManager.ZoneRenderer.SetIconSize(IconSize * Scale); + GameManager.ZoneRenderer.Map.SetMapScreenScale(IconSize * Scale); } } diff --git a/Assets/Scripts/Zone Display/MapPresenter.cs b/Assets/Scripts/Zone Display/MapPresenter.cs new file mode 100644 index 000000000..3e5a87a0d --- /dev/null +++ b/Assets/Scripts/Zone Display/MapPresenter.cs @@ -0,0 +1,215 @@ +using System.Collections.Generic; +using CultMath; +using CultMath.UnityBridge; +using GameCult.Caching; +using UnityEngine; +using UnityEngine.Rendering; +using static CultMath.math; +using float2 = CultMath.float2; +using float3 = CultMath.float3; +using float4 = CultMath.float4; +using quaternion = CultMath.quaternion; + +// The one owner of map-space projection (ruling map-icons-placed-in-2d): where every map icon and belt asteroid is +// drawn, its yaw, its size, its fade, and the contour's depth range. Icons are not world objects: each is a mesh, +// a material instance and an authored rotation read once from the icon child at registration (the child is then +// destroyed), drawn every frame on the Minimap layer at the map plane from sim state through DrawAhead. Roll and +// pitch never reach an icon, and field depth never decides whether one is seen. Both map cameras (the minimap's +// and the map screen's) see these same draws; only the size input differs. +public class MapPresenter +{ + // The map plane. Every map camera is orthographic and looks down from above it, so this height is inside every + // clip range; the offsets only order overlapping icons (planets under stations under ships, belts under all). + public const float PlaneY = 0f; + public const float BeltOffset = -.1f, PlanetOffset = 0f, OrbitalOffset = .1f, ShipOffset = .2f; + + // The one place the heading of an icon is decided (question contact-icon-heading, yaw-for-all): every icon turns + // with its entity's yaw. Another answer changes only this function. + public static float IconYaw(Entity entity, quaternion drawnRotation) + { + var forward = drawnRotation.ToUnity() * Vector3.forward; + return Mathf.Atan2(forward.x, forward.z) * Mathf.Rad2Deg; + } + + private sealed class Icon + { + public Mesh Mesh; + public Material Material; + public Color Base; + public Quaternion Authored; + public float Offset; + public Entity Entity; + public CultRecordKey Planet; + public float Fade; + public Matrix4x4 Drawn; + } + + private readonly List _icons = new List(); + private readonly Dictionary _entityIcons = new Dictionary(); + private readonly Material[] _gravityMaterials; + private readonly int _layer = LayerMask.NameToLayer("Minimap"); + private float _minimapScale = 1, _mapScreenScale; + private bool _mapScreenOpen; + private readonly Material _asteroidMaterial; + private readonly Vector4 _spriteSheet; + private readonly float _asteroidSize; + + public MapPresenter(Material[] gravityMaterials, Material asteroidMaterial, int spriteColumns, int spriteRows, + float asteroidSize) + { + _gravityMaterials = gravityMaterials; + _asteroidMaterial = asteroidMaterial; + _spriteSheet = new Vector4(spriteColumns, spriteRows, 0, 0); + _asteroidSize = asteroidSize; + } + + // The icon size: the map screen's scale while it is open, else the minimap's zoom. + public float IconSize => _mapScreenOpen ? _mapScreenScale : _minimapScale; + public void SetMinimapScale(float scale) => _minimapScale = scale; + public void OpenMapScreen(bool open) => _mapScreenOpen = open; + public void SetMapScreenScale(float scale) => _mapScreenScale = scale; + + public int IconCount => _icons.Count; + // What the last Draw issued, for the smoke. + public int IconDraws { get; private set; } + public int BeltDraws { get; private set; } + public int LastBeltInstances { get; private set; } + public bool TryDrawn(Entity entity, out Matrix4x4 drawn) + { + drawn = default; + if (!_entityIcons.TryGetValue(entity, out var icon)) return false; + drawn = icon.Drawn; + return true; + } + + // Reads the icon child once (mesh, material, rotation relative to its root) and destroys it: from here on no + // world transform carries the icon. + private Icon Take(MeshRenderer renderer, Transform root, float offset) + { + var icon = new Icon + { + Mesh = renderer.GetComponent().sharedMesh, + Material = Object.Instantiate(renderer.sharedMaterial), + Authored = Quaternion.Inverse(root.rotation) * renderer.transform.rotation, + Offset = offset, + Fade = 1, + }; + icon.Base = icon.Material.HasProperty("_Color") ? icon.Material.GetColor("_Color") : Color.white; + Object.Destroy(renderer.gameObject); + _icons.Add(icon); + return icon; + } + + public void RegisterEntity(Entity entity, MeshRenderer renderer, Transform root) + { + var icon = Take(renderer, root, entity is Ship ? ShipOffset : OrbitalOffset); + icon.Entity = entity; + icon.Fade = 0; + _entityIcons[entity] = icon; + } + + public void RegisterPlanet(CultRecordKey key, MeshRenderer renderer, Transform root) => + Take(renderer, root, PlanetOffset).Planet = key; + + // The entity's fade, applied to its icon the same frame the hull's materials take it. + public void SetFade(Entity entity, float fade) + { + if (_entityIcons.TryGetValue(entity, out var icon)) icon.Fade = fade; + } + + public void Unregister(Entity entity) + { + if (!_entityIcons.Remove(entity, out var icon)) return; + _icons.Remove(icon); + Object.Destroy(icon.Material); + } + + private sealed class Belt + { + public float4[] Source; + public ComputeBuffer Buffer; + public MaterialPropertyBlock Block = new MaterialPropertyBlock(); + } + + private readonly Dictionary _belts = new Dictionary(); + private static readonly int BeltProperty = Shader.PropertyToID("_Belt"); + private static readonly int SheetProperty = Shader.PropertyToID("_Sheet"); + private static readonly int PlaneProperty = Shader.PropertyToID("_PlaneY"); + private static readonly int SizeProperty = Shader.PropertyToID("_AsteroidSize"); + + // A belt's map sprites: the 3D belt's per-frame float4s (x, y planar, z spin, w size), uploaded as they are. + public void RegisterBelt(CultRecordKey key, float4[] source) => + _belts[key] = new Belt { Source = source, Buffer = new ComputeBuffer(source.Length, 16) }; + + // One instanced draw per belt: six vertices per asteroid, expanded to a quad on the map plane by the shader. + private void DrawBelt(Belt belt) + { + belt.Buffer.SetData(belt.Source); + belt.Block.SetBuffer(BeltProperty, belt.Buffer); + belt.Block.SetVector(SheetProperty, _spriteSheet); + belt.Block.SetFloat(PlaneProperty, PlaneY + BeltOffset); + belt.Block.SetFloat(SizeProperty, _asteroidSize); + var parameters = new RenderParams(_asteroidMaterial) + { + layer = _layer, matProps = belt.Block, shadowCastingMode = ShadowCastingMode.Off, + worldBounds = new Bounds(Vector3.zero, Vector3.one * 1e6f), + }; + Graphics.RenderPrimitives(parameters, MeshTopology.Triangles, 6, belt.Source.Length); + BeltDraws++; + LastBeltInstances = belt.Source.Length; + } + + // Every registered icon and belt for this frame, from sim state at the drawn time; and the contour's depth range, + // the deepest drawn planet's field depth (the map gravity materials' normalisation, not a camera bound). + public void Draw(Zone zone, float lead) + { + var size = IconSize; + IconDraws = BeltDraws = 0; + var maxDepth = 0f; + foreach (var icon in _icons) + { + float2 planar; + Quaternion yaw = Quaternion.identity; + if (icon.Entity != null) + { + var drawn = DrawAhead.Position(icon.Entity, lead); + planar = drawn.xz; + var rotation = icon.Entity is Ship ship ? DrawAhead.Rotation(ship, lead) : quaternion.identity; + yaw = Quaternion.Euler(0, IconYaw(icon.Entity, rotation), 0); + } + else + { + var body = zone.PlanetInstances[icon.Planet]; + planar = DrawAhead.Orbit(zone, body.BodyData.Orbit.Key, lead); + maxDepth = Mathf.Max(maxDepth, -DrawAhead.Height(zone, planar, lead)); + } + + if (icon.Fade <= .01f) continue; + if (icon.Material.HasProperty("_Color")) + icon.Material.SetColor("_Color", new Color(icon.Base.r, icon.Base.g, icon.Base.b, icon.Base.a * icon.Fade)); + var parameters = new RenderParams(icon.Material) { layer = _layer, shadowCastingMode = ShadowCastingMode.Off }; + icon.Drawn = Matrix4x4.TRS(new Vector3(planar.x, PlaneY + icon.Offset, planar.y), yaw * icon.Authored, + Vector3.one * size); + Graphics.RenderMesh(parameters, icon.Mesh, 0, icon.Drawn); + IconDraws++; + } + + var depthRange = Mathf.Max(maxDepth, 1f); + foreach (var material in _gravityMaterials) + { + material.SetFloat("_StartDepth", 0f); + material.SetFloat("_DepthRange", depthRange); + } + + foreach (var belt in _belts.Values) DrawBelt(belt); + } + + public void Clear() + { + foreach (var icon in _icons) Object.Destroy(icon.Material); + _icons.Clear(); + _entityIcons.Clear(); + foreach (var belt in _belts.Values) belt.Buffer.Release(); + _belts.Clear(); + } +} diff --git a/Assets/Scripts/Zone Display/MapView.cs b/Assets/Scripts/Zone Display/MapView.cs deleted file mode 100644 index a2e51c246..000000000 --- a/Assets/Scripts/Zone Display/MapView.cs +++ /dev/null @@ -1,111 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -using System; -using System.Collections; -using System.Collections.Generic; -using UnityEngine; -using UnityEngine.InputSystem; -using UnityEngine.Serialization; - -public class MapView : MonoBehaviour -{ - public Camera Main; - public Camera MinimapGravity; - public Camera Minimap; - public MeshRenderer GravityRenderer; - public float ZoomSpeed; - [FormerlySerializedAs("SectorRenderer")] public ZoneRenderer ZoneRenderer; - - private Transform _minimapTransform; - private CopyTransform _minimapCopyTransform; - private RenderTexture _minimapTexture; - private Transform _minimapGravityTransform; - private CopyTransform _minimapGravityCopyTransform; - private RenderTexture _minimapGravityTexture; - private Transform _gravityRendererTransform; - private CopyTransform _gravityRendererCopyTransform; - private RenderTexture _fullscreenGravityTexture; - private AetheriaInput.UIActions _input; - private float _viewDistance = 1024; - private float _aspectRatio; - private Vector3 _previousGravityRendererScale; - - void OnEnable() - { - _aspectRatio = (float) Screen.width / Screen.height; - Main.gameObject.SetActive(false); - _minimapTexture = Minimap.targetTexture; - _minimapTransform = Minimap.transform; - _minimapCopyTransform = Minimap.GetComponent(); - _minimapCopyTransform.enabled = false; - _minimapGravityTexture = MinimapGravity.targetTexture; - _minimapGravityTransform = MinimapGravity.transform; - _minimapGravityCopyTransform = MinimapGravity.GetComponent(); - _minimapGravityCopyTransform.enabled = false; - _gravityRendererTransform = GravityRenderer.transform; - _gravityRendererCopyTransform = GravityRenderer.GetComponent(); - _gravityRendererCopyTransform.enabled = false; - _previousGravityRendererScale = _gravityRendererTransform.localScale; - - _fullscreenGravityTexture = new RenderTexture(Screen.width / 2, Screen.height / 2, 1, RenderTextureFormat.RFloat); - GravityRenderer.material.mainTexture = _fullscreenGravityTexture; - MinimapGravity.targetTexture = _fullscreenGravityTexture; - Minimap.targetTexture = null; - SetZoom(); - } - - void SetZoom() - { - //ZoneRenderer.ViewDistance = _viewDistance * _aspectRatio; - Minimap.orthographicSize = MinimapGravity.orthographicSize = _viewDistance; - _gravityRendererTransform.localScale = new Vector3(_viewDistance*_aspectRatio*2, _viewDistance*2, 1); - } - - // public void BindInput(AetheriaInput.UIActions input) - // { - // _input = input; - // _input.ScrollWheel.performed += OnScroll; - // } - - // private void OnScroll(InputAction.CallbackContext context) - // { - // _viewDistance *= (1 - context.ReadValue().y * ZoomSpeed); - // SetZoom(); - // } - - void Update() - { - if (_input.Click.ReadValue() > .5f) - { - var delta = _input.Drag.ReadValue(); - - var pos = _minimapTransform.position; - pos.x -= delta.x * _viewDistance / Screen.height; - pos.z -= delta.y * _viewDistance / Screen.height; - - var gravPos = _gravityRendererTransform.position; - gravPos.x = pos.x; - gravPos.z = pos.z; - - _gravityRendererTransform.position = gravPos; - Main.transform.position = _minimapTransform.position = _minimapGravityTransform.position = pos; - } - } - - private void OnDisable() - { - // _input.ScrollWheel.performed -= OnScroll; - - Main.gameObject.SetActive(true); - _minimapGravityCopyTransform.enabled = true; - _minimapCopyTransform.enabled = true; - _gravityRendererCopyTransform.enabled = true; - - Minimap.targetTexture = _minimapTexture; - MinimapGravity.targetTexture = _minimapGravityTexture; - GravityRenderer.material.mainTexture = _minimapGravityTexture; - _fullscreenGravityTexture.Release(); - } -} diff --git a/Assets/Scripts/Zone Display/MapView.cs.meta b/Assets/Scripts/Zone Display/MapView.cs.meta deleted file mode 100644 index b97643a75..000000000 --- a/Assets/Scripts/Zone Display/MapView.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 76d9e45f376fcfe409f3a4f26ccb4f3e -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/Zone Display/ZoneRenderer.cs b/Assets/Scripts/Zone Display/ZoneRenderer.cs index 8126eb096..3c584c956 100644 --- a/Assets/Scripts/Zone Display/ZoneRenderer.cs +++ b/Assets/Scripts/Zone Display/ZoneRenderer.cs @@ -59,7 +59,8 @@ public class ZoneRenderer : MonoBehaviour // public Material AsteroidMaterial; [Header("Prefabs")] - public MeshFilter AsteroidBeltUI; + // The map's belt sprites (Aetheria/MapBelt): MapPresenter draws every belt with it. + public Material MapAsteroidMaterial; public PlanetObject Planet; public GasGiantObject GasGiant; public SunObject Sun; @@ -72,7 +73,6 @@ public class ZoneRenderer : MonoBehaviour [HideInInspector] public Dictionary EntityInstances = new Dictionary(); [HideInInspector] public Dictionary Planets = new Dictionary(); - private Dictionary _beltObjects = new Dictionary(); private Dictionary _beltMeshes = new Dictionary(); private Dictionary _beltMatrices = new Dictionary(); // Cut 1 (docs/mining-cut.md): the renderer's own buffer, filled from Zone.EvaluateBelt each frame. No pose @@ -93,13 +93,14 @@ public class ZoneRenderer : MonoBehaviour private IDisposable[] _perspectiveSubscriptions = new IDisposable[2]; private List _zoneSubscriptions = new List(); private PlanetObject[] _suns; - private bool _showAsteroidUI; public Dictionary WormholeInstances = new Dictionary(); private Dictionary _floatingItems = new Dictionary(); private Dictionary _mines = new Dictionary(); private Dictionary _clouds = new Dictionary(); + // The one owner of map-space projection: every map icon and belt sprite is drawn by it. + public MapPresenter Map { get; private set; } public Zone Zone { get; private set; } public ItemManager ItemManager { get; set; } @@ -151,31 +152,15 @@ public class ZoneRenderer : MonoBehaviour _minimapDistance = value; foreach (var camera in MinimapCameras) camera.orthographicSize = value; - SetIconSize(value * Settings.MinimapIconSize); + Map.SetMinimapScale(value * Settings.MinimapIconSize); // MinimapGravityQuad.transform.localScale = value * 2 * Vector3.one; } } - public bool ShowAsteroidUI + void Awake() { - get { return _showAsteroidUI; } - set - { - if (value != _showAsteroidUI) - { - _showAsteroidUI = value; - foreach (var beltUI in _beltObjects.Values) - { - beltUI.Filter.gameObject.SetActive(_showAsteroidUI); - } - } - } - } - - public void SetIconSize(float size) - { - foreach(var entityInstance in EntityInstances.Values) entityInstance.MapIcon.transform.localScale = Vector3.one * size; - foreach(var planet in Planets.Values) planet.Icon.transform.localScale = Vector3.one * size; + Map = new MapPresenter(MapGravityMaterials, MapAsteroidMaterial, AsteroidSpritesheetWidth, + AsteroidSpritesheetHeight, Settings.MinimapAsteroidSize); } void Start() @@ -395,12 +380,7 @@ public class ZoneRenderer : MonoBehaviour } Planets.Clear(); - foreach (var beltObject in _beltObjects.Values) - { - Destroy(beltObject.Filter); - } - - _beltObjects.Clear(); + Map.Clear(); _beltMeshes.Clear(); _beltMatrices.Clear(); _beltTransforms.Clear(); @@ -455,6 +435,7 @@ public class ZoneRenderer : MonoBehaviour } } + Map.Unregister(entity); Destroy(EntityInstances[entity].gameObject); EntityInstances.Remove(entity); } @@ -476,16 +457,7 @@ public class ZoneRenderer : MonoBehaviour _beltMatrices[key][i] = new Matrix4x4[i < meshes.Count - 1 ? count : count + remainder]; } - var beltObject = Instantiate(AsteroidBeltUI, ZoneRoot); - var collider = beltObject.GetComponent(); - var belt = new AsteroidBeltUI(Zone, - Zone.AsteroidBelts[key], - beltObject, - collider, - AsteroidSpritesheetWidth, - AsteroidSpritesheetHeight, - Settings.MinimapAsteroidSize); - _beltObjects[key] = belt; + Map.RegisterBelt(key, _beltTransforms[key]); } else { @@ -525,7 +497,7 @@ public class ZoneRenderer : MonoBehaviour var planetInstance = Zone.PlanetInstances[key]; planet.Body.transform.localScale = planetInstance.BodyRadius * Vector3.one; - planet.Icon.transform.localScale = Settings.IconSize.Evaluate(planetInstance.BodyData.Mass) * Vector3.one; + Map.RegisterPlanet(key, planet.Icon, planet.transform); Planets[key] = planet; @@ -539,9 +511,13 @@ public class ZoneRenderer : MonoBehaviour LODHandler.FindPlanets(); } + void LateUpdate() + { + Map.Draw(Zone, ActionGameManager.Clock.Lead); + } + void Update() { - var maxDepth = 0f; var lead = ActionGameManager.Clock.Lead; GravityLowering.Lower(Zone, lead, ref _gravityWells); foreach (var loot in _floatingItems.Values) @@ -594,9 +570,6 @@ public class ZoneRenderer : MonoBehaviour Graphics.DrawMeshInstanced(meshes[i].Mesh, 0, meshes[i].Material, matrices[i]); } } - - if(_showAsteroidUI) - _beltObjects[key].Update(transforms, height); } foreach (var planet in Planets) @@ -604,10 +577,8 @@ public class ZoneRenderer : MonoBehaviour var planetInstance = Zone.PlanetInstances[planet.Key]; var p = DrawAhead.Orbit(Zone, planetInstance.BodyData.Orbit.Key, lead); var height = DrawAhead.Height(Zone, p, lead); - if (-height > maxDepth) maxDepth = -height; planet.Value.transform.position = new Vector3(p.x, 0, p.y); planet.Value.Body.transform.localPosition = new Vector3(0, height + planetInstance.BodyRadius * 2, 0); - planet.Value.Icon.transform.localPosition = new Vector3(0, height, 0); if (planet.Value is GasGiantObject gasGiantObject) { if (!(planet.Value is SunObject)) @@ -641,13 +612,6 @@ public class ZoneRenderer : MonoBehaviour wormhole.icon.transform.rotation = Quaternion.Euler(0, 0, Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg - 90); } - //var fogPos = FogCameraParent.position; - var depthRange = Mathf.Max(maxDepth, 1f); - foreach (var mat in MapGravityMaterials) - { - mat.SetFloat("_StartDepth", 0f); - mat.SetFloat("_DepthRange", depthRange); - } // var gravPos = MinimapGravityQuad.transform.position; // MinimapGravityQuad.transform.position = gravPos; // MinimapTintQuad.transform.position = gravPos - Vector3.up*10; @@ -661,98 +625,3 @@ public class InstancedMesh public Mesh Mesh; public Material Material; } - -public class AsteroidBeltUI -{ - public MeshFilter Filter; - private MeshCollider _collider; - private Vector3[] _vertices; - private Vector3[] _normals; - private Vector2[] _uvs; - private int[] _indices; - private CultRecordKey _orbitParent; - private Mesh _mesh; - private float _size; - private Zone _zone; - private AsteroidBelt _belt; - private float _scale; - - public AsteroidBeltUI(Zone zone, - AsteroidBelt belt, - MeshFilter meshFilter, - MeshCollider collider, - int spritesheetWidth, - int spritesheetHeight, - float scale) - { - _belt = belt; - _zone = zone; - Filter = meshFilter; - _collider = collider; - var orbit = zone.Orbits[belt.Data.Orbit.Key]; - _orbitParent = orbit.Data.Parent.Key; - _vertices = new Vector3[_belt.Data.Asteroids.Length * 4]; - _normals = new Vector3[_belt.Data.Asteroids.Length * 4]; - _uvs = new Vector2[_belt.Data.Asteroids.Length * 4]; - _indices = new int[_belt.Data.Asteroids.Length * 6]; - _scale = scale; - - var maxDist = 0f; - var spriteSize = float2(1f / spritesheetWidth, 1f / spritesheetHeight); - // vertex order: bottom left, top left, top right, bottom right - for (var i = 0; i < belt.Data.Asteroids.Length; i++) - { - if (belt.Data.Asteroids[i].Distance > maxDist) - maxDist = belt.Data.Asteroids[i].Distance; - var spriteX = Random.Range(0, spritesheetWidth); - var spriteY = Random.Range(0, spritesheetHeight); - - _uvs[i * 4] = new Vector2(spriteX * spriteSize.x, spriteY * spriteSize.y); - _uvs[i * 4 + 1] = new Vector2(spriteX * spriteSize.x, spriteY * spriteSize.y + spriteSize.y); - _uvs[i * 4 + 2] = new Vector2(spriteX * spriteSize.x + spriteSize.x, spriteY * spriteSize.y + spriteSize.y); - _uvs[i * 4 + 3] = new Vector2(spriteX * spriteSize.x + spriteSize.x, spriteY * spriteSize.y); - - _indices[i * 6] = i * 4; - _indices[i * 6 + 1] = i * 4 + 1; - _indices[i * 6 + 2] = i * 4 + 3; - _indices[i * 6 + 3] = i * 4 + 3; - _indices[i * 6 + 4] = i * 4 + 1; - _indices[i * 6 + 5] = i * 4 + 2; - } - - for (var i = 0; i < _normals.Length; i++) - { - _normals[i] = -Vector3.forward; - } - - _mesh = new Mesh(); - _mesh.vertices = _vertices; - _mesh.uv = _uvs; - _mesh.triangles = _indices; - _mesh.normals = _normals; - _mesh.bounds = new Bounds(Vector3.zero, Vector3.one * maxDist); - _size = maxDist; - - Filter.mesh = _mesh; - //_collider.sharedMesh = _mesh; - } - - public void Update(float4[] transforms, float height) - { - var parentPosition = DrawAhead.Orbit(_zone, _orbitParent, ActionGameManager.Clock.Lead); - for (var i = 0; i < transforms.Length; i++) - { - var transform = transforms[i]; - var rotation = Quaternion.Euler(90, transform.z, 0); - var position = new Vector3(transform.x, height, transform.y); - _vertices[i * 4] = rotation * new Vector3(-transform.w * _scale, -transform.w * _scale, 0) + position; - _vertices[i * 4 + 1] = rotation * new Vector3(-transform.w * _scale, transform.w * _scale, 0) + position; - _vertices[i * 4 + 2] = rotation * new Vector3(transform.w * _scale, transform.w * _scale, 0) + position; - _vertices[i * 4 + 3] = rotation * new Vector3(transform.w * _scale, -transform.w * _scale, 0) + position; - } - - _mesh.bounds = new Bounds(new Vector3(parentPosition.x, 0, parentPosition.y), Vector3.one * (_size * 2)); - _mesh.vertices = _vertices; - //_collider.sharedMesh = _mesh; - } -} diff --git a/Assets/Shaders/MapBelt.shader b/Assets/Shaders/MapBelt.shader new file mode 100644 index 000000000..9c4d0dfcf --- /dev/null +++ b/Assets/Shaders/MapBelt.shader @@ -0,0 +1,59 @@ +// The minimap and map screen's belt asteroids: one instanced draw per belt, six vertices per asteroid expanded to a +// quad on the map plane. _Belt holds the belt's per-frame float4s (x, y planar, z spin in degrees, w size); the sprite +// cell is a hash of the instance id, so a belt keeps its sprites without any per-asteroid CPU state. +Shader "Aetheria/MapBelt" +{ + Properties { _MainTex ("Sprite Sheet", 2D) = "white" {} _Color ("Color", Color) = (1,1,1,1) } + SubShader + { + Tags { "Queue"="Transparent" "RenderType"="Transparent" "IgnoreProjector"="True" } + Cull Off ZWrite Off Blend SrcAlpha OneMinusSrcAlpha + Pass + { + CGPROGRAM + #pragma vertex vert + #pragma fragment frag + #pragma target 4.5 + #include "UnityCG.cginc" + + sampler2D _MainTex; + fixed4 _Color; + StructuredBuffer _Belt; + float4 _Sheet; + float _PlaneY; + float _AsteroidSize; + + struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; }; + + uint Hash(uint x) + { + x = x * 747796405u + 2891336453u; + x = ((x >> ((x >> 28u) + 4u)) ^ x) * 277803737u; + return (x >> 22u) ^ x; + } + + v2f vert(uint vid : SV_VertexID, uint iid : SV_InstanceID) + { + // Triangles (0,1,3) and (3,1,2) over the corners bottom left, top left, top right, bottom right. + static const uint corner[6] = { 0, 1, 3, 3, 1, 2 }; + static const float2 offsets[4] = { float2(-1, -1), float2(-1, 1), float2(1, 1), float2(1, -1) }; + float4 a = _Belt[iid]; + float2 o = offsets[corner[vid]]; + float theta = radians(a.z); + float s = sin(theta), c = cos(theta); + float2 q = o * a.w * _AsteroidSize; + float3 world = float3(a.x + q.x * c + q.y * s, _PlaneY, a.y - q.x * s + q.y * c); + uint cell = Hash(iid) % (uint)(_Sheet.x * _Sheet.y); + float2 size = float2(1.0 / _Sheet.x, 1.0 / _Sheet.y); + float2 origin = float2(cell % (uint)_Sheet.x, cell / (uint)_Sheet.x) * size; + v2f v; + v.pos = mul(UNITY_MATRIX_VP, float4(world, 1)); + v.uv = origin + (o * .5 + .5) * size; + return v; + } + + fixed4 frag(v2f v) : SV_Target { return tex2D(_MainTex, v.uv) * _Color; } + ENDCG + } + } +} diff --git a/Assets/Shaders/MapBelt.shader.meta b/Assets/Shaders/MapBelt.shader.meta new file mode 100644 index 000000000..350a8b48a --- /dev/null +++ b/Assets/Shaders/MapBelt.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 22e2fc798f044a3aa2f6cd71f1df09a7 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/tests/Aetheria.Shared.Tests/ScenarioTests.cs b/tests/Aetheria.Shared.Tests/ScenarioTests.cs index 11ce2ddf2..0a0d6e345 100644 --- a/tests/Aetheria.Shared.Tests/ScenarioTests.cs +++ b/tests/Aetheria.Shared.Tests/ScenarioTests.cs @@ -555,7 +555,7 @@ public sealed partial class RunStartTests Assert.False(main.IsTutorial); } - // PlayerSettings Key 2, the never-written tutorial flag, is retired: a player.cc written with it still loads, every other field intact. + // PlayerSettings Key 2, the never-written tutorial flag, and PlayerGraphicsSettings Key 1, ShowAsteroidsInMinimap, are retired: a player.cc written with it still loads, every other field intact. [Fact] public void OldPlayerSettingsLoad() { @@ -569,7 +569,10 @@ public sealed partial class RunStartTests MessagePackSerializer.Serialize(ref writer, new Dictionary { ["story"] = "hash" }, options); MessagePackSerializer.Serialize(ref writer, new PlayerGameplaySettings { TemperatureUnit = TemperatureUnit.Kelvin, SignificantDigits = 5 }, options); MessagePackSerializer.Serialize(ref writer, new PlayerInputSettings { ActionBarInputs = { "/1" } }, options); - MessagePackSerializer.Serialize(ref writer, new PlayerGraphicsSettings { NebulaQuality = Quality.High, ShowAsteroidsInMinimap = true }, options); + writer.WriteArrayHeader(3); // PlayerGraphicsSettings with its retired Key 1 (ShowAsteroidsInMinimap) + MessagePackSerializer.Serialize(ref writer, Quality.High, options); + writer.Write(true); + writer.Write(.4f); writer.Flush(); var settings = MessagePackSerializer.Deserialize(buffer.WrittenMemory, options); @@ -578,7 +581,7 @@ public sealed partial class RunStartTests Assert.Equal("hash", settings.HashedStoryFiles["story"]); Assert.Equal((TemperatureUnit.Kelvin, 5), (settings.GameplaySettings.TemperatureUnit, settings.GameplaySettings.SignificantDigits)); Assert.Equal("/1", Assert.Single(settings.InputSettings.ActionBarInputs)); - Assert.Equal((Quality.High, true), (settings.GraphicsSettings.NebulaQuality, settings.GraphicsSettings.ShowAsteroidsInMinimap)); + Assert.Equal((Quality.High, .4f), (settings.GraphicsSettings.NebulaQuality, settings.GraphicsSettings.ScreenShake)); } // A scenario's Brief is player text (the menu shows it): it names gear by what the catalog sells today. A name a rename retired,