M0 回合制战斗壳层:接口与单局状态、调试入口、暂停联动
This commit is contained in:
@@ -1 +1 @@
|
||||
␍ATKFormatText ATK {0}␍MATFormatText MAT {0}␍SPDFormatText SPD {0}HPFormatTextHP {0}MPFormatTextMP {0}␍STAFormatText STA {0}WarehouseCapacity{0} ITEMS / {1} TOTAL UNITS
|
||||
␍ATKFormatText ATK {0}␍MATFormatText MAT {0}␍SPDFormatText SPD {0}HPFormatTextHP {0}MPFormatTextMP {0}␍STAFormatText STA {0}WarehouseCapacity{0} ITEMS / {1} TOTAL UNITS␍BattleBarText {0} / {1}
|
||||
@@ -26,5 +26,9 @@
|
||||
{
|
||||
"name": "WarehouseCapacity",
|
||||
"format": "{0} ITEMS / {1} TOTAL UNITS"
|
||||
},
|
||||
{
|
||||
"name": "BattleBarText",
|
||||
"format": "{0} / {1}"
|
||||
}
|
||||
]
|
||||
Binary file not shown.
@@ -26,5 +26,12 @@
|
||||
"UIGroupName": "Default",
|
||||
"allowMultiInstance": false,
|
||||
"pauseCoveredUIForm": true
|
||||
},
|
||||
{
|
||||
"id": 103,
|
||||
"assetName": "BattleForm",
|
||||
"UIGroupName": "Default",
|
||||
"allowMultiInstance": false,
|
||||
"pauseCoveredUIForm": true
|
||||
}
|
||||
]
|
||||
@@ -36,6 +36,10 @@ namespace SepCore.Definition
|
||||
/// 关于
|
||||
/// </summary>
|
||||
AboutForm = 102,
|
||||
/// <summary>
|
||||
/// 战斗界面
|
||||
/// </summary>
|
||||
BattleForm = 103,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,592 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using SepCore.UI;
|
||||
using TMPro;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace SepCore.Editor
|
||||
{
|
||||
public static class BattleUIPrefabBuilder
|
||||
{
|
||||
private const string PrefabPath = "Assets/GameMain/UI/UIForms/BattleForm.prefab";
|
||||
private const string CharacterAtlasPath = "Assets/GameMain/Textures/Characters.png";
|
||||
private const string EnemyAtlasPath = "Assets/GameMain/Textures/Monsters.png";
|
||||
private const string ItemAtlasPath = "Assets/GameMain/Textures/Items.png";
|
||||
private const string PreviewCameraName = "__BattlePreviewCamera";
|
||||
private const string PreviewRootName = "__BattlePreview";
|
||||
private const string PreviousScenePathKey = "BattleUI.PreviousScenePath";
|
||||
|
||||
private static readonly Color Overlay = new Color32(8, 7, 12, 184);
|
||||
private static readonly Color Charcoal = new Color32(25, 24, 30, 255);
|
||||
private static readonly Color CharcoalSoft = new Color32(42, 39, 47, 255);
|
||||
private static readonly Color Ivory = new Color32(244, 237, 214, 255);
|
||||
private static readonly Color Ink = new Color32(43, 37, 35, 255);
|
||||
private static readonly Color White = new Color32(248, 248, 246, 255);
|
||||
private static readonly Color MutedWhite = new Color32(192, 190, 184, 255);
|
||||
private static readonly Color Green = new Color32(62, 164, 94, 255);
|
||||
private static readonly Color Blue = new Color32(62, 119, 190, 255);
|
||||
private static readonly Color Red = new Color32(191, 69, 65, 255);
|
||||
private static readonly Color Gold = new Color32(218, 169, 64, 255);
|
||||
private static readonly Color EnemyFrame = new Color32(91, 39, 54, 235);
|
||||
|
||||
private static TMP_FontAsset font;
|
||||
private static Sprite panelBeige;
|
||||
private static Sprite panelBrown;
|
||||
private static Sprite panelBlue;
|
||||
private static Sprite buttonBlue;
|
||||
private static Sprite buttonBluePressed;
|
||||
private static Sprite buttonBrown;
|
||||
private static Sprite buttonBrownPressed;
|
||||
private static Sprite buttonBeige;
|
||||
private static Sprite buttonBeigePressed;
|
||||
private static Sprite buttonGrey;
|
||||
private static Sprite buttonGreyPressed;
|
||||
private static Sprite escapeIcon;
|
||||
private static Sprite[] playerIcons;
|
||||
private static Sprite[] enemyIcons;
|
||||
private static Sprite attackIcon;
|
||||
private static Sprite skillIcon;
|
||||
private static Sprite itemIcon;
|
||||
|
||||
[MenuItem("Utility/UI/Build Battle UI")]
|
||||
public static void Build()
|
||||
{
|
||||
LoadAssets();
|
||||
|
||||
Scene previousScene = SceneManager.GetActiveScene();
|
||||
Scene temporaryScene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Additive);
|
||||
GameObject root = null;
|
||||
|
||||
try
|
||||
{
|
||||
SceneManager.SetActiveScene(temporaryScene);
|
||||
root = BuildRoot();
|
||||
UISerializationRoot serializationRoot = root.GetComponent<UISerializationRoot>();
|
||||
serializationRoot.RefreshItems();
|
||||
UIAssetsTools.ApplyGeneratedComponentsToRoot(serializationRoot);
|
||||
|
||||
bool success;
|
||||
PrefabUtility.SaveAsPrefabAsset(root, PrefabPath, out success);
|
||||
if (!success)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to save BattleForm prefab.");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (root != null)
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(root);
|
||||
}
|
||||
|
||||
if (previousScene.IsValid())
|
||||
{
|
||||
SceneManager.SetActiveScene(previousScene);
|
||||
}
|
||||
|
||||
EditorSceneManager.CloseScene(temporaryScene, true);
|
||||
}
|
||||
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
Selection.activeObject = AssetDatabase.LoadAssetAtPath<GameObject>(PrefabPath);
|
||||
Debug.Log("Battle UI prefab rebuilt: " + PrefabPath);
|
||||
}
|
||||
|
||||
[MenuItem("Utility/UI/Preview Battle UI")]
|
||||
public static void OpenPreview()
|
||||
{
|
||||
ClosePreviewInternal();
|
||||
|
||||
Scene previousScene = SceneManager.GetActiveScene();
|
||||
SessionState.SetString(PreviousScenePathKey, previousScene.path);
|
||||
Scene previewScene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Additive);
|
||||
SceneManager.SetActiveScene(previewScene);
|
||||
|
||||
GameObject cameraObject = new GameObject(PreviewCameraName, typeof(Camera));
|
||||
Camera previewCamera = cameraObject.GetComponent<Camera>();
|
||||
previewCamera.clearFlags = CameraClearFlags.SolidColor;
|
||||
previewCamera.backgroundColor = new Color(0.22f, 0.25f, 0.23f, 1f);
|
||||
previewCamera.orthographic = true;
|
||||
previewCamera.orthographicSize = 5f;
|
||||
cameraObject.transform.position = new Vector3(0f, 0f, -10f);
|
||||
|
||||
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PrefabPath);
|
||||
if (prefab == null)
|
||||
{
|
||||
throw new InvalidOperationException("Build BattleForm before opening its preview.");
|
||||
}
|
||||
|
||||
GameObject previewRoot = (GameObject)PrefabUtility.InstantiatePrefab(prefab, previewScene);
|
||||
previewRoot.name = PreviewRootName;
|
||||
Canvas canvas = previewRoot.GetComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceCamera;
|
||||
canvas.worldCamera = previewCamera;
|
||||
canvas.planeDistance = 1f;
|
||||
previewRoot.GetComponent<RectTransform>().localScale = Vector3.one;
|
||||
Selection.activeGameObject = previewRoot;
|
||||
Debug.Log("Battle UI preview opened in an unsaved scene.");
|
||||
}
|
||||
|
||||
[MenuItem("Utility/UI/Close Battle UI Preview")]
|
||||
public static void ClosePreview()
|
||||
{
|
||||
ClosePreviewInternal();
|
||||
}
|
||||
|
||||
private static void ClosePreviewInternal()
|
||||
{
|
||||
Camera[] cameras = Resources.FindObjectsOfTypeAll<Camera>();
|
||||
Camera previewCamera = cameras.FirstOrDefault(candidate => candidate != null &&
|
||||
candidate.gameObject.name == PreviewCameraName && candidate.gameObject.scene.IsValid());
|
||||
if (previewCamera == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Scene previewScene = previewCamera.gameObject.scene;
|
||||
string previousPath = SessionState.GetString(PreviousScenePathKey, string.Empty);
|
||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||
{
|
||||
Scene loadedScene = SceneManager.GetSceneAt(i);
|
||||
if (loadedScene.IsValid() && loadedScene.path == previousPath)
|
||||
{
|
||||
SceneManager.SetActiveScene(loadedScene);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EditorSceneManager.CloseScene(previewScene, true);
|
||||
SessionState.EraseString(PreviousScenePathKey);
|
||||
}
|
||||
|
||||
private static GameObject BuildRoot()
|
||||
{
|
||||
GameObject root = new GameObject("BattleForm", typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler),
|
||||
typeof(GraphicRaycaster), typeof(CanvasGroup), typeof(UISerializationRoot));
|
||||
root.layer = LayerMask.NameToLayer("UI");
|
||||
|
||||
Canvas canvas = root.GetComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.pixelPerfect = true;
|
||||
canvas.overrideSorting = true;
|
||||
canvas.sortingOrder = 500;
|
||||
|
||||
CanvasScaler scaler = root.GetComponent<CanvasScaler>();
|
||||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
scaler.referenceResolution = new Vector2(1920f, 1080f);
|
||||
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
|
||||
scaler.matchWidthOrHeight = 0.5f;
|
||||
|
||||
RectTransform rootRect = root.GetComponent<RectTransform>();
|
||||
rootRect.anchorMin = Vector2.zero;
|
||||
rootRect.anchorMax = Vector2.one;
|
||||
rootRect.offsetMin = Vector2.zero;
|
||||
rootRect.offsetMax = Vector2.zero;
|
||||
|
||||
AddImage(Stretch("BattleOverlay", rootRect, 0f, 0f, 0f, 0f), Overlay, null);
|
||||
RectTransform safeFrame = Fixed("SafeFrame", rootRect, new Vector2(0.5f, 0.5f),
|
||||
new Vector2(0.5f, 0.5f), Vector2.zero, new Vector2(1920f, 1080f));
|
||||
|
||||
BuildTurnOrder(safeFrame);
|
||||
BuildPlayers(safeFrame);
|
||||
BuildEnemies(safeFrame);
|
||||
BuildActionPanel(safeFrame);
|
||||
return root;
|
||||
}
|
||||
|
||||
private static void BuildTurnOrder(RectTransform parent)
|
||||
{
|
||||
RectTransform panel = AddPanel(
|
||||
Fixed("TurnOrderPanel", parent, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f),
|
||||
new Vector2(0f, -24f), new Vector2(1016f, 112f)), panelBrown, Color.white);
|
||||
AddImage(TopStretch("ActiveAccent", panel, 0f, 0f, 0f, 6f), Gold, null);
|
||||
|
||||
TextMeshProUGUI round = AddText(
|
||||
Fixed("RoundText", panel, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f),
|
||||
new Vector2(28f, 0f), new Vector2(156f, 74f)), "ROUND 01", 25, Ivory,
|
||||
TextAlignmentOptions.MidlineLeft, FontStyles.Bold);
|
||||
RegisterReference(round, "roundText");
|
||||
|
||||
Sprite[] icons =
|
||||
{
|
||||
playerIcons[3], enemyIcons[2], playerIcons[0], enemyIcons[0],
|
||||
playerIcons[2], enemyIcons[1], playerIcons[1], enemyIcons[3]
|
||||
};
|
||||
string[] labels = { "P4", "E3", "P1", "E1", "P3", "E2", "P2", "E4" };
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
RectTransform slotRect = Fixed("TurnSlot" + (i + 1), panel, new Vector2(0f, 0.5f),
|
||||
new Vector2(0f, 0.5f), new Vector2(194f + i * 98f, 0f), new Vector2(82f, 82f));
|
||||
Image background = AddImage(slotRect, i == 0 ? Ivory : CharcoalSoft, null);
|
||||
background.raycastTarget = false;
|
||||
|
||||
RectTransform marker = Stretch("ActiveMarker", slotRect, -3f, -3f, -3f, -3f);
|
||||
Image markerImage = AddImage(marker, Color.clear, null);
|
||||
Outline markerOutline = marker.gameObject.AddComponent<Outline>();
|
||||
markerOutline.effectColor = Gold;
|
||||
markerOutline.effectDistance = new Vector2(3f, -3f);
|
||||
markerOutline.useGraphicAlpha = false;
|
||||
marker.gameObject.SetActive(i == 0);
|
||||
|
||||
Image icon = AddSprite(Fixed("Icon", slotRect, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f),
|
||||
new Vector2(0f, -5f), new Vector2(54f, 54f)), icons[i], Color.white, true);
|
||||
TextMeshProUGUI label = AddText(
|
||||
Fixed("Label", slotRect, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f),
|
||||
new Vector2(0f, 5f), new Vector2(68f, 22f)), labels[i], 15,
|
||||
i == 0 ? Ink : MutedWhite, TextAlignmentOptions.Center, FontStyles.Bold);
|
||||
|
||||
BattleTurnSlotItem slot = slotRect.gameObject.AddComponent<BattleTurnSlotItem>();
|
||||
slot.ConfigureEditor(marker.gameObject, icon, label);
|
||||
RegisterReference(slot, "turnSlot" + (i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
private static void BuildPlayers(RectTransform parent)
|
||||
{
|
||||
int[] hp = { 92, 150, 58, 100 };
|
||||
int[] maxHp = { 120, 150, 90, 100 };
|
||||
int[] mp = { 24, 10, 72, 50 };
|
||||
int[] maxMp = { 40, 30, 80, 50 };
|
||||
|
||||
CreatePlayerCard(parent, 0, new Vector2(0f, 1f), new Vector2(0f, 1f), new Vector2(32f, -32f), false,
|
||||
hp[0], maxHp[0], mp[0], maxMp[0], false);
|
||||
CreatePlayerCard(parent, 1, new Vector2(1f, 1f), new Vector2(1f, 1f), new Vector2(-32f, -32f), true,
|
||||
hp[1], maxHp[1], mp[1], maxMp[1], false);
|
||||
CreatePlayerCard(parent, 2, new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(32f, 28f), false,
|
||||
hp[2], maxHp[2], mp[2], maxMp[2], false);
|
||||
CreatePlayerCard(parent, 3, new Vector2(1f, 0f), new Vector2(1f, 0f), new Vector2(-32f, 28f), true,
|
||||
hp[3], maxHp[3], mp[3], maxMp[3], true);
|
||||
}
|
||||
|
||||
private static void CreatePlayerCard(RectTransform parent, int index, Vector2 anchor, Vector2 pivot,
|
||||
Vector2 position, bool mirror, int hp, int maxHp, int mp, int maxMp, bool active)
|
||||
{
|
||||
RectTransform cardRect = AddPanel(Fixed("PlayerCard" + (index + 1), parent, anchor, pivot, position,
|
||||
new Vector2(400f, 176f)), panelBeige, Color.white);
|
||||
|
||||
RectTransform marker = TopStretch("ActiveMarker", cardRect, 0f, 0f, 0f, 7f);
|
||||
AddImage(marker, Gold, null);
|
||||
marker.gameObject.SetActive(active);
|
||||
|
||||
float iconX = mirror ? 278f : 18f;
|
||||
float contentX = mirror ? 18f : 140f;
|
||||
RectTransform iconFrame = Fixed("IconFrame", cardRect, new Vector2(0f, 1f), new Vector2(0f, 1f),
|
||||
new Vector2(iconX, -18f), new Vector2(104f, 140f));
|
||||
AddImage(iconFrame, Charcoal, null);
|
||||
Image icon = AddSprite(Fixed("Icon", iconFrame, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f),
|
||||
new Vector2(0f, -8f), new Vector2(88f, 96f)), playerIcons[index], Color.white, true);
|
||||
AddText(Fixed("PartyIndex", iconFrame, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f),
|
||||
new Vector2(0f, 7f), new Vector2(84f, 24f)), "P" + (index + 1), 14, MutedWhite,
|
||||
TextAlignmentOptions.Center, FontStyles.Bold);
|
||||
|
||||
TextMeshProUGUI name = AddText(
|
||||
Fixed("CharacterName", cardRect, new Vector2(0f, 1f), new Vector2(0f, 1f),
|
||||
new Vector2(contentX, -14f), new Vector2(242f, 38f)), "角色 " + (index + 1), 24, Ink,
|
||||
mirror ? TextAlignmentOptions.TopRight : TextAlignmentOptions.TopLeft, FontStyles.Bold);
|
||||
Image hpFill = CreateBar(cardRect, "HP", contentX, 64f, 242f, Green, hp, maxHp, out TextMeshProUGUI hpText);
|
||||
Image mpFill = CreateBar(cardRect, "MP", contentX, 112f, 242f, Blue, mp, maxMp, out TextMeshProUGUI mpText);
|
||||
|
||||
BattleActorCardItem card = cardRect.gameObject.AddComponent<BattleActorCardItem>();
|
||||
card.ConfigureEditor(marker.gameObject, icon, hpFill, mpFill, name, hpText, mpText);
|
||||
RegisterReference(card, "playerCard" + (index + 1));
|
||||
}
|
||||
|
||||
private static void BuildEnemies(RectTransform parent)
|
||||
{
|
||||
RectTransform enemyRoot = Fixed("EnemyRoot", parent, new Vector2(0.5f, 0.5f),
|
||||
new Vector2(0.5f, 0.5f), new Vector2(0f, 32f), new Vector2(820f, 290f));
|
||||
int[] hp = { 36, 80, 44, 61 };
|
||||
int[] maxHp = { 50, 80, 60, 80 };
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
RectTransform slotRect = Fixed("EnemySlot" + (i + 1), enemyRoot, new Vector2(0.5f, 0.5f),
|
||||
new Vector2(0.5f, 0.5f), new Vector2(-300f + i * 200f, 0f), new Vector2(170f, 248f));
|
||||
Image targetGraphic = AddImage(slotRect, new Color(EnemyFrame.r, EnemyFrame.g, EnemyFrame.b, 0.68f), null);
|
||||
targetGraphic.raycastTarget = true;
|
||||
Button targetButton = slotRect.gameObject.AddComponent<Button>();
|
||||
targetButton.targetGraphic = targetGraphic;
|
||||
targetButton.transition = Selectable.Transition.ColorTint;
|
||||
|
||||
RectTransform selected = Stretch("SelectedMarker", slotRect, -4f, -4f, -4f, -4f);
|
||||
AddImage(selected, Color.clear, null);
|
||||
Outline selectedOutline = selected.gameObject.AddComponent<Outline>();
|
||||
selectedOutline.effectColor = Gold;
|
||||
selectedOutline.effectDistance = new Vector2(4f, -4f);
|
||||
selectedOutline.useGraphicAlpha = false;
|
||||
selected.gameObject.SetActive(i == 1);
|
||||
|
||||
Image icon = AddSprite(Fixed("Icon", slotRect, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f),
|
||||
new Vector2(0f, -12f), new Vector2(142f, 146f)), enemyIcons[i], Color.white, true);
|
||||
TextMeshProUGUI name = AddText(
|
||||
Fixed("EnemyName", slotRect, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f),
|
||||
new Vector2(0f, 55f), new Vector2(154f, 30f)), "敌人 " + (i + 1), 18, White,
|
||||
TextAlignmentOptions.Center, FontStyles.Bold);
|
||||
Image hpFill = CreateEnemyBar(slotRect, hp[i], maxHp[i], out TextMeshProUGUI hpText);
|
||||
|
||||
BattleEnemySlotItem slot = slotRect.gameObject.AddComponent<BattleEnemySlotItem>();
|
||||
slot.ConfigureEditor(targetButton, selected.gameObject, icon, hpFill, name, hpText);
|
||||
RegisterReference(slot, "enemySlot" + (i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
private static void BuildActionPanel(RectTransform parent)
|
||||
{
|
||||
RectTransform panel = AddPanel(
|
||||
Fixed("ActionPanel", parent, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f),
|
||||
new Vector2(0f, 28f), new Vector2(920f, 176f)), panelBlue, Color.white);
|
||||
AddImage(TopStretch("CurrentAccent", panel, 0f, 0f, 0f, 7f), Gold, null);
|
||||
|
||||
TextMeshProUGUI currentActor = AddText(
|
||||
Fixed("CurrentActorText", panel, new Vector2(0f, 1f), new Vector2(0f, 1f),
|
||||
new Vector2(28f, -15f), new Vector2(500f, 38f)), "角色 4", 24, White,
|
||||
TextAlignmentOptions.MidlineLeft, FontStyles.Bold);
|
||||
RegisterReference(currentActor, "currentActorText");
|
||||
AddText(Fixed("TurnState", panel, new Vector2(1f, 1f), new Vector2(1f, 1f),
|
||||
new Vector2(-28f, -17f), new Vector2(260f, 34f)), "YOUR MOVE", 15, Gold,
|
||||
TextAlignmentOptions.MidlineRight, FontStyles.Bold);
|
||||
|
||||
Button attack = CreateActionButton(panel, "AttackButton", "攻击", attackIcon, 25f, ButtonStyle.Brown);
|
||||
Button skill = CreateActionButton(panel, "SkillButton", "技能", skillIcon, 243f, ButtonStyle.Blue);
|
||||
Button item = CreateActionButton(panel, "ItemButton", "道具", itemIcon, 461f, ButtonStyle.Grey);
|
||||
Button escape = CreateActionButton(panel, "EscapeButton", "逃跑", escapeIcon, 679f, ButtonStyle.Beige);
|
||||
item.interactable = false;
|
||||
|
||||
RegisterReference(attack, "attackButton");
|
||||
RegisterReference(skill, "skillButton");
|
||||
RegisterReference(item, "itemButton");
|
||||
RegisterReference(escape, "escapeButton");
|
||||
}
|
||||
|
||||
private static Button CreateActionButton(RectTransform parent, string name, string label, Sprite icon,
|
||||
float x, ButtonStyle style)
|
||||
{
|
||||
RectTransform rect = Fixed(name, parent, new Vector2(0f, 1f), new Vector2(0f, 1f),
|
||||
new Vector2(x, -66f), new Vector2(216f, 82f));
|
||||
Sprite normal;
|
||||
Sprite pressed;
|
||||
Color textColor;
|
||||
switch (style)
|
||||
{
|
||||
case ButtonStyle.Blue:
|
||||
normal = buttonBlue;
|
||||
pressed = buttonBluePressed;
|
||||
textColor = White;
|
||||
break;
|
||||
case ButtonStyle.Brown:
|
||||
normal = buttonBrown;
|
||||
pressed = buttonBrownPressed;
|
||||
textColor = White;
|
||||
break;
|
||||
case ButtonStyle.Beige:
|
||||
normal = buttonBeige;
|
||||
pressed = buttonBeigePressed;
|
||||
textColor = Ink;
|
||||
break;
|
||||
default:
|
||||
normal = buttonGrey;
|
||||
pressed = buttonGreyPressed;
|
||||
textColor = Ink;
|
||||
break;
|
||||
}
|
||||
|
||||
Image background = AddImage(rect, Color.white, normal);
|
||||
background.type = Image.Type.Sliced;
|
||||
background.raycastTarget = true;
|
||||
Button button = rect.gameObject.AddComponent<Button>();
|
||||
button.targetGraphic = background;
|
||||
button.transition = Selectable.Transition.SpriteSwap;
|
||||
SpriteState state = button.spriteState;
|
||||
state.highlightedSprite = normal;
|
||||
state.selectedSprite = normal;
|
||||
state.pressedSprite = pressed;
|
||||
state.disabledSprite = pressed;
|
||||
button.spriteState = state;
|
||||
|
||||
Color iconColor = style == ButtonStyle.Beige ? Ink : Color.white;
|
||||
AddSprite(Fixed("Icon", rect, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f),
|
||||
new Vector2(24f, 0f), new Vector2(46f, 46f)), icon, iconColor, true);
|
||||
AddText(Fixed("Label", rect, new Vector2(0f, 0.5f), new Vector2(0f, 0.5f),
|
||||
new Vector2(80f, 0f), new Vector2(112f, 50f)), label, 25, textColor,
|
||||
TextAlignmentOptions.Center, FontStyles.Bold);
|
||||
return button;
|
||||
}
|
||||
|
||||
private static Image CreateBar(RectTransform parent, string label, float x, float top, float width,
|
||||
Color fillColor, int current, int maximum, out TextMeshProUGUI valueText)
|
||||
{
|
||||
RectTransform bar = Fixed(label + "Bar", parent, new Vector2(0f, 1f), new Vector2(0f, 1f),
|
||||
new Vector2(x, -top), new Vector2(width, 32f));
|
||||
AddImage(bar, Charcoal, null);
|
||||
RectTransform fillRect = Stretch("Fill", bar, 3f, 3f, 3f, 3f);
|
||||
fillRect.anchorMax = new Vector2(Mathf.Clamp01((float)current / maximum), 1f);
|
||||
fillRect.offsetMax = new Vector2(0f, -3f);
|
||||
Image fill = AddImage(fillRect, fillColor, null);
|
||||
valueText = AddText(Stretch("Value", bar, 10f, 10f, 0f, 0f),
|
||||
label + " " + current + " / " + maximum, 15, White, TextAlignmentOptions.Center, FontStyles.Bold);
|
||||
return fill;
|
||||
}
|
||||
|
||||
private static Image CreateEnemyBar(RectTransform parent, int current, int maximum,
|
||||
out TextMeshProUGUI valueText)
|
||||
{
|
||||
RectTransform bar = Fixed("HPBar", parent, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f),
|
||||
new Vector2(0f, 17f), new Vector2(150f, 26f));
|
||||
AddImage(bar, Charcoal, null);
|
||||
RectTransform fillRect = Stretch("Fill", bar, 3f, 3f, 3f, 3f);
|
||||
fillRect.anchorMax = new Vector2(Mathf.Clamp01((float)current / maximum), 1f);
|
||||
fillRect.offsetMax = new Vector2(0f, -3f);
|
||||
Image fill = AddImage(fillRect, Red, null);
|
||||
valueText = AddText(Stretch("Value", bar, 6f, 6f, 0f, 0f), current + " / " + maximum, 13, White,
|
||||
TextAlignmentOptions.Center, FontStyles.Bold);
|
||||
return fill;
|
||||
}
|
||||
|
||||
private static void LoadAssets()
|
||||
{
|
||||
font = AssetDatabase.LoadAssetAtPath<TMP_FontAsset>("Assets/GameMain/Fonts/MainTMPFont.asset");
|
||||
panelBeige = LoadSprite("Assets/GameMain/Textures/UI/panel_beige.png");
|
||||
panelBrown = LoadSprite("Assets/GameMain/Textures/UI/panel_brown.png");
|
||||
panelBlue = LoadSprite("Assets/GameMain/Textures/UI/panel_blue.png");
|
||||
buttonBlue = LoadSprite("Assets/GameMain/Textures/UI/buttonLong_blue.png");
|
||||
buttonBluePressed = LoadSprite("Assets/GameMain/Textures/UI/buttonLong_blue_pressed.png");
|
||||
buttonBrown = LoadSprite("Assets/GameMain/Textures/UI/buttonLong_brown.png");
|
||||
buttonBrownPressed = LoadSprite("Assets/GameMain/Textures/UI/buttonLong_brown_pressed.png");
|
||||
buttonBeige = LoadSprite("Assets/GameMain/Textures/UI/buttonLong_beige.png");
|
||||
buttonBeigePressed = LoadSprite("Assets/GameMain/Textures/UI/buttonLong_beige_pressed.png");
|
||||
buttonGrey = LoadSprite("Assets/GameMain/Textures/UI/buttonLong_grey.png");
|
||||
buttonGreyPressed = LoadSprite("Assets/GameMain/Textures/UI/buttonLong_grey_pressed.png");
|
||||
escapeIcon = LoadSprite("Assets/GameMain/Textures/UI/arrowSilver_left.png");
|
||||
playerIcons = Enumerable.Range(0, 4).Select(i => LoadSprite(CharacterAtlasPath, "rogues_" + i)).ToArray();
|
||||
int[] enemyIndexes = { 0, 1, 2, 3 };
|
||||
enemyIcons = enemyIndexes.Select(i => LoadSprite(EnemyAtlasPath, "monsters_" + i)).ToArray();
|
||||
attackIcon = LoadSprite(ItemAtlasPath, "items_0");
|
||||
skillIcon = LoadSprite(ItemAtlasPath, "items_110");
|
||||
itemIcon = LoadSprite(ItemAtlasPath, "items_220");
|
||||
|
||||
if (font == null || playerIcons.Any(sprite => sprite == null) || enemyIcons.Any(sprite => sprite == null))
|
||||
{
|
||||
throw new InvalidOperationException("Battle UI assets are incomplete.");
|
||||
}
|
||||
}
|
||||
|
||||
private static Sprite LoadSprite(string path)
|
||||
{
|
||||
return AssetDatabase.LoadAssetAtPath<Sprite>(path);
|
||||
}
|
||||
|
||||
private static Sprite LoadSprite(string path, string name)
|
||||
{
|
||||
return AssetDatabase.LoadAllAssetsAtPath(path).OfType<Sprite>().FirstOrDefault(sprite => sprite.name == name);
|
||||
}
|
||||
|
||||
private static RectTransform AddPanel(RectTransform rect, Sprite sprite, Color color)
|
||||
{
|
||||
Image image = AddImage(rect, color, sprite);
|
||||
image.type = Image.Type.Sliced;
|
||||
return rect;
|
||||
}
|
||||
|
||||
private static Image AddImage(RectTransform rect, Color color, Sprite sprite)
|
||||
{
|
||||
Image image = rect.gameObject.AddComponent<Image>();
|
||||
image.color = color;
|
||||
image.sprite = sprite;
|
||||
image.raycastTarget = false;
|
||||
return image;
|
||||
}
|
||||
|
||||
private static Image AddSprite(RectTransform rect, Sprite sprite, Color color, bool preserveAspect)
|
||||
{
|
||||
Image image = AddImage(rect, color, sprite);
|
||||
image.preserveAspect = preserveAspect;
|
||||
return image;
|
||||
}
|
||||
|
||||
private static TextMeshProUGUI AddText(RectTransform rect, string value, float fontSize, Color color,
|
||||
TextAlignmentOptions alignment, FontStyles style)
|
||||
{
|
||||
TextMeshProUGUI text = rect.gameObject.AddComponent<TextMeshProUGUI>();
|
||||
text.font = font;
|
||||
text.text = value;
|
||||
text.fontSize = fontSize;
|
||||
text.fontStyle = style;
|
||||
text.color = color;
|
||||
text.alignment = alignment;
|
||||
text.enableWordWrapping = false;
|
||||
text.overflowMode = TextOverflowModes.Ellipsis;
|
||||
text.raycastTarget = false;
|
||||
text.characterSpacing = 0f;
|
||||
return text;
|
||||
}
|
||||
|
||||
private static RectTransform NewRect(string name, Transform parent)
|
||||
{
|
||||
GameObject gameObject = new GameObject(name, typeof(RectTransform));
|
||||
gameObject.layer = LayerMask.NameToLayer("UI");
|
||||
RectTransform rect = gameObject.GetComponent<RectTransform>();
|
||||
rect.SetParent(parent, false);
|
||||
return rect;
|
||||
}
|
||||
|
||||
private static RectTransform Fixed(string name, Transform parent, Vector2 anchor, Vector2 pivot,
|
||||
Vector2 position, Vector2 size)
|
||||
{
|
||||
RectTransform rect = NewRect(name, parent);
|
||||
rect.anchorMin = anchor;
|
||||
rect.anchorMax = anchor;
|
||||
rect.pivot = pivot;
|
||||
rect.anchoredPosition = position;
|
||||
rect.sizeDelta = size;
|
||||
return rect;
|
||||
}
|
||||
|
||||
private static RectTransform Stretch(string name, Transform parent, float left, float right, float bottom,
|
||||
float top)
|
||||
{
|
||||
RectTransform rect = NewRect(name, parent);
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.offsetMin = new Vector2(left, bottom);
|
||||
rect.offsetMax = new Vector2(-right, -top);
|
||||
return rect;
|
||||
}
|
||||
|
||||
private static RectTransform TopStretch(string name, Transform parent, float left, float right, float top,
|
||||
float height)
|
||||
{
|
||||
RectTransform rect = NewRect(name, parent);
|
||||
rect.anchorMin = new Vector2(0f, 1f);
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.pivot = new Vector2(0.5f, 1f);
|
||||
rect.offsetMin = new Vector2(left, -top - height);
|
||||
rect.offsetMax = new Vector2(-right, -top);
|
||||
return rect;
|
||||
}
|
||||
|
||||
private static void RegisterReference(Component component, string variableName)
|
||||
{
|
||||
UISerializationItem item = component.GetComponent<UISerializationItem>();
|
||||
if (item == null)
|
||||
{
|
||||
item = component.gameObject.AddComponent<UISerializationItem>();
|
||||
}
|
||||
|
||||
item.RefreshComponents();
|
||||
item.SetReference(component, true, variableName);
|
||||
}
|
||||
|
||||
private enum ButtonStyle
|
||||
{
|
||||
Blue,
|
||||
Brown,
|
||||
Beige,
|
||||
Grey
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 019291a41e12a4f2088fe5542024e54a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -6,7 +6,8 @@
|
||||
"GUID:a2d8a19598eca814496b089021d08d60",
|
||||
"GUID:a04b272484018094ab427253f20c416c",
|
||||
"GUID:11ec41dbc3557e6499b2845ba531a9b0",
|
||||
"GUID:73a59a40d41e9ce48b3e0515aeec4217"
|
||||
"GUID:73a59a40d41e9ce48b3e0515aeec4217",
|
||||
"GUID:6055be8ebefd69e48b49212b09b47b2f"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
@@ -17,4 +18,4 @@
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
using GameFramework.Localization;
|
||||
using System;
|
||||
using SepCore.Sound;
|
||||
using SepCore.Debugger;
|
||||
using SepCore.Definition;
|
||||
using UnityGameFramework.Runtime;
|
||||
using ProcedureOwner = GameFramework.Fsm.IFsm<GameFramework.Procedure.IProcedureManager>;
|
||||
@@ -20,6 +21,11 @@ namespace SepCore.Procedure
|
||||
{
|
||||
base.OnEnter(procedureOwner);
|
||||
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
// 战斗壳层调试入口:仅编辑器与开发构建注册,正式构建不可见
|
||||
GameEntry.Debugger.RegisterDebuggerWindow("Battle/Shell", new BattleDebuggerWindow());
|
||||
#endif
|
||||
|
||||
// 构建信息:发布版本时,把一些数据以 Json 的格式写入 Assets/GameMain/Configs/BuildInfo.txt,供游戏逻辑读取
|
||||
GameEntry.BuiltinData.InitBuildInfo();
|
||||
|
||||
|
||||
@@ -12,10 +12,22 @@ public partial class GameEntry : MonoBehaviour
|
||||
|
||||
public static SaveComponent Save { get; private set; }
|
||||
|
||||
public static RandomComponent Random { get; private set; }
|
||||
|
||||
public static TurnBattleComponent TurnBattle { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 单局战斗协调器(普通类,非场景组件)。
|
||||
/// </summary>
|
||||
public static RunBattleCoordinator RunBattle { get; private set; }
|
||||
|
||||
private static void InitCustomComponents()
|
||||
{
|
||||
BuiltinData = UnityGameFramework.Runtime.GameEntry.GetComponent<BuiltinDataComponent>();
|
||||
Luban = UnityGameFramework.Runtime.GameEntry.GetComponent<LubanComponent>();
|
||||
Save = UnityGameFramework.Runtime.GameEntry.GetComponent<SaveComponent>();
|
||||
Random = UnityGameFramework.Runtime.GameEntry.GetComponent<RandomComponent>();
|
||||
TurnBattle = UnityGameFramework.Runtime.GameEntry.GetComponent<TurnBattleComponent>();
|
||||
RunBattle = new RunBattleCoordinator();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b415c54b5bb04d62bf67dfdddc382c2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,178 @@
|
||||
using System.Collections.Generic;
|
||||
using SepCore.Definition;
|
||||
|
||||
namespace SepCore.Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 单个效果对单个目标产生的数值或状态变化。
|
||||
/// </summary>
|
||||
public sealed class BattleActionEffectRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// 目标单位。
|
||||
/// </summary>
|
||||
public readonly int TargetUnitId;
|
||||
|
||||
/// <summary>
|
||||
/// 效果结算前的 HP。
|
||||
/// </summary>
|
||||
public readonly int BeforeHp;
|
||||
|
||||
/// <summary>
|
||||
/// 效果结算后的 HP。
|
||||
/// </summary>
|
||||
public readonly int AfterHp;
|
||||
|
||||
/// <summary>
|
||||
/// 效果结算前的 MP。
|
||||
/// </summary>
|
||||
public readonly int BeforeMp;
|
||||
|
||||
/// <summary>
|
||||
/// 效果结算后的 MP。
|
||||
/// </summary>
|
||||
public readonly int AfterMp;
|
||||
|
||||
/// <summary>
|
||||
/// 状态变化列表;无状态变化时为空。
|
||||
/// </summary>
|
||||
public readonly IReadOnlyList<BattleStatusChangeRecord> StatusChanges;
|
||||
|
||||
public BattleActionEffectRecord(int targetUnitId, int beforeHp, int afterHp, int beforeMp, int afterMp,
|
||||
IReadOnlyList<BattleStatusChangeRecord> statusChanges)
|
||||
{
|
||||
TargetUnitId = targetUnitId;
|
||||
BeforeHp = beforeHp;
|
||||
AfterHp = afterHp;
|
||||
BeforeMp = beforeMp;
|
||||
AfterMp = afterMp;
|
||||
StatusChanges = statusChanges != null ? Copy(statusChanges) : new BattleStatusChangeRecord[0];
|
||||
}
|
||||
|
||||
private static BattleStatusChangeRecord[] Copy(IReadOnlyList<BattleStatusChangeRecord> source)
|
||||
{
|
||||
BattleStatusChangeRecord[] copy = new BattleStatusChangeRecord[source.Count];
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
copy[i] = source[i];
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 状态变化记录。
|
||||
/// </summary>
|
||||
public sealed class BattleStatusChangeRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态类型。
|
||||
/// </summary>
|
||||
public readonly BattleStatusType StatusType;
|
||||
|
||||
/// <summary>
|
||||
/// 变化后的剩余持续次数;状态被移除时为 0。
|
||||
/// </summary>
|
||||
public readonly int RemainingRounds;
|
||||
|
||||
public BattleStatusChangeRecord(BattleStatusType statusType, int remainingRounds)
|
||||
{
|
||||
StatusType = statusType;
|
||||
RemainingRounds = remainingRounds;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一次行动产生的有序行动记录。
|
||||
/// 首版 UI 立即消费并显示;未来动画系统可以逐条等待播放,不需要修改战斗规则内核。
|
||||
/// </summary>
|
||||
public sealed class BattleActionRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// 行动者。
|
||||
/// </summary>
|
||||
public readonly int ActorUnitId;
|
||||
|
||||
/// <summary>
|
||||
/// 指令类型,对应配表 BattleActionType。
|
||||
/// </summary>
|
||||
public readonly BattleActionType CommandType;
|
||||
|
||||
/// <summary>
|
||||
/// 攻击或技能配置 ID;逃跑为 0。
|
||||
/// </summary>
|
||||
public readonly int ActionConfigId;
|
||||
|
||||
/// <summary>
|
||||
/// 按结算顺序排列的效果记录。
|
||||
/// </summary>
|
||||
public readonly IReadOnlyList<BattleActionEffectRecord> Effects;
|
||||
|
||||
public BattleActionRecord(int actorUnitId, BattleActionType commandType, int actionConfigId,
|
||||
IReadOnlyList<BattleActionEffectRecord> effects)
|
||||
{
|
||||
ActorUnitId = actorUnitId;
|
||||
CommandType = commandType;
|
||||
ActionConfigId = actionConfigId;
|
||||
Effects = effects != null ? Copy(effects) : new BattleActionEffectRecord[0];
|
||||
}
|
||||
|
||||
private static BattleActionEffectRecord[] Copy(IReadOnlyList<BattleActionEffectRecord> source)
|
||||
{
|
||||
BattleActionEffectRecord[] copy = new BattleActionEffectRecord[source.Count];
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
copy[i] = source[i];
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 战斗推进返回值。
|
||||
/// </summary>
|
||||
public sealed class BattleAdvance
|
||||
{
|
||||
/// <summary>
|
||||
/// 本次推进产生的有序行动记录列表。
|
||||
/// </summary>
|
||||
public readonly IReadOnlyList<BattleActionRecord> Records;
|
||||
|
||||
/// <summary>
|
||||
/// 推进结束后的只读战斗快照。
|
||||
/// </summary>
|
||||
public readonly BattleSnapshot Snapshot;
|
||||
|
||||
/// <summary>
|
||||
/// 流程状态。
|
||||
/// </summary>
|
||||
public readonly BattleFlowState FlowState;
|
||||
|
||||
/// <summary>
|
||||
/// 仅在 Completed 时存在,否则为 null。
|
||||
/// </summary>
|
||||
public readonly BattleResult Result;
|
||||
|
||||
public BattleAdvance(IReadOnlyList<BattleActionRecord> records, BattleSnapshot snapshot,
|
||||
BattleFlowState flowState, BattleResult result)
|
||||
{
|
||||
Records = records != null ? Copy(records) : new BattleActionRecord[0];
|
||||
Snapshot = snapshot;
|
||||
FlowState = flowState;
|
||||
Result = result;
|
||||
}
|
||||
|
||||
private static BattleActionRecord[] Copy(IReadOnlyList<BattleActionRecord> source)
|
||||
{
|
||||
BattleActionRecord[] copy = new BattleActionRecord[source.Count];
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
copy[i] = source[i];
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84f740882a72430ca27c4d77004257d8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
using SepCore.Definition;
|
||||
|
||||
namespace SepCore.Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 玩家指令。
|
||||
/// 非法指令(错误行动者、错误阵营、错误目标数量、阵亡或逃跑目标)不得消耗 MP、行动机会或随机数。
|
||||
/// </summary>
|
||||
public sealed class BattleCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前获得行动机会的玩家单位。
|
||||
/// </summary>
|
||||
public readonly int ActorUnitId;
|
||||
|
||||
/// <summary>
|
||||
/// 指令类型,对应配表 BattleActionType。
|
||||
/// </summary>
|
||||
public readonly BattleActionType CommandType;
|
||||
|
||||
/// <summary>
|
||||
/// 攻击或技能对应的配置 ID;逃跑为 0。
|
||||
/// </summary>
|
||||
public readonly int ActionConfigId;
|
||||
|
||||
/// <summary>
|
||||
/// 按目标类型解析后的唯一运行时目标列表。
|
||||
/// </summary>
|
||||
public readonly IReadOnlyList<int> TargetUnitIds;
|
||||
|
||||
public BattleCommand(int actorUnitId, BattleActionType commandType, int actionConfigId,
|
||||
IReadOnlyList<int> targetUnitIds)
|
||||
{
|
||||
ActorUnitId = actorUnitId;
|
||||
CommandType = commandType;
|
||||
ActionConfigId = actionConfigId;
|
||||
TargetUnitIds = targetUnitIds != null ? Copy(targetUnitIds) : new int[0];
|
||||
}
|
||||
|
||||
private static int[] Copy(IReadOnlyList<int> source)
|
||||
{
|
||||
int[] copy = new int[source.Count];
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
copy[i] = source[i];
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce89addfe88643eda73478128e385d5a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace SepCore.Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 遭遇输入,由探索层在碰撞发生时创建。
|
||||
/// 地图坐标、碰撞器、GameObject 和警惕组件不进入战斗请求,由探索层继续持有。
|
||||
/// </summary>
|
||||
public sealed class BattleEncounter
|
||||
{
|
||||
/// <summary>
|
||||
/// 触发碰撞的地图敌人实例的单局唯一标识;不能使用敌人配置 ID 代替。
|
||||
/// </summary>
|
||||
public readonly int EncounterId;
|
||||
|
||||
/// <summary>
|
||||
/// 该地图敌人代表的敌人队伍预设 ID。
|
||||
/// </summary>
|
||||
public readonly int EnemyPartyConfigId;
|
||||
|
||||
/// <summary>
|
||||
/// 碰撞时敌人警惕值未满为 true,否则为 false。
|
||||
/// </summary>
|
||||
public readonly bool IsPreemptive;
|
||||
|
||||
public BattleEncounter(int encounterId, int enemyPartyConfigId, bool isPreemptive)
|
||||
{
|
||||
EncounterId = encounterId;
|
||||
EnemyPartyConfigId = enemyPartyConfigId;
|
||||
IsPreemptive = isPreemptive;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42d8ce9387864e68a141ace8c990f06f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace SepCore.Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 战斗单位阵营。
|
||||
/// </summary>
|
||||
public enum BattleFaction
|
||||
{
|
||||
/// <summary>
|
||||
/// 玩家阵营。
|
||||
/// </summary>
|
||||
Player,
|
||||
|
||||
/// <summary>
|
||||
/// 敌人阵营。
|
||||
/// </summary>
|
||||
Enemy,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d809efd802bf499c8237b00546a35c15
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace SepCore.Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 战斗流程状态。
|
||||
/// </summary>
|
||||
public enum BattleFlowState
|
||||
{
|
||||
/// <summary>
|
||||
/// 等待当前玩家行动者提交指令。
|
||||
/// </summary>
|
||||
AwaitingPlayerCommand,
|
||||
|
||||
/// <summary>
|
||||
/// 战斗完成,结果已产出。
|
||||
/// </summary>
|
||||
Completed,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bef66af163534a79b4b14ba27ed80665
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace SepCore.Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 战斗结束结果,四种结果互斥。
|
||||
/// </summary>
|
||||
public enum BattleOutcome
|
||||
{
|
||||
/// <summary>
|
||||
/// 所有敌人阵亡。
|
||||
/// </summary>
|
||||
Victory,
|
||||
|
||||
/// <summary>
|
||||
/// 所有玩家成功逃跑,且没有玩家阵亡。
|
||||
/// </summary>
|
||||
AllEscaped,
|
||||
|
||||
/// <summary>
|
||||
/// 至少一名玩家逃跑,其余仍在战斗中的玩家全部阵亡。
|
||||
/// </summary>
|
||||
PartialEscapeDefeat,
|
||||
|
||||
/// <summary>
|
||||
/// 所有玩家阵亡,没有任何玩家成功逃跑。
|
||||
/// </summary>
|
||||
TotalDefeat,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 56279ec78df04139b731da9dd07e7f14
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,80 @@
|
||||
namespace SepCore.Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 玩家输入快照,是进入本场战斗时的值快照。
|
||||
/// 装备计算由单局角色快照构建器完成;战斗内核不读取角色存档、装备栏或背包。
|
||||
/// </summary>
|
||||
public sealed class BattlePlayerInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色配置标识;同时用于把结果回写到单局角色状态。
|
||||
/// </summary>
|
||||
public readonly int CharacterId;
|
||||
|
||||
/// <summary>
|
||||
/// 当前战备中的顺序。
|
||||
/// </summary>
|
||||
public readonly int PartyOrder;
|
||||
|
||||
/// <summary>
|
||||
/// 单局临时状态中的当前 HP。
|
||||
/// </summary>
|
||||
public readonly int CurrentHp;
|
||||
|
||||
/// <summary>
|
||||
/// 单局临时状态中的当前 MP。
|
||||
/// </summary>
|
||||
public readonly int CurrentMp;
|
||||
|
||||
/// <summary>
|
||||
/// 已结算装备加成的最终 HP 上限。
|
||||
/// </summary>
|
||||
public readonly int MaxHp;
|
||||
|
||||
/// <summary>
|
||||
/// 已结算装备加成的最终 MP 上限。
|
||||
/// </summary>
|
||||
public readonly int MaxMp;
|
||||
|
||||
/// <summary>
|
||||
/// 已结算装备加成的最终攻击。
|
||||
/// </summary>
|
||||
public readonly int Atk;
|
||||
|
||||
/// <summary>
|
||||
/// 已结算装备加成的最终魔力。
|
||||
/// </summary>
|
||||
public readonly int Mat;
|
||||
|
||||
/// <summary>
|
||||
/// 已结算装备加成的最终速度。
|
||||
/// </summary>
|
||||
public readonly int Speed;
|
||||
|
||||
/// <summary>
|
||||
/// 普通攻击行动配置 ID。
|
||||
/// </summary>
|
||||
public readonly int AttackActionId;
|
||||
|
||||
/// <summary>
|
||||
/// 该角色首版唯一技能行动配置 ID。
|
||||
/// </summary>
|
||||
public readonly int SkillActionId;
|
||||
|
||||
public BattlePlayerInput(int characterId, int partyOrder, int currentHp, int currentMp,
|
||||
int maxHp, int maxMp, int atk, int mat, int speed, int attackActionId, int skillActionId)
|
||||
{
|
||||
CharacterId = characterId;
|
||||
PartyOrder = partyOrder;
|
||||
CurrentHp = currentHp;
|
||||
CurrentMp = currentMp;
|
||||
MaxHp = maxHp;
|
||||
MaxMp = maxMp;
|
||||
Atk = atk;
|
||||
Mat = mat;
|
||||
Speed = speed;
|
||||
AttackActionId = attackActionId;
|
||||
SkillActionId = skillActionId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c518c47a9854ba19045abd1f211eb7d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,84 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SepCore.Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 每名参战玩家的战后结果。
|
||||
/// 保留原始战斗值:阵亡者 HP 为 0;恢复 1/1 的规则由 RunBattleCoordinator 统一应用。
|
||||
/// </summary>
|
||||
public sealed class BattlePlayerResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色配置标识。
|
||||
/// </summary>
|
||||
public readonly int CharacterId;
|
||||
|
||||
/// <summary>
|
||||
/// 战斗结束时的当前 HP。
|
||||
/// </summary>
|
||||
public readonly int CurrentHp;
|
||||
|
||||
/// <summary>
|
||||
/// 战斗结束时的当前 MP。
|
||||
/// </summary>
|
||||
public readonly int CurrentMp;
|
||||
|
||||
/// <summary>
|
||||
/// 是否在本场战斗中阵亡。
|
||||
/// </summary>
|
||||
public readonly bool WasDefeated;
|
||||
|
||||
/// <summary>
|
||||
/// 是否在本场战斗中成功逃跑。
|
||||
/// </summary>
|
||||
public readonly bool Escaped;
|
||||
|
||||
public BattlePlayerResult(int characterId, int currentHp, int currentMp, bool wasDefeated, bool escaped)
|
||||
{
|
||||
CharacterId = characterId;
|
||||
CurrentHp = currentHp;
|
||||
CurrentMp = currentMp;
|
||||
WasDefeated = wasDefeated;
|
||||
Escaped = escaped;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 战斗结果。
|
||||
/// </summary>
|
||||
public sealed class BattleResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 关联原地图敌人实例。
|
||||
/// </summary>
|
||||
public readonly int EncounterId;
|
||||
|
||||
/// <summary>
|
||||
/// 四种战斗结果之一。
|
||||
/// </summary>
|
||||
public readonly BattleOutcome Outcome;
|
||||
|
||||
/// <summary>
|
||||
/// 每名参战玩家的原始战后结果。
|
||||
/// </summary>
|
||||
public readonly IReadOnlyList<BattlePlayerResult> Players;
|
||||
|
||||
public BattleResult(int encounterId, BattleOutcome outcome, IReadOnlyList<BattlePlayerResult> players)
|
||||
{
|
||||
EncounterId = encounterId;
|
||||
Outcome = outcome;
|
||||
Players = players != null ? Copy(players) : new BattlePlayerResult[0];
|
||||
}
|
||||
|
||||
private static BattlePlayerResult[] Copy(IReadOnlyList<BattlePlayerResult> source)
|
||||
{
|
||||
BattlePlayerResult[] copy = new BattlePlayerResult[source.Count];
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
copy[i] = source[i];
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da5169df3c26408ba2b665960f5632fa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,82 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SepCore.Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 探索恢复计划,由 RunBattleCoordinator 根据战斗结果统一生成,探索层只执行计划。
|
||||
/// 探索层不得再次根据玩家 HP 或逃跑数量重判战斗结果。
|
||||
/// </summary>
|
||||
public sealed class BattleReturnPlan
|
||||
{
|
||||
/// <summary>
|
||||
/// 关联原地图敌人实例。
|
||||
/// </summary>
|
||||
public readonly int EncounterId;
|
||||
|
||||
/// <summary>
|
||||
/// 战斗结果。
|
||||
/// </summary>
|
||||
public readonly BattleOutcome Outcome;
|
||||
|
||||
/// <summary>
|
||||
/// 回写后的玩家临时状态(含 1/1 恢复规则应用后的结果)。
|
||||
/// </summary>
|
||||
public readonly IReadOnlyList<BattlePlayerResult> Players;
|
||||
|
||||
/// <summary>
|
||||
/// 是否移除触发敌人(胜利)。
|
||||
/// </summary>
|
||||
public readonly bool RemoveEncounterEnemy;
|
||||
|
||||
/// <summary>
|
||||
/// 是否保留并恢复触发敌人,下次按完整预设重建(逃跑类结果)。
|
||||
/// </summary>
|
||||
public readonly bool ResetEncounterEnemy;
|
||||
|
||||
/// <summary>
|
||||
/// 是否使用本局随机源结算掉落(胜利)。
|
||||
/// </summary>
|
||||
public readonly bool ShouldRollDrops;
|
||||
|
||||
/// <summary>
|
||||
/// 逃跑类结果的保护时间(毫秒);其他结果为 0。
|
||||
/// </summary>
|
||||
public readonly int ProtectionMs;
|
||||
|
||||
/// <summary>
|
||||
/// 是否恢复探索更新。
|
||||
/// </summary>
|
||||
public readonly bool ShouldResumeExploration;
|
||||
|
||||
/// <summary>
|
||||
/// 是否以单局失败结束,不返回探索。
|
||||
/// </summary>
|
||||
public readonly bool EndsRunAsDefeated;
|
||||
|
||||
public BattleReturnPlan(int encounterId, BattleOutcome outcome,
|
||||
IReadOnlyList<BattlePlayerResult> players, bool removeEncounterEnemy, bool resetEncounterEnemy,
|
||||
bool shouldRollDrops, int protectionMs, bool shouldResumeExploration, bool endsRunAsDefeated)
|
||||
{
|
||||
EncounterId = encounterId;
|
||||
Outcome = outcome;
|
||||
Players = players != null ? Copy(players) : new BattlePlayerResult[0];
|
||||
RemoveEncounterEnemy = removeEncounterEnemy;
|
||||
ResetEncounterEnemy = resetEncounterEnemy;
|
||||
ShouldRollDrops = shouldRollDrops;
|
||||
ProtectionMs = protectionMs;
|
||||
ShouldResumeExploration = shouldResumeExploration;
|
||||
EndsRunAsDefeated = endsRunAsDefeated;
|
||||
}
|
||||
|
||||
private static BattlePlayerResult[] Copy(IReadOnlyList<BattlePlayerResult> source)
|
||||
{
|
||||
BattlePlayerResult[] copy = new BattlePlayerResult[source.Count];
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
copy[i] = source[i];
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec4ca0d279034f4a939fd35aedab383e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,223 @@
|
||||
using System.Collections.Generic;
|
||||
using SepCore.Definition;
|
||||
|
||||
namespace SepCore.Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 单位剩余状态快照。
|
||||
/// </summary>
|
||||
public sealed class BattleStatusSnapshot
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态类型。
|
||||
/// </summary>
|
||||
public readonly BattleStatusType StatusType;
|
||||
|
||||
/// <summary>
|
||||
/// 剩余持续次数(行动机会);重复施加取较大值,移除后为 0。
|
||||
/// </summary>
|
||||
public readonly int RemainingRounds;
|
||||
|
||||
public BattleStatusSnapshot(BattleStatusType statusType, int remainingRounds)
|
||||
{
|
||||
StatusType = statusType;
|
||||
RemainingRounds = remainingRounds;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单个战斗单位的只读视图。
|
||||
/// </summary>
|
||||
public sealed class BattleUnitSnapshot
|
||||
{
|
||||
/// <summary>
|
||||
/// 本场战斗单位的唯一运行时标识;重复配置的敌人拥有不同 ID。
|
||||
/// </summary>
|
||||
public readonly int BattleUnitId;
|
||||
|
||||
/// <summary>
|
||||
/// 阵营。
|
||||
/// </summary>
|
||||
public readonly BattleFaction Faction;
|
||||
|
||||
/// <summary>
|
||||
/// 配置标识:玩家为 CharacterId,敌人为 EnemyConfigId。
|
||||
/// </summary>
|
||||
public readonly int ConfigId;
|
||||
|
||||
/// <summary>
|
||||
/// 同阵营同速度的最终并列规则顺序。
|
||||
/// </summary>
|
||||
public readonly int PartyOrder;
|
||||
|
||||
/// <summary>
|
||||
/// 当前 HP。
|
||||
/// </summary>
|
||||
public readonly int CurrentHp;
|
||||
|
||||
/// <summary>
|
||||
/// HP 上限。
|
||||
/// </summary>
|
||||
public readonly int MaxHp;
|
||||
|
||||
/// <summary>
|
||||
/// 当前 MP。
|
||||
/// </summary>
|
||||
public readonly int CurrentMp;
|
||||
|
||||
/// <summary>
|
||||
/// MP 上限。
|
||||
/// </summary>
|
||||
public readonly int MaxMp;
|
||||
|
||||
/// <summary>
|
||||
/// 攻击。
|
||||
/// </summary>
|
||||
public readonly int Atk;
|
||||
|
||||
/// <summary>
|
||||
/// 魔力。
|
||||
/// </summary>
|
||||
public readonly int Mat;
|
||||
|
||||
/// <summary>
|
||||
/// 当前速度。
|
||||
/// </summary>
|
||||
public readonly int Speed;
|
||||
|
||||
/// <summary>
|
||||
/// 是否阵亡。
|
||||
/// </summary>
|
||||
public readonly bool IsDefeated;
|
||||
|
||||
/// <summary>
|
||||
/// 是否已逃跑。
|
||||
/// </summary>
|
||||
public readonly bool IsEscaped;
|
||||
|
||||
/// <summary>
|
||||
/// 剩余状态列表。
|
||||
/// </summary>
|
||||
public readonly IReadOnlyList<BattleStatusSnapshot> Statuses;
|
||||
|
||||
/// <summary>
|
||||
/// 可用行动配置 ID 列表。
|
||||
/// </summary>
|
||||
public readonly IReadOnlyList<int> AvailableActionIds;
|
||||
|
||||
public BattleUnitSnapshot(int battleUnitId, BattleFaction faction, int configId, int partyOrder,
|
||||
int currentHp, int maxHp, int currentMp, int maxMp, int atk, int mat, int speed,
|
||||
bool isDefeated, bool isEscaped, IReadOnlyList<BattleStatusSnapshot> statuses,
|
||||
IReadOnlyList<int> availableActionIds)
|
||||
{
|
||||
BattleUnitId = battleUnitId;
|
||||
Faction = faction;
|
||||
ConfigId = configId;
|
||||
PartyOrder = partyOrder;
|
||||
CurrentHp = currentHp;
|
||||
MaxHp = maxHp;
|
||||
CurrentMp = currentMp;
|
||||
MaxMp = maxMp;
|
||||
Atk = atk;
|
||||
Mat = mat;
|
||||
Speed = speed;
|
||||
IsDefeated = isDefeated;
|
||||
IsEscaped = isEscaped;
|
||||
Statuses = statuses != null ? Copy(statuses) : new BattleStatusSnapshot[0];
|
||||
AvailableActionIds = availableActionIds != null ? Copy(availableActionIds) : new int[0];
|
||||
}
|
||||
|
||||
private static BattleStatusSnapshot[] Copy(IReadOnlyList<BattleStatusSnapshot> source)
|
||||
{
|
||||
BattleStatusSnapshot[] copy = new BattleStatusSnapshot[source.Count];
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
copy[i] = source[i];
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
private static int[] Copy(IReadOnlyList<int> source)
|
||||
{
|
||||
int[] copy = new int[source.Count];
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
copy[i] = source[i];
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 只读战斗快照,是 UI 和敌人策略唯一允许读取的战斗状态。
|
||||
/// 每次推进都创建逻辑上的只读快照,不把可修改的内部列表或战斗单位引用暴露给调用方。
|
||||
/// </summary>
|
||||
public sealed class BattleSnapshot
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前轮次,从 1 开始。
|
||||
/// </summary>
|
||||
public readonly int RoundNumber;
|
||||
|
||||
/// <summary>
|
||||
/// 当前是否为先制战斗第一轮。
|
||||
/// </summary>
|
||||
public readonly bool IsPreemptiveRound;
|
||||
|
||||
/// <summary>
|
||||
/// 当前行动者;战斗完成时为 0。
|
||||
/// </summary>
|
||||
public readonly int CurrentActorUnitId;
|
||||
|
||||
/// <summary>
|
||||
/// 全部单位的只读视图,保持稳定显示顺序。
|
||||
/// </summary>
|
||||
public readonly IReadOnlyList<BattleUnitSnapshot> Units;
|
||||
|
||||
/// <summary>
|
||||
/// 本轮从当前行动者开始的剩余运行时单位 ID;速度变化后重建。
|
||||
/// </summary>
|
||||
public readonly IReadOnlyList<int> RemainingTurnOrder;
|
||||
|
||||
/// <summary>
|
||||
/// 流程状态。
|
||||
/// </summary>
|
||||
public readonly BattleFlowState FlowState;
|
||||
|
||||
public BattleSnapshot(int roundNumber, bool isPreemptiveRound, int currentActorUnitId,
|
||||
IReadOnlyList<BattleUnitSnapshot> units, IReadOnlyList<int> remainingTurnOrder,
|
||||
BattleFlowState flowState)
|
||||
{
|
||||
RoundNumber = roundNumber;
|
||||
IsPreemptiveRound = isPreemptiveRound;
|
||||
CurrentActorUnitId = currentActorUnitId;
|
||||
Units = units != null ? Copy(units) : new BattleUnitSnapshot[0];
|
||||
RemainingTurnOrder = remainingTurnOrder != null ? Copy(remainingTurnOrder) : new int[0];
|
||||
FlowState = flowState;
|
||||
}
|
||||
|
||||
private static BattleUnitSnapshot[] Copy(IReadOnlyList<BattleUnitSnapshot> source)
|
||||
{
|
||||
BattleUnitSnapshot[] copy = new BattleUnitSnapshot[source.Count];
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
copy[i] = source[i];
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
private static int[] Copy(IReadOnlyList<int> source)
|
||||
{
|
||||
int[] copy = new int[source.Count];
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
copy[i] = source[i];
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5bfa0a211d34ba6874d7d0b276192a3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Collections.Generic;
|
||||
using SepCore.CustomComponent;
|
||||
|
||||
namespace SepCore.Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 战斗启动请求,创建后视为只读。
|
||||
/// 战斗开始失败时不得消耗随机数、修改单局状态或暂停状态计数。
|
||||
/// </summary>
|
||||
public sealed class BattleStartRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于将结果关联回地图敌人实例。
|
||||
/// </summary>
|
||||
public readonly int EncounterId;
|
||||
|
||||
/// <summary>
|
||||
/// 1 到 4 个玩家输入,保持战备顺序。
|
||||
/// </summary>
|
||||
public readonly IReadOnlyList<BattlePlayerInput> Players;
|
||||
|
||||
/// <summary>
|
||||
/// 用于按队伍预设创建 1 到 4 个敌人运行时单位。
|
||||
/// </summary>
|
||||
public readonly int EnemyPartyConfigId;
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用第一轮玩家先制。
|
||||
/// </summary>
|
||||
public readonly bool IsPreemptive;
|
||||
|
||||
/// <summary>
|
||||
/// 当前单局唯一的随机源实例,来自 RandomComponent,不创建战斗私有随机源。
|
||||
/// </summary>
|
||||
public readonly IRunRandomSource Random;
|
||||
|
||||
public BattleStartRequest(int encounterId, IReadOnlyList<BattlePlayerInput> players,
|
||||
int enemyPartyConfigId, bool isPreemptive, IRunRandomSource random)
|
||||
{
|
||||
EncounterId = encounterId;
|
||||
Players = players != null ? Copy(players) : new BattlePlayerInput[0];
|
||||
EnemyPartyConfigId = enemyPartyConfigId;
|
||||
IsPreemptive = isPreemptive;
|
||||
Random = random;
|
||||
}
|
||||
|
||||
private static BattlePlayerInput[] Copy(IReadOnlyList<BattlePlayerInput> source)
|
||||
{
|
||||
BattlePlayerInput[] copy = new BattlePlayerInput[source.Count];
|
||||
for (int i = 0; i < source.Count; i++)
|
||||
{
|
||||
copy[i] = source[i];
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 040ffec92b064e3fbd9d9a8db9b39291
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,32 @@
|
||||
using SepCore.Definition;
|
||||
|
||||
namespace SepCore.Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 战斗配置访问接口,隔离 BattleSession 与 GameEntry.Luban。
|
||||
/// 运行时适配器读取现有 Luban 表,测试使用内存配置。
|
||||
/// 配置缺失属于战斗启动失败,不允许用零值单位继续战斗。
|
||||
/// </summary>
|
||||
public interface IBattleConfigProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 按 ID 获取敌人队伍预设。
|
||||
/// </summary>
|
||||
EnemyPartyConfig GetEnemyParty(int id);
|
||||
|
||||
/// <summary>
|
||||
/// 按 ID 获取敌人配置。
|
||||
/// </summary>
|
||||
EnemyConfig GetEnemy(int id);
|
||||
|
||||
/// <summary>
|
||||
/// 按 ID 获取行动配置。
|
||||
/// </summary>
|
||||
BattleActionConfig GetAction(int id);
|
||||
|
||||
/// <summary>
|
||||
/// 获取全局配置。
|
||||
/// </summary>
|
||||
GlobalConfig GetGlobal();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf1d3721d95a45a0a88d81baae7ec0f7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
using SepCore.CustomComponent;
|
||||
|
||||
namespace SepCore.Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 敌人策略接口。
|
||||
/// 策略只读取快照并返回指令,不直接修改 BattleSession。
|
||||
/// 当前没有可执行行动时,本次行动机会记为跳过,不能形成无限循环。
|
||||
/// </summary>
|
||||
public interface IEnemyBattlePolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// 为当前敌人行动者决定指令。
|
||||
/// </summary>
|
||||
/// <param name="snapshot">只读战斗快照。</param>
|
||||
/// <param name="actorUnitId">当前获得行动机会的敌人单位。</param>
|
||||
/// <param name="random">本局共享随机源。</param>
|
||||
BattleCommand Decide(BattleSnapshot snapshot, int actorUnitId, IRunRandomSource random);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e803cf69950a4fb9b0b8d63603f99a4b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace SepCore.Battle
|
||||
{
|
||||
/// <summary>
|
||||
/// 探索层恢复接口。
|
||||
/// 碰撞方主动把 BattleEncounter 交给 RunBattleCoordinator;协调器不扫描场景寻找敌人。
|
||||
/// ApplyBattleReturn 必须按 EncounterId 精确操作触发敌人,不能按 EnemyConfigId 批量处理同类敌人。
|
||||
/// </summary>
|
||||
public interface IExplorationBattleBridge
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行探索恢复计划。
|
||||
/// </summary>
|
||||
/// <param name="plan">由 RunBattleCoordinator 生成的恢复计划。</param>
|
||||
void ApplyBattleReturn(BattleReturnPlan plan);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf022ae8498b418784037549ce415e3a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bdc9ce5dfa33f4f409f98053881b767a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace SepCore.CustomComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// 本局共享随机数接口。
|
||||
/// 同一单局的地图生成、敌人 AI、逃跑和敌人掉落依次消费同一个实例;
|
||||
/// 战斗不能根据 EncounterId、回合数或当前时间重新播种。
|
||||
/// </summary>
|
||||
public interface IRunRandomSource
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回 [minInclusive, maxExclusive) 范围内的随机整数。
|
||||
/// </summary>
|
||||
int NextInt(int minInclusive, int maxExclusive);
|
||||
|
||||
/// <summary>
|
||||
/// 以千分比概率判定是否成功;RollPermille(0) 必失败,RollPermille(1000) 必成功。
|
||||
/// </summary>
|
||||
bool RollPermille(int successPermille);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e17f39494674cf08295b96df17642e7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,46 @@
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace SepCore.CustomComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// 单局共享随机组件。
|
||||
/// 种子由局外(战备/部署界面玩家输入)带入,进入单局时初始化;
|
||||
/// 地图生成、物资点、敌人 AI、逃跑判定和敌人掉落依次消费同一个随机源,
|
||||
/// 战斗只沿用该随机源,不创建战斗私有随机源。
|
||||
/// 对应 Docs/GameDesign/03_RunExploration.md 的随机生成器约定。
|
||||
/// </summary>
|
||||
public class RandomComponent : GameFrameworkComponent
|
||||
{
|
||||
private int _seed;
|
||||
private IRunRandomSource _random;
|
||||
|
||||
/// <summary>
|
||||
/// 获取本局随机种子;未开始单局时为 0。
|
||||
/// </summary>
|
||||
public int Seed => _seed;
|
||||
|
||||
/// <summary>
|
||||
/// 获取本局共享随机源;未开始单局时为 null。
|
||||
/// </summary>
|
||||
public IRunRandomSource Random => _random;
|
||||
|
||||
/// <summary>
|
||||
/// 开始一局新的单局:使用局外带入的种子初始化共享随机源。
|
||||
/// </summary>
|
||||
/// <param name="seed">本局随机种子。</param>
|
||||
public void BeginRun(int seed)
|
||||
{
|
||||
_seed = seed;
|
||||
_random = new RunRandomSource(seed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结束当前单局,清空种子与随机源。
|
||||
/// </summary>
|
||||
public void EndRun()
|
||||
{
|
||||
_seed = 0;
|
||||
_random = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 373a779ff43a4289b8e8cb23459d7bb3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
|
||||
namespace SepCore.CustomComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// 以 System.Random 实现的本局共享随机源。
|
||||
/// 由 TurnBattleComponent 以单局 seed 创建,供地图生成、敌人 AI、逃跑与掉落共用。
|
||||
/// EditMode 测试使用可注入的序列随机源,不依赖本类。
|
||||
/// </summary>
|
||||
public sealed class RunRandomSource : IRunRandomSource
|
||||
{
|
||||
private readonly Random _random;
|
||||
|
||||
/// <summary>
|
||||
/// 使用指定种子创建随机源。
|
||||
/// </summary>
|
||||
public RunRandomSource(int seed)
|
||||
{
|
||||
_random = new Random(seed);
|
||||
}
|
||||
|
||||
public int NextInt(int minInclusive, int maxExclusive)
|
||||
{
|
||||
return _random.Next(minInclusive, maxExclusive);
|
||||
}
|
||||
|
||||
public bool RollPermille(int successPermille)
|
||||
{
|
||||
return NextInt(0, 1000) < successPermille;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4dc1aacc472441329e01cf158f17981e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2569cd06130964d66b109493dabdfc77
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,112 @@
|
||||
using System.Collections.Generic;
|
||||
using SepCore.Battle;
|
||||
using SepCore.Definition;
|
||||
using SepCore.UI;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace SepCore.CustomComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// 单局战斗协调器(普通类,不挂场景)。
|
||||
/// 负责从单局状态构建请求、预留战斗占用、暂停/恢复探索更新与计时、打开/关闭战斗 UI;
|
||||
/// 后续里程碑在此创建并校验 BattleController,并消费 BattleResult 生成 BattleReturnPlan。
|
||||
/// 不负责回合排序、效果计算和 UI 控件状态。
|
||||
/// </summary>
|
||||
public sealed class RunBattleCoordinator
|
||||
{
|
||||
/// <summary>
|
||||
/// 尝试开始一场战斗。
|
||||
/// 战斗占用已激活时拒绝;请求构建失败时释放占用且不暂停地图。
|
||||
/// </summary>
|
||||
/// <param name="encounter">探索层创建的遭遇输入。</param>
|
||||
/// <returns>是否成功开始。</returns>
|
||||
public bool TryStartBattle(BattleEncounter encounter)
|
||||
{
|
||||
if (encounter == null)
|
||||
{
|
||||
Log.Error("Can not start battle with null encounter.");
|
||||
return false;
|
||||
}
|
||||
|
||||
TurnBattleComponent runBattle = GameEntry.TurnBattle;
|
||||
if (runBattle == null || runBattle.IsBattleActive)
|
||||
{
|
||||
Log.Warning("Can not start battle because battle occupancy is already active.");
|
||||
return false;
|
||||
}
|
||||
|
||||
BattleStartRequest request = BuildStartRequest(encounter);
|
||||
if (request == null)
|
||||
{
|
||||
Log.Warning("Can not start battle because start request is invalid.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 预留战斗占用并暂停探索更新与单局计时;
|
||||
// 后续里程碑在此创建并校验 BattleController,失败则释放占用且不暂停地图
|
||||
runBattle.TryReserveBattle();
|
||||
runBattle.SetExplorationPaused(true);
|
||||
runBattle.SetTimerPaused(true);
|
||||
|
||||
GameEntry.UI.OpenUIForm(UIFormType.BattleForm);
|
||||
Log.Info("Battle started with encounter '{0}'.", encounter.EncounterId);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭调试壳层并恢复探索更新与单局计时。
|
||||
/// M0 调试入口专用;M7 起由 ApplyResult 按战斗结果统一回写与恢复。
|
||||
/// </summary>
|
||||
public void EndDebugBattle()
|
||||
{
|
||||
TurnBattleComponent runBattle = GameEntry.TurnBattle;
|
||||
if (runBattle == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UGuiForm form = GameEntry.UI.GetUIForm(UIFormType.BattleForm);
|
||||
if (form != null)
|
||||
{
|
||||
GameEntry.UI.CloseUIForm(form);
|
||||
}
|
||||
|
||||
runBattle.SetExplorationPaused(false);
|
||||
runBattle.SetTimerPaused(false);
|
||||
runBattle.ReleaseBattle();
|
||||
Log.Info("Debug battle closed.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从单局临时角色状态与共享随机源构建启动请求。
|
||||
/// 请求创建后视为只读;构建失败返回 null,不消耗随机数。
|
||||
/// </summary>
|
||||
private static BattleStartRequest BuildStartRequest(BattleEncounter encounter)
|
||||
{
|
||||
IRunRandomSource random = GameEntry.Random != null ? GameEntry.Random.Random : null;
|
||||
if (random == null)
|
||||
{
|
||||
Log.Warning("Can not start battle because run random source is not initialized.");
|
||||
return null;
|
||||
}
|
||||
|
||||
List<BattlePlayerInput> players = new List<BattlePlayerInput>();
|
||||
foreach (RunPlayerState state in GameEntry.TurnBattle.Players)
|
||||
{
|
||||
players.Add(new BattlePlayerInput(state.CharacterId, state.PartyOrder,
|
||||
state.CurrentHp, state.CurrentMp, state.MaxHp, state.MaxMp,
|
||||
state.Atk, state.Mat, state.Speed,
|
||||
state.AttackActionId, state.SkillActionId));
|
||||
}
|
||||
|
||||
if (players.Count == 0)
|
||||
{
|
||||
Log.Warning("Can not start battle because no run player state exists.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BattleStartRequest(encounter.EncounterId, players, encounter.EnemyPartyConfigId,
|
||||
encounter.IsPreemptive, random);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0146e261d98d40109183900a1d033fc9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,64 @@
|
||||
namespace SepCore.CustomComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// 单局临时角色状态。
|
||||
/// 数值由单局快照构建器在开战前从角色与装备结算得到;战斗期间只读,战斗结束回写当前 HP/MP。
|
||||
/// </summary>
|
||||
public sealed class RunPlayerState
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色配置标识。
|
||||
/// </summary>
|
||||
public int CharacterId;
|
||||
|
||||
/// <summary>
|
||||
/// 当前战备中的顺序,1 开始。
|
||||
/// </summary>
|
||||
public int PartyOrder;
|
||||
|
||||
/// <summary>
|
||||
/// 当前 HP。
|
||||
/// </summary>
|
||||
public int CurrentHp;
|
||||
|
||||
/// <summary>
|
||||
/// 当前 MP。
|
||||
/// </summary>
|
||||
public int CurrentMp;
|
||||
|
||||
/// <summary>
|
||||
/// 已结算装备加成的最终 HP 上限。
|
||||
/// </summary>
|
||||
public int MaxHp;
|
||||
|
||||
/// <summary>
|
||||
/// 已结算装备加成的最终 MP 上限。
|
||||
/// </summary>
|
||||
public int MaxMp;
|
||||
|
||||
/// <summary>
|
||||
/// 已结算装备加成的最终攻击。
|
||||
/// </summary>
|
||||
public int Atk;
|
||||
|
||||
/// <summary>
|
||||
/// 已结算装备加成的最终魔力。
|
||||
/// </summary>
|
||||
public int Mat;
|
||||
|
||||
/// <summary>
|
||||
/// 已结算装备加成的最终速度。
|
||||
/// </summary>
|
||||
public int Speed;
|
||||
|
||||
/// <summary>
|
||||
/// 普通攻击行动配置 ID。
|
||||
/// </summary>
|
||||
public int AttackActionId;
|
||||
|
||||
/// <summary>
|
||||
/// 该角色首版唯一技能行动配置 ID。
|
||||
/// </summary>
|
||||
public int SkillActionId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3caaef559cc641769b9ede0b4d033532
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,136 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace SepCore.CustomComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// 单局战斗组件。
|
||||
/// 负责本局临时角色状态、计时、探索暂停标志和战斗占用的最小状态。
|
||||
/// 本局种子与共享随机源由独立的 RandomComponent 持有,战斗只沿用,不创建私有随机源。
|
||||
/// 战斗协调(构建请求、打开 UI、回写结果)由独立的 RunBattleCoordinator 负责。
|
||||
/// </summary>
|
||||
public class TurnBattleComponent : GameFrameworkComponent
|
||||
{
|
||||
private readonly List<RunPlayerState> _players = new List<RunPlayerState>();
|
||||
private float _elapsedMs;
|
||||
private bool _timerPaused;
|
||||
private bool _explorationPaused;
|
||||
private bool _battleActive;
|
||||
|
||||
/// <summary>
|
||||
/// 获取本局临时角色状态列表(只读)。
|
||||
/// </summary>
|
||||
public IReadOnlyList<RunPlayerState> Players => _players;
|
||||
|
||||
/// <summary>
|
||||
/// 获取本局已流逝时间(毫秒)。
|
||||
/// </summary>
|
||||
public long RunElapsedMs => (long)_elapsedMs;
|
||||
|
||||
/// <summary>
|
||||
/// 获取单局计时是否已暂停。
|
||||
/// </summary>
|
||||
public bool IsTimerPaused => _timerPaused;
|
||||
|
||||
/// <summary>
|
||||
/// 获取探索更新是否已暂停。
|
||||
/// 战斗期间为 true,地图更新统一以此作为门禁。
|
||||
/// </summary>
|
||||
public bool IsExplorationPaused => _explorationPaused;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否存在战斗占用。
|
||||
/// 战斗重复进入由此标志拒绝。
|
||||
/// </summary>
|
||||
public bool IsBattleActive => _battleActive;
|
||||
|
||||
/// <summary>
|
||||
/// 开始一局新的单局:重置全部临时状态。
|
||||
/// </summary>
|
||||
public void BeginRun()
|
||||
{
|
||||
_players.Clear();
|
||||
_elapsedMs = 0;
|
||||
_timerPaused = false;
|
||||
_explorationPaused = false;
|
||||
_battleActive = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结束当前单局,清空全部临时状态。
|
||||
/// </summary>
|
||||
public void EndRun()
|
||||
{
|
||||
_players.Clear();
|
||||
_elapsedMs = 0;
|
||||
_timerPaused = false;
|
||||
_explorationPaused = false;
|
||||
_battleActive = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用指定状态替换本局临时角色列表,保持战备顺序。
|
||||
/// </summary>
|
||||
/// <param name="players">新的临时角色状态,可为空。</param>
|
||||
public void ReplacePlayers(IEnumerable<RunPlayerState> players)
|
||||
{
|
||||
_players.Clear();
|
||||
if (players != null)
|
||||
{
|
||||
_players.AddRange(players);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 预留战斗占用,拒绝同一帧或未结束战斗的重复进入。
|
||||
/// </summary>
|
||||
/// <returns>预留成功返回 true;已存在战斗占用时返回 false。</returns>
|
||||
public bool TryReserveBattle()
|
||||
{
|
||||
if (_battleActive)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_battleActive = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放战斗占用。
|
||||
/// </summary>
|
||||
public void ReleaseBattle()
|
||||
{
|
||||
_battleActive = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置探索更新暂停状态。
|
||||
/// </summary>
|
||||
/// <param name="paused">是否暂停探索更新。</param>
|
||||
public void SetExplorationPaused(bool paused)
|
||||
{
|
||||
_explorationPaused = paused;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置单局计时暂停状态。
|
||||
/// </summary>
|
||||
/// <param name="paused">是否暂停单局计时。</param>
|
||||
public void SetTimerPaused(bool paused)
|
||||
{
|
||||
_timerPaused = paused;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_timerPaused)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_elapsedMs += Time.deltaTime * 1000f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ceac378d38794bafb31a203753565121
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,144 @@
|
||||
using GameFramework.Debugger;
|
||||
using SepCore.Battle;
|
||||
using SepCore.CustomComponent;
|
||||
using SepCore.Definition;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace SepCore.Debugger
|
||||
{
|
||||
/// <summary>
|
||||
/// 战斗壳层调试入口(仅供 Editor/Development Build,正式构建不注册)。
|
||||
/// 模拟探索层:缺少单局状态时用配表角色与固定种子初始化临时状态,
|
||||
/// 然后通过 RunBattleCoordinator 以固定调试遭遇开始/结束战斗,不依赖尚未实现的地图敌人。
|
||||
/// </summary>
|
||||
public class BattleDebuggerWindow : IDebuggerWindow
|
||||
{
|
||||
private const int DebugRunSeed = 240829;
|
||||
|
||||
public void Initialize(params object[] args)
|
||||
{
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnEnter()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnLeave()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnDraw()
|
||||
{
|
||||
GUILayout.Label("<b>Battle Shell</b>");
|
||||
GUILayout.BeginHorizontal("box");
|
||||
{
|
||||
if (GUILayout.Button("Open", GUILayout.Height(30)))
|
||||
{
|
||||
OpenShell();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Close", GUILayout.Height(30)))
|
||||
{
|
||||
GameEntry.RunBattle.EndDebugBattle();
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
DrawRunState();
|
||||
}
|
||||
|
||||
private static void DrawRunState()
|
||||
{
|
||||
GUILayout.Label("<b>Run State</b>");
|
||||
GUILayout.BeginVertical("box");
|
||||
{
|
||||
TurnBattleComponent runBattle = GameEntry.TurnBattle;
|
||||
if (runBattle == null)
|
||||
{
|
||||
GUILayout.Label("TurnBattleComponent missing.");
|
||||
return;
|
||||
}
|
||||
|
||||
GUILayout.Label("RunElapsedMs: " + runBattle.RunElapsedMs.ToString());
|
||||
GUILayout.Label("IsTimerPaused: " + runBattle.IsTimerPaused);
|
||||
GUILayout.Label("IsExplorationPaused: " + runBattle.IsExplorationPaused);
|
||||
GUILayout.Label("IsBattleActive: " + runBattle.IsBattleActive);
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private static void OpenShell()
|
||||
{
|
||||
InitDebugRunStateIfNeeded();
|
||||
|
||||
// 固定调试遭遇:EncounterId=1,敌人队伍预设 1,普通战斗
|
||||
bool started = GameEntry.RunBattle.TryStartBattle(new BattleEncounter(1, 1, false));
|
||||
if (!started)
|
||||
{
|
||||
Log.Warning("Battle debug shell can not start, maybe a battle is already active.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void InitDebugRunStateIfNeeded()
|
||||
{
|
||||
if (GameEntry.Random.Random == null)
|
||||
{
|
||||
GameEntry.Random.BeginRun(DebugRunSeed);
|
||||
}
|
||||
|
||||
if (GameEntry.TurnBattle.Players.Count == 0)
|
||||
{
|
||||
GameEntry.TurnBattle.ReplacePlayers(BuildDebugPlayers());
|
||||
}
|
||||
}
|
||||
|
||||
private static List<RunPlayerState> BuildDebugPlayers()
|
||||
{
|
||||
List<RunPlayerState> players = new List<RunPlayerState>();
|
||||
GlobalConfig global = GameEntry.Luban.Global != null ? GameEntry.Luban.Global.Data : null;
|
||||
if (global == null)
|
||||
{
|
||||
Log.Warning("Can not build debug players without global config.");
|
||||
return players;
|
||||
}
|
||||
|
||||
int order = 1;
|
||||
foreach (int characterId in global.NewGameCharacterIds)
|
||||
{
|
||||
CharacterConfig config = GameEntry.Luban.Get<CharacterConfig>(characterId);
|
||||
if (config == null)
|
||||
{
|
||||
Log.Warning("Debug character '{0}' missing in config, skipped.", characterId.ToString());
|
||||
continue;
|
||||
}
|
||||
|
||||
players.Add(new RunPlayerState
|
||||
{
|
||||
CharacterId = characterId,
|
||||
PartyOrder = order++,
|
||||
CurrentHp = config.MaxHp,
|
||||
CurrentMp = config.MaxMp,
|
||||
MaxHp = config.MaxHp,
|
||||
MaxMp = config.MaxMp,
|
||||
Atk = config.Atk,
|
||||
Mat = config.Mat,
|
||||
Speed = config.Speed,
|
||||
AttackActionId = config.AttackActionId,
|
||||
SkillActionId = config.SkillActionId,
|
||||
});
|
||||
}
|
||||
|
||||
return players;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0024ae1248654a28b520630f8c4a149b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d85f3e1b668434911af528b43e3ad41f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace SepCore.UI
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class BattleActorCardItem : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private GameObject activeMarker;
|
||||
[SerializeField] private Image icon;
|
||||
[SerializeField] private Image hpFill;
|
||||
[SerializeField] private Image mpFill;
|
||||
[SerializeField] private TextMeshProUGUI characterName;
|
||||
[SerializeField] private TextMeshProUGUI hpText;
|
||||
[SerializeField] private TextMeshProUGUI mpText;
|
||||
|
||||
public GameObject ActiveMarker => activeMarker;
|
||||
public Image Icon => icon;
|
||||
public Image HpFill => hpFill;
|
||||
public Image MpFill => mpFill;
|
||||
public TextMeshProUGUI CharacterName => characterName;
|
||||
public TextMeshProUGUI HpText => hpText;
|
||||
public TextMeshProUGUI MpText => mpText;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public void ConfigureEditor(GameObject marker, Image iconImage, Image hpFillImage, Image mpFillImage,
|
||||
TextMeshProUGUI nameLabel, TextMeshProUGUI hpLabel, TextMeshProUGUI mpLabel)
|
||||
{
|
||||
activeMarker = marker;
|
||||
icon = iconImage;
|
||||
hpFill = hpFillImage;
|
||||
mpFill = mpFillImage;
|
||||
characterName = nameLabel;
|
||||
hpText = hpLabel;
|
||||
mpText = mpLabel;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 084cbeaade555447b83112b1c528e263
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace SepCore.UI
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class BattleEnemySlotItem : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Button targetButton;
|
||||
[SerializeField] private GameObject selectedMarker;
|
||||
[SerializeField] private Image icon;
|
||||
[SerializeField] private Image hpFill;
|
||||
[SerializeField] private TextMeshProUGUI enemyName;
|
||||
[SerializeField] private TextMeshProUGUI hpText;
|
||||
|
||||
public Button TargetButton => targetButton;
|
||||
public GameObject SelectedMarker => selectedMarker;
|
||||
public Image Icon => icon;
|
||||
public Image HpFill => hpFill;
|
||||
public TextMeshProUGUI EnemyName => enemyName;
|
||||
public TextMeshProUGUI HpText => hpText;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public void ConfigureEditor(Button button, GameObject marker, Image iconImage, Image hpFillImage,
|
||||
TextMeshProUGUI nameLabel, TextMeshProUGUI hpLabel)
|
||||
{
|
||||
targetButton = button;
|
||||
selectedMarker = marker;
|
||||
icon = iconImage;
|
||||
hpFill = hpFillImage;
|
||||
enemyName = nameLabel;
|
||||
hpText = hpLabel;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8efb693fb8da3406ea05e81cb56a6c20
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,64 @@
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace SepCore.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 战斗界面逻辑(手写 partial,与自动生成的 BattleForm.cs 合并)。
|
||||
/// 首版为半透明壳层:只负责展示快照、收集指令与占位反馈;
|
||||
/// 不修改战斗数据、不读取存档、不决定掉落,也不承担暂停地图计时等流程职责。
|
||||
/// 暂停地图更新由单局流程(RunBattleCoordinator / 探索层)负责。
|
||||
/// </summary>
|
||||
public partial class BattleForm : UGuiForm
|
||||
{
|
||||
protected override void OnInit(object userData)
|
||||
{
|
||||
base.OnInit(userData);
|
||||
|
||||
View.attackButton.onClick.AddListener(OnAttackButtonClick);
|
||||
View.skillButton.onClick.AddListener(OnSkillButtonClick);
|
||||
View.itemButton.onClick.AddListener(OnItemButtonClick);
|
||||
View.escapeButton.onClick.AddListener(OnEscapeButtonClick);
|
||||
|
||||
// 道具行动保留但首版禁用,不产生 BattleCommand
|
||||
View.itemButton.interactable = false;
|
||||
}
|
||||
|
||||
protected override void OnOpen(object userData)
|
||||
{
|
||||
base.OnOpen(userData);
|
||||
|
||||
View.currentActorText.text = string.Empty;
|
||||
Log.Info("BattleForm opened as empty shell.");
|
||||
}
|
||||
|
||||
protected override void OnClose(bool isShutdown, object userData)
|
||||
{
|
||||
View.attackButton.onClick.RemoveListener(OnAttackButtonClick);
|
||||
View.skillButton.onClick.RemoveListener(OnSkillButtonClick);
|
||||
View.itemButton.onClick.RemoveListener(OnItemButtonClick);
|
||||
View.escapeButton.onClick.RemoveListener(OnEscapeButtonClick);
|
||||
|
||||
base.OnClose(isShutdown, userData);
|
||||
}
|
||||
|
||||
private void OnAttackButtonClick()
|
||||
{
|
||||
Log.Info("BattleForm placeholder: attack clicked.");
|
||||
}
|
||||
|
||||
private void OnSkillButtonClick()
|
||||
{
|
||||
Log.Info("BattleForm placeholder: skill clicked.");
|
||||
}
|
||||
|
||||
private void OnItemButtonClick()
|
||||
{
|
||||
Log.Info("BattleForm placeholder: item clicked.");
|
||||
}
|
||||
|
||||
private void OnEscapeButtonClick()
|
||||
{
|
||||
Log.Info("BattleForm placeholder: escape clicked.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 090bea621c85436a8cbe0ad03aa94ebb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
// <auto-generated />
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.UI
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public partial class BattleForm : UGuiForm
|
||||
{
|
||||
[SerializeField] private BattleView _view = null;
|
||||
|
||||
public BattleView View => _view;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9442df9c5d6124f96a96de27d76fc7e3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,27 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace SepCore.UI
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class BattleTurnSlotItem : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private GameObject activeMarker;
|
||||
[SerializeField] private Image icon;
|
||||
[SerializeField] private TextMeshProUGUI label;
|
||||
|
||||
public GameObject ActiveMarker => activeMarker;
|
||||
public Image Icon => icon;
|
||||
public TextMeshProUGUI Label => label;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public void ConfigureEditor(GameObject marker, Image iconImage, TextMeshProUGUI slotLabel)
|
||||
{
|
||||
activeMarker = marker;
|
||||
icon = iconImage;
|
||||
label = slotLabel;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8703508b1b1894f67aeb402ef5b11113
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
// <auto-generated />
|
||||
using UnityEngine;
|
||||
|
||||
namespace SepCore.UI
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public partial class BattleView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] public global::UnityEngine.RectTransform turnSlotsRoot;
|
||||
[SerializeField] public global::SepCore.UI.BattleTurnSlotItem battleTurnSlotTemplate;
|
||||
[SerializeField] public global::SepCore.UI.BattleActorCardItem playerCard1;
|
||||
[SerializeField] public global::SepCore.UI.BattleActorCardItem playerCard2;
|
||||
[SerializeField] public global::SepCore.UI.BattleActorCardItem playerCard3;
|
||||
[SerializeField] public global::SepCore.UI.BattleActorCardItem playerCard4;
|
||||
[SerializeField] public global::UnityEngine.UI.HorizontalLayoutGroup enemySlotsRoot;
|
||||
[SerializeField] public global::SepCore.UI.BattleEnemySlotItem battleEnemySlotTemplate;
|
||||
[SerializeField] public global::TMPro.TextMeshProUGUI currentActorText;
|
||||
[SerializeField] public global::UnityEngine.UI.Button attackButton;
|
||||
[SerializeField] public global::UnityEngine.UI.Button skillButton;
|
||||
[SerializeField] public global::UnityEngine.UI.Button itemButton;
|
||||
[SerializeField] public global::UnityEngine.UI.Button escapeButton;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da8c2ddc0d704496db8c9d57244ecca9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,8 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using SepCore.Base;
|
||||
using SepCore.Definition;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace SepCore.UI
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c182b185d9a14720941276dac40c806
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -803,7 +803,7 @@ RectTransform:
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0, y: 0}
|
||||
|
||||
@@ -0,0 +1,745 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &315737946333849545
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1032890010353872394}
|
||||
- component: {fileID: 8743427817136059705}
|
||||
- component: {fileID: 3448128685652061419}
|
||||
- component: {fileID: 6548688303832215691}
|
||||
m_Layer: 5
|
||||
m_Name: Value
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &1032890010353872394
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 315737946333849545}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 4530832911368872301}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: -12, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &8743427817136059705
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 315737946333849545}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &3448128685652061419
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 315737946333849545}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 6566a3a7cfd184008ab2282feea295f8, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
key:
|
||||
--- !u!114 &6548688303832215691
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 315737946333849545}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: 36 / 50
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 99d811b0183246646a2ce8df996f4bca, type: 2}
|
||||
m_sharedMaterial: {fileID: -1106088975554028259, guid: 99d811b0183246646a2ce8df996f4bca, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4294375672
|
||||
m_fontColor: {r: 0.972549, g: 0.972549, b: 0.9647059, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 24
|
||||
m_fontSizeBase: 24
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 0
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 72
|
||||
m_fontStyle: 1
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 0
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 1
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 1
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_IsTextObjectScaleStatic: 0
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
--- !u!1 &2872746999120331172
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4195264261407777418}
|
||||
- component: {fileID: 14304438620706751}
|
||||
- component: {fileID: 5125136592895039868}
|
||||
m_Layer: 5
|
||||
m_Name: Icon
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &4195264261407777418
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2872746999120331172}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 66654602858681116}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: -30}
|
||||
m_SizeDelta: {x: -80, y: 240}
|
||||
m_Pivot: {x: 0.5, y: 1}
|
||||
--- !u!222 &14304438620706751
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2872746999120331172}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &5125136592895039868
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2872746999120331172}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: -1464279840, guid: ab4b4c43d441f4ad082dc06434e19433, type: 3}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 1
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &2961341881146330160
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1319420703214429996}
|
||||
- component: {fileID: 7220939995580395683}
|
||||
- component: {fileID: 408182180496962794}
|
||||
m_Layer: 5
|
||||
m_Name: Fill
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &1319420703214429996
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2961341881146330160}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 4530832911368872301}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0.72, y: 1}
|
||||
m_AnchoredPosition: {x: 1.5, y: 0}
|
||||
m_SizeDelta: {x: -3, y: -6}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &7220939995580395683
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2961341881146330160}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &408182180496962794
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2961341881146330160}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.7490196, g: 0.27058825, b: 0.25490198, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &3834659533609917762
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4530832911368872301}
|
||||
- component: {fileID: 1956709582553251687}
|
||||
- component: {fileID: 3146990881720930154}
|
||||
m_Layer: 5
|
||||
m_Name: HPBar
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &4530832911368872301
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3834659533609917762}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 1319420703214429996}
|
||||
- {fileID: 1032890010353872394}
|
||||
m_Father: {fileID: 66654602858681116}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: -350}
|
||||
m_SizeDelta: {x: -80, y: 40}
|
||||
m_Pivot: {x: 0.5, y: 1}
|
||||
--- !u!222 &1956709582553251687
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3834659533609917762}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &3146990881720930154
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3834659533609917762}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.09803922, g: 0.09411765, b: 0.11764706, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &5757760741501601689
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 66654602858681116}
|
||||
- component: {fileID: 8915804054968323550}
|
||||
- component: {fileID: 3919750086208422527}
|
||||
- component: {fileID: 3962122869308545550}
|
||||
- component: {fileID: 7014130549974408270}
|
||||
m_Layer: 5
|
||||
m_Name: BattleEnemySlotItem
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &66654602858681116
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5757760741501601689}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 7180864886869484082}
|
||||
- {fileID: 4195264261407777418}
|
||||
- {fileID: 8288357573332182777}
|
||||
- {fileID: 4530832911368872301}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 320, y: 450}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &8915804054968323550
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5757760741501601689}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &3919750086208422527
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5757760741501601689}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.35686275, g: 0.15294118, b: 0.21176471, a: 0.68}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &3962122869308545550
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5757760741501601689}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 1
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Selected
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 3919750086208422527}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!114 &7014130549974408270
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5757760741501601689}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 8efb693fb8da3406ea05e81cb56a6c20, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
targetButton: {fileID: 3962122869308545550}
|
||||
selectedMarker: {fileID: 5786947224843219099}
|
||||
icon: {fileID: 5125136592895039868}
|
||||
hpFill: {fileID: 408182180496962794}
|
||||
enemyName: {fileID: 2436887621614352063}
|
||||
hpText: {fileID: 6548688303832215691}
|
||||
--- !u!1 &5786947224843219099
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 7180864886869484082}
|
||||
- component: {fileID: 3180979755225221759}
|
||||
- component: {fileID: 1991837413665372884}
|
||||
- component: {fileID: 638193206132314291}
|
||||
m_Layer: 5
|
||||
m_Name: SelectedMarker
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &7180864886869484082
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5786947224843219099}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 66654602858681116}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 16, y: 16}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &3180979755225221759
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5786947224843219099}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &1991837413665372884
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5786947224843219099}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0, g: 0, b: 0, a: 0}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &638193206132314291
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 5786947224843219099}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: e19747de3f5aca642ab2be37e372fb86, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_EffectColor: {r: 0.85490197, g: 0.6627451, b: 0.2509804, a: 1}
|
||||
m_EffectDistance: {x: 4, y: -4}
|
||||
m_UseGraphicAlpha: 0
|
||||
--- !u!1 &8891393653902315856
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 8288357573332182777}
|
||||
- component: {fileID: 6990201675971539953}
|
||||
- component: {fileID: 2436887621614352063}
|
||||
m_Layer: 5
|
||||
m_Name: EnemyName
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &8288357573332182777
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8891393653902315856}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 66654602858681116}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 1}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: -330}
|
||||
m_SizeDelta: {x: -80, y: 40}
|
||||
m_Pivot: {x: 0.5, y: 0}
|
||||
--- !u!222 &6990201675971539953
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8891393653902315856}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &2436887621614352063
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 8891393653902315856}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: "\u654C\u4EBA 1"
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 99d811b0183246646a2ce8df996f4bca, type: 2}
|
||||
m_sharedMaterial: {fileID: -1106088975554028259, guid: 99d811b0183246646a2ce8df996f4bca, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4294375672
|
||||
m_fontColor: {r: 0.972549, g: 0.972549, b: 0.9647059, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 30
|
||||
m_fontSizeBase: 30
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 0
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 72
|
||||
m_fontStyle: 1
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 0
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 1
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 1
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_IsTextObjectScaleStatic: 0
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 37620799d96fb400b8015d9b961f65ea
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,441 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &50573898148948422
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4729178116724390878}
|
||||
- component: {fileID: 71101594289859535}
|
||||
- component: {fileID: 9222076356080591541}
|
||||
- component: {fileID: 7540689783192064037}
|
||||
m_Layer: 5
|
||||
m_Name: ActiveMarker
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &4729178116724390878
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 50573898148948422}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 3288665624823021036}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 4, y: 4}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &71101594289859535
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 50573898148948422}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &9222076356080591541
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 50573898148948422}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0, g: 0, b: 0, a: 0}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &7540689783192064037
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 50573898148948422}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: e19747de3f5aca642ab2be37e372fb86, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_EffectColor: {r: 0.85490197, g: 0.6627451, b: 0.2509804, a: 1}
|
||||
m_EffectDistance: {x: 4, y: -4}
|
||||
m_UseGraphicAlpha: 0
|
||||
--- !u!1 &1070990104557590534
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 6913906517704956117}
|
||||
- component: {fileID: 304895294419268740}
|
||||
- component: {fileID: 4832355492385208460}
|
||||
m_Layer: 5
|
||||
m_Name: BattleTurnSlotItem
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &6913906517704956117
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1070990104557590534}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 3288665624823021036}
|
||||
- {fileID: 4632523145797722141}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 140, y: 200}
|
||||
m_Pivot: {x: 0, y: 0.5}
|
||||
--- !u!222 &304895294419268740
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1070990104557590534}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &4832355492385208460
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1070990104557590534}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 8703508b1b1894f67aeb402ef5b11113, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
activeMarker: {fileID: 50573898148948422}
|
||||
icon: {fileID: 8946722496830791082}
|
||||
label: {fileID: 8975591190078523951}
|
||||
--- !u!1 &1648080089057891741
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4632523145797722141}
|
||||
- component: {fileID: 7720048289083606538}
|
||||
- component: {fileID: 8975591190078523951}
|
||||
m_Layer: 5
|
||||
m_Name: Label
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &4632523145797722141
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1648080089057891741}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 6913906517704956117}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: -10}
|
||||
m_SizeDelta: {x: 0, y: 40}
|
||||
m_Pivot: {x: 0.5, y: 0}
|
||||
--- !u!222 &7720048289083606538
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1648080089057891741}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &8975591190078523951
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1648080089057891741}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_text: P4
|
||||
m_isRightToLeft: 0
|
||||
m_fontAsset: {fileID: 11400000, guid: 99d811b0183246646a2ce8df996f4bca, type: 2}
|
||||
m_sharedMaterial: {fileID: -1106088975554028259, guid: 99d811b0183246646a2ce8df996f4bca, type: 2}
|
||||
m_fontSharedMaterials: []
|
||||
m_fontMaterial: {fileID: 0}
|
||||
m_fontMaterials: []
|
||||
m_fontColor32:
|
||||
serializedVersion: 2
|
||||
rgba: 4280493355
|
||||
m_fontColor: {r: 0.16862746, g: 0.14509805, b: 0.13725491, a: 1}
|
||||
m_enableVertexGradient: 0
|
||||
m_colorMode: 3
|
||||
m_fontColorGradient:
|
||||
topLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
topRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomLeft: {r: 1, g: 1, b: 1, a: 1}
|
||||
bottomRight: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_fontColorGradientPreset: {fileID: 0}
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
rgba: 4294967295
|
||||
m_fontSize: 30
|
||||
m_fontSizeBase: 30
|
||||
m_fontWeight: 400
|
||||
m_enableAutoSizing: 0
|
||||
m_fontSizeMin: 18
|
||||
m_fontSizeMax: 72
|
||||
m_fontStyle: 1
|
||||
m_HorizontalAlignment: 2
|
||||
m_VerticalAlignment: 512
|
||||
m_textAlignment: 65535
|
||||
m_characterSpacing: 0
|
||||
m_wordSpacing: 0
|
||||
m_lineSpacing: 0
|
||||
m_lineSpacingMax: 0
|
||||
m_paragraphSpacing: 0
|
||||
m_charWidthMaxAdj: 0
|
||||
m_enableWordWrapping: 0
|
||||
m_wordWrappingRatios: 0.4
|
||||
m_overflowMode: 1
|
||||
m_linkedTextComponent: {fileID: 0}
|
||||
parentLinkedComponent: {fileID: 0}
|
||||
m_enableKerning: 1
|
||||
m_enableExtraPadding: 0
|
||||
checkPaddingRequired: 0
|
||||
m_isRichText: 1
|
||||
m_parseCtrlCharacters: 1
|
||||
m_isOrthographic: 1
|
||||
m_isCullingEnabled: 0
|
||||
m_horizontalMapping: 0
|
||||
m_verticalMapping: 0
|
||||
m_uvLineOffset: 0
|
||||
m_geometrySortingOrder: 0
|
||||
m_IsTextObjectScaleStatic: 0
|
||||
m_VertexBufferAutoSizeReduction: 0
|
||||
m_useMaxVisibleDescender: 1
|
||||
m_pageToDisplay: 1
|
||||
m_margin: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_isUsingLegacyAnimationComponent: 0
|
||||
m_isVolumetricText: 0
|
||||
m_hasFontAssetChanged: 0
|
||||
m_baseMaterial: {fileID: 0}
|
||||
m_maskOffset: {x: 0, y: 0, z: 0, w: 0}
|
||||
--- !u!1 &2984359946857549121
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 3288665624823021036}
|
||||
- component: {fileID: 8759304451656729876}
|
||||
- component: {fileID: 7956995434306964309}
|
||||
m_Layer: 5
|
||||
m_Name: bg
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &3288665624823021036
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2984359946857549121}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 4729178116724390878}
|
||||
- {fileID: 2234951399611286210}
|
||||
m_Father: {fileID: 6913906517704956117}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 15}
|
||||
m_SizeDelta: {x: 0, y: -50}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &8759304451656729876
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2984359946857549121}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &7956995434306964309
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2984359946857549121}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.16470589, g: 0.15294118, b: 0.18431373, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 0}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 0
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!1 &4853830557563889511
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 2234951399611286210}
|
||||
- component: {fileID: 764635409290338171}
|
||||
- component: {fileID: 8946722496830791082}
|
||||
m_Layer: 5
|
||||
m_Name: Icon
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &2234951399611286210
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4853830557563889511}
|
||||
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 3288665624823021036}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &764635409290338171
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4853830557563889511}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &8946722496830791082
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4853830557563889511}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: -126584339, guid: 8c4b5efcfed4c45fabe8458836ccd4c3, type: 3}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 1
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa6822449b9304c168a5ba43072f1658
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+91
-1
@@ -154,8 +154,98 @@ Transform:
|
||||
- {fileID: 513208573}
|
||||
- {fileID: 2198765432}
|
||||
- {fileID: 519191920}
|
||||
- {fileID: 342367578}
|
||||
- {fileID: 342367581}
|
||||
m_Father: {fileID: 1852670053}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &342367577
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 342367578}
|
||||
- component: {fileID: 342367579}
|
||||
m_Layer: 0
|
||||
m_Name: Turn Battle
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &342367578
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 342367577}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 119167776}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &342367579
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 342367577}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: ceac378d38794bafb31a203753565121, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
--- !u!1 &342367580
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 342367581}
|
||||
- component: {fileID: 342367582}
|
||||
m_Layer: 0
|
||||
m_Name: Random
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &342367581
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 342367580}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 119167776}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &342367582
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 342367580}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 373a779ff43a4289b8e8cb23459d7bb3, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
--- !u!1001 &343730742
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -876,7 +966,7 @@ MonoBehaviour:
|
||||
m_UiScaleMode: 1
|
||||
m_ReferencePixelsPerUnit: 100
|
||||
m_ScaleFactor: 1
|
||||
m_ReferenceResolution: {x: 2560, y: 1600}
|
||||
m_ReferenceResolution: {x: 2560, y: 1440}
|
||||
m_ScreenMatchMode: 0
|
||||
m_MatchWidthOrHeight: 0
|
||||
m_PhysicalUnit: 3
|
||||
|
||||
@@ -0,0 +1,727 @@
|
||||
# 回合制战斗开发 TODO
|
||||
|
||||
> 状态:进行中(M0 组件已落地,其余里程碑未开始)
|
||||
>
|
||||
> 创建日期:2026-08-31
|
||||
>
|
||||
> 相关设计:`Docs/GameDesign/04_TurnBasedCombat.md`、`Docs/GameDesign/03_RunExploration.md`、`Docs/GameDesign/06_PrototypeScope.md`
|
||||
>
|
||||
> 目标:在不新增 Battle 场景的前提下,实现覆盖在地图上的半透明多对多回合制战斗 UI,以及可独立测试的战斗规则内核。
|
||||
|
||||
## 一、完成纪律
|
||||
|
||||
- 里程碑是否完成只看标题中的 `[ ]`;第八节的复选框单独追踪 22 条设计验收,不代替里程碑完成状态。
|
||||
- 代码写完、静态检查通过或 Unity 编译通过,都不能单独把里程碑改为 `[x]`。
|
||||
- 只有完成该里程碑列出的 Unity 编译、EditMode 测试和 Play Mode 可见验收,并记录证据后,才能改为 `[x]`。
|
||||
- 每次只实现当前里程碑。本里程碑“明确不做”的内容不能因为顺手而提前并入。
|
||||
- 生成代码不能手改。Luban 结构或数据需要调整时,修改源表后运行 `数据表/gen_cli.sh`。
|
||||
- 战斗内核不得直接读写 `SaveData`,不得直接操作地图敌人物体、掉落物或局外结算。
|
||||
- 本文档当前所有里程碑均为未完成;本文档落地不代表任何代码或运行时验收已经完成。
|
||||
|
||||
每个里程碑完成后按以下格式补证据:
|
||||
|
||||
```text
|
||||
代码状态:未开始 / 进行中 / 已完成
|
||||
Unity 编译:未验证 / 通过(日期、日志或截图)
|
||||
EditMode:未验证 / 通过(用例数量、结果)
|
||||
Play Mode:未验证 / 通过(操作步骤、可见结果、截图或录像)
|
||||
提交或差异:commit / diff 路径
|
||||
剩余问题:无 / 具体问题
|
||||
```
|
||||
|
||||
## 二、总体进度
|
||||
|
||||
| 里程碑 | 可见结果 | 当前状态 |
|
||||
| --- | --- | --- |
|
||||
| M0 接口与调试入口 | Main 场景可打开和关闭半透明战斗壳层,地图更新暂停 | 已完成 |
|
||||
| M1 1v1 攻击闭环 | 玩家攻击、敌人反击并产生胜负 | 未开始 |
|
||||
| M2 4v4 回合调度 | 多人队伍按实时速度和并列规则行动 | 未开始 |
|
||||
| M3 技能、目标与效果 | 数值型攻击和技能可合法选目标并结算 HP/MP | 未开始 |
|
||||
| M4 状态与速度变化 | 眩晕跳过下一次行动,速度变化立即刷新顺序 | 未开始 |
|
||||
| M5 先制与确定性随机 | 先制首轮和普通次轮正确,同一随机输入可复现 | 未开始 |
|
||||
| M6 单人逃跑与结束矩阵 | 成功、失败、全逃跑、部分逃跑后阵亡均可见 | 未开始 |
|
||||
| M7 单局状态回写 | HP/MP、阵亡恢复和战斗状态清理正确回到临时单局状态 | 未开始 |
|
||||
| M8 探索遭遇接入 | 碰撞进入战斗,胜利移除敌人,逃跑恢复敌人并获得保护 | 未开始 |
|
||||
| M9 战斗 UI 完成 | 最小信息、输入限制和即时反馈完整 | 未开始 |
|
||||
| M10 全量回归 | 战斗文档 22 条验收全部留有运行证据 | 未开始 |
|
||||
|
||||
### 验证记录
|
||||
|
||||
| 里程碑 | 代码 | Unity 编译 | EditMode | Play Mode | 证据 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| M0 | 已完成 | 通过(2026-08-31,dotnet build SepCore.Runtime.csproj 0 错误;Unity 编辑器内 Play Mode 实测编译通过) | 不适用(M0 无 EditMode 用例) | 通过(2026-08-31,见 M0 证据) | 已记录(M0 小节) |
|
||||
| M1 | 未开始 | 未验证 | 未验证 | 未验证 | 未记录 |
|
||||
| M2 | 未开始 | 未验证 | 未验证 | 未验证 | 未记录 |
|
||||
| M3 | 未开始 | 未验证 | 未验证 | 未验证 | 未记录 |
|
||||
| M4 | 未开始 | 未验证 | 未验证 | 未验证 | 未记录 |
|
||||
| M5 | 未开始 | 未验证 | 未验证 | 未验证 | 未记录 |
|
||||
| M6 | 未开始 | 未验证 | 未验证 | 未验证 | 未记录 |
|
||||
| M7 | 未开始 | 未验证 | 未验证 | 未验证 | 未记录 |
|
||||
| M8 | 未开始 | 未验证 | 未验证 | 未验证 | 未记录 |
|
||||
| M9 | 未开始 | 未验证 | 未验证 | 未验证 | 未记录 |
|
||||
| M10 | 未开始 | 未验证 | 未验证 | 未验证 | 未记录 |
|
||||
|
||||
## 三、已确认的范围与约定
|
||||
|
||||
以下决定于 2026-08-31 确认,后续实现不得自行改变:
|
||||
|
||||
| 项目 | 约定 |
|
||||
| --- | --- |
|
||||
| 单局基础 | 本 TODO 包含最小单局临时状态及其接口,不包含完整地图生成、搜索和警惕 AI 的实现 |
|
||||
| 战斗承载 | 不新增 Battle 场景;在 Main 场景地图上覆盖半透明的顶级 `BattleForm` |
|
||||
| 地图暂停 | 战斗期间暂停地图角色、敌人 AI、碰撞、搜索和单局计时的更新,战斗 UI 继续响应 |
|
||||
| 模块边界 | 战斗接收值快照和本局随机接口,返回结果;不直接读写存档、地图敌人或掉落 |
|
||||
| 眩晕 | `DurationRounds=N` 影响目标未来 N 次行动机会;`Stun(1)` 跳过下一次行动后失效 |
|
||||
| 状态重复施加 | 同类状态不叠层,保留较长的剩余持续次数 |
|
||||
| 友方目标 | `SingleAlly` 包含施法者;`Self` 只能选择施法者自己 |
|
||||
| 阵亡目标 | 阵亡和已逃跑单位不是合法目标;首版没有战斗内复活 |
|
||||
| 战斗表现 | 首版使用面板式即时结算,不做站位动画、攻击动画、VFX、Timeline 或音效同步 |
|
||||
| 操作方式 | 首版使用鼠标点击行动和目标 |
|
||||
| 行动顺序 UI | 显示当前行动者和本轮剩余行动者;速度变化后立即刷新,不预测下一轮 |
|
||||
|
||||
## 四、模块所有权
|
||||
|
||||
### 4.1 目录与程序集
|
||||
|
||||
| 位置 | 所有权 |
|
||||
| --- | --- |
|
||||
| `Assets/GameMain/Scripts/Runtime/Battle/` | 战斗接口与 DTO、纯战斗模型、调度、指令校验、效果结算、敌人策略和 `BattleController` |
|
||||
| `Assets/GameMain/Scripts/Runtime/CustomComponent/Run/` | 最小单局临时状态、探索暂停和战斗协调 |
|
||||
| `Assets/GameMain/Scripts/Runtime/CustomComponent/Random/` | 本局种子与共享随机源(`RandomComponent`、`IRunRandomSource`、`RunRandomSource`) |
|
||||
| `Assets/GameMain/Scripts/UI/Battle/` | `BattleForm`、子 Form/View 和手写 `.Logic.cs` 表现逻辑 |
|
||||
| `Assets/GameMain/Scripts/Base/Event/` | 只有确实需要跨模块广播的 UGF 事件;战斗内部通信不走全局事件总线 |
|
||||
| `Assets/GameMain/Tests/EditMode/Battle/` | 战斗规则的纯逻辑 EditMode 测试及测试程序集 |
|
||||
|
||||
不新增战斗程序集。接口、DTO 和内核均放入现有 `SepCore.Runtime`;`SepCore.Presentation` 已经单向引用 Runtime,Runtime 不得反向引用 Presentation。
|
||||
|
||||
### 4.2 责任划分
|
||||
|
||||
| 模块 | 负责 | 不负责 |
|
||||
| --- | --- | --- |
|
||||
| `RandomComponent` | 本局 seed、共享随机源;地图生成、敌人 AI、逃跑和掉落共用 | 战斗行动规则、地图对象、存档写盘 |
|
||||
| `TurnBattleComponent` | 临时角色状态、计时、战斗占用、探索暂停标志 | 战斗行动规则、战斗 UI、存档写盘 |
|
||||
| `RunBattleCoordinator` | 从单局状态构建请求、打开 UI、提交结果、生成地图恢复计划 | 回合排序、效果计算、UI 控件状态 |
|
||||
| `BattleSession` | 单位、轮次、行动机会、效果、状态、敌人 AI、逃跑和结束判定 | 地图对象、SaveData、UI GameObject |
|
||||
| `BattleController` | 暴露只读快照、接收玩家指令、推进自动行动、产出行动记录和最终结果 | 直接渲染 UI、直接控制地图 |
|
||||
| `BattleForm` | 展示快照、收集玩家指令、即时播放行动记录、展示结束结果 | 修改战斗数据、读取存档、决定掉落 |
|
||||
| 探索适配器 | 碰撞发起遭遇、冻结/恢复地图、敌人移除或复位、掉落、2 秒保护 | 战斗内部规则 |
|
||||
|
||||
### 4.3 调用链
|
||||
|
||||
```text
|
||||
队长与地图敌人碰撞
|
||||
-> 探索层创建 BattleEncounter
|
||||
-> RunBattleCoordinator.TryStartBattle
|
||||
-> TurnBattleComponent 预留战斗占用,拒绝重复遭遇
|
||||
-> RunBattleCoordinator 构建 BattleStartRequest
|
||||
-> 创建并校验 BattleController;失败则释放占用且不暂停地图
|
||||
-> 校验成功后提交战斗占用并暂停探索更新
|
||||
-> 打开 BattleForm,并调用 BattleController.Start
|
||||
-> BattleForm 展示 BattleSnapshot
|
||||
-> 玩家提交 BattleCommand / 敌人策略自动提交行动
|
||||
-> BattleController 返回 BattleAdvance
|
||||
-> BattleForm 即时展示 BattleActionRecord
|
||||
-> BattleSession 产生 BattleResult
|
||||
-> RunBattleCoordinator.ApplyResult
|
||||
-> 更新单局临时角色状态并生成 BattleReturnPlan
|
||||
-> 探索层执行敌人、掉落、保护或单局失败处理
|
||||
-> 关闭 BattleForm,并在允许继续单局时恢复探索更新
|
||||
```
|
||||
|
||||
## 五、接口契约
|
||||
|
||||
本节固定接口语义。实现时允许按 C# 规范调整只读集合或构造函数形式,但不得改变所有权和数据流。
|
||||
|
||||
### 5.1 身份规则
|
||||
|
||||
| 标识 | 用途 |
|
||||
| --- | --- |
|
||||
| `EncounterId` | 地图敌人实例的单局唯一标识;不能使用敌人配置 ID 代替 |
|
||||
| `BattleUnitId` | 本场战斗单位的唯一运行时标识;重复配置的敌人必须拥有不同 ID |
|
||||
| `CharacterId` | 玩家角色配置标识;同时用于把结果回写到单局角色状态 |
|
||||
| `EnemyConfigId` | 敌人种类配置标识;同一敌人队伍内可以重复 |
|
||||
| `PartyOrder` | 玩家取战备顺序,敌人取 `EnemyPartyConfig.EnemyIds` 顺序;作为同阵营同速度的最终并列规则 |
|
||||
|
||||
所有 UI 指令和目标选择都使用 `BattleUnitId`,不能使用可能重复的 `EnemyConfigId`。
|
||||
|
||||
### 5.2 遭遇输入
|
||||
|
||||
`BattleEncounter` 由探索层创建:
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `EncounterId` | 触发碰撞的地图敌人实例 |
|
||||
| `EnemyPartyConfigId` | 该地图敌人代表的敌人队伍预设 |
|
||||
| `IsPreemptive` | 碰撞时敌人警惕值未满为 true,否则为 false |
|
||||
|
||||
地图坐标、碰撞器、GameObject 和警惕组件不进入战斗请求,由探索层继续持有。
|
||||
|
||||
### 5.3 玩家输入快照
|
||||
|
||||
`BattlePlayerInput` 是进入本场战斗时的值快照:
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `CharacterId` | 角色配置 ID |
|
||||
| `PartyOrder` | 当前战备中的顺序 |
|
||||
| `CurrentHp` / `CurrentMp` | 单局临时状态中的当前值 |
|
||||
| `MaxHp` / `MaxMp` | 已结算装备加成的最终上限 |
|
||||
| `Atk` / `Mat` / `Speed` | 已结算装备加成的最终战斗属性 |
|
||||
| `AttackActionId` | 普通攻击行动配置 ID |
|
||||
| `SkillActionId` | 该角色首版唯一技能行动配置 ID |
|
||||
|
||||
装备计算由单局角色快照构建器完成。`BattleSession` 不读取 `CharacterSave`、装备栏或背包。
|
||||
|
||||
### 5.4 战斗启动请求
|
||||
|
||||
`BattleStartRequest` 至少包含:
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `EncounterId` | 用于将结果关联回地图敌人实例 |
|
||||
| `Players` | 1 到 4 个 `BattlePlayerInput`,保持战备顺序 |
|
||||
| `EnemyPartyConfigId` | 用于按队伍预设创建 1 到 4 个敌人运行时单位 |
|
||||
| `IsPreemptive` | 是否启用第一轮玩家先制 |
|
||||
| `Random` | 当前单局唯一的 `IRunRandomSource` 实例,来自 `RandomComponent`,不创建战斗私有随机源 |
|
||||
|
||||
请求在创建后视为只读。战斗开始失败时不得消耗随机数、修改单局状态或暂停状态计数。
|
||||
|
||||
### 5.5 随机数接口
|
||||
|
||||
```csharp
|
||||
public interface IRunRandomSource
|
||||
{
|
||||
int NextInt(int minInclusive, int maxExclusive);
|
||||
bool RollPermille(int successPermille);
|
||||
}
|
||||
```
|
||||
|
||||
- 本局种子由局外(战备界面玩家输入)带入,`RandomComponent.BeginRun(seed)` 初始化共享随机源。
|
||||
- 同一单局的地图生成、敌人 AI、逃跑和敌人掉落依次消费同一个实例;`RandomComponent` 是唯一持有者。
|
||||
- 战斗不能根据 EncounterId、回合数或当前时间重新播种。
|
||||
- `RollPermille(0)` 必须失败,`RollPermille(1000)` 必须成功。
|
||||
- EditMode 测试使用可注入的序列随机源,不依赖 Unity 全局随机状态。
|
||||
|
||||
### 5.6 配置访问接口
|
||||
|
||||
`IBattleConfigProvider` 隔离 `BattleSession` 与 `GameEntry.Luban`。运行时适配器读取现有 Luban 表,测试使用内存配置。
|
||||
|
||||
```csharp
|
||||
public interface IBattleConfigProvider
|
||||
{
|
||||
EnemyPartyConfig GetEnemyParty(int id);
|
||||
EnemyConfig GetEnemy(int id);
|
||||
BattleActionConfig GetAction(int id);
|
||||
GlobalConfig GetGlobal();
|
||||
}
|
||||
```
|
||||
|
||||
配置缺失属于战斗启动失败,不允许用零值单位继续战斗。普通消费路径信任已经导出的合法配表,不在每次行动中重复做全表完整性扫描。
|
||||
|
||||
### 5.7 玩家指令
|
||||
|
||||
`BattleCommand` 至少包含:
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `ActorUnitId` | 当前获得行动机会的玩家单位 |
|
||||
| `CommandType` | `Attack`、`Skill`、`Item` 或 `Escape`;复用配表 `BattleActionType` 枚举,不单独定义 |
|
||||
| `ActionConfigId` | 攻击或技能对应的配置 ID;逃跑为 0 |
|
||||
| `TargetUnitIds` | 按目标类型解析后的唯一运行时目标列表 |
|
||||
|
||||
- `Item` 按钮可见但首版禁用,不产生 `BattleCommand`。
|
||||
- 只有 `BattleSnapshot` 指示的当前玩家单位能提交指令。
|
||||
- MP 不足时技能按钮禁用;控制器仍需拒绝伪造的非法指令。
|
||||
- `SingleAlly` 包含自己;`Self` 必须且只能以自己为目标。
|
||||
- `AllAllies` 包含自己;全体目标由控制器根据当前合法单位展开,UI 不自行拼装。
|
||||
- 阵亡、逃跑和敌对阵营错误的目标均非法。
|
||||
- 非法指令不得消耗 MP、行动机会或随机数。
|
||||
|
||||
### 5.8 只读战斗快照
|
||||
|
||||
`BattleSnapshot` 是 UI 和敌人策略唯一允许读取的战斗状态。每次推进都创建逻辑上的只读快照,不能把可修改的内部列表或 `BattleUnit` 引用暴露给调用方。
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `RoundNumber` | 当前轮次,从 1 开始 |
|
||||
| `IsPreemptiveRound` | 当前是否为先制战斗第一轮 |
|
||||
| `CurrentActorUnitId` | 当前行动者;战斗完成时为空 |
|
||||
| `Units` | 全部单位的只读视图,保持稳定显示顺序 |
|
||||
| `RemainingTurnOrder` | 本轮从当前行动者开始的剩余运行时单位 ID;速度变化后重建 |
|
||||
| `FlowState` | `AwaitingPlayerCommand` 或 `Completed` |
|
||||
|
||||
每个单位视图至少包含 `BattleUnitId`、阵营、配置 ID、`PartyOrder`、当前及上限 HP/MP、ATK、MAT、Speed、是否阵亡、是否逃跑、剩余状态和可用行动 ID。UI 不根据这些字段自行推演下一轮或判定胜负。
|
||||
|
||||
### 5.9 战斗推进返回值
|
||||
|
||||
`BattleController.Start()` 和 `SubmitCommand()` 返回 `BattleAdvance`:
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `Records` | 本次推进产生的有序 `BattleActionRecord` 列表 |
|
||||
| `Snapshot` | 推进结束后的只读战斗快照 |
|
||||
| `FlowState` | `AwaitingPlayerCommand` 或 `Completed` |
|
||||
| `Result` | 仅在 Completed 时存在 |
|
||||
|
||||
`BattleActionRecord` 记录行动者、行动、目标,以及每个效果前后的数值或状态变化。首版 UI 立即消费并显示;未来动画系统可以逐条等待播放,不需要修改战斗规则内核。
|
||||
|
||||
### 5.10 敌人策略接口
|
||||
|
||||
```csharp
|
||||
public interface IEnemyBattlePolicy
|
||||
{
|
||||
BattleCommand Decide(
|
||||
BattleSnapshot snapshot,
|
||||
int actorUnitId,
|
||||
IRunRandomSource random);
|
||||
}
|
||||
```
|
||||
|
||||
- `EnemyAiType.Random` 对当前可用行动做等概率选择,再对合法的单体目标做等概率选择。
|
||||
- MP 不足的行动不进入候选集。
|
||||
- 全体目标不额外随机目标。
|
||||
- 当前没有可执行行动时,本次行动机会记为跳过,不能形成无限循环。
|
||||
- 策略只读取快照并返回指令,不直接修改 `BattleSession`。
|
||||
|
||||
### 5.11 战斗结果
|
||||
|
||||
`BattleOutcome` 固定为以下四种:
|
||||
|
||||
| 结果 | 条件 | 单局是否继续 |
|
||||
| --- | --- | --- |
|
||||
| `Victory` | 所有敌人阵亡 | 是 |
|
||||
| `AllEscaped` | 所有玩家成功逃跑,且没有玩家阵亡 | 是 |
|
||||
| `PartialEscapeDefeat` | 至少一名玩家逃跑,其余仍在战斗中的玩家全部阵亡 | 是 |
|
||||
| `TotalDefeat` | 所有玩家阵亡,没有任何玩家成功逃跑 | 否 |
|
||||
|
||||
`BattleResult` 至少包含:
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `EncounterId` | 关联原地图敌人实例 |
|
||||
| `Outcome` | 上述四种战斗结果之一 |
|
||||
| `Players` | 每名参战玩家的原始战后结果 |
|
||||
|
||||
每个玩家结果包含 `CharacterId`、`CurrentHp`、`CurrentMp`、`WasDefeated`、`Escaped`。战斗结果保留原始战斗值:阵亡者 HP 为 0;恢复到 1 HP/1 MP 的规则由 `RunBattleCoordinator` 在非全员阵亡结果中统一应用。
|
||||
|
||||
### 5.12 探索恢复计划
|
||||
|
||||
`RunBattleCoordinator` 根据 `BattleResult` 生成 `BattleReturnPlan`,探索层只执行计划:
|
||||
|
||||
`BattleReturnPlan` 至少包含 `EncounterId`、`Outcome`、回写后的玩家临时状态、`RemoveEncounterEnemy`、`ResetEncounterEnemy`、`ShouldRollDrops`、`ProtectionMs`、`ShouldResumeExploration` 和 `EndsRunAsDefeated`。这些布尔值由协调器按下表统一产生,探索层不得再次根据玩家 HP 或逃跑数量重判战斗结果。
|
||||
|
||||
| 战斗结果 | 玩家状态 | 触发敌人 | 掉落 | 保护时间 | 单局计时 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| `Victory` | 存活者保留 HP/MP,阵亡者恢复配置的 1/1,清除战斗状态 | 移除 | 使用本局随机源结算 | 无 | 恢复 |
|
||||
| `AllEscaped` | 逃跑时 HP/MP 保留,清除战斗状态 | 保留并恢复地图行为,下次按完整预设重建 | 无 | 配置的 2 秒 | 恢复 |
|
||||
| `PartialEscapeDefeat` | 逃跑者保留 HP/MP,阵亡者恢复配置的 1/1,清除状态 | 保留并恢复地图行为,下次按完整预设重建 | 无 | 配置的 2 秒 | 恢复 |
|
||||
| `TotalDefeat` | 不返回探索 | 保持到单局失败清理 | 无 | 无 | 不恢复,进入失败结算 |
|
||||
|
||||
其他地图敌人在战斗期间被冻结;战斗结束后恢复战前的警惕和追击状态,不重新初始化。
|
||||
|
||||
探索层通过以下最小接口接收恢复计划:
|
||||
|
||||
```csharp
|
||||
public interface IExplorationBattleBridge
|
||||
{
|
||||
void ApplyBattleReturn(BattleReturnPlan plan);
|
||||
}
|
||||
```
|
||||
|
||||
碰撞方主动把 `BattleEncounter` 交给 `RunBattleCoordinator.TryStartBattle`;协调器不扫描场景寻找敌人。`ApplyBattleReturn` 必须按 `EncounterId` 精确操作触发敌人,不能按 `EnemyConfigId` 批量处理同类敌人。
|
||||
|
||||
### 5.13 探索暂停接口
|
||||
|
||||
- `TurnBattleComponent.IsExplorationPaused` 是地图更新的统一门禁。
|
||||
- 玩家移动、队员跟随、敌人巡逻/警惕/追击、搜索进度、地图碰撞触发和单局计时都必须遵守该门禁。
|
||||
- 战斗 UI、战斗内核和使用 unscaled time 的 UI 反馈不受该门禁影响。
|
||||
- 首版不使用切换场景实现暂停。
|
||||
- 不把 `Time.timeScale = 0` 作为战斗模块的唯一暂停机制,避免 UI 和后续表现链被一同停止。
|
||||
- 战斗重复进入由 `TurnBattleComponent` 的战斗占用状态拒绝;同一碰撞帧不得创建多场战斗。
|
||||
|
||||
## 六、固定规则
|
||||
|
||||
### 6.1 下一行动者
|
||||
|
||||
每次行动或跳过结束后,重新从本轮尚未获得行动机会且仍在战斗中的单位中选择:
|
||||
|
||||
1. 先制战斗第一轮仍有玩家未行动时,只在玩家中选择;玩家全部行动后才允许选择敌人。
|
||||
2. 当前速度高者优先。
|
||||
3. 当前速度相同时,玩家优先于敌人。
|
||||
4. 同阵营同速度时,`PartyOrder` 小者优先。
|
||||
5. 已经获得过本轮行动机会的单位不会因速度变化再次行动。
|
||||
6. 候选集为空时开启下一轮;先制限制只适用于第一轮。
|
||||
|
||||
阵亡和逃跑单位不再进入候选集。眩晕单位仍会被选为下一行动者,随后跳过这次机会、标记本轮已行动并消耗一次眩晕持续次数。
|
||||
|
||||
### 6.2 数值与效果
|
||||
|
||||
- 每个 `BattleEffect` 按配置列表顺序结算。
|
||||
- 目标变化值为 `FlatValue + SourceStat * SourceScalePermille / 1000`。
|
||||
- 使用整数运算;首版不加入暴击、防御、命中、浮动伤害或元素克制。
|
||||
- HP 和 MP 始终钳制到 0 与对应上限之间。
|
||||
- HP 降到 0 时立即标记阵亡,之后不能成为合法目标或行动者。
|
||||
- MP 不足的行动不可执行,不扣除行动机会。
|
||||
- 战斗中的属性和状态变化只属于本场战斗;除最终 HP/MP 外不写回探索。
|
||||
|
||||
### 6.3 眩晕
|
||||
|
||||
- `Stun(1)` 表示跳过目标未来一次行动机会。
|
||||
- 目标本轮尚未行动时被施加眩晕,会跳过本轮即将到来的行动。
|
||||
- 目标本轮已经行动时被施加眩晕,会跳过下一轮的行动。
|
||||
- 重复施加时,剩余次数取当前值与新值中的较大值,不相加。
|
||||
- 战斗结束时清除全部战斗状态。
|
||||
|
||||
### 6.4 逃跑
|
||||
|
||||
- 逃跑只允许当前玩家行动者选择。
|
||||
- 每次尝试使用 `GlobalConfig.EscapeSuccessPermille` 和本局随机源独立判定。
|
||||
- 成功或失败都消耗当前角色本轮行动机会。
|
||||
- 成功后角色立即从当前战斗候选集和合法目标中移除,但不视为阵亡。
|
||||
- 只要仍有玩家留在战斗中,单个角色逃跑不结束战斗。
|
||||
|
||||
## 七、开发里程碑
|
||||
|
||||
### [x] M0 接口、最小单局状态与战斗壳层
|
||||
|
||||
目标:建立不会被后续规则推翻的数据流,并在 Main 场景得到第一个可见结果。
|
||||
|
||||
实现范围:
|
||||
|
||||
- 建立第五节定义的 DTO、枚举和接口。
|
||||
- 新增 `RandomComponent`:本局 seed 与共享随机源,种子由局外带入,地图生成、敌人 AI、逃跑和掉落共用。
|
||||
- 新增 `TurnBattleComponent` 最小状态:临时玩家列表、计时、`IsExplorationPaused` 和战斗占用标志。
|
||||
- 将 `RandomComponent`、`TurnBattleComponent` 挂入 Launcher 的自定义组件节点,并在 `GameEntry.Custom` 暴露统一访问入口。
|
||||
- 新增 `RunBattleCoordinator` 空流程,能用固定调试遭遇构建 `BattleStartRequest`。
|
||||
- 新增半透明顶级 `BattleForm`、`UIFormType` 与 `UIFormConfig`,沿用现有 Form/View/`.Logic.cs` 生成和维护规则。
|
||||
- 增加仅供 Editor/Development Build 使用的调试入口,不依赖尚未实现的地图敌人。
|
||||
- 打开战斗 UI 时暂停调试地图计时,关闭调试壳层后恢复。
|
||||
|
||||
当前进度(2026-08-31):
|
||||
|
||||
- 已完成:`RandomComponent`(`GameEntry.Random`,Launcher.unity "Random" GameObject);`TurnBattleComponent`(`GameEntry.TurnBattle`,Launcher.unity "Turn Battle" GameObject);`IRunRandomSource`、`RunRandomSource`、`RunPlayerState`;第五节全部 DTO/枚举/接口(`Battle/` 目录,命名空间 `SepCore.Battle`);`RunBattleCoordinator`(`GameEntry.RunBattle`,空流程:预留占用、构建 `BattleStartRequest`、暂停/恢复探索与计时、开/关 `BattleForm`,`EndDebugBattle` 为 M0 专用,M7 由 `ApplyResult` 替代);`BattleForm.Logic.cs` 壳层逻辑(按钮绑定、道具禁用、占位反馈,不含暂停职责);`BattleDebuggerWindow` 调试入口(`#if UNITY_EDITOR || DEVELOPMENT_BUILD` 注册于 `ProcedureLaunch`,路径 Battle/Shell,固定遭遇 EncounterId=1/队伍预设 1,缺单局状态时用配表角色与固定种子初始化);配表 `UIFormType.BattleForm = 103` 与 `UIFormConfig` 行已导出。
|
||||
- 未完成:`BattleController` 创建与校验(M1 起);暂停联动已由协调器承担,等待 M0 Play Mode 验收。
|
||||
- 已知注意点:单局计时暂停来源不止战斗(暂停菜单也会暂停),后续需要多来源组合;`RunRandomSource` 使用 System.Random,跨平台同 seed 序列不保证一致,仅需同构建内复现;`BattleView` 的回合槽与敌人槽为模板 + 运行时实例化(`turnSlotsRoot`/`enemySlotsRoot` + template),数据接入时由 Logic 负责实例化。
|
||||
|
||||
验证证据:
|
||||
|
||||
```text
|
||||
代码状态:已完成
|
||||
Unity 编译:通过(2026-08-31,dotnet build SepCore.Runtime.csproj 0 错误;Unity 编辑器内编译与 Play Mode 实测通过)
|
||||
EditMode:不适用(M0 无 EditMode 用例)
|
||||
Play Mode:通过(2026-08-31,Debugger -> Battle/Shell 实测:
|
||||
1. Main 场景未切换、未卸载,半透明 BattleForm 覆盖在地图上;
|
||||
2. 调试入口 Open 打开战斗壳层,按钮可点击响应;
|
||||
3. Run State 读数:打开后 RunElapsedMs 停止增长、IsTimerPaused=true、IsExplorationPaused=true、IsBattleActive=true,Close 后计时恢复增长、标志复位;
|
||||
4. 壳层打开时重复点击 Open 被拒绝(TryStartBattle 占用检查 + allowMultiInstance=false),不会打开第二场。
|
||||
截图:未提供)
|
||||
提交或差异:未提交(工作区包含全部 M0 代码、配表导出与 Launcher.unity 挂载)
|
||||
剩余问题:无
|
||||
```
|
||||
|
||||
明确不做:回合调度、攻击、敌人 AI、胜负和真实碰撞接入。
|
||||
|
||||
Play Mode 验收:
|
||||
|
||||
- Main 场景不切换、不卸载。
|
||||
- 调试入口能打开覆盖地图的半透明战斗 UI。
|
||||
- 战斗 UI 打开时地图计时或可观察的地图更新停止,UI 按钮仍可响应。
|
||||
- 重复触发调试入口不会打开第二场战斗。
|
||||
|
||||
### [ ] M1 1v1 攻击闭环
|
||||
|
||||
目标:完成第一场能亲手打完的战斗。
|
||||
|
||||
实现范围:
|
||||
|
||||
- 创建 1 名玩家和 1 名敌人的 `BattleSession`。
|
||||
- 支持轮次、当前行动者和每轮一次行动机会。
|
||||
- 支持普通攻击、简单 HP 伤害、MP/HP 钳制和阵亡。
|
||||
- 实现 `EnemyAiType.Random` 的免费攻击选择和合法目标选择。
|
||||
- 实现 `Victory` 与 `TotalDefeat`。
|
||||
- UI 显示双方 HP、当前行动者和攻击按钮。
|
||||
|
||||
明确不做:多人队伍、技能、状态、先制和逃跑。
|
||||
|
||||
Play Mode 验收:
|
||||
|
||||
- 玩家点击攻击后敌人 HP 立即变化。
|
||||
- 未结束时敌人能自动反击,玩家 HP 立即变化。
|
||||
- 任一方 HP 归零后战斗停止接受指令并显示对应结果。
|
||||
- 行动记录的顺序和最终数值与 UI 一致。
|
||||
|
||||
### [ ] M2 1 至 4 人多对多与实时回合顺序
|
||||
|
||||
目标:完成设计要求的多对多调度骨架。
|
||||
|
||||
实现范围:
|
||||
|
||||
- 从 `BattleStartRequest.Players` 和 `EnemyPartyConfig` 创建最多 4v4 的单位。
|
||||
- 为重复敌人配置生成不同 `BattleUnitId`。
|
||||
- 实现当前速度、阵营优先和 `PartyOrder` 并列规则。
|
||||
- 每次行动后重新选择本轮下一行动者。
|
||||
- 阵亡单位在轮到前死亡时不再行动。
|
||||
- UI 显示当前行动者和本轮剩余顺序。
|
||||
|
||||
明确不做:速度修改效果、眩晕、技能和先制。
|
||||
|
||||
Play Mode 验收:
|
||||
|
||||
- 调试场景可分别启动 1v1、2v2 和 4v4。
|
||||
- 同速度时玩家先于敌人,同阵营按队伍顺序行动。
|
||||
- 每个仍在战斗中的单位每轮最多行动一次。
|
||||
- 两个相同 EnemyConfig 的敌人可以分别选择、受伤和死亡。
|
||||
|
||||
### [ ] M3 技能、目标选择、MP 与通用效果
|
||||
|
||||
目标:让当前配表中的数值型玩家和敌人行动可用。
|
||||
|
||||
实现范围:
|
||||
|
||||
- 支持 `Attack` 和 `Skill` 行动配置。
|
||||
- 本阶段只结算数值型 `BattleEffect`;状态型效果由 M4 接入。
|
||||
- 支持 `Self`、`SingleAlly`、`AllAllies`、`SingleEnemy` 和 `AllEnemies`。
|
||||
- 实现 MP 校验和成功执行后的 MP 扣除。
|
||||
- 实现 HP 治疗、单体伤害和全体伤害。
|
||||
- UI 根据行动配置进入或跳过目标选择,并禁用 MP 不足的技能。
|
||||
- 控制器拒绝错误行动者、错误阵营、错误目标数量、阵亡目标和逃跑目标。
|
||||
|
||||
明确不做:角色 4 的眩晕效果、速度修改、逃跑和道具效果。
|
||||
|
||||
Play Mode 验收:
|
||||
|
||||
- 角色 1 至 3 的单体伤害、单体治疗和全体伤害技能能够完整结算。
|
||||
- `SingleAlly` 可以选择施法者自己。
|
||||
- 全体技能一次结算全部合法目标。
|
||||
- MP 不足时技能不可点击,伪造非法指令也不会消耗行动机会。
|
||||
- 治疗不会超过 MaxHP,伤害不会使 HP 低于 0。
|
||||
|
||||
### [ ] M4 眩晕、无法行动与实时速度变化
|
||||
|
||||
目标:补齐会改变行动机会或下一行动者的战斗内变化。
|
||||
|
||||
实现范围:
|
||||
|
||||
- 实现 `BattleStatusType.Stun` 及已确认的持续次数语义。
|
||||
- 实现同类状态刷新而非叠层。
|
||||
- 实现对 Speed 的简单数值修改。
|
||||
- 速度改变后立即重算本轮剩余顺序。
|
||||
- 为 EditMode 和开发调试场景提供可复现的速度变化用例;不要求修改四名正式角色的技能定位。
|
||||
|
||||
明确不做:复杂状态、每轮伤害、状态图标动画和永久属性写回。
|
||||
|
||||
Play Mode 验收:
|
||||
|
||||
- 角色 4 的眩晕技能能使尚未行动的目标跳过本次行动。
|
||||
- 已经行动的目标被眩晕后,会跳过下一轮的行动。
|
||||
- 重复眩晕不会叠加总次数,只保留较长持续次数。
|
||||
- 调试行动改变未行动单位速度后,本轮剩余顺序立即更新。
|
||||
- 已在本轮行动过的单位不会因速度提高再次行动。
|
||||
|
||||
### [ ] M5 先制首轮与确定性随机
|
||||
|
||||
目标:接通普通遭遇和主动接敌的规则差异,并证明战斗随机来自单局。
|
||||
|
||||
实现范围:
|
||||
|
||||
- 实现 `IsPreemptive`。
|
||||
- 第一轮所有尚未行动的玩家先于所有敌人,玩家内部仍按当前速度和队伍顺序选择。
|
||||
- 第二轮恢复双方共同按速度选择。
|
||||
- 敌人行动和目标选择只消费注入的本局随机源。
|
||||
- 增加固定随机序列的 EditMode 回归用例。
|
||||
|
||||
明确不做:真实警惕碰撞接入和敌人掉落。
|
||||
|
||||
Play Mode 验收:
|
||||
|
||||
- 同一队伍可分别以普通和先制模式启动。
|
||||
- 先制第一轮中,即使敌人速度更高,也要等所有玩家行动后再行动。
|
||||
- 第二轮高速敌人恢复正常优先级。
|
||||
- 相同初始状态与固定随机序列产生相同敌人行动和目标记录。
|
||||
|
||||
### [ ] M6 单角色逃跑与结束结果矩阵
|
||||
|
||||
目标:完成所有战斗结束分支,但暂不操作地图。
|
||||
|
||||
实现范围:
|
||||
|
||||
- 增加逃跑按钮和 `Escape` 指令。
|
||||
- 使用 `GlobalConfig.EscapeSuccessPermille` 与本局随机源判定。
|
||||
- 成功或失败均消耗当前行动机会。
|
||||
- 成功逃跑单位立即离开候选集和合法目标集合。
|
||||
- 实现 `AllEscaped` 与 `PartialEscapeDefeat`。
|
||||
- 固定四种 `BattleOutcome` 的互斥判定和结果数据。
|
||||
|
||||
明确不做:2 秒地图保护、地图敌人恢复和 HP/MP 回写。
|
||||
|
||||
Play Mode 验收:
|
||||
|
||||
- 逃跑失败后角色仍在战斗中,随后敌人正常行动。
|
||||
- 单人逃跑成功不会在仍有队友战斗时结束战斗。
|
||||
- 所有人成功逃跑得到 `AllEscaped`。
|
||||
- 至少一人逃跑、其余人阵亡得到 `PartialEscapeDefeat`,而不是 `TotalDefeat`。
|
||||
- 没有人逃跑且全部阵亡得到 `TotalDefeat`。
|
||||
|
||||
### [ ] M7 单局临时状态回写与战斗关闭
|
||||
|
||||
目标:让调试战斗能正确返回同一局临时状态。
|
||||
|
||||
实现范围:
|
||||
|
||||
- `RunBattleCoordinator` 消费 `BattleResult`,不得由 UI 自行回写。
|
||||
- 胜利时存活者保留 HP/MP,阵亡者按全局配置恢复到 1/1。
|
||||
- 全逃跑时所有角色保留逃跑时 HP/MP。
|
||||
- 部分逃跑失败时逃跑者保留值,阵亡者恢复到 1/1。
|
||||
- 清除所有战斗状态,不把 Speed 修改或眩晕带回探索。
|
||||
- `TotalDefeat` 进入单局失败入口,不恢复探索更新。
|
||||
- 非全员阵亡时关闭 `BattleForm` 并恢复探索更新。
|
||||
|
||||
明确不做:真实地图敌人、掉落和保护碰撞门禁。
|
||||
|
||||
Play Mode 验收:
|
||||
|
||||
- 连续打开两场调试战斗时,第二场使用第一场返回的玩家 HP/MP。
|
||||
- 第一场阵亡但非全员阵亡的角色,第二场以 1 HP/1 MP 参战。
|
||||
- 上一场眩晕或速度修改不会进入下一场。
|
||||
- 全员阵亡后不能返回可操作地图状态。
|
||||
- 本阶段全过程不修改 `GameEntry.Save.Data` 或写入 `save.json`。
|
||||
|
||||
### [ ] M8 真实探索遭遇、敌人恢复、掉落与保护
|
||||
|
||||
目标:通过既定接口接入探索层,形成碰撞到返回地图的完整垂直流程。
|
||||
|
||||
前置依赖:探索层必须提供唯一 `EncounterId`、敌人队伍预设 ID、警惕是否已满、地图暂停门禁和敌人实例生命周期接口。
|
||||
|
||||
实现范围:
|
||||
|
||||
- 队长碰撞调用探索适配器创建 `BattleEncounter`。
|
||||
- 搜索被战斗中断时保留搜索进度和已掉落物。
|
||||
- 战斗期间冻结所有地图更新,其他敌人保持战前警惕和追击状态。
|
||||
- `Victory` 移除触发敌人,并通过掉落服务使用本局随机源结算掉落。
|
||||
- `AllEscaped` 和 `PartialEscapeDefeat` 保留触发敌人,恢复地图行为;下次进入战斗重新按完整队伍预设创建敌方。
|
||||
- 逃跑类结果开启 `GlobalConfig.EscapeProtectionMs` 保护。
|
||||
- 保护期间禁止警惕增长、追击和战斗触发;结束后仍重叠可立即再次触发。
|
||||
|
||||
明确不做:地图生成、敌人巡逻算法、警惕值算法、物品背包实现和掉落表设计。
|
||||
|
||||
Play Mode 验收:
|
||||
|
||||
- 警惕未满碰撞进入先制战斗,警惕已满碰撞进入普通战斗。
|
||||
- 附近其他地图敌人不会加入战斗,战斗后状态保持不变。
|
||||
- 胜利后只有触发敌人消失并可能产生掉落。
|
||||
- 逃跑后触发敌人仍在;再次碰撞面对完整初始敌人队伍。
|
||||
- 2 秒保护内不能立刻重入战斗,保护结束仍重叠时可以重入。
|
||||
|
||||
### [ ] M9 战斗 UI、输入限制与最小表现完成
|
||||
|
||||
目标:达到设计文档要求的最小可读、可操作战斗界面。
|
||||
|
||||
实现范围:
|
||||
|
||||
- 我方所有角色显示名称、HP、MP、阵亡和逃跑状态。
|
||||
- 所有敌人显示名称、HP 条和不可选状态。
|
||||
- 显示当前行动者和本轮剩余行动顺序。
|
||||
- 行动菜单只在等待当前玩家指令时出现。
|
||||
- 攻击、技能、逃跑可用;道具占位可见但禁用。
|
||||
- 单体目标提供明确选中态;取消目标选择返回行动菜单且不消耗行动。
|
||||
- 战斗期间不能打开共享背包、保险箱或调整保险箱内容。
|
||||
- UI 文案走现有本地化/格式化文本流程,不在逻辑代码中硬编码。
|
||||
- 保持半透明覆盖效果,让玩家能够确认仍在原地图场景。
|
||||
|
||||
明确不做:角色站位、骨骼或序列帧动画、VFX、Timeline、镜头演出和音效同步。
|
||||
|
||||
Play Mode 验收:
|
||||
|
||||
- 1 至 4 名玩家和敌人的最长名称、HP/MP 数值不会溢出或遮挡。
|
||||
- 速度变化后顺序显示与下一实际行动者一致。
|
||||
- 非当前玩家不能操作,敌人自动行动期间不能提交玩家指令。
|
||||
- 背包和保险箱入口在战斗中不可用。
|
||||
- 取消目标、MP 不足、阵亡目标和逃跑目标均不会误消耗行动。
|
||||
|
||||
### [ ] M10 全量回归与原型完成
|
||||
|
||||
目标:关闭战斗模块全部设计验收,清理调试入口和遗留临时代码。
|
||||
|
||||
实现范围:
|
||||
|
||||
- 完成第八节的全部验收映射。
|
||||
- 覆盖 EditMode 规则测试和实际探索 Play Mode 流程。
|
||||
- 调试入口只在 Editor/Development Build 可用,正式构建不可见。
|
||||
- 清理临时日志、硬编码测试数据和无用调试按钮。
|
||||
- 检查生成文件、资源收集、UIForm 配表和 Prefab 引用。
|
||||
- 更新本文档进度、证据和剩余问题。
|
||||
|
||||
Play Mode 验收:
|
||||
|
||||
- 从地图碰撞进入战斗、完成任一结果并返回或进入失败结算的流程可重复执行。
|
||||
- 普通、先制、胜利、全逃跑、部分逃跑失败和全员阵亡均留有证据。
|
||||
- 连续多场战斗没有重复事件订阅、重复 UI、随机源重置或地图暂停泄漏。
|
||||
- Unity Console 没有由战斗流程产生的新异常或错误。
|
||||
|
||||
## 八、设计验收映射
|
||||
|
||||
以下 22 项对应 `Docs/GameDesign/04_TurnBasedCombat.md`。只有对应里程碑完成运行验收后才能勾选。
|
||||
|
||||
- [ ] 多个玩家和多个敌人可以同时参战。对应 M2。
|
||||
- [ ] 玩家与敌人上限均为 4,并按战备和敌人队伍预设创建。对应 M2。
|
||||
- [ ] 普通轮次每个单位只行动一次,每次行动后按当前速度决定下一位。对应 M2、M4。
|
||||
- [ ] 同速玩家优先,速度变化立即影响尚未行动单位。对应 M2、M4。
|
||||
- [ ] 同阵营同速按队伍顺序行动。对应 M2。
|
||||
- [ ] 死亡、眩晕或无法行动单位跳过本轮。对应 M2、M4。
|
||||
- [ ] 轮到玩家角色时才显示并处理该角色的行动选择。对应 M1、M9。
|
||||
- [ ] 先制第一轮所有玩家排在所有敌人之前。对应 M5。
|
||||
- [ ] 先制第二轮恢复普通速度规则。对应 M5。
|
||||
- [ ] 玩家可选择攻击、技能和逃跑,道具保留但不可用。对应 M3、M6、M9。
|
||||
- [ ] 战斗中不能打开共享背包或保险箱。对应 M9。
|
||||
- [ ] 敌人随机选择可用行动和合法目标。对应 M1、M5。
|
||||
- [ ] 敌人全灭时胜利,玩家全灭时战斗和单局失败。对应 M1、M6、M7。
|
||||
- [ ] 单个角色可逃跑;只要有人逃跑,其他人阵亡不导致单局失败。对应 M6。
|
||||
- [ ] 逃跑使用配置成功率,失败后继续留在战斗。对应 M6。
|
||||
- [ ] 逃跑成功或失败均消耗本轮行动。对应 M6。
|
||||
- [ ] 逃跑返回战前位置并获得 2 秒保护。对应 M7、M8。
|
||||
- [ ] 逃跑者保留 HP/MP,部分失败中的阵亡者恢复 1/1。对应 M7。
|
||||
- [ ] 任何非全员阵亡结果中的阵亡角色恢复 1/1。对应 M7。
|
||||
- [ ] 逃跑后地图敌人恢复,重战按完整预设创建。对应 M8。
|
||||
- [ ] 保护期间不警惕、不追击、不触发战斗,结束仍重叠时可重入。对应 M8。
|
||||
- [ ] 战斗 UI 显示我方 HP/MP、行动顺序和敌人 HP。对应 M9。
|
||||
|
||||
## 九、最低测试矩阵
|
||||
|
||||
### EditMode
|
||||
|
||||
| 类别 | 必测情况 |
|
||||
| --- | --- |
|
||||
| 单位创建 | 1v1、4v4、重复 EnemyConfig、不合法队伍人数 |
|
||||
| 普通排序 | 速度不同、跨阵营同速、同阵营同速、每轮只行动一次 |
|
||||
| 动态排序 | 未行动单位加速/减速、已行动单位加速、死亡后移出候选集 |
|
||||
| 先制 | 第一轮玩家全先行、第二轮恢复普通规则 |
|
||||
| 目标 | Self、SingleAlly 含自己、全体目标、阵营错误、阵亡和逃跑目标 |
|
||||
| MP 与效果 | MP 不足、伤害、治疗、全体效果、HP/MP 钳制、顺序效果 |
|
||||
| 眩晕 | 行动前施加、行动后施加、重复施加、跳过后失效 |
|
||||
| 敌人 AI | 过滤不可用行动、合法目标、无行动时跳过、固定随机序列 |
|
||||
| 逃跑 | 0/1000 边界、成功/失败消耗行动、四种结束结果 |
|
||||
| 回写 | 胜利阵亡恢复、全逃跑保留、部分失败恢复、全灭不恢复探索 |
|
||||
|
||||
### Play Mode
|
||||
|
||||
| 流程 | 必测情况 |
|
||||
| --- | --- |
|
||||
| UI 生命周期 | 打开、关闭、重复进入、非当前行动者不可输入 |
|
||||
| 地图暂停 | 玩家、敌人、搜索、计时停止;战斗 UI 仍响应 |
|
||||
| 遭遇 | 普通碰撞、先制碰撞、附近敌人不参战 |
|
||||
| 返回 | 胜利移除敌人、逃跑保留敌人、完整队伍重建 |
|
||||
| 保护 | 保护期不重入,结束仍重叠可重入 |
|
||||
| 连战 | HP/MP 延续、状态清除、共享随机序列不重置 |
|
||||
| 失败 | 全员阵亡进入单局失败,不短暂恢复地图操作 |
|
||||
|
||||
## 十、原型完成条件
|
||||
|
||||
只有同时满足以下条件,本文档状态才能改为“完成”:
|
||||
|
||||
- M0 至 M10 全部标记为 `[x]`,且每个里程碑都有独立的 Unity/Play Mode 证据。
|
||||
- 第八节 22 条设计验收全部通过。
|
||||
- EditMode 最低测试矩阵全部通过。
|
||||
- 战斗过程中没有直接写 `SaveData`、切换 Battle 场景或重建单局随机源。
|
||||
- 胜利、逃跑和失败对地图敌人、玩家临时状态、计时和保护期的处理符合本文档。
|
||||
- 正式构建中没有开发调试入口和测试数据。
|
||||
- 静态检查、Unity 编译、EditMode 和 Play Mode 结果分别记录,不用静态检查代替运行验收。
|
||||
+2
-1
@@ -6,4 +6,5 @@
|
||||
| --- | --- | --- |
|
||||
| 1 | [Luban 数据表组件](01_LubanDataTable.md) | 配置表的导出、加载、访问约定与新增表流程 |
|
||||
| 2 | [存档数据结构](02_SaveData.md) | 存档序列化格式、字段定义与生命周期 |
|
||||
| 3 | [UI 维护要点](03_UIMaintenance.md) | Form/View 组织、嵌套 Form 协作、事件通信与常见坑 |
|
||||
| 3 | [UI 维护要点](03_UIMaintenance.md) | Form/View 组织、嵌套 Form 协作、事件通信与常见坑 |
|
||||
| 4 | [回合制战斗开发 TODO](04_TurnBasedCombatDevelopmentTODO.md) | 战斗接口边界、垂直开发里程碑与运行验收追踪 |
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user