squash commits

This commit is contained in:
2025-01-07 18:54:46 +02:00
parent 855639487b
commit 62c0a21987
3632 changed files with 708443 additions and 999 deletions
@@ -0,0 +1,571 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using UMA;
using UnityEngine;
[Serializable]
public struct ColorDef
{
public int chan; // What channel this is
public uint mCol; // The multiplicative
public uint aCol; // The additive
public ColorDef(int Channels, uint MCol, uint ACol)
{
chan = Channels;
mCol = MCol;
aCol = ACol;
}
public static uint ToUInt(Color32 color)
{
return (uint)((color.a << 24) | (color.r << 16) |
(color.g << 8) | (color.b << 0));
}
public static Color32 ToColor(uint color)
{
byte a = (byte)((color >> 24) & 0xff);
byte r = (byte)((color >> 16) & 0xff);
byte g = (byte)((color >> 8) & 0xff);
byte b = (byte)((color >> 0) & 0xff);
return new Color32(r, g, b, a);
}
}
[Serializable]
public struct ShaderParmDef
{
public string name;
public int type;
public uint value; // could be a float, int, or color
}
[Serializable]
public struct SharedColorDef
{
public string name;
public int count;
public ColorDef[] channels;
public string[] shaderParms;
public SharedColorDef(string Name, int ChannelCount)
{
name = Name;
count = ChannelCount;
channels = new ColorDef[0];
shaderParms = new string[0];
}
public void SetChannels(ColorDef[] Channels)
{
channels = Channels;
}
}
[Serializable]
public struct DnaDef
{
public string Name;
public int val;
public DnaDef(string name, float value)
{
Name = name;
val = Convert.ToInt16(value * 10000);
}
public DnaDef(string name, int value)
{
Name = name;
val = value;
}
public float Value
{
get
{
return ((float)val) / 10000;
}
set
{
val = Convert.ToInt16(value * 10000);
}
}
}
[Serializable]
public struct AvatarDefinition
{
public string RaceName;
public string[] Wardrobe;
public SharedColorDef[] Colors;
public DnaDef[] Dna;
#region Setters
public void SetColors(OverlayColorData[] CurrentColors)
{
List<SharedColorDef> newColors = new List<SharedColorDef>();
for (int i1 = 0; i1 < CurrentColors.Length; i1++)
{
OverlayColorData col = CurrentColors[i1];
SharedColorDef scd = new SharedColorDef(col.name, col.channelCount);
List<ColorDef> colorchannels = new List<ColorDef>();
if (col.PropertyBlock != null)
{
List<string> shaderParms = new List<string>();
foreach (UMAProperty prop in col.PropertyBlock.shaderProperties)
{
string property = prop.ToString();
}
scd.shaderParms = shaderParms.ToArray();
}
for (int i = 0; i < col.channelCount; i++)
{
if (!col.isDefault(i))
{
Color Mask = col.channelMask[i];
Color Additive = col.channelAdditiveMask[i];
colorchannels.Add(new ColorDef(i, ColorDef.ToUInt(Mask), ColorDef.ToUInt(Additive)));
}
}
if (colorchannels.Count > 0)
{
scd.SetChannels(colorchannels.ToArray());
newColors.Add(scd);
}
}
Colors = newColors.ToArray();
}
public void SetDefaultColors(string[] colorNames, uint[] colors)
{
if (colorNames.Length != colors.Length)
{
Debug.LogError("Color lengths must match");
return;
}
List<SharedColorDef> sharedcolors = new List<SharedColorDef>();
for (int i=0;i<colorNames.Length;i++)
{
SharedColorDef scd = new SharedColorDef(colorNames[i], 1);
ColorDef col = new ColorDef(1, colors[i], 0);
scd.channels = new ColorDef[1];
scd.channels[0] = col;
}
Colors = sharedcolors.ToArray();
}
public void SetDNA(UMAPredefinedDNA dna)
{
List<DnaDef> defs = new List<DnaDef>();
for (int i = 0; i < dna.PreloadValues.Count; i++)
{
DnaValue d = dna.PreloadValues[i];
defs.Add(new DnaDef(d.Name, d.Value));
}
Dna = defs.ToArray();
}
// No Garbage Version
public void SetDNA(DnaValue[] dna)
{
Dna = new DnaDef[dna.Length];
for (int i=0;i<dna.Length;i++)
{
Dna[i].Name = dna[i].Name;
Dna[i].Value = dna[i].Value;
}
}
public void SetDNA(string[] names, float[] values)
{
if (names.Length != values.Length)
{
Debug.LogError("SetDNA: length of names and values must match.");
return;
}
Dna = new DnaDef[names.Length];
for (int i = 0; i < names.Length; i++)
{
Dna[i].Name = names[i];
Dna[i].Value = values[i];
}
}
#endregion
public string ToCompressedString(string seperator = "\n")
{
StringBuilder theString = new StringBuilder();
theString.Append("BB*");
theString.Append(seperator);
theString.Append("R:");
theString.Append(RaceName);
theString.Append(seperator);
if (Wardrobe != null)
{
theString.Append("W:");
for (int i = 0; i < Wardrobe.Length; i++)
{
string w = Wardrobe[i];
theString.Append(w);
theString.Append(",");
}
theString.Append(seperator);
}
if (Colors != null)
{
for (int i = 0; i < Colors.Length; i++)
{
SharedColorDef scd = Colors[i];
theString.Append("C:");
theString.Append(scd.name);
theString.Append(',');
theString.Append(scd.count);
theString.Append('>');
for (int i1 = 0; i1 < scd.channels.Length; i1++)
{
ColorDef c = scd.channels[i1];
theString.Append(c.chan);
theString.Append(',');
theString.Append(c.mCol.ToString("X"));
if (c.aCol != 0)
{
theString.Append(',');
theString.Append(c.aCol.ToString("X"));
}
theString.Append(';');
}
if (scd.shaderParms != null)
{
for (int i2 = 0; i2 < scd.shaderParms.Length; i2++)
{
theString.Append("P:");
theString.Append(Base64Encode(scd.shaderParms[i2]));
theString.Append(';');
}
}
theString.Append('<');
}
theString.Append(seperator);
}
if (Dna != null)
{
theString.Append("D:");
for (int i = 0; i < Dna.Length; i++)
{
DnaDef d = Dna[i];
theString.Append(d.Name);
theString.Append('=');
theString.Append(d.val.ToString("X"));
theString.Append(';');
}
theString.Append(seperator);
}
return theString.ToString();
}
public static AvatarDefinition FromCompressedString(string compressed, char seperator = '\n')
{
if (compressed.StartsWith("AA*"))
{
return FromCompressedStringV1(compressed.Substring(3), seperator);
}
else
{
return FromCompressedStringV2(compressed.Substring(3), seperator);
}
}
public static AvatarDefinition FromCompressedStringV1(string compressed, char seperator = '\n')
{
char[] splitter = new char[1];
AvatarDefinition adf = new AvatarDefinition();
splitter[0] = seperator;
string[] SplitLines = compressed.Split(splitter);
List<SharedColorDef> Colors = new List<SharedColorDef>();
foreach (string s in SplitLines)
{
if (String.IsNullOrEmpty(s)) continue;
switch (s[0])
{
case 'R':
// Unpack Race
adf.RaceName = s.Substring(2).Trim();
break;
case 'W':
// Unpack Wardrobe
splitter[0] = ',';
adf.Wardrobe = s.Substring(2).Trim().Split(splitter, StringSplitOptions.RemoveEmptyEntries);
break;
case 'C':
// Unpack Colors
splitter[0] = '=';
string[] SharedColor = s.Substring(2).Trim().Split(splitter, StringSplitOptions.RemoveEmptyEntries);
if (SharedColor.Length > 1)
{
SharedColorDef scd = new SharedColorDef();
splitter[0] = ',';
string[] maincol = SharedColor[0].Split(splitter, StringSplitOptions.RemoveEmptyEntries);
if (maincol.Length > 1)
{
scd.name = maincol[0];
scd.count = Convert.ToInt32(maincol[1]);
splitter[0] = ';';
string[] ColorDefs = SharedColor[1].Split(splitter, StringSplitOptions.RemoveEmptyEntries);
List<ColorDef> theColors = new List<ColorDef>();
if (ColorDefs != null)
{
if (ColorDefs.Length > 0)
{
foreach (string c in ColorDefs)
{
splitter[0] = ',';
string[] vals = c.Split(splitter, StringSplitOptions.RemoveEmptyEntries);
if (vals.Length == 2)
{
ColorDef cdef = new ColorDef(Convert.ToInt32(vals[0]), Convert.ToUInt32(vals[1], 16), 0);
theColors.Add(cdef);
}
else if (vals.Length == 3)
{
ColorDef cdef = new ColorDef(Convert.ToInt32(vals[0]), Convert.ToUInt32(vals[1], 16), Convert.ToUInt32(vals[2], 16));
theColors.Add(cdef);
}
}
}
}
scd.channels = theColors.ToArray();
Colors.Add(scd);
}
}
break;
case 'D':
// Unpack DNA
splitter[0] = ';';
string[] Dna = s.Substring(2).Trim().Split(splitter, StringSplitOptions.RemoveEmptyEntries);
if (Dna.Length > 0)
{
List<DnaDef> theDna = new List<DnaDef>();
foreach (string d in Dna)
{
splitter[0] = '=';
string[] dnaval = d.Split(splitter, StringSplitOptions.RemoveEmptyEntries);
if (dnaval.Length > 1)
{
DnaDef newDna = new DnaDef(dnaval[0], Convert.ToInt32(dnaval[1], 16));
theDna.Add(newDna);
}
}
adf.Dna = theDna.ToArray();
}
break;
}
}
adf.Colors = Colors.ToArray();
return adf;
}
public static AvatarDefinition FromCompressedStringV2(string compressed,char seperator='\n')
{
char[] splitter = new char[1];
AvatarDefinition adf = new AvatarDefinition();
splitter[0] = seperator;
string[] SplitLines = compressed.Split(splitter);
// List<SharedColorDef> Colors = new List<SharedColorDef>();
for (int i = 0; i < SplitLines.Length; i++)
{
string s = SplitLines[i];
if (String.IsNullOrEmpty(s))
{
continue;
}
switch (s[0])
{
case 'R':
// Unpack Race
adf.RaceName = s.Substring(2).Trim();
break;
case 'W':
// Unpack Wardrobe
splitter[0] = ',';
adf.Wardrobe = s.Substring(2).Trim().Split(splitter, StringSplitOptions.RemoveEmptyEntries);
break;
case 'C':
// Unpack Colors
adf.Colors = UnpackColors(s);
break;
case 'D':
// Unpack DNA
splitter[0] = ';';
string[] Dna = s.Substring(2).Trim().Split(splitter, StringSplitOptions.RemoveEmptyEntries);
if (Dna.Length > 0)
{
List<DnaDef> theDna = new List<DnaDef>();
for (int i1 = 0; i1 < Dna.Length; i1++)
{
string d = Dna[i1];
splitter[0] = '=';
string[] dnaval = d.Split(splitter, StringSplitOptions.RemoveEmptyEntries);
if (dnaval.Length > 1)
{
DnaDef newDna = new DnaDef(dnaval[0], Convert.ToInt32(dnaval[1],16));
theDna.Add(newDna);
}
}
adf.Dna = theDna.ToArray();
}
break;
}
}
//adf.Colors = Colors.ToArray();
return adf;
}
private static SharedColorDef[] UnpackColors(string s)
{
List<SharedColorDef> colors = new List<SharedColorDef>();
string[] encodedColors = s.Split(new char[] { '<' }, StringSplitOptions.RemoveEmptyEntries);
for(int i= 0; i < encodedColors.Length; i++)
{
UnpackAColor(colors, encodedColors[i]);
}
return colors.ToArray();
}
private static void UnpackAColor(List<SharedColorDef> Colors, string s)
{
char[] splitter = { '>' };
string[] SharedColor = s.Substring(2).Trim().Split(splitter, StringSplitOptions.RemoveEmptyEntries);
if (SharedColor.Length > 1)
{
SharedColorDef scd = new SharedColorDef();
List<string> ShaderParms = new List<string>();
splitter[0] = ',';
string[] maincol = SharedColor[0].Split(splitter, StringSplitOptions.RemoveEmptyEntries);
if (maincol.Length > 1)
{
scd.name = maincol[0];
scd.count = Convert.ToInt32(maincol[1]);
splitter[0] = ';';
string[] ColorDefs = SharedColor[1].Split(splitter, StringSplitOptions.RemoveEmptyEntries);
List<ColorDef> theColors = new List<ColorDef>();
if (ColorDefs != null)
{
if (ColorDefs.Length > 0)
{
for (int i1 = 0; i1 < ColorDefs.Length; i1++)
{
if (String.IsNullOrEmpty(ColorDefs[i1]))
continue;
if (ColorDefs[i1][0] == 'P')
{
ShaderParms.Add(Base64Decode(ColorDefs[i1].Substring(2)));
continue;
}
string c = ColorDefs[i1];
splitter[0] = ',';
string[] vals = c.Split(splitter, StringSplitOptions.RemoveEmptyEntries);
if (vals.Length == 2)
{
ColorDef cdef = new ColorDef(Convert.ToInt32(vals[0]), Convert.ToUInt32(vals[1], 16), 0);
theColors.Add(cdef);
}
else if (vals.Length == 3)
{
ColorDef cdef = new ColorDef(Convert.ToInt32(vals[0]), Convert.ToUInt32(vals[1], 16), Convert.ToUInt32(vals[2], 16));
theColors.Add(cdef);
}
}
}
}
scd.channels = theColors.ToArray();
scd.shaderParms = ShaderParms.ToArray();
Colors.Add(scd);
}
}
}
// Ascii version of the string. Not as good as binary formatter,
// but 1/2 the size of a string.
public byte[] ToASCIIString()
{
return Encoding.ASCII.GetBytes(ToCompressedString());
}
public static AvatarDefinition FromASCIIString(byte[] asciiString)
{
return FromCompressedString(Encoding.ASCII.GetString(asciiString));
}
public static string Base64Encode(string plainText)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
}
public static string Base64Decode(string base64EncodedData)
{
var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData);
return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
}
}
[Serializable]
public class BinaryDefinition
{
public AvatarDefinition adf;
public BinaryDefinition(AvatarDefinition Adf)
{
adf = Adf;
}
/// <summary>
/// This is likely not compatible cross platform
/// </summary>
/// <param name="bf"></param>
/// <returns></returns>
public static byte[] ToBinary(BinaryFormatter bf, AvatarDefinition adf)
{
using (var ms = new System.IO.MemoryStream())
{
bf.Serialize(ms,new BinaryDefinition(adf));
return ms.ToArray();
}
}
public AvatarDefinition FromBinary(byte[] bin, BinaryFormatter bf)
{
using (var memStream = new MemoryStream())
{
memStream.Write(bin, 0, bin.Length);
memStream.Seek(0, SeekOrigin.Begin);
BinaryDefinition bdf = (BinaryDefinition) bf.Deserialize(memStream);
return bdf.adf;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 1d96acc78b4466945b4866ca39e8418d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 35611
packageName: UMA 2
packageVersion: 2.13
assetPath: Assets/UMA/Core/Extensions/DynamicCharacterSystem/Scripts/AvatarDefinition.cs
uploadId: 679826
@@ -0,0 +1,185 @@
using System.Collections.Generic;
using UnityEngine;
namespace UMA.CharacterSystem
{
[DisallowMultipleComponent]
[RequireComponent(typeof(DynamicCharacterAvatar))]
public class DCARendererManager : MonoBehaviour
{
[System.Serializable]
public class RendererElement
{
public List<UMARendererAsset> rendererAssets = new List<UMARendererAsset>();
public List<SlotDataAsset> slotAssets = new List<SlotDataAsset>();
public List<string> wardrobeSlots = new List<string>();
}
public List<RendererElement> RendererElements = new List<RendererElement>();
bool lastState;
public bool RenderersEnabled = true;
private DynamicCharacterAvatar avatar;
private UMAData.UMARecipe umaRecipe = new UMAData.UMARecipe();
List<SlotDataAsset> wardrobeSlotAssets = new List<SlotDataAsset>();
private UMAContextBase context;
private List<SlotData> slotsToAdd = new List<SlotData>();
#pragma warning disable 0414
//for use with the editor to save the settings for whether to show the help box or not.
//pragma disables the warning for not being used in this class.
[SerializeField]
private bool showHelp = true;
#pragma warning restore 0414
// Use this for initialization
void Start()
{
avatar = GetComponent<DynamicCharacterAvatar>();
avatar.CharacterBegun.AddListener(CharacterBegun);
context = UMAContextBase.Instance;
lastState = RenderersEnabled; // only cause it to rebuild if it actually changes
}
private void Update()
{
if (RenderersEnabled != lastState)
{
if (avatar.activeRace.isValid == false)
{
return;
}
if (avatar.UpdatePending())
{
return;
}
#if UMA_ADDRESSABLES
if (avatar.AddressableBuildPending)
{
return;
}
#endif
if (avatar.hide)
{
return;
}
lastState = RenderersEnabled;
avatar.BuildCharacter();
}
}
void CharacterBegun(UMAData umaData)
{
if (!RenderersEnabled)
{
return;
}
//If mesh is not dirty then we haven't changed slots.
if (!umaData.isMeshDirty)
{
return;
}
SlotData[] slots = umaData.umaRecipe.slotDataList;
slotsToAdd.Clear();
for (int i1 = 0; i1 < RendererElements.Count; i1++)
{
RendererElement element = RendererElements[i1];
if (element.rendererAssets == null || element.rendererAssets.Count <= 0)
{
continue;
}
wardrobeSlotAssets.Clear();
//First, lets collect a list of the slotDataAssets that are present in the wardrobe recipes of the wardrobe slots we've specified
for (int i2 = 0; i2 < element.wardrobeSlots.Count; i2++)
{
string wardrobeSlot = element.wardrobeSlots[i2];
UMATextRecipe recipe = avatar.GetWardrobeItem(wardrobeSlot);
if (recipe != null)
{
recipe.Load(umaRecipe, context);
if (umaRecipe.slotDataList != null)
{
for (int i = 0; i < umaRecipe.slotDataList.Length; i++)
{
SlotData slotData = umaRecipe.slotDataList[i];
if (slotData == null)
{
continue;
}
if (slotData.isBlendShapeSource)
{
continue;
}
if (slotData != null && slotData.asset != null)
{
wardrobeSlotAssets.Add(slotData.asset);
}
}
}
}
}
//Next, check each slot for if they are in the list of specified slots or exist in one of the wardrobe recipes of the wardrobe slot we specified.
for (int i2 = 0; i2 < slots.Length; i2++)
{
SlotData slot = slots[i2];
// if (element.slotAssets.Contains(slot.asset) || wardrobeSlotAssets.Contains(slot.asset))
if (HasSlot(element.slotAssets,slot.slotName) || HasSlot(wardrobeSlotAssets,slot.slotName))
{
//We check for at least one rendererAsset at the top level for loop.
//Set our existing slot to the first renderer in our renderer list.
slot.rendererAsset = element.rendererAssets[0];
//If we have more renderers then make a copy of the SlotData and set that copy's rendererAsset to this item's renderer.
//Add the newly created slots to a running list to combine back with the entire slot list at the end.
for (int i = 1; i < element.rendererAssets.Count; i++)
{
SlotData addSlot = slot.Copy();
addSlot.rendererAsset = element.rendererAssets[i];
slotsToAdd.Add(addSlot);
}
}
}
}
//If we have added Slots, then add the first slots to the list and set the recipe's slots to the new combined list.
if (slotsToAdd.Count > 0)
{
slotsToAdd.AddRange(slots);
umaData.umaRecipe.SetSlots(slotsToAdd.ToArray());
slotsToAdd.Clear();
}
wardrobeSlotAssets.Clear();
}
private bool HasSlot(List<SlotDataAsset> slots, string slotName)
{
if (slots != null)
{
for (int i = 0; i < slots.Count; i++)
{
SlotDataAsset sl = slots[i];
if (sl != null)
{
if (sl.slotName == slotName)
{
return true;
}
}
}
}
return false;
}
}
}
@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 74ea9524f26d57548a9f99b6e3da50f7
timeCreated: 1548910751
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 35611
packageName: UMA 2
packageVersion: 2.13
assetPath: Assets/UMA/Core/Extensions/DynamicCharacterSystem/Scripts/DCARendererManager.cs
uploadId: 679826
@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: bcffb0a198605474c8a4d1c67c1515fe
timeCreated: 1428420101
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 35611
packageName: UMA 2
packageVersion: 2.13
assetPath: Assets/UMA/Core/Extensions/DynamicCharacterSystem/Scripts/DynamicCharacterAvatar.cs
uploadId: 679826
@@ -0,0 +1,470 @@
using UnityEngine;
#if UNITY_EDITOR
#endif
using System.Collections.Generic;
namespace UMA.CharacterSystem
{
public class DynamicCharacterSystem : DynamicCharacterSystemBase
{
public Dictionary<string, UMATextRecipe> RecipeIndex = new Dictionary<string, UMATextRecipe>();
public Dictionary<string, Dictionary<string, List<UMATextRecipe>>> Recipes = new Dictionary<string, Dictionary<string, List<UMATextRecipe>>>();
public Dictionary<string, string> CharacterRecipes = new Dictionary<string, string>();
public bool initializeOnAwake = true;
[HideInInspector]
[System.NonSerialized]
public bool initialized = false;
private bool isInitializing = false;
public bool dynamicallyAddFromResources;
[Tooltip("Limit the Global Library search to the following folders (no starting slash and seperate multiple entries with a comma)")]
public string resourcesCharactersFolder = "";
[Tooltip("Limit the Global Library search to the following folders (no starting slash and seperate multiple entries with a comma)")]
public string resourcesRecipesFolder = "";
public bool dynamicallyAddFromAssetBundles;
[Tooltip("Limit the AssetBundles search to the following bundles (no starting slash and seperate multiple entries with a comma)")]
public string assetBundlesForCharactersToSearch;
[Tooltip("Limit the AssetBundles search to the following bundles (no starting slash and seperate multiple entries with a comma)")]
public string assetBundlesForRecipesToSearch;
[Tooltip("If true will automatically scan and add all UMATextRecipes from any downloaded bundles.")]
public bool addAllRecipesFromDownloadedBundles = true;
[HideInInspector]
public UMAContextBase context;
//This is a ditionary of asset bundles that were loaded into the library. This can be queried to store a list of active assetBundles that might be useful to preload etc
public Dictionary<string, List<string>> assetBundlesUsedDict = new Dictionary<string, List<string>>();
[System.NonSerialized]
[HideInInspector]
public bool downloadAssetsEnabled = true;
public override void Awake()
{
if (initializeOnAwake)
{
if (!initialized)
{
Init();
}
}
}
public override void Start()
{
if (!initialized)
{
Init();
}
}
public override void Init()
{
if (initialized || isInitializing)
{
return;
}
if (context == null)
{
context = UMAContextBase.Instance;
}
isInitializing = true;
Recipes.Clear();
var possibleRaces = context.GetAllRaces();
for (int i = 0; i < possibleRaces.Length; i++)
{
//we need to check that this is not null- the user may not have downloaded it yet
if (possibleRaces[i] == null)
{
continue;
}
if (possibleRaces[i].raceName == "RaceDataPlaceholder")
{
continue;
}
if (Recipes.ContainsKey(possibleRaces[i].raceName))
{
if (Debug.isDebugBuild)
{
Debug.LogWarning("Warning: multiple races found for key:" + possibleRaces[i].raceName);
}
}
else
{
Recipes.Add(possibleRaces[i].raceName, new Dictionary<string, List<UMATextRecipe>>());
}
}
initialized = true;
isInitializing = false;
}
/// <summary>
/// Ensures DCS has a race key for the given race in its dictionaries. Use when you want to add recipes to DCS before the actual racedata has been downloaded
/// </summary>
public void EnsureRaceKey(string race)
{
if (!Recipes.ContainsKey(race))
{
Recipes.Add(race, new Dictionary<string, List<UMATextRecipe>>());
}
}
/// <summary>
/// Refreshes DCS Dictionaries based on the current races in the RaceLibrary. Ensures any newly added races get backwards compatible recipes assigned to them
/// </summary>
public void RefreshRaceKeys()
{
if (!initialized)
{
Init();
return;
}
if (addAllRecipesFromDownloadedBundles)
{
Refresh(false, "");
return;
}
var possibleRaces = context.GetAllRacesBase();
for (int i = 0; i < possibleRaces.Length; i++)
{
if (!Recipes.ContainsKey(possibleRaces[i].raceName) && possibleRaces[i].raceName != "RaceDataPlaceholder")
{
Recipes.Add(possibleRaces[i].raceName, new Dictionary<string, List<UMATextRecipe>>());
}
//then make sure any currently added recipes are also assigned to this race if they are compatible
foreach (string race in Recipes.Keys)
{
if (race != possibleRaces[i].raceName)
{
foreach (KeyValuePair<string, List<UMATextRecipe>> kp in Recipes[race])
{
AddRecipes(kp.Value.ToArray());
}
}
}
}
}
/// <summary>
/// Forces DCS to update its recipes to include all recipes that are in Resources or in downloaded assetBundles (optionally filtering by assetBundle name)
/// </summary>
/// <param name="forceUpdateRaceLibrary">If true will force RaceLibrary to add any races that were in any downloaded assetBundles and then call this Refresh again.</param>
/// <param name="bundleToGather">Limit the recipes found tto a defined asset bundle</param>
public override void Refresh(bool forceUpdateRaceLibrary = true, string bundleToGather = "")
{
if (!initialized)
{
Init();
return;
}
RaceData[] possibleRaces = new RaceData[0];
if (forceUpdateRaceLibrary)
{
possibleRaces = context.GetAllRaces();//if any new races are added by this then RaceLibrary will Re-Trigger this if there was anything new so dont do anything else
}
else
{
possibleRaces = context.GetAllRacesBase();
for (int i = 0; i < possibleRaces.Length; i++)
{
//we need to check that this is not null- the user may not have downloaded it yet
if (possibleRaces[i] != null)
{
if (!Recipes.ContainsKey(possibleRaces[i].raceName) && possibleRaces[i].raceName != "RaceDataPlaceholder")
{
Recipes.Add(possibleRaces[i].raceName, new Dictionary<string, List<UMATextRecipe>>());
}
}
}
}
}
private void AddCharacterRecipes(TextAsset[] characterRecipes)
{
for (int i = 0; i < characterRecipes.Length; i++)
{
TextAsset characterRecipe = characterRecipes[i];
if (!CharacterRecipes.ContainsKey(characterRecipe.name))
{
CharacterRecipes.Add(characterRecipe.name, characterRecipe.text);
}
else
{
CharacterRecipes[characterRecipe.name] = characterRecipe.text;
}
}
//This doesn't actually seem to do anything apart from slow things down- maybe we can hook into the UMAGarbage collection rate and do this at the same time? Or just after?
//StartCoroutine(CleanFilesFromResourcesAndBundles());
}
public void AddRecipesFromAB(UMATextRecipe[] uparts)
{
AddRecipes(uparts, "");
}
public void AddRecipe(UMATextRecipe upart)
{
if (upart != null)
{
AddRecipes(new UMATextRecipe[] { upart });
}
}
//This could be private I think- TODO confirm
public void AddRecipes(UMATextRecipe[] uparts, string filename = "")
{
for (int i1 = 0; i1 < uparts.Length; i1++)
{
UMATextRecipe u = uparts[i1];
if (filename == "" || (filename != "" && filename.Trim() == u.name))
{
var thisWardrobeSlot = u.wardrobeSlot;
if (u.GetType() == typeof(UMAWardrobeCollection))
{
//we have a problem here because when the placeholder asset is returned its wardrobeCollection.sets.Count == 0
//so we need to do it the other way round i.e. add it when its downloading but if the libraries contain it when its downloaded and the sets count is 0 remove it
if ((u as UMAWardrobeCollection).wardrobeCollection.sets.Count == 0)
{
if (RecipeIndex.ContainsKey(u.name))
{
if (Debug.isDebugBuild)
{
Debug.LogWarning("DCS removed " + u.name + " from RecipeIndex");
}
RecipeIndex.Remove(u.name);
}
}
thisWardrobeSlot = "WardrobeCollection";
}
//we might be refreshing so check its not already there
if (!RecipeIndex.ContainsKey(u.name))
{
RecipeIndex.Add(u.name, u);
}
else
{
RecipeIndex[u.name] = u;
}
for (int i = 0; i < u.compatibleRaces.Count; i++)
{
if (u.GetType() == typeof(UMAWardrobeCollection) && (u as UMAWardrobeCollection).wardrobeCollection.sets.Count > 0)
{
//if the collection doesn't have a wardrobeSet for this race continue
//again when its downloading this data isn't there
if ((u as UMAWardrobeCollection).wardrobeCollection[u.compatibleRaces[i]].Count == 0)
{
if (Recipes.ContainsKey(u.compatibleRaces[i]))
{
if (Recipes[u.compatibleRaces[i]].ContainsKey("WardrobeCollection"))
{
if (Recipes[u.compatibleRaces[i]]["WardrobeCollection"].Contains(u))
{
if (Debug.isDebugBuild)
{
Debug.LogWarning("DCS removed " + u.name + " from Recipes");
}
Recipes[u.compatibleRaces[i]]["WardrobeCollection"].Remove(u);
if (RecipeIndex.ContainsKey(u.name))
{
if (Debug.isDebugBuild)
{
Debug.LogWarning("DCS removed " + u.name + " from RecipeIndex");
}
RecipeIndex.Remove(u.name);
}
continue;
}
}
}
}
}
//When recipes that are compatible with multiple races are downloaded we may not have all the races actually downloaded
//but that should not stop DCS making an index of recipes that are compatible with that race for when it becomes available
if (!Recipes.ContainsKey(u.compatibleRaces[i]))
{
Recipes.Add(u.compatibleRaces[i], new Dictionary<string, List<UMATextRecipe>>());
}
if (Recipes.ContainsKey(u.compatibleRaces[i]))
{
Dictionary<string, List<UMATextRecipe>> RaceRecipes = Recipes[u.compatibleRaces[i]];
if (!RaceRecipes.ContainsKey(thisWardrobeSlot))
{
RaceRecipes.Add(thisWardrobeSlot, new List<UMATextRecipe>());
}
//we might be refreshing so replace anything that is already there with the downloaded versions- else add
bool added = false;
for (int ir = 0; ir < RaceRecipes[thisWardrobeSlot].Count; ir++)
{
if (RaceRecipes[thisWardrobeSlot][ir].name == u.name)
{
RaceRecipes[thisWardrobeSlot][ir] = u;
added = true;
}
}
if (!added)
{
RaceRecipes[thisWardrobeSlot].Add(u);
}
}
//backwards compatible race slots
foreach (string racekey in Recipes.Keys)
{
//here we also need to check that the race itself has a wardrobe slot that matches the one in the compatible race
//11012017 Dont trigger backwards compatible races to download
RaceData raceKeyRace = context.GetRace(racekey);
if (raceKeyRace == null)
{
continue;
}
if (raceKeyRace.IsCrossCompatibleWith(u.compatibleRaces[i]) && (raceKeyRace.wardrobeSlots.Contains(thisWardrobeSlot) || thisWardrobeSlot == "WardrobeCollection"))
{
Dictionary<string, List<UMATextRecipe>> RaceRecipes = Recipes[racekey];
if (!RaceRecipes.ContainsKey(thisWardrobeSlot))
{
RaceRecipes.Add(thisWardrobeSlot, new List<UMATextRecipe>());
}
//we might be refreshing so replace anything that is already there with the downloaded versions- else add
bool added = false;
for (int ir = 0; ir < RaceRecipes[thisWardrobeSlot].Count; ir++)
{
if (RaceRecipes[thisWardrobeSlot][ir].name == u.name)
{
RaceRecipes[thisWardrobeSlot][ir] = u;
added = true;
}
}
if (!added)
{
RaceRecipes[thisWardrobeSlot].Add(u);
}
}
}
}
}
}
//This doesn't actually seem to do anything apart from slow things down
//StartCoroutine(CleanFilesFromResourcesAndBundles());
}
/// <summary>
/// Get a recipe from the DCS dictionary, optionally 'dynamicallyAdding' it from Resources/AssetBundles if the component is set up to do this.
/// </summary>
public UMATextRecipe GetRecipe(string filename, bool dynamicallyAdd = true)
{
UMATextRecipe foundRecipe = null;
if (RecipeIndex.ContainsKey(filename))
{
foundRecipe = RecipeIndex[filename];
}
else
{
if (dynamicallyAdd)
{
if (RecipeIndex.ContainsKey(filename))
{
foundRecipe = RecipeIndex[filename];
}
}
}
return foundRecipe;
}
/// <summary>
/// Gets the originating asset bundle for a given recipe.
/// </summary>
/// <returns>The originating asset bundle.</returns>
/// <param name="recipeName">Recipe name.</param>
public string GetOriginatingAssetBundle(string recipeName)
{
string originatingAssetBundle = "";
if (assetBundlesUsedDict.Count == 0)
{
return originatingAssetBundle;
}
else
{
foreach (KeyValuePair<string, List<string>> kp in assetBundlesUsedDict)
{
if (kp.Value.Contains(recipeName))
{
originatingAssetBundle = kp.Key;
break;
}
}
}
if (originatingAssetBundle == "")
{
if (Debug.isDebugBuild)
{
Debug.Log(recipeName + " was not found in any loaded AssetBundle");
}
}
else
{
if (Debug.isDebugBuild)
{
Debug.Log("originatingAssetBundle for " + recipeName + " was " + originatingAssetBundle);
}
}
return originatingAssetBundle;
}
#region OVERRIDES FROM BASE - REQUIRED BY RECIPE EDITOR IF IT IS IN STANDARD ASSETS
/// <summary>
/// Gets the recipe names in the DynamicCharacterSystem libraries for the given race and slot (used by RecipeEditor because of StandardAssets)
/// </summary>
public override List<string> GetRecipeNamesForRaceSlot(string race, string slot)
{
Refresh();
List<string> recipeNamesForRaceSlot = new List<string>();
if (Recipes.ContainsKey(race))
{
if (Recipes[race].ContainsKey(slot))
{
for (int i = 0; i < Recipes[race][slot].Count; i++)
{
UMATextRecipe utr = Recipes[race][slot][i];
recipeNamesForRaceSlot.Add(utr.name);
}
}
}
return recipeNamesForRaceSlot;
}
/// <summary>
/// Gets the recipes in the DynamicCharacterSystem libraries for the given race and slot (used by RecipeEditor because of StandardAssets)
/// </summary>
public override List<UMARecipeBase> GetRecipesForRaceSlot(string race, string slot)
{
Refresh();
List<UMARecipeBase> recipesForRaceSlot = new List<UMARecipeBase>();
if (Recipes.ContainsKey(race))
{
if (Recipes[race].ContainsKey(slot))
{
for (int i = 0; i < Recipes[race][slot].Count; i++)
{
UMATextRecipe utr = Recipes[race][slot][i];
recipesForRaceSlot.Add(utr);
}
}
}
return recipesForRaceSlot;
}
/// <summary>
/// Use GetRecipe unless your calling script resides in StandardAssets. Returns the recipe of the given filename from the dictionary as an UMARecipeBase
/// </summary>
public override UMARecipeBase GetBaseRecipe(string filename, bool dynamicallyAdd = true)
{
return GetRecipe(filename, dynamicallyAdd);
}
#endregion
}
}
@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: c0eca91bcc56f7b4895b6c690d3197a2
timeCreated: 1457030904
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 35611
packageName: UMA 2
packageVersion: 2.13
assetPath: Assets/UMA/Core/Extensions/DynamicCharacterSystem/Scripts/DynamicCharacterSystem.cs
uploadId: 679826
@@ -0,0 +1,369 @@
using UnityEngine;
#if UNITY_EDITOR
#endif
using System.Collections.Generic;
namespace UMA.CharacterSystem
{
public class DynamicOverlayLibrary : OverlayLibrary
{
//extra fields for Dynamic Version
public bool dynamicallyAddFromResources = true;
[Tooltip("Limit the Resources search to the following folders (no starting slash and seperate multiple entries with a comma)")]
public string resourcesFolderPath = "";
public bool dynamicallyAddFromAssetBundles;
[Tooltip("Limit the AssetBundles search to the following bundles (no starting slash and seperate multiple entries with a comma)")]
public string assetBundleNamesToSearch = "";
//This is a ditionary of asset bundles that were loaded into the library at runtime.
//CharacterAvatar can query this this to find out what asset bundles were required to create itself
//or other scripts can use it to find out which asset bundles are being used by the Libraries at any given point.
public Dictionary<string, List<string>> assetBundlesUsedDict = new Dictionary<string, List<string>>();
#if UNITY_EDITOR
List<OverlayDataAsset> editorAddedAssets = new List<OverlayDataAsset>();
#endif
[System.NonSerialized]
[HideInInspector]
public bool downloadAssetsEnabled = true;
public void Start()
{
if (Application.isPlaying)
{
assetBundlesUsedDict.Clear();
}
#if UNITY_EDITOR
if (Application.isPlaying)
{
ClearEditorAddedAssets();
}
#endif
}
/// <summary>
/// Clears any editor added assets when the Scene is closed
/// </summary>
void OnDestroy()
{
#if UNITY_EDITOR
ClearEditorAddedAssets();
#endif
}
public void ClearEditorAddedAssets()
{
#if UNITY_EDITOR
if (editorAddedAssets.Count > 0)
{
editorAddedAssets.Clear();
}
#endif
}
#if UNITY_EDITOR
OverlayData GetEditorAddedAsset(int? nameHash = null, string overlayName = "")
{
OverlayData foundOverlay = null;
if (editorAddedAssets.Count > 0)
{
foreach (OverlayDataAsset edOverlay in editorAddedAssets)
{
if(edOverlay != null)
{
if(nameHash != null)
{
if(edOverlay.nameHash == nameHash)
foundOverlay = new OverlayData(edOverlay);
}
else if(overlayName != null)
{
if(overlayName != "")
if(edOverlay.overlayName == overlayName)
foundOverlay = new OverlayData(edOverlay);
}
}
}
}
return foundOverlay;
}
#endif
public void UpdateDynamicOverlayLibrary(int? nameHash = null)
{
var Overlays = UMAAssetIndexer.Instance.GetAllAssets<OverlayDataAsset>();
if (nameHash != null)
{
Overlays.RemoveAll(x => x.nameHash != nameHash);
}
AddOverlayAssets(Overlays.ToArray());
}
public void UpdateDynamicOverlayLibrary(string overlayName)
{
var Overlays = UMAAssetIndexer.Instance.GetAllAssets<OverlayDataAsset>();
Overlays.RemoveAll(x => x.overlayName != overlayName);
AddOverlayAssets(Overlays.ToArray());
}
#pragma warning disable 618
private void AddOverlayAssets(OverlayDataAsset[] overlays)
{
foreach (OverlayDataAsset overlay in overlays)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
bool alreadyExisted = false;
foreach (OverlayDataAsset addedOverlay in overlayElementList)
{
if (addedOverlay == overlay)
{
alreadyExisted = true;
break;
}
}
if (alreadyExisted)
continue;
if (!editorAddedAssets.Contains(overlay))
editorAddedAssets.Add(overlay);
}
else
#endif
AddOverlayAsset(overlay);
}
//This doesn't actually seem to do anything apart from slow things down
//StartCoroutine(CleanOverlaysFromResourcesAndBundles());
}
#pragma warning restore 618
/*IEnumerator CleanOverlaysFromResourcesAndBundles()
{
yield return null;
Resources.UnloadUnusedAssets();
yield break;
}*/
public override OverlayData InstantiateOverlay(string name)
{
OverlayData res;
try
{
res = base.InstantiateOverlay(name);
}
catch
{
res = null;
}
#if UNITY_EDITOR
if (!Application.isPlaying && res == null)
{
res = GetEditorAddedAsset(null, name);
}
#endif
if (res == null)
{
//we try to load the overlay dynamically
UpdateDynamicOverlayLibrary(name);
try {
res = base.InstantiateOverlay(name);
}
catch
{
res = null;
}
if (res == null)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
res = GetEditorAddedAsset(null, name);
if (res != null)
{
return res;
}
}
#endif
throw new UMAResourceNotFoundException("dOverlayLibrary: Unable to find: " + name);
}
}
return res;
}
//we dont seem to be able to use nameHash for some reason so in this case we are screwed- DOES THIS EVER HAPPEN?
public override OverlayData InstantiateOverlay(int nameHash)
{
if (Debug.isDebugBuild)
Debug.Log("OverlayLibrary tried to InstantiateOverlay using Hash");
OverlayData res;
try
{
res = base.InstantiateOverlay(nameHash);
}
catch
{
res = null;
}
#if UNITY_EDITOR
if (!Application.isPlaying && res == null)
{
res = GetEditorAddedAsset(nameHash);
}
#endif
if (res == null)
{
UpdateDynamicOverlayLibrary(nameHash);
try {
res = base.InstantiateOverlay(nameHash);
}
catch
{
res = null;
}
if (res == null)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
res = GetEditorAddedAsset(nameHash);
if (res != null)
{
return res;
}
}
#endif
throw new UMAResourceNotFoundException("dOverlayLibrary: Unable to find: " + nameHash);
}
}
return res;
}
public override OverlayData InstantiateOverlay(string name, Color color)
{
OverlayData res;
try
{
res = base.InstantiateOverlay(name);
}
catch
{
res = null;
}
#if UNITY_EDITOR
if (!Application.isPlaying && res == null)
{
res = GetEditorAddedAsset(null, name);
}
#endif
if (res == null)
{
//we do something
UpdateDynamicOverlayLibrary(name);
try {
res = base.InstantiateOverlay(name);
}
catch
{
res = null;
}
if (res == null)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
res = GetEditorAddedAsset(null, name);
if (res != null)
{
res.colorData.color = color;
return res;
}
}
#endif
throw new UMAResourceNotFoundException("dOverlayLibrary: Unable to find: " + name);
}
}
res.colorData.color = color;
return res;
}
//we dont seem to be able to use nameHash for some reason so in this case we are screwed- DOES THIS EVER HAPPEN?
public override OverlayData InstantiateOverlay(int nameHash, Color color)
{
if (Debug.isDebugBuild)
Debug.Log("OverlayLibrary tried to InstantiateOverlay using Hash");
OverlayData res;
try
{
res = base.InstantiateOverlay(nameHash);
}
catch
{
res = null;
}
#if UNITY_EDITOR
if (!Application.isPlaying && res == null)
{
res = GetEditorAddedAsset(nameHash);
}
#endif
if (res == null)
{
UpdateDynamicOverlayLibrary(nameHash);
try {
res = base.InstantiateOverlay(nameHash);
}
catch
{
res = null;
}
if (res == null)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
res = GetEditorAddedAsset(nameHash);
if (res != null)
{
res.colorData.color = color;
return res;
}
}
#endif
throw new UMAResourceNotFoundException("dOverlayLibrary: Unable to find: " + nameHash);
}
}
res.colorData.color = color;
return res;
}
/// <summary>
/// Gets the originating asset bundle.
/// </summary>
/// <returns>The originating asset bundle.</returns>
/// <param name="overlayName">Overlay name.</param>
public string GetOriginatingAssetBundle(string overlayName)
{
string originatingAssetBundle = "";
if (assetBundlesUsedDict.Count > 0)
{
foreach (KeyValuePair<string, List<string>> kp in assetBundlesUsedDict)
{
if (kp.Value.Contains(overlayName))
{
originatingAssetBundle = kp.Key;
break;
}
}
}
if (originatingAssetBundle == "")
{
if (Debug.isDebugBuild)
Debug.Log(overlayName + " has not been loaded from any AssetBundle");
}
else
{
if (Debug.isDebugBuild)
Debug.Log("originatingAssetBundle for " + overlayName + " was " + originatingAssetBundle);
}
return originatingAssetBundle;
}
}
}
@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: d9f039051ce08a94d9a607179f30c21f
timeCreated: 1457020167
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 35611
packageName: UMA 2
packageVersion: 2.13
assetPath: Assets/UMA/Core/Extensions/DynamicCharacterSystem/Scripts/DynamicOverlayLibrary.cs
uploadId: 679826
@@ -0,0 +1,384 @@
using UnityEngine;
#if UNITY_EDITOR
#endif
using System.Collections.Generic;
namespace UMA.CharacterSystem
{
public class DynamicRaceLibrary : RaceLibrary
{
//extra fields for Dynamic Version
public bool dynamicallyAddFromResources = true;
[Tooltip("Limit the Global Library search to the following folders (no starting slash and seperate multiple entries with a comma)")]
public string resourcesFolderPath = "";
public bool dynamicallyAddFromAssetBundles;
[Tooltip("Limit the AssetBundles search to the following bundles (no starting slash and seperate multiple entries with a comma)")]
public string assetBundleNamesToSearch = "";
//This is a ditionary of asset bundles that were loaded into the library at runtime.
//CharacterAvatar can query this this to find out what asset bundles were required to create itself
//or other scripts can use it to find out which asset bundles are being used by the Libraries at any given point.
public Dictionary<string, List<string>> assetBundlesUsedDict = new Dictionary<string, List<string>>();
#if UNITY_EDITOR
//if we have already added everything in the editor dont do it again
bool allAssetsAddedInEditor = false;
List<RaceData> editorAddedAssets = new List<RaceData>();
#endif
[System.NonSerialized]
bool allStartingAssetsAdded = false;
[System.NonSerialized]
[HideInInspector]
public bool downloadAssetsEnabled = true;
#if UNITY_EDITOR
void Reset()
{
allStartingAssetsAdded = false;//make this false to that loading new scenes makes the library update
allAssetsAddedInEditor = false;
}
#endif
public void Awake()
{
#if UNITY_EDITOR
allStartingAssetsAdded = false;//make this false to that loading new scenes makes the library update
allAssetsAddedInEditor = false;
#endif
}
public void Start()
{
if (Application.isPlaying)
{
assetBundlesUsedDict.Clear();
}
#if UNITY_EDITOR
if (Application.isPlaying)
{
ClearEditorAddedAssets();
}
allStartingAssetsAdded = false;//make this false to that loading new scenes makes the library update
allAssetsAddedInEditor = false;
#endif
}
/// <summary>
/// Clears any editor added assets when the Scene is closed
/// </summary>
void OnDestroy()
{
#if UNITY_EDITOR
ClearEditorAddedAssets();
#endif
}
public void ClearEditorAddedAssets()
{
#if UNITY_EDITOR
if (editorAddedAssets.Count > 0)
{
editorAddedAssets.Clear();
}
allAssetsAddedInEditor = false;
#endif
}
#if UNITY_EDITOR
RaceData GetEditorAddedAsset(int? raceHash = null, string raceName = "")
{
RaceData foundRaceData = null;
if (editorAddedAssets.Count > 0)
{
foreach (RaceData edRace in editorAddedAssets)
{
if (edRace != null)
{
if (raceHash != null)
{
if (UMAUtils.StringToHash(edRace.raceName) == raceHash)
foundRaceData = edRace;
}
else if (raceName != null)
{
if (raceName != "")
if (edRace.raceName == raceName)
foundRaceData = edRace;
}
}
}
}
return foundRaceData;
}
#endif
public void UpdateDynamicRaceLibrary(bool downloadAssets, int? raceHash = null)
{
if (allStartingAssetsAdded && raceHash == null)
return;
//Making the race library scan everything (by sending raceHash = null) only needs to happen once- at all other times a specific race will have been requested (and been added by dynamic asset loader) so it will already be here if it needs to be.
if (raceHash == null && Application.isPlaying && allStartingAssetsAdded == false)
allStartingAssetsAdded = true;
#if UNITY_EDITOR
if (allAssetsAddedInEditor && raceHash == null)
return;
#endif
var Races = UMAAssetIndexer.Instance.GetAllAssets<RaceData>();
if (raceHash != null)
{
// Remove all races that don't match the hash
}
UpdateDynamicRaceLibrary(raceHash);
#if UNITY_EDITOR
if (raceHash == null && !Application.isPlaying)
allAssetsAddedInEditor = true;
#endif
}
public void UpdateDynamicRaceLibrary(string raceName)
{
var races = UMAAssetIndexer.Instance.GetAllAssets<RaceData>();
races.RemoveAll(x => x.raceName != raceName);
AddRaces(races.ToArray());
}
public void UpdateDynamicRaceLibrary(int? raceHash)
{
var races = UMAAssetIndexer.Instance.GetAllAssets<RaceData>();
if (raceHash != null)
{
races.RemoveAll(x => x.name.GetHashCode() != raceHash);
}
AddRaces(races.ToArray());
}
#pragma warning disable 618
private void AddRaces(RaceData[] races)
{
int currentNumRaces = raceElementList.Length;
foreach (RaceData race in races)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
bool alreadyExisted = false;
foreach(RaceData addedRace in raceElementList)
{
if (addedRace == race)
{
alreadyExisted = true;
break;
}
}
if (alreadyExisted)
continue;
if (!editorAddedAssets.Contains(race))
{
editorAddedAssets.Add(race);
if (UMAContextBase.Instance != null)
{
UMAContext.Instance.ValidateDictionaries();
}
}
}
else
#endif
AddRace(race);
}
//Ensure the new races have keys in the DCS dictionary
if (currentNumRaces != raceElementList.Length && Application.isPlaying)
{
if (UMAContextBase.Instance != null)
{
UMAContextBase.Instance.ValidateDictionaries();
}
}
//This doesn't actually seem to do anything apart from slow things down
//StartCoroutine(CleanRacesFromResourcesAndBundles());
}
#pragma warning restore 618
/*IEnumerator CleanRacesFromResourcesAndBundles()
{
yield return null;
Resources.UnloadUnusedAssets();
yield break;
}*/
#pragma warning disable 618
//We need to override AddRace Too because if the element is not in the list anymore it causes an error...
override public void AddRace(RaceData race)
{
if (race == null)
return;
race.UpdateDictionary();
try
{
base.AddRace(race);
}
catch
{
//if there is an error it will be because RaceElementList contained an empty refrence
List<RaceData> newRaceElementList = new List<RaceData>();
for (int i = 0; i < raceElementList.Length; i++)
{
if (raceElementList[i] != null)
{
raceElementList[i].UpdateDictionary();
newRaceElementList.Add(raceElementList[i]);
}
}
raceElementList = newRaceElementList.ToArray();
base.AddRace(race);
}
}
#pragma warning restore 618
//TODO if this works it should maybe be the other way round for backwards compatability- i.e. so unless you do something different this does what it always did do...
public override RaceData GetRace(string raceName)
{
return GetRace(raceName, true);
}
public RaceData GetRace(string raceName, bool allowUpdate = true)
{
if ((raceName == null) || (raceName.Length == 0))
return null;
RaceData res;
res = base.GetRace(raceName);
#if UNITY_EDITOR
if (!Application.isPlaying && res == null)
{
res = GetEditorAddedAsset(null, raceName);
}
#endif
if (res == null && allowUpdate)
{
//we try to load the race dynamically
UpdateDynamicRaceLibrary(raceName);
res = base.GetRace(raceName);
if (res == null)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
res = GetEditorAddedAsset(null, raceName);
if (res != null)
{
return res;
}
}
#endif
return null;
}
}
return res;
}
public override RaceData GetRace(int nameHash)
{
return GetRace(nameHash, true);
}
public RaceData GetRace(int nameHash, bool allowUpdate = true)
{
if (nameHash == 0)
return null;
RaceData res;
res = base.GetRace(nameHash);
#if UNITY_EDITOR
if (!Application.isPlaying && res == null)
{
res = GetEditorAddedAsset(nameHash);
}
#endif
if (res == null && allowUpdate)
{
UpdateDynamicRaceLibrary(true, nameHash);
res = base.GetRace(nameHash);
if (res == null)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
res = GetEditorAddedAsset(nameHash);
if (res != null)
{
return res;
}
}
#endif
return null;
}
}
return res;
}
/// <summary>
/// Returns the current list of races without adding from assetBundles or Resources
/// </summary>
/// <returns></returns>
public RaceData[] GetAllRacesBase()
{
return base.GetAllRaces();
}
/// <summary>
/// Gets all the races that are available including in Resources (but does not cause downloads for races that are in assetbundles)
/// </summary>
/// <returns></returns>
public override RaceData[] GetAllRaces()
{
UpdateDynamicRaceLibrary(false);
#if UNITY_EDITOR
if (!Application.isPlaying)
{
//we need a combined array of the editor added assets and the baseGetAllRaces Array
List<RaceData> combinedRaceDatas = new List<RaceData>(base.GetAllRaces());
if (editorAddedAssets.Count > 0)
{
combinedRaceDatas.AddRange(editorAddedAssets);
}
return combinedRaceDatas.ToArray();
}
else
#endif
return base.GetAllRaces();
}
/// <summary>
/// Gets the originating asset bundle.
/// </summary>
/// <returns>The originating asset bundle.</returns>
/// <param name="raceName">Race name.</param>
public string GetOriginatingAssetBundle(string raceName)
{
string originatingAssetBundle = "";
if (assetBundlesUsedDict.Count > 0)
{
foreach (KeyValuePair<string, List<string>> kp in assetBundlesUsedDict)
{
if (kp.Value.Contains(raceName))
{
originatingAssetBundle = kp.Key;
break;
}
}
}
if (originatingAssetBundle == "")
{
if (Debug.isDebugBuild)
Debug.Log(raceName + " was not found in any loaded AssetBundle");
}
else
{
if (Debug.isDebugBuild)
Debug.Log("originatingAssetBundle for " + raceName + " was " + originatingAssetBundle);
}
return originatingAssetBundle;
}
}
}
@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 20680b3241850f340b2d42b5685d8ed3
timeCreated: 1457022844
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 35611
packageName: UMA 2
packageVersion: 2.13
assetPath: Assets/UMA/Core/Extensions/DynamicCharacterSystem/Scripts/DynamicRaceLibrary.cs
uploadId: 679826
@@ -0,0 +1,354 @@
using UnityEngine;
#if UNITY_EDITOR
#endif
using System.Collections.Generic;
namespace UMA.CharacterSystem
{
public class DynamicSlotLibrary : SlotLibrary
{
public bool dynamicallyAddFromResources = true;
[Tooltip("Limit the Resources search to the following folders (no starting slash and seperate multiple entries with a comma)")]
public string resourcesFolderPath = "";
public bool dynamicallyAddFromAssetBundles;
[Tooltip("Limit the AssetBundles search to the following bundles (no starting slash and seperate multiple entries with a comma)")]
public string assetBundleNamesToSearch = "";
//This is a ditionary of asset bundles that were loaded into the library at runtime.
//CharacterAvatar can query this this to find out what asset bundles were required to create itself
//or other scripts can use it to find out which asset bundles are being used by the Libraries at any given point.
public Dictionary<string, List<string>> assetBundlesUsedDict = new Dictionary<string, List<string>>();
#if UNITY_EDITOR
List<SlotDataAsset> editorAddedAssets = new List<SlotDataAsset>();
#endif
[System.NonSerialized]
[HideInInspector]
public bool downloadAssetsEnabled = true;
public void Start()
{
if (Application.isPlaying)
{
assetBundlesUsedDict.Clear();
}
#if UNITY_EDITOR
if (Application.isPlaying)
{
ClearEditorAddedAssets();
}
#endif
}
/// <summary>
/// Clears any editor added assets when the Scene is closed
/// </summary>
void OnDestroy()
{
#if UNITY_EDITOR
ClearEditorAddedAssets();
#endif
}
public void ClearEditorAddedAssets()
{
#if UNITY_EDITOR
if (editorAddedAssets.Count > 0)
{
editorAddedAssets.Clear();
}
#endif
}
#if UNITY_EDITOR
SlotData GetEditorAddedAsset(int? nameHash = null, string slotName = "")
{
SlotData foundSlot = null;
if (editorAddedAssets.Count > 0)
{
foreach (SlotDataAsset edSlot in editorAddedAssets)
{
if(edSlot != null)
{
if (nameHash != null)
{
if(edSlot.nameHash == nameHash)
foundSlot = new SlotData(edSlot);
}else if(slotName != null)
{
if(slotName != "")
if(edSlot.slotName == slotName)
foundSlot = new SlotData(edSlot);
}
}
}
}
return foundSlot;
}
#endif
public void UpdateDynamicSlotLibrary(int? nameHash = null)
{
var Slots = UMAAssetIndexer.Instance.GetAllAssets<SlotDataAsset>();
if (nameHash != null)
{
Slots.RemoveAll(x => x.nameHash != nameHash);
}
AddSlotAssets(Slots.ToArray());
// DynamicAssetLoader.Instance.AddAssets<SlotDataAsset>(ref assetBundlesUsedDict, dynamicallyAddFromResources, dynamicallyAddFromAssetBundles, downloadAssetsEnabled, assetBundleNamesToSearch, resourcesFolderPath, nameHash, "", AddSlotAssets);
}
public void UpdateDynamicSlotLibrary(string slotName)
{
var Slots = UMAAssetIndexer.Instance.GetAllAssets<SlotDataAsset>();
Slots.RemoveAll(x => x.slotName != slotName);
AddSlotAssets(Slots.ToArray());
//DynamicAssetLoader.Instance.AddAssets<SlotDataAsset>(ref assetBundlesUsedDict, dynamicallyAddFromResources, dynamicallyAddFromAssetBundles, downloadAssetsEnabled, assetBundleNamesToSearch, resourcesFolderPath, null, slotName, AddSlotAssets);
}
private void AddSlotAssets(SlotDataAsset[] slots)
{
foreach (SlotDataAsset slot in slots)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
if (HasSlot(slot.nameHash))
continue;
if (!editorAddedAssets.Contains(slot))
editorAddedAssets.Add(slot);
}
else
#endif
AddSlotAsset(slot);
}
//This doesn't actually seem to do anything apart from slow things down
//StartCoroutine(CleanSlotsFromResourcesAndBundles());
}
/*IEnumerator CleanSlotsFromResourcesAndBundles()
{
yield return null;
Resources.UnloadUnusedAssets();
yield break;
}*/
public override SlotData InstantiateSlot(string name)
{
SlotData res;
try
{
res = base.InstantiateSlot(UMAUtils.StringToHash(name));
}
catch
{
res = null;
}
#if UNITY_EDITOR
if (!Application.isPlaying && res == null)
{
res = GetEditorAddedAsset(null, name);
}
#endif
if (res == null)
{
//we try to load the slot dynamically
UpdateDynamicSlotLibrary(name);
try
{
res = base.InstantiateSlot(name);
}
catch
{
res = null;
}
if (res == null)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
res = GetEditorAddedAsset(null, name);
if (res != null)
{
return res;
}
}
#endif
throw new UMAResourceNotFoundException("dSlotLibrary (211): Unable to find: " + name);
}
}
return res;
}
public override SlotData InstantiateSlot(int nameHash)
{
SlotData res;
try
{
res = base.InstantiateSlot(nameHash);
}
catch
{
res = null;
}
#if UNITY_EDITOR
if (!Application.isPlaying && res == null)
{
res = GetEditorAddedAsset(nameHash);
}
#endif
if (res == null)
{
UpdateDynamicSlotLibrary(nameHash);
try
{
res = base.InstantiateSlot(nameHash);
}
catch
{
res = null;
}
if (res == null)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
res = GetEditorAddedAsset(nameHash);
if (res != null)
{
return res;
}
}
#endif
throw new UMAResourceNotFoundException("dSlotLibrary: Unable to find: " + nameHash);
}
}
return res;
}
public override SlotData InstantiateSlot(string name, List<OverlayData> overlayList)
{
SlotData res;
try
{
res = base.InstantiateSlot(name);
}
catch
{
res = null;
}
#if UNITY_EDITOR
if (!Application.isPlaying && res == null)
{
res = GetEditorAddedAsset(null, name);
}
#endif
if (res == null)
{
//we load dynamically
UpdateDynamicSlotLibrary(name);
try {
res = base.InstantiateSlot(name);
}
catch
{
res = null;
}
if (res == null)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
res = GetEditorAddedAsset(null, name);
if (res != null)
{
res.SetOverlayList(overlayList);
return res;
}
}
#endif
throw new UMAResourceNotFoundException("dSlotLibrary: Unable to find: " + name);
}
}
res.SetOverlayList(overlayList);
return res;
}
public override SlotData InstantiateSlot(int nameHash, List<OverlayData> overlayList)
{
SlotData res;
try
{
res = base.InstantiateSlot(nameHash);
}
catch
{
res = null;
}
#if UNITY_EDITOR
if (!Application.isPlaying && res == null)
{
res = GetEditorAddedAsset(nameHash);
}
#endif
if (res == null)
{
UpdateDynamicSlotLibrary(nameHash);
try {
res = base.InstantiateSlot(nameHash);
}
catch
{
res = null;
}
if (res == null)
{
#if UNITY_EDITOR
if (!Application.isPlaying)
{
res = GetEditorAddedAsset(nameHash);
if (res != null)
{
res.SetOverlayList(overlayList);
return res;
}
}
#endif
throw new UMAResourceNotFoundException("dSlotLibrary: Unable to find: " + nameHash);
}
}
res.SetOverlayList(overlayList);
return res;
}
/// <summary>
/// Gets the originating asset bundle.
/// </summary>
/// <returns>The originating asset bundle.</returns>
/// <param name="slotName">slot name.</param>
public string GetOriginatingAssetBundle(string slotName)
{
string originatingAssetBundle = "";
if (assetBundlesUsedDict.Count > 0)
{
foreach (KeyValuePair<string, List<string>> kp in assetBundlesUsedDict)
{
if (kp.Value.Contains(slotName) || kp.Value.Contains(slotName.Replace("_Slot", "")))//just try without '_Slot' as well since autogenerated slot.slotname doesn't end with autogenrated '_Slot'
{
originatingAssetBundle = kp.Key;
break;
}
}
}
if (originatingAssetBundle == "")
{
if (Debug.isDebugBuild)
Debug.Log(slotName + " was not found in any loaded AssetBundle");
}
else
{
if (Debug.isDebugBuild)
Debug.Log("originatingAssetBundle for " + slotName + " was " + originatingAssetBundle);
}
return originatingAssetBundle;
}
}
}
@@ -0,0 +1,20 @@
fileFormatVersion: 2
guid: 1b9962f1e2cfc7e42bda744040c488b2
labels:
- UMA_Text
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 35611
packageName: UMA 2
packageVersion: 2.13
assetPath: Assets/UMA/Core/Extensions/DynamicCharacterSystem/Scripts/DynamicSlotLibrary.cs
uploadId: 679826
@@ -0,0 +1,27 @@
#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
namespace UMA.CharacterSystem
{
public class UMAAssetPostProcessor : AssetPostprocessor
{
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
if (BuildPipeline.isBuildingPlayer || UnityEditorInternal.InternalEditorUtility.inBatchMode || Application.isPlaying)
{
return;
}
if (EditorPrefs.GetBool("UMA_POSTPROCESS_ALL_ASSETS", false))
{
// don't call if it's the indexer that's being updated!!!
if (UMAAssetIndexer.Instance != null)
{
UMAAssetIndexer.Instance.OnPostprocessAllAssets(importedAssets, deletedAssets, movedAssets, movedFromAssetPaths);
}
}
}
}
}
#endif
@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: fedf25d4f2a5a3c4689105fc5853bc75
timeCreated: 1486604331
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 35611
packageName: UMA 2
packageVersion: 2.13
assetPath: Assets/UMA/Core/Extensions/DynamicCharacterSystem/Scripts/UMAAssetModificationProcessor.cs
uploadId: 679826
@@ -0,0 +1,111 @@
using UnityEngine;
#if UNITY_EDITOR
#endif
namespace UMA.CharacterSystem
{
//Because this is a class for user generated content it is marked as partial so it can be extended without modifying the underlying code
public partial class UMADynamicCharacterAvatarRecipe : UMATextRecipe
{
//if we ditched the additional fields in UMATextRecipe this would need
/*[SerializeField]
public List<WardrobeSettings> activeWardrobeSet = new List<WardrobeSettings>();*/
#region CONSTRUCTOR
public UMADynamicCharacterAvatarRecipe()
{
recipeType = "DynamicCharacterAvatar";
}
#if UNITY_EDITOR
public UMADynamicCharacterAvatarRecipe(UMATextRecipe recipeToCopyFrom)
{
if (recipeToCopyFrom.recipeType == "DynamicCharacterAvatar")
{
CopyFromUTR(recipeToCopyFrom);
}
}
#endif
public UMADynamicCharacterAvatarRecipe(DynamicCharacterAvatar dca, string recipeName = "", DynamicCharacterAvatar.SaveOptions customSaveOptions = DynamicCharacterAvatar.SaveOptions.useDefaults)
{
recipeType = "DynamicCharacterAvatar";
if (customSaveOptions.HasFlagSet(DynamicCharacterAvatar.SaveOptions.useDefaults))
{
customSaveOptions = dca.defaultSaveOptions;
}
if (recipeName == "")
{
recipeName = dca.gameObject.name;
}
recipeString = JsonUtility.ToJson(new DCSPackRecipe(dca, recipeName, "DynamicCharacterAvatar", customSaveOptions));
}
#endregion
#region EDITOR ONLY METHODS
#if UNITY_EDITOR
/// <summary>
/// If the given UMATextRecipe was of recipeType "DynamicCharacterAvatar", copies its to this UMADynamicCharacterAvatarRecipe, otherwise returns false.
/// </summary>
/// <param name="recipeToCopyFrom"></param>
/// <returns></returns>
private bool CopyFromUTR(UMATextRecipe recipeToCopyFrom)
{
if (recipeToCopyFrom.recipeType != "DynamicCharacterAvatar" || recipeToCopyFrom.GetType() != typeof(UMATextRecipe))
{
return false;
}
recipeType = "DynamicCharacterAvatar";
var recipeModel = JsonUtility.FromJson<DCSPackRecipe>(recipeToCopyFrom.recipeString);
recipeModel.packedRecipeType = "DynamicCharacterAvatar";
recipeString = JsonUtility.ToJson(recipeModel);
name = recipeToCopyFrom.name;
return true;
}
#endif
#endregion
//Override Load from PackedRecipeBase
/// <summary>
/// NOTE: Use GetUniversalPackRecipe to get a recipe that includes a wardrobeSet. Load this Recipe's recipeString into the specified UMAData.UMARecipe.
/// </summary>
public override void Load(UMA.UMAData.UMARecipe umaRecipe, UMAContextBase context)
{
if ((recipeString != null) && (recipeString.Length > 0))
{
if (RecipeHasWardrobeSet(recipeString))
{
activeWardrobeSet = GetRecipesWardrobeSet(recipeString);
}
else
{
if (Debug.isDebugBuild)
{
Debug.LogWarning("[UMADynamicCharacterAvatar] recipe did not have wardrobe set");
}
}
var packedRecipe = PackedLoadDCSInternal(context);
if (packedRecipe != null)
{
UnpackRecipe(umaRecipe, packedRecipe, context);
}
}
}
/*we are not going to have a create menu option for DynamicCharacterAvatar recipes I dont think
#if UNITY_EDITOR
[UnityEditor.MenuItem("Assets/Create/UMA Dynamic Character Avatar Recipe")]
public static void CreateDCAAsset()
{
UMA.Editor.CustomAssetUtility.CreateAsset<UMADynamicCharacterAvatarRecipe>();
}
#endif
*/
}
}
@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 9e51b5f5a8a166140bb25f982e78d57e
timeCreated: 1484778187
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 35611
packageName: UMA 2
packageVersion: 2.13
assetPath: Assets/UMA/Core/Extensions/DynamicCharacterSystem/Scripts/UMADynamicCharacterAvatarRecipe.cs
uploadId: 679826
@@ -0,0 +1,222 @@
using System.Collections.Generic;
using UMA;
using UMA.CharacterSystem;
using UnityEngine;
public class UMAMaterialAnimator : MonoBehaviour
{
public string slotTag = "AnimateMaterial"; // Any slots with this tag will be animated.
public enum MaterialAnimationType
{
Float,
Color
}
[System.Serializable]
public class MaterialAnimation
{
#if UNITY_EDITOR
public bool show = true; // If true, the animation is shown in the inspector.
#endif
public MaterialAnimationType type; // The type of animation.
public string overlayTag; // The tag of the overlay to animate (or empty for all overlays).
public string propertyName; // The name of the property to animate.
public AnimationCurve curve = new AnimationCurve(); // The curve that is used to animate the property.
public bool useChannel; // If true, the channel number is used. If false, the property is animated directly.
public int channelNumber; // The overlay number to animate, if any. 0 = base, 1 = first overlay, etc.
public float MinFloatValue; // The minimum value of the property.
public float MaxFloatValue; // The maximum value of the property.
public Color MinColorValue; // The minimum value of the property.
public Color MaxColorValue; // The maximum value of the property.
public override string ToString()
{
if (type == MaterialAnimationType.Float)
{
return $"{propertyName} Float {MinFloatValue} {MaxFloatValue}";
}
else
{
return $"{propertyName} Color {ColorUtility.ToHtmlStringRGBA(MinColorValue)} {ColorUtility.ToHtmlStringRGBA(MaxColorValue)}";
}
}
public void Apply(MaterialAnimationInstance instance, float time, int propertyIndex = 0)
{
if (MaterialAnimationType.Float == type)
{
ApplyFloat(instance, time, MinFloatValue, MaxFloatValue, propertyIndex);
}
else
{
ApplyColor(instance, time, MinColorValue, MaxColorValue, propertyIndex);
}
}
public void ApplyColor(MaterialAnimationInstance mat, float time, Color MinValue, Color MaxValue, int propertyIndex = 0)
{
Color value = (curve.Evaluate(time) * MaxValue) + MinValue;
// First see if it has a shader-level property
if (mat.material.HasProperty(propertyName))
{
mat.material.SetColor(propertyName, value);
}
// Then see if it has a layer-level property
string layerPropertyName = $"propertyName{mat.layer}";
if (mat.material.HasProperty(propertyName))
{
mat.material.SetColor(propertyName, value);
}
// only if we are using a channel do we need to set the channel property
if (useChannel)
{
string layerChannelPropertyName = $"propertyName{mat.layer}_{channelNumber}";
if (mat.material.HasProperty(layerChannelPropertyName))
{
mat.material.SetColor(layerChannelPropertyName, value);
}
}
}
public void ApplyFloat(MaterialAnimationInstance mat, float time, float MinValue, float MaxValue, int propertyIndex = 0)
{
float value = (curve.Evaluate(time) * MaxValue) + MinValue;
// First see if it has a shader-level property
if (mat.material.HasProperty(propertyName))
{
mat.material.SetFloat(propertyName, value);
}
// Then see if it has a layer-level property
string layerPropertyName = $"propertyName{mat.layer}";
if (mat.material.HasProperty(propertyName))
{
mat.material.SetFloat(propertyName, value);
}
// only if we are using a channel do we need to set the channel property
if (useChannel)
{
string layerChannelPropertyName = $"propertyName{mat.layer}_{channelNumber}";
if (mat.material.HasProperty(layerChannelPropertyName))
{
mat.material.SetFloat(layerChannelPropertyName, value);
}
}
}
}
[SerializeField]
public List<MaterialAnimation> animations = new List<MaterialAnimation>();
private bool initialized = false;
public class MaterialAnimationInstance
{
public MaterialAnimation animation; // The animation that this instance is using.
public Material material; // The material that is being animated.
public SlotData slot; // The slot that is being animated.
public int layer; // The layer that is being animated.
}
private List<MaterialAnimationInstance> instances = new List<MaterialAnimationInstance>();
// Start is called before the first frame update
void Start()
{
Initialize();
}
// Setup the event to find/update the slot material
public void Initialize()
{
DynamicCharacterAvatar avatar = GetComponentInParent<DynamicCharacterAvatar>();
if (avatar != null)
{
if (avatar.umaData != null)
{
avatar.umaData.CharacterUpdated.AddListener(OnCharacterUpdated);
initialized = true;
}
}
}
private void OnCharacterUpdated(UMAData umaData)
{
var slots = umaData.umaRecipe.GetIndexedSlotsByTag();
var renderers = umaData.GetRenderers();
// Generate a fresh list of material instances
instances.Clear();
for (int i = 0; i < animations.Count; i++)
{
MaterialAnimation anim = animations[i];
if (anim != null)
{
if (slots.ContainsKey(slotTag))
{
List<SlotData> slotlist = slots[slotTag];
if (slotlist != null)
{
// For each slot with the tag
// Get the slot data
// and then process the overlays to see if any need to be animated.
foreach (SlotData slot in slotlist)
{
// Get the renderer for this slot
var renderer = renderers[slot.skinnedMeshRenderer];
if (renderer != null)
{
// Get the material for this slot
Material material = renderer.sharedMaterials[slot.submeshIndex];
if (material != null)
{
var ovls = slot.GetOverlayList();
for (int layer = 0; layer < ovls.Count; layer++)
{
var ovl = ovls[layer];
// if we DO have an Overlay
// AND we have no overlayTag OR the overlay has the overlayTag
// then add the animation instance
if (ovl != null && (string.IsNullOrEmpty(anim.overlayTag) || ovl.HasTag(anim.overlayTag)))
{
MaterialAnimationInstance instance = new MaterialAnimationInstance();
instance.animation = anim;
instance.material = material;
instance.slot = slot;
instance.layer = layer;
instances.Add(instance);
}
}
}
}
}
}
}
}
}
}
// Update is called once per frame
void Update()
{
if (!initialized)
{
Initialize();
if (!initialized)
{
return;
}
}
for(int i=0; i<instances.Count; i++)
{
MaterialAnimationInstance anim = instances[i];
if (anim != null)
{
anim.animation.Apply(anim, Time.time, i);
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 947dbc3beb7523a488c18f7a09232cbc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 35611
packageName: UMA 2
packageVersion: 2.13
assetPath: Assets/UMA/Core/Extensions/DynamicCharacterSystem/Scripts/UMAMaterialAnimator.cs
uploadId: 679826
@@ -0,0 +1,103 @@
using System.Collections.Generic;
using System.Linq;
using System;
#if UNITY_EDITOR
#endif
namespace UMA
{
[Serializable]
public class DnaValue
{
public string Name;
public float Value;
public DnaValue(string name, float value)
{
Name = name;
Value = value;
}
}
[Serializable]
public class UMAPredefinedDNA
{
/// <summary>
/// This class is used for preloading DNA on DynamicCharacterAvatars
/// </summary>
/// <remarks>
/// UMAPredefinedDNA is assigned to a DynamicCharacterAvatar to allow it to preload DNA when it is built.
/// Without this, it is not possible to pregenerate random DNA because the DNA is loaded from the Race at build time.
/// After the UMAData is created, this DNA will be applied to the UMA as part of the build process, so you don't have
/// to build the DCA twice to get randomized data/.
/// </remarks>
public List<DnaValue> PreloadValues = new List<DnaValue>();
public int Count
{
get
{
return PreloadValues.Count;
}
}
public void RemoveDNA(string Name)
{
PreloadValues.RemoveAll(x => x.Name == Name);
}
public bool ContainsName(string Name)
{
return PreloadValues.Count(x => x.Name == Name) > 0;
}
public void AddRange(UMAPredefinedDNA newDNA)
{
PreloadValues.AddRange(newDNA.PreloadValues);
}
public void AddDNA(string Name, float Value)
{
for (int i = 0; i < PreloadValues.Count; i++)
{
DnaValue value = PreloadValues[i];
if (value.Name.Equals(Name))
{
value.Value = Value;
return;
}
}
// If not found, add new one
PreloadValues.Add(new DnaValue(Name, Value));
}
public void Clear()
{
PreloadValues.Clear();
}
public float GetValue(string Name)
{
if (ContainsName(Name))
{
for (int i = 0; i < PreloadValues.Count; i++)
{
DnaValue value = PreloadValues[i];
if (value.Name == Name)
{
return value.Value;
}
}
}
return 0;
}
public UMAPredefinedDNA Clone()
{
UMAPredefinedDNA newdna = new UMAPredefinedDNA();
for (int i = 0; i < PreloadValues.Count; i++)
{
DnaValue d = PreloadValues[i];
newdna.AddDNA(d.Name, d.Value);
}
return newdna;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 4af22a65fb2039b4eb839b9a8ddeff18
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 35611
packageName: UMA 2
packageVersion: 2.13
assetPath: Assets/UMA/Core/Extensions/DynamicCharacterSystem/Scripts/UMAPredefinedDNA.cs
uploadId: 679826
@@ -0,0 +1,12 @@
using System;
namespace UMA.CharacterSystem
{
[Serializable]
public class UMAPreset
{
public UMAPredefinedDNA PredefinedDNA;
public DynamicCharacterAvatar.WardrobeRecipeList DefaultWardrobe;
public DynamicCharacterAvatar.ColorValueList DefaultColors;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: a961175c4cf79284bb490d510e7327bb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 35611
packageName: UMA 2
packageVersion: 2.13
assetPath: Assets/UMA/Core/Extensions/DynamicCharacterSystem/Scripts/UMAPreset.cs
uploadId: 679826
@@ -0,0 +1,213 @@
using System.Collections.Generic;
using UMA.CharacterSystem;
using UnityEngine;
namespace UMA
{
public class UMARandomAvatar : MonoBehaviour
{
public List<UMARandomizer> Randomizers;
public GameObject prefab;
public GameObject ParentObject;
public bool ShowPlaceholder;
public bool GenerateGrid;
public int GridXSize = 5;
public int GridZSize = 4;
public float GridDistance = 1.5f;
public float RandomOffset = 0.0f;
public bool RandomRotation;
public string NameBase = "Pat";
public UMARandomAvatarEvent RandomAvatarGenerated;
private DynamicCharacterAvatar RandomAvatar;
private GameObject character;
// Use this for initialization
void Start()
{
if (ParentObject == null)
{
ParentObject = this.gameObject;
}
if (!GenerateGrid)
{
if (RandomRotation)
{
GenerateRandomCharacter(transform.position, RandRotation(transform.rotation),NameBase);
}
else
{
GenerateRandomCharacter(transform.position, transform.rotation, NameBase);
}
}
else
{
float xstart = 0-((GridXSize * GridDistance) / 2.0f);
int i = 0;
for (int x=0;x<GridXSize;x++)
{
float zstart = 0-((GridZSize * GridDistance) / 2.0f);
for (int z=0;z<GridZSize;z++)
{
Vector3 pos = new Vector3(transform.position.x + xstart, transform.position.y, transform.position.z + zstart);
if (RandomOffset != 0.0f)
{
pos.x = pos.x + Random.Range(-RandomOffset, RandomOffset);
pos.z = pos.z + Random.Range(-RandomOffset, RandomOffset);
}
if (RandomRotation)
{
GenerateRandomCharacter(pos, RandRotation(transform.rotation),NameBase + " "+ i);
}
else
{
GenerateRandomCharacter(pos, transform.rotation, NameBase + " " + i);
}
++i;
zstart += GridDistance;
}
xstart += GridDistance;
}
}
}
private Quaternion RandRotation(Quaternion src)
{
Vector3 Euler = src.eulerAngles;
return Quaternion.Euler(Euler.x, Random.Range(0.0f, 359.9f), Euler.z);
}
public void GenerateRandomCharacter(Vector3 Pos, Quaternion Rot, string Name)
{
if (prefab)
{
GameObject go = GameObject.Instantiate(prefab, Pos, Rot);
if (ParentObject != null)
{
go.transform.parent = ParentObject.transform;
}
RandomAvatar = go.GetComponent<DynamicCharacterAvatar>();
go.name = Name;
// Event for possible networking here
if (RandomAvatarGenerated != null)
{
RandomAvatarGenerated.Invoke(gameObject, go);
}
}
Randomize(RandomAvatar);
RandomAvatar.BuildCharacter(!RandomAvatar.BundleCheck);
}
public RandomWardrobeSlot GetRandomWardrobe(List<RandomWardrobeSlot> wardrobeSlots)
{
int total = 0;
for (int i = 0; i < wardrobeSlots.Count; i++)
{
RandomWardrobeSlot rws = wardrobeSlots[i];
total += rws.Chance;
}
for (int i = 0; i < wardrobeSlots.Count; i++)
{
RandomWardrobeSlot rws = wardrobeSlots[i];
if (UnityEngine.Random.Range(0,total) < rws.Chance)
{
return rws;
}
}
return wardrobeSlots[wardrobeSlots.Count - 1];
}
private OverlayColorData GetRandomColor(RandomColors rc)
{
int inx = UnityEngine.Random.Range(0, rc.ColorTable.colors.Length);
return rc.ColorTable.colors[inx];
}
private void AddRandomSlot(DynamicCharacterAvatar Avatar, RandomWardrobeSlot uwr)
{
Avatar.SetSlot(uwr.WardrobeSlot);
if (uwr.Colors != null)
{
for (int i = 0; i < uwr.Colors.Count; i++)
{
RandomColors rc = uwr.Colors[i];
if (rc.ColorTable != null)
{
OverlayColorData ocd = GetRandomColor(rc);
Avatar.SetColor(rc.ColorName, ocd, false);
}
}
}
}
#if UNITY_EDITOR
void OnDrawGizmos()
{
if (ShowPlaceholder)
{
Gizmos.DrawCube(transform.position, Vector3.one);
}
}
#endif
public void Randomize(DynamicCharacterAvatar Avatar)
{
// Must clear that out!
Avatar.WardrobeRecipes.Clear();
UMARandomizer Randomizer = null;
if (Randomizers != null)
{
if (Randomizers.Count == 0)
{
return;
}
if (Randomizers.Count == 1)
{
Randomizer = Randomizers[0];
}
else
{
Randomizer = Randomizers[UnityEngine.Random.Range(0, Randomizers.Count)];
}
}
if (Avatar != null && Randomizer != null)
{
RandomAvatar ra = Randomizer.GetRandomAvatar();
Avatar.ChangeRaceData(ra.RaceName);
//Avatar.BuildCharacterEnabled = true;
var RandomDNA = ra.GetRandomDNA();
Avatar.predefinedDNA = RandomDNA;
var RandomSlots = ra.GetRandomSlots();
if (ra.SharedColors != null && ra.SharedColors.Count > 0)
{
for (int i = 0; i < ra.SharedColors.Count; i++)
{
RandomColors rc = ra.SharedColors[i];
if (rc.ColorTable != null)
{
Avatar.SetColor(rc.ColorName, GetRandomColor(rc), false);
}
}
}
foreach (string s in RandomSlots.Keys)
{
List<RandomWardrobeSlot> RandomWardrobe = RandomSlots[s];
RandomWardrobeSlot uwr = GetRandomWardrobe(RandomWardrobe);
if (uwr.WardrobeSlot != null)
{
AddRandomSlot(Avatar, uwr);
}
}
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 910d06e64183ac547ab816b9f03574ff
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 35611
packageName: UMA 2
packageVersion: 2.13
assetPath: Assets/UMA/Core/Extensions/DynamicCharacterSystem/Scripts/UMARandomAvatar.cs
uploadId: 679826
@@ -0,0 +1,303 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UMA.CharacterSystem;
namespace UMA
{
// A Random DNA
[Serializable]
public class RandomDNA
{
public string DnaName;
public float MinValue;
public float MaxValue;
#if UNITY_EDITOR
public bool Delete { get; set; } = false;
#endif
public RandomDNA(string name)
{
DnaName = name;
MinValue = 0.0f;
MaxValue = 1.0f;
}
}
// A list of possible colors for a shared color.
[Serializable]
public class RandomColors
{
public string ColorName;
public SharedColorTable ColorTable;
// public List<RandomColor> Colors;
#if UNITY_EDITOR
public bool GuiFoldout;
public bool Delete;
public int CurrentColor;
#endif
public RandomColors(string name, SharedColorTable sct)
{
#if UNITY_EDITOR
CurrentColor = 0;
GuiFoldout = true;
Delete = false;
#endif
ColorName = name;
ColorTable = sct;
}
public RandomColors(RandomWardrobeSlot rws)
{
#if UNITY_EDITOR
CurrentColor = 0;
GuiFoldout = true;
Delete = false;
#endif
}
}
[Serializable]
public class RandomWardrobeSlot
{
public UMAWardrobeRecipe WardrobeSlot;
[Range(1, 100)]
public int Chance = 1;
public List<RandomColors> Colors;
public string _slotName;
public string SlotName
{
get
{
if (WardrobeSlot != null)
{
return WardrobeSlot.wardrobeSlot;
}
return _slotName;
}
}
#if UNITY_EDITOR
public bool GuiFoldout;
public bool Delete;
public bool AddColorTable;
public string[] PossibleColors;
public string SortName
{
get
{
string slot = "";
if (WardrobeSlot != null)
{
slot = WardrobeSlot.name;
}
return SlotName + slot;
}
}
#endif
public RandomWardrobeSlot(UMAWardrobeRecipe slot, string slotName)
{
#if UNITY_EDITOR
GuiFoldout = true;
Delete = false;
_slotName = slotName;
if (slot == null)
{
PossibleColors = new string[0];
}
else
{
UMAPackedRecipeBase.UMAPackRecipe upr = slot.PackedLoad();
List<string> cols = new List<string>();
for (int i = 0; i < upr.fColors.Length; i++)
{
UMAPackedRecipeBase.PackedOverlayColorDataV3 pcd = upr.fColors[i];
if (pcd.name.Trim() != "-")
{
cols.Add(pcd.name);
}
}
PossibleColors = cols.ToArray();
}
#endif
Colors = new List<RandomColors>();
WardrobeSlot = slot;
}
}
[Serializable]
public class RandomAvatar
{
public string RaceName;
[Range(1, 100)]
public int Chance = 1;
public List<RandomColors> SharedColors;
public List<RandomWardrobeSlot> RandomWardrobeSlots;
public List<RandomDNA> RandomDna;
public RaceData raceData;
#if UNITY_EDITOR
public bool GuiFoldout;
public bool ColorsFoldout;
public bool WardrobeFoldout;
public bool DnaFoldout;
public bool Delete;
public bool AddColorTable;
public bool DnaChanged;
public string[] PossibleColors;
public string[] PossibleDNA;
public int SelectedDNA;
public string DNAAdd;
public int DNADel;
public int currentWardrobeSlot;
#endif
public UMAPredefinedDNA GetRandomDNA()
{
UMAPredefinedDNA theDNA = new UMAPredefinedDNA();
for (int i = 0; i < RandomDna.Count; i++)
{
RandomDNA rd = this.RandomDna[i];
theDNA.AddDNA(rd.DnaName, UnityEngine.Random.Range(rd.MinValue, rd.MaxValue));
}
return theDNA;
}
public Dictionary<string, List<RandomWardrobeSlot>> GetRandomSlots()
{
Dictionary<string, List<RandomWardrobeSlot>> RandomSlots = new Dictionary<string, List<RandomWardrobeSlot>>();
for (int i = 0; i < RandomWardrobeSlots.Count; i++)
{
RandomWardrobeSlot rws = RandomWardrobeSlots[i];
string wslot = rws.SlotName;//rws.WardrobeSlot.wardrobeSlot;
if (!RandomSlots.ContainsKey(wslot))
{
RandomSlots.Add(wslot, new List<RandomWardrobeSlot>());
}
RandomSlots[wslot].Add(rws);
}
return RandomSlots;
}
private List<RandomColors> GetColorListForRace(RaceData rc)
{
UMATextRecipe utr = rc.baseRaceRecipe as UMATextRecipe;
UMAPackedRecipeBase.UMAPackRecipe upr = utr.PackedLoad();
List<string> cols = new List<string>();
for (int i = 0; i < upr.fColors.Length; i++)
{
UMAPackedRecipeBase.PackedOverlayColorDataV3 pcd = upr.fColors[i];
if (pcd.name.Trim() != "-")
{
cols.Add(pcd.name);
}
}
List<RandomColors> newColors = new List<RandomColors>();
for (int i = 0; i < cols.Count; i++)
{
string s = cols[i];
RandomColors rcs = new RandomColors(s, null);
newColors.Add(rcs);
}
return newColors;
}
#if UNITY_EDITOR
public void SetupDNA(RaceData rc)
{
List<string> DNAList = new List<string>();
for (int i = 0; i < rc.dnaConverterList.Length; i++)
{
IDNAConverter cvt = rc.dnaConverterList[i];
if (cvt.DNAType == typeof(DynamicUMADna))
{
DNAList.AddRange(((IDynamicDNAConverter)cvt).dnaAsset.Names);
}
}
PossibleDNA = DNAList.ToArray();
}
#endif
public RandomAvatar(RaceData race)
{
raceData = race;
RaceName = race.raceName;
SharedColors = new List<RandomColors>();
RandomWardrobeSlots = new List<RandomWardrobeSlot>();
RandomDna = new List<RandomDNA>();
SharedColors = GetColorListForRace(race);
#if UNITY_EDITOR
GuiFoldout = true;
Delete = false;
SetupDNA(race);
#endif
}
}
public class UMARandomizer : ScriptableObject
{
#if UNITY_EDITOR
public int currentRace { get; set; } = 0;
public string[] races { get; set; } = new string[0];
public List<RaceData> raceDatas { get; set; } = new List<RaceData>();
public List<UMAWardrobeRecipe> droppedItems { get; set; } = new List<UMAWardrobeRecipe>();
#if UMA_HOTKEYS
[UnityEditor.MenuItem("Assets/Create/UMA/Misc/Randomizer %#h")]
#else
[UnityEditor.MenuItem("Assets/Create/UMA/Misc/Randomizer")]
#endif
public static void CreatePreloadAsset()
{
UMA.CustomAssetUtility.CreateAsset<UMARandomizer>();
}
#endif
public int RandomCount
{
get
{
return RandomAvatars.Count;
}
}
public List<RandomAvatar> RandomAvatars = new List<RandomAvatar>();
public RandomAvatar GetRandomAvatar()
{
if (RandomAvatars.Count == 1)
{
return RandomAvatars[0];
}
if (RandomAvatars.Count == 0)
{
return null;
}
int total = 0;
// find the total number of chances.
for (int i = 0; i < RandomAvatars.Count; i++)
{
RandomAvatar ra = RandomAvatars[i];
total += ra.Chance;
}
for (int i = 0; i < RandomAvatars.Count; i++)
{
RandomAvatar ra = RandomAvatars[i];
int rval = UnityEngine.Random.Range(0, total);
if (rval < ra.Chance)
{
return ra;
}
}
return RandomAvatars[RandomAvatars.Count - 1];
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 7a834b471b2a70f46be58ea3025b119f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 35611
packageName: UMA 2
packageVersion: 2.13
assetPath: Assets/UMA/Core/Extensions/DynamicCharacterSystem/Scripts/UMARandomizer.cs
uploadId: 679826
@@ -0,0 +1,216 @@
using UnityEngine;
#if UNITY_EDITOR
#endif
using System.Collections.Generic;
namespace UMA.CharacterSystem
{
//Because this is a class for user generated content it is marked as partial so it can be extended without modifying the underlying code
public partial class UMAWardrobeCollection : UMATextRecipe
{
[Tooltip("Cover images for the collection as a whole. Use these for a promotional images for this collection, presenting the goodies inside.")]
public List<Sprite> coverImages = new List<Sprite>();
public WardrobeCollectionList wardrobeCollection = new WardrobeCollectionList();
[Tooltip("WardrobeCollections can also contain an arbitrary list of wardrobeRecipes, not associated with any particular race.You can use this to make a 'hairStyles' pack or a 'tattoos' pack for example")]
public List<string> arbitraryRecipes = new List<string>();
#region CONSTRUCTOR
public UMAWardrobeCollection()
{
recipeType = "WardrobeCollection";
wardrobeSlot = "FullOutfit";
}
#endregion
#region PUBLIC METHODS
/// <summary>
/// Gets the CoverImage for the collection at the desired index (if set) if none is set falls back to the first wardrobeThumb (if set)
/// </summary>
public Sprite GetCoverImage(int desiredIndex = 0)
{
if (coverImages.Count > desiredIndex)
{
return coverImages[desiredIndex];
}
else if (coverImages.Count > 0)
{
return coverImages[0];
}
else if (wardrobeRecipeThumbs.Count > 0)
{
return wardrobeRecipeThumbs[0].thumb;
}
else
{
return null;
}
}
/// <summary>
/// Requests each recipe in the Collection from DynamicCharacterSystem (optionally limited by race) which will trigger the download of the recipes if they are in asset bundles.
/// </summary>
public void EnsureLocalAvailability(string forRace = "")
{
//Ensure WardrobeCollection items
var thisRecipeNames = wardrobeCollection.GetAllRecipeNamesInCollection(forRace);
if (thisRecipeNames.Count > 0)
{
//we maybe adding recipes for races we have not downloaded yet so make sure DCS has a place for them in its index
if (forRace != "")
{
UMAContext.Instance.EnsureRaceKey(forRace);
}
else
{
for (int i = 0; i < compatibleRaces.Count; i++)
{
string race = compatibleRaces[i];
UMAContext.Instance.EnsureRaceKey(race);
}
}
for (int i = 0; i < thisRecipeNames.Count; i++)
{
UMAContext.Instance.GetRecipe(thisRecipeNames[i], true);
}
}
//Ensure Arbitrary Items
if(arbitraryRecipes.Count > 0)
{
for (int i = 0; i < arbitraryRecipes.Count; i++)
{
UMAContext.Instance.GetRecipe(arbitraryRecipes[i], true);
}
}
}
public List<WardrobeSettings> GetRacesWardrobeSet(string race)
{
var thisContext = UMAContextBase.Instance;
if(thisContext == null)
{
if (Debug.isDebugBuild)
{
Debug.LogWarning("Getting the WardrobeSet from a WardrobeCollection requires a valid UMAContextBase in the scene");
}
return new List<WardrobeSettings>();
}
var thisRace = UMAContext.Instance.GetRace(race);
return GetRacesWardrobeSet(thisRace);
}
/// <summary>
/// Gets the wardrobeSet set in this collection for the given race
/// Or wardrobeSet for first matched cross compatible race the given race has
/// </summary>
public List<WardrobeSettings> GetRacesWardrobeSet(RaceData race)
{
var setToUse = wardrobeCollection[race.raceName];
//if no set was directly compatible with the active race, check if it has sets for any cross compatible races that race may have
if (setToUse.Count == 0)
{
var thisDCACCRaces = race.GetCrossCompatibleRaces();
for (int i = 0; i < thisDCACCRaces.Count; i++)
{
if (wardrobeCollection[thisDCACCRaces[i]].Count > 0)
{
setToUse = wardrobeCollection[thisDCACCRaces[i]];
break;
}
}
}
return setToUse;
}
/// <summary>
/// Gets the recipe names for the given race from the WardrobeCollection
/// </summary>
public List<string> GetRacesRecipeNames(string race, DynamicCharacterSystem dcs)
{
var recipesToGet = GetRacesWardrobeSet(race);
List<string> recipesWeGot = new List<string>();
for (int i = 0; i < recipesToGet.Count; i++)
{
recipesWeGot.Add(recipesToGet[i].recipe);
}
return recipesWeGot;
}
/// <summary>
/// Gets the wardrobeRecipes for the given race from the WardrobeCollection
/// </summary>
public List<UMATextRecipe> GetRacesRecipes(string race, DynamicCharacterSystem dcs)
{
var recipesToGet = GetRacesWardrobeSet(race);
List<UMATextRecipe> recipesWeGot = new List<UMATextRecipe>();
for (int i = 0; i < recipesToGet.Count; i++)
{
recipesWeGot.Add(dcs.GetRecipe(recipesToGet[i].recipe, true));
}
return recipesWeGot;
}
/// <summary>
/// Gets the recipe names from this collections arbitrary recipes list
/// </summary>
public List<string> GetArbitraryRecipesNames()
{
return arbitraryRecipes;
}
/// <summary>
/// Gets the wardrobeRecipes from this collections arbitrary recipes list
/// </summary>
public List<UMATextRecipe> GetArbitraryRecipes(DynamicCharacterSystem dcs)
{
List<UMATextRecipe> recipesWeGot = new List<UMATextRecipe>();
for (int i = 0; i < arbitraryRecipes.Count; i++)
{
recipesWeGot.Add(dcs.GetRecipe(arbitraryRecipes[i], true));
}
return recipesWeGot;
}
/// <summary>
/// Gets a DCSUnversalPackRecipeModel that has the wardrobeSet set to be the set in this collection for the given race of the sent avatar
/// Or if this recipe is cross compatible returns the wardrobe set for the first matched cross compatible race
/// </summary>
public DCSUniversalPackRecipe GetUniversalPackRecipe(DynamicCharacterAvatar dca, UMAContextBase context)
{
var thisPackRecipe = PackedLoadDCSInternal(context);
RaceData race = dca.activeRace.racedata;
if (dca.activeRace.racedata == null)
{
race = dca.activeRace.data;
}
var setToUse = GetRacesWardrobeSet(race);
thisPackRecipe.wardrobeSet = setToUse;
thisPackRecipe.race = dca.activeRace.name;
return thisPackRecipe;
}
//Override Load from PackedRecipeBase
/// <summary>
/// NOTE: Use GetUniversalPackRecipe to get a recipe that includes a wardrobeSet. Load this Recipe's recipeString into the specified UMAData.UMARecipe.
/// </summary>
public override void Load(UMA.UMAData.UMARecipe umaRecipe, UMAContextBase context)
{
if ((recipeString != null) && (recipeString.Length > 0))
{
var packedRecipe = PackedLoadDCSInternal(context);
if(packedRecipe != null)
{
UnpackRecipe(umaRecipe, packedRecipe, context);
}
}
}
#endregion
#if UNITY_EDITOR
[UnityEditor.MenuItem("Assets/Create/UMA/DCS/Wardrobe Collection")]
public static void CreateWardrobeCollectionAsset()
{
UMA.CustomAssetUtility.CreateAsset<UMAWardrobeCollection>();
}
#endif
}
}
@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 6ae7341e07d1b8f41aa37f5e2c910168
timeCreated: 1483564771
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 35611
packageName: UMA 2
packageVersion: 2.13
assetPath: Assets/UMA/Core/Extensions/DynamicCharacterSystem/Scripts/UMAWardrobeCollection.cs
uploadId: 679826
@@ -0,0 +1,108 @@
using UnityEngine;
using System.Collections.Generic;
#if UNITY_EDITOR
#endif
namespace UMA.CharacterSystem
{
public partial class UMAWardrobeRecipe : UMATextRecipe
{
[SerializeField]
[Tooltip("For tracking incompatible items. Not automatic.")]
public List<UMAWardrobeRecipe> IncompatibleRecipes = new List<UMAWardrobeRecipe>();
[SerializeField]
[Tooltip("The system does not use this field. Use it for whatever you need.")]
public string UserField;
#region FIELDS
[SerializeField]
public string replaces;
public bool HasReplaces
{
get
{
if (string.IsNullOrEmpty(replaces))
{
return false;
}
if (replaces.ToLower() == "nothing")
{
return false;
}
return true;
}
}
#endregion
#region CONSTRUCTOR
//if we get sent an UMATextRecipe that has a recipe type of Wardrobe then we create a new asset that has that assets properties
//save that asset and rename the asset to be the name of the asset we deleted and maybe show a message saying 'Please update your AssetBundles'
public UMAWardrobeRecipe()
{
recipeType = "Wardrobe";
}
#if UNITY_EDITOR
public UMAWardrobeRecipe(UMATextRecipe recipeToCopyFrom)
{
if(recipeToCopyFrom.recipeType == "Wardrobe")
{
CopyFromUTR(recipeToCopyFrom);
}
}
#endif
#endregion
#region EDITOR ONLY METHODS
#if UNITY_EDITOR
private bool CopyFromUTR(UMATextRecipe recipeToCopyFrom)
{
if (Debug.isDebugBuild)
{
Debug.Log("WardrobeConverts");
}
if (recipeToCopyFrom.recipeType != "Wardrobe" || recipeToCopyFrom.GetType() != typeof(UMATextRecipe))
{
return false;
}
recipeType = "Wardrobe";
recipeString = recipeToCopyFrom.recipeString;
compatibleRaces = recipeToCopyFrom.compatibleRaces;
wardrobeSlot = recipeToCopyFrom.wardrobeSlot;
suppressWardrobeSlots = recipeToCopyFrom.suppressWardrobeSlots;
Hides = recipeToCopyFrom.Hides;
wardrobeRecipeThumbs = recipeToCopyFrom.wardrobeRecipeThumbs;
name = recipeToCopyFrom.name;
if (recipeToCopyFrom.OverrideDNA != null)
{
OverrideDNA = recipeToCopyFrom.OverrideDNA.Clone();
}
DisplayValue = recipeToCopyFrom.DisplayValue;
return true;
}
#endif
#endregion
#if UNITY_EDITOR
#if UMA_HOTKEYS
[UnityEditor.MenuItem("Assets/Create/UMA/DCS/Wardrobe Recipe %#w")]
#else
[UnityEditor.MenuItem("Assets/Create/UMA/DCS/Wardrobe Recipe")]
#endif
public static void CreateWardrobeRecipeAsset()
{
UMA.CustomAssetUtility.CreateAsset<UMAWardrobeRecipe>();
}
#endif
}
}
@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: f0188188c253e0c4bb9b46e19fa7312e
timeCreated: 1509671724
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 20283042ce050754a848394ca20d509c, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 35611
packageName: UMA 2
packageVersion: 2.13
assetPath: Assets/UMA/Core/Extensions/DynamicCharacterSystem/Scripts/UMAWardrobeRecipe.cs
uploadId: 679826
@@ -0,0 +1,720 @@
using UnityEngine;
using System;
using System.Reflection;
using System.Collections.Generic;
using UMA.CharacterSystem;
using System.Linq.Expressions;
namespace UMA
{
public partial class UMATextRecipe : UMAPackedRecipeBase
{
//TODO use the recipeTypeOpts enum for this everywhere
public string recipeType = "Standard";
[SerializeField]
public string DisplayValue; // some sort of text value.
[SerializeField]
public List<string> compatibleRaces = new List<string>();
[SerializeField]
public List<WardrobeRecipeThumb> wardrobeRecipeThumbs = new List<WardrobeRecipeThumb>();
public string wardrobeSlot = "None";
[SerializeField]
public bool Appended = false;
[SerializeField]
public List<string> Hides = new List<string>();
[SerializeField]
public List<string> HideTags = new List<string>();
[SerializeField]
public List<string> suppressWardrobeSlots = new List<string>();
[SerializeField]
public List<WardrobeSettings> activeWardrobeSet = new List<WardrobeSettings>();//used in the editor to draw 'DynamicCharacterSystem' type recipe assets, in a different way to 'Standard' or 'Wardrobe' assets
[SerializeField]
public List<MeshHideAsset> MeshHideAssets = new List<MeshHideAsset>();
[SerializeField]
public UMAPredefinedDNA OverrideDNA = new UMAPredefinedDNA();
[SerializeField]
public bool disabled = false;
#if UNITY_EDITOR
/// <summary>
/// Converts this recipe to the given child type. Used by RecipeEditor to convert old recipes
/// </summary>
public virtual void ConvertToType(string typeName)
{
if (Debug.isDebugBuild)
{
Debug.Log("Tried to convert to " + typeName);
}
Type[] array = Assembly.GetAssembly(typeof(UMATextRecipe)).GetTypes();
for (int i = 0; i < array.Length; i++)
{
Type t = array[i];
if (t.Name == typeName)
{
if (Debug.isDebugBuild)
{
Debug.Log("found matching type");
}
MethodInfo ConvertMethod = t.GetMethod("ConvertFromUTR", BindingFlags.Instance | BindingFlags.NonPublic);
if (ConvertMethod != null)
{
if (Debug.isDebugBuild)
{
Debug.Log("Found Convert method");
}
var newT = ScriptableObject.CreateInstance(t);
ConvertMethod.Invoke(newT, new object[] { this, true });
break;
}
else
{
if (Debug.isDebugBuild)
{
Debug.Log("No convert method found in type " + t.Name);
}
}
}
}
}
#endif
#if UNITY_EDITOR
/// <summary>
/// Creates a temporary UMAContextBase for use when editing recipes when the open Scene does not have an UMAContextBase or libraries set up
/// </summary>
///
public override UMAContextBase CreateEditorContext()
{
//UMAContextBase.CreateEditorContext();
return UMAContextBase.Instance;
}
#endif
/// <summary>
/// Gets the thumbnail for this WardrobeRecipe filtered by racename
/// </summary>
/// <param name="racename"></param>
/// <returns></returns>
public Sprite GetWardrobeRecipeThumbFor(string racename)
{
Sprite foundSprite = null;
if (wardrobeRecipeThumbs.Count > 0)
{
for (int i = 0; i < wardrobeRecipeThumbs.Count; i++)
{
WardrobeRecipeThumb wdt = wardrobeRecipeThumbs[i];
//Set a default when the first option with a value is found
if (foundSprite == null && wdt.thumb != null)
{
foundSprite = wdt.thumb;
}
//Override that if there is a specific one is found for this race
if (wdt.race == racename)
{
if (wdt.thumb != null)
{
foundSprite = wdt.thumb;
}
}
}
}
//this could be extended to generate a sprite/texture if the recipe only has a filename but has no sprite refrence (i.e. if it was generated by the user)
return foundSprite;
}
public static List<WardrobeSettings> GenerateWardrobeSet(Dictionary<string, UMATextRecipe> wardrobeRecipes, Dictionary<string, UMAWardrobeCollection> wardrobeCollections, Dictionary<string, List<UMATextRecipe>> addlrecipes, params string[] slotsToSave)
{
var thisWardrobeSettings = GenerateWardrobeSet(wardrobeRecipes, slotsToSave);
thisWardrobeSettings.AddRange(GenerateWardrobeSet(addlrecipes, slotsToSave));
foreach(UMAWardrobeCollection uwr in wardrobeCollections.Values)
{
thisWardrobeSettings.Add(new WardrobeSettings("WardrobeCollection", uwr.name));
}
return thisWardrobeSettings;
}
public static List<WardrobeSettings> GenerateWardrobeSet(Dictionary<string, List<UMATextRecipe>> addlRecipes, params string[] slotsToSave)
{
if (addlRecipes == null)
{
return null;
}
var wardrobeSet = new List<WardrobeSettings>();
if (addlRecipes.Count == 0)
{
return wardrobeSet;
}
if (slotsToSave.Length > 0)
{
for (int i = 0; i < slotsToSave.Length; i++)
{
string s = slotsToSave[i];
if (addlRecipes.ContainsKey(s))
{
for (int i1 = 0; i1 < addlRecipes[s].Count; i1++)
{
UMATextRecipe utr = addlRecipes[s][i1];
if (utr != null)
{
wardrobeSet.Add(new WardrobeSettings(s, utr.name));
continue;
}
}
}
wardrobeSet.Add(new WardrobeSettings(s, ""));
}
}
else
{
foreach (KeyValuePair<string, List<UMATextRecipe>> kp in addlRecipes)
{
for (int i = 0; i < kp.Value.Count; i++)
{
UMATextRecipe utr = kp.Value[i];
wardrobeSet.Add(new WardrobeSettings(utr.wardrobeSlot, utr.name));
}
}
}
return wardrobeSet;
}
public static List<WardrobeSettings> GenerateWardrobeSet(Dictionary<string, UMATextRecipe> wardrobeRecipes, params string[] slotsToSave)
{
if (wardrobeRecipes == null)
{
return null;
}
var wardrobeSet = new List<WardrobeSettings>();
if (wardrobeRecipes.Count == 0)
{
return wardrobeSet;
}
if (slotsToSave.Length > 0)
{
for (int i = 0; i < slotsToSave.Length; i++)
{
string s = slotsToSave[i];
if (wardrobeRecipes.ContainsKey(s))
{
UMATextRecipe utr = wardrobeRecipes[s];
if (utr != null)
{
wardrobeSet.Add(new WardrobeSettings(s, utr.name));
continue;
}
}
wardrobeSet.Add(new WardrobeSettings(s, ""));
}
}
else
{
foreach (KeyValuePair<string, UMATextRecipe> kp in wardrobeRecipes)
{
wardrobeSet.Add(new WardrobeSettings(kp.Key, kp.Value.name));
}
}
return wardrobeSet;
}
//Override Load from PackedRecipeBase
/// <summary>
/// Load this Recipe's recipeString into the specified UMAData.UMARecipe. If there is Wardrobe data in the recipe string, its values are set to this recipe assets 'activeWardrobeSet' field
/// </summary>
/// <param name="umaRecipe">UMA recipe.</param>
/// <param name="context">Context.</param>
public override void Load(UMA.UMAData.UMARecipe umaRecipe, UMAContextBase context = null)
{
try
{
//This check can be removed in future- If we set the recipeType properly from now on we should not need to do this check
var typeInRecipe = GetRecipesType(recipeString);
recipeType = typeInRecipe != "Standard" ? typeInRecipe : recipeType;
if (RecipeHasWardrobeSet(recipeString))
{
activeWardrobeSet = GetRecipesWardrobeSet(recipeString);
}
//if its an old UMARecipe there wont be an activeWardrobeSet field
if (activeWardrobeSet == null)
{
recipeType = "Standard";
base.Load(umaRecipe, context);
return;
}
//if it has a wardrobeSet or was saved using the DCSPackRecipe Model
if (activeWardrobeSet.Count > 0 || (recipeType == "DynamicCharacterAvatar" /*|| recipeType == "WardrobeCollection"*/))
{
var packedRecipe = PackedLoadDCSInternal(context/*, recipeString*/);
UnpackRecipe(umaRecipe, packedRecipe, context);
}
else //we can use standard UMALoading
{
base.Load(umaRecipe, context);
}
}
catch (UMAResourceNotFoundException e)
{
Debug.LogError($"UMAResourceNotFoundException on recipe {this.name} race {umaRecipe.raceData.raceName} file {umaRecipe.raceData.name}: {e.Message}");
}
catch (Exception e)
{
Debug.LogError("Error loading recipe: " + name + " " + e.Message);
}
}
/// <summary>
/// Internal call to static PackedLoadDCS which uses the assets string and object and returns a DCSUniversalPackRecipe data model that can be used by any UMA
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
protected DCSUniversalPackRecipe PackedLoadDCSInternal(UMAContextBase context/*, string recipeToUnpack*/)
{
return PackedLoadDCS(context, recipeString, this);
}
/// <summary>
/// Returns the recipe string as a DCSUniversalPackRecipe data model that can be used by any UMA
/// </summary>
/// <param name="context"></param>
/// <param name="recipeToUnpack"></param>
/// <param name="targetUTR">If set the wardrobeSet (if it exists) and the recipeType will assigned to UMATextRecipe assets fields (used by the Recipe Editor)</param>
/// <returns></returns>
public static DCSUniversalPackRecipe PackedLoadDCS(UMAContextBase context, string recipeToUnpack, UMATextRecipe targetUTR = null)
{
if ((recipeToUnpack == null) || (recipeToUnpack.Length == 0))
{
return new DCSUniversalPackRecipe();
}
//first use the DCSRecipeChecker to check if this is a DCS recipe
var typeInRecipe = GetRecipesType(recipeToUnpack);
var targetRecipeType = typeInRecipe != "Standard" ? typeInRecipe : (targetUTR != null ? targetUTR.recipeType : "Standard");
if (targetUTR != null)
{
targetUTR.recipeType = targetRecipeType;
if (RecipeHasWardrobeSet(recipeToUnpack))
{
targetUTR.activeWardrobeSet = GetRecipesWardrobeSet(recipeToUnpack);
}
}
//Right now the only recipeType that uses the DCSModel is "DynamicCharacterAvatar"
DCSUniversalPackRecipe thisUnpackedUniversal = null;
if (targetRecipeType == "DynamicCharacterAvatar" || targetRecipeType == "WardrobeCollection")
{
var thisUnpacked = JsonUtility.FromJson<DCSPackRecipe>(recipeToUnpack);
thisUnpackedUniversal = new DCSUniversalPackRecipe(thisUnpacked);
}
else
{
var thisUnpacked = JsonUtility.FromJson<UMAPackRecipe>(recipeToUnpack);
thisUnpackedUniversal = new DCSUniversalPackRecipe(thisUnpacked);
}
if (RecipeHasWardrobeSet(recipeToUnpack))
{
thisUnpackedUniversal.wardrobeSet = GetRecipesWardrobeSet(recipeToUnpack);
}
return thisUnpackedUniversal;
}
//TODO: once everyone has their recipes updated remove this- we only want UMATextRecipes to save as 'Standard'
/// <summary>
/// Saves a 'Standard' UMATextRecipe. If saving a DynamicCharacterAvatar as 'Backwards Compatible' this will save a recipe that has slots/overlay data AND a wardrobe set
/// </summary>
public void Save(UMAData.UMARecipe umaRecipe, UMAContextBase context, Dictionary<string, UMATextRecipe> wardrobeRecipes, bool backwardsCompatible = true)
{
if (wardrobeRecipes.Count > 0)
{
activeWardrobeSet = GenerateWardrobeSet(wardrobeRecipes);
}
recipeType = backwardsCompatible ? "Standard" : "DynamicCharacterAvatar";
Save(umaRecipe, context);
}
//This is used when an inspected recipe asset is saved
public override void Save(UMAData.UMARecipe umaRecipe, UMAContextBase context)
{
if (recipeType == "Wardrobe")//Wardrobe Recipes can save the standard UMA way- they dont have WardrobeSets- although the recipe string wont have a packedRecipeType field
{
base.Save(umaRecipe, context);
}
else if (recipeType != "Standard")//this will just be for type DynamicCharacterAvatar- and WardrobeCollection if we add that
{
var packedRecipe = PackRecipeV3(umaRecipe);
//DCSPackRecipe doesn't do any more work, it just gets the values from PackRecipeV3 that we need and discards the rest
var packedRecipeToSave = new DCSPackRecipe(packedRecipe, this.name, recipeType, activeWardrobeSet);
recipeString = JsonUtility.ToJson(packedRecipeToSave);
}
else //This will be Standard- this is 'backwards Compatible' and is also how the Recipe Editor saves 'backwardsCompatible' 'Standard' recipes when they are inspected
{
umaRecipe.MergeMatchingOverlays();
var packedRecipe = PackRecipeV3(umaRecipe);
var packedRecipeToSave = new DCSUniversalPackRecipe(packedRecipe);//this gets us a recipe with all the standard stuff plus our extra fields
//so now we can save the wardrobeSet into it if it existed
if (activeWardrobeSet != null)
{
if (activeWardrobeSet.Count > 0)
{
packedRecipeToSave.wardrobeSet = activeWardrobeSet;
}
}
recipeString = JsonUtility.ToJson(packedRecipeToSave);
}
}
//This is the save method used by DCA.DoSave and the 'optimized' UMA Menu save options
/// <summary>
/// Save the DynamicCharacterAvatar using the optimized DCSPackRecipe model (Not compatible with non-DynamicCharacterAvatars)
/// </summary>
/// <param name="dcaToSave"></param>
/// <param name="recipeName"></param>
/// <param name="saveOptions">Set the save flags options to choose which properties of the Avatar to save</param>
public void SaveDCS(DynamicCharacterAvatar dcaToSave, string recipeName, DynamicCharacterAvatar.SaveOptions saveOptions)
{
recipeString = JsonUtility.ToJson(new DCSPackRecipe(dcaToSave, recipeName, "DynamicCharacterAvatar", saveOptions));
}
/// <summary>
/// Super lightweight model used for quickly checking the recipe type of a given recipeString and whether the recipe has and wardrobe data. Use the helper methods below for easy access
/// </summary>
private class DCSRecipeChecker
{
public string packedRecipeType = "Standard";
//we can have backwards compatibility here too
public List<WardrobeSettings> wardrobeRecipesJson = new List<WardrobeSettings>();
public List<WardrobeSettings> wardrobeSet = new List<WardrobeSettings>();
public List<WardrobeSettings> checkedWardrobeSet
{
get
{
if (wardrobeSet.Count > 0)
{
return wardrobeSet;
}
else
{
return wardrobeRecipesJson;
}
}
}
}
//Helper Methods for use with DCSRecipeChecker
/// <summary>
/// Get the recipeType of the given recipeString
/// </summary>
/// <param name="recipeString"></param>
/// <returns></returns>
public static string GetRecipesType(string recipeString)
{
if (String.IsNullOrEmpty(recipeString))
{
return "Standard";
}
return JsonUtility.FromJson<DCSRecipeChecker>(recipeString).packedRecipeType;
}
/// <summary>
/// Check if the given recipe string has any wardrobeSet data
/// </summary>
/// <param name="recipeString"></param>
/// <returns></returns>
public static bool RecipeHasWardrobeSet(string recipeString)
{
if (String.IsNullOrEmpty(recipeString))
{
return false;
}
return JsonUtility.FromJson<DCSRecipeChecker>(recipeString).checkedWardrobeSet.Count > 0;
}
/// <summary>
/// Get the wardrobeSet data from the given recipe string
/// </summary>
/// <param name="recipeString"></param>
/// <returns></returns>
public static List<WardrobeSettings> GetRecipesWardrobeSet(string recipeString)
{
if (String.IsNullOrEmpty(recipeString))
{
return null;
}
return JsonUtility.FromJson<DCSRecipeChecker>(recipeString).checkedWardrobeSet;
}
[System.Serializable]
public class DCSPackRecipe
{
public string packedRecipeType = "DynamicCharacterAvatar";
public string name;
public string race;
public List<UMAPackedDna> dna;
public List<PackedOverlayColorDataV3> characterColors;
public List<WardrobeSettings> wardrobeSet;
public string raceAnimatorController;
private OverlayColorData[] _sharedColors = null;
#region PUBLIC PROPERTIES
public OverlayColorData[] sharedColors
{
get
{
if (_sharedColors == null)
{
if (characterColors != null && characterColors.Count > 0)
{
var colorData = new OverlayColorData[characterColors.Count];
for (int i = 0; i < colorData.Length; i++)
{
colorData[i] = new OverlayColorData();
characterColors[i].SetOverlayColorData(colorData[i]);
}
_sharedColors = colorData;
}
else
{
_sharedColors = new OverlayColorData[0];
}
}
return _sharedColors;
}
}
#endregion
#region CONSTRUCTOR
/// <summary>
/// This is the main DCS Data model now. When a DCS is saved it is saved using this
/// </summary>
public DCSPackRecipe() { }
/// <summary>
/// Use this model for saving a DCS Avatar to a light weight json string. Use the save options flags to determine what aspects of the avatar are saved
/// </summary>
/// <param name="dcaToSave"></param>
/// <param name="recipeName"></param>
/// <param name="pRecipeType"></param>
/// <param name="saveOptions"></param>
/// <param name="slotsToSave"></param>
public DCSPackRecipe(DynamicCharacterAvatar dcaToSave, string recipeName, string pRecipeType, DynamicCharacterAvatar.SaveOptions saveOptions, params string[] slotsToSave)
{
if (pRecipeType != "DynamicCharacterAvatar")
{
Debug.LogWarning("DCSPackRecipe Type can only be used for recipeTypes 'DynamicCharacterAvatar'");
return;
}
var recipeToSave = dcaToSave.umaData.umaRecipe;
packedRecipeType = pRecipeType;
name = recipeName;
race = dcaToSave.activeRace.racedata.raceName;
if (saveOptions.HasFlagSet(DynamicCharacterAvatar.SaveOptions.saveDNA))
{
dna = GetPackedDNA(recipeToSave);
}
if (saveOptions.HasFlagSet(DynamicCharacterAvatar.SaveOptions.saveColors))
{
characterColors = new List<PackedOverlayColorDataV3>();
for (int i = 0; i < recipeToSave.sharedColors.Length; i++)
{
characterColors.Add(new PackedOverlayColorDataV3(recipeToSave.sharedColors[i]));
}
}
if (saveOptions.HasFlagSet(DynamicCharacterAvatar.SaveOptions.saveWardrobe))
{
wardrobeSet = GenerateWardrobeSet(dcaToSave.WardrobeRecipes, dcaToSave.WardrobeCollections, dcaToSave.AdditiveRecipes, slotsToSave);
}
if (saveOptions.HasFlagSet(DynamicCharacterAvatar.SaveOptions.saveAnimator))
{
if (dcaToSave.animationController != null)
{
raceAnimatorController = (dcaToSave.animationController.name);
}
}
}
/// <summary>
/// Converts standard UMA Pack Recipe data into this data model, either when loading an old recipe, or when the RecipeEditor is saving a DCS recipe asset
/// </summary>
/// <param name="umaPackRecipe"></param>
/// <param name="recipeName"></param>
/// <param name="pRecipeType"></param>
/// <param name="wardrobeSetToSave"></param>
public DCSPackRecipe(UMAPackRecipe umaPackRecipe, string recipeName = "", string pRecipeType = "Standard", List<WardrobeSettings> wardrobeSetToSave = null)
{
//Debug.Log("DCSPackRecipe from umaPackRecipe");
packedRecipeType = pRecipeType;
name = recipeName != "" ? recipeName : umaPackRecipe.race + "PackRecipe";//- if its coming from the recipe editor the we wont want to change a name
race = umaPackRecipe.race;
dna = umaPackRecipe.packedDna;
characterColors = new List<PackedOverlayColorDataV3>(umaPackRecipe.fColors);
//if this is coming from the Recipe Editor then it will need wardrobe settings- but it cant send them- however they will be in the activeWardrobeSet field
if (wardrobeSetToSave != null)
{
wardrobeSet = wardrobeSetToSave;
}
}
#endregion
}
//This is a Universal model that makes the new DCSModel 'backwards compatible' so the data can be used in the inspector- its only used at runtime and not saved unless you save with the 'backwards Compatible' save options
[System.Serializable]
public class DCSUniversalPackRecipe : UMAPackRecipe
{
[SerializeField]
public List<WardrobeSettings> wardrobeSet = null;
public string packedRecipeType = "Standard";
private OverlayColorData[] _sharedColors = null;
public OverlayColorData[] sharedColors
{
get
{
if (_sharedColors == null)
{
//PackedOverlayColorDataV3
if (fColors != null)
{
if (fColors.Length > 0)
{
var colorData = new OverlayColorData[fColors.Length];
for (int i = 0; i < colorData.Length; i++)
{
colorData[i] = new OverlayColorData();
fColors[i].SetOverlayColorData(colorData[i]);
}
_sharedColors = colorData;
}
else
{
_sharedColors = new OverlayColorData[0];
}
}//PackedOverlayColorDataV3
else if (colors != null)
{
if (colors.Length > 0)
{
var colorData = new OverlayColorData[colors.Length];
for (int i = 0; i < colorData.Length; i++)
{
colorData[i] = new OverlayColorData();
colors[i].SetOverlayColorData(colorData[i]);
}
_sharedColors = colorData;
}
else
{
_sharedColors = new OverlayColorData[0];
}
}
else
{
_sharedColors = new OverlayColorData[0];
}
}
return _sharedColors;
}
}
#region CONSTRUCTORS
public DCSUniversalPackRecipe()
{
}
/// <summary>
/// Convert an UMAPackRecipe into a DCSUniversalPackRecipe. Used when DCS needs to load an old UMA
/// </summary>
/// <param name="umaPackRecipe"></param>
/// <param name="pRecipeType"></param>
public DCSUniversalPackRecipe(UMAPackRecipe umaPackRecipe, string pRecipeType = "Standard")
{
//Debug.Log("Created universal model from UMAPackRecipe");
packedRecipeType = pRecipeType;
version = umaPackRecipe.version;
packedSlotDataList = umaPackRecipe.packedSlotDataList;
slotsV3 = umaPackRecipe.slotsV3;
colors = umaPackRecipe.colors;
fColors = umaPackRecipe.fColors;
sharedColorCount = umaPackRecipe.sharedColorCount;
race = umaPackRecipe.race;
umaDna = umaPackRecipe.umaDna;
packedDna = umaPackRecipe.packedDna;
}
/// <summary>
/// This is used when an avatar asset, saved using the DCS Model, is loaded for editing in the Inspector
/// </summary>
/// <param name="dcsPackRecipe"></param>
public DCSUniversalPackRecipe(DCSPackRecipe dcsPackRecipe)
{
//Debug.Log("Created universal model from DCSPackRecipe");
packedRecipeType = dcsPackRecipe.packedRecipeType;
version = 3;
slotsV3 = new PackedSlotDataV3[0];//we need this otherwise the RecipeInspector doesn't show anything...
race = dcsPackRecipe.race;
fColors = dcsPackRecipe.characterColors.ToArray();
sharedColorCount = dcsPackRecipe.characterColors.Count;
packedDna = dcsPackRecipe.dna;
wardrobeSet = dcsPackRecipe.wardrobeSet;
}
/// <summary>
/// This is used to save a DCS avatar to a 'backwards compatible' UMA model
/// </summary>
/// <param name="recipeToSave"></param>
/// <param name="wardrobeRecipes"></param>
/// <param name="pRecipeType"></param>
public DCSUniversalPackRecipe(UMAData.UMARecipe recipeToSave, Dictionary<string, UMATextRecipe> wardrobeRecipes = null, string pRecipeType = "DynamicCharacterAvatar")
{
//Debug.Log("Created universal model from Avatar");
var packedRecipe = PackRecipeV3(recipeToSave);
packedRecipeType = pRecipeType;
version = packedRecipe.version;
packedSlotDataList = packedRecipe.packedSlotDataList;
slotsV3 = packedRecipe.slotsV3;
colors = packedRecipe.colors;
fColors = packedRecipe.fColors;
sharedColorCount = packedRecipe.sharedColorCount;
race = packedRecipe.race;
umaDna = packedRecipe.umaDna;
packedDna = packedRecipe.packedDna;
wardrobeSet = GenerateWardrobeSet(wardrobeRecipes);
}
#endregion
#region METHODS
public UMADnaBase[] GetAllDna()
{
List<UMADnaBase> unpackedDna = UMAPackedRecipeBase.UnPackDNA(packedDna);
return unpackedDna.ToArray();
}
#endregion
}
}
}
@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: 3482ddde2f9c74588b2cd4c106487dd0
timeCreated: 1438475149
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 35611
packageName: UMA 2
packageVersion: 2.13
assetPath: Assets/UMA/Core/Extensions/DynamicCharacterSystem/Scripts/pUMATextRecipe.cs
uploadId: 679826