3D: камера как на карте мира, расчистка только в зоне, почта под Наградами, старт с City3D

This commit is contained in:
2026-11-05 17:46:42 +03:00
parent e26eeaffaf
commit 413c627d5c
8 changed files with 86 additions and 50 deletions
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 030e644cb052c0f4d959e499015370fd
+2
View File
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 904233d7647ed024d920bdbbd4058aba
+6 -3
View File
@@ -3,11 +3,13 @@ using UnityEditor;
// Следит, чтобы сцена карты мира (WorldMap, от Саши) была в Build Settings --
// иначе переход город -> карта не работает. Срабатывает сам после каждой компиляции.
// Стартовая сцена (05.11): 3D-поселение City3D, если оно есть в проекте, иначе 2D-город MVP1.
[InitializeOnLoad]
public static class SceneListFixer
{
const string CityScene = "Assets/Scenes/MVP1.unity";
const string WorldScene = "Assets/Scenes/WorldMap.unity";
const string City3DScene = "Assets/Scenes/City3D.unity";
static SceneListFixer() { EditorApplication.delayCall += Ensure; }
@@ -15,15 +17,16 @@ public static class SceneListFixer
{
var scenes = new List<EditorBuildSettingsScene>(EditorBuildSettings.scenes);
bool changed = false;
foreach (var path in new[] { CityScene, WorldScene })
string start = AssetDatabase.LoadAssetAtPath<SceneAsset>(City3DScene) != null ? City3DScene : CityScene;
foreach (var path in new[] { start, CityScene, WorldScene })
{
if (AssetDatabase.LoadAssetAtPath<SceneAsset>(path) == null) continue;
if (scenes.Exists(s => s.path == path)) continue;
scenes.Add(new EditorBuildSettingsScene(path, true));
changed = true;
}
// город должен быть первым (с него стартует игра)
int city = scenes.FindIndex(s => s.path == CityScene);
// стартовый город должен быть первым (с него стартует игра)
int city = scenes.FindIndex(s => s.path == start);
if (city > 0) { var c = scenes[city]; scenes.RemoveAt(city); scenes.Insert(0, c); changed = true; }
if (changed)
{
+32 -32
View File
@@ -574,11 +574,11 @@ RectTransform:
- {fileID: 8073695889979356888}
m_Father: {fileID: 3982763195980837839}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 432, y: 0}
m_AnchorMin: {x: 0.416667, y: 1}
m_AnchorMax: {x: 0.416667, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 216, y: 190}
m_Pivot: {x: 0, y: 1}
m_Pivot: {x: 0.5, y: 1}
--- !u!222 &1161890866068155973
CanvasRenderer:
m_ObjectHideFlags: 0
@@ -949,11 +949,11 @@ RectTransform:
- {fileID: 6625169345854146367}
m_Father: {fileID: 3982763195980837839}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 1625, y: 0}
m_AnchorMin: {x: 0.75, y: 1}
m_AnchorMax: {x: 0.75, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 216, y: 190}
m_Pivot: {x: 0, y: 1}
m_Pivot: {x: 0.5, y: 1}
--- !u!222 &1574865706497193506
CanvasRenderer:
m_ObjectHideFlags: 0
@@ -2083,11 +2083,11 @@ RectTransform:
- {fileID: 5819281947737217065}
m_Father: {fileID: 3982763195980837839}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 1409, y: 0}
m_AnchorMin: {x: 0.583333, y: 1}
m_AnchorMax: {x: 0.583333, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 216, y: 190}
m_Pivot: {x: 0, y: 1}
m_Pivot: {x: 0.5, y: 1}
--- !u!222 &8054631941258821217
CanvasRenderer:
m_ObjectHideFlags: 0
@@ -2724,11 +2724,11 @@ RectTransform:
- {fileID: 6422401267007934049}
m_Father: {fileID: 3982763195980837839}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchorMin: {x: 0.083333, y: 1}
m_AnchorMax: {x: 0.083333, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 216, y: 190}
m_Pivot: {x: 0, y: 1}
m_Pivot: {x: 0.5, y: 1}
--- !u!222 &1319220892576480966
CanvasRenderer:
m_ObjectHideFlags: 0
@@ -3692,11 +3692,11 @@ RectTransform:
- {fileID: 4996814669716176554}
m_Father: {fileID: 3982763195980837839}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 216, y: 0}
m_AnchorMin: {x: 0.25, y: 1}
m_AnchorMax: {x: 0.25, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 216, y: 190}
m_Pivot: {x: 0, y: 1}
m_Pivot: {x: 0.5, y: 1}
--- !u!222 &5060908328969132528
CanvasRenderer:
m_ObjectHideFlags: 0
@@ -4111,13 +4111,14 @@ RectTransform:
- {fileID: 3561707029477629692}
- {fileID: 3476476116216033849}
- {fileID: 628544459168404126}
- {fileID: 113684291289998490}
m_Father: {fileID: 2090086157864901368}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 1, y: 1}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: -145, y: -425}
m_AnchoredPosition: {x: -145, y: -190}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
m_Pivot: {x: 0.5, y: 1}
--- !u!114 &4213699917549184054
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -9582,13 +9583,13 @@ RectTransform:
- {fileID: 8503115147530003650}
- {fileID: 3734242786053216068}
- {fileID: 1570549891241898707}
m_Father: {fileID: 3982763195980837839}
m_Father: {fileID: 4178416176766117917}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 1, y: 1}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: -162, y: 172.02252}
m_SizeDelta: {x: 128.34, y: 102.34}
m_Pivot: {x: 0, y: 1}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 118, y: 118}
m_Pivot: {x: 1, y: 1}
--- !u!222 &2919605703939985733
CanvasRenderer:
m_ObjectHideFlags: 0
@@ -10263,11 +10264,11 @@ RectTransform:
- {fileID: 8771264060587801238}
m_Father: {fileID: 3982763195980837839}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 1808, y: 0}
m_AnchorMin: {x: 0.916667, y: 1}
m_AnchorMax: {x: 0.916667, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 216, y: 190}
m_Pivot: {x: 0, y: 1}
m_Pivot: {x: 0.5, y: 1}
--- !u!222 &7841802503944609977
CanvasRenderer:
m_ObjectHideFlags: 0
@@ -14658,7 +14659,6 @@ RectTransform:
- {fileID: 3980605506120029191}
- {fileID: 6797326426102539044}
- {fileID: 6246972527393936552}
- {fileID: 113684291289998490}
m_Father: {fileID: 2090086157864901368}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
+16 -7
View File
@@ -20,7 +20,6 @@ public class City3DCamera : MonoBehaviour
bool focusing;
Vector3 lastPointer;
bool panning;
float lastPinch;
// Перемещение камеры пальцем можно выключить на время перетаскивания здания
public bool AllowPan { get; set; } = true;
@@ -43,6 +42,10 @@ public class City3DCamera : MonoBehaviour
public void BeginPan(Vector3 screen) { panning = AllowPan; lastPointer = screen; focusing = false; }
public void EndPan() { panning = false; }
// Палец ещё не сдвинулся настолько, чтобы считать это перетаскиванием: камера стоит, но точка запоминается --
// когда перетаскивание начнётся, карта не прыгнет (как на карте мира, 05.11)
public void Track(Vector3 screen) { lastPointer = screen; }
public void Pan(Vector3 screen)
{
if (!panning || cam == null) return;
@@ -50,6 +53,7 @@ public class City3DCamera : MonoBehaviour
target += a - b;
lastPointer = screen;
Clamp();
Apply(); // сразу: иначе следующий шаг считался бы от старого положения камеры -- камера дёргалась (05.11)
}
public void Zoom(float delta)
@@ -65,14 +69,19 @@ public class City3DCamera : MonoBehaviour
if (UIBlocker.IsPointerOverUI()) wheel = 0f; // колесо над окнами HUD -- не приближать камеру (05.11)
if (Mathf.Abs(wheel) > 0.01f) Zoom(wheel * 0.1f);
// Два пальца -- приближение
if (Input.touchCount == 2)
// Два пальца -- приближение, как на карте мира: расстояние камеры меняется во столько раз,
// во сколько изменилось расстояние между пальцами за кадр (не зависит от того, увидели ли касание) (05.11)
if (Input.touchCount >= 2)
{
float d = Vector2.Distance(Input.GetTouch(0).position, Input.GetTouch(1).position);
if (Input.GetTouch(1).phase == TouchPhase.Began) lastPinch = d;
else if (lastPinch > 0) { Zoom((d - lastPinch) / Mathf.Max(200f, Screen.height)); lastPinch = d; }
Touch a = Input.GetTouch(0), b = Input.GetTouch(1);
float previous = ((a.position - a.deltaPosition) - (b.position - b.deltaPosition)).magnitude;
float current = (a.position - b.position).magnitude;
if (previous > 0.01f && current > 0.01f)
{
focusing = false;
distance = Mathf.Clamp(distance * previous / current, minDistance, maxDistance);
}
}
else lastPinch = 0;
if (focusing)
{
+15 -5
View File
@@ -677,7 +677,8 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
{
if ((pos - pointerDown).magnitude > Screen.height * 0.015f) pointerMoved = true;
if (draggingGhost) DragGhost(pos);
else cameraRig.Pan(pos);
else if (pointerMoved) cameraRig.Pan(pos); // карта едет только при настоящем перетаскивании
else cameraRig.Track(pos); // а мелкое дрожание пальца при тапе её не трогает
}
else if (up && !pointerOverGui)
{
@@ -730,6 +731,12 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
}
selectedBuilding = building;
selectedObstacle = building == null ? map.ObstacleAt(cx, cy) : null;
// Расчищать можно только в открытой зоне стройки (за её пределами -- после улучшения Ратуши) (05.11)
if (selectedObstacle != null && !map.IsOpen(cx, cy, townHallLevel))
{
selectedObstacle = null;
Say("Это за пределами зоны стройки -- откроется после улучшения Ратуши");
}
}
// Клетка под пальцем. groundOnly -- только земля (перетаскивание призрака); иначе здание под пальцем -- его клетка
@@ -845,10 +852,13 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
// Окно HUD открыто -- временные кнопки сцены не показываем (и не ловим нажатия)
if (UIPanelManager.Instance != null && UIPanelManager.Instance.CurrentId != "") return;
// Под верхней панелью HUD, справа: Ратуша (тест), редактирование
Box(new Rect(w - 480, HudTop, 290, 44), $"3D (прототип) • Ратуша {townHallLevel} ур.");
if (Button(new Rect(w - 185, HudTop, 55, 44), "−")) ChangeTownHall(-1);
if (Button(new Rect(w - 125, HudTop, 55, 44), "+")) ChangeTownHall(+1);
// Под верхней панелью HUD, справа: Ратуша −/+ -- только в редакторе и отладочной сборке (тест зон), редактирование
if (Application.isEditor || Debug.isDebugBuild)
{
Box(new Rect(w - 480, HudTop, 290, 44), $"3D (тест) • Ратуша {townHallLevel} ур.");
if (Button(new Rect(w - 185, HudTop, 55, 44), "−")) ChangeTownHall(-1);
if (Button(new Rect(w - 125, HudTop, 55, 44), "+")) ChangeTownHall(+1);
}
if (mode == Mode.View || mode == Mode.Edit)
{
if (Button(new Rect(w - 250, HudTop + 50, 240, 44), mode == Mode.Edit ? "Готово" : "Редактировать"))
+10
View File
@@ -187,9 +187,19 @@ public static class City3DGameplay
if (!canvas.isRootCanvas || canvas.renderMode != RenderMode.ScreenSpaceCamera) continue;
canvas.worldCamera = cam;
canvas.planeDistance = cam.nearClipPlane + 0.05f;
HideWorldSprites(canvas);
}
}
// В HUD есть картинка-спрайт мира (SpriteRenderer) -- плашка с мечом по центру нижней панели. На холсте у
// камеры она рисуется поверх всего HUD, даже поверх открытых окон, а сама картинка без прозрачности
// ("шахматка"). Решение 05.11 (Миша, Володя): в 3D-сцене её убираем, пока не будет новой картинки или кнопки.
static void HideWorldSprites(Canvas canvas)
{
foreach (var sr in canvas.GetComponentsInChildren<SpriteRenderer>(true))
sr.enabled = false;
}
// Награда за расчистку (решение 04.11): +10 дерева за дерево или +10 камня за валун, один раз, в пределах склада
public static void RewardClearing(bool tree)
{