fix: 火山玩法回退

This commit is contained in:
2026-08-02 13:43:06 +08:00
parent 48dcc9ec3a
commit 56337c5173
13 changed files with 49 additions and 315 deletions
@@ -366,18 +366,13 @@ namespace AibisDream.EditorTests.Huoshan
}
[Test]
public void ParticleGeometry_UsesVisualEdgeDistanceAndShortestSeparationAxis()
public void ParticleGeometry_UsesShortestSeparationAxisForWordBounds()
{
var left = new Bounds(Vector3.zero, new Vector3(4f, 1f, 0.01f));
var right = new Bounds(
new Vector3(4.15f, 0f, 0f),
new Vector3(3.8f, 0f, 0f),
new Vector3(4f, 1f, 0.01f));
Assert.That(
ExpressionParticleGeometry.BoundsDistance(left, right),
Is.EqualTo(0.15f).Within(0.0001f));
right.center = new Vector3(3.8f, 0f, 0f);
Assert.That(
ExpressionParticleGeometry.TryGetSeparation(
left,
@@ -20,7 +20,6 @@ MonoBehaviour:
floatingFontScale: 1
nonTargetCountScale: 1
wordSpacing: 0.18
visualEdgeConnectionDistance: 0
minimumReadablePhraseScale: 0.65
- localeCode: ja-JP
unitMode: 0
@@ -29,7 +28,6 @@ MonoBehaviour:
floatingFontScale: 1
nonTargetCountScale: 1
wordSpacing: 0.18
visualEdgeConnectionDistance: 0
minimumReadablePhraseScale: 0.65
- localeCode: en
unitMode: 1
@@ -38,7 +36,6 @@ MonoBehaviour:
floatingFontScale: 0.7
nonTargetCountScale: 0.75
wordSpacing: 0.18
visualEdgeConnectionDistance: 0.18
minimumReadablePhraseScale: 0.65
- localeCode: es
unitMode: 1
@@ -47,7 +44,6 @@ MonoBehaviour:
floatingFontScale: 0.7
nonTargetCountScale: 0.75
wordSpacing: 0.18
visualEdgeConnectionDistance: 0.18
minimumReadablePhraseScale: 0.65
- localeCode: ru
unitMode: 1
@@ -56,7 +52,6 @@ MonoBehaviour:
floatingFontScale: 0.7
nonTargetCountScale: 0.75
wordSpacing: 0.18
visualEdgeConnectionDistance: 0.18
minimumReadablePhraseScale: 0.65
- localeCode: pt-BR
unitMode: 1
@@ -65,5 +60,4 @@ MonoBehaviour:
floatingFontScale: 0.7
nonTargetCountScale: 0.75
wordSpacing: 0.18
visualEdgeConnectionDistance: 0.18
minimumReadablePhraseScale: 0.65
@@ -133,20 +133,15 @@ namespace AibisDream.MiniGame.Language
// 跳过红色粒子
if (p1.isRed && p2.isRed) continue;
if (!p1.connections.Contains(p2))
continue;
float distance = Vector3.Distance(p1.transform.position, p2.transform.position);
if (distance < connectionDistance)
{
float alpha = Mathf.Lerp(0.7f, 0f, distance / connectionDistance) * fadeOutAlpha;
Color lineColor = nonTargetConnectionColor;
lineColor.a = alpha * nonTargetAlphaScale * nonTargetConnectionColor.a;
ResolveConnectionVisual(
p1,
p2,
out Vector3 from,
out Vector3 to,
out float proximity);
float alpha = Mathf.Lerp(0f, 0.7f, proximity) * fadeOutAlpha;
Color lineColor = nonTargetConnectionColor;
lineColor.a = alpha * nonTargetAlphaScale * nonTargetConnectionColor.a;
DrawClippedLine(from, to, lineColor);
DrawClippedLine(p1.transform.position, p2.transform.position, lineColor);
}
}
}
}
@@ -167,50 +162,23 @@ namespace AibisDream.MiniGame.Language
{
for (int j = i + 1; j < redParticles.Count; j++)
{
CandidateParticle first = redParticles[i];
CandidateParticle second = redParticles[j];
if (!first.connections.Contains(second))
continue;
float distance = Vector3.Distance(
redParticles[i].transform.position,
redParticles[j].transform.position
);
ResolveConnectionVisual(
first,
second,
out Vector3 from,
out Vector3 to,
out float proximity);
float alpha = Mathf.Lerp(0.4f, 1f, proximity);
Color lineColor = targetConnectionColor;
lineColor.a = alpha * targetAlphaScale * targetConnectionColor.a;
if (distance < connectionDistance)
{
float alpha = Mathf.Lerp(1f, 0.4f, distance / connectionDistance);
Color lineColor = targetConnectionColor;
lineColor.a = alpha * targetAlphaScale * targetConnectionColor.a;
DrawClippedLine(from, to, lineColor);
DrawClippedLine(redParticles[i].transform.position, redParticles[j].transform.position, lineColor);
}
}
}
}
private void ResolveConnectionVisual(
CandidateParticle first,
CandidateParticle second,
out Vector3 from,
out Vector3 to,
out float proximity)
{
if (manager != null &&
manager.TryGetConnectionVisual(
first,
second,
out from,
out to,
out proximity))
{
return;
}
from = first.transform.position;
to = second.transform.position;
float distance = Vector3.Distance(from, to);
proximity = 1f - Mathf.Clamp01(distance / Mathf.Max(0.0001f, connectionDistance));
}
private void DrawPropagationEffects()
{
Draw.LineGeometry = LineGeometry.Flat2D;
@@ -4,67 +4,6 @@ namespace AibisDream.MiniGame.Language
{
public static class ExpressionParticleGeometry
{
public static bool TryGetConnection(
TextParticle first,
TextParticle second,
float centerDistanceLimit,
float edgeDistanceLimit,
out Vector3 from,
out Vector3 to,
out float proximity)
{
from = first != null ? first.transform.position : Vector3.zero;
to = second != null ? second.transform.position : Vector3.zero;
proximity = 0f;
if (first == null || second == null)
return false;
float centerDistance = Vector3.Distance(
first.transform.position,
second.transform.position);
bool centerConnected =
centerDistanceLimit > 0f && centerDistance < centerDistanceLimit;
Bounds firstBounds = first.GetVisualWorldBounds();
Bounds secondBounds = second.GetVisualWorldBounds();
float edgeDistance = BoundsDistance(firstBounds, secondBounds);
bool edgeConnected =
edgeDistanceLimit > 0f && edgeDistance <= edgeDistanceLimit;
if (!centerConnected && !edgeConnected)
return false;
from = firstBounds.ClosestPoint(secondBounds.center);
to = secondBounds.ClosestPoint(firstBounds.center);
if ((to - from).sqrMagnitude < 0.000001f)
{
from = first.transform.position;
to = second.transform.position;
}
float centerProximity = centerConnected
? 1f - Mathf.Clamp01(centerDistance / centerDistanceLimit)
: 0f;
float edgeProximity = edgeConnected
? 1f - Mathf.Clamp01(edgeDistance / Mathf.Max(0.0001f, edgeDistanceLimit))
: 0f;
proximity = Mathf.Max(centerProximity, edgeProximity);
return true;
}
public static float BoundsDistance(Bounds first, Bounds second)
{
float dx = Mathf.Max(
first.min.x - second.max.x,
second.min.x - first.max.x,
0f);
float dy = Mathf.Max(
first.min.y - second.max.y,
second.min.y - first.max.y,
0f);
return Mathf.Sqrt(dx * dx + dy * dy);
}
public static bool TryGetSeparation(
Bounds first,
Bounds second,
@@ -22,7 +22,6 @@ namespace AibisDream.MiniGame.Language
[SerializeField, Min(0.1f)] private float floatingFontScale = 1f;
[SerializeField, Min(0f)] private float nonTargetCountScale = 1f;
[SerializeField, Min(0f)] private float wordSpacing = 0.18f;
[SerializeField, Min(0f)] private float visualEdgeConnectionDistance = 0f;
[SerializeField, Range(0.1f, 1f)] private float minimumReadablePhraseScale = 0.65f;
public string LocaleCode => localeCode;
@@ -32,7 +31,6 @@ namespace AibisDream.MiniGame.Language
public float FloatingFontScale => floatingFontScale;
public float NonTargetCountScale => nonTargetCountScale;
public float WordSpacing => wordSpacing;
public float VisualEdgeConnectionDistance => visualEdgeConnectionDistance;
public float MinimumReadablePhraseScale => minimumReadablePhraseScale;
public int ScaleNonTargetCount(int baseCount)
@@ -1198,12 +1198,12 @@ namespace AibisDream.MiniGame.Language
continue;
}
if (TryGetConnectionVisual(
candidateParticles[i],
candidateParticles[j],
out _,
out _,
out _))
float distance = Vector3.Distance(
candidateParticles[i].transform.position,
candidateParticles[j].transform.position
);
if (distance < connectionDistance)
{
candidateParticles[i].connections.Add(candidateParticles[j]);
candidateParticles[j].connections.Add(candidateParticles[i]);
@@ -1219,12 +1219,12 @@ namespace AibisDream.MiniGame.Language
{
for (int j = i + 1; j < candidateParticles.Count; j++)
{
if (TryGetConnectionVisual(
candidateParticles[i],
candidateParticles[j],
out _,
out _,
out _))
float distance = Vector3.Distance(
candidateParticles[i].transform.position,
candidateParticles[j].transform.position
);
if (distance < connectionDistance)
{
string key = $"{Mathf.Min(i, j)}-{Mathf.Max(i, j)}";
previousConnections[key] = true;
@@ -1241,12 +1241,12 @@ namespace AibisDream.MiniGame.Language
{
for (int j = i + 1; j < candidateParticles.Count; j++)
{
if (TryGetConnectionVisual(
candidateParticles[i],
candidateParticles[j],
out _,
out _,
out _))
float distance = Vector3.Distance(
candidateParticles[i].transform.position,
candidateParticles[j].transform.position
);
if (distance < connectionDistance)
{
string key = $"{Mathf.Min(i, j)}-{Mathf.Max(i, j)}";
currentConnections[key] = true;
@@ -1291,7 +1291,10 @@ namespace AibisDream.MiniGame.Language
{
if (other.isRed) continue;
if (red.connections.Contains(other))
float distance = Vector3.Distance(
red.transform.position,
other.transform.position);
if (distance < connectionDistance)
return false;
}
}
@@ -1299,27 +1302,6 @@ namespace AibisDream.MiniGame.Language
return true;
}
public bool TryGetConnectionVisual(
CandidateParticle first,
CandidateParticle second,
out Vector3 from,
out Vector3 to,
out float proximity)
{
float edgeDistance =
ActiveUnitMode == ExpressionParticleUnitMode.Word
? activeSnapshot?.Profile?.VisualEdgeConnectionDistance ?? 0.18f
: 0f;
return ExpressionParticleGeometry.TryGetConnection(
first,
second,
connectionDistance,
edgeDistance,
out from,
out to,
out proximity);
}
private void OnTargetsCompleted()
{
completionPhase = CompletionPhase.Completed;
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 84ac13641a36486d8f1a891bad6b1f28
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: f6244beee7e24912bd0e6019ac802f92
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,19 +0,0 @@
{
"name": "AibisDream.Huoshan.Tests.PlayMode",
"rootNamespace": "AibisDream.Huoshan.Tests.PlayMode",
"references": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false,
"optionalUnityReferences": [
"TestAssemblies"
]
}
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 6ab7be47fb2561242a443b00c0089c26
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,90 +0,0 @@
using System;
using System.Collections;
using System.Reflection;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
namespace AibisDream.Huoshan.Tests.PlayMode
{
public sealed class ExpressionParticlePlayModeTests
{
private const string RuntimeAssemblyName = "Assembly-CSharp";
[UnityTest]
public IEnumerator LongWords_ConnectThroughVisualEdgeDistance()
{
GameObject firstObject = new GameObject("FirstWordParticle");
GameObject secondObject = new GameObject("SecondWordParticle");
try
{
Type particleType = RequireRuntimeType(
"AibisDream.MiniGame.Language.CandidateParticle");
Component first = firstObject.AddComponent(particleType);
Component second = secondObject.AddComponent(particleType);
Invoke(first, "ForceSetUnitText", "internationalization");
Invoke(second, "ForceSetUnitText", "characteristically");
yield return null;
Bounds firstBounds = (Bounds)Invoke(first, "GetVisualWorldBounds");
Bounds secondBounds = (Bounds)Invoke(second, "GetVisualWorldBounds");
secondObject.transform.position = new Vector3(
firstBounds.extents.x + secondBounds.extents.x + 0.1f,
0f,
0f);
Type geometryType = RequireRuntimeType(
"AibisDream.MiniGame.Language.ExpressionParticleGeometry");
MethodInfo connection = geometryType.GetMethod(
"TryGetConnection",
BindingFlags.Public | BindingFlags.Static);
object[] arguments =
{
first,
second,
0.01f,
0.18f,
Vector3.zero,
Vector3.zero,
0f
};
Assert.That(connection, Is.Not.Null);
bool connected = (bool)connection.Invoke(null, arguments);
Assert.That(connected, Is.True);
Assert.That((float)arguments[6], Is.GreaterThan(0f));
Assert.That(
Vector3.Distance(
firstObject.transform.position,
secondObject.transform.position),
Is.GreaterThan(0.01f));
}
finally
{
UnityEngine.Object.Destroy(firstObject);
UnityEngine.Object.Destroy(secondObject);
}
yield return null;
}
private static Type RequireRuntimeType(string fullName)
{
Type type = Type.GetType($"{fullName}, {RuntimeAssemblyName}");
Assert.That(type, Is.Not.Null, fullName);
return type;
}
private static object Invoke(
object target,
string methodName,
params object[] arguments)
{
MethodInfo method = target.GetType().GetMethod(
methodName,
BindingFlags.Public | BindingFlags.Instance);
Assert.That(method, Is.Not.Null, methodName);
return method.Invoke(target, arguments);
}
}
}
@@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 26043594097949158ba1572f70c94f78
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -89,8 +89,9 @@ truth leak 和老虎机覆盖池都使用同一份快照或同一 Unit Mode,
粒子角色恢复。
Word Profile 还会缩小三类字号、降低非目标粒子数量,并启用宽度感知排版、
字形边缘连接、Bounds 分离、屏幕边界和鼠标命中。全屏 Actor 故障/老虎机演出
仍保持原来的字符级视觉。
Bounds 分离、屏幕边界和鼠标命中。粒子连接仍统一使用原有的中心距离判定和
中心到中心绘制,不受文字内容或字形宽度影响。全屏 Actor 故障/老虎机演出仍
保持原来的字符级视觉。
## 内容校验
@@ -107,6 +108,6 @@ Word Profile 还会缩小三类字号、降低非目标粒子数量,并启用
包含 `|``` 或只有空白,并按 Locale 模式验证;
- completion node、preset、memory key、颜色、Timeline 等结构参数不误报。
EditMode 与 PlayMode 测试覆盖原始字符串、空值不回退、缺 Key 占位、显式
Locale、轮次配置、Locale/Profile 解析、Word/Grapheme 切分、快照、粒子
数量倍率,以及真实粒子组件的 Bounds 命中和字形边缘连接行为。
EditMode 测试覆盖原始字符串、空值不回退、缺 Key 占位、显式 Locale、轮次
配置、Locale/Profile 解析、Word/Grapheme 切分、快照、粒子数量倍率,以及
真实粒子组件的 Bounds 命中和宽度感知分离行为。