返回提交历史
Modified
Common/DeathDomainVisualSystem.cs
+58
-36
Modified
Common/SickleTrailPrimitiveSystem.cs
+30
-56
Modified
Localization/en-US.hjson
+4
-4
Modified
Localization/zh-Hans.hjson
+4
-4
Modified
UI/DeathAltarUISystem.cs
+578
-215
XFEstudio/DeathMod
死亡祭坛 UI 响应式适配与视觉优化
- 新增 layoutScale,UI 元素随窗口缩放自适应布局 - 抽象按钮、选择器等为复用组件,统一交互与风格 - 配方条目支持多行多列材料展示,信息完整显示 - 技能节点文本分离,底部新增状态进度条 - 所有装饰元素动态缩放,适配高分辨率 - 抽取绘制逻辑至 AltarDrawHelpers,优化代码结构 - 修复部分文本未缩放问题,语言文件格式化
846e09b
代码差异
5 个文件
+674
-315
@@ -21,12 +21,12 @@ internal class DeathDomainVisualSystem : ModSystem
21
21
22
22
public override void Load()
23
23
{
24
On_Main.DrawBackground += DrawDomainBackdropsAfterVanillaBackground;
24
On_Main.DoDraw_WallsTilesNPCs += DrawDomainBackdropsBeforeWallsAndTiles;
25
25
}
26
26
27
27
public override void Unload()
28
28
{
29
On_Main.DrawBackground -= DrawDomainBackdropsAfterVanillaBackground;
29
On_Main.DoDraw_WallsTilesNPCs -= DrawDomainBackdropsBeforeWallsAndTiles;
30
30
}
31
31
32
32
public override void PostUpdateEverything()
@@ -383,58 +383,56 @@ internal class DeathDomainVisualSystem : ModSystem
383
383
DrawDomainRing(batch, pixel, center, playerDomainRadius, mastery, animation, drawBackdrop: false);
384
384
}
385
385
386
private void DrawDomainBackdropsAfterVanillaBackground(On_Main.orig_DrawBackground orig, Main self)
386
private void DrawDomainBackdropsBeforeWallsAndTiles(On_Main.orig_DoDraw_WallsTilesNPCs orig, Main self)
387
387
{
388
DrawDomainBackdrops();
388
389
orig(self);
390
}
391
392
private void DrawDomainBackdrops()
393
{
389
394
if (Main.gameMenu || !Main.LocalPlayer.active)
390
395
return;
391
396
392
397
bool anyFullScreen = false;
393
398
bool anyClipped = false;
399
float fullScreenMastery = 0f;
400
float fullScreenReveal = 0f;
394
401
for (int index = 0; index < Main.maxPlayers; index++)
395
402
{
396
403
if (domainAnimation[index] <= 0.001f || !Main.player[index].active)
397
404
continue;
398
anyFullScreen |= cachedFullScreen[index];
399
anyClipped |= !cachedFullScreen[index];
405
if (cachedFullScreen[index])
406
{
407
anyFullScreen = true;
408
fullScreenMastery = Math.Max(fullScreenMastery, GetMasteryProgress(cachedMastery[index]));
409
fullScreenReveal = Math.Max(
410
fullScreenReveal,
411
MathHelper.SmoothStep(0f, 1f, domainAnimation[index]));
412
}
413
else
414
{
415
anyClipped = true;
416
}
400
417
}
401
418
if (!anyFullScreen && !anyClipped)
402
419
return;
403
420
421
// This hook runs during final world composition, after Terraria has drawn
422
// (or composited) its background and before it starts walls and tiles.
423
// Reuse the active world SpriteBatch so the domain is never captured in
424
// the scrolling background render target.
404
425
SpriteBatch batch = Main.spriteBatch;
405
426
if (anyFullScreen)
406
427
{
407
batch.Begin(
408
SpriteSortMode.Deferred,
409
BlendState.AlphaBlend,
410
SamplerState.LinearClamp,
411
DepthStencilState.None,
412
RasterizerState.CullNone,
413
null,
414
Matrix.Identity);
415
for (int index = 0; index < Main.maxPlayers; index++)
416
{
417
if (!cachedFullScreen[index] || domainAnimation[index] <= 0.001f || !Main.player[index].active)
418
continue;
419
420
DrawFullScreenBackdrop(
421
batch,
422
GetMasteryProgress(cachedMastery[index]),
423
MathHelper.SmoothStep(0f, 1f, domainAnimation[index]));
424
}
425
batch.End();
428
// The backdrop is camera-relative, so multiple mastered domains share
429
// one strongest presentation instead of darkening the entire scene once
430
// per player.
431
DrawFullScreenBackdrop(batch, fullScreenMastery, fullScreenReveal);
426
432
}
427
433
428
434
if (anyClipped)
429
435
{
430
batch.Begin(
431
SpriteSortMode.Deferred,
432
BlendState.AlphaBlend,
433
SamplerState.LinearClamp,
434
DepthStencilState.None,
435
RasterizerState.CullNone,
436
null,
437
Main.GameViewMatrix.TransformationMatrix);
438
436
for (int index = 0; index < Main.maxPlayers; index++)
439
437
{
440
438
Player player = Main.player[index];
@@ -463,7 +461,6 @@ internal class DeathDomainVisualSystem : ModSystem
463
461
masteryProgress,
464
462
reveal);
465
463
}
466
batch.End();
467
464
}
468
465
}
469
466
@@ -502,6 +499,7 @@ internal class DeathDomainVisualSystem : ModSystem
502
499
float referenceDiameter = (160f + DeathNecklace.RadiusGrowthPerLevel * (DeathNecklace.MaxCoreLevel - 1)) * 2f;
503
500
float horizontalScale = Main.screenWidth / referenceDiameter;
504
501
float verticalScale = Main.screenHeight / referenceDiameter;
502
GetUntransformedScreenBounds(out Vector2 destination, out float destinationWidth, out float destinationHeight);
505
503
for (int layer = 0; layer < 3; layer++)
506
504
{
507
505
float opacity = layer switch
@@ -517,9 +515,9 @@ internal class DeathDomainVisualSystem : ModSystem
517
515
DrawBackdropBand(
518
516
batch,
519
517
texture,
520
Vector2.Zero,
521
Main.screenWidth,
522
Main.screenHeight,
518
destination,
519
destinationWidth,
520
destinationHeight,
523
521
sourceOffsetX,
524
522
sourceOffsetY,
525
523
viewWidth,
@@ -528,6 +526,30 @@ internal class DeathDomainVisualSystem : ModSystem
528
526
}
529
527
}
530
528
529
private static void GetUntransformedScreenBounds(
530
out Vector2 destination,
531
out float destinationWidth,
532
out float destinationHeight)
533
{
534
// This pass shares Terraria's active world SpriteBatch. Map the physical
535
// viewport back through that batch's transform so the full-screen domain
536
// still lands on the exact screen bounds at every game zoom. Taking all
537
// four corners also keeps the viewport covered if another mod rotates or
538
// shears the world view.
539
Matrix inverse = Matrix.Invert(Main.GameViewMatrix.TransformationMatrix);
540
Vector2 topLeft = Vector2.Transform(Vector2.Zero, inverse);
541
Vector2 topRight = Vector2.Transform(new Vector2(Main.screenWidth, 0f), inverse);
542
Vector2 bottomLeft = Vector2.Transform(new Vector2(0f, Main.screenHeight), inverse);
543
Vector2 bottomRight = Vector2.Transform(new Vector2(Main.screenWidth, Main.screenHeight), inverse);
544
float left = Math.Min(Math.Min(topLeft.X, topRight.X), Math.Min(bottomLeft.X, bottomRight.X));
545
float right = Math.Max(Math.Max(topLeft.X, topRight.X), Math.Max(bottomLeft.X, bottomRight.X));
546
float top = Math.Min(Math.Min(topLeft.Y, topRight.Y), Math.Min(bottomLeft.Y, bottomRight.Y));
547
float bottom = Math.Max(Math.Max(topLeft.Y, topRight.Y), Math.Max(bottomLeft.Y, bottomRight.Y));
548
destination = new Vector2(left, top);
549
destinationWidth = Math.Max(1f, right - left);
550
destinationHeight = Math.Max(1f, bottom - top);
551
}
552
531
553
private static void GetBackdropSource(
532
554
int layer,
533
555
Texture2D texture,
@@ -4,6 +4,7 @@ using Microsoft.Xna.Framework.Graphics;
4
4
using System;
5
5
using System.Collections.Generic;
6
6
using Terraria;
7
using Terraria.GameContent;
7
8
using Terraria.ModLoader;
8
9
9
10
namespace DeathMod.Common;
@@ -12,7 +13,6 @@ namespace DeathMod.Common;
12
13
internal sealed class SickleTrailPrimitiveSystem : ModSystem
13
14
{
14
15
private const int CurveSubdivisions = 4;
15
private static BasicEffect? trailEffect;
16
16
17
17
internal static void DrawTrail(IReadOnlyList<Vector2> controlPoints, NormalSickle sickle, bool alternateAttack)
18
18
{
@@ -23,48 +23,13 @@ internal sealed class SickleTrailPrimitiveSystem : ModSystem
23
23
if (curve.Count < 2)
24
24
return;
25
25
26
BasicEffect effect = trailEffect ??= new BasicEffect(Main.instance.GraphicsDevice)
27
{
28
VertexColorEnabled = true,
29
TextureEnabled = false
30
};
31
VertexPositionColor[] outer = BuildStrip(curve, sickle, alternateAttack, core: false);
32
VertexPositionColor[] core = BuildStrip(curve, sickle, alternateAttack, core: true);
33
if (outer.Length < 4 || core.Length < 4)
34
return;
35
36
GraphicsDevice graphics = Main.instance.GraphicsDevice;
37
Main.spriteBatch.End();
38
graphics.BlendState = BlendState.AlphaBlend;
39
graphics.DepthStencilState = DepthStencilState.None;
40
graphics.RasterizerState = RasterizerState.CullNone;
41
effect.World = Main.GameViewMatrix.TransformationMatrix;
42
effect.View = Matrix.Identity;
43
effect.Projection = Matrix.CreateOrthographicOffCenter(0f, Main.screenWidth, Main.screenHeight, 0f, 0f, 1f);
44
45
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
46
{
47
pass.Apply();
48
graphics.DrawUserPrimitives(PrimitiveType.TriangleStrip, outer, 0, outer.Length - 2);
49
graphics.DrawUserPrimitives(PrimitiveType.TriangleStrip, core, 0, core.Length - 2);
50
}
51
52
Main.spriteBatch.Begin(
53
SpriteSortMode.Deferred,
54
BlendState.AlphaBlend,
55
SamplerState.PointClamp,
56
DepthStencilState.None,
57
RasterizerState.CullNone,
58
null,
59
Main.GameViewMatrix.TransformationMatrix);
60
}
61
62
public override void Unload()
63
{
64
BasicEffect? oldEffect = trailEffect;
65
trailEffect = null;
66
if (oldEffect is not null && !Main.dedServ)
67
Main.QueueMainThreadAction(oldEffect.Dispose);
26
// Projectile.PreDraw runs inside Terraria's shared SpriteBatch. Ending that
27
// batch and applying a BasicEffect here leaves FNA's vertex shader state in
28
// a format that the following player draw cannot consume. Draw the ribbon
29
// with the already active batch so this effect owns no shared render state.
30
Texture2D pixel = TextureAssets.MagicPixel.Value;
31
DrawCurveLayer(pixel, curve, sickle, alternateAttack, core: false);
32
DrawCurveLayer(pixel, curve, sickle, alternateAttack, core: true);
68
33
}
69
34
70
35
private static List<Vector2> BuildSmoothCurve(IReadOnlyList<Vector2> controlPoints)
@@ -90,21 +55,25 @@ internal sealed class SickleTrailPrimitiveSystem : ModSystem
90
55
return result;
91
56
}
92
57
93
private static VertexPositionColor[] BuildStrip(
58
private static void DrawCurveLayer(
59
Texture2D pixel,
94
60
IReadOnlyList<Vector2> points,
95
61
NormalSickle sickle,
96
62
bool alternateAttack,
97
63
bool core)
98
64
{
99
VertexPositionColor[] vertices = new VertexPositionColor[points.Count * 2];
100
65
float attackScale = alternateAttack ? 1.25f : 1f;
101
for (int index = 0; index < points.Count; index++)
66
for (int index = 0; index < points.Count - 1; index++)
102
67
{
103
float progress = index / (points.Count - 1f);
68
Vector2 start = points[index];
69
Vector2 end = points[index + 1];
70
Vector2 difference = end - start;
71
float length = difference.Length();
72
if (length <= 0.2f)
73
continue;
74
75
float progress = (index + 0.5f) / (points.Count - 1f);
104
76
float strength = progress * progress * (3f - 2f * progress);
105
Vector2 previous = points[Math.Max(0, index - 1)];
106
Vector2 next = points[Math.Min(points.Count - 1, index + 1)];
107
Vector2 normal = (next - previous).SafeNormalize(Vector2.UnitX).RotatedBy(MathHelper.PiOver2);
108
77
109
78
float width = sickle.TrailWidth * attackScale * MathHelper.Lerp(0.08f, 1f, strength);
110
79
if (sickle.SwingStyle == SickleSwingStyle.Rising)
@@ -120,12 +89,17 @@ internal sealed class SickleTrailPrimitiveSystem : ModSystem
120
89
Color color = core
121
90
? Color.Lerp(sickle.SickleSecondaryColor, Color.White, strength * 0.18f)
122
91
: Color.Lerp(sickle.SickleColor, sickle.SickleSecondaryColor, strength * 0.22f);
123
color *= opacity;
124
Vector2 screenPoint = points[index] - Main.screenPosition;
125
Vector2 halfWidth = normal * Math.Max(0.5f, width * 0.5f);
126
vertices[index * 2] = new VertexPositionColor(new Vector3(screenPoint - halfWidth, 0f), color);
127
vertices[index * 2 + 1] = new VertexPositionColor(new Vector3(screenPoint + halfWidth, 0f), color);
92
float overlap = Math.Min(2f, width * 0.25f);
93
Vector2 midpoint = (start + end) * 0.5f - Main.screenPosition;
94
Main.EntitySpriteDraw(
95
pixel,
96
midpoint,
97
null,
98
color * opacity,
99
difference.ToRotation(),
100
pixel.Size() * 0.5f,
101
new Vector2((length + overlap) / pixel.Width, Math.Max(1f, width) / pixel.Height),
102
SpriteEffects.None);
128
103
}
129
return vertices;
130
104
}
131
105
}
@@ -165,14 +165,14 @@ Mods: {
165
165
AltarTitle: Death Altar · Soul Skill Tree
166
166
RecipeHelpButton: Recipe Help
167
167
BackToSkillTree: Back to Tree
168
RecipeHelpTitle: "DeathMod Recipes ({0})"
168
RecipeHelpTitle: DeathMod Recipes ({0})
169
169
NoModRecipes: No DeathMod crafting recipes are currently available
170
RecipeCraftingStation: "Station: {0}"
170
RecipeCraftingStation: Station: {0}
171
171
RecipeStationByHand: By hand
172
172
RecipeStationWorkbench: Work Bench
173
173
RecipeStationAnvil: Iron or Lead Anvil
174
174
RecipeStationDeathAltar: Death Altar
175
RecipeStationUnknown: "Unknown station #{0}"
175
RecipeStationUnknown: Unknown station #{0}
176
176
SelectEquipment: Available equipment
177
177
NoUpgradeableItems: No upgradeable equipment in inventory or equipment slots
178
178
InventoryLocation: Inventory
@@ -195,7 +195,7 @@ Mods: {
195
195
UpgradeCosts: {
196
196
Souls: "{0} character souls"
197
197
SoulEssence: "{0} Soul Essence"
198
SoulEssenceRefund: "Refunds {0} Soul Essence"
198
SoulEssenceRefund: Refunds {0} Soul Essence
199
199
}
200
200
201
201
UpgradeValues: {
@@ -165,14 +165,14 @@ Mods: {
165
165
AltarTitle: 死神祭坛 · 灵魂技能树
166
166
RecipeHelpButton: 合成帮助
167
167
BackToSkillTree: 返回技能树
168
RecipeHelpTitle: "死神模组合成表({0})"
168
RecipeHelpTitle: 死神模组合成表({0})
169
169
NoModRecipes: 暂无可用的本模组合成配方
170
RecipeCraftingStation: "制作站:{0}"
170
RecipeCraftingStation: 制作站:{0}
171
171
RecipeStationByHand: 徒手制作
172
172
RecipeStationWorkbench: 工作台
173
173
RecipeStationAnvil: 铁砧或铅砧
174
174
RecipeStationDeathAltar: 死神祭坛
175
RecipeStationUnknown: "未知制作站 #{0}"
175
RecipeStationUnknown: 未知制作站 #{0}
176
176
SelectEquipment: 可改造装备
177
177
NoUpgradeableItems: 背包和装备栏中没有可改造装备
178
178
InventoryLocation: 背包
@@ -195,7 +195,7 @@ Mods: {
195
195
UpgradeCosts: {
196
196
Souls: "{0} 人物灵魂"
197
197
SoulEssence: "{0} 灵魂精华"
198
SoulEssenceRefund: "返还 {0} 灵魂精华"
198
SoulEssenceRefund: 返还 {0} 灵魂精华
199
199
}
200
200
201
201
UpgradeValues: {