Merge pull request 'Add custom block support to the API and update to latest GC version' (#6) from customblocks into master

This commit is contained in:
Norbi Peti 2020-12-17 21:21:18 +00:00
commit 9c5c980c0b
11 changed files with 317 additions and 43 deletions

View file

@ -213,7 +213,7 @@ namespace GamecraftModdingAPI
throw new BlockTypeException("The block has the wrong group! The type is " + GetType() + throw new BlockTypeException("The block has the wrong group! The type is " + GetType() +
" while the group is " + id.groupID); " while the group is " + id.groupID);
} }
else if (type != typeof(Block)) else if (type != typeof(Block) && !typeof(CustomBlock).IsAssignableFrom(type))
Logging.LogWarning($"Unknown block type! Add {type} to the dictionary."); Logging.LogWarning($"Unknown block type! Add {type} to the dictionary.");
} }

View file

@ -227,7 +227,7 @@ namespace GamecraftModdingAPI.Blocks
for (int i = 0; i < joints.count; i++) for (int i = 0; i < joints.count; i++)
{ {
ref var joint = ref joints.buffer[i]; ref var joint = ref joints.buffer[i];
if (joint.jointState == JointState.Broken) continue; if (joint.isBroken) continue;
if (joint.connectedEntityA == id) list.Add(new SimBody(joint.connectedEntityB)); if (joint.connectedEntityA == id) list.Add(new SimBody(joint.connectedEntityB));
else if (joint.connectedEntityB == id) list.Add(new SimBody(joint.connectedEntityA)); else if (joint.connectedEntityB == id) list.Add(new SimBody(joint.connectedEntityA));
} }

View file

