Merge cut feedback-present (Soul verdict cut-feedback-present.s1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
d1f8302d54
13 changed files with 548 additions and 1 deletions
|
|
@ -2205,6 +2205,81 @@ MonoBehaviour:
|
|||
m_FlexibleWidth: -1
|
||||
m_FlexibleHeight: -1
|
||||
m_LayoutPriority: 1
|
||||
--- !u!1001 &177891521
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 775333809}
|
||||
m_Modifications:
|
||||
- target: {fileID: 1337198075554910, guid: 25c7aa9ad705fe143a4d71c1c8deb7d4, type: 3}
|
||||
propertyPath: m_Name
|
||||
value: TinyExplosion
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4441266138185142, guid: 25c7aa9ad705fe143a4d71c1c8deb7d4, type: 3}
|
||||
propertyPath: m_LocalPosition.x
|
||||
value: -4.2346153
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4441266138185142, guid: 25c7aa9ad705fe143a4d71c1c8deb7d4, type: 3}
|
||||
propertyPath: m_LocalPosition.y
|
||||
value: 2.42
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4441266138185142, guid: 25c7aa9ad705fe143a4d71c1c8deb7d4, type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: -21.91896
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4441266138185142, guid: 25c7aa9ad705fe143a4d71c1c8deb7d4, type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
value: 0.7071068
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4441266138185142, guid: 25c7aa9ad705fe143a4d71c1c8deb7d4, type: 3}
|
||||
propertyPath: m_LocalRotation.x
|
||||
value: -0.7071068
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4441266138185142, guid: 25c7aa9ad705fe143a4d71c1c8deb7d4, type: 3}
|
||||
propertyPath: m_LocalRotation.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4441266138185142, guid: 25c7aa9ad705fe143a4d71c1c8deb7d4, type: 3}
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4441266138185142, guid: 25c7aa9ad705fe143a4d71c1c8deb7d4, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.x
|
||||
value: -90
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4441266138185142, guid: 25c7aa9ad705fe143a4d71c1c8deb7d4, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 4441266138185142, guid: 25c7aa9ad705fe143a4d71c1c8deb7d4, type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 25c7aa9ad705fe143a4d71c1c8deb7d4, type: 3}
|
||||
--- !u!114 &177891522 stripped
|
||||
MonoBehaviour:
|
||||
m_CorrespondingSourceObject: {fileID: 8662091027736085435, guid: 25c7aa9ad705fe143a4d71c1c8deb7d4,
|
||||
type: 3}
|
||||
m_PrefabInstance: {fileID: 177891521}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1281fe44325f9b548aa42dc331b17266, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
--- !u!4 &177891523 stripped
|
||||
Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 4441266138185142, guid: 25c7aa9ad705fe143a4d71c1c8deb7d4,
|
||||
type: 3}
|
||||
m_PrefabInstance: {fileID: 177891521}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!1 &178733679
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
|
@ -12201,6 +12276,94 @@ RectTransform:
|
|||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 0, y: 0}
|
||||
m_Pivot: {x: 0, y: 0}
|
||||
--- !u!1 &775333807
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 775333809}
|
||||
- component: {fileID: 775333808}
|
||||
m_Layer: 0
|
||||
m_Name: Impact Feedback
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &775333808
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 775333807}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: bfdc38a81d00b0347bb55ad6469535ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::ImpactFeedback
|
||||
HullEffect: {fileID: 177891522}
|
||||
IntensityScale: 10
|
||||
MinEffectScale: 0.3
|
||||
Impulse:
|
||||
m_ImpulseChannel: 1
|
||||
m_ImpulseShape: 0
|
||||
m_CustomImpulseShape:
|
||||
serializedVersion: 2
|
||||
m_Curve: []
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
m_ImpulseDuration: 0.2
|
||||
m_ImpulseType: 3
|
||||
m_DissipationRate: 0
|
||||
m_RawSignal: {fileID: 0}
|
||||
m_AmplitudeGain: 1
|
||||
m_FrequencyGain: 1
|
||||
m_RepeatMode: 0
|
||||
m_Randomize: 1
|
||||
m_TimeEnvelope:
|
||||
m_AttackShape:
|
||||
serializedVersion: 2
|
||||
m_Curve: []
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
m_DecayShape:
|
||||
serializedVersion: 2
|
||||
m_Curve: []
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
m_AttackTime: 0
|
||||
m_SustainTime: 0.2
|
||||
m_DecayTime: 0.7
|
||||
m_ScaleWithImpact: 1
|
||||
m_HoldForever: 0
|
||||
m_ImpactRadius: 100
|
||||
m_DirectionMode: 0
|
||||
m_DissipationMode: 2
|
||||
m_DissipationDistance: 100
|
||||
m_PropagationSpeed: 343
|
||||
--- !u!4 &775333809
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 775333807}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 177891523}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &776416423
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
|
@ -13630,6 +13793,7 @@ GameObject:
|
|||
m_Component:
|
||||
- component: {fileID: 887677623}
|
||||
- component: {fileID: 887677622}
|
||||
- component: {fileID: 887677624}
|
||||
m_Layer: 0
|
||||
m_Name: Third Person Rig
|
||||
m_TagString: Untagged
|
||||
|
|
@ -13692,6 +13856,29 @@ Transform:
|
|||
- {fileID: 1319157835}
|
||||
m_Father: {fileID: 1665127616}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &887677624
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 887677621}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 00b2d199b96b516448144ab30fb26aed, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Cinemachine::Cinemachine.CinemachineImpulseListener
|
||||
m_ApplyAfter: 2
|
||||
m_ChannelMask: 1
|
||||
m_Gain: 1
|
||||
m_Use2DDistance: 0
|
||||
m_UseCameraSpace: 1
|
||||
m_ReactionSettings:
|
||||
m_SecondaryNoise: {fileID: 0}
|
||||
m_AmplitudeGain: 1
|
||||
m_FrequencyGain: 1
|
||||
m_Duration: 1
|
||||
m_NoiseOffsets: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &906767441
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
|
@ -38310,3 +38497,4 @@ SceneRoots:
|
|||
- {fileID: 990038542}
|
||||
- {fileID: 2135526003}
|
||||
- {fileID: 495677244}
|
||||
- {fileID: 775333809}
|
||||
|
|
|
|||
134
Assets/Scripts/Editor/ImpactPresenterSmoke.cs
Normal file
134
Assets/Scripts/Editor/ImpactPresenterSmoke.cs
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using GameCult.Caching;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using static CultMath.math;
|
||||
|
||||
// Batch proof of ImpactPresenter's wiring, without a scene: a zone ship is bound to an EntityInstance through the game's
|
||||
// own SetEntity, and impacts are published on the ship's Capabilities exactly as FireControl does. A hull absorb spawns
|
||||
// one hull effect under the instance and, for the player's ship at ScreenShake 1, raises one camera impulse; ScreenShake 0
|
||||
// or another ship raises none; a shield absorb spawns no hull effect. The shield ripple itself (ShieldManager.ShowHit
|
||||
// needs a shield material and a rig) is looked at in the editor, not here.
|
||||
//
|
||||
// Unity -batchmode -nographics -projectPath <project> -executeMethod ImpactPresenterSmoke.Run -logFile <log>
|
||||
//
|
||||
// Do not pass -quit: Run exits the Editor itself, 0 on success and 1 on any failure.
|
||||
public static class ImpactPresenterSmoke
|
||||
{
|
||||
private const BindingFlags Any = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
|
||||
|
||||
public static void Run()
|
||||
{
|
||||
var work = Path.Combine(Path.GetTempPath(), $"impact-presenter-smoke-{Guid.NewGuid():N}");
|
||||
GameObject[] objects = null;
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(work);
|
||||
objects = Check(work);
|
||||
Clean(objects, work);
|
||||
EditorApplication.Exit(0);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception);
|
||||
Clean(objects, work);
|
||||
EditorApplication.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Edit mode runs no Awake or Start; the smoke runs the ones the game's scene would have.
|
||||
private static void Awaken(Component component) => component.GetType().GetMethod("Awake", Any)?.Invoke(component, null);
|
||||
|
||||
private static GameObject[] Check(string work)
|
||||
{
|
||||
var settings = AssetDatabase.LoadAssetAtPath<GameSettings>("Assets/Resources/Settings.asset")?.GameplaySettings
|
||||
?? throw new InvalidOperationException("No Settings.asset.");
|
||||
|
||||
var shape = new Shape(5, 4);
|
||||
foreach (var cell in shape.AllCoordinates) shape[cell] = true;
|
||||
var hullData = new HullData
|
||||
{
|
||||
Name = "Smoke Hull", HullType = HullType.Ship, Shape = shape, Durability = 1000, Mass = 1000, Armor = 0
|
||||
};
|
||||
var cache = AetheriaStores.Open(Path.Combine(work, "smoke.cc"), catalogWritable: true);
|
||||
cache.Upsert(new VerseGrammar { Revision = 1 });
|
||||
cache.Upsert(hullData);
|
||||
cache.FlushAsync().Wait();
|
||||
var ledger = new ProvenanceLedger();
|
||||
for (var i = 1; i <= 8; i++) ledger.Lots[i] = new Lot { Origin = new Attributed(), Quality = 1 };
|
||||
var items = new ItemManager(cache, ledger, settings, _ => { });
|
||||
var zone = new Zone(items, new PlanetSettings(), new ZonePack(), new GalaxyZone { Name = "Smoke", Owner = null }, null);
|
||||
var hullRef = items.ItemData.RefOf<ItemData>(items.ItemData.GetByName<HullData>("Smoke Hull"));
|
||||
var ship = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 1 }, new EntitySettings());
|
||||
var other = new Ship(items, zone, new EquippableItem { Data = hullRef, Durability = 1000, Lot = 2 }, new EntitySettings());
|
||||
zone.Entities.Add(ship);
|
||||
zone.Entities.Add(other);
|
||||
ship.Activate();
|
||||
other.Activate();
|
||||
|
||||
// The scene's side: the tuning object with a pooled effect, and the game manager that names the player's ship.
|
||||
var feedbackObject = new GameObject("Smoke Impact Feedback");
|
||||
var feedback = feedbackObject.AddComponent<ImpactFeedback>();
|
||||
Awaken(feedback);
|
||||
var effectObject = new GameObject("Smoke Hull Effect");
|
||||
feedback.HullEffect = effectObject.AddComponent<Prototype>();
|
||||
var managerObject = new GameObject("Smoke Game Manager");
|
||||
var manager = managerObject.AddComponent<ActionGameManager>();
|
||||
typeof(ActionGameManager).GetProperty("Instance", Any).SetValue(null, manager);
|
||||
manager.CurrentEntity = ship;
|
||||
|
||||
var instanceObject = new GameObject("Smoke Entity Instance");
|
||||
var instance = instanceObject.AddComponent<EntityInstance>();
|
||||
Awaken(instance);
|
||||
instance.HullColliders = new HullCollider[0];
|
||||
instance.RadiatorHardpoints = new RadiatorHardpoint[0];
|
||||
instance.ThrusterHardpoints = new ThrusterHardpoint[0];
|
||||
instance.WeaponHardpoints = new WeaponHardpoint[0];
|
||||
instance.ArticulationPoints = new ArticulationPoint[0];
|
||||
instance.SetEntity(null, ship);
|
||||
|
||||
int Effects() => instance.GetComponentsInChildren<Prototype>(true).Count(p => p != null && !p.isOriginalPrototype);
|
||||
void Expect(string what, int effects, int kicks)
|
||||
{
|
||||
if (Effects() != effects || feedback.KicksRaised != kicks)
|
||||
throw new InvalidOperationException($"{what}: {Effects()} hull effects (want {effects}), {feedback.KicksRaised} impulses (want {kicks}).");
|
||||
}
|
||||
AbsorbEvent Hit(AbsorbLayer layer) => new AbsorbEvent(float3(1f, 0f, 2f), float3(0f, 0f, 1f), 100f, DamageType.Kinetic, layer);
|
||||
|
||||
ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake = 1f;
|
||||
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Hull));
|
||||
Expect("player's hull hit", 1, 1);
|
||||
var effect = instance.GetComponentsInChildren<Prototype>(true).Single(p => !p.isOriginalPrototype);
|
||||
var drawn = DrawAhead.Carried(ship, float3(1f, 0f, 2f), ActionGameManager.Clock.Lead);
|
||||
if (abs(effect.transform.position.x - drawn.x) > 1e-3f || abs(effect.transform.position.z - drawn.z) > 1e-3f)
|
||||
throw new InvalidOperationException("The hull effect is not at the drawn impact point.");
|
||||
|
||||
ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake = 0f;
|
||||
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Hull));
|
||||
Expect("hull hit at ScreenShake 0", 2, 1);
|
||||
|
||||
ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake = 1f;
|
||||
manager.CurrentEntity = other;
|
||||
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Hull));
|
||||
Expect("hull hit on another ship", 3, 1);
|
||||
|
||||
manager.CurrentEntity = ship;
|
||||
ship.Capabilities.PublishAbsorb(Hit(AbsorbLayer.Shield));
|
||||
Expect("player's shield hit", 3, 2);
|
||||
|
||||
Debug.Log("IMPACT_PRESENTER_SMOKE ok effects=3 impulses=2");
|
||||
return new[] { feedbackObject, effectObject, managerObject, instanceObject };
|
||||
}
|
||||
|
||||
private static void Clean(GameObject[] objects, string work)
|
||||
{
|
||||
if (objects != null)
|
||||
foreach (var go in objects)
|
||||
if (go != null) UnityEngine.Object.DestroyImmediate(go);
|
||||
try { if (Directory.Exists(work)) Directory.Delete(work, true); }
|
||||
catch (Exception error) { Debug.LogWarning($"Impact presenter smoke left {work}: {error.Message}"); }
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Editor/ImpactPresenterSmoke.cs.meta
Normal file
2
Assets/Scripts/Editor/ImpactPresenterSmoke.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b1b27e4493b2cfe48805c786a5090cd3
|
||||
61
Assets/Scripts/Editor/WireImpactFeedback.cs
Normal file
61
Assets/Scripts/Editor/WireImpactFeedback.cs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
using Cinemachine;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
|
||||
// Adds the impact-feedback tuning object to ARPG.unity and the impulse listener to its follow camera, then saves the
|
||||
// scene. Idempotent: a scene that already has them is left as it is.
|
||||
//
|
||||
// Unity -batchmode -projectPath <project> -executeMethod WireImpactFeedback.Run -logFile <log>
|
||||
//
|
||||
// Do not pass -quit: Run exits the Editor itself, 0 on success and 1 on any failure. The menu item runs the same wiring.
|
||||
public static class WireImpactFeedback
|
||||
{
|
||||
private const string ScenePath = "Assets/Scenes/ARPG.unity";
|
||||
// The weapons' own hit effect (the nested prefab AC5's Projectile uses as HitEffect): no new art.
|
||||
private const string HullEffectPath = "Assets/Prefabs/Fire & Explosion Effects/Prefabs/TinyExplosion.prefab";
|
||||
|
||||
[MenuItem("Aetheria/Wire Impact Feedback")]
|
||||
public static void Menu() => Wire();
|
||||
|
||||
public static void Run()
|
||||
{
|
||||
try
|
||||
{
|
||||
Wire();
|
||||
EditorApplication.Exit(0);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception);
|
||||
EditorApplication.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Wire()
|
||||
{
|
||||
var scene = EditorSceneManager.OpenScene(ScenePath, OpenSceneMode.Single);
|
||||
var manager = UnityEngine.Object.FindFirstObjectByType<ActionGameManager>();
|
||||
if (manager == null || manager.FollowCamera == null) throw new InvalidOperationException("ARPG.unity has no ActionGameManager with a FollowCamera.");
|
||||
|
||||
if (manager.FollowCamera.GetComponent<CinemachineImpulseListener>() == null)
|
||||
manager.FollowCamera.gameObject.AddComponent<CinemachineImpulseListener>();
|
||||
|
||||
if (UnityEngine.Object.FindFirstObjectByType<ImpactFeedback>() == null)
|
||||
{
|
||||
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(HullEffectPath);
|
||||
if (prefab == null) throw new InvalidOperationException($"No prefab at {HullEffectPath}.");
|
||||
var root = new GameObject("Impact Feedback");
|
||||
var feedback = root.AddComponent<ImpactFeedback>();
|
||||
var effect = (GameObject) PrefabUtility.InstantiatePrefab(prefab, root.transform);
|
||||
feedback.HullEffect = effect.GetComponent<Prototype>();
|
||||
if (feedback.HullEffect == null) throw new InvalidOperationException("The hull effect prefab has no Prototype.");
|
||||
EditorUtility.SetDirty(feedback);
|
||||
}
|
||||
|
||||
EditorSceneManager.MarkSceneDirty(scene);
|
||||
if (!EditorSceneManager.SaveScene(scene)) throw new InvalidOperationException("The scene did not save.");
|
||||
Debug.Log("WIRE_IMPACT_FEEDBACK ok");
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Editor/WireImpactFeedback.cs.meta
Normal file
2
Assets/Scripts/Editor/WireImpactFeedback.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9083abd01a947a847847d2f6fc177153
|
||||
|
|
@ -167,6 +167,13 @@ public class EntityInstance : MonoBehaviour
|
|||
|
||||
if(Shield)
|
||||
Shield.Entity = entity;
|
||||
|
||||
// Landed impacts reach the screen through the entity's one binding: the shield ripple (ShieldManager) and the
|
||||
// hull reaction and camera kick (ImpactPresenter) subscribe to the Absorb stream the simulation publishes.
|
||||
var impacts = gameObject.GetComponent<ImpactPresenter>() ?? gameObject.AddComponent<ImpactPresenter>();
|
||||
impacts.Entity = entity;
|
||||
(gameObject.GetComponent<CapabilityPresentationBinder>() ?? gameObject.AddComponent<CapabilityPresentationBinder>())
|
||||
.Bind(entity.Capabilities);
|
||||
foreach (var hullCollider in HullColliders) hullCollider.Entity = entity;
|
||||
|
||||
foreach (var item in entity.Equipment)
|
||||
|
|
|
|||
39
Assets/Scripts/Gameplay/ImpactFeedback.cs
Normal file
39
Assets/Scripts/Gameplay/ImpactFeedback.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
using Cinemachine;
|
||||
using UnityEngine;
|
||||
|
||||
// Presentation tuning for landed impacts (ImpactPresenter reads it, nothing in the simulation does): the hull
|
||||
// reaction's effect prefab and size, how strongly a hit's share of the hull reads (Feedback.Cue's intensityScale),
|
||||
// and the camera impulse. A scene component, so the scene is where it is tuned; absent in menus and test scenes,
|
||||
// where ImpactPresenter does nothing.
|
||||
public class ImpactFeedback : MonoBehaviour
|
||||
{
|
||||
public static ImpactFeedback Instance { get; private set; }
|
||||
|
||||
// A scene instance of a pooled hit effect (Prototype); a hit spawns one under the struck EntityInstance.
|
||||
public Prototype HullEffect;
|
||||
|
||||
// Multiplies a hit's share of the receiver's max durability before it saturates to 0..1 (Feedback.Cue).
|
||||
public float IntensityScale = 10f;
|
||||
|
||||
// The effect's scale never drops below this, so the lightest hit still reads.
|
||||
public float MinEffectScale = .3f;
|
||||
|
||||
public CinemachineImpulseDefinition Impulse = new CinemachineImpulseDefinition();
|
||||
|
||||
// Impulses raised since the scene loaded; the batchmode smoke reads it.
|
||||
public int KicksRaised { get; private set; }
|
||||
|
||||
private void Awake() => Instance = this;
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (Instance == this) Instance = null;
|
||||
}
|
||||
|
||||
// Raises one camera impulse of the given force, travelling along `direction` from `position`.
|
||||
public void Kick(Vector3 position, Vector3 direction, float force)
|
||||
{
|
||||
KicksRaised++;
|
||||
Impulse.CreateEvent(position, direction.normalized * force);
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Gameplay/ImpactFeedback.cs.meta
Normal file
2
Assets/Scripts/Gameplay/ImpactFeedback.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bfdc38a81d00b0347bb55ad6469535ff
|
||||
47
Assets/Scripts/Gameplay/ImpactPresenter.cs
Normal file
47
Assets/Scripts/Gameplay/ImpactPresenter.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
using System.Collections.Generic;
|
||||
using CultMath.UnityBridge;
|
||||
using UnityEngine;
|
||||
|
||||
// How one entity's landed impacts look and feel: the hull reaction at the drawn impact point, and the player's camera
|
||||
// kick. The simulation publishes each impact once on the entity's Capabilities (FireControl); Feedback.Cue decides how
|
||||
// hard it reads and whether the camera feels it, DrawAhead.Carried decides where it is drawn; this decides nothing.
|
||||
// The shield ripple is ShieldManager's. Everything here is display-only and runs in real time.
|
||||
public class ImpactPresenter : MonoBehaviour, IAbsorbPresenter
|
||||
{
|
||||
public Entity Entity;
|
||||
|
||||
private readonly List<Prototype> _live = new List<Prototype>();
|
||||
|
||||
public void Absorb(AbsorbEvent e)
|
||||
{
|
||||
var feedback = ImpactFeedback.Instance;
|
||||
if (feedback == null || Entity == null) return;
|
||||
|
||||
var manager = ActionGameManager.Instance;
|
||||
var cue = Feedback.Cue(e, Entity.EquippedHull.Data.Durability,
|
||||
manager != null && Entity == manager.CurrentEntity,
|
||||
ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake, feedback.IntensityScale);
|
||||
var point = DrawAhead.Carried(Entity, cue.Position, ActionGameManager.Clock.Lead).ToUnity();
|
||||
|
||||
if (e.Layer == AbsorbLayer.Hull && feedback.HullEffect != null)
|
||||
{
|
||||
_live.RemoveAll(live => live == null || !live.gameObject.activeSelf);
|
||||
var effect = feedback.HullEffect.Instantiate<Prototype>();
|
||||
var size = Mathf.Max(feedback.MinEffectScale, cue.Intensity);
|
||||
effect.transform.SetParent(transform, false);
|
||||
effect.transform.position = point;
|
||||
effect.transform.localScale = Vector3.one * (size / Mathf.Max(transform.lossyScale.x, 1e-3f));
|
||||
_live.Add(effect);
|
||||
}
|
||||
|
||||
if (cue.Shake > 0f)
|
||||
feedback.Kick(point, new Vector3(cue.Direction.x, 0f, cue.Direction.z), cue.Shake);
|
||||
}
|
||||
|
||||
// A live effect is a pooled child of this instance; hand it back before the instance takes it down with it.
|
||||
private void OnDestroy()
|
||||
{
|
||||
foreach (var live in _live)
|
||||
if (live != null && live.gameObject.activeSelf) live.ReturnToPool();
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Gameplay/ImpactPresenter.cs.meta
Normal file
2
Assets/Scripts/Gameplay/ImpactPresenter.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 775c61f785344d54b9f41ad3328cecee
|
||||
|
|
@ -8,7 +8,7 @@ using static CultMath.math;
|
|||
using UniRx;
|
||||
using UnityEngine;
|
||||
|
||||
public class ShieldManager : MonoBehaviour
|
||||
public class ShieldManager : MonoBehaviour, IAbsorbPresenter
|
||||
{
|
||||
public Prototype ShieldPrototype;
|
||||
public float CollisionHitDuration = 3;
|
||||
|
|
@ -86,6 +86,14 @@ public class ShieldManager : MonoBehaviour
|
|||
}
|
||||
}
|
||||
|
||||
// A shot the shield took ripples it where the shot landed, drawn on the hull as it is drawn. The hull layer is
|
||||
// ImpactPresenter's.
|
||||
public void Absorb(AbsorbEvent e)
|
||||
{
|
||||
if (e.Layer != AbsorbLayer.Shield || Entity == null || Entity.Shield == null || !Entity.Shield.Item.Active.Value) return;
|
||||
ShowHit(DrawAhead.Carried(Entity, e.Position, ActionGameManager.Clock.Lead).ToUnity(), CollisionHitDuration);
|
||||
}
|
||||
|
||||
public void ShowHit(Vector3 point, float duration)
|
||||
{
|
||||
var shield = ShieldPrototype.Instantiate<ShieldAnimation>();
|
||||
|
|
|
|||
|
|
@ -299,9 +299,14 @@ public class MainMenu : MonoBehaviour
|
|||
_nextMenu.panel.AddField("Show Asteroids in Minimap",
|
||||
() => ActionGameManager.PlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap,
|
||||
b => ActionGameManager.PlayerSettings.GraphicsSettings.ShowAsteroidsInMinimap = b);
|
||||
_nextMenu.panel.AddField("Screen Shake",
|
||||
() => ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake,
|
||||
f => ActionGameManager.PlayerSettings.GraphicsSettings.ScreenShake = f,
|
||||
0, 1);
|
||||
_nextMenu.panel.AddButton("Back",
|
||||
() =>
|
||||
{
|
||||
ActionGameManager.SavePlayerSettings();
|
||||
ShowSettings();
|
||||
Fade(false);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -323,6 +323,53 @@ public sealed partial class FireControlCut124Tests
|
|||
Assert.Equal(DamageType.Kinetic, shield.DamageType);
|
||||
}
|
||||
|
||||
// The direct absorb's direction and damage type are the shot's: a non-Kinetic weapon fired along a heading with
|
||||
// both components negative, so neither an abs of the direction nor a constant Kinetic type can agree with it.
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public void ADirectAbsorbCarriesTheShotsHeadingAndDamageTypeWhateverTheyAre(bool shielded)
|
||||
{
|
||||
var e = Build(TestSettings(), SolidShape(21, 3), velocity: 0, damage: 100f, equipShield: shielded, shieldCapacity: 100000f, custom: new[] { ("Marker", new Shape(), new int2(2, 1), 1f) });
|
||||
e.Weapon.WeaponData.DamageType = DamageType.Thermal;
|
||||
// The mount fires inside an arc about the shooter's facing, so the shooter turns to the heading it fires along.
|
||||
e.Shooter.Direction = float2(-.6f, -.8f);
|
||||
e.Target.Position = e.Shooter.Position + float3(-30f, 0f, -40f);
|
||||
for (var i = 0; i < 30; i++) e.Zone.Update(.1f);
|
||||
var subs = new List<IDisposable>();
|
||||
var (outcome, absorbs) = FireAtTheMarker(e, subs);
|
||||
foreach (var s in subs) s.Dispose();
|
||||
|
||||
Assert.Equal(shielded, outcome.Shielded);
|
||||
var absorb = Assert.Single(absorbs);
|
||||
Assert.Equal(shielded ? AbsorbLayer.Shield : AbsorbLayer.Hull, absorb.Layer);
|
||||
Assert.Equal(DamageType.Thermal, absorb.DamageType);
|
||||
Assert.Equal(-.6f, absorb.Direction.x, 1e-3);
|
||||
Assert.Equal(0f, absorb.Direction.y, 1e-6);
|
||||
Assert.Equal(-.8f, absorb.Direction.z, 1e-3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ABlastAbsorbCarriesTheBlastsDamageTypeAndTheTargetsHeightOnBothLayers()
|
||||
{
|
||||
foreach (var shield in new[] { false, true })
|
||||
{
|
||||
var e = Build(TestSettings(), SolidShape(5, 4), equipShield: shield, shieldCapacity: 100000f);
|
||||
e.HullData.GridOffset = 5f;
|
||||
for (var i = 0; i < 30; i++) e.Zone.Update(.1f);
|
||||
Assert.Equal(5f, e.Target.Position.y, 1e-4);
|
||||
var subs = new List<IDisposable>();
|
||||
var takes = Collect(e.Target, subs);
|
||||
FireControl.Detonate(e.Zone, e.Target.Position.xz + float2(1f, 0f), 2f, 50f, DamageType.Thermal);
|
||||
foreach (var s in subs) s.Dispose();
|
||||
|
||||
var take = Assert.Single(takes);
|
||||
Assert.Equal(shield ? AbsorbLayer.Shield : AbsorbLayer.Hull, take.Layer);
|
||||
Assert.Equal(DamageType.Thermal, take.DamageType);
|
||||
Assert.Equal(5f, take.Position.y, 1e-4);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ABlastShieldAbsorbCarriesTheShareTheBlastPointAndTheAwayDirection()
|
||||
{
|
||||
|
|
@ -404,6 +451,9 @@ public sealed partial class FireControlCut124Tests
|
|||
var point = float3(t.Position.x + 2f, 9f, t.Position.z + 1f);
|
||||
Assert.Equal(9f, DrawAhead.Carried(t, point, 0f).y, 1e-4);
|
||||
Assert.Equal(DrawAhead.Position(t, .5f).y + 4f, DrawAhead.Carried(t, point, .5f).y, 1e-4);
|
||||
|
||||
// The drawn impact cell sits at the hull's own height, not at world y 0.
|
||||
Assert.Equal(DrawAhead.Position(t, .5f).y, DrawAhead.Impact(t, new int2(7, 1), .5f).y, 1e-4);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue