Feature/hero 3d #21
@@ -408,17 +408,20 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
|
||||
go.AddComponent<MeshRenderer>().sharedMaterial = Map3D.TexturedMat(groundTexture);
|
||||
go.AddComponent<MeshCollider>().sharedMesh = mesh;
|
||||
|
||||
// Лес и луга за краем участка: камера не видит пустого неба
|
||||
Map3D.Block(transform, "OuterLand", PrimitiveType.Cube, new Color(0.24f, 0.4f, 0.22f), new Vector3(n / 2f, -0.7f, n / 2f),
|
||||
new Vector2(n + 200f, n + 200f), 0.1f);
|
||||
// Местность за краем участка -- продолжение холмов, реки и гор (City3DLayout.SkirtHeight), дальше -- равнина
|
||||
BuildSkirt();
|
||||
Map3D.Block(transform, "OuterLand", PrimitiveType.Cube, new Color(0.3f, 0.5f, 0.26f),
|
||||
new Vector3(n / 2f, City3DLayout.SkirtFloor - 0.05f, n / 2f), new Vector2(n + 2 * City3DLayout.SkirtCells + 300f, n + 2 * City3DLayout.SkirtCells + 300f), 0.1f);
|
||||
var forest = new System.Random(7);
|
||||
for (int i = 0; i < 140; i++)
|
||||
for (int i = 0; i < 260; i++)
|
||||
{
|
||||
// кольцо деревьев вокруг участка
|
||||
float angle = (float)forest.NextDouble() * Mathf.PI * 2f, r = n * 0.72f + (float)forest.NextDouble() * 14f;
|
||||
var p = new Vector3(n / 2f + Mathf.Cos(angle) * r, -0.6f, n / 2f + Mathf.Sin(angle) * r);
|
||||
if (p.x > 0 && p.x < n && p.z > 0 && p.z < n) continue; // внутри участка -- свои деревья
|
||||
Map3D.Tree(transform, "ForestTree", p, 1.4f + (float)forest.NextDouble() * 0.8f, forest.Next(6));
|
||||
// лес вокруг участка: гуще у края, реже дальше; не в реке и не на горах
|
||||
float angle = (float)forest.NextDouble() * Mathf.PI * 2f;
|
||||
float r = n * 0.6f + Mathf.Pow((float)forest.NextDouble(), 1.6f) * (City3DLayout.SkirtCells + 4f);
|
||||
float x = n / 2f + Mathf.Cos(angle) * r, z = n / 2f + Mathf.Sin(angle) * r;
|
||||
if (x > -0.5f && x < n + 0.5f && z > -0.5f && z < n + 0.5f) continue; // внутри участка -- свои деревья
|
||||
if (City3DLayout.IsRiver(x, z) || City3DLayout.IsMountain(x, z)) continue;
|
||||
Map3D.Tree(transform, "ForestTree", new Vector3(x, City3DLayout.SkirtHeight(x, z), z), 1.4f + (float)forest.NextDouble() * 0.8f, forest.Next(6));
|
||||
}
|
||||
|
||||
// Вода -- плоскость над руслом
|
||||
@@ -427,10 +430,61 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
|
||||
Destroy(water.GetComponent<Collider>());
|
||||
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.transform.localScale = new Vector3(9f, 0.05f, n + 2 * (City3DLayout.SkirtCells - 22)); // река течёт и за краем участка
|
||||
water.GetComponent<Renderer>().sharedMaterial = Mat(new Color(0.3f, 0.58f, 0.85f));
|
||||
}
|
||||
|
||||
// Местность вокруг участка: сетка 1 клетка (на стыке -- те же вершины, что у земли участка, без щелей),
|
||||
// без самого участка. Цвет -- тот же, что у земли (трава пятнами, берег, вода, горы со снегом), 1 пиксель на клетку
|
||||
void BuildSkirt()
|
||||
{
|
||||
int n = City3DLayout.Size, s = City3DLayout.SkirtCells, w = n + 2 * s;
|
||||
var verts = new Vector3[(w + 1) * (w + 1)];
|
||||
var uvs = new Vector2[verts.Length];
|
||||
for (int z = 0; z <= w; z++)
|
||||
for (int x = 0; x <= w; x++)
|
||||
{
|
||||
float wx = x - s, wz = z - s;
|
||||
int i = z * (w + 1) + x;
|
||||
verts[i] = new Vector3(wx, City3DLayout.SkirtHeight(wx, wz), wz);
|
||||
uvs[i] = new Vector2(x / (float)w, z / (float)w);
|
||||
}
|
||||
var tris = new List<int>(w * w * 6);
|
||||
for (int z = 0; z < w; z++)
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
if (x >= s && x < s + n && z >= s && z < s + n) continue; // участок -- своя земля
|
||||
int i = z * (w + 1) + x;
|
||||
tris.Add(i); tris.Add(i + w + 1); tris.Add(i + 1);
|
||||
tris.Add(i + 1); tris.Add(i + w + 1); tris.Add(i + w + 2);
|
||||
}
|
||||
var mesh = new Mesh { name = "City3D_Skirt", indexFormat = UnityEngine.Rendering.IndexFormat.UInt32 };
|
||||
mesh.vertices = verts;
|
||||
mesh.uv = uvs;
|
||||
mesh.SetTriangles(tris, 0);
|
||||
mesh.RecalculateNormals();
|
||||
|
||||
var tex = new Texture2D(w, w, TextureFormat.RGBA32, false) { filterMode = FilterMode.Bilinear, wrapMode = TextureWrapMode.Clamp };
|
||||
var px = new Color32[w * w];
|
||||
for (int z = 0; z < w; z++)
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
float wx = x - s + 0.5f, wz = z - s + 0.5f, h = City3DLayout.SkirtHeight(wx, wz);
|
||||
Color c = City3DLayout.IsRiver(wx, wz) ? new Color(0.32f, 0.5f, 0.72f)
|
||||
: City3DLayout.IsMountain(wx, wz) && h > 1.5f ? Color.Lerp(new Color(0.48f, 0.45f, 0.42f), new Color(0.85f, 0.86f, 0.88f), Mathf.Clamp01((h - 3.2f) / 1.5f))
|
||||
: Mathf.Abs(wx - City3DLayout.RiverX(wz)) < 2.2f ? 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(wx * 0.25f, wz * 0.25f));
|
||||
px[z * w + x] = c;
|
||||
}
|
||||
tex.SetPixels32(px);
|
||||
tex.Apply(false);
|
||||
|
||||
var go = new GameObject("Skirt");
|
||||
go.transform.SetParent(transform, false);
|
||||
go.AddComponent<MeshFilter>().sharedMesh = mesh;
|
||||
go.AddComponent<MeshRenderer>().sharedMaterial = Map3D.TexturedMat(tex); // без коллайдера: тап и стройка -- только на участке
|
||||
}
|
||||
|
||||
void BuildObstacles()
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
@@ -39,9 +39,22 @@ public sealed class City3DLayout
|
||||
return hills;
|
||||
}
|
||||
|
||||
static float RiverX(float z) => 37f + 3f * Mathf.Sin(z * 0.17f);
|
||||
static bool IsRiver(float x, float z) => Mathf.Abs(x - RiverX(z)) < 1.3f;
|
||||
static bool IsMountain(float x, float z) => x < 9f && z > 34f || x < 4f && z > 24f;
|
||||
public static float RiverX(float z) => 37f + 3f * Mathf.Sin(z * 0.17f);
|
||||
public static bool IsRiver(float x, float z) => Mathf.Abs(x - RiverX(z)) < 1.3f;
|
||||
public static bool IsMountain(float x, float z) => x < 9f && z > 34f || x < 4f && z > 24f;
|
||||
|
||||
// Местность за краем участка (только вид, стройки нет): те же холмы, река и горы продолжаются без шва,
|
||||
// к дальнему краю (SkirtCells) плавно опускаются до общей равнины SkirtFloor (06.11: при отдалении край участка
|
||||
// выглядел обрезанным -- холмы и река обрывались уступом)
|
||||
public const int SkirtCells = 60;
|
||||
public const float SkirtFloor = -0.7f;
|
||||
|
||||
public static float SkirtHeight(float x, float z)
|
||||
{
|
||||
float outside = Mathf.Max(0f, Mathf.Max(Mathf.Max(-x, x - Size), Mathf.Max(-z, z - Size)));
|
||||
float fade = 1f - Mathf.SmoothStep(0f, 1f, Mathf.InverseLerp(SkirtCells - 20f, SkirtCells, outside));
|
||||
return Mathf.Lerp(SkirtFloor, HeightAt(x, z), fade);
|
||||
}
|
||||
|
||||
public static City3DLayout Create(PlacementRules rules = null)
|
||||
{
|
||||
|
||||
@@ -119,6 +119,7 @@
|
||||
|
||||
| Дата | Кто | Что |
|
||||
| --- | --- | --- |
|
||||
| 06.11.2026 | Claude (Саша) | 3D: местность за краем участка — 60 клеток вокруг (`City3DController.BuildSkirt`, `City3DLayout.SkirtHeight`): те же холмы, река и горы продолжаются без шва (на стыке те же вершины) и к дальнему краю плавно опускаются до равнины; река течёт дальше, вокруг лес. Только вид: без коллайдера, стройки и тапов нет. Раньше при отдалении участок обрывался уступом. |
|
||||
| 06.11.2026 | Claude (Саша) | Герой у Терема в 3D (перенос из 2D): `City3D/City3DHero.cs` — фигурка из простых форм появляется у ворот достроенного Терема и гуляет по свободным клеткам 3D-карты (земля, открытая зона, без зданий и деревьев) не дальше 6 клеток от Терема, шагами на соседнюю клетку — сквозь здания не проходит; тап — фраза и окно героя; в режиме стройки и переноса прячется. Создаёт и убирает `City3DController.UpdateHero`. На экономику и сохранение не влияет. |
|
||||
| 06.11.2026 | Claude (Саша) | Открытие зоны Ратушей: камера показывает только край новой зоны — старую зону и 2 клетки новой за её границей (`ZoneEdgeCells`), а не всю зону; ближе, чем была, не подъезжает (`City3DCamera.FocusOn(..., onlyZoomOut)`). Раньше при ускорении улучшения Ратуши камера улетала слишком далеко. |
|
||||
| 06.11.2026 | Claude (Валдемар) | Этап 1 перехода на 3D (2D-город больше не развиваем). Сверка: все 12 зданий 2D (8 `Data_*.asset` + Склад, Амбар, Госпиталь, Терем) есть в 3D с теми же ценами и уровнем Ратуши; ни одна кнопка не ведёт в MVP1. Камера (`City3DCamera`): центр кадра не уходит за открытые зоны; при самом дальнем отдалении открытая зона видна целиком между HUD сверху и снизу — дальше всего = max(подобранное в DEV, сколько нужно для зоны), считается по проекции камеры (`FitDistance`); «+ / −» и колесо приближают плавно; перетаскивание считается на высоте земли под пальцем (на холмах карта идёт точно за пальцем); подъезд к призраку — `MoveTo`, без смены приближения. Ввод: после щипка отпущенный палец больше не считается тапом (не открывается окно здания). `SceneListFixer` больше не добавляет и не включает MVP1 в Build Settings. Числа камеры — ТЕСТ. |
|
||||
|
||||
Reference in New Issue
Block a user