@ -0,0 +1,157 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using DataLoader;
using GamecraftModdingAPI.App;
using GamecraftModdingAPI.Utility;
using GPUInstancer;
using HarmonyLib;
using RobocraftX.Blocks;
using RobocraftX.Common;
using RobocraftX.Rendering;
using Svelto.DataStructures;
using Svelto.ECS;
using Svelto.Tasks;
using Unity.Entities.Conversion;
using Unity.Physics;
using UnityEngine;
using UnityEngine.AddressableAssets;
using BoxCollider = UnityEngine.BoxCollider;
using Material = UnityEngine.Material;
using Object = UnityEngine.Object;
using ScalingPermission = DataLoader.ScalingPermission;
namespace GamecraftModdingAPI.Blocks
{
public class CustomBlock : Block
{
private static ushort nextID = 500;
/// <summary>
/// Key: Prefab path
/// </summary>
private static Dictionary<string, Type> _customBlocks = new Dictionary<string, Type>();
private static bool _canRegister = true;
/// <summary>
/// Register a custom block type. Call it as soon as possible (in OnApplicationStart()).<br />
/// You need a Unity project with Addressables and Havok installed and need a prefab added as an addressable asset.
/// Build the addressables and the project and copy the catalog.json from StreamingAssets, you'll need to reference this file.
/// Also copy the asset files from the subfolder to the same path in the game.
/// </summary>
/// <typeparam name="T">The custom block type</typeparam>
public static void RegisterCustomBlock<T>() where T : CustomBlock
{
if (!_canRegister)
throw new InvalidOperationException(
"It's too late to register custom blocks. Register it before the game starts loading.");
var type = typeof(T);
var attr = type.GetCustomAttribute<CustomBlockAttribute>();
if (attr == null)
throw new ArgumentException("The custom block type is missing the CustomBlock annotation");
string typeName = type.FullName ??
throw new ArgumentException("The given block type doesn't have a concrete full name.");
if (!File.Exists(attr.Catalog))
throw new FileNotFoundException("The specified catalog cannot be found for " + typeName);
_customBlocks.Add(attr.AssetPath, type);
Logging.MetaDebugLog("Registered custom block type " + typeName);
}
public CustomBlock(EGID id) : base(id)
{
if (id.groupID != Group)
throw new BlockTypeException("The block is not a custom block! It has a group of " + id.groupID);
}
public CustomBlock(uint id) : this(new EGID(id, Group))
{
}
public static ExclusiveGroup Group { get; } = new ExclusiveGroup("Custom block");
[HarmonyPatch]
public static class Patch
{
private static Material[] materials;
public static void Prefix(List<PrefabData> prefabData, IList<GameObject> prefabs)
{
for (var index = 0; index < prefabs.Count; index++)
{
if (prefabData[index].prefabName == "ConsoleBlock")
materials = prefabs[index].GetComponentsInChildren<MeshRenderer>()[0].sharedMaterials;
}
for (var index = 0; index < prefabs.Count; index++)
{
if (_customBlocks.ContainsKey(prefabData[index].prefabName)) //This is a custom block
prefabs[index].GetComponentsInChildren<MeshRenderer>()[0].sharedMaterials = materials;
}
}
public static MethodBase TargetMethod()
{ //General block registration
return AccessTools.Method("RobocraftX.Rendering.ECSGPUIResourceManager:InitPreRegisteredPrefabs");
}
}
[HarmonyPatch]
public static class CubeRegistrationPatch
{
public static void Prefix(IDataDB dataDB)
{
//var abd = dataDB.GetValue<CubeListData>((int) BlockIDs.AluminiumCube);
foreach (var (key, type) in _customBlocks)
{
var attr = type.GetCustomAttribute<CustomBlockAttribute>();
var cld = new CubeListData
{ //"Assets/Prefabs/Cube.prefab" - "CTR_CommandBlock" - "strConsoleBlock"
cubeType = attr.Type,
cubeCategory = attr.Category,
inventoryCategory = attr.InventoryCategory,
ID = nextID++,
Path = attr.AssetPath, //Index out of range exception: Asset failed to load (wrong path)
SpriteName = attr.SpriteName,
CubeNameKey = attr.NameKey,
CubeDescriptionKey = attr.DescKey,
SelectableFaces = new[] {0, 1, 2, 3, 4, 5},
GridScale = new[] {5, 5, 5},
Mass = attr.Mass,
Material = attr.Material,
scalingPermission = attr.ScalingPermission,
SortIndex = attr.SortIndex,
DefaultColour = attr.DefaultColor.Index,
Volume = attr.Volume,
EdgeConnectingFaces = new[] {0, 1, 2, 3, 4, 5},
PointDataVolumeMultiplier = 1f
};
dataDB.GetValues<CubeListData>().Add(cld.ID.ToString(), cld); //The registration needs to happen after the ID has been set
dataDB.GetFasterValues<CubeListData>().Add(cld.ID, cld); //So can't use the builtin method to create a CubeListData
}
_canRegister = false;
}
public static MethodBase TargetMethod()
{
return AccessTools.Method("RobocraftX.CR.MainGame.MainGameCompositionRoot:Init");
}
}
public static IEnumerator Prep()
{ //TODO: Don't let the game load until this finishes
foreach (var type in _customBlocks.Values)
{
var attr = type.GetCustomAttribute<CustomBlockAttribute>();
Logging.Log("Loading custom block catalog " + attr.Catalog);
var res = Addressables.LoadContentCatalogAsync(attr.Catalog);
while (!res.IsDone) yield return Yield.It;
Logging.Log("Loaded custom block catalog: " + res.Result.LocatorId);
Addressables.AddResourceLocator(res.Result);
}
}
}
}

View file

@ -0,0 +1,85 @@
using System;
using DataLoader;
namespace GamecraftModdingAPI.Blocks
{
[AttributeUsage(AttributeTargets.Class)]
public class CustomBlockAttribute : Attribute
{
/// <summary>
/// Custom block attribute necessary for configuration.
/// </summary>
/// <param name="catalog">File path to the catalog.json that holds asset references for the custom block</param>
/// <param name="assetPath">The path/address to the block's prefab specified in Unity</param>
/// <param name="nameKey">The translation key for the block's name</param>
/// <param name="spriteName">The path to the inventory sprite for the block, console block by default</param>
/// <param name="descKey">The translation key for the block's description</param>
public CustomBlockAttribute(string catalog, string assetPath, string nameKey,
string spriteName = "CTR_CommandBlock", string descKey = "")
{
Catalog = catalog;
AssetPath = assetPath;
SpriteName = spriteName;
NameKey = nameKey;
DescKey = descKey;
}
/// <summary>
/// The location of the catalog.json file used to find assets for this block.
/// </summary>
public string Catalog { get; }
/// <summary>
/// The asset path/address for the block's prefab.
/// </summary>
public string AssetPath { get; }
/// <summary>
/// The name of the sprite used in the inventory.
/// </summary>
public string SpriteName { get; }
/// <summary>
/// The translation key for the block's name.
/// </summary>
public string NameKey { get; }
/// <summary>
/// The translation key for the block's description.
/// </summary>
public string DescKey { get; }
/// <summary>
/// The block's type - block, joint, light.
/// </summary>
public CubeType Type { get; set; } = CubeType.Block;
/// <summary>
/// The block's category, so it's treated as a pre-existing functional block.
/// </summary>
public CubeCategory Category { get; set; } = CubeCategory.General;
/// <summary>
/// The block's inventory category.
/// </summary>
public InventoryCategory InventoryCategory { get; set; } = InventoryCategory.Shapes;
/// <summary>
/// The block's mass.
/// </summary>
public float Mass { get; set; } = 1f;
/// <summary>
/// The key of the material properties this block should use.
/// </summary>
public string Material { get; set; } = "Aluminium";
/// <summary>
/// The scaling permission determining what scaling is allowed on this block.
/// </summary>
public ScalingPermission ScalingPermission { get; set; }
/// <summary>
/// The sort index in the inventory.
/// </summary>
public int SortIndex { get; set; }
/// <summary>
/// The default color of the block when placed.
/// </summary>
public BlockColor DefaultColor { get; set; }
/// <summary>
/// The volume of the block.
/// </summary>
public float Volume { get; set; } = 1f;
}
}

