XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

DeathMod

【Terraria】DeathMod (now aka Soul Harvest)

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/DeathMod

死亡祭坛 UI 响应式适配与视觉优化

- 新增 layoutScale,UI 元素随窗口缩放自适应布局 - 抽象按钮、选择器等为复用组件,统一交互与风格 - 配方条目支持多行多列材料展示,信息完整显示 - 技能节点文本分离,底部新增状态进度条 - 所有装饰元素动态缩放,适配高分辨率 - 抽取绘制逻辑至 AltarDrawHelpers,优化代码结构 - 修复部分文本未缩放问题,语言文件格式化

846e09b
XFE工作室室长 <mail@xfegzs.com>
提交于

代码差异

5 个文件 +674 -315
Modified Common/DeathDomainVisualSystem.cs +58 -36
@@ -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,
Modified Common/SickleTrailPrimitiveSystem.cs +30 -56
@@ -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 }
Modified Localization/en-US.hjson +4 -4
@@ -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: {
Modified Localization/zh-Hans.hjson +4 -4
@@ -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: {