3D: версия на экране, отладочная сетка, сборка APK одной кнопкой и починка сборки Android
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Android;
|
||||
using UnityEngine;
|
||||
|
||||
// Починка сборки Android на этом компьютере (05.11):
|
||||
// 1. Имя пользователя Windows с русскими буквами (C:\Users\Вальдемар). Gradle хранит свои файлы в
|
||||
// C:\Users\<имя>\.gradle, и одна из его программ (prefab) не понимает такой путь -- сборка падает.
|
||||
// Перед сборкой переносим папку Gradle в C:\gradle (переменная GRADLE_USER_HOME для процесса Unity,
|
||||
// сборщик её наследует). Ничего в Windows менять не нужно.
|
||||
// 2. Два Kotlin в зависимостях (1.8 у Unity и 1.6 у одного из пакетов) -- "Duplicate class kotlin...".
|
||||
// В сгенерированный проект Gradle дописываем правило: все kotlin-stdlib -- одной версии 1.8.22.
|
||||
[InitializeOnLoad]
|
||||
public class AndroidBuildFixes : IPostGenerateGradleAndroidProject
|
||||
{
|
||||
const string GradleHome = @"C:\gradle";
|
||||
const string Marker = "// Bogatyri: kotlin-stdlib одной версии";
|
||||
|
||||
static AndroidBuildFixes() { EnsureAsciiGradleHome(); }
|
||||
|
||||
public static void EnsureAsciiGradleHome()
|
||||
{
|
||||
string current = Environment.GetEnvironmentVariable("GRADLE_USER_HOME");
|
||||
if (!string.IsNullOrEmpty(current) && IsAscii(current)) return;
|
||||
if (IsAscii(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile))) return; // путь и так латиницей
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(GradleHome);
|
||||
Environment.SetEnvironmentVariable("GRADLE_USER_HOME", GradleHome, EnvironmentVariableTarget.Process);
|
||||
Debug.Log("[СБОРКА] Папка Gradle: " + GradleHome + " (в пути пользователя есть русские буквы)");
|
||||
}
|
||||
catch (Exception e) { Debug.LogWarning("[СБОРКА] Не удалось задать GRADLE_USER_HOME: " + e.Message); }
|
||||
}
|
||||
|
||||
static bool IsAscii(string s) => !Regex.IsMatch(s ?? "", @"[^\u0000-\u007F]");
|
||||
|
||||
public int callbackOrder => 999;
|
||||
|
||||
// path -- папка unityLibrary сгенерированного проекта
|
||||
public void OnPostGenerateGradleAndroidProject(string path)
|
||||
{
|
||||
string root = Directory.GetParent(path).FullName;
|
||||
foreach (var file in new[] { Path.Combine(root, "launcher", "build.gradle"), Path.Combine(path, "build.gradle") })
|
||||
{
|
||||
if (!File.Exists(file)) continue;
|
||||
string text = File.ReadAllText(file);
|
||||
if (text.Contains(Marker)) continue;
|
||||
text += "\n" + Marker + "\n" +
|
||||
"configurations.all {\n" +
|
||||
" resolutionStrategy.eachDependency { details ->\n" +
|
||||
" if (details.requested.group == 'org.jetbrains.kotlin' && details.requested.name.startsWith('kotlin-stdlib')) {\n" +
|
||||
" details.useVersion('1.8.22')\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"}\n";
|
||||
File.WriteAllText(file, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build.Reporting;
|
||||
|
||||
// Сборка APK одной кнопкой (05.11): Tools -> Bogatyri -> Собрать APK.
|
||||
// Файл: C:\MVP1\Builds\Bogatyri_<ветка>_v<версия>_<дата>.apk; сцены -- из списка сборки (первая -- стартовая).
|
||||
// Итог сборки (успех, размер, первые ошибки) пишется в Builds\last_build.txt.
|
||||
// Сборку можно запустить и файлом-запросом Builds\build_request.txt (так её запускает Claude): Unity заметит
|
||||
// его в течение пары секунд, удалит и начнёт сборку.
|
||||
[InitializeOnLoad]
|
||||
public static class ApkBuilder
|
||||
{
|
||||
const string Folder = "Builds";
|
||||
const string RequestFile = "Builds/build_request.txt";
|
||||
const string ResultFile = "Builds/last_build.txt";
|
||||
static double nextCheck;
|
||||
|
||||
static ApkBuilder()
|
||||
{
|
||||
EditorApplication.update += Poll;
|
||||
}
|
||||
|
||||
static void Poll()
|
||||
{
|
||||
if (EditorApplication.timeSinceStartup < nextCheck) return;
|
||||
nextCheck = EditorApplication.timeSinceStartup + 2;
|
||||
if (!File.Exists(RequestFile) || BuildPipeline.isBuildingPlayer || EditorApplication.isCompiling || EditorApplication.isPlaying) return;
|
||||
File.Delete(RequestFile);
|
||||
EditorApplication.delayCall += Build;
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Bogatyri/Собрать APK")]
|
||||
public static void Build()
|
||||
{
|
||||
AndroidBuildFixes.EnsureAsciiGradleHome();
|
||||
Directory.CreateDirectory(Folder);
|
||||
string branch = Git("rev-parse --abbrev-ref HEAD").Replace('/', '-');
|
||||
if (branch.Length == 0) branch = "local";
|
||||
string file = $"{Folder}/Bogatyri_{branch}_v{PlayerSettings.bundleVersion}_{DateTime.Now:yyyyMMdd_HHmm}.apk";
|
||||
var scenes = EditorBuildSettings.scenes.Where(s => s.enabled).Select(s => s.path).ToArray();
|
||||
|
||||
File.WriteAllText(ResultFile, $"Сборка идёт: {DateTime.Now:dd.MM.yyyy HH:mm}\n{file}\n");
|
||||
BuildReport report;
|
||||
try
|
||||
{
|
||||
EditorUserBuildSettings.buildAppBundle = false; // обычный APK
|
||||
report = BuildPipeline.BuildPlayer(new BuildPlayerOptions
|
||||
{
|
||||
scenes = scenes,
|
||||
locationPathName = file,
|
||||
target = BuildTarget.Android,
|
||||
options = BuildOptions.None
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
File.WriteAllText(ResultFile, $"ОШИБКА {DateTime.Now:dd.MM.yyyy HH:mm}\n{e}\n");
|
||||
throw;
|
||||
}
|
||||
|
||||
var s = report.summary;
|
||||
var text = new StringBuilder();
|
||||
text.AppendLine($"{(s.result == BuildResult.Succeeded ? "ГОТОВО" : "ОШИБКА")} {DateTime.Now:dd.MM.yyyy HH:mm} результат: {s.result}");
|
||||
text.AppendLine($"Файл: {Path.GetFullPath(file)}");
|
||||
if (s.result == BuildResult.Succeeded && File.Exists(file)) text.AppendLine($"Размер: {new FileInfo(file).Length / 1048576.0:0.0} МБ");
|
||||
text.AppendLine($"Версия: {PlayerSettings.bundleVersion}, код {PlayerSettings.Android.bundleVersionCode}, ветка {branch} {Git("rev-parse --short HEAD")}");
|
||||
text.AppendLine($"Сцены: {string.Join(", ", scenes)}");
|
||||
text.AppendLine($"Время: {s.totalTime}, ошибок: {s.totalErrors}, предупреждений: {s.totalWarnings}");
|
||||
int shown = 0;
|
||||
foreach (var step in report.steps)
|
||||
foreach (var m in step.messages)
|
||||
if (m.type == UnityEngine.LogType.Error || m.type == UnityEngine.LogType.Exception)
|
||||
{
|
||||
if (shown++ >= 15) break;
|
||||
string msg = m.content.Length > 1500 ? m.content.Substring(0, 1500) + " ..." : m.content;
|
||||
text.AppendLine("--- " + msg);
|
||||
}
|
||||
File.WriteAllText(ResultFile, text.ToString());
|
||||
UnityEngine.Debug.Log("[СБОРКА] " + text.ToString().Split('\n')[0] + " -> " + file);
|
||||
if (s.result == BuildResult.Succeeded) EditorUtility.RevealInFinder(file);
|
||||
}
|
||||
|
||||
static string Git(string args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var p = Process.Start(new ProcessStartInfo("git", args)
|
||||
{
|
||||
RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true,
|
||||
WorkingDirectory = Directory.GetCurrentDirectory()
|
||||
});
|
||||
string o = p.StandardOutput.ReadToEnd().Trim();
|
||||
p.WaitForExit(5000);
|
||||
return o;
|
||||
}
|
||||
catch { return ""; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.Build.Reporting;
|
||||
|
||||
// Перед каждым билдом записывает в Assets/Resources/BuildInfo.txt: ветку, коммит и дату сборки (05.11).
|
||||
// Игра показывает это в углу экрана рядом с версией (App/GameVersionLabel). После билда файл удаляется,
|
||||
// чтобы не попадать в git.
|
||||
public class BuildInfoWriter : IPreprocessBuildWithReport, IPostprocessBuildWithReport
|
||||
{
|
||||
const string Path = "Assets/Resources/BuildInfo.txt";
|
||||
public int callbackOrder => 0;
|
||||
|
||||
public void OnPreprocessBuild(BuildReport report)
|
||||
{
|
||||
string branch = Or(Git("rev-parse --abbrev-ref HEAD")), commit = Or(Git("rev-parse --short HEAD"));
|
||||
bool dirty = Git("status --porcelain --untracked-files=no").Length > 0;
|
||||
string info = $"{branch} {commit}{(dirty ? "+" : "")} • {DateTime.Now:dd.MM.yyyy HH:mm}";
|
||||
Directory.CreateDirectory("Assets/Resources");
|
||||
File.WriteAllText(Path, info);
|
||||
AssetDatabase.ImportAsset(Path);
|
||||
UnityEngine.Debug.Log("[ВЕРСИЯ] Сборка: v" + PlayerSettings.bundleVersion + " • " + info);
|
||||
}
|
||||
|
||||
public void OnPostprocessBuild(BuildReport report)
|
||||
{
|
||||
AssetDatabase.DeleteAsset(Path);
|
||||
}
|
||||
|
||||
static string Git(string args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var p = Process.Start(new ProcessStartInfo("git", args)
|
||||
{
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WorkingDirectory = Directory.GetCurrentDirectory()
|
||||
});
|
||||
string output = p.StandardOutput.ReadToEnd().Trim();
|
||||
p.WaitForExit(5000);
|
||||
return output;
|
||||
}
|
||||
catch { return ""; }
|
||||
}
|
||||
|
||||
static string Or(string s) => string.IsNullOrEmpty(s) ? "?" : s;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1218c67fef1faaf448c6623df897ac12
|
||||
@@ -17,6 +17,7 @@ public class DebugPanel : MonoBehaviour
|
||||
|
||||
GameObject panel;
|
||||
TextMeshProUGUI info;
|
||||
UnityEngine.UI.Button gridButton;
|
||||
float nextRefresh;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
||||
@@ -81,11 +82,25 @@ public class DebugPanel : MonoBehaviour
|
||||
|
||||
var row4 = UIKit.Row(card, 70, 10);
|
||||
Btn(row4, "Рабочие", UIKit.Theme.Blue, () => BuildingWindow.Open(null));
|
||||
gridButton = UIKit.Button(row4, GridLabel, UIKit.Theme.Blue, ToggleGrid, -1, 70, 24);
|
||||
UIKit.Size(gridButton, -1, 70, 1);
|
||||
Btn(row4, "Закрыть", UIKit.Theme.Gray, () => panel.SetActive(false));
|
||||
|
||||
panel.SetActive(false);
|
||||
}
|
||||
|
||||
// Отладочная сетка стройки (3D-поселение и 2D-город): видна всегда, ярче, каждая 5-я линия жёлтая (05.11, просьба Миши)
|
||||
void ToggleGrid()
|
||||
{
|
||||
bool on = !(City3DController.DebugGrid || BuildGrid.DebugVisible);
|
||||
City3DController.SetDebugGrid(on);
|
||||
bool building = BuildModeController.Instance != null && BuildModeController.Instance.IsActive;
|
||||
BuildGrid.SetDebugVisible(on, building);
|
||||
if (gridButton != null) UIKit.SetButton(gridButton, GridLabel, true);
|
||||
}
|
||||
|
||||
static string GridLabel => City3DController.DebugGrid || BuildGrid.DebugVisible ? "Сетка: вкл" : "Сетка: выкл";
|
||||
|
||||
static void Btn(Transform row, string label, Color color, System.Action onClick)
|
||||
{
|
||||
var b = UIKit.Button(row, label, color, onClick, -1, 70, 24);
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
using UnityEngine;
|
||||
|
||||
// Версия игры на экране (05.11, требование Миши: в каждом билде видно, что это за версия).
|
||||
// В углу экрана: версия из Player Settings (Application.version) и сборка -- ветка, коммит, дата.
|
||||
// Сборку пишет Editor/BuildInfoWriter перед билдом в Resources/BuildInfo.txt; в редакторе -- "редактор".
|
||||
// Создаётся сам при запуске, живёт между сценами.
|
||||
public class GameVersionLabel : MonoBehaviour
|
||||
{
|
||||
static GameVersionLabel instance;
|
||||
string text;
|
||||
GUIStyle style, shadow;
|
||||
|
||||
public static string BuildInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
var asset = Resources.Load<TextAsset>("BuildInfo");
|
||||
return asset != null ? asset.text.Trim() : "редактор";
|
||||
}
|
||||
}
|
||||
|
||||
public static string FullVersion => $"v{Application.version} • {BuildInfo}";
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
||||
static void Create()
|
||||
{
|
||||
if (instance != null) return;
|
||||
var go = new GameObject("GameVersionLabel");
|
||||
DontDestroyOnLoad(go);
|
||||
instance = go.AddComponent<GameVersionLabel>();
|
||||
instance.text = FullVersion;
|
||||
Debug.Log("[ВЕРСИЯ] " + instance.text);
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
if (style == null)
|
||||
{
|
||||
int size = Mathf.Max(12, Screen.height / 60);
|
||||
style = new GUIStyle(GUI.skin.label) { fontSize = size, alignment = TextAnchor.LowerLeft };
|
||||
style.normal.textColor = new Color(1f, 1f, 1f, 0.75f);
|
||||
shadow = new GUIStyle(style);
|
||||
shadow.normal.textColor = new Color(0f, 0f, 0f, 0.6f);
|
||||
}
|
||||
GUI.depth = -1000; // поверх всего
|
||||
var r = new Rect(8, Screen.height - style.fontSize * 2 - 4, Screen.width * 0.6f, style.fontSize * 2);
|
||||
GUI.Label(new Rect(r.x + 1, r.y + 1, r.width, r.height), text, shadow);
|
||||
GUI.Label(r, text, style);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4dae86c365fe55948b878547633d712a
|
||||
@@ -99,6 +99,17 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
|
||||
if (UIPanelManager.Instance != null) UIPanelManager.Instance.CloseAll();
|
||||
}
|
||||
|
||||
// Отладочная сетка (05.11, просьба Миши): видна всегда, ярче, каждая 5-я линия жёлтая -- примерять референсы.
|
||||
// Включается кнопкой "Сетка" в панели DEV.
|
||||
public static bool DebugGrid { get; private set; }
|
||||
|
||||
public static void SetDebugGrid(bool on)
|
||||
{
|
||||
DebugGrid = on;
|
||||
var city = FindAnyObjectByType<City3DController>();
|
||||
if (city != null && city.map != null) city.RefreshGround();
|
||||
}
|
||||
|
||||
bool stateApplied; // сохранённый город уже поставлен (в первом кадре, после загрузки сохранения в Start)
|
||||
|
||||
void OnEnable()
|
||||
@@ -324,7 +335,7 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
|
||||
void RefreshGround()
|
||||
{
|
||||
int n = City3DLayout.Size, px = PixelsPerCell;
|
||||
bool grid = mode != Mode.View;
|
||||
bool grid = mode != Mode.View || DebugGrid;
|
||||
var pixels = new Color32[n * px * n * px];
|
||||
for (int y = 0; y < n; y++)
|
||||
for (int x = 0; x < n; x++)
|
||||
@@ -349,7 +360,8 @@ public class City3DController : MonoBehaviour, City3DSave.ILive
|
||||
for (int ix = 0; ix < px; ix++)
|
||||
{
|
||||
Color p = c;
|
||||
if (grid && (ix == 0 || iy == 0)) p *= 0.85f; // линии клеток
|
||||
if (grid && (ix == 0 || iy == 0)) p *= DebugGrid ? 0.55f : 0.85f; // линии клеток
|
||||
if (DebugGrid && ((ix == 0 && x % 5 == 0) || (iy == 0 && y % 5 == 0))) p = new Color(1f, 0.85f, 0.2f); // каждая 5-я
|
||||
// граница открытой зоны -- светлая линия
|
||||
if (grid && open && (ix == 0 && !map.IsOpen(x - 1, y, townHallLevel) || ix == px - 1 && !map.IsOpen(x + 1, y, townHallLevel)
|
||||
|| iy == 0 && !map.IsOpen(x, y - 1, townHallLevel) || iy == px - 1 && !map.IsOpen(x, y + 1, townHallLevel)))
|
||||
|
||||
@@ -35,6 +35,20 @@ public class BuildGrid : MonoBehaviour
|
||||
[Tooltip("Посчитать область самому: старые участки + Ратуша + запас")]
|
||||
public bool autoArea = true;
|
||||
|
||||
// Отладка (05.11, просьба Миши): сетка видна всегда и ярче, каждая 5-я линия жирнее -- чтобы примерять
|
||||
// к ней размеры зданий и референсы. Включается кнопкой "Сетка" в панели DEV.
|
||||
public static bool DebugVisible { get; private set; }
|
||||
|
||||
public static void SetDebugVisible(bool on, bool buildModeActive)
|
||||
{
|
||||
DebugVisible = on;
|
||||
if (BuildManager.Instance == null) return; // 2D-город не открыт -- сетка появится, когда откроется
|
||||
var grid = Instance;
|
||||
if (grid == null) return;
|
||||
grid.lastOverlayKey = null;
|
||||
grid.ShowOverlay(on || buildModeActive);
|
||||
}
|
||||
|
||||
// Здание, которое сейчас переносят: его клетки считаются свободными (режим стройки, перенос)
|
||||
public static GameObject IgnoredBuilding;
|
||||
[Tooltip("Запас вокруг старых участков, клеток")]
|
||||
@@ -258,6 +272,7 @@ public class BuildGrid : MonoBehaviour
|
||||
public void ShowOverlay(bool show)
|
||||
{
|
||||
EnsureInit();
|
||||
show |= DebugVisible; // отладочная сетка не прячется при выходе из режима стройки
|
||||
if (show && overlay == null) CreateOverlay();
|
||||
if (overlay == null) return;
|
||||
overlay.enabled = show;
|
||||
@@ -283,7 +298,8 @@ public class BuildGrid : MonoBehaviour
|
||||
void RedrawOverlay()
|
||||
{
|
||||
// перерисовываем только если занятость поменялась
|
||||
var key = new System.Text.StringBuilder(Width * Height);
|
||||
var key = new System.Text.StringBuilder(Width * Height + 1);
|
||||
key.Append(DebugVisible ? 'D' : 'n');
|
||||
for (int y = 0; y < Height; y++) for (int x = 0; x < Width; x++) key.Append(busy[x, y] ? '1' : '0');
|
||||
string k = key.ToString();
|
||||
if (k == lastOverlayKey) return;
|
||||
@@ -291,7 +307,8 @@ public class BuildGrid : MonoBehaviour
|
||||
|
||||
int w = overlayTex.width, h = overlayTex.height;
|
||||
var px = new Color32[w * h];
|
||||
Color32 line = lineColor, free = freeColor, bad = busyColor;
|
||||
Color32 line = DebugVisible ? new Color(1f, 1f, 1f, 0.7f) : lineColor, free = freeColor, bad = busyColor;
|
||||
Color32 major = new Color32(255, 230, 80, 230); // каждая 5-я клетка -- жёлтая линия (только в отладке)
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
int cy = y / PixelsPerCell, iy = y % PixelsPerCell;
|
||||
@@ -300,6 +317,7 @@ public class BuildGrid : MonoBehaviour
|
||||
int cx = x / PixelsPerCell, ix = x % PixelsPerCell;
|
||||
bool edge = ix == 0 || iy == 0 || x == w - 1 || y == h - 1;
|
||||
px[y * w + x] = busy[cx, cy] ? bad : (edge ? line : free);
|
||||
if (DebugVisible && ((ix == 0 && cx % 5 == 0) || (iy == 0 && cy % 5 == 0))) px[y * w + x] = major;
|
||||
if (busy[cx, cy] && edge) px[y * w + x] = new Color32(bad.r, bad.g, bad.b, (byte)Mathf.Min(255, bad.a + 50));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user