View file

@ -19,10 +19,10 @@ namespace GamecraftModdingAPI.Blocks
/// </summary> /// </summary>
public float MaxForce public float MaxForce
{ {
get => BlockEngine.GetBlockInfo(this, (DampedSpringReadOnlyStruct dsrs) => dsrs.maxForce); get => BlockEngine.GetBlockInfo(this, (DampedSpringReadOnlyStruct dsrs) => dsrs.springFrequency);
set => BlockEngine.SetBlockInfo(this, set => BlockEngine.SetBlockInfo(this,
(ref DampedSpringReadOnlyStruct dsrs, float val) => dsrs.maxForce = val, value); (ref DampedSpringReadOnlyStruct dsrs, float val) => dsrs.springFrequency = val, value);
} }
/// <summary> /// <summary>
@ -39,10 +39,10 @@ namespace GamecraftModdingAPI.Blocks
/// </summary> /// </summary>
public float Damping public float Damping
{ {
get => BlockEngine.GetBlockInfo(this, (LinearJointForcesReadOnlyStruct ljf) => ljf.dampingForceMagnitude); get => BlockEngine.GetBlockInfo(this, (DampedSpringReadOnlyStruct ljf) => ljf.springDamping);
set => BlockEngine.SetBlockInfo(this, set => BlockEngine.SetBlockInfo(this,
(ref LinearJointForcesReadOnlyStruct ljf, float val) => ljf.dampingForceMagnitude = val, value); (ref DampedSpringReadOnlyStruct ljf, float val) => ljf.springDamping = val, value);
} }
} }
} }

View file

@ -40,11 +40,11 @@ namespace GamecraftModdingAPI.Blocks
/// </summary> /// </summary>
public float MaximumForce public float MaximumForce
{ {
get => BlockEngine.GetBlockInfo(this, (PistonReadOnlyStruct st) => st.maxForce); get => BlockEngine.GetBlockInfo(this, (PistonReadOnlyStruct st) => st.pistonVelocity);
set set
{ {
BlockEngine.SetBlockInfo(this, (ref PistonReadOnlyStruct st, float val) => st.maxForce = val, value); BlockEngine.SetBlockInfo(this, (ref PistonReadOnlyStruct st, float val) => st.pistonVelocity = val, value);
} }
} }
} }

View file

@ -52,11 +52,11 @@ namespace GamecraftModdingAPI.Blocks
/// </summary> /// </summary>
public float MaximumForce public float MaximumForce
{ {
get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.maxForce); get => BlockEngine.GetBlockInfo(this, (ServoReadOnlyStruct st) => st.servoVelocity);
set set
{ {
BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, float val) => st.maxForce = val, value); BlockEngine.SetBlockInfo(this, (ref ServoReadOnlyStruct st, float val) => st.servoVelocity = val, value);
} }
} }

View file

