riversong code showcase

This commit is contained in:
Daniele Marotta
2026-05-21 15:52:18 +02:00
commit 4c9eea1c02
462 changed files with 23406 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
using System;
using System.Globalization;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
#endif
namespace DanieleMarotta.RiversongCodeShowcase
{
#if UNITY_EDITOR
public class BuildVersionPreprocessor : IPreprocessBuildWithReport
{
private const string AssetPath = "Assets/_Project/Config/BuildVersion.asset";
public int callbackOrder => 0;
public void OnPreprocessBuild(BuildReport report)
{
var buildVersion = AssetDatabase.LoadAssetAtPath<BuildVersionAsset>(AssetPath);
if (!buildVersion)
throw new BuildFailedException($"Missing build version asset at '{AssetPath}'.");
var today = DateTime.Now;
var dayKey = today.ToString("yyyyMMdd", CultureInfo.InvariantCulture);
if (string.Equals(buildVersion.LastBuildDate, dayKey, StringComparison.Ordinal))
buildVersion.LastBuildCounter++;
else
{
buildVersion.LastBuildDate = dayKey;
buildVersion.LastBuildCounter = 1;
}
buildVersion.CurrentVersion = string.Format(
CultureInfo.InvariantCulture,
"{0:00}.{1:00}.{2:0000}.{3}",
today.Month,
today.Day,
today.Year,
buildVersion.LastBuildCounter);
EditorUtility.SetDirty(buildVersion);
AssetDatabase.SaveAssets();
}
}
#endif
}

View File

@@ -0,0 +1,48 @@
using System.IO;
using Sirenix.OdinInspector;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace DanieleMarotta.RiversongCodeShowcase
{
public class IconMaker : MonoBehaviour
{
#if UNITY_EDITOR
[SerializeField]
[PreviewField]
private RenderTexture _iconTexture;
[Button(ButtonSizes.Large)]
[GUIColor("cyan")]
public void MakeIcon()
{
if (!_iconTexture) return;
var folderPath = "Assets/_Project/GameAssets/UI/Icons";
if (!Directory.Exists(folderPath)) Directory.CreateDirectory(folderPath);
var fileName = $"Icon_{GUID.Generate()}.png";
RenderTexture.active = _iconTexture;
var temp = new Texture2D(_iconTexture.width, _iconTexture.height, TextureFormat.RGBA32, false);
temp.ReadPixels(new Rect(0, 0, _iconTexture.width, _iconTexture.height), 0, 0);
temp.Apply();
var fullPath = Path.Combine(folderPath, fileName);
File.WriteAllBytes(fullPath, temp.EncodeToPNG());
AssetDatabase.Refresh();
var importer = (TextureImporter)AssetImporter.GetAtPath(fullPath);
importer.textureType = TextureImporterType.Sprite;
importer.spriteImportMode = SpriteImportMode.Single;
importer.maxTextureSize = 256;
importer.mipmapEnabled = true;
importer.SaveAndReimport();
}
#endif
}
}

View File

