新增SensorToolkit

This commit is contained in:
2026-05-23 08:48:48 +08:00
parent 9369f512d1
commit 81c326af53
557 changed files with 186698 additions and 137 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 262a50ffa4954f42a4a38a0eb8361f25
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,42 @@
#if PLAYMAKER
using System.Collections;
using HutongGames.PlayMaker;
namespace Micosmo.SensorToolkit.PlayMaker {
[ActionCategory("SensorToolkit")]
[Tooltip("Clears the sensor of its detections.")]
public class SensorClear : SensorToolkitAction<BasePulsableSensor> {
[Tooltip("Pulse sensor each frame.")]
public bool everyFrame;
public override void Reset() {
base.Reset();
everyFrame = false;
}
public override void OnEnter() {
DoAction();
if (!everyFrame) {
Finish();
}
}
public override void OnUpdate() {
DoAction();
}
void DoAction() {
if (typedSensor == null) {
return;
}
typedSensor.Clear();
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8c2597a264b5f224789810e9475ab164
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,53 @@
#if PLAYMAKER
using System.Collections;
using HutongGames.PlayMaker;
namespace Micosmo.SensorToolkit.PlayMaker {
[ActionCategory("SensorToolkit")]
[Tooltip("Configure how often a sensor should pulse.")]
public class SensorConfigurePulseRoutine : SensorToolkitAction<IPulseRoutine> {
[ObjectType(typeof(PulseRoutine.Modes))]
public FsmEnum pulseMode;
[HideIf("HidePulseInterval")]
public FsmFloat pulseInterval;
[Tooltip("Configure the pulse routine each frame.")]
public bool everyFrame;
PulseRoutine.Modes _pulseMode => (PulseRoutine.Modes)pulseMode.Value;
public bool HidePulseInterval() => _pulseMode != PulseRoutine.Modes.FixedInterval;
public override void Reset() {
base.Reset();
pulseMode = null;
pulseInterval = 1;
everyFrame = false;
}
public override void OnEnter() {
DoAction();
if (!everyFrame) {
Finish();
}
}
public override void OnUpdate() {
DoAction();
}
void DoAction() {
if (typedSensor == null) {
return;
}
typedSensor.PulseMode = _pulseMode;
typedSensor.PulseInterval = pulseInterval.Value;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 037e4bd61c1e407a83a431dcd7300f65
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,90 @@
#if PLAYMAKER
using System.Collections;
using HutongGames.PlayMaker;
namespace Micosmo.SensorToolkit.PlayMaker {
[ActionCategory("SensorToolkit")]
[Tooltip("For a valid raycasting sensor this will retrieve the ray intersection details for a detected object. Works with the Ray Sensor, Arc Sensor and their 2D analogues.")]
public class SensorGetDetectionRayHit : SensorToolkitAction<IRayCastingSensor> {
[ActionSection("Inputs")]
[RequiredField]
[Tooltip("The object to retrieve the RayHit for")]
public FsmGameObject targetObject;
[Tooltip("Run each frame?")]
public bool everyFrame;
[ActionSection("Outputs")]
[UIHint(UIHint.Variable)]
[Tooltip("Stores the point where the ray intersected the object")]
public FsmVector3 storePoint;
[UIHint(UIHint.Variable)]
[Tooltip("Stores the normal vector at the interesection point")]
public FsmVector3 storeNormal;
[UIHint(UIHint.Variable)]
[Tooltip("Stores the distance travelled by the ray before intersecting the object")]
public FsmFloat storeDistance;
[UIHint(UIHint.Variable)]
[Tooltip("Stores the fraction of the ray's length travelled before intersecting the object")]
public FsmFloat storeDistanceFraction;
[ActionSection("Events")]
[Tooltip("Invoked if the targetObject is detected")]
public FsmEvent isIntersectedEvent;
[Tooltip("Invoked if the targetObject is not detected")]
public FsmEvent isNotIntersectedEvent;
UnityEngine.GameObject _targetObject => targetObject.Value;
public override void Reset() {
base.Reset();
targetObject = null;
storePoint = null;
storeNormal = null;
storeDistance = null;
storeDistanceFraction = null;
isIntersectedEvent = null;
isNotIntersectedEvent = null;
everyFrame = false;
}
public override void OnEnter() {
DoAction();
if (!everyFrame) {
Finish();
}
}
public override void OnUpdate() {
DoAction();
}
void DoAction() {
if (typedSensor == null || _targetObject == null) {
return;
}
var hit = typedSensor.GetDetectionRayHit(_targetObject);
storePoint.Value = hit.Point;
storeNormal.Value = hit.Normal;
storeDistance.Value = hit.Distance;
storeDistanceFraction.Value = hit.DistanceFraction;
if (hit.Equals(RayHit.None)) {
Fsm.Event(isNotIntersectedEvent);
} else {
Fsm.Event(isIntersectedEvent);
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d6c77c99d20944a5985cd50453deda05
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,222 @@
#if PLAYMAKER
using System.Linq;
using System.Collections;
using HutongGames.PlayMaker;
namespace Micosmo.SensorToolkit.PlayMaker {
[ActionCategory("SensorToolkit")]
[Tooltip("Queries the sensor for the GameObjects it detects. There's a handful of query types including: 'All' to get all detected objects and 'Nearest' to get only the nearest detected object by distance. The query can be fine-tuned to return objects with specific tags, or objects that have specific components.")]
public class SensorGetDetections : SensorToolkitAction<Sensor> {
public enum QueryType { All, ByDistance, ByDistanceToPoint, Nearest, NearestToPoint }
[ActionSection("Inputs")]
[ObjectType(typeof(QueryType))]
public FsmEnum queryType;
[Tooltip("Find only detected objects with this tag.")]
public FsmString tag;
[HideIf("hideTestPoint")]
[Tooltip("Order detections by distance to this point.")]
public FsmVector3 testPoint;
[Tooltip("Set steering configurations each frame.")]
public bool everyFrame;
[ActionSection("Outputs")]
[HideIf("isSingleResult")]
[UIHint(UIHint.Variable)]
[Tooltip("Stores the number of detections matching the query.")]
public FsmInt storeDetectionCount;
[HideIf("isSingleResult")]
[UIHint(UIHint.Variable)]
[ArrayEditor(VariableType.GameObject)]
[Tooltip("Stores GameObjects detected by the sensor, if there is one.")]
public FsmArray storeAllDetected;
[HideIf("isSingleResult")]
[UIHint(UIHint.Variable)]
[ArrayEditor(VariableType.Object)]
[Tooltip("Detections must have matching component. Store all the components here.")]
public FsmArray storeAllComponents;
[ActionSection("Outputs")]
[HideIf("isArrayResult")]
[UIHint(UIHint.Variable)]
[Tooltip("Stores all GameObjects detected by the sensor.")]
public FsmGameObject storeDetected;
[HideIf("isArrayResult")]
[UIHint(UIHint.Variable)]
[ObjectType(typeof(UnityEngine.Component))]
[Tooltip("Detections must have matching component. Store the component here.")]
public FsmObject storeComponent;
[ActionSection("Events")]
[Tooltip("Fires this event if there is at least one detected GameObject that matches the search filters.")]
public FsmEvent detectedEvent;
[Tooltip("Fires this event if no GameObject is detected that matches the search filters.")]
public FsmEvent noneDetectedEvent;
QueryType _queryType => (QueryType)queryType.Value;
string _tag => tag.Value;
bool useTag => !string.IsNullOrEmpty(_tag);
System.Type componentType => isSingleResult() ? storeComponent.ObjectType : storeAllComponents.ObjectType;
bool useComponent => isSingleResult() ? !storeComponent.IsNone : !storeAllComponents.IsNone;
public bool isArrayResult() => _queryType != QueryType.Nearest && _queryType != QueryType.NearestToPoint;
public bool isSingleResult() => !isArrayResult();
public bool hideTestPoint() => !(_queryType == QueryType.ByDistanceToPoint || _queryType == QueryType.NearestToPoint);
public override void Reset() {
base.Reset();
queryType = QueryType.All;
storeDetectionCount = null;
storeAllComponents = null;
storeAllDetected = null;
storeComponent = null;
storeDetected = null;
testPoint = null;
tag = null;
detectedEvent = null;
noneDetectedEvent = null;
everyFrame = false;
}
public override void OnEnter() {
DoAction();
if (!everyFrame) {
Finish();
}
}
public override void OnUpdate() {
DoAction();
}
void DoAction() {
if (typedSensor == null) {
return;
}
var isSomethingDetected = switchAction();
if (isSomethingDetected) {
Fsm.Event(detectedEvent);
} else {
Fsm.Event(noneDetectedEvent);
}
}
bool switchAction() {
switch(_queryType) {
case QueryType.All:
return DoAll();
case QueryType.ByDistance:
return DoByDistance();
case QueryType.ByDistanceToPoint:
return DoByDistanceToPoint();
case QueryType.Nearest:
return DoNearest();
case QueryType.NearestToPoint:
return DoNearestToPoint();
default:
return false;
}
}
bool DoAll() {
if (useComponent) {
var components = useTag
? typedSensor.GetDetectedComponents(componentType, _tag).ToArray()
: typedSensor.GetDetectedComponents(componentType).ToArray();
storeAllComponents.Values = components;
storeAllDetected.Values = components.Select(c => c.gameObject).ToArray();
} else {
storeAllDetected.Values = useTag
? typedSensor.GetDetections(_tag).ToArray()
: typedSensor.GetDetections().ToArray();
}
if (!storeDetectionCount.IsNone) {
storeDetectionCount.Value = storeAllDetected.Values.Length;
}
return storeAllDetected.Values.Length > 0;
}
bool DoByDistance() {
if (useComponent) {
var components = useTag
? typedSensor.GetDetectedComponentsByDistance(componentType, _tag).ToArray()
: typedSensor.GetDetectedComponentsByDistance(componentType).ToArray();
storeAllComponents.Values = components;
storeAllDetected.Values = components.Select(c => c.gameObject).ToArray();
} else {
storeAllDetected.Values = useTag
? typedSensor.GetDetectionsByDistance(_tag).ToArray()
: typedSensor.GetDetectionsByDistance().ToArray();
}
if (!storeDetectionCount.IsNone) {
storeDetectionCount.Value = storeAllDetected.Values.Length;
}
return storeAllDetected.Values.Length > 0;
}
bool DoByDistanceToPoint() {
if (useComponent) {
var components = useTag
? typedSensor.GetDetectedComponentsByDistanceToPoint(testPoint.Value, componentType, _tag).ToArray()
: typedSensor.GetDetectedComponentsByDistanceToPoint(testPoint.Value, componentType).ToArray();
storeAllComponents.Values = components;
storeAllDetected.Values = components.Select(c => c.gameObject).ToArray();
} else {
storeAllDetected.Values = useTag
? typedSensor.GetDetectionsByDistanceToPoint(testPoint.Value, _tag).ToArray()
: typedSensor.GetDetectionsByDistanceToPoint(testPoint.Value).ToArray();
}
if (!storeDetectionCount.IsNone) {
storeDetectionCount.Value = storeAllDetected.Values.Length;
}
return storeAllDetected.Values.Length > 0;
}
bool DoNearest() {
if (useComponent) {
var component = useTag
? typedSensor.GetNearestComponent(componentType, _tag)
: typedSensor.GetNearestComponent(componentType);
storeComponent.Value = component;
storeDetected.Value = component != null ? component.gameObject : null;
} else {
storeDetected.Value = useTag
? typedSensor.GetNearestDetection(_tag)
: typedSensor.GetNearestDetection();
}
return storeDetected.Value != null;
}
bool DoNearestToPoint() {
if (useComponent) {
var component = useTag
? typedSensor.GetNearestComponentToPoint(testPoint.Value, componentType, _tag)
: typedSensor.GetNearestComponentToPoint(testPoint.Value, componentType);
storeComponent.Value = component;
storeDetected.Value = component != null ? component.gameObject : null;
} else {
storeDetected.Value = useTag
? typedSensor.GetNearestDetectionToPoint(testPoint.Value, _tag)
: typedSensor.GetNearestDetectionToPoint(testPoint.Value);
}
return storeDetected.Value != null;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7ef0c9b8e3934a3bb9169776eb7bbcf8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,113 @@
#if PLAYMAKER
using System.Collections;
using System.Linq;
using HutongGames.PlayMaker;
namespace Micosmo.SensorToolkit.PlayMaker {
[ActionCategory("SensorToolkit")]
[Tooltip("For a LOS Sensor this will give more detailed results from the line-of-sight test performed on an object. You will rarely need this action. To know an objects visibility it's better to use the 'Sensor Get Signal' action and use the 'Store Strength' output.")]
public class SensorGetLineOfSightResult : SensorToolkitAction3DOr2D<LOSSensor, LOSSensor2D> {
[ActionSection("Inputs")]
[RequiredField]
[Tooltip("Retrieves LOS result for this GameObject")]
public FsmGameObject targetObject;
[Tooltip("Runs the action every frame.")]
public bool everyFrame;
[ActionSection("Outputs")]
[UIHint(UIHint.Variable)]
public FsmFloat storeVisibility;
[UIHint(UIHint.Variable)]
public FsmBool storeIsVisible;
[Tooltip("Store the array of visible LOSTarget Transforms here.")]
[UIHint(UIHint.Variable)]
[ArrayEditor(VariableType.GameObject)]
public FsmArray storeVisibleTransforms;
[Tooltip("Store the array of visible target positions here.")]
[UIHint(UIHint.Variable)]
[ArrayEditor(VariableType.Vector3)]
public FsmArray storeVisiblePositions;
public override void Reset() {
base.Reset();
targetObject = null;
storeVisibility = null;
storeIsVisible = null;
storeVisibleTransforms = null;
storeVisiblePositions = null;
everyFrame = false;
}
public override void OnEnter2D(LOSSensor2D sensor) {
OnUpdate2D(sensor);
if (!everyFrame) {
Finish();
}
}
public override void OnEnter3D(LOSSensor sensor) {
OnUpdate3D(sensor);
if (!everyFrame) {
Finish();
}
}
public override void OnExit3D(LOSSensor sensor) { }
public override void OnExit2D(LOSSensor2D sensor) { }
public override void OnUpdate2D(LOSSensor2D sensor) {
var result = sensor.GetResult(targetObject.Value);
if (result != null) {
storeVisibility.Value = result.Visibility;
storeIsVisible.Value = result.IsVisible;
if (!storeVisibleTransforms.IsNone) {
storeVisibleTransforms.Values = result.Rays
.Where(r => r.TargetTransform != null && r.Visibility > 0)
.Select(r => r.TargetTransform).ToArray();
}
if (!storeVisiblePositions.IsNone) {
var visiblePositions = result.Rays.Where(r => r.Visibility > 0).Select(r => r.TargetPoint).ToList();
var boxedPositions = new object[visiblePositions.Count];
for (int i = 0; i < visiblePositions.Count; i++) {
boxedPositions[i] = visiblePositions[i];
}
storeVisiblePositions.Values = boxedPositions;
}
}
}
public override void OnUpdate3D(LOSSensor sensor) {
var result = sensor.GetResult(targetObject.Value);
if (result != null) {
storeVisibility.Value = result.Visibility;
storeIsVisible.Value = result.IsVisible;
if (!storeVisibleTransforms.IsNone) {
storeVisibleTransforms.Values = result.Rays
.Where(r => r.TargetTransform != null && r.Visibility > 0)
.Select(r => r.TargetTransform).ToArray();
}
if (!storeVisiblePositions.IsNone) {
var visiblePositions = result.Rays.Where(r => r.Visibility > 0).Select(r => r.TargetPoint).ToList();
var boxedPositions = new object[visiblePositions.Count];
for (int i = 0; i < visiblePositions.Count; i++) {
boxedPositions[i] = visiblePositions[i];
}
storeVisiblePositions.Values = boxedPositions;
}
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4dc5b16be65d4e7c9fdca28a3783bcad
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,84 @@
#if PLAYMAKER
using System.Collections;
using HutongGames.PlayMaker;
namespace Micosmo.SensorToolkit.PlayMaker {
[ActionCategory("SensorToolkit")]
[Tooltip("For a valid raycasting sensor this will retrieve the ray intersection details where it's obstructed. Works with the Ray Sensor, Arc Sensor, NavMesh Sensor and their 2D analogues.")]
public class SensorGetObstructionRayHit : SensorToolkitAction<IRayCastingSensor> {
[ActionSection("Inputs")]
[Tooltip("Run each frame?")]
public bool everyFrame;
[ActionSection("Outputs")]
[UIHint(UIHint.Variable)]
[Tooltip("Stores the point where the ray is obstructed")]
public FsmVector3 storePoint;
[UIHint(UIHint.Variable)]
[Tooltip("Stores the normal vector at the interesection point")]
public FsmVector3 storeNormal;
[UIHint(UIHint.Variable)]
[Tooltip("Stores the distance travelled by the ray before it's obstructed")]
public FsmFloat storeDistance;
[UIHint(UIHint.Variable)]
[Tooltip("Stores the fraction of the ray's length travelled before reaching the obstruction")]
public FsmFloat storeDistanceFraction;
[ActionSection("Events")]
[Tooltip("Invoked if the sensor is obstructed")]
public FsmEvent isObstructedEvent;
[Tooltip("Invoked if the sensor is not obstructed")]
public FsmEvent isNotObstructedEvent;
public override void Reset() {
base.Reset();
storePoint = null;
storeNormal = null;
storeDistance = null;
storeDistanceFraction = null;
isObstructedEvent = null;
isNotObstructedEvent = null;
everyFrame = false;
}
public override void OnEnter() {
DoAction();
if (!everyFrame) {
Finish();
}
}
public override void OnUpdate() {
DoAction();
}
void DoAction() {
if (typedSensor == null) {
return;
}
var hit = typedSensor.GetObstructionRayHit();
storePoint.Value = hit.Point;
storeNormal.Value = hit.Normal;
storeDistance.Value = hit.Distance;
storeDistanceFraction.Value = hit.DistanceFraction;
if (hit.Equals(RayHit.None)) {
Fsm.Event(isNotObstructedEvent);
} else {
Fsm.Event(isObstructedEvent);
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 90de75cb475540f9bd5b4b881e36d0c8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,93 @@
#if PLAYMAKER
using System.Collections;
using HutongGames.PlayMaker;
namespace Micosmo.SensorToolkit.PlayMaker {
[ActionCategory("SensorToolkit")]
[Tooltip("Retrieve the Signal data for a GameObject. This will give you the objects visibility (Signal Strength) and it's center point. Use this also if you want to check if a GameObject is currently detected or not.")]
public class SensorGetSignal : SensorToolkitAction<Sensor> {
[ActionSection("Inputs")]
[RequiredField]
[Tooltip("Retrieves the Signal for this GameObject")]
public FsmGameObject targetObject;
[Tooltip("Run each frame?")]
public bool everyFrame;
[ActionSection("Outputs")]
[UIHint(UIHint.Variable)]
[Tooltip("Stores size of the signal's bounding box. Taken from Signal.Bounds.size")]
public FsmVector3 storeSignalBoundsSize;
[UIHint(UIHint.Variable)]
[Tooltip("Stores the center-point of the signal's bounding box (world space). Taken from Signal.Bounds.center")]
public FsmVector3 storeSignalBoundsCenter;
[UIHint(UIHint.Variable)]
[Tooltip("Stores the signals 'strength'. Can be interpreted as visibility score between 0-1.")]
public FsmFloat storeSignalStrength;
[ActionSection("Events")]
[Tooltip("Invoked if a signal exists for the target object")]
public FsmEvent isDetectedEvent;
[Tooltip("Invoked when there is no signal for the target object")]
public FsmEvent notDetectedEvent;
UnityEngine.GameObject _targetObject => targetObject.Value;
public override void Reset() {
base.Reset();
targetObject = null;
storeSignalBoundsSize = null;
storeSignalBoundsCenter = null;
storeSignalStrength = null;
isDetectedEvent = null;
notDetectedEvent = null;
everyFrame = false;
}
public override void OnEnter() {
DoAction();
if (!everyFrame) {
Finish();
}
}
public override void OnUpdate() {
DoAction();
}
void DoAction() {
if (typedSensor == null || _targetObject == null) {
SetSignal(default);
return;
}
Signal signal;
if (typedSensor.TryGetSignal(_targetObject, out signal)) {
SetSignal(signal);
Fsm.Event(isDetectedEvent);
} else {
SetSignal(new Signal {
Object = _targetObject,
Bounds = new UnityEngine.Bounds(_targetObject.transform.position, UnityEngine.Vector3.zero),
Strength = 0f
});
Fsm.Event(notDetectedEvent);
}
}
void SetSignal(Signal signal) {
storeSignalBoundsSize.Value = signal.Bounds.size;
storeSignalBoundsCenter.Value = signal.Bounds.center;
storeSignalStrength.Value = signal.Strength;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f87adb93e0e44e7a87e98fce9106f22d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,94 @@
#if PLAYMAKER
using System.Collections;
using HutongGames.PlayMaker;
namespace Micosmo.SensorToolkit.PlayMaker {
[ActionCategory("SensorToolkit")]
[Tooltip("Subscribe to a sensors OnDetection, OnLostDetection events.")]
public class SensorListenDetectionEvents : SensorToolkitAction<Sensor> {
public enum EventType { NewDetection, LostDetection }
[ActionSection("New Detection")]
[Tooltip("Event fired when a new object was detected")]
public FsmEvent newDetectionEvent;
[UIHint(UIHint.Variable)]
[Tooltip("Stores the gameobject that was newly detected")]
public FsmGameObject storeNewDetection;
[ActionSection("Detection Lost")]
[Tooltip("Event fired when a detection was lost")]
public FsmEvent lostDetectionEvent;
[UIHint(UIHint.Variable)]
[Tooltip("Stores the gameobject whose detection was lost")]
public FsmGameObject storeLostDetection;
[ActionSection("Any Detections")]
[Tooltip("Event fired when there were previously no detections and now there is at least one detection.")]
public FsmEvent someDetectionEvent;
[Tooltip("Event fired when all detections are lost.")]
public FsmEvent noDetectionEvent;
public override void Reset() {
base.Reset();
newDetectionEvent = null;
storeNewDetection = null;
lostDetectionEvent = null;
storeLostDetection = null;
someDetectionEvent = null;
noDetectionEvent = null;
}
public override void OnEnter() {
if (typedSensor == null) {
return;
}
typedSensor.OnDetected.AddListener(OnDetectionHandler);
typedSensor.OnLostDetection.AddListener(DetectionLostHandler);
typedSensor.OnSomeDetection.AddListener(OnSomeDetectionHandler);
typedSensor.OnNoDetection.AddListener(OnNoDetectionHandler);
}
public override void OnExit() {
if (typedSensor == null) {
return;
}
typedSensor.OnDetected.RemoveListener(OnDetectionHandler);
typedSensor.OnLostDetection.RemoveListener(DetectionLostHandler);
typedSensor.OnSomeDetection.RemoveListener(OnSomeDetectionHandler);
typedSensor.OnNoDetection.RemoveListener(OnNoDetectionHandler);
}
void OnDetectionHandler(UnityEngine.GameObject go, Sensor sensor) {
if (!storeNewDetection.IsNone) {
storeNewDetection.Value = go;
}
Fsm.Event(newDetectionEvent);
}
void DetectionLostHandler(UnityEngine.GameObject go, Sensor sensor) {
if (!storeLostDetection.IsNone) {
storeLostDetection.Value = go;
}
Fsm.Event(lostDetectionEvent);
}
void OnSomeDetectionHandler() {
Fsm.Event(someDetectionEvent);
}
void OnNoDetectionHandler() {
Fsm.Event(noDetectionEvent);
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9231a456607c485bbe572dd146ceb783
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,49 @@
#if PLAYMAKER
using System.Collections;
using HutongGames.PlayMaker;
namespace Micosmo.SensorToolkit.PlayMaker {
[ActionCategory("SensorToolkit")]
[Tooltip("Manually pulses the sensor.")]
public class SensorPulse : SensorToolkitAction<BasePulsableSensor> {
[Tooltip("Also pulse any input sensors.")]
public bool pulseInputs;
[Tooltip("Pulse sensor each frame.")]
public bool everyFrame;
public override void Reset() {
base.Reset();
pulseInputs = false;
everyFrame = false;
}
public override void OnEnter() {
DoAction();
if (!everyFrame) {
Finish();
}
}
public override void OnUpdate() {
DoAction();
}
void DoAction() {
if (typedSensor == null) {
return;
}
if (pulseInputs) {
typedSensor.PulseAll();
} else {
typedSensor.Pulse();
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4cfcd2cc9a8d47d6a9ebad465000606c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,52 @@
#if PLAYMAKER
using System.Collections;
using HutongGames.PlayMaker;
namespace Micosmo.SensorToolkit.PlayMaker {
public abstract class SensorToolkitAction<T> : FsmStateAction where T : class {
[HideIf("isSpecificSensorMode")]
[RequiredField, DisplayOrder(0)]
[Tooltip("The GameObject that owns the sensor.")]
public FsmOwnerDefault gameObject;
[HideIf("isGameObjectMode")]
[DisplayOrder(1)]
[ObjectType(typeof(BasePulsableSensor))]
[Title("Specific Sensor")]
[Tooltip("You may optionally specify the sensor to act on here. This is useful when there are multiple sensors on the same GameObject.")]
public FsmObject sensor;
public bool isGameObjectMode() => (gameObject.OwnerOption == OwnerDefaultOption.SpecifyGameObject) && !isSpecificSensorMode();
public bool isSpecificSensorMode() => sensor != null && (sensor.UsesVariable || sensor.Value != null);
ComponentCache typedSensorCache;
protected T typedSensor {
get {
if (isSpecificSensorMode()) {
return sensor.Value as T;
}
var owner = Fsm.GetOwnerDefaultTarget(gameObject);
return typedSensorCache.GetComponent<T>(owner);
}
}
public override string ErrorCheck() {
var sensorIsNull = ReferenceEquals(typedSensor, null);
if (!isSpecificSensorMode() && sensorIsNull && Fsm.GetOwnerDefaultTarget(gameObject) != null) {
return $"GameObject requires a sensor matching {typeof(T)}";
}
if (sensor.Value != null && sensorIsNull && sensor.Value != null) {
return $"Sensor is incompatible with this action. Must be {typeof(T)}";
}
return base.ErrorCheck();
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e30bcb13a297325408f87556d56999f3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,97 @@
#if PLAYMAKER
using System.Collections;
using HutongGames.PlayMaker;
namespace Micosmo.SensorToolkit.PlayMaker {
public abstract class SensorToolkitAction3DOr2D<T1, T2> : FsmStateAction where T1 : BasePulsableSensor where T2 : BasePulsableSensor {
[HideIf("isSpecificSensorMode")]
[RequiredField, DisplayOrder(0)]
[Tooltip("The GameObject that owns the sensor.")]
public FsmOwnerDefault gameObject;
[HideIf("isGameObjectMode")]
[DisplayOrder(1)]
[ObjectType(typeof(BasePulsableSensor))]
[Title("Specific Sensor")]
[Tooltip("You may optionally specify the sensor to act on here. This is useful when there are multiple sensors on the same GameObject.")]
public FsmObject sensor;
public bool isGameObjectMode() => (gameObject.OwnerOption == OwnerDefaultOption.SpecifyGameObject) && !isSpecificSensorMode();
public bool isSpecificSensorMode() => sensor != null && (sensor.UsesVariable || sensor.Value != null);
ComponentCache typedSensor3DCache;
ComponentCache typedSensor2DCache;
protected T1 sensor3D {
get {
if (isSpecificSensorMode()) {
return sensor.Value as T1;
}
var owner = Fsm.GetOwnerDefaultTarget(gameObject);
return typedSensor3DCache.GetComponent<T1>(owner);
}
}
protected T2 sensor2D {
get {
if (isSpecificSensorMode()) {
return sensor.Value as T2;
}
var owner = Fsm.GetOwnerDefaultTarget(gameObject);
return typedSensor2DCache.GetComponent<T2>(owner);
}
}
public override void OnEnter() {
if (sensor3D != null) {
OnEnter3D(sensor3D);
} else if (sensor2D != null) {
OnEnter2D(sensor2D);
}
}
public override void OnExit() {
if (sensor3D != null) {
OnExit3D(sensor3D);
} else if (sensor2D != null) {
OnExit2D(sensor2D);
}
}
public override void OnUpdate() {
if (sensor3D != null) {
OnUpdate3D(sensor3D);
} else if (sensor2D != null) {
OnUpdate2D(sensor2D);
}
}
public abstract void OnEnter3D(T1 sensor);
public abstract void OnEnter2D(T2 sensor);
public abstract void OnExit3D(T1 sensor);
public abstract void OnExit2D(T2 sensor);
public abstract void OnUpdate3D(T1 sensor);
public abstract void OnUpdate2D(T2 sensor);
public override string ErrorCheck() {
var sensor2DIsNull = ReferenceEquals(sensor2D, null);
var sensor3DIsNull = ReferenceEquals(sensor3D, null);
if (!isSpecificSensorMode() && sensor3DIsNull && sensor2DIsNull && Fsm.GetOwnerDefaultTarget(gameObject) != null) {
return $"GameObject requires a sensor matching either {typeof(T1)} or {typeof(T2)}";
}
if (sensor.Value != null && sensor3DIsNull && sensor2DIsNull && sensor.Value != null) {
return $"Sensor is incompatible with this action. Must be either {typeof(T1)} or {typeof(T2)}";
}
return base.ErrorCheck();
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 870d3baba0bd44329f20f48caf6fd3b2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,58 @@
#if PLAYMAKER
using System.Collections;
using HutongGames.PlayMaker;
namespace Micosmo.SensorToolkit.PlayMaker
{
[ActionCategory("SensorToolkit")]
[Tooltip ("Sets the properties of a FOVCollider object. Note that rebuilding the collider can incur a large performance cost, so it is not recommended on a per-frame basis.")]
public class SetFOVCollider : FsmStateAction {
[RequiredField]
[ObjectType(typeof(FOVCollider))]
public FsmObject fovCollider;
public FsmFloat length;
public FsmFloat nearDistance;
[HasFloatSlider(0,180f)]
public FsmFloat FOVAngle;
[HasFloatSlider(0, 180f)]
public FsmFloat elevationAngle;
public FsmInt resolution;
public FOVCollider _fovCollider => fovCollider.Value as FOVCollider;
public override void Reset() {
OwnerDefaultSensor();
length = 5f;
nearDistance = 0.1f;
FOVAngle = 90f;
resolution = 1;
elevationAngle = 90f;
}
void OwnerDefaultSensor() {
if (Owner != null) {
fovCollider = new FsmObject() { Value = Owner.GetComponent(typeof(FOVCollider)) };
} else {
fovCollider = null;
}
}
public override void OnEnter() {
setCollider();
Finish();
}
void setCollider() {
_fovCollider.Length = length.Value;
_fovCollider.NearDistance = nearDistance.Value;
_fovCollider.FOVAngle = FOVAngle.Value;
_fovCollider.ElevationAngle = elevationAngle.Value;
_fovCollider.Resolution = resolution.Value;
_fovCollider.CreateCollider();
}
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 23794e41a90f46c58221b496c555eaf7
timeCreated: 1498906059
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,54 @@
#if PLAYMAKER
using System.Collections;
using HutongGames.PlayMaker;
namespace Micosmo.SensorToolkit.PlayMaker
{
[ActionCategory("SensorToolkit")]
[Tooltip ("Sets the properties of a FOVCollider2D object. Note that rebuilding the collider can incur a large performance cost, so it is not recommended on a per-frame basis.")]
public class SetFOVCollider2D : FsmStateAction {
[RequiredField]
[ObjectType(typeof(FOVCollider2D))]
public FsmObject fovCollider;
public FsmFloat length;
public FsmFloat nearDistance;
[HasFloatSlider(0,180f)]
public FsmFloat FOVAngle;
public FsmInt resolution;
public FOVCollider2D _fovCollider => fovCollider.Value as FOVCollider2D;
public override void Reset() {
OwnerDefaultSensor();
length = 5f;
nearDistance = 0.1f;
FOVAngle = 90f;
resolution = 1;
}
void OwnerDefaultSensor() {
if (Owner != null) {
fovCollider = new FsmObject() { Value = Owner.GetComponent(typeof(FOVCollider2D)) };
} else {
fovCollider = null;
}
}
public override void OnEnter() {
setCollider();
Finish();
}
void setCollider() {
_fovCollider.Length = length.Value;
_fovCollider.NearDistance = nearDistance.Value;
_fovCollider.FOVAngle = FOVAngle.Value;
_fovCollider.Resolution = resolution.Value;
_fovCollider.CreateCollider();
}
}
}
#endif

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 43ca970d11ce4c889302de822190bdbb
timeCreated: 1498914605
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,83 @@
#if PLAYMAKER
using System.Collections;
using HutongGames.PlayMaker;
namespace Micosmo.SensorToolkit.PlayMaker {
[ActionCategory("SensorToolkit")]
[Tooltip("Exposes some configuration settings on the Steering Sensor that control its behaviour.")]
public class SteeringSensorConfigure : SensorToolkitAction3DOr2D<SteeringSensor, SteeringSensor2D> {
[ActionSection("Seek")]
public bool setArrivalDistanceThreshold;
public bool HideArrivalDistanceThreshold() => !setArrivalDistanceThreshold;
[Tooltip("The distance from target when it is reached.")]
[HideIf("HideArrivalDistanceThreshold")]
public FsmFloat arrivalDistanceThreshold;
[ActionSection("Velocity")]
public bool setVelocity;
public bool HideVelocity() => !setVelocity;
[Tooltip("Speed sensor should aim for in absence of obstacles.")]
[HideIf("HideVelocity")]
public FsmFloat preferredSpeed;
[Tooltip("Max speed that can be taken to avoid collision.")]
[HideIf("HideVelocity")]
public FsmFloat maxSpeed;
[Tooltip("Set steering configurations each frame.")]
public bool everyFrame;
public override void Reset() {
base.Reset();
setArrivalDistanceThreshold = false;
arrivalDistanceThreshold = 1f;
setVelocity = false;
preferredSpeed = 1f;
maxSpeed = 1f;
everyFrame = false;
}
public override void OnEnter3D(SteeringSensor sensor) {
OnUpdate3D(sensor);
if (!everyFrame) {
Finish();
}
}
public override void OnEnter2D(SteeringSensor2D sensor) {
OnUpdate2D(sensor);
if (!everyFrame) {
Finish();
}
}
public override void OnExit3D(SteeringSensor sensor) { }
public override void OnExit2D(SteeringSensor2D sensor) { }
public override void OnUpdate3D(SteeringSensor sensor) {
OnUpdate(sensor);
}
public override void OnUpdate2D(SteeringSensor2D sensor) {
OnUpdate(sensor);
}
void OnUpdate(ISteeringSensor sensor) {
if (setArrivalDistanceThreshold) {
sensor.Seek.ArriveDistanceThreshold = arrivalDistanceThreshold.Value;
}
if (setVelocity) {
sensor.Velocity.PreferredSpeed = preferredSpeed.Value;
sensor.Velocity.MaxSpeed = maxSpeed.Value;
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 46fa1c4208a944949227a66f357c4624
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,149 @@
#if PLAYMAKER
using System.Collections;
using HutongGames.PlayMaker;
namespace Micosmo.SensorToolkit.PlayMaker {
[ActionCategory("SensorToolkit")]
[Tooltip("Sets the target position to be seeked by the Steering Sensor. When the 'Target Distance' is set to a large number it behaves like a Flee behaviour. If the built-in locomotion is enabled the sensor will start moving to the target. Otherwise you can store the steering vector and move towards it with your own locomotion system.")]
public class SteeringSensorSeek : SensorToolkitAction3DOr2D<SteeringSensor, SteeringSensor2D> {
public enum DestinationMode { GameObject, Position, Direction, Wander, Stop }
[ActionSection("Inputs")]
[ObjectType(typeof(DestinationMode))]
[Tooltip("How will the seek destination be specified.")]
public FsmEnum seekMode;
[HideIf("HideDestinationGameObject")]
[Tooltip("The gameobject that should be moved to.")]
public FsmGameObject destinationGameObject;
[HideIf("HideDestinationPosition")]
[Tooltip("The position that should be moved to.")]
public FsmVector3 destinationPosition;
[HideIf("HideDestinationDirection")]
[Tooltip("The direction that should be moved towards.")]
public FsmVector3 destinationDirection;
[HideIf("HideTargetDistance")]
[Tooltip("The target distance from the destination.")]
public FsmFloat targetDistance;
[HideIf("HideStopAtDestination")]
[Tooltip("Stop when the destination has been reached.")]
public bool stopAtDestination;
[HideIf("HideStopOnExit")]
[Tooltip("Clear the destination when the state exits.")]
public bool stopOnExit;
[Tooltip("Runs the action every frame.")]
public bool everyFrame;
[ActionSection("Outputs")]
[UIHint(UIHint.Variable)]
[Tooltip("Stores the Steering Vector calculated by the sensor.")]
public FsmVector3 storeSteeringVector;
[ActionSection("Events")]
[Tooltip("Fires this event if the destination position has been reached.")]
public FsmEvent destinationReachedEvent;
DestinationMode _seekMode => (DestinationMode)seekMode.Value;
public bool HideDestinationGameObject() => _seekMode != DestinationMode.GameObject;
public bool HideDestinationPosition() => _seekMode != DestinationMode.Position;
public bool HideDestinationDirection() => _seekMode != DestinationMode.Direction;
public bool HideTargetDistance() => !(_seekMode == DestinationMode.GameObject || _seekMode == DestinationMode.Position);
public bool HideStopAtDestination() => !(_seekMode == DestinationMode.GameObject || _seekMode == DestinationMode.Position);
public bool HideStopOnExit() => _seekMode == DestinationMode.Stop;
public override void Reset() {
base.Reset();
seekMode = DestinationMode.GameObject;
destinationGameObject = null;
destinationPosition = null;
destinationDirection = null;
targetDistance = 0f;
stopAtDestination = true;
stopOnExit = true;
storeSteeringVector = null;
destinationReachedEvent = null;
everyFrame = false;
}
public override void OnEnter2D(SteeringSensor2D sensor) {
OnUpdate2D(sensor);
if (!everyFrame) {
Finish();
}
}
public override void OnEnter3D(SteeringSensor sensor) {
OnUpdate3D(sensor);
if (!everyFrame) {
Finish();
}
}
public override void OnUpdate2D(SteeringSensor2D sensor) {
OnUpdate(sensor);
}
public override void OnExit3D(SteeringSensor sensor) {
if (stopOnExit && !HideStopOnExit()) {
sensor.Stop();
}
}
public override void OnExit2D(SteeringSensor2D sensor) {
if (stopOnExit && !HideStopOnExit()) {
sensor.Stop();
}
}
public override void OnUpdate3D(SteeringSensor sensor) {
OnUpdate(sensor);
}
void OnUpdate(ISteeringSensor sensor) {
var targetDistance = !HideTargetDistance() ? this.targetDistance.Value : 0f;
if (_seekMode == DestinationMode.GameObject) {
if (destinationGameObject.Value != null) {
if (stopAtDestination) {
sensor.ArriveTo(destinationGameObject.Value.transform, targetDistance);
} else {
sensor.SeekTo(destinationGameObject.Value.transform, targetDistance);
}
}
} else if (_seekMode == DestinationMode.Position) {
if (stopAtDestination) {
sensor.ArriveTo(destinationPosition.Value, targetDistance);
} else {
sensor.SeekTo(destinationPosition.Value, targetDistance);
}
} else if (_seekMode == DestinationMode.Direction) {
sensor.SeekDirection(destinationDirection.Value);
} else if (_seekMode == DestinationMode.Wander) {
sensor.Wander();
} else {
sensor.Stop();
}
if (!storeSteeringVector.IsNone) {
storeSteeringVector.Value = sensor.GetSteeringVector();
}
if (sensor.IsDestinationReached) {
Fsm.Event(destinationReachedEvent);
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e4c47c2af9f54e408ee3638625b6492c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,99 @@
#if PLAYMAKER
using System.Collections;
using HutongGames.PlayMaker;
namespace Micosmo.SensorToolkit.PlayMaker {
[ActionCategory("SensorToolkit")]
[Tooltip("If the Steering Sensor has built-in locomotion enabled this will control the strafing behaviour. The target is a direction or GameObject the agent should face while it seeks it's destination.")]
public class SteeringSensorStrafe : SensorToolkitAction3DOr2D<SteeringSensor, SteeringSensor2D> {
public enum TargetMode { GameObject, Direction, None }
[ActionSection("Inputs")]
[ObjectType(typeof(TargetMode))]
[Tooltip("How will the strafe target be specified.")]
public FsmEnum targetMode;
[UIHint(UIHint.Variable)]
[HideIf("HideTargetGameObject")]
[Tooltip("The gameobject that should be faced.")]
public FsmGameObject targetGameObject;
[HideIf("HideTargetDirection")]
[Tooltip("The position that should be faced.")]
public FsmVector3 targetDirection;
[HideIf("HideClearOnExit")]
[Tooltip("Clear the strafe target on exit.")]
public bool clearOnExit;
[Tooltip("Runs the action every frame.")]
public bool everyFrame;
TargetMode _targetMode => (TargetMode)targetMode.Value;
public bool HideTargetGameObject() => _targetMode != TargetMode.GameObject;
public bool HideTargetDirection() => _targetMode != TargetMode.Direction;
public bool HideClearOnExit() => _targetMode == TargetMode.None;
public override void Reset() {
base.Reset();
targetMode = TargetMode.GameObject;
targetGameObject = null;
targetDirection = null;
clearOnExit = true;
everyFrame = false;
}
public override void OnEnter2D(SteeringSensor2D sensor) {
OnUpdate2D(sensor);
if (!everyFrame) {
Finish();
}
}
public override void OnEnter3D(SteeringSensor sensor) {
OnUpdate3D(sensor);
if (!everyFrame) {
Finish();
}
}
public override void OnExit3D(SteeringSensor sensor) {
if (clearOnExit && !HideClearOnExit()) {
sensor.Locomotion.Strafing.Clear();
}
}
public override void OnExit2D(SteeringSensor2D sensor) {
if (clearOnExit && !HideClearOnExit()) {
sensor.Locomotion.Strafing.Clear();
}
}
public override void OnUpdate2D(SteeringSensor2D sensor) {
if (_targetMode == TargetMode.GameObject) {
sensor.Locomotion.Strafing.SetFaceTarget(targetGameObject.Value?.transform);
} else if (_targetMode == TargetMode.Direction) {
sensor.Locomotion.Strafing.SetFaceTarget(targetDirection.Value);
} else {
sensor.Locomotion.Strafing.Clear();
}
}
public override void OnUpdate3D(SteeringSensor sensor) {
if (_targetMode == TargetMode.GameObject) {
sensor.Locomotion.Strafing.SetFaceTarget(targetGameObject.Value?.transform);
} else if (_targetMode == TargetMode.Direction) {
sensor.Locomotion.Strafing.SetFaceTarget(targetDirection.Value);
} else {
sensor.Locomotion.Strafing.Clear();
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 23bf2edb4f694e809fb31ac8731f093b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,17 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: -1572125070, guid: 336aa50a81ce85b47b50a7b6adf85a76, type: 3}
m_Name: Variable Type Definition
m_EditorClassIdentifier:
VariableTypesDefinition:
- Name: SensorToolkit/Sensor
Type: Micosmo.SensorToolkit.BasePulsableSensor

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0b3691a8e4480db4983d75c870f6045f
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 2cd8d2403670b3e45afd7ec3df87246f
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: