feat(gameplay): 调整单位属性和纹理设置并优化主角移动系统

- 修改多个纹理资源的最大尺寸从2048降至512,并添加PlayableAds平台设置
- 调整第一、二章多个单位的移动速度、攻击力和攻击持续时间参数
- 重命名精锐士兵预制体名称,移除"新"字后缀
- 降低精锐士兵的攻击力和攻击持续时间

- 为主角战斗组件添加detectRange字段,实现寻敌和攻击范围分离
- 重构主角移动系统,从点击移动改为自动巡逻模式,增加栓绳机制防止远离基地
- 实现巡逻和追击状态切换,在发现敌人时停止巡逻进行攻击
-
This commit is contained in:
huadexuan committed 2026-09-14 18:36:50 +08:00
1 parent 769aea30f3
commit 8d43961f67
28 files changed
+443 -125

No files matched your search

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

@@ -72,7 +72,7 @@ TextureImporter:
platformSettings: platformSettings:
- serializedVersion: 3 - serializedVersion: 3
buildTarget: DefaultTexturePlatform buildTarget: DefaultTexturePlatform
maxTextureSize: 2048 maxTextureSize: 512
maxPlaceholderSize: 32 maxPlaceholderSize: 32
resizeAlgorithm: 0 resizeAlgorithm: 0
textureFormat: -1 textureFormat: -1
@@ -140,6 +140,20 @@ TextureImporter:
ignorePlatformSupport: 0 ignorePlatformSupport: 0
androidETC2FallbackOverride: 0 androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0 forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: PlayableAds
maxTextureSize: 2048
maxPlaceholderSize: 32
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet: spriteSheet:
serializedVersion: 2 serializedVersion: 2
sprites: [] sprites: []
+15 -1
View File
@@ -72,7 +72,7 @@ TextureImporter:
platformSettings: platformSettings:
- serializedVersion: 3 - serializedVersion: 3
buildTarget: DefaultTexturePlatform buildTarget: DefaultTexturePlatform
maxTextureSize: 2048 maxTextureSize: 512
maxPlaceholderSize: 32 maxPlaceholderSize: 32
resizeAlgorithm: 0 resizeAlgorithm: 0
textureFormat: -1 textureFormat: -1
@@ -140,6 +140,20 @@ TextureImporter:
ignorePlatformSupport: 0 ignorePlatformSupport: 0
androidETC2FallbackOverride: 0 androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0 forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: PlayableAds
maxTextureSize: 2048
maxPlaceholderSize: 32
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet: spriteSheet:
serializedVersion: 2 serializedVersion: 2
sprites: [] sprites: []
@@ -72,7 +72,7 @@ TextureImporter:
platformSettings: platformSettings:
- serializedVersion: 3 - serializedVersion: 3
buildTarget: DefaultTexturePlatform buildTarget: DefaultTexturePlatform
maxTextureSize: 2048 maxTextureSize: 512
maxPlaceholderSize: 32 maxPlaceholderSize: 32
resizeAlgorithm: 0 resizeAlgorithm: 0
textureFormat: -1 textureFormat: -1
@@ -140,6 +140,20 @@ TextureImporter:
ignorePlatformSupport: 0 ignorePlatformSupport: 0
androidETC2FallbackOverride: 0 androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0 forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: PlayableAds
maxTextureSize: 2048
maxPlaceholderSize: 32
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet: spriteSheet:
serializedVersion: 2 serializedVersion: 2
sprites: [] sprites: []
+3 -3
View File
@@ -189,15 +189,15 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: b91aa6ae607324ce19fc46225a44e70a, type: 3} m_Script: {fileID: 11500000, guid: b91aa6ae607324ce19fc46225a44e70a, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
speed: 3 speed: 1.8
gravity: -20 gravity: -20
range: 500 range: 500
detectRange: 200 detectRange: 200
attackRange: 1 attackRange: 1
modelFacingOffset: 0 modelFacingOffset: 0
useRootMotion: 1 useRootMotion: 1
attackPower: 28 attackPower: 30
attackDuration: 1 attackDuration: 1.2
attackType: 0 attackType: 0
projectilePrefab: {fileID: 0} projectilePrefab: {fileID: 0}
projectileSpawnPoint: {fileID: 0} projectileSpawnPoint: {fileID: 0}
+3 -3
View File
@@ -206,15 +206,15 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: b91aa6ae607324ce19fc46225a44e70a, type: 3} m_Script: {fileID: 11500000, guid: b91aa6ae607324ce19fc46225a44e70a, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
speed: 5 speed: 1.8
gravity: -20 gravity: -20
range: 500 range: 500
detectRange: 200 detectRange: 200
attackRange: 6 attackRange: 6
modelFacingOffset: 90 modelFacingOffset: 90
useRootMotion: 0 useRootMotion: 0
attackPower: 34 attackPower: 40
attackDuration: 1.33 attackDuration: 1.5
attackType: 2 attackType: 2
projectilePrefab: {fileID: 1754121762047325928, guid: e59aafedb502e65409f16e33cee0eed6, projectilePrefab: {fileID: 1754121762047325928, guid: e59aafedb502e65409f16e33cee0eed6,
type: 3} type: 3}
+3 -3
View File
@@ -200,15 +200,15 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: b91aa6ae607324ce19fc46225a44e70a, type: 3} m_Script: {fileID: 11500000, guid: b91aa6ae607324ce19fc46225a44e70a, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
speed: 3 speed: 1.5
gravity: -20 gravity: -20
range: 18 range: 18
detectRange: 20 detectRange: 20
attackRange: 6 attackRange: 6
modelFacingOffset: 45 modelFacingOffset: 45
useRootMotion: 1 useRootMotion: 1
attackPower: 42 attackPower: 90
attackDuration: 1.82 attackDuration: 2.4
attackType: 2 attackType: 2
projectilePrefab: {fileID: 3410584434205606752, guid: ea38dfd40c228456e8e1c98952673009, projectilePrefab: {fileID: 3410584434205606752, guid: ea38dfd40c228456e8e1c98952673009,
type: 3} type: 3}
+3 -3
View File
@@ -261,15 +261,15 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: b91aa6ae607324ce19fc46225a44e70a, type: 3} m_Script: {fileID: 11500000, guid: b91aa6ae607324ce19fc46225a44e70a, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
speed: 2.5 speed: 1.4
gravity: -20 gravity: -20
range: 18 range: 18
detectRange: 20 detectRange: 20
attackRange: 1.2 attackRange: 1.2
modelFacingOffset: 45 modelFacingOffset: 45
useRootMotion: 1 useRootMotion: 1
attackPower: 24 attackPower: 20
attackDuration: 1.54 attackDuration: 1.8
attackType: 0 attackType: 0
projectilePrefab: {fileID: 0} projectilePrefab: {fileID: 0}
projectileSpawnPoint: {fileID: 0} projectileSpawnPoint: {fileID: 0}
+3 -3
View File
@@ -135,7 +135,7 @@ PrefabInstance:
- target: {fileID: 919132149155446097, guid: 62d9f853c067ff443bd2a3aa382ab563, - target: {fileID: 919132149155446097, guid: 62d9f853c067ff443bd2a3aa382ab563,
type: 3} type: 3}
propertyPath: m_Name propertyPath: m_Name
value: "\u7CBE\u9510\u58EB\u5175\u65B0" value: "\u7CBE\u9510\u58EB\u5175"
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 919132149155446097, guid: 62d9f853c067ff443bd2a3aa382ab563, - target: {fileID: 919132149155446097, guid: 62d9f853c067ff443bd2a3aa382ab563,
type: 3} type: 3}
@@ -255,8 +255,8 @@ MonoBehaviour:
attackRange: 1.2 attackRange: 1.2
modelFacingOffset: 0 modelFacingOffset: 0
useRootMotion: 1 useRootMotion: 1
attackPower: 68 attackPower: 55
attackDuration: 2.22 attackDuration: 1.6
attackType: 0 attackType: 0
projectilePrefab: {fileID: 0} projectilePrefab: {fileID: 0}
projectileSpawnPoint: {fileID: 0} projectileSpawnPoint: {fileID: 0}
+3 -3
View File
@@ -313,15 +313,15 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: b91aa6ae607324ce19fc46225a44e70a, type: 3} m_Script: {fileID: 11500000, guid: b91aa6ae607324ce19fc46225a44e70a, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
speed: 5 speed: 2.2
gravity: -20 gravity: -20
range: 500 range: 500
detectRange: 200 detectRange: 200
attackRange: 5 attackRange: 5
modelFacingOffset: 0 modelFacingOffset: 0
useRootMotion: 0 useRootMotion: 0
attackPower: 12 attackPower: 15
attackDuration: 0.8 attackDuration: 0.9
attackType: 2 attackType: 2
projectilePrefab: {fileID: 1754121762047325928, guid: e59aafedb502e65409f16e33cee0eed6, projectilePrefab: {fileID: 1754121762047325928, guid: e59aafedb502e65409f16e33cee0eed6,
type: 3} type: 3}
+3 -3
View File
@@ -148,15 +148,15 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: b91aa6ae607324ce19fc46225a44e70a, type: 3} m_Script: {fileID: 11500000, guid: b91aa6ae607324ce19fc46225a44e70a, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
speed: 4 speed: 2.5
gravity: -20 gravity: -20
range: 18 range: 18
detectRange: 20 detectRange: 20
attackRange: 1.1 attackRange: 1.1
modelFacingOffset: 0 modelFacingOffset: 0
useRootMotion: 1 useRootMotion: 1
attackPower: 38 attackPower: 32
attackDuration: 0.95 attackDuration: 1
attackType: 0 attackType: 0
projectilePrefab: {fileID: 0} projectilePrefab: {fileID: 0}
projectileSpawnPoint: {fileID: 0} projectileSpawnPoint: {fileID: 0}
+3 -3
View File
@@ -132,15 +132,15 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: b91aa6ae607324ce19fc46225a44e70a, type: 3} m_Script: {fileID: 11500000, guid: b91aa6ae607324ce19fc46225a44e70a, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
speed: 2.1 speed: 1.1
gravity: -20 gravity: -20
range: 500 range: 500
detectRange: 200 detectRange: 200
attackRange: 1.1 attackRange: 1.1
modelFacingOffset: 90 modelFacingOffset: 90
useRootMotion: 1 useRootMotion: 1
attackPower: 72 attackPower: 110
attackDuration: 2.5 attackDuration: 2.8
attackType: 0 attackType: 0
projectilePrefab: {fileID: 0} projectilePrefab: {fileID: 0}
projectileSpawnPoint: {fileID: 0} projectileSpawnPoint: {fileID: 0}
+3 -3
View File
@@ -173,15 +173,15 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: b91aa6ae607324ce19fc46225a44e70a, type: 3} m_Script: {fileID: 11500000, guid: b91aa6ae607324ce19fc46225a44e70a, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
speed: 2 speed: 1.6
gravity: -20 gravity: -20
range: 18 range: 18
detectRange: 20 detectRange: 20
attackRange: 1 attackRange: 1
modelFacingOffset: 0 modelFacingOffset: 0
useRootMotion: 0 useRootMotion: 0
attackPower: 20 attackPower: 16
attackDuration: 1.8 attackDuration: 1.6
attackType: 0 attackType: 0
projectilePrefab: {fileID: 0} projectilePrefab: {fileID: 0}
projectileSpawnPoint: {fileID: 0} projectileSpawnPoint: {fileID: 0}
+6 -6
View File
@@ -20,11 +20,11 @@ MonoBehaviour:
type: 3} type: 3}
manaCost: 2 manaCost: 2
cooldownTime: 1 cooldownTime: 1
productionInterval: 8 productionInterval: 6
buildingCost: 100 buildingCost: 100
baseMaxHealth: 100 baseMaxHealth: 300
baseAttackPower: 50 baseAttackPower: 30
baseMoveSpeed: 3 baseMoveSpeed: 1.8
healthPerLevel: 10 healthPerLevel: 30
attackPerLevel: 5 attackPerLevel: 3
moveSpeedPerLevel: 0 moveSpeedPerLevel: 0
+7 -7
View File
@@ -20,11 +20,11 @@ MonoBehaviour:
type: 3} type: 3}
manaCost: 3 manaCost: 3
cooldownTime: 1 cooldownTime: 1
productionInterval: 10 productionInterval: 8
buildingCost: 100 buildingCost: 120
baseMaxHealth: 80 baseMaxHealth: 200
baseAttackPower: 60 baseAttackPower: 40
baseMoveSpeed: 3.5 baseMoveSpeed: 1.8
healthPerLevel: 8 healthPerLevel: 20
attackPerLevel: 6 attackPerLevel: 3
moveSpeedPerLevel: 0 moveSpeedPerLevel: 0
+7 -5
View File
@@ -19,9 +19,11 @@ MonoBehaviour:
unitPrefab: {fileID: 0} unitPrefab: {fileID: 0}
manaCost: 3 manaCost: 3
cooldownTime: 1 cooldownTime: 1
baseMaxHealth: 145 productionInterval: 8
baseAttackPower: 36 buildingCost: 120
baseMoveSpeed: 3.5 baseMaxHealth: 200
healthPerLevel: 7.25 baseAttackPower: 15
attackPerLevel: 1.8 baseMoveSpeed: 2.2
healthPerLevel: 20
attackPerLevel: 1
moveSpeedPerLevel: 0 moveSpeedPerLevel: 0
+6 -6
View File
@@ -21,10 +21,10 @@ MonoBehaviour:
manaCost: 6 manaCost: 6
cooldownTime: 1 cooldownTime: 1
productionInterval: 12 productionInterval: 12
buildingCost: 100 buildingCost: 150
baseMaxHealth: 70 baseMaxHealth: 160
baseAttackPower: 100 baseAttackPower: 90
baseMoveSpeed: 3 baseMoveSpeed: 1.5
healthPerLevel: 7 healthPerLevel: 16
attackPerLevel: 10 attackPerLevel: 7
moveSpeedPerLevel: 0 moveSpeedPerLevel: 0
+7 -5
View File
@@ -19,9 +19,11 @@ MonoBehaviour:
unitPrefab: {fileID: 0} unitPrefab: {fileID: 0}
manaCost: 4 manaCost: 4
cooldownTime: 1 cooldownTime: 1
baseMaxHealth: 360 productionInterval: 10
baseAttackPower: 38 buildingCost: 150
baseMoveSpeed: 3.8 baseMaxHealth: 380
healthPerLevel: 18 baseAttackPower: 32
attackPerLevel: 1.9 baseMoveSpeed: 2.5
healthPerLevel: 38
attackPerLevel: 3
moveSpeedPerLevel: 0 moveSpeedPerLevel: 0
+7 -5
View File
@@ -20,9 +20,11 @@ MonoBehaviour:
type: 3} type: 3}
manaCost: 4 manaCost: 4
cooldownTime: 1 cooldownTime: 1
baseMaxHealth: 150 productionInterval: 10
baseAttackPower: 40 buildingCost: 150
baseMoveSpeed: 2.5 baseMaxHealth: 900
healthPerLevel: 15 baseAttackPower: 20
attackPerLevel: 4 baseMoveSpeed: 1.4
healthPerLevel: 90
attackPerLevel: 2
moveSpeedPerLevel: 0 moveSpeedPerLevel: 0
+7 -5
View File
@@ -19,9 +19,11 @@ MonoBehaviour:
unitPrefab: {fileID: 0} unitPrefab: {fileID: 0}
manaCost: 7 manaCost: 7
cooldownTime: 1 cooldownTime: 1
baseMaxHealth: 820 productionInterval: 20
baseAttackPower: 72 buildingCost: 250
baseMoveSpeed: 2.5 baseMaxHealth: 1400
healthPerLevel: 41 baseAttackPower: 110
attackPerLevel: 3.6 baseMoveSpeed: 1.1
healthPerLevel: 140
attackPerLevel: 9
moveSpeedPerLevel: 0 moveSpeedPerLevel: 0
+7 -5
View File
@@ -20,9 +20,11 @@ MonoBehaviour:
type: 3} type: 3}
manaCost: 5 manaCost: 5
cooldownTime: 1 cooldownTime: 1
baseMaxHealth: 120 productionInterval: 12
baseAttackPower: 80 buildingCost: 180
baseMoveSpeed: 3.5 baseMaxHealth: 420
healthPerLevel: 12 baseAttackPower: 55
attackPerLevel: 8 baseMoveSpeed: 2
healthPerLevel: 42
attackPerLevel: 4
moveSpeedPerLevel: 0 moveSpeedPerLevel: 0
+7 -5
View File
@@ -19,9 +19,11 @@ MonoBehaviour:
unitPrefab: {fileID: 0} unitPrefab: {fileID: 0}
manaCost: 1 manaCost: 1
cooldownTime: 1 cooldownTime: 1
baseMaxHealth: 170 productionInterval: 5
baseAttackPower: 22 buildingCost: 80
baseMoveSpeed: 4 baseMaxHealth: 340
healthPerLevel: 8.5 baseAttackPower: 16
attackPerLevel: 1.1 baseMoveSpeed: 1.6
healthPerLevel: 34
attackPerLevel: 1
moveSpeedPerLevel: 0 moveSpeedPerLevel: 0
+42 -6
View File
@@ -7,6 +7,9 @@ public class MainCharacterCombat : MonoBehaviour
[Min(0f)] [Min(0f)]
public float attackRange = 8f; public float attackRange = 8f;
[Min(0f)]
public float detectRange = 10f;
[Min(0f)] [Min(0f)]
public float attackPower = 50f; public float attackPower = 50f;
@@ -86,13 +89,39 @@ public class MainCharacterCombat : MonoBehaviour
return; return;
} }
if (movement == null || movement.IsMoving || animator == null) if (animator == null)
{ {
return; return;
} }
// 巡逻中也能发现敌人;发现后停止巡逻,没有敌人则恢复巡逻
target = FindNearestEnemy(); target = FindNearestEnemy();
if (target == null || attackCooldown > 0f) if (target == null)
{
if (movement != null)
{
movement.ClearChaseTarget();
movement.SetPatrolEnabled(true);
}
return;
}
if (movement != null)
{
movement.SetPatrolEnabled(false);
// 寻敌范围内但超出攻击范围:主动走近敌人再攻击(受基地栓绳限制)
float targetDistance = GetHorizontalDistance(target.transform.position);
if (targetDistance > attackRange)
{
movement.SetChaseTarget(target.transform.position);
return;
}
movement.ClearChaseTarget();
}
if (attackCooldown > 0f)
{ {
return; return;
} }
@@ -165,7 +194,7 @@ public class MainCharacterCombat : MonoBehaviour
if (movement != null) if (movement != null)
{ {
movement.StopMovement(); movement.SetPatrolEnabled(false);
} }
if (attacking) if (attacking)
@@ -334,8 +363,7 @@ public class MainCharacterCombat : MonoBehaviour
private bool IsInsideMovementBounds(Vector3 position) private bool IsInsideMovementBounds(Vector3 position)
{ {
return movement == null || (position.x >= movement.xLimits.x && position.x <= movement.xLimits.y return movement == null || movement.IsWithinLeash(position);
&& position.z >= movement.zLimits.x && position.z <= movement.zLimits.y);
} }
private void CheckSummonFinished() private void CheckSummonFinished()
@@ -466,10 +494,18 @@ public class MainCharacterCombat : MonoBehaviour
animator.SetInteger("speed", 0); animator.SetInteger("speed", 0);
} }
private float GetHorizontalDistance(Vector3 position)
{
Vector3 offset = position - transform.position;
offset.y = 0f;
return offset.magnitude;
}
private GameObject FindNearestEnemy() private GameObject FindNearestEnemy()
{ {
// 索敌范围使用较小的 detectRange,主角不主动远离基地寻找敌人
GameObject nearest = null; GameObject nearest = null;
float nearestDistance = attackRange * attackRange; float nearestDistance = detectRange * detectRange;
foreach (GameObject enemy in GameObject.FindGameObjectsWithTag("Enemy")) foreach (GameObject enemy in GameObject.FindGameObjectsWithTag("Enemy"))
{ {
+169 -37
View File
@@ -1,49 +1,91 @@
using UnityEngine; using UnityEngine;
using UnityEngine.EventSystems;
/// <summary>
/// 主角自动巡逻移动:不再支持点击地面移动(避免与拖动屏幕、建筑放置的输入冲突)。
/// 没有敌人时在己方主建筑周围小范围来回走动,所有移动被限制在基地附近(栓绳范围)。
/// 战斗组件发现敌人后调用 SetPatrolEnabled(false) 停止巡逻进行攻击。
/// </summary>
public class MainCharacterDebugMovement : MonoBehaviour public class MainCharacterDebugMovement : MonoBehaviour
{ {
[Header("电脑调试移动")] [Header("巡逻移动")]
[Min(0f)] [Min(0f)]
public float moveSpeed = 4f; public float moveSpeed = 4f;
[Min(0f)] [Min(0f)]
public float clickStopDistance = 0.08f; public float patrolStopDistance = 0.15f;
[Header("移动范围")] [Min(0f)]
public bool limitToBattlePlatform = true; public float patrolPause = 1.5f;
public Vector2 xLimits = new Vector2(-7.5f, 7.5f);
public Vector2 zLimits = new Vector2(-14f, 14f); [Header("巡逻区域(以主建筑为锚点)")]
[Min(0f)]
public float patrolRadius = 5f;
[Min(0f)]
public float leashRadius = 8f;
public string friendlyBuildingTag = "Friend";
private Animator animator; private Animator animator;
private Vector3 clickTarget; private Transform baseAnchor;
private bool hasClickTarget; private Vector3 fallbackAnchor;
private Vector3 patrolTarget;
private Vector3 chaseTarget;
private bool hasPatrolTarget;
private bool hasChaseTarget;
private float pauseTimer;
private bool patrolEnabled = true;
public bool IsMoving => hasClickTarget; public bool IsMoving { get; private set; }
private Vector3 AnchorPosition
{
get
{
Vector3 position = baseAnchor != null ? baseAnchor.position : fallbackAnchor;
position.y = transform.position.y;
return position;
}
}
private void Start() private void Start()
{ {
animator = GetComponent<Animator>(); animator = GetComponent<Animator>();
clickTarget = transform.position; fallbackAnchor = transform.position;
baseAnchor = FindMainBase();
SetWalking(false); SetWalking(false);
} }
private void Update() private void Update()
{ {
HandleClickTarget(); if (!patrolEnabled)
if (!hasClickTarget)
{ {
UpdateChase();
return;
}
if (pauseTimer > 0f)
{
pauseTimer -= Time.deltaTime;
IsMoving = false;
SetWalking(false); SetWalking(false);
return; return;
} }
Vector3 toTarget = clickTarget - transform.position; if (!hasPatrolTarget)
{
patrolTarget = GetNextPatrolPoint();
hasPatrolTarget = true;
}
Vector3 toTarget = patrolTarget - transform.position;
toTarget.y = 0f; toTarget.y = 0f;
if (toTarget.magnitude <= clickStopDistance) if (toTarget.magnitude <= patrolStopDistance)
{ {
hasClickTarget = false; hasPatrolTarget = false;
pauseTimer = patrolPause;
IsMoving = false;
SetWalking(false); SetWalking(false);
return; return;
} }
@@ -51,32 +93,122 @@ public class MainCharacterDebugMovement : MonoBehaviour
Move(toTarget.normalized); Move(toTarget.normalized);
} }
private void HandleClickTarget() private void UpdateChase()
{ {
if (!Input.GetMouseButtonDown(0) || Camera.main == null) if (!hasChaseTarget)
{
IsMoving = false;
SetWalking(false);
return;
}
Vector3 toTarget = chaseTarget - transform.position;
toTarget.y = 0f;
if (toTarget.magnitude <= patrolStopDistance)
{
IsMoving = false;
SetWalking(false);
return;
}
// 追击同样受栓绳限制,不会完全离开基地
Move(toTarget.normalized);
}
private Transform FindMainBase()
{
Transform firstBuilding = null;
foreach (GameObject candidate in GameObject.FindGameObjectsWithTag(friendlyBuildingTag))
{
if (candidate == null || candidate.transform.root == transform.root)
{
continue;
}
if (candidate.GetComponentInParent<BuildingHealth>() == null)
{
continue;
}
// 主建筑按名称优先匹配;找不到时退回第一个己方建筑
if (candidate.name.Contains("主要") || candidate.name.Contains("基地"))
{
return candidate.transform;
}
if (firstBuilding == null)
{
firstBuilding = candidate.transform;
}
}
return firstBuilding;
}
private Vector3 GetNextPatrolPoint()
{
Vector2 offset = Random.insideUnitCircle * patrolRadius;
return ClampToLeash(AnchorPosition + new Vector3(offset.x, 0f, offset.y));
}
private Vector3 ClampToLeash(Vector3 position)
{
Vector3 offset = position - AnchorPosition;
offset.y = 0f;
if (offset.magnitude > leashRadius)
{
offset = offset.normalized * leashRadius;
}
return AnchorPosition + offset;
}
public bool IsWithinLeash(Vector3 position)
{
Vector3 offset = position - AnchorPosition;
offset.y = 0f;
return offset.magnitude <= leashRadius;
}
public void SetPatrolEnabled(bool enabled)
{
if (patrolEnabled == enabled)
{ {
return; return;
} }
if (EventSystem.current != null && EventSystem.current.IsPointerOverGameObject()) patrolEnabled = enabled;
if (!enabled)
{ {
return; hasPatrolTarget = false;
pauseTimer = 0f;
IsMoving = false;
SetWalking(false);
} }
else
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (!Physics.Raycast(ray, out RaycastHit hit))
{ {
return; hasChaseTarget = false;
} }
}
clickTarget = hit.point; public void SetChaseTarget(Vector3 position)
clickTarget.y = transform.position.y; {
hasClickTarget = true; chaseTarget = position;
chaseTarget.y = transform.position.y;
hasChaseTarget = true;
}
public void ClearChaseTarget()
{
hasChaseTarget = false;
} }
public void StopMovement() public void StopMovement()
{ {
hasClickTarget = false; hasPatrolTarget = false;
hasChaseTarget = false;
IsMoving = false;
SetWalking(false); SetWalking(false);
} }
@@ -85,19 +217,19 @@ public class MainCharacterDebugMovement : MonoBehaviour
direction.y = 0f; direction.y = 0f;
if (direction.sqrMagnitude <= 0.001f) if (direction.sqrMagnitude <= 0.001f)
{ {
IsMoving = false;
SetWalking(false); SetWalking(false);
return; return;
} }
IsMoving = true;
SetWalking(true); SetWalking(true);
transform.position += direction * moveSpeed * Time.deltaTime; transform.position += direction * moveSpeed * Time.deltaTime;
if (limitToBattlePlatform)
{ // 栓绳:任何情况下都不能完全离开基地
Vector3 position = transform.position; Vector3 clamped = ClampToLeash(transform.position);
position.x = Mathf.Clamp(position.x, xLimits.x, xLimits.y); clamped.y = transform.position.y;
position.z = Mathf.Clamp(position.z, zLimits.x, zLimits.y); transform.position = clamped;
transform.position = position;
}
transform.rotation = Quaternion.LookRotation(direction, Vector3.up); transform.rotation = Quaternion.LookRotation(direction, Vector3.up);
} }
@@ -109,4 +241,4 @@ public class MainCharacterDebugMovement : MonoBehaviour
animator.SetInteger("speed", walking ? 1 : 0); animator.SetInteger("speed", walking ? 1 : 0);
} }
} }
} }
+3
View File
@@ -20,6 +20,7 @@
- [2026-09-09 13:43:50] 能量恢复速度应该更慢一些,用户建议改为 3 秒恢复 1 格。**Why:** 1 秒 1 格太快,不利于战斗节奏控制。**How to apply:** 召唤系统能量恢复速率应偏慢,给玩家资源压力。 - [2026-09-09 13:43:50] 能量恢复速度应该更慢一些,用户建议改为 3 秒恢复 1 格。**Why:** 1 秒 1 格太快,不利于战斗节奏控制。**How to apply:** 召唤系统能量恢复速率应偏慢,给玩家资源压力。
- [2026-09-09 20:37:24] 用户希望提前实现数据架构而非临时方案,即使当前阶段功能简单。**Why:** 用户有 10 年后端开发经验,理解数据分层的重要性,希望逐步完善而非推倒重来。**How to apply:** 对于有明确扩展方向的功能(升级、存档、联网),优先设计可扩展架构并临时写死数据,而非等需求明确后再重构。 - [2026-09-09 20:37:24] 用户希望提前实现数据架构而非临时方案,即使当前阶段功能简单。**Why:** 用户有 10 年后端开发经验,理解数据分层的重要性,希望逐步完善而非推倒重来。**How to apply:** 对于有明确扩展方向的功能(升级、存档、联网),优先设计可扩展架构并临时写死数据,而非等需求明确后再重构。
- [2026-09-10 18:38:45] 正交俯视视角(38-43°)下,垂直墙面几乎看不见(只能看到墙顶),因为墙面平行于视线方向被自身遮挡。适合的装饰物:高耸物体(旗帜、石柱)、有明显剪影的物体(木箱、碎石堆)、对比色强的物体;不适合:低矮平面物(矮墙、栅栏)、细长水平物(绳索)。**Why:** 这是正交俯视视角的固有盲区,无法通过调整角度完全解决(角度太平会牺牲战场全局视野)。**How to apply:** 场景装饰设计时优先使用垂直高度≥2米的物体,避免依赖水平或低矮结构。 - [2026-09-10 18:38:45] 正交俯视视角(38-43°)下,垂直墙面几乎看不见(只能看到墙顶),因为墙面平行于视线方向被自身遮挡。适合的装饰物:高耸物体(旗帜、石柱)、有明显剪影的物体(木箱、碎石堆)、对比色强的物体;不适合:低矮平面物(矮墙、栅栏)、细长水平物(绳索)。**Why:** 这是正交俯视视角的固有盲区,无法通过调整角度完全解决(角度太平会牺牲战场全局视野)。**How to apply:** 场景装饰设计时优先使用垂直高度≥2米的物体,避免依赖水平或低矮结构。
- [2026-09-14 18:24:56] 用户认可"参考游戏是基地离得远、镜头更远、单位推进慢"的观感方向(2026-09-14 数值改慢后反馈"好多了")。**Why:** 之前双方距离近+移速快,单位两三秒就接触,战斗缺乏观察窗口。**How to apply:** 后续地图/镜头调整优先加长双方距离、拉远摄像机;数值宁慢勿快。
### Project ### Project
- [2026-08-28 14:26:56] 弓箭手远程箭矢的设计要求:保持发射时高度水平飞行,命中目标后直接结算伤害并销毁,不产生实体物理击退。**Why:** 当前玩法希望远程攻击是命中判定而非物理推动。**How to apply:** 后续调整箭矢、Projectile 或弓箭手攻击逻辑时保持这一行为。 - [2026-08-28 14:26:56] 弓箭手远程箭矢的设计要求:保持发射时高度水平飞行,命中目标后直接结算伤害并销毁,不产生实体物理击退。**Why:** 当前玩法希望远程攻击是命中判定而非物理推动。**How to apply:** 后续调整箭矢、Projectile 或弓箭手攻击逻辑时保持这一行为。
@@ -41,6 +42,8 @@
- [2026-09-10 18:38:13] 第一关地图(Level1-1.scene)已采用"皇室战争"风格简化设计:移除所有边界矮墙(俯视角只能看到墙顶),改用旗帜+石柱交替排列标识边界(X=±5.5,Z轴8个位置交替);保留中间战道掩体(木箱×6、碎石堆×4);天空改为纯色蓝灰色+线性雾效(起始40m,结束100m);摄像机添加边界Clamp限制(X: -7~7, Z: -32~93,宽松模式),主角靠近边界时摄像机停止跟随;光照调整为从后上方照射(50°, 0°, 0°),角色正面清晰可见。**Why:** 正交俯视视角下垂直墙面几乎看不见(只能看到墙顶),旗帜+石柱有高度和轮廓,视觉识别强。**How to apply:** 后续关卡延续此风格,避免使用低矮平面装饰物。 - [2026-09-10 18:38:13] 第一关地图(Level1-1.scene)已采用"皇室战争"风格简化设计:移除所有边界矮墙(俯视角只能看到墙顶),改用旗帜+石柱交替排列标识边界(X=±5.5,Z轴8个位置交替);保留中间战道掩体(木箱×6、碎石堆×4);天空改为纯色蓝灰色+线性雾效(起始40m,结束100m);摄像机添加边界Clamp限制(X: -7~7, Z: -32~93,宽松模式),主角靠近边界时摄像机停止跟随;光照调整为从后上方照射(50°, 0°, 0°),角色正面清晰可见。**Why:** 正交俯视视角下垂直墙面几乎看不见(只能看到墙顶),旗帜+石柱有高度和轮廓,视觉识别强。**How to apply:** 后续关卡延续此风格,避免使用低矮平面装饰物。
- [2026-09-10 18:38:32] 召唤逻辑已优化碰撞处理:当目标位置被占用时,自动螺旋搜索周围空位(左右搜索8次×前后3层×2方向,约102个候选点);如果所有位置都被占用,自动退还能量豆并取消召唤,避免浪费能量。参数:summonSearchCount=8, summonDepthSearchCount=3, summonSearchStep=0.8f, summonDepthSearchStep=1f。 - [2026-09-10 18:38:32] 召唤逻辑已优化碰撞处理:当目标位置被占用时,自动螺旋搜索周围空位(左右搜索8次×前后3层×2方向,约102个候选点);如果所有位置都被占用,自动退还能量豆并取消召唤,避免浪费能量。参数:summonSearchCount=8, summonDepthSearchCount=3, summonSearchStep=0.8f, summonDepthSearchStep=1f。
- [2026-09-11 14:55:27] 敌人血条颜色问题已修复:WorldHealthBar 在 Awake() 时根据 GameObject.tag 判断颜色,WaveSpawner 在 Instantiate() 后设置标签会导致时序错误(血条已创建成绿色)。解决方案:直接在敌人 Prefab 上设置 Enemy 标签,确保 Awake() 时标签已正确。已修改狼人/蓝色怪物/弓箭猎人/石巨人四个 Prefab。**Why:** 血条颜色在对象创建时确定,后设置标签无法自动刷新。**How to apply:** 后续新增敌人 Prefab 时直接在 Prefab 上设置 Enemy 标签,不依赖运行时代码设置。 - [2026-09-11 14:55:27] 敌人血条颜色问题已修复:WorldHealthBar 在 Awake() 时根据 GameObject.tag 判断颜色,WaveSpawner 在 Instantiate() 后设置标签会导致时序错误(血条已创建成绿色)。解决方案:直接在敌人 Prefab 上设置 Enemy 标签,确保 Awake() 时标签已正确。已修改狼人/蓝色怪物/弓箭猎人/石巨人四个 Prefab。**Why:** 血条颜色在对象创建时确定,后设置标签无法自动刷新。**How to apply:** 后续新增敌人 Prefab 时直接在 Prefab 上设置 Enemy 标签,不依赖运行时代码设置。
- [2026-09-14 16:00:19] 2026-09-13 玩法大转向:从闯关推图改为"建筑生产+自动作战+主角辅助"的对攻玩法(见《召唤之战-核心玩法.md》)。旧闯关玩法由用户在另一台电脑的分支保存,当前 main 按新玩法开发。提交 beab4cf 已实现:GoldSystem/GoldMine 金币经济、Barracks 兵营产兵、BuildingPlacementManager 建筑摆放、CharacterData 新增 productionInterval/buildingCost、UnitBase/Projectile 支持攻击建筑、CoreBattleTest.scene 验证场景、CameraFollow 重写(WASD/鼠标/触摸拖动+缩放)。胜负目标改为摧毁对方主建筑。**How to apply:** 后续开发以建筑/经济/产兵系统为主线,能量豆召唤相关旧逻辑仅作为历史参考。
- [2026-09-14 18:24:46] 2026-09-14 性能实测(9 个角色 Prefab):大多数 5~8k 三角面/22 骨骼/512 贴图,性能没问题;两个待处理异常——蓝色怪物 34408 面(是量产低价单位,建议减面到 8k 左右)、法师贴图 2048x2048(建议缩到 512)。另建议设计层限制每方场上单位上限(约15)保性能与 2.5D 可读性,并确认方向光关实时阴影,最终以低端安卓真机小游戏包为准。**How to apply:** 后续做性能优化或新增量产型敌人时按此基准控制面数。
### Reference ### Reference
- [2026-09-09 12:23:17] 飞行物脚本 Projectile.cs 负责锁敌追踪、碰撞检测和伤害结算。箭矢预制体 ArrowProjectile_White.prefab 已配置 Projectile 组件和 CapsuleCollider(Is Trigger)。远程单位需在攻击动画松弦帧添加 OnAttackEffect Event,并在 UnitBase 中设置 attackType=RangedProjectile、projectilePrefab、projectileSpawnPoint。 - [2026-09-09 12:23:17] 飞行物脚本 Projectile.cs 负责锁敌追踪、碰撞检测和伤害结算。箭矢预制体 ArrowProjectile_White.prefab 已配置 Projectile 组件和 CapsuleCollider(Is Trigger)。远程单位需在攻击动画松弦帧添加 OnAttackEffect Event,并在 UnitBase 中设置 attackType=RangedProjectile、projectilePrefab、projectileSpawnPoint。
+5 -3
View File
@@ -50,9 +50,11 @@
### 3.1 主角职责 ### 3.1 主角职责
- 主角可以普通攻击; - 主角可以普通攻击;
- 主角可以由玩家手动移动; - 主角全程由 AI 自动控制,不支持点击地面手动移动(避免与拖动屏幕、建筑放置的输入冲突);
- 玩家没有主动操作时,主角自动巡逻并攻击附近敌人; - 没有敌人时,主角在己方主建筑周围小范围来回巡逻;
- 主角可以手动释放技能; - 索敌范围较小:只有敌人进入主角附近一定范围内才会被发现并攻击,不主动远离基地寻找敌人;
- 巡逻和追击都被限制在基地附近区域,主角不能完全离开基地;
- 主角可以手动释放技能(通过 UI 技能按钮,不占用地面点击操作);
- 主角技能冷却较长,主要用于辅助、补刀和改变局部战况; - 主角技能冷却较长,主要用于辅助、补刀和改变局部战况;
- 主角不是主要战斗力,不能替代建筑生产的军队; - 主角不是主要战斗力,不能替代建筑生产的军队;
- 主角的价值是提升玩家参与感,并在关键时刻提供战术干预。 - 主角的价值是提升玩家参与感,并在关键时刻提供战术干预。
+91
View File
@@ -0,0 +1,91 @@
# 角色数值设计
> 当前版本:第一版数值基准(配合建筑对攻玩法)
>
> 数值目标:放慢战斗节奏、拉长存活时间,让"建造时机"和"兵种组合"有观察和反应的窗口。
## 一、设计基准
以**人类士兵**为锚点单位(DPS = 25),确定全表数值关系:
| 目标 | 数值 |
|---|---|
| 基础近战互杀时间 | 12~15 秒 |
| 脆皮远程被近战击杀 | 6~9 秒 |
| 坦克(盾卫)承受基础近战输出 | 35 秒以上 |
| 超重坦克(石巨人)承受基础近战输出 | 55 秒以上 |
| 单位穿越中场距离(约 25~30 单位) | 12~17 秒 |
衍生规则:
- 攻击力 = 单次伤害;DPS = 攻击力 ÷ 普攻间隔;
- 越贵的单位 DPS/生存越强,但必须有明显短板(速度、攻速或费用);
- 远程单位攻击范围 5~6,近战 1.0~1.2;
- 移动速度整体压在 1.1~2.5,宁可慢不可快(快了会放大两边距离近的问题)。
## 二、基础属性表(1 级)
| 角色 | 定位 | 生命 | 攻击力 | 普攻间隔(秒/次) | DPS | 攻击范围 | 移动速度 |
|---|---|---|---|---|---|---|---|
| 人类士兵 | 基础近战 | 300 | 30 | 1.2 | 25 | 1.0 | 1.8 |
| 盾卫 | 重装坦克 | 900 | 20 | 1.8 | 11 | 1.2 | 1.4 |
| 弓箭手 | 远程输出 | 200 | 40 | 1.5 | 27 | 6.0 | 1.8 |
| 法师 | 远程爆发 | 160 | 90 | 2.4 | 38 | 6.0 | 1.5 |
| 精锐士兵 | 近战强袭 | 420 | 55 | 1.6 | 34 | 1.2 | 2.0 |
| 弓箭猎人 | 持续远程 | 200 | 15 | 0.9 | 17 | 5.0 | 2.2 |
| 狂暴狼人 | 快速突袭 | 380 | 32 | 1.0 | 32 | 1.1 | 2.5 |
| 蓝色怪物 | 低价肉盾 | 340 | 16 | 1.6 | 10 | 1.0 | 1.6 |
| 石巨人 | 超重坦克 | 1400 | 110 | 2.8 | 39 | 1.1 | 1.1 |
注:Prefab 实际有 9 个角色(第一章 5 个 + 第二章 4 个),全部列出,未上场的可先不用。
## 三、兵营生产与费用
| 角色 | 生产间隔(秒/个) | 建造费用(金币) |
|---|---|---|
| 人类士兵 | 6 | 100 |
| 蓝色怪物 | 5 | 80 |
| 弓箭手 | 8 | 120 |
| 弓箭猎人 | 8 | 120 |
| 盾卫 | 10 | 150 |
| 狂暴狼人 | 10 | 150 |
| 法师 | 12 | 150 |
| 精锐士兵 | 12 | 180 |
| 石巨人 | 20 | 250 |
经济节奏参考:金矿产出速率定稿后,按"一座金矿约支撑 1.5 个中级兵营持续运转"回推。
## 四、击杀时间参考(以人类士兵 DPS 25 进攻)
| 目标 | 击杀时间 |
|---|---|
| 法师(160) | 6.4 秒 |
| 弓箭手 / 弓箭猎人(200) | 8 秒 |
| 人类士兵(300) | 12 秒 |
| 蓝色怪物(340) | 13.6 秒 |
| 狂暴狼人(380) | 15.2 秒 |
| 精锐士兵(420) | 16.8 秒 |
| 盾卫(900) | 36 秒 |
| 石巨人(1400) | 56 秒 |
## 五、升级成长建议
- 生命成长:每级约基础值的 10%(如人类士兵 +30/级);
- 攻击成长:每级约基础值的 8%(如人类士兵 +2.5/级,取整 +3);
- 移动速度:不随等级成长;
- 关键等级节点(10/20/30 级)再考虑技能强化,不体现在基础数值里。
## 六、数值落地位置(改数值看这里)
| 数值 | 所在文件 |
|---|---|
| 生命 / 攻击力 / 移动速度 / 生产间隔 / 建造费用 | `Assets/Resources/Data/<角色名>Data.asset`(CharacterData) |
| 普攻间隔(attackDuration)/ 攻击范围(attackRange)/ 攻击类型 | `Assets/Prefabs/第N章/<角色名>.prefab`(UnitBase 组件) |
| 兵营生产逻辑 | `Barracks.cs`(按 1 级属性初始化单位) |
修改任一角色时,本表同步更新。
## 七、遗留调整项(数值之外)
- 当前 CoreBattleTest 地图双方距离仍偏近、镜头偏近,参考游戏是"基地远离 + 镜头更远"的观感;数值放缓后如果仍显拥挤,优先做地图加长和摄像机 bounds 扩大,而不是继续压低移速;
- 主角(召唤师)数值不在本表内,主角定位是辅助,等装备系统定型后单独设计。