@@ -0,0 +1,204 @@
using System.Collections.Generic;
using System.IO;
using Sirenix.OdinInspector;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
#endif
namespace DanieleMarotta.RiversongCodeShowcase
{
public class MeshBaker : MonoBehaviour
{
[SerializeField]
private string _layer = "Default";
[SerializeField]
private List<GameObject> _doNotBake = new();
#if UNITY_EDITOR
[Button(ButtonSizes.Large)]
[GUIColor("cyan")]
private void BakeMesh()
{
var excluded = GetExcluded();
var combineInstancesByMaterial = CollectCombineInstances(excluded);
if (combineInstancesByMaterial.ValueCount <= 0) return;
var bakedName = $"{gameObject.name.Replace("_Authoring", string.Empty)}_Baked";
var bakedMesh = CreateBakedMesh(bakedName, combineInstancesByMaterial);
var parentFolder = GetBakeOutputFolder(out var isPrefab);
AssetDatabase.CreateAsset(bakedMesh, $"{parentFolder}/{bakedName}.asset");
if (isPrefab) SaveBakedPrefab(parentFolder, bakedName, bakedMesh, combineInstancesByMaterial);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
private ListMultiDictionary<Material, CombineInstance> CollectCombineInstances(HashSet<Transform> excluded)
{
var meshRenderers = GetComponentsInChildren<MeshRenderer>();
var combineInstancesByMaterial = new ListMultiDictionary<Material, CombineInstance>();
foreach (var childRenderer in meshRenderers)
{
if (excluded.Contains(childRenderer.transform)) continue;
AddRendererCombineInstances(childRenderer, combineInstancesByMaterial);
}
return combineInstancesByMaterial;
}
private void AddRendererCombineInstances(MeshRenderer childRenderer, ListMultiDictionary<Material, CombineInstance> combineInstancesByMaterial)
{
var meshFilter = childRenderer.GetComponent<MeshFilter>();
if (!meshFilter || !meshFilter.sharedMesh) return;
var mesh = meshFilter.sharedMesh;
var materials = childRenderer.sharedMaterials;
var subMeshCount = Mathf.Min(mesh.subMeshCount, materials.Length);
for (var i = 0; i < subMeshCount; i++)
{
var material = materials[i];
if (!material) continue;
combineInstancesByMaterial.Add(
material,
new CombineInstance
{
mesh = mesh,
subMeshIndex = i,
transform = meshFilter.transform.localToWorldMatrix
});
}
}
private Mesh CreateBakedMesh(string bakedName, ListMultiDictionary<Material, CombineInstance> combineInstancesByMaterial)
{
var bakedMesh = new Mesh { name = bakedName };
if (combineInstancesByMaterial.KeyCount == 1)
{
CombineSingleMaterialMesh(bakedMesh, combineInstancesByMaterial);
return bakedMesh;
}
CombineMultiMaterialMesh(bakedMesh, combineInstancesByMaterial);
return bakedMesh;
}
private void CombineSingleMaterialMesh(Mesh bakedMesh, ListMultiDictionary<Material, CombineInstance> combineInstancesByMaterial)
{
foreach (var material in combineInstancesByMaterial.Keys)
{
var combineInstances = combineInstancesByMaterial[material];
bakedMesh.CombineMeshes(combineInstances.ToArray(), true, true);
}
}
private void CombineMultiMaterialMesh(Mesh bakedMesh, ListMultiDictionary<Material, CombineInstance> combineInstancesByMaterial)
{
var finalCombineInstances = new List<CombineInstance>();
var tempMeshes = new List<Mesh>();
foreach (var material in combineInstancesByMaterial.Keys)
{
var tempMesh = new Mesh();
tempMesh.CombineMeshes(combineInstancesByMaterial[material].ToArray(), true, true);
finalCombineInstances.Add(
new CombineInstance
{
mesh = tempMesh,
transform = Matrix4x4.identity
});
tempMeshes.Add(tempMesh);
}
bakedMesh.CombineMeshes(finalCombineInstances.ToArray(), false, false);
foreach (var mesh in tempMeshes) DestroyImmediate(mesh);
}
private string GetBakeOutputFolder(out bool isPrefab)
{
var selectedPrefab = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(gameObject);
if (string.IsNullOrEmpty(selectedPrefab))
{
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
if (prefabStage && prefabStage.prefabContentsRoot == gameObject) selectedPrefab = prefabStage.assetPath;
}
isPrefab = !string.IsNullOrEmpty(selectedPrefab);
return isPrefab ? Path.GetDirectoryName(selectedPrefab) : "Assets";
}
private void SaveBakedPrefab(string parentFolder, string bakedName, Mesh bakedMesh, ListMultiDictionary<Material, CombineInstance> combineInstancesByMaterial)
{
var baked = new GameObject(bakedName) { layer = LayerMask.NameToLayer(_layer) };
baked.AddComponent<MeshFilter>().sharedMesh = bakedMesh;
var bakedRenderer = baked.AddComponent<MeshRenderer>();
bakedRenderer.sharedMaterials = new List<Material>(combineInstancesByMaterial.Keys).ToArray();
AddExcluded(baked.transform);
PrefabUtility.SaveAsPrefabAsset(baked, $"{parentFolder}/{bakedName}.prefab");
DestroyImmediate(baked);
}
private HashSet<Transform> GetExcluded()
{
var excluded = new HashSet<Transform>();
foreach (var obj in _doNotBake)
{
if (!obj) continue;
ExcludeHierarchy(obj.transform, excluded);
}
return excluded;
}
private void ExcludeHierarchy(Transform root, HashSet<Transform> excluded)
{
excluded.Add(root);
for (var i = 0; i < root.childCount; i++) ExcludeHierarchy(root.GetChild(i), excluded);
}
private void AddExcluded(Transform root)
{
foreach (var obj in _doNotBake)
{
if (!obj) continue;
var clone = Instantiate(obj, root);
clone.name = obj.name;
ApplyRelativeTransform(obj.transform, clone.transform);
}
}
private void ApplyRelativeTransform(Transform source, Transform target)
{
var relativeMatrix = transform.worldToLocalMatrix * source.localToWorldMatrix;
var localPosition = relativeMatrix.MultiplyPoint3x4(Vector3.zero);
var localForward = relativeMatrix.MultiplyVector(Vector3.forward);
var localUp = relativeMatrix.MultiplyVector(Vector3.up);
var scaleX = relativeMatrix.GetColumn(0).magnitude;
var scaleY = relativeMatrix.GetColumn(1).magnitude;
var scaleZ = relativeMatrix.GetColumn(2).magnitude;
target.localPosition = localPosition;
target.localRotation = Quaternion.LookRotation(localForward, localUp);
target.localScale = new Vector3(scaleX, scaleY, scaleZ);
}
#endif
}
}

View File

@@ -0,0 +1,91 @@
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Splines;
#if UNITY_EDITOR
using System.IO;
using UnityEditor;
#endif
namespace DanieleMarotta.RiversongCodeShowcase
{
[RequireComponent(typeof(SplineContainer))]
public class WaterFlowMapBaker : MonoBehaviour
{
#if UNITY_EDITOR
public int WorldSize = 100;
public int MapSize = 512;
public float InfluenceRadius = 10;
public Texture2D OutputTexture;
[Button(ButtonSizes.Large)]
[GUIColor("cyan")]
public void BakeFlowMap()
{
var splineContainer = GetComponent<SplineContainer>();
var texture = new Texture2D(MapSize, MapSize, TextureFormat.RGBA32, false, true)
{
wrapMode = TextureWrapMode.Clamp,
filterMode = FilterMode.Bilinear
};
for (var y = 0; y < MapSize; y++)
for (var x = 0; x < MapSize; x++)
{
var c = new Color(0.5f, 0.5f, 0, 0);
var uv = new Vector2((x + 0.5f) / MapSize, (y + 0.5f) / MapSize);
var world = new Vector3(uv.x * WorldSize, 0, uv.y * WorldSize);
var distance = SplineUtility.GetNearestPoint(splineContainer.Spline, world, out _, out var t);
if (distance <= InfluenceRadius)
{
var tangent = splineContainer.Spline.EvaluateTangent(t);
var direction = new Vector2(tangent.x, tangent.z);
if (direction.sqrMagnitude > 0.0001f) direction.Normalize();
var influence = Mathf.Clamp01(1 - distance / InfluenceRadius);
c.r = direction.x * 0.5f + 0.5f;
c.g = direction.y * 0.5f + 0.5f;
c.b = influence;
}
texture.SetPixel(x, y, c);
}
texture.Apply(false, false);
var textureDirty = false;
if (!OutputTexture)
{
var path = EditorUtility.SaveFilePanelInProject("Save Flow Map", "WaterFlowMap", "asset", string.Empty);
if (!string.IsNullOrEmpty(path))
{
texture.name = Path.GetFileNameWithoutExtension(path);
AssetDatabase.CreateAsset(texture, path);
OutputTexture = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
textureDirty = true;
}
}
else
{
texture.name = OutputTexture.name;
EditorUtility.CopySerialized(texture, OutputTexture);
textureDirty = true;
}
if (textureDirty)
{
EditorUtility.SetDirty(OutputTexture);
AssetDatabase.SaveAssets();
}
}
#endif
}
}