- Add Menu scene with Rive-driven login and navigation UI (RiveLoginController, RiveNavigationController) for entering a username and starting/navigating the client via Rive triggers - Add SceneTransitionController to fade out and load the Landscape gameplay scene when Play is selected - Add procedural low-poly triangle field background and animated wave panel (LowPolyTriangleField, TriangleWavePanel) with matching editor menus and materials - Add MenuLightAnimator for animated menu lighting and UI fade helpers (MenuFadeIn, DebugFadeToggle/Bridge) - Rename SampleScene to Landscape and wire ChatNetworkManager to hide the new Rive login canvas once gameplay starts - Bump Input System action codegen version and add Rive package references/project settings updates to support the new UI
305 lines
10 KiB
C#
305 lines
10 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
namespace Procedural
|
|
{
|
|
[ExecuteAlways]
|
|
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
|
|
public class LowPolyTriangleField : MonoBehaviour
|
|
{
|
|
[Header("Grid")]
|
|
[Min(1)] public int columns = 24;
|
|
[Min(1)] public int rows = 14;
|
|
public float cellSize = 1.5f;
|
|
[Range(0f, 0.5f)] public float jitterAmount = 0.35f;
|
|
|
|
[Header("Static Shape")]
|
|
public float noiseScale = 0.12f;
|
|
public float heightAmplitude = 1.2f;
|
|
|
|
[Header("Wave Animation")]
|
|
public bool animate = true;
|
|
public float waveAmplitude = 0.25f;
|
|
public float waveFrequency = 0.25f;
|
|
public float waveSpeed = 0.6f;
|
|
|
|
[Header("Edges")]
|
|
public float edgeWidth = 0.06f;
|
|
public float edgeHeightOffset = 0.02f;
|
|
|
|
[Header("Materials")]
|
|
public Material faceMaterial;
|
|
public Material edgeMaterial;
|
|
|
|
[Header("Random")]
|
|
public int seed = 12345;
|
|
|
|
Mesh _mesh;
|
|
MeshFilter _meshFilter;
|
|
|
|
Vector2[] _gridXZ;
|
|
float[] _staticHeight;
|
|
Vector3[] _gridNormalAccum;
|
|
int[] _gridNormalCount;
|
|
List<int[]> _triangles;
|
|
List<(int a, int b)> _uniqueEdges;
|
|
|
|
Vector3[] _facePositions;
|
|
Vector3[] _faceNormals;
|
|
int[] _faceIndices;
|
|
|
|
Vector3[] _edgePositions;
|
|
int[] _edgeIndices;
|
|
|
|
bool _topologyDirty = true;
|
|
|
|
void OnEnable()
|
|
{
|
|
_meshFilter = GetComponent<MeshFilter>();
|
|
RebuildTopology();
|
|
UpdateGeometry(0f);
|
|
#if UNITY_EDITOR
|
|
EditorApplication.update -= EditorTick;
|
|
if (!Application.isPlaying)
|
|
EditorApplication.update += EditorTick;
|
|
#endif
|
|
}
|
|
|
|
void OnDisable()
|
|
{
|
|
#if UNITY_EDITOR
|
|
EditorApplication.update -= EditorTick;
|
|
#endif
|
|
}
|
|
|
|
void OnValidate()
|
|
{
|
|
_topologyDirty = true;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
void EditorTick()
|
|
{
|
|
if (Application.isPlaying) return;
|
|
if (_topologyDirty) RebuildTopology();
|
|
if (animate) UpdateGeometry((float)EditorApplication.timeSinceStartup);
|
|
if (this != null) EditorUtility.SetDirty(this);
|
|
}
|
|
#endif
|
|
|
|
void Update()
|
|
{
|
|
if (!Application.isPlaying) return;
|
|
if (_topologyDirty) RebuildTopology();
|
|
if (animate) UpdateGeometry(Time.time);
|
|
}
|
|
|
|
void RebuildTopology()
|
|
{
|
|
_topologyDirty = false;
|
|
|
|
int vertsX = columns + 1;
|
|
int vertsZ = rows + 1;
|
|
int gridCount = vertsX * vertsZ;
|
|
|
|
_gridXZ = new Vector2[gridCount];
|
|
_staticHeight = new float[gridCount];
|
|
|
|
var rng = new System.Random(seed);
|
|
float originX = -columns * cellSize * 0.5f;
|
|
float originZ = -rows * cellSize * 0.5f;
|
|
|
|
for (int j = 0; j < vertsZ; j++)
|
|
{
|
|
for (int i = 0; i < vertsX; i++)
|
|
{
|
|
int idx = j * vertsX + i;
|
|
bool border = i == 0 || i == columns || j == 0 || j == rows;
|
|
float jitterX = border ? 0f : ((float)rng.NextDouble() * 2f - 1f) * jitterAmount * cellSize;
|
|
float jitterZ = border ? 0f : ((float)rng.NextDouble() * 2f - 1f) * jitterAmount * cellSize;
|
|
|
|
float x = originX + i * cellSize + jitterX;
|
|
float z = originZ + j * cellSize + jitterZ;
|
|
_gridXZ[idx] = new Vector2(x, z);
|
|
|
|
float n = Mathf.PerlinNoise(x * noiseScale + seed * 0.017f, z * noiseScale + seed * 0.017f);
|
|
_staticHeight[idx] = (n * 2f - 1f) * heightAmplitude;
|
|
}
|
|
}
|
|
|
|
_triangles = new List<int[]>(columns * rows * 2);
|
|
var edgeSet = new HashSet<(int, int)>();
|
|
_uniqueEdges = new List<(int, int)>();
|
|
|
|
for (int j = 0; j < rows; j++)
|
|
{
|
|
for (int i = 0; i < columns; i++)
|
|
{
|
|
int i00 = j * vertsX + i;
|
|
int i10 = j * vertsX + (i + 1);
|
|
int i01 = (j + 1) * vertsX + i;
|
|
int i11 = (j + 1) * vertsX + (i + 1);
|
|
|
|
bool flip = rng.Next(2) == 0;
|
|
|
|
if (flip)
|
|
{
|
|
AddTriangle(i00, i11, i01, edgeSet);
|
|
AddTriangle(i00, i10, i11, edgeSet);
|
|
}
|
|
else
|
|
{
|
|
AddTriangle(i00, i10, i01, edgeSet);
|
|
AddTriangle(i10, i11, i01, edgeSet);
|
|
}
|
|
}
|
|
}
|
|
|
|
int faceVertCount = _triangles.Count * 3;
|
|
_facePositions = new Vector3[faceVertCount];
|
|
_faceNormals = new Vector3[faceVertCount];
|
|
_faceIndices = new int[faceVertCount];
|
|
for (int k = 0; k < faceVertCount; k++) _faceIndices[k] = k;
|
|
|
|
int edgeVertCount = _uniqueEdges.Count * 4;
|
|
_edgePositions = new Vector3[edgeVertCount];
|
|
_edgeIndices = new int[_uniqueEdges.Count * 6];
|
|
for (int e = 0; e < _uniqueEdges.Count; e++)
|
|
{
|
|
int b = e * 4;
|
|
int t = e * 6;
|
|
_edgeIndices[t + 0] = b + 0;
|
|
_edgeIndices[t + 1] = b + 2;
|
|
_edgeIndices[t + 2] = b + 1;
|
|
_edgeIndices[t + 3] = b + 1;
|
|
_edgeIndices[t + 4] = b + 2;
|
|
_edgeIndices[t + 5] = b + 3;
|
|
}
|
|
|
|
_gridNormalAccum = new Vector3[gridCount];
|
|
_gridNormalCount = new int[gridCount];
|
|
|
|
if (_mesh == null)
|
|
{
|
|
_mesh = new Mesh { name = "LowPolyTriangleField" };
|
|
_mesh.MarkDynamic();
|
|
}
|
|
_mesh.indexFormat = (faceVertCount + edgeVertCount) > 60000
|
|
? UnityEngine.Rendering.IndexFormat.UInt32
|
|
: UnityEngine.Rendering.IndexFormat.UInt16;
|
|
_mesh.Clear();
|
|
_meshFilter.sharedMesh = _mesh;
|
|
|
|
var renderer = GetComponent<MeshRenderer>();
|
|
renderer.sharedMaterials = new[] { faceMaterial, edgeMaterial };
|
|
|
|
void AddTriangle(int a, int b2, int c, HashSet<(int, int)> edges)
|
|
{
|
|
_triangles.Add(new[] { a, b2, c });
|
|
AddEdge(a, b2, edges);
|
|
AddEdge(b2, c, edges);
|
|
AddEdge(c, a, edges);
|
|
}
|
|
|
|
void AddEdge(int a, int b2, HashSet<(int, int)> edges)
|
|
{
|
|
var key = a < b2 ? (a, b2) : (b2, a);
|
|
if (edges.Add(key)) _uniqueEdges.Add(key);
|
|
}
|
|
}
|
|
|
|
void UpdateGeometry(float time)
|
|
{
|
|
if (_gridXZ == null) return;
|
|
|
|
int gridCount = _gridXZ.Length;
|
|
var pos = new Vector3[gridCount];
|
|
for (int idx = 0; idx < gridCount; idx++)
|
|
{
|
|
var xz = _gridXZ[idx];
|
|
float wave = 0f;
|
|
if (animate)
|
|
{
|
|
wave = waveAmplitude * 0.5f * (
|
|
Mathf.Sin((xz.x + xz.y) * waveFrequency + time * waveSpeed) +
|
|
Mathf.Sin((xz.x - xz.y) * waveFrequency * 0.7f + time * waveSpeed * 1.3f));
|
|
}
|
|
pos[idx] = new Vector3(xz.x, _staticHeight[idx] + wave, xz.y);
|
|
_gridNormalAccum[idx] = Vector3.zero;
|
|
_gridNormalCount[idx] = 0;
|
|
}
|
|
|
|
for (int t = 0; t < _triangles.Count; t++)
|
|
{
|
|
var tri = _triangles[t];
|
|
Vector3 v0 = pos[tri[0]];
|
|
Vector3 v1 = pos[tri[1]];
|
|
Vector3 v2 = pos[tri[2]];
|
|
Vector3 normal = Vector3.Cross(v1 - v0, v2 - v0).normalized;
|
|
|
|
int b = t * 3;
|
|
_facePositions[b + 0] = v0;
|
|
_facePositions[b + 1] = v1;
|
|
_facePositions[b + 2] = v2;
|
|
_faceNormals[b + 0] = normal;
|
|
_faceNormals[b + 1] = normal;
|
|
_faceNormals[b + 2] = normal;
|
|
|
|
_gridNormalAccum[tri[0]] += normal; _gridNormalCount[tri[0]]++;
|
|
_gridNormalAccum[tri[1]] += normal; _gridNormalCount[tri[1]]++;
|
|
_gridNormalAccum[tri[2]] += normal; _gridNormalCount[tri[2]]++;
|
|
}
|
|
|
|
for (int e = 0; e < _uniqueEdges.Count; e++)
|
|
{
|
|
var (a, bIdx) = _uniqueEdges[e];
|
|
Vector3 p0 = pos[a];
|
|
Vector3 p1 = pos[bIdx];
|
|
Vector3 n0 = _gridNormalCount[a] > 0 ? (_gridNormalAccum[a] / _gridNormalCount[a]).normalized : Vector3.up;
|
|
Vector3 n1 = _gridNormalCount[bIdx] > 0 ? (_gridNormalAccum[bIdx] / _gridNormalCount[bIdx]).normalized : Vector3.up;
|
|
|
|
Vector3 dir = (p1 - p0).normalized;
|
|
Vector3 avgNormal = (n0 + n1).normalized;
|
|
Vector3 side = Vector3.Cross(dir, avgNormal).normalized * (edgeWidth * 0.5f);
|
|
|
|
Vector3 p0Off = p0 + n0 * edgeHeightOffset;
|
|
Vector3 p1Off = p1 + n1 * edgeHeightOffset;
|
|
|
|
int b = e * 4;
|
|
_edgePositions[b + 0] = p0Off - side;
|
|
_edgePositions[b + 1] = p0Off + side;
|
|
_edgePositions[b + 2] = p1Off - side;
|
|
_edgePositions[b + 3] = p1Off + side;
|
|
}
|
|
|
|
_mesh.subMeshCount = 2;
|
|
_mesh.SetVertices(_facePositions.Length > 0 ? Concat(_facePositions, _edgePositions) : _edgePositions);
|
|
|
|
int faceVertCount = _facePositions.Length;
|
|
var normals = new Vector3[faceVertCount + _edgePositions.Length];
|
|
for (int i = 0; i < faceVertCount; i++) normals[i] = _faceNormals[i];
|
|
for (int i = 0; i < _edgePositions.Length; i++) normals[faceVertCount + i] = Vector3.up;
|
|
_mesh.SetNormals(normals);
|
|
|
|
_mesh.SetIndices(_faceIndices, MeshTopology.Triangles, 0, false);
|
|
|
|
var offsetEdgeIndices = new int[_edgeIndices.Length];
|
|
for (int i = 0; i < _edgeIndices.Length; i++) offsetEdgeIndices[i] = _edgeIndices[i] + faceVertCount;
|
|
_mesh.SetIndices(offsetEdgeIndices, MeshTopology.Triangles, 1, false);
|
|
|
|
_mesh.RecalculateBounds();
|
|
}
|
|
|
|
static Vector3[] Concat(Vector3[] a, Vector3[] b)
|
|
{
|
|
var result = new Vector3[a.Length + b.Length];
|
|
a.CopyTo(result, 0);
|
|
b.CopyTo(result, a.Length);
|
|
return result;
|
|
}
|
|
}
|
|
}
|