@ -35,10 +35,8 @@ namespace GamecraftModdingAPI.Blocks
BlockEngine.SetBlockInfo(this, (ref TextBlockDataStruct tbds, string val) => BlockEngine.SetBlockInfo(this, (ref TextBlockDataStruct tbds, string val) =>
{ {
tbds.textCurrent.Set(val); tbds.textCurrent.Set(val);
tbds.textStored.Set(val); tbds.textStored.Set(val, true);
}, value); }, value);
BlockEngine.SetBlockInfo(this,
(ref TextBlockNetworkDataStruct st, string val) => st.newTextBlockStringContent.Set(val), value);
} }
} }
@ -54,8 +52,6 @@ namespace GamecraftModdingAPI.Blocks
if (value == null) value = ""; if (value == null) value = "";
BlockEngine.SetBlockInfo(this, (ref TextBlockDataStruct tbds, string val) => BlockEngine.SetBlockInfo(this, (ref TextBlockDataStruct tbds, string val) =>
tbds.textBlockID.Set(val), value); tbds.textBlockID.Set(val), value);
BlockEngine.SetBlockInfo(this,
(ref TextBlockNetworkDataStruct st, string val) => st.newTextBlockID.Set(val), value);
} }
} }
} }

View file

@ -78,9 +78,9 @@
<HintPath>..\ref\Gamecraft_Data\Managed\Facepunch.Steamworks.Win64.dll</HintPath> <HintPath>..\ref\Gamecraft_Data\Managed\Facepunch.Steamworks.Win64.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Facepunch.Steamworks.Win64.dll</HintPath> <HintPath>..\..\ref\Gamecraft_Data\Managed\Facepunch.Steamworks.Win64.dll</HintPath>
</Reference> </Reference>
<Reference Include="FMOD"> <Reference Include="FMODUnity">
<HintPath>..\ref\Gamecraft_Data\Managed\FMOD.dll</HintPath> <HintPath>..\ref\Gamecraft_Data\Managed\FMODUnity.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\FMOD.dll</HintPath> <HintPath>..\..\ref\Gamecraft_Data\Managed\FMODUnity.dll</HintPath>
</Reference> </Reference>
<Reference Include="FullGame"> <Reference Include="FullGame">
<HintPath>..\ref\Gamecraft_Data\Managed\FullGame.dll</HintPath> <HintPath>..\ref\Gamecraft_Data\Managed\FullGame.dll</HintPath>
@ -266,6 +266,10 @@
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Music.dll</HintPath> <HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Music.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Music.dll</HintPath> <HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Music.dll</HintPath>
</Reference> </Reference>
<Reference Include="Gamecraft.NetStrings">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.NetStrings.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.NetStrings.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.PerformanceWarnings"> <Reference Include="Gamecraft.PerformanceWarnings">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.PerformanceWarnings.dll</HintPath> <HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.PerformanceWarnings.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.PerformanceWarnings.dll</HintPath> <HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.PerformanceWarnings.dll</HintPath>
@ -286,6 +290,10 @@
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Projectiles.dll</HintPath> <HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Projectiles.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Projectiles.dll</HintPath> <HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Projectiles.dll</HintPath>
</Reference> </Reference>
<Reference Include="Gamecraft.Serialization">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Serialization.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Serialization.dll</HintPath>
</Reference>
<Reference Include="Gamecraft.Tweaks"> <Reference Include="Gamecraft.Tweaks">
<HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Tweaks.dll</HintPath> <HintPath>..\ref\Gamecraft_Data\Managed\Gamecraft.Tweaks.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Tweaks.dll</HintPath> <HintPath>..\..\ref\Gamecraft_Data\Managed\Gamecraft.Tweaks.dll</HintPath>
@ -510,6 +518,10 @@
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.NetworkEntityStream.dll</HintPath> <HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.NetworkEntityStream.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.NetworkEntityStream.dll</HintPath> <HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.NetworkEntityStream.dll</HintPath>
</Reference> </Reference>
<Reference Include="RobocraftX.Multiplayer.Serializers">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.Serializers.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.Multiplayer.Serializers.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.MultiplayerInput"> <Reference Include="RobocraftX.MultiplayerInput">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.MultiplayerInput.dll</HintPath> <HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.MultiplayerInput.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.MultiplayerInput.dll</HintPath> <HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.MultiplayerInput.dll</HintPath>
@ -554,10 +566,6 @@
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.SaveGameDialog.dll</HintPath> <HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.SaveGameDialog.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.SaveGameDialog.dll</HintPath> <HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.SaveGameDialog.dll</HintPath>
</Reference> </Reference>
<Reference Include="RobocraftX.Serializers">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Serializers.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.Serializers.dll</HintPath>
</Reference>
<Reference Include="RobocraftX.Services"> <Reference Include="RobocraftX.Services">
<HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Services.dll</HintPath> <HintPath>..\ref\Gamecraft_Data\Managed\RobocraftX.Services.dll</HintPath>
<HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.Services.dll</HintPath> <HintPath>..\..\ref\Gamecraft_Data\Managed\RobocraftX.Services.dll</HintPath>

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Reflection.Emit; using System.Reflection.Emit;
@ -17,6 +18,9 @@ using RobocraftX.SimulationModeState;
using RobocraftX.FrontEnd; using RobocraftX.FrontEnd;
using Unity.Mathematics; using Unity.Mathematics;
using UnityEngine; using UnityEngine;
using RobocraftX.Schedulers;
using Svelto.Tasks.ExtraLean;
using uREPL;
using GamecraftModdingAPI.Commands; using GamecraftModdingAPI.Commands;
using GamecraftModdingAPI.Events; using GamecraftModdingAPI.Events;
@ -288,6 +292,13 @@ namespace GamecraftModdingAPI.Tests
.BlockGroup = group; .BlockGroup = group;
}).Build(); }).Build();
CommandBuilder.Builder("placeCustomBlock", "Places a custom block, needs a custom catalog and assets.")
.Action((float x, float y, float z) =>
{
Logging.CommandLog("Block placed: " +
Block.PlaceNew<TestBlock>((BlockIDs) 500, new float3(0, 0, 0)));
}).Build();
GameClient.SetDebugInfo("InstalledMods", InstalledMods); GameClient.SetDebugInfo("InstalledMods", InstalledMods);
Block.Placed += (sender, args) => Block.Placed += (sender, args) =>
Logging.MetaDebugLog("Placed block " + args.Type + " with ID " + args.ID); Logging.MetaDebugLog("Placed block " + args.Type + " with ID " + args.ID);
@ -348,6 +359,27 @@ namespace GamecraftModdingAPI.Tests
Logging.Log("Compatible GamecraftScripting detected"); Logging.Log("Compatible GamecraftScripting detected");
} }
CommandBuilder.Builder("enableCompletions")
.Action(() =>
{
var p = Window.selected.main.parameters;
p.useCommandCompletion = true;
p.useMonoCompletion = true;
p.useGlobalClassCompletion = true;
Log.Output("Submitted: " + Window.selected.submittedCode);
})
.Build();
CustomBlock.Prep().RunOn(ExtraLean.UIScheduler);
try
{
CustomBlock.RegisterCustomBlock<TestBlock>();
Logging.MetaDebugLog("Registered test custom block");
}
catch (FileNotFoundException)
{
Logging.MetaDebugLog("Test custom block catalog not found");
}
#if TEST #if TEST
TestRoot.RunTests(); TestRoot.RunTests();
#endif #endif
@ -410,6 +442,18 @@ namespace GamecraftModdingAPI.Tests
return ((Action) MinimumSpecsCheck.CheckRequirementsMet).Method; return ((Action) MinimumSpecsCheck.CheckRequirementsMet).Method;
} }
} }
[CustomBlock("customCatalog.json", "Assets/Prefabs/Cube.prefab", "strAluminiumCube", SortIndex = 12)]
public class TestBlock : CustomBlock
{
public TestBlock(EGID id) : base(id)
{
}
public TestBlock(uint id) : base(id)
{
}
}
} }
#endif #endif
} }

View file

@ -43,38 +43,22 @@ namespace GamecraftModdingAPI.Utility
{ {
get get
{ {
sound.getParameterValue(key, out float val, out float finalVal); sound.getParameterByName(key, out float val, out float finalVal);
return val; return val;
} }
set => sound.setParameterValue(key, value); set => sound.setParameterByName(key, value);
} }
public float this[int index] public float this[PARAMETER_ID index]
{ {
get get
{ {
sound.getParameterValueByIndex(index, out float val, out float finalVal); sound.getParameterByID(index, out float val, out float finalVal);
return val; return val;
} }
set => sound.setParameterValueByIndex(index, value); set => sound.setParameterByID(index, value);
}
public string[] Parameters
{
get
{
sound.getParameterCount(out int count);
string[] parameters = new string[count];
for (int i = 0; i < count; i++)
{
sound.getParameterByIndex(i, out ParameterInstance param);
param.getDescription(out PARAMETER_DESCRIPTION desc);
parameters[i] = desc.name;
}
return parameters;
}
} }
public float3 Position public float3 Position