Map presenter: one owner places every map icon in 2D, belts become one instanced draw (not yet compiled)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-16 23:26:26 +02:00
commit 281745394b
16 changed files with 360 additions and 408 deletions

View file

@ -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: []

View file

@ -1,7 +1,8 @@
fileFormatVersion: 2
guid: 1f1bfa2175a13b647888e2949e0cb138
PrefabImporter:
guid: c1012d5466454651b7279be93ef9af88
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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:

View file

@ -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}

View file

@ -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

View file

@ -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<MeshRenderer>();
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);
}
}
}

View file

@ -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;
}

View file

@ -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,

View file

@ -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);
}
}

View file

@ -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<Icon> _icons = new List<Icon>();
private readonly Dictionary<Entity, Icon> _entityIcons = new Dictionary<Entity, Icon>();
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<MeshFilter>().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<CultRecordKey, Belt> _belts = new Dictionary<CultRecordKey, Belt>();
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();
}
}

View file

@ -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<CopyTransform>();
_minimapCopyTransform.enabled = false;
_minimapGravityTexture = MinimapGravity.targetTexture;
_minimapGravityTransform = MinimapGravity.transform;
_minimapGravityCopyTransform = MinimapGravity.GetComponent<CopyTransform>();
_minimapGravityCopyTransform.enabled = false;
_gravityRendererTransform = GravityRenderer.transform;
_gravityRendererCopyTransform = GravityRenderer.GetComponent<CopyTransform>();
_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<Vector2>().y * ZoomSpeed);
// SetZoom();
// }
void Update()
{
if (_input.Click.ReadValue<float>() > .5f)
{
var delta = _input.Drag.ReadValue<Vector2>();
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();
}
}

View file

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 76d9e45f376fcfe409f3a4f26ccb4f3e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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<Entity, EntityInstance> EntityInstances = new Dictionary<Entity, EntityInstance>();
[HideInInspector] public Dictionary<CultRecordKey, PlanetObject> Planets = new Dictionary<CultRecordKey, PlanetObject>();
private Dictionary<CultRecordKey, AsteroidBeltUI> _beltObjects = new Dictionary<CultRecordKey, AsteroidBeltUI>();
private Dictionary<CultRecordKey, InstancedMesh[]> _beltMeshes = new Dictionary<CultRecordKey, InstancedMesh[]>();
private Dictionary<CultRecordKey, Matrix4x4[][]> _beltMatrices = new Dictionary<CultRecordKey, Matrix4x4[][]>();
// 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<IDisposable> _zoneSubscriptions = new List<IDisposable>();
private PlanetObject[] _suns;
private bool _showAsteroidUI;
public Dictionary<Wormhole, (GameObject gravity, CompassIcon icon)> WormholeInstances = new Dictionary<Wormhole, (GameObject, CompassIcon)>();
private Dictionary<FloatingItem, ItemPickup> _floatingItems = new Dictionary<FloatingItem, ItemPickup>();
private Dictionary<Mine, MineInstance> _mines = new Dictionary<Mine, MineInstance>();
private Dictionary<VapourCloud, VapourCloudInstance> _clouds = new Dictionary<VapourCloud, VapourCloudInstance>();
// 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<MeshCollider>();
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;
}
}

View file

@ -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<float4> _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
}
}
}

View file

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 22e2fc798f044a3aa2f6cd71f1df09a7
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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<string, string> { ["story"] = "hash" }, options);
MessagePackSerializer.Serialize(ref writer, new PlayerGameplaySettings { TemperatureUnit = TemperatureUnit.Kelvin, SignificantDigits = 5 }, options);
MessagePackSerializer.Serialize(ref writer, new PlayerInputSettings { ActionBarInputs = { "<Keyboard>/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<PlayerSettings>(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("<Keyboard>/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,