3D-сцены красивее: цвета, свет, небо, дымка; домики с крышами, замок, шатры, деревья, камни

Цвет -- в _BaseColor (URP), раньше всё было белым. Настройка 3D-рендерера добавляет его в URP Asset
каждого уровня качества (в проекте URP задан только в Quality) + отдельная команда меню.
Карта мира: замок со стенами и башнями, города -- домики, лагеря -- шатры с костром, кристаллы точек, рощи.
3D-поселение: здания с двускатными крышами и приметами, ели и лиственные, песчаные берега, снег на скалах.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Alexander
2026-11-04 15:03:16 +01:00
co-authored by Claude Opus 5.5
parent 9a6e5a2e7b
commit 9a91926b2c
7 changed files with 866 additions and 101 deletions
+92 -52
View File
@@ -88,10 +88,7 @@ public class City3DController : MonoBehaviour
cameraRig.target = new Vector3(City3DLayout.Size / 2f, 0, City3DLayout.Size / 2f);
cameraRig.Apply();
// Материал URP по умолчанию берём у примитива: шейдер точно есть в сборке
var probe = GameObject.CreatePrimitive(PrimitiveType.Cube);
baseMaterial = new Material(probe.GetComponent<Renderer>().sharedMaterial);
Destroy(probe);
Map3D.SetUpLook(cam, 30f, 85f); // солнце, небо, дымка вдали
layout = City3DLayout.Create();
map = layout.Map;
@@ -105,11 +102,7 @@ public class City3DController : MonoBehaviour
RefreshGround();
}
Material Mat(Color color)
{
var m = new Material(baseMaterial) { color = color };
return m;
}
static Material Mat(Color color) => Map3D.Mat(color);
void BuildGround()
{
@@ -146,9 +139,7 @@ public class City3DController : MonoBehaviour
filterMode = FilterMode.Point,
wrapMode = TextureWrapMode.Clamp
};
var mat = Mat(Color.white);
mat.mainTexture = groundTexture;
go.AddComponent<MeshRenderer>().sharedMaterial = mat;
go.AddComponent<MeshRenderer>().sharedMaterial = Map3D.TexturedMat(groundTexture);
go.AddComponent<MeshCollider>().sharedMesh = mesh;
// Вода -- плоскость над руслом
@@ -158,50 +149,35 @@ public class City3DController : MonoBehaviour
water.transform.SetParent(transform, false);
water.transform.position = new Vector3(37f, -0.25f, n / 2f);
water.transform.localScale = new Vector3(9f, 0.05f, n);
water.GetComponent<Renderer>().sharedMaterial = Mat(new Color(0.25f, 0.5f, 0.85f));
water.GetComponent<Renderer>().sharedMaterial = Mat(new Color(0.3f, 0.58f, 0.85f));
}
void BuildObstacles()
{
var trunk = Mat(new Color(0.45f, 0.3f, 0.18f));
var leaves = Mat(new Color(0.18f, 0.48f, 0.2f));
var stone = Mat(new Color(0.55f, 0.55f, 0.58f));
int i = 0;
foreach (var o in layout.Obstacles)
{
var root = new GameObject(o.Id);
root.transform.SetParent(transform, false);
Vector2 center = Vector2.zero;
foreach (var c in o.Cells) center += new Vector2(c.x + 0.5f, c.y + 0.5f);
center /= o.Cells.Count;
float h = City3DLayout.HeightAt(center.x, center.y);
root.transform.position = new Vector3(center.x, h, center.y);
if (o.IsTree)
{
Part(root, PrimitiveType.Cylinder, new Vector3(0, 0.5f, 0), new Vector3(0.25f, 0.5f, 0.25f), trunk);
Part(root, PrimitiveType.Sphere, new Vector3(0, 1.4f, 0), new Vector3(1.1f, 1.3f, 1.1f), leaves);
}
else
{
float s = o.Cells.Count > 1 ? 1.8f : 0.9f;
Part(root, PrimitiveType.Sphere, new Vector3(0, s * 0.3f, 0), new Vector3(s, s * 0.7f, s * 0.9f), stone);
}
obstacleViews[o.Id] = root;
var ground = new Vector3(center.x, City3DLayout.HeightAt(center.x, center.y), center.y);
i++;
var view = o.IsTree
? Map3D.Tree(transform, o.Id, ground, 1.0f + (i * 37 % 10) * 0.05f, i) // ели и лиственные вперемешку
: Map3D.Rock(transform, o.Id, ground, o.Cells.Count > 1 ? 1.7f : 0.85f, i);
obstacleViews[o.Id] = view.gameObject;
}
}
// Трава кустиками -- только украшение: прячется под зданием и возвращается
void BuildGrass()
{
var grass = Mat(new Color(0.35f, 0.65f, 0.3f));
foreach (var p in layout.Grass)
Color[] greens = { new Color(0.3f, 0.6f, 0.25f), new Color(0.38f, 0.66f, 0.28f), new Color(0.45f, 0.68f, 0.3f) };
for (int i = 0; i < layout.Grass.Count; i++)
{
var g = GameObject.CreatePrimitive(PrimitiveType.Cube);
Destroy(g.GetComponent<Collider>());
g.name = "Grass";
g.transform.SetParent(transform, false);
g.transform.position = new Vector3(p.x, City3DLayout.HeightAt(p.x, p.y) + 0.08f, p.y);
g.transform.localScale = new Vector3(0.25f, 0.16f, 0.25f);
g.transform.rotation = Quaternion.Euler(0, p.x * 37f, 0);
g.GetComponent<Renderer>().sharedMaterial = grass;
Vector2 p = layout.Grass[i];
var g = Map3D.Shape(transform, "Grass", Map3D.Cone, greens[i % greens.Length],
new Vector3(p.x, City3DLayout.HeightAt(p.x, p.y), p.y), Vector2.one * 0.28f, 0.22f + (i % 3) * 0.06f).gameObject;
grassViews.Add(g);
}
}
@@ -227,9 +203,13 @@ public class City3DController : MonoBehaviour
for (int x = 0; x < n; x++)
{
CellKind k = map.KindAt(x, y);
Color c = k == CellKind.Water ? new Color(0.3f, 0.45f, 0.7f)
: k == CellKind.Mountain ? new Color(0.5f, 0.48f, 0.45f)
: Color.Lerp(new Color(0.35f, 0.58f, 0.3f), new Color(0.42f, 0.66f, 0.34f), Mathf.PerlinNoise(x * 0.3f, y * 0.3f));
bool shore = k == CellKind.Ground && (map.KindAt(x - 1, y) == CellKind.Water || map.KindAt(x + 1, y) == CellKind.Water
|| map.KindAt(x, y - 1) == CellKind.Water || map.KindAt(x, y + 1) == CellKind.Water);
float hm = map.HeightAt(x, y) / 1000f;
Color c = k == CellKind.Water ? new Color(0.32f, 0.5f, 0.72f)
: k == CellKind.Mountain ? Color.Lerp(new Color(0.48f, 0.45f, 0.42f), new Color(0.85f, 0.86f, 0.88f), Mathf.Clamp01((hm - 3.2f) / 1.5f)) // снег на вершинах
: shore ? new Color(0.78f, 0.72f, 0.52f) // песчаный берег
: Color.Lerp(new Color(0.33f, 0.56f, 0.27f), new Color(0.45f, 0.66f, 0.32f), Mathf.PerlinNoise(x * 0.25f, y * 0.25f));
if (grid && k == CellKind.Ground)
{
int zone = map.ZoneAt(x, y);
@@ -271,18 +251,65 @@ public class City3DController : MonoBehaviour
return true;
}
// Кровля по типу здания
static Color RoofFor(int type)
{
switch (Types[type].Name)
{
case "Ратуша": return new Color(0.65f, 0.15f, 0.12f);
case "Госпиталь": return new Color(0.45f, 0.55f, 0.65f);
case "Казармы": return new Color(0.4f, 0.15f, 0.12f);
case "Каменоломня":
case "Шахта": return new Color(0.35f, 0.35f, 0.38f);
default: return Color.Lerp(Types[type].Color, new Color(0.3f, 0.15f, 0.1f), 0.55f);
}
}
// Здание на примитивах: стены и двускатная крыша в «единичном» объёме Body (растягивается под габарит и высоту),
// у некоторых -- свои приметы: башня Ратуши, флаг Казарм, крест Госпиталя, тёмный вход Шахты
GameObject CreateBuildingView(string id, int type)
{
var t = Types[type];
var root = new GameObject(id + "_" + t.Name);
root.transform.SetParent(transform, false);
var body = GameObject.CreatePrimitive(PrimitiveType.Cube);
body.name = "Body";
body.transform.SetParent(root.transform, false);
body.GetComponent<Renderer>().sharedMaterial = Mat(t.Color);
var door = Part(root, PrimitiveType.Cube, Vector3.zero, Vector3.one, Mat(new Color(0.25f, 0.18f, 0.12f)));
var body = new GameObject("Body").transform;
body.SetParent(root.transform, false);
var hit = body.gameObject.AddComponent<BoxCollider>(); // тап по зданию попадает в него целиком
hit.center = new Vector3(0, 0.5f, 0);
hit.size = Vector3.one;
Color walls = Color.Lerp(t.Color, Color.white, 0.3f), roofColor = RoofFor(type);
Unit(body, PrimitiveType.Cube, new Vector3(0, 0.31f, 0), new Vector3(1f, 0.62f, 1f), walls);
var top = Map3D.Shape(body, "Roof", Map3D.Roof, roofColor, Vector3.zero, new Vector2(1.08f, 1.12f), 0.38f).transform;
top.localPosition = new Vector3(0, 0.62f, 0);
switch (t.Name)
{
case "Ратуша":
Unit(body, PrimitiveType.Cube, new Vector3(0.3f, 0.7f, 0.3f), new Vector3(0.28f, 1.4f, 0.28f), walls);
var spire = Map3D.Shape(body, "Spire", Map3D.Pyramid, roofColor, Vector3.zero, Vector2.one * 0.4f, 0.4f, 45f).transform;
spire.localPosition = new Vector3(0.3f, 1.4f, 0.3f);
break;
case "Казармы":
Unit(body, PrimitiveType.Cylinder, new Vector3(-0.4f, 0.75f, 0.4f), new Vector3(0.03f, 0.75f, 0.03f), new Color(0.35f, 0.25f, 0.15f));
Unit(body, PrimitiveType.Cube, new Vector3(-0.32f, 1.35f, 0.4f), new Vector3(0.16f, 0.12f, 0.01f), new Color(0.8f, 0.15f, 0.15f));
break;
case "Госпиталь":
Unit(body, PrimitiveType.Cube, new Vector3(0, 0.38f, -0.505f), new Vector3(0.08f, 0.28f, 0.01f), new Color(0.85f, 0.1f, 0.1f));
Unit(body, PrimitiveType.Cube, new Vector3(0, 0.38f, -0.505f), new Vector3(0.24f, 0.08f, 0.01f), new Color(0.85f, 0.1f, 0.1f));
break;
case "Шахта":
Unit(body, PrimitiveType.Cube, new Vector3(0, 0.2f, -0.505f), new Vector3(0.35f, 0.4f, 0.02f), new Color(0.1f, 0.1f, 0.12f));
break;
case "Ферма":
// грядки перед амбаром
for (int i = 0; i < 3; i++)
Unit(body, PrimitiveType.Cube, new Vector3(-0.3f + i * 0.3f, 0.02f, -0.62f), new Vector3(0.22f, 0.04f, 0.2f), new Color(0.55f, 0.75f, 0.25f));
break;
}
var door = Part(root, PrimitiveType.Cube, Vector3.zero, Vector3.one, Mat(new Color(0.28f, 0.18f, 0.1f)));
door.name = "Door"; // показывает, куда повёрнуто здание
var foundation = Part(root, PrimitiveType.Cube, Vector3.zero, Vector3.one, Mat(new Color(0.6f, 0.57f, 0.5f)));
var foundation = Part(root, PrimitiveType.Cube, Vector3.zero, Vector3.one, Mat(new Color(0.62f, 0.58f, 0.52f)));
foundation.name = "Foundation";
var labelGo = new GameObject("Label");
@@ -298,6 +325,17 @@ public class City3DController : MonoBehaviour
return root;
}
// Деталь в единичном объёме здания (Body растягивается под габарит и высоту)
static void Unit(Transform body, PrimitiveType type, Vector3 localPos, Vector3 scale, Color color)
{
var p = GameObject.CreatePrimitive(type);
Destroy(p.GetComponent<Collider>());
p.transform.SetParent(body, false);
p.transform.localPosition = localPos;
p.transform.localScale = scale;
p.GetComponent<Renderer>().sharedMaterial = Mat(color);
}
// Положение, поворот, фундамент по данным карты (или призрака при переносе)
void UpdateBuildingView(string id) { if (map.TryGetBuilding(id, out var r)) LayoutView(buildingViews[id], buildingType[id], r.X, r.Y, r.Rotation); }
@@ -313,9 +351,11 @@ public class City3DController : MonoBehaviour
float top = max / 1000f, bottom = min / 1000f - 0.3f;
root.transform.position = new Vector3(x + w / 2f, top, y + h / 2f);
// Body -- в размерах без поворота и повёрнут сам: конёк крыши и приметы поворачиваются вместе со зданием
var body = root.transform.Find("Body");
body.localScale = new Vector3(w - 0.3f, t.Height, h - 0.3f);
body.localPosition = new Vector3(0, t.Height / 2f, 0);
body.localScale = new Vector3(t.W - 0.3f, t.Height, t.H - 0.3f);
body.localPosition = Vector3.zero;
body.localRotation = Quaternion.Euler(0, -rot, 0);
var foundation = root.transform.Find("Foundation");
foundation.localScale = new Vector3(w - 0.1f, top - bottom, h - 0.1f);
foundation.localPosition = new Vector3(0, (bottom - top) / 2f, 0);