feat(gameplay): 更新法师主角预制体和法师生怪功能

- 添加了召唤师主角预制体文件
- 更新法师生怪预制体,替换为新的火球弹道效果
- 修复投射物斜向发射问题,统一使用Quaternion.identity旋转

fix(projectile): 修复远程投射物旋转和目标锁定问题

- 修正Projectile组件,让投射物保持水平飞行并始终朝向目标
- 修改UnitBase中投射物实例化逻辑,不再传递spawn point的旋转
- 解决法师/弓箭手attackPos骨骼动画影响导致火球和箭矢斜向发射的问题

refactor(texture): 优化火焰球纹理导入设置

- 禁用mipmap以提升显示效果
- 启用alpha透明度支持
- 将纹理类型改为Sprite模式
- 启用wrap模式改善渲染表现

docs(update): 记录远程弹道视觉方案调整和投射物修复说明
```
This commit is contained in:
huadexuan committed 2026-09-22 17:55:35 +08:00
1 parent 420cec1a00
commit 1ec6f52172
23 files changed
+10300 -25

No files matched your search

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: DC4YvX78V3IbYvHN9oe/X4pxmq+FMdTgNOnS7vYmv+hkGer3DYI8CuI=
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,42 @@
%YAML 1.1
%TAG !u! tag:yousandi.cn,2023:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: "\u6CD5\u5E08\u706B\u7403\u62D6\u5C3E"
m_Shader: {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _AlphaTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 2800000, guid: b78cdc381d845964db705ec23f2821a1, type: 3}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- PixelSnap: 0
- _EnableExternalAlpha: 0
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _Flip: {r: 1, g: 1, b: 1, a: 1}
- _RendererColor: {r: 1, g: 1, b: 1, a: 1}
m_BuildTextureStacks: []
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: DytMsCz+U3LyXgmVhjq79+ez2n77fsfkCyxGgPKlA843JkSxKUPP4zk=
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,42 @@
%YAML 1.1
%TAG !u! tag:yousandi.cn,2023:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: "\u6CD5\u5E08\u706B\u7403\u6838\u5FC3"
m_Shader: {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _AlphaTex:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
- _MainTex:
m_Texture: {fileID: 2800000, guid: 5663d75a82b776f42ac385ae0eeebe2b, type: 3}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats:
- PixelSnap: 0
- _EnableExternalAlpha: 0
m_Colors:
- _Color: {r: 1, g: 1, b: 1, a: 1}
- _Flip: {r: 1, g: 1, b: 1, a: 1}
- _RendererColor: {r: 1, g: 1, b: 1, a: 1}
m_BuildTextureStacks: []
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: Wi8XvCyrAHiiQOF+hyy3qlp5tZI7jkSSux27GZwnDqF4Au7CCsXbves=
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 630 KiB

After

Width:  |  Height:  |  Size: 475 KiB

+5 -5
View File
@@ -6,7 +6,7 @@ TextureImporter:
serializedVersion: 14
mipmaps:
mipMapMode: 0
enableMipMap: 1
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
@@ -40,8 +40,8 @@ TextureImporter:
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
@@ -55,9 +55,9 @@ TextureImporter:
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 0
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: DC9JvCL8W3/trwokEvh4wPRyMaLN8r7D6fFTKWsnQnn7WT8oD8P7Jtk=
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: Xiwb4SL8W3Lz7BX42lIRI87AlIc91UC/USzKhxsnv0dLv+dyQqQpPew=
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+19 -17
View File
@@ -120,10 +120,6 @@ PrefabInstance:
type: 3}
insertIndex: -1
addedObject: {fileID: 6516315820976732023}
- targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 6b859b567d8ba4a7a87c03aa4b87c18c,
type: 3}
insertIndex: -1
addedObject: {fileID: 6843810197477671806}
- targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 6b859b567d8ba4a7a87c03aa4b87c18c,
type: 3}
insertIndex: -1
@@ -136,6 +132,10 @@ PrefabInstance:
type: 3}
insertIndex: -1
addedObject: {fileID: 3317273023278999503}
- targetCorrespondingSourceObject: {fileID: 919132149155446097, guid: 6b859b567d8ba4a7a87c03aa4b87c18c,
type: 3}
insertIndex: -1
addedObject: {fileID: 8307978848533769194}
m_SourcePrefab: {fileID: 100100000, guid: 6b859b567d8ba4a7a87c03aa4b87c18c, type: 3}
--- !u!1 &106035464483675767 stripped
GameObject:
@@ -228,19 +228,6 @@ MonoBehaviour:
damageNumberRiseHeight: 1.2
damageNumberSpawnHeight: 1.5
damageNumberSize: 0.09
--- !u!114 &6843810197477671806
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 106035464483675767}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: e6f40b66b50871440a97763c44d480a4, type: 3}
m_Name:
m_EditorClassIdentifier:
teamId: 0
--- !u!114 &6944470424331181836
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -298,6 +285,21 @@ CapsuleCollider:
m_Height: 2
m_Direction: 1
m_Center: {x: 0, y: 1, z: 0}
--- !u!114 &8307978848533769194
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 106035464483675767}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 8250ea299184b428ea8f0fa765c39264, type: 3}
m_Name:
m_EditorClassIdentifier:
showStunIndicator: 1
indicatorHeight: 0.35
stunIndicatorPrefab: {fileID: 0}
--- !u!4 &1444656758206389486 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 1852576806548013000, guid: 6b859b567d8ba4a7a87c03aa4b87c18c,
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: WSsesn/8Wnh2uvWj2sd43LDLdErFJj1JjOw73ypfmeserMTwOVqLG7A=
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

@@ -0,0 +1,177 @@
fileFormatVersion: 2
guid: XX0X5n7+UHMKwlIOpYF8wyFF/Jg7po7ctCZ3PKM3kdtsw1AY7UqPm5c=
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 14
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
webStreaming: 0
priorityLevel: 0
uploadedMode: 2
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 256
maxPlaceholderSize: 32
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
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
- serializedVersion: 3
buildTarget: Server
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
- serializedVersion: 3
buildTarget: WebGL
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
- serializedVersion: 3
buildTarget: WeixinMiniGame
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
- 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:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
doOverrideTextureManagerOperations: 0
platformOperationGroupSettings:
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

@@ -0,0 +1,177 @@
fileFormatVersion: 2
guid: CnwZtn6qVioanhoUDVzxUJXRJ+48ntjWvID/HYZWmH0GuYV7Hqsk7Bo=
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 14
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
webStreaming: 0
priorityLevel: 0
uploadedMode: 2
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 256
maxPlaceholderSize: 32
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
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
- serializedVersion: 3
buildTarget: Server
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
- serializedVersion: 3
buildTarget: WebGL
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
- serializedVersion: 3
buildTarget: WeixinMiniGame
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
- 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:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
doOverrideTextureManagerOperations: 0
platformOperationGroupSettings:
userData:
assetBundleName:
assetBundleVariant:
+1 -1
View File
@@ -215,7 +215,7 @@ MonoBehaviour:
attackPower: 90
attackDuration: 2.4
attackType: 2
projectilePrefab: {fileID: 3410584434205606752, guid: ea38dfd40c228456e8e1c98952673009,
projectilePrefab: {fileID: 4171736658291362950, guid: af4d8a89a285d9f4b831a024be8e34f4,
type: 3}
projectileSpawnPoint: {fileID: 7498589538563774666}
projectileSpeed: 20
Binary file not shown.
+3
View File
@@ -71,6 +71,9 @@ public class Projectile : MonoBehaviour
return;
}
// 投射物保持水平飞行,并让根节点前方始终指向当前目标。
transform.rotation = Quaternion.LookRotation(direction, Vector3.up);
if (Physics.Raycast(transform.position, direction, out RaycastHit hit, distance))
{
TryHit(hit.collider.gameObject);
+2 -2
View File
@@ -944,7 +944,7 @@ public class UnitBase : MonoBehaviour
GameObject projectileObject = Instantiate(
projectilePrefab,
projectileSpawnPoint.position,
projectileSpawnPoint.rotation);
Quaternion.identity);
Projectile projectile = projectileObject.GetComponent<Projectile>();
if (projectile != null)
{
@@ -967,7 +967,7 @@ public class UnitBase : MonoBehaviour
GameObject projectileObject = Instantiate(
projectilePrefab,
projectileSpawnPoint.position,
projectileSpawnPoint.rotation);
Quaternion.identity);
Projectile projectile = projectileObject.GetComponent<Projectile>();
if (projectile != null)
{
+2
View File
@@ -86,6 +86,8 @@
- [2026-09-21 11:10:20] 2026-09-21 UnitBase 目标锁定规则已更新:单位只有在没有有效目标时才寻敌;锁定后移动和攻击期间保持当前目标,新敌人不打断;当前目标死亡、失活或攻击状态下离开攻击范围时才解除锁定,下一帧重新寻敌。单位与建筑目标统一使用同一规则,并通过 WorldHealthBar.IsDead 判断死亡。
- [2026-09-21 11:16:19] 2026-09-21 UnitBase 目标锁定规则修正:攻击状态一旦进入就不再寻敌,保持当前目标,避免多个单位奔向同一建筑时擦肩后切换目标。仅当当前目标死亡/失活,或因技能等原因不再满足攻击距离时才退出攻击状态;退出后才重新寻敌。单位和建筑目标统一使用同一规则。
- [2026-09-21 14:26:28] 2026-09-21 据点目标规则修复:CrystalMineCapture 占领后会实时把 UnitTeam.teamId 改为蓝方/红方/中立;UnitBase.IsTargetValid 必须同时检查目标存活状态和目标是否仍属于敌方。出生使命建筑若已被己方占领或变为中立,应立即失效并回退攻击敌方主城堡。
- [2026-09-22 10:58:17] 2026-09-21 远程弹道视觉方案调整建议:普通远程弹道继续使用 Projectile.cs 处理锁敌、飞行、碰撞和伤害;火球等圆形视觉优先使用 SpriteRenderer 始终朝向摄像机,不强制使用 Quad,以避免贴图方向难调。若有明显尾焰,再拆分核心 billboard 与按飞行方向旋转的尾焰层。
- [2026-09-22 13:39:47] 2026-09-22 投射物斜向问题修复:UnitBase 发射普通攻击和技能投射物时,不再把骨骼下 attackPos.rotation 传给 Projectile 根节点,统一使用 Quaternion.identity;attackPos 只负责提供生成位置。原因是法师/弓箭手的 attackPos 挂在手骨骼下,会继承动画旋转导致火球和箭矢斜着出现。
### Reference
- [2026-09-09 12:23:17] 飞行物脚本 Projectile.cs 负责锁敌追踪、碰撞检测和伤害结算。箭矢预制体 ArrowProjectile_White.prefab 已配置 Projectile 组件和 CapsuleCollider(Is Trigger)。远程单位需在攻击动画松弦帧添加 OnAttackEffect Event,并在 UnitBase 中设置 attackType=RangedProjectile、projectilePrefab、projectileSpawnPoint。