3D: камера в пределах открытых зон, при отдалении видна вся зона, плавный зум, без тапа после щипка; MVP1 не возвращается в сборку; навыки героя без символа, которого нет в шрифте
This commit is contained in:
@@ -4,6 +4,7 @@ using UnityEditor;
|
||||
// Следит, чтобы сцена карты мира (WorldMap, от Саши) была в Build Settings --
|
||||
// иначе переход город -> карта не работает. Срабатывает сам после каждой компиляции.
|
||||
// Стартовая сцена (05.11): 3D-поселение City3D, если оно есть в проекте, иначе 2D-город MVP1.
|
||||
// Выключенный в Build Settings MVP1 (06.11) скрипт не трогает и заново не добавляет.
|
||||
[InitializeOnLoad]
|
||||
public static class SceneListFixer
|
||||
{
|
||||
@@ -18,7 +19,9 @@ public static class SceneListFixer
|
||||
var scenes = new List<EditorBuildSettingsScene>(EditorBuildSettings.scenes);
|
||||
bool changed = false;
|
||||
string start = AssetDatabase.LoadAssetAtPath<SceneAsset>(City3DScene) != null ? City3DScene : CityScene;
|
||||
foreach (var path in new[] { start, CityScene, WorldScene })
|
||||
// 2D-город (MVP1) больше не развиваем (06.11): при 3D-поселении его в список не добавляем и не включаем
|
||||
var wanted = new[] { start, WorldScene };
|
||||
foreach (var path in wanted)
|
||||
{
|
||||
if (AssetDatabase.LoadAssetAtPath<SceneAsset>(path) == null) continue;
|
||||
if (scenes.Exists(s => s.path == path)) continue;
|
||||
|
||||
@@ -452,7 +452,7 @@ public static class BuildingWindow
|
||||
for (int i = 0; i < GameData.HeroSkillNames.Length; i++)
|
||||
{
|
||||
bool has = gp.HasHeroSkill(i);
|
||||
UIKit.Text(card, has ? $"<color=#9be59b>✓</color> {GameData.HeroSkillNames[i]}"
|
||||
UIKit.Text(card, has ? $"<color=#9be59b>•</color> {GameData.HeroSkillNames[i]} — открыт" // без «✓»: его нет в шрифте LiberationSans (06.11)
|
||||
: $"{GameData.HeroSkillNames[i]} — с {GameData.HeroSkillLevels[i]} ур.",
|
||||
22, has ? UIKit.Theme.Text : UIKit.Theme.TextDim);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,25 @@ public class City3DCamera : MonoBehaviour
|
||||
public Vector3 target; // точка на земле, на которую смотрит камера
|
||||
public Rect bounds = new Rect(0, 0, 48, 48); // где может быть target (x, z)
|
||||
|
||||
// Открытая часть поселения (зоны по уровню Ратуши), 06.11: центр кадра из неё не уходит, а при самом дальнем
|
||||
// отдалении она видна целиком -- даже если "Дальше всего" (maxDistance) подобрано меньше. Ширина 0 -- не задана
|
||||
Rect openArea;
|
||||
float fitOpenDistance; // с какого расстояния открытая часть видна целиком (между HUD сверху и снизу)
|
||||
public const float HudTopShare = 0.16f, HudBottomShare = 0.16f; // какую долю экрана закрывает HUD (ТЕСТ)
|
||||
|
||||
// Дальше всего сейчас: подобранное число, но не ближе, чем нужно, чтобы увидеть всё открытое поселение
|
||||
public float MaxDistanceNow => Mathf.Max(maxDistance, fitOpenDistance);
|
||||
|
||||
public void SetOpenArea(Rect area)
|
||||
{
|
||||
openArea = area;
|
||||
fitOpenDistance = FitDistance(area);
|
||||
Clamp();
|
||||
}
|
||||
|
||||
// Плавное приближение кнопками "+ / −" и колесом: камера доезжает до нужного расстояния за доли секунды
|
||||
float zoomTo = -1f;
|
||||
|
||||
Camera cam;
|
||||
Vector3 focusTarget;
|
||||
float focusDistance;
|
||||
@@ -71,14 +90,60 @@ public class City3DCamera : MonoBehaviour
|
||||
public void FocusOn(Rect area)
|
||||
{
|
||||
focusTarget = new Vector3(area.center.x, 0, area.center.y);
|
||||
// отойти так, чтобы область поместилась по высоте экрана (с запасом)
|
||||
float size = Mathf.Max(area.width, area.height);
|
||||
focusDistance = Mathf.Clamp(size * 1.15f, minDistance, maxDistance);
|
||||
// отойти так, чтобы область поместилась между HUD сверху и снизу
|
||||
focusDistance = Mathf.Clamp(FitDistance(area), minDistance, MaxDistanceNow);
|
||||
focusing = true;
|
||||
zoomTo = -1f;
|
||||
}
|
||||
|
||||
// Плавно подъехать к точке, не меняя приближения
|
||||
public void MoveTo(Vector3 point)
|
||||
{
|
||||
focusTarget = new Vector3(point.x, 0, point.z);
|
||||
focusDistance = distance;
|
||||
focusing = true;
|
||||
zoomTo = -1f;
|
||||
}
|
||||
|
||||
// Расстояние, с которого прямоугольник земли (x, z) виден целиком, если смотреть на его центр. Считается по
|
||||
// настоящей проекции камеры (наклон, поле зрения, форма экрана), поэтому верно при любых числах из DEV
|
||||
public float FitDistance(Rect area)
|
||||
{
|
||||
if (cam == null) cam = GetComponent<Camera>();
|
||||
if (cam == null || area.width <= 0 || area.height <= 0) return 0f;
|
||||
Vector3 keepPos = transform.position;
|
||||
Quaternion keepRot = transform.rotation;
|
||||
float keepOrtho = cam.orthographicSize;
|
||||
var center = new Vector3(area.center.x, 0, area.center.y);
|
||||
Vector3[] corners =
|
||||
{
|
||||
new Vector3(area.xMin, 0, area.yMin), new Vector3(area.xMax, 0, area.yMin),
|
||||
new Vector3(area.xMin, 0, area.yMax), new Vector3(area.xMax, 0, area.yMax),
|
||||
};
|
||||
float lo = 1f, hi = 400f;
|
||||
for (int i = 0; i < 24; i++) // двоичный поиск: ближе -- не помещается, дальше -- помещается
|
||||
{
|
||||
float d = (lo + hi) * 0.5f;
|
||||
PlaceAt(center, d);
|
||||
bool fits = true;
|
||||
foreach (var c in corners)
|
||||
{
|
||||
Vector3 v = cam.WorldToViewportPoint(c);
|
||||
if (v.z <= 0 || v.x < 0.02f || v.x > 0.98f || v.y < HudBottomShare || v.y > 1f - HudTopShare) { fits = false; break; }
|
||||
}
|
||||
if (fits) hi = d; else lo = d;
|
||||
}
|
||||
transform.SetPositionAndRotation(keepPos, keepRot);
|
||||
cam.orthographicSize = keepOrtho;
|
||||
return hi;
|
||||
}
|
||||
|
||||
// Начать / продолжить / закончить перемещение камеры. Зовёт контроллер ввода (он решает, что под пальцем)
|
||||
public void BeginPan(Vector3 screen) { panning = AllowPan; lastPointer = screen; focusing = false; }
|
||||
public void BeginPan(Vector3 screen)
|
||||
{
|
||||
panning = AllowPan; lastPointer = screen; focusing = false; zoomTo = -1f;
|
||||
panPlaneY = GroundHeightUnder(screen);
|
||||
}
|
||||
public void EndPan() { panning = false; }
|
||||
|
||||
// Палец ещё не сдвинулся настолько, чтобы считать это перетаскиванием: камера стоит, но точка запоминается --
|
||||
@@ -95,14 +160,25 @@ public class City3DCamera : MonoBehaviour
|
||||
Apply(); // сразу: иначе следующий шаг считался бы от старого положения камеры -- камера дёргалась (05.11)
|
||||
}
|
||||
|
||||
// Кнопки "+ / −" и колесо: плавно (06.11). delta > 0 -- ближе
|
||||
public void Zoom(float delta)
|
||||
{
|
||||
focusing = false;
|
||||
distance = Mathf.Clamp(distance * (1f - delta), minDistance, maxDistance);
|
||||
float from = zoomTo > 0 ? zoomTo : distance;
|
||||
zoomTo = Mathf.Clamp(from * (1f - delta), minDistance, MaxDistanceNow);
|
||||
}
|
||||
|
||||
float fitPitch, fitFov, fitAspect; // при каких наклоне, поле зрения и форме экрана считали fitOpenDistance
|
||||
|
||||
void Update()
|
||||
{
|
||||
// Наклон или поле зрения поменяли в DEV, повернули экран -- пересчитать, откуда видно всё поселение
|
||||
if (openArea.width > 0 && cam != null && (fitPitch != pitch || fitFov != cam.fieldOfView || fitAspect != cam.aspect))
|
||||
{
|
||||
fitPitch = pitch; fitFov = cam.fieldOfView; fitAspect = cam.aspect;
|
||||
fitOpenDistance = FitDistance(openArea);
|
||||
}
|
||||
|
||||
// Колесо мыши (редактор, ПК)
|
||||
float wheel = Input.mouseScrollDelta.y;
|
||||
if (City3DInput.OverHud(Input.mousePosition)) wheel = 0f; // колесо над окнами HUD -- не приближать камеру (05.11)
|
||||
@@ -118,10 +194,18 @@ public class City3DCamera : MonoBehaviour
|
||||
if (previous > 0.01f && current > 0.01f)
|
||||
{
|
||||
focusing = false;
|
||||
distance = Mathf.Clamp(distance * previous / current, minDistance, maxDistance);
|
||||
zoomTo = -1f;
|
||||
distance = Mathf.Clamp(distance * previous / current, minDistance, MaxDistanceNow);
|
||||
}
|
||||
}
|
||||
|
||||
if (zoomTo > 0)
|
||||
{
|
||||
distance = Mathf.Lerp(distance, zoomTo, 1f - Mathf.Exp(-Time.unscaledDeltaTime * 12f));
|
||||
if (Mathf.Abs(distance - zoomTo) < 0.02f) { distance = zoomTo; zoomTo = -1f; }
|
||||
}
|
||||
distance = Mathf.Clamp(distance, minDistance, MaxDistanceNow);
|
||||
|
||||
if (focusing)
|
||||
{
|
||||
target = Vector3.Lerp(target, focusTarget, Time.unscaledDeltaTime * 2.5f);
|
||||
@@ -133,8 +217,12 @@ public class City3DCamera : MonoBehaviour
|
||||
|
||||
void Clamp()
|
||||
{
|
||||
target.x = Mathf.Clamp(target.x, bounds.xMin, bounds.xMax);
|
||||
target.z = Mathf.Clamp(target.z, bounds.yMin, bounds.yMax);
|
||||
Rect r = bounds;
|
||||
if (openArea.width > 0 && openArea.height > 0) // центр кадра -- только над открытой частью поселения
|
||||
r = Rect.MinMaxRect(Mathf.Max(r.xMin, openArea.xMin), Mathf.Max(r.yMin, openArea.yMin),
|
||||
Mathf.Min(r.xMax, openArea.xMax), Mathf.Min(r.yMax, openArea.yMax));
|
||||
target.x = Mathf.Clamp(target.x, r.xMin, r.xMax);
|
||||
target.z = Mathf.Clamp(target.z, r.yMin, r.yMax);
|
||||
target.y = 0;
|
||||
}
|
||||
|
||||
@@ -144,15 +232,8 @@ public class City3DCamera : MonoBehaviour
|
||||
|
||||
public void Apply()
|
||||
{
|
||||
Quaternion rot = Quaternion.Euler(pitch, yaw, 0);
|
||||
transform.rotation = rot;
|
||||
transform.position = target - rot * Vector3.forward * distance;
|
||||
// Камера сцены может быть ортографической (вид 2.5D без перспективы): у неё расстояние не меняет размер города
|
||||
// на экране -- «+ / −» и щипок ничего не делали (05.11). Тогда приближение -- через размер кадра: столько же
|
||||
// земли, сколько видно перспективной камерой с этого расстояния
|
||||
if (cam == null) cam = GetComponent<Camera>();
|
||||
if (cam != null && cam.orthographic)
|
||||
cam.orthographicSize = distance * Mathf.Tan(cam.fieldOfView * 0.5f * Mathf.Deg2Rad);
|
||||
PlaceAt(target, distance);
|
||||
if (RenderSettings.fog && RenderSettings.fogMode == FogMode.Linear)
|
||||
{
|
||||
RenderSettings.fogStartDistance = distance + fogStartBehind;
|
||||
@@ -160,11 +241,36 @@ public class City3DCamera : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
// Точка на плоскости земли (y = 0) под экранной точкой
|
||||
// Поставить камеру: смотрит на точку center с расстояния d.
|
||||
// Камера сцены может быть ортографической (вид 2.5D без перспективы): у неё расстояние не меняет размер города
|
||||
// на экране -- «+ / −» и щипок ничего не делали (05.11). Тогда приближение -- через размер кадра: столько же
|
||||
// земли, сколько видно перспективной камерой с этого расстояния
|
||||
void PlaceAt(Vector3 center, float d)
|
||||
{
|
||||
Quaternion rot = Quaternion.Euler(pitch, yaw, 0);
|
||||
transform.rotation = rot;
|
||||
transform.position = center - rot * Vector3.forward * d;
|
||||
if (cam != null && cam.orthographic)
|
||||
cam.orthographicSize = d * Mathf.Tan(cam.fieldOfView * 0.5f * Mathf.Deg2Rad);
|
||||
}
|
||||
|
||||
// Точка под пальцем на горизонтальной плоскости на высоте земли, где палец коснулся (06.11): на холме карта
|
||||
// идёт за пальцем точно, а не чуть быстрее или медленнее, как было с плоскостью y = 0
|
||||
float panPlaneY;
|
||||
|
||||
Vector3 GroundPoint(Vector3 screen)
|
||||
{
|
||||
Ray ray = cam.ScreenPointToRay(screen);
|
||||
var plane = new Plane(Vector3.up, Vector3.zero);
|
||||
var plane = new Plane(Vector3.up, new Vector3(0, panPlaneY, 0));
|
||||
return plane.Raycast(ray, out float t) ? ray.GetPoint(t) : target;
|
||||
}
|
||||
|
||||
// Высота земли (объект "Ground" 3D-поселения) под экранной точкой; не попали -- 0
|
||||
float GroundHeightUnder(Vector3 screen)
|
||||
{
|
||||
if (cam == null) return 0f;
|
||||
foreach (var hit in Physics.RaycastAll(cam.ScreenPointToRay(screen), 500f))
|
||||
if (hit.collider.gameObject.name == "Ground") return hit.point.y;
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,6 +199,7 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
|
||||
if (d == null) MoveFrom2D(); // 3D-города в сохранении ещё нет, а 2D есть -- перенести постройки игрока
|
||||
}
|
||||
else if (pendingFrom2D.Count > 0) PlacePendingFrom2D(); // здания из 2D, которые ждали места
|
||||
UpdateCameraArea();
|
||||
UpdateGrass();
|
||||
RefreshGround();
|
||||
if (CitySync.Instance != null) CitySync.Instance.SyncNow();
|
||||
@@ -349,6 +350,7 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
|
||||
cameraRig = cam.GetComponent<City3DCamera>() ?? cam.gameObject.AddComponent<City3DCamera>();
|
||||
cameraRig.bounds = new Rect(2, 2, City3DLayout.Size - 4, City3DLayout.Size - 4);
|
||||
cameraRig.target = new Vector3(City3DLayout.Size / 2f, 0, City3DLayout.Size / 2f);
|
||||
UpdateCameraArea();
|
||||
cameraRig.Apply();
|
||||
|
||||
Map3D.SetUpLook(cam, 30f, 85f); // солнце, небо, дымка вдали
|
||||
@@ -696,8 +698,7 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
|
||||
{
|
||||
ghostX = fx; ghostY = fy; ghostRot = frot;
|
||||
// камера плавно подъезжает к месту, не меняя приближения
|
||||
float size = cameraRig.distance / 1.15f;
|
||||
cameraRig.FocusOn(new Rect(fx + t.W / 2f - size / 2f, fy + t.H / 2f - size / 2f, size, size));
|
||||
cameraRig.MoveTo(new Vector3(fx + t.W / 2f, 0, fy + t.H / 2f));
|
||||
}
|
||||
EnsureFootprint();
|
||||
RefreshGround();
|
||||
@@ -830,7 +831,9 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
|
||||
Vector3 pos;
|
||||
if (Input.touchCount > 0)
|
||||
{
|
||||
if (Input.touchCount > 1) { cameraRig.EndPan(); draggingGhost = false; return; } // два пальца -- зум камеры
|
||||
// два пальца -- зум камеры. Касание считается перетаскиванием: когда пальцы отпустят, это не тап
|
||||
// (иначе после щипка открывалось окно здания под оставшимся пальцем) (06.11)
|
||||
if (Input.touchCount > 1) { cameraRig.EndPan(); draggingGhost = false; pointerMoved = true; return; }
|
||||
Touch touch = Input.GetTouch(0);
|
||||
pos = touch.position;
|
||||
down = touch.phase == TouchPhase.Began;
|
||||
@@ -974,6 +977,7 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
|
||||
int before = townHallLevel;
|
||||
townHallLevel = Mathf.Clamp(level, 1, 5);
|
||||
if (townHallLevel == before) return;
|
||||
UpdateCameraArea();
|
||||
RefreshGround();
|
||||
if (ghost != null) UpdateGhost();
|
||||
if (townHallLevel > before)
|
||||
@@ -986,6 +990,14 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
|
||||
}
|
||||
}
|
||||
|
||||
// Камера: центр кадра -- только над открытой зоной, при самом дальнем отдалении видна вся зона (06.11)
|
||||
void UpdateCameraArea()
|
||||
{
|
||||
if (cameraRig == null) return;
|
||||
int half = City3DLayout.ZoneHalfSize(townHallLevel), c = City3DLayout.Size / 2;
|
||||
cameraRig.SetOpenArea(new Rect(c - half, c - half, half * 2, half * 2));
|
||||
}
|
||||
|
||||
void Say(string text)
|
||||
{
|
||||
message = text;
|
||||
|
||||
Reference in New Issue
Block a user