mirror of
https://gitgud.io/AbstractConcept/rimworld-animation-studio.git
synced 2024-08-15 00:43:27 +00:00
Keyframe stretching plus appendage manipulation
This commit is contained in:
parent
18c0473f39
commit
1af7f41d63
427 changed files with 7003 additions and 1147 deletions
8
Assets/Scripts/AdvancedPolygonCollider.meta
Normal file
8
Assets/Scripts/AdvancedPolygonCollider.meta
Normal file
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 177db18368bfbba47a6b714d621984dc
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,551 @@
|
|||
/*
|
||||
Advanced Polygon Collider (c) 2015 Digital Ruby, LLC
|
||||
http://www.digitalruby.com
|
||||
|
||||
Source code may not be redistributed. Use in apps and games is fine.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using UnityEditor;
|
||||
|
||||
#endif
|
||||
|
||||
namespace DigitalRuby.AdvancedPolygonCollider
|
||||
{
|
||||
public struct PolygonParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// Texture - must be readable and writeable.
|
||||
/// </summary>
|
||||
public Texture2D Texture;
|
||||
|
||||
/// <summary>
|
||||
/// Source rect from the texture containing the sprite.
|
||||
/// </summary>
|
||||
public Rect Rect;
|
||||
|
||||
/// <summary>
|
||||
/// Offset (pivot) for the sprite
|
||||
/// </summary>
|
||||
public Vector2 Offset;
|
||||
|
||||
/// <summary>
|
||||
/// X multiplier if pixels per unit are used, otherwise 1 (see UpdatePolygonCollider method).
|
||||
/// </summary>
|
||||
public float XMultiplier;
|
||||
|
||||
/// <summary>
|
||||
/// Y multiplier if pixels per unit are used, otherwise 1 (see UpdatePolygonCollider method).
|
||||
/// </summary>
|
||||
public float YMultiplier;
|
||||
|
||||
/// <summary>
|
||||
/// Alpha tolerance. Pixels with greater than this are considered solid.
|
||||
/// </summary>
|
||||
public byte AlphaTolerance;
|
||||
|
||||
/// <summary>
|
||||
/// Distance threshold to collapse vertices in pixels.
|
||||
/// </summary>
|
||||
public int DistanceThreshold;
|
||||
|
||||
/// <summary>
|
||||
/// True to decompose into convex polygons, false otherwise.
|
||||
/// </summary>
|
||||
public bool Decompose;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use the cache. Values will be cached accordingly.
|
||||
/// </summary>
|
||||
public bool UseCache;
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int h = Texture.GetHashCode();
|
||||
if (h == 0)
|
||||
{
|
||||
h = 1;
|
||||
}
|
||||
return h * (int)(Rect.GetHashCode() * XMultiplier * YMultiplier * AlphaTolerance * Mathf.Max(DistanceThreshold, 1) * (Decompose ? 2 : 1));
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is PolygonParameters)
|
||||
{
|
||||
PolygonParameters p = (PolygonParameters)obj;
|
||||
return Texture == p.Texture && Rect == p.Rect &&
|
||||
XMultiplier == p.XMultiplier && YMultiplier == p.YMultiplier &&
|
||||
AlphaTolerance == p.AlphaTolerance && DistanceThreshold == p.DistanceThreshold &&
|
||||
Decompose == p.Decompose;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[RequireComponent(typeof(PolygonCollider2D))]
|
||||
[RequireComponent(typeof(SpriteRenderer))]
|
||||
[ExecuteInEditMode]
|
||||
public class AdvancedPolygonCollider : MonoBehaviour
|
||||
{
|
||||
[Serializable]
|
||||
public struct ArrayWrapper
|
||||
{
|
||||
[SerializeField]
|
||||
public Vector2[] Array;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct ListWrapper
|
||||
{
|
||||
[SerializeField]
|
||||
public List<ArrayWrapper> List;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct CacheEntry
|
||||
{
|
||||
[SerializeField]
|
||||
public CacheKey Key;
|
||||
|
||||
[SerializeField]
|
||||
public ListWrapper Value;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct CacheKey
|
||||
{
|
||||
[SerializeField]
|
||||
public Texture2D Texture;
|
||||
|
||||
[SerializeField]
|
||||
public Rect Rect;
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Texture.GetHashCode() * Rect.GetHashCode();
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is CacheKey)
|
||||
{
|
||||
CacheKey k = (CacheKey)obj;
|
||||
return (Texture == k.Texture && Rect == k.Rect);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Tooltip("Pixels with alpha greater than this count as solid.")]
|
||||
[Range(0, 254)]
|
||||
public byte AlphaTolerance = 20;
|
||||
|
||||
[Tooltip("Points further away than this number of pixels will be consolidated.")]
|
||||
[Range(0, 64)]
|
||||
public int DistanceThreshold = 8;
|
||||
|
||||
[Tooltip("Scale of the polygon.")]
|
||||
[Range(0.5f, 2.0f)]
|
||||
public float Scale = 1.0f;
|
||||
|
||||
[Tooltip("Whether to decompse vertices into convex only polygons.")]
|
||||
public bool Decompose = false;
|
||||
|
||||
[Tooltip("Whether to live update everything when in play mode. Typically for performance this can be false, " +
|
||||
"but if you plan on making changes to the sprite or parameters at runtime, you will want to set this to true.")]
|
||||
public bool RunInPlayMode = false;
|
||||
|
||||
[Tooltip("True to use the cache, false otherwise. The cache is populated in editor and play mode and uses the most recent geometry " +
|
||||
"for a texture and rect regardless of other parameters. When ignoring the cache, values will not be added to the cache either. Cache is " +
|
||||
"only useful if you will be changing your sprite at run-time (i.e. animation)")]
|
||||
public bool UseCache;
|
||||
|
||||
private SpriteRenderer spriteRenderer;
|
||||
private PolygonCollider2D polygonCollider;
|
||||
private bool dirty;
|
||||
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private byte lastAlphaTolerance;
|
||||
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private float lastScale;
|
||||
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private int lastDistanceThreshold;
|
||||
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private bool lastDecompose;
|
||||
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private Sprite lastSprite;
|
||||
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private Rect lastRect = new Rect();
|
||||
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private Vector2 lastOffset = new Vector2(-99999.0f, -99999.0f);
|
||||
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private float lastPixelsPerUnit;
|
||||
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private bool lastFlipX;
|
||||
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private bool lastFlipY;
|
||||
|
||||
private static readonly Dictionary<CacheKey, List<Vector2[]>> cache = new Dictionary<CacheKey, List<Vector2[]>>();
|
||||
|
||||
[Tooltip("All the cached objects from the editor. Do not modify this data.")]
|
||||
[SerializeField]
|
||||
private List<CacheEntry> editorCache = new List<CacheEntry>();
|
||||
|
||||
// private readonly AdvancedPolygonColliderAutoGeometry geometryDetector = new AdvancedPolygonColliderAutoGeometry();
|
||||
private readonly TextureConverter geometryDetector = new TextureConverter();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
private Texture2D blackBackground;
|
||||
|
||||
#endif
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
// move editor cache to regular cache
|
||||
foreach (var v in editorCache)
|
||||
{
|
||||
List<Vector2[]> list = new List<Vector2[]>();
|
||||
cache[v.Key] = list;
|
||||
foreach (ArrayWrapper w in v.Value.List)
|
||||
{
|
||||
list.Add(w.Array);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
blackBackground = new Texture2D(1, 1);
|
||||
blackBackground.SetPixel(0, 0, new Color(0.0f, 0.0f, 0.0f, 0.8f));
|
||||
blackBackground.Apply();
|
||||
|
||||
#endif
|
||||
|
||||
polygonCollider = GetComponent<PolygonCollider2D>();
|
||||
spriteRenderer = GetComponent<SpriteRenderer>();
|
||||
}
|
||||
|
||||
private void UpdateDirtyState()
|
||||
{
|
||||
if (spriteRenderer.sprite != lastSprite)
|
||||
{
|
||||
lastSprite = spriteRenderer.sprite;
|
||||
dirty = true;
|
||||
}
|
||||
if (spriteRenderer.sprite != null)
|
||||
{
|
||||
if (lastOffset != spriteRenderer.sprite.pivot)
|
||||
{
|
||||
lastOffset = spriteRenderer.sprite.pivot;
|
||||
dirty = true;
|
||||
}
|
||||
if (lastRect != spriteRenderer.sprite.rect)
|
||||
{
|
||||
lastRect = spriteRenderer.sprite.rect;
|
||||
dirty = true;
|
||||
}
|
||||
if (lastPixelsPerUnit != spriteRenderer.sprite.pixelsPerUnit)
|
||||
{
|
||||
lastPixelsPerUnit = spriteRenderer.sprite.pixelsPerUnit;
|
||||
dirty = true;
|
||||
}
|
||||
if (lastFlipX != spriteRenderer.flipX)
|
||||
{
|
||||
lastFlipX = spriteRenderer.flipX;
|
||||
dirty = true;
|
||||
}
|
||||
if (lastFlipY != spriteRenderer.flipY)
|
||||
{
|
||||
lastFlipY = spriteRenderer.flipY;
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
if (AlphaTolerance != lastAlphaTolerance)
|
||||
{
|
||||
lastAlphaTolerance = AlphaTolerance;
|
||||
dirty = true;
|
||||
}
|
||||
if (Scale != lastScale)
|
||||
{
|
||||
lastScale = Scale;
|
||||
dirty = true;
|
||||
}
|
||||
if (DistanceThreshold != lastDistanceThreshold)
|
||||
{
|
||||
lastDistanceThreshold = DistanceThreshold;
|
||||
dirty = true;
|
||||
}
|
||||
if (Decompose != lastDecompose)
|
||||
{
|
||||
lastDecompose = Decompose;
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
if (!RunInPlayMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (!UseCache)
|
||||
{
|
||||
editorCache.Clear();
|
||||
}
|
||||
|
||||
UpdateDirtyState();
|
||||
if (dirty)
|
||||
{
|
||||
RecalculatePolygon();
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
UnityEditor.Handles.BeginGUI();
|
||||
GUI.color = Color.white;
|
||||
string text = " Vertices: " + VerticesCount + " ";
|
||||
var view = UnityEditor.SceneView.currentDrawingSceneView;
|
||||
Vector3 screenPos = view.camera.WorldToScreenPoint(gameObject.transform.position);
|
||||
Vector2 size = GUI.skin.label.CalcSize(new GUIContent(text));
|
||||
GUI.skin.box.normal.background = blackBackground;
|
||||
Rect rect = new Rect(screenPos.x - (size.x / 2), -screenPos.y + view.position.height + 4, size.x, size.y);
|
||||
GUI.Box(rect, GUIContent.none);
|
||||
GUI.Label(rect, text);
|
||||
UnityEditor.Handles.EndGUI();
|
||||
}
|
||||
|
||||
private void AddEditorCache(ref PolygonParameters p, List<Vector2[]> list)
|
||||
{
|
||||
CacheKey key = new CacheKey();
|
||||
key.Texture = p.Texture;
|
||||
key.Rect = p.Rect;
|
||||
|
||||
CacheEntry e = new CacheEntry();
|
||||
e.Key = key;
|
||||
e.Value = new ListWrapper();
|
||||
e.Value.List = new List<ArrayWrapper>();
|
||||
foreach (Vector2[] v in list)
|
||||
{
|
||||
ArrayWrapper w = new ArrayWrapper();
|
||||
w.Array = v;
|
||||
e.Value.List.Add(w);
|
||||
}
|
||||
|
||||
for (int i = 0; i < editorCache.Count; i++)
|
||||
{
|
||||
if (editorCache[i].Key.Equals(key))
|
||||
{
|
||||
editorCache.RemoveAt(i);
|
||||
editorCache.Insert(i, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
editorCache.Add(e);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public void RecalculatePolygon()
|
||||
{
|
||||
if (spriteRenderer.sprite != null)
|
||||
{
|
||||
PolygonParameters p = new PolygonParameters();
|
||||
p.AlphaTolerance = AlphaTolerance;
|
||||
p.Decompose = Decompose;
|
||||
p.DistanceThreshold = DistanceThreshold;
|
||||
p.Rect = spriteRenderer.sprite.rect;
|
||||
p.Offset = spriteRenderer.sprite.pivot;
|
||||
p.Texture = spriteRenderer.sprite.texture;
|
||||
p.XMultiplier = (spriteRenderer.sprite.rect.width * 0.5f) / spriteRenderer.sprite.pixelsPerUnit;
|
||||
p.YMultiplier = (spriteRenderer.sprite.rect.height * 0.5f) / spriteRenderer.sprite.pixelsPerUnit;
|
||||
p.UseCache = UseCache;
|
||||
UpdatePolygonCollider(ref p);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdatePolygonCollider(ref PolygonParameters p)
|
||||
{
|
||||
if (spriteRenderer.sprite == null || spriteRenderer.sprite.texture == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dirty = false;
|
||||
List<Vector2[]> cached;
|
||||
|
||||
if (Application.isPlaying && p.UseCache)
|
||||
{
|
||||
CacheKey key = new CacheKey();
|
||||
key.Texture = p.Texture;
|
||||
key.Rect = p.Rect;
|
||||
|
||||
if (cache.TryGetValue(key, out cached))
|
||||
{
|
||||
polygonCollider.pathCount = cached.Count;
|
||||
for (int i = 0; i < cached.Count; i++)
|
||||
{
|
||||
polygonCollider.SetPath(i, cached[i]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PopulateCollider(polygonCollider, ref p);
|
||||
}
|
||||
|
||||
public int VerticesCount
|
||||
{
|
||||
get { return (polygonCollider == null ? 0 : polygonCollider.GetTotalPointCount()); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populate the vertices of a collider
|
||||
/// </summary>
|
||||
/// <param name="collider">Collider to setup vertices in.</param>
|
||||
/// <param name="p">Polygon creation parameters</param>
|
||||
public void PopulateCollider(PolygonCollider2D collider, ref PolygonParameters p)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (p.Texture.format != TextureFormat.ARGB32 && p.Texture.format != TextureFormat.BGRA32 && p.Texture.format != TextureFormat.RGBA32 &&
|
||||
p.Texture.format != TextureFormat.RGB24 && p.Texture.format != TextureFormat.Alpha8 && p.Texture.format != TextureFormat.RGBAFloat &&
|
||||
p.Texture.format != TextureFormat.RGBAHalf && p.Texture.format != TextureFormat.RGB565)
|
||||
{
|
||||
Debug.LogWarning("Advanced Polygon Collider works best with a non-compressed texture in ARGB32, BGRA32, RGB24, RGBA4444, RGB565, RGBAFloat or RGBAHalf format");
|
||||
}
|
||||
int width = (int)p.Rect.width;
|
||||
int height = (int)p.Rect.height;
|
||||
int x = (int)p.Rect.x;
|
||||
int y = (int)p.Rect.y;
|
||||
UnityEngine.Color[] pixels = p.Texture.GetPixels(x, y, width, height, 0);
|
||||
List<Vertices> verts = geometryDetector.DetectVertices(pixels, width, p.AlphaTolerance);
|
||||
int pathIndex = 0;
|
||||
List<Vector2[]> list = new List<Vector2[]>();
|
||||
|
||||
for (int i = 0; i < verts.Count; i++)
|
||||
{
|
||||
ProcessVertices(collider, verts[i], list, ref p, ref pathIndex);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
|
||||
#endif
|
||||
|
||||
if (p.UseCache)
|
||||
{
|
||||
CacheKey key = new CacheKey();
|
||||
key.Texture = p.Texture;
|
||||
key.Rect = p.Rect;
|
||||
cache[key] = list;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
}
|
||||
else if (p.UseCache)
|
||||
{
|
||||
AddEditorCache(ref p, list);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Debug.Log("Updated polygon.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError("Error creating collider: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Vector2[]> ProcessVertices(PolygonCollider2D collider, Vertices v, List<Vector2[]> list, ref PolygonParameters p, ref int pathIndex)
|
||||
{
|
||||
Vector2 offset = p.Offset;
|
||||
float flipXMultiplier = (spriteRenderer.flipX ? -1.0f : 1.0f);
|
||||
float flipYMultiplier = (spriteRenderer.flipY ? -1.0f : 1.0f);
|
||||
|
||||
if (p.DistanceThreshold > 1)
|
||||
{
|
||||
v = SimplifyTools.DouglasPeuckerSimplify (v, p.DistanceThreshold);
|
||||
}
|
||||
|
||||
if (p.Decompose)
|
||||
{
|
||||
List<List<Vector2>> points = BayazitDecomposer.ConvexPartition(v);
|
||||
for (int j = 0; j < points.Count; j++)
|
||||
{
|
||||
List<Vector2> v2 = points[j];
|
||||
for (int i = 0; i < v2.Count; i++)
|
||||
{
|
||||
float xValue = (2.0f * (((v2[i].x - offset.x) + 0.5f) / p.Rect.width));
|
||||
float yValue = (2.0f * (((v2[i].y - offset.y) + 0.5f) / p.Rect.height));
|
||||
v2[i] = new Vector2(xValue * p.XMultiplier * Scale * flipXMultiplier, yValue * p.YMultiplier * Scale * flipYMultiplier);
|
||||
}
|
||||
Vector2[] arr = v2.ToArray();
|
||||
collider.pathCount = pathIndex + 1;
|
||||
collider.SetPath(pathIndex++, arr);
|
||||
list.Add(arr);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
collider.pathCount = pathIndex + 1;
|
||||
for (int i = 0; i < v.Count; i++)
|
||||
{
|
||||
float xValue = (2.0f * (((v[i].x - offset.x) + 0.5f) / p.Rect.width));
|
||||
float yValue = (2.0f * (((v[i].y - offset.y) + 0.5f) / p.Rect.height));
|
||||
v[i] = new Vector2(xValue * p.XMultiplier * Scale * flipXMultiplier, yValue * p.YMultiplier * Scale * flipYMultiplier);
|
||||
}
|
||||
Vector2[] arr = v.ToArray();
|
||||
collider.SetPath(pathIndex++, arr);
|
||||
list.Add(arr);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8c55c83d4c4dd1145ad5fbbab3c3df36
|
||||
timeCreated: 1450474475
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2d44a3587cd9f0f4ebdfbea6d8a9c789
|
||||
timeCreated: 1450472189
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -15,7 +15,7 @@ namespace RimWorldAnimationStudio
|
|||
[XmlArray("blacklistedRaces"), XmlArrayItem("li")] public List<string> blacklistedRaces = new List<string>();
|
||||
[XmlArray("tags"), XmlArrayItem("li")] public List<string> tags;
|
||||
|
||||
public string gender;
|
||||
[XmlIgnore] public ActorGender gender;
|
||||
public BodyTypeOffset bodyTypeOffset = new BodyTypeOffset();
|
||||
public bool initiator = false;
|
||||
public bool? controlGenitalAngle;
|
||||
|
|
|
@ -57,9 +57,7 @@ namespace RimWorldAnimationStudio
|
|||
HeadFacing.Add((float)keyframe.atTick / (float)duration, keyframe.headFacing, true);
|
||||
BodyFacing.Add((float)keyframe.atTick / (float)duration, keyframe.bodyFacing, true);
|
||||
HeadBob.Add((float)keyframe.atTick / (float)duration, keyframe.headBob, true);
|
||||
|
||||
if (keyframe.genitalAngle.HasValue)
|
||||
{ GenitalAngle.Add((float)keyframe.atTick / (float)duration, keyframe.genitalAngle.Value, true); }
|
||||
GenitalAngle.Add((float)keyframe.atTick / (float)duration, keyframe.genitalAngle, true);
|
||||
|
||||
if (i + 1 < keyframes.Count)
|
||||
{ keyframes[i].tickDuration = keyframes[i + 1].atTick.Value - keyframes[i].atTick.Value; }
|
||||
|
@ -74,9 +72,7 @@ namespace RimWorldAnimationStudio
|
|||
HeadFacing.Add((float)keyframePosition / (float)duration, keyframe.headFacing, true);
|
||||
BodyFacing.Add((float)keyframePosition / (float)duration, keyframe.bodyFacing, true);
|
||||
HeadBob.Add((float)keyframePosition / (float)duration, keyframe.headBob, true);
|
||||
|
||||
if (keyframe.genitalAngle.HasValue)
|
||||
{ GenitalAngle.Add((float)keyframePosition / (float)duration, keyframe.genitalAngle.Value, true); }
|
||||
GenitalAngle.Add((float)keyframePosition / (float)duration, keyframe.genitalAngle, true);
|
||||
|
||||
if (keyframe.tickDuration != 1 && keyframe.quiver.HasValue)
|
||||
{
|
||||
|
|
|
@ -30,6 +30,7 @@ namespace RimWorldAnimationStudio
|
|||
}
|
||||
}
|
||||
|
||||
// move to app manager
|
||||
public void RunPreSaveOperations()
|
||||
{
|
||||
// Stage edits
|
||||
|
@ -45,25 +46,32 @@ namespace RimWorldAnimationStudio
|
|||
int stageWindowSize = animationStages[i].stageWindowSize > 0 ? animationStages[i].stageWindowSize : animationStages[i].animationClips.Select(x => x.duration).Max();
|
||||
int cycles = Mathf.CeilToInt(animationStages[i].playTimeTicks / stageWindowSize);
|
||||
|
||||
Debug.Log(animationStages[i].playTimeTicks);
|
||||
Debug.Log(animationStages[i].stageWindowSize);
|
||||
Debug.Log(cycles);
|
||||
stage.isLooping = cycles > 1;
|
||||
}
|
||||
|
||||
// Body part list edit
|
||||
foreach (Actor actor in actors)
|
||||
// Actor edits
|
||||
for (int i = 0; i < actors.Count; i++)
|
||||
{
|
||||
actor.isFucking = actor.requiredGenitals.Contains("Any appendage");
|
||||
Actor actor = actors[i];
|
||||
|
||||
actor.isFucking = actor.requiredGenitals.Contains("Any appendage") ? (bool?)true : null;
|
||||
|
||||
if (actor.isFucking == true)
|
||||
{ actor.requiredGenitals.Remove("Any appendage"); }
|
||||
|
||||
actor.isFucked= actor.requiredGenitals.Contains("Any orifice");
|
||||
actor.isFucked = actor.requiredGenitals.Contains("Any orifice") ? (bool?)true : null;
|
||||
|
||||
if (actor.isFucked == true)
|
||||
{ actor.requiredGenitals.Remove("Any orifice"); }
|
||||
|
||||
actor.controlGenitalAngle = animationStages.Any(x => x.animationClips[i].keyframes.Any(y => y.genitalAngle != 0)) ? (bool?)true : null;
|
||||
|
||||
//if (actor.requiredGender.Contains("Female")) actor.gender = ActorGender.Female;
|
||||
//else if (actor.requiredGender.Contains("Male")) actor.gender = ActorGender.Male;
|
||||
//else actor.gender = ActorGender.None;
|
||||
}
|
||||
|
||||
//actors.OrderBy(x => (int)x.gender);
|
||||
}
|
||||
|
||||
public void RunPostLoadOperations()
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace RimWorldAnimationStudio
|
|||
public float headFacing = 2;
|
||||
public float bodyFacing = 2;
|
||||
|
||||
public float? genitalAngle;
|
||||
public float genitalAngle;
|
||||
public bool? quiver;
|
||||
|
||||
public bool ShouldSerializegenitalAngle() { return genitalAngle != null; }
|
||||
|
|
|
@ -9,16 +9,31 @@ namespace RimWorldAnimationStudio
|
|||
public class ActorBody : MonoBehaviour, IPointerClickHandler, IDragHandler
|
||||
{
|
||||
public int actorID;
|
||||
public string bodyType = "Male";
|
||||
public string bodyType = "Male";
|
||||
public bool isSelected = false;
|
||||
|
||||
public SpriteRenderer bodyRenderer;
|
||||
public SpriteRenderer headRenderer;
|
||||
public SpriteRenderer appendageRenderer;
|
||||
|
||||
public bool actorBodyPartSelected { get { return GetComponentsInChildren<ActorBodyPart>().Any(x => x.isSelected); } }
|
||||
|
||||
public void Initialize(int actorID)
|
||||
{
|
||||
this.actorID = actorID;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (Workspace.actorID == actorID && Workspace.selectedBodyPart == null)
|
||||
{ bodyRenderer.color = Constants.ColorGreen; }
|
||||
|
||||
else
|
||||
{ bodyRenderer.color = Constants.ColorWhite; }
|
||||
|
||||
appendageRenderer.gameObject.SetActive(Workspace.animationDef.actors[actorID].requiredGenitals.Any(x => x == "Penis" || x == "Any appendage"));
|
||||
}
|
||||
|
||||
public void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
if (eventData.pointerCurrentRaycast.gameObject.GetComponent<ActorBody>() == null)
|
||||
|
@ -31,9 +46,9 @@ namespace RimWorldAnimationStudio
|
|||
{
|
||||
Activate();
|
||||
|
||||
PawnKeyframe keyframe = Workspace.Instance.GetCurrentPawnKeyframe();
|
||||
PawnKeyframe keyframe = Workspace.Instance.GetCurrentPawnKeyframe(true);
|
||||
|
||||
if (Workspace.Instance.GetCurrentPawnKeyframe() == null)
|
||||
if (keyframe == null)
|
||||
{ Debug.LogWarning("Cannot alter actor - no keyframe data available"); return; }
|
||||
|
||||
Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
|
||||
|
@ -66,18 +81,28 @@ namespace RimWorldAnimationStudio
|
|||
public void Activate()
|
||||
{
|
||||
Workspace.actorID = actorID;
|
||||
Workspace.selectedBodyPart = null;
|
||||
|
||||
foreach (ActorBody actorBody in AnimationController.Instance.actorBodies.GetComponentsInChildren<ActorBody>())
|
||||
/*foreach (ActorBody actorBody in AnimationController.Instance.actorBodies.GetComponentsInChildren<ActorBody>())
|
||||
{
|
||||
if (actorBody == this)
|
||||
{ continue; }
|
||||
|
||||
actorBody.bodyRenderer.color = Constants.ColorWhite;
|
||||
actorBody.headRenderer.color = Constants.ColorWhite;
|
||||
actorBody.appendageRenderer.color = Constants.ColorWhite;
|
||||
|
||||
actorBody.isSelected = false;
|
||||
}
|
||||
|
||||
bodyRenderer.color = Constants.ColorGreen;
|
||||
headRenderer.color = Constants.ColorGreen;
|
||||
appendageRenderer.color = Constants.ColorGreen;
|
||||
|
||||
foreach (ActorBodyPart actorBodyPartSelected in GetComponentsInChildren<ActorBodyPart>())
|
||||
{ actorBodyPartSelected.isSelected = false; }
|
||||
|
||||
isSelected = true;*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,17 @@ namespace RimWorldAnimationStudio
|
|||
{
|
||||
public SpriteRenderer bodyPartRenderer;
|
||||
public ActorBody parent;
|
||||
public bool isHead = false;
|
||||
public bool isSelected = false;
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if ((Workspace.actorID == parent.actorID && Workspace.selectedBodyPart == null) || Workspace.selectedBodyPart == this)
|
||||
{ bodyPartRenderer.color = Constants.ColorGreen; }
|
||||
|
||||
else
|
||||
{ bodyPartRenderer.color = Constants.ColorWhite; }
|
||||
}
|
||||
|
||||
public void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
|
@ -23,38 +34,48 @@ namespace RimWorldAnimationStudio
|
|||
{
|
||||
Activate();
|
||||
|
||||
PawnKeyframe keyframe = Workspace.Instance.GetCurrentPawnKeyframe();
|
||||
PawnKeyframe keyframe = Workspace.Instance.GetCurrentPawnKeyframe(true);
|
||||
|
||||
if (Workspace.Instance.GetCurrentPawnKeyframe() == null)
|
||||
if (keyframe == null)
|
||||
{ Debug.LogWarning("Cannot alter actor - no keyframe data available"); return; }
|
||||
|
||||
Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
|
||||
|
||||
if (Workspace.actorManipulationMode == ActorManipulationMode.Pan)
|
||||
if (isHead)
|
||||
{
|
||||
float distance = ((Vector2)mousePosition - (Vector2)transform.position).y;
|
||||
|
||||
Vector3 headOffset = new Vector3(0f, 0.34f, 0f);
|
||||
headOffset = Quaternion.Euler(0, 0, keyframe.bodyAngle) * headOffset;
|
||||
|
||||
distance = Vector2.Dot(parent.transform.up, (Vector2)(mousePosition - parent.transform.position - headOffset));
|
||||
if (Workspace.actorManipulationMode == ActorManipulationMode.Pan)
|
||||
{
|
||||
Vector3 headOffset = new Vector3(0f, 0.34f, 0f);
|
||||
headOffset = Quaternion.Euler(0, 0, keyframe.bodyAngle) * headOffset;
|
||||
|
||||
keyframe.headBob = distance;
|
||||
float distance = Vector2.Dot(parent.transform.up, (Vector2)(mousePosition - parent.transform.position - headOffset));
|
||||
|
||||
keyframe.headBob = distance;
|
||||
}
|
||||
|
||||
else if (Workspace.actorManipulationMode == ActorManipulationMode.Rotate)
|
||||
{
|
||||
float angle = Vector2.SignedAngle(Vector2.down, (Vector2)mousePosition - (Vector2)transform.position);
|
||||
keyframe.headAngle = angle;
|
||||
}
|
||||
|
||||
else if (Workspace.actorManipulationMode == ActorManipulationMode.Face)
|
||||
{
|
||||
float angle = Vector2.SignedAngle(Vector2.up, (Vector2)mousePosition - (Vector2)transform.position);
|
||||
int facing = -Mathf.RoundToInt(angle / 90f);
|
||||
facing = facing < 0 ? facing + 4 : facing;
|
||||
|
||||
keyframe.headFacing = facing;
|
||||
}
|
||||
}
|
||||
|
||||
else if (Workspace.actorManipulationMode == ActorManipulationMode.Rotate)
|
||||
else
|
||||
{
|
||||
float angle = Vector2.SignedAngle(Vector2.down, (Vector2)mousePosition - (Vector2)transform.position);
|
||||
keyframe.headAngle = angle;
|
||||
}
|
||||
|
||||
else if (Workspace.actorManipulationMode == ActorManipulationMode.Face)
|
||||
{
|
||||
float angle = Vector2.SignedAngle(Vector2.up, (Vector2)mousePosition - (Vector2)transform.position);
|
||||
int facing = -Mathf.RoundToInt(angle / 90f);
|
||||
facing = facing < 0 ? facing + 4 : facing;
|
||||
|
||||
keyframe.headFacing = facing;
|
||||
if (Workspace.actorManipulationMode == ActorManipulationMode.Rotate)
|
||||
{
|
||||
float angle = Vector2.SignedAngle(Vector2.up, (Vector2)mousePosition - (Vector2)transform.position);
|
||||
keyframe.genitalAngle = angle;
|
||||
}
|
||||
}
|
||||
|
||||
PawnAnimationClip clip = Workspace.Instance.GetPawnAnimationClip(parent.actorID);
|
||||
|
@ -64,14 +85,23 @@ namespace RimWorldAnimationStudio
|
|||
public void Activate()
|
||||
{
|
||||
Workspace.actorID = parent.actorID;
|
||||
Workspace.selectedBodyPart = this;
|
||||
|
||||
/*foreach (ActorBodyPart actorBodyPart in AnimationController.Instance.actorBodies.GetComponentsInChildren<ActorBodyPart>())
|
||||
{
|
||||
actorBodyPart.isSelected = false;
|
||||
}
|
||||
|
||||
foreach (ActorBody actorBody in AnimationController.Instance.actorBodies.GetComponentsInChildren<ActorBody>())
|
||||
{
|
||||
actorBody.bodyRenderer.color = Constants.ColorWhite;
|
||||
actorBody.headRenderer.color = Constants.ColorWhite;
|
||||
actorBody.appendageRenderer.color = Constants.ColorWhite;
|
||||
}
|
||||
|
||||
bodyPartRenderer.color = Constants.ColorGreen;
|
||||
|
||||
isSelected = true;*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,10 +11,14 @@ namespace RimWorldAnimationStudio
|
|||
public InputField positionXField;
|
||||
public InputField positionZField;
|
||||
public InputField rotationField;
|
||||
public InputField headBobField;
|
||||
public InputField headRotationField;
|
||||
public InputField appendageRotationField;
|
||||
|
||||
private int lastTick = -1;
|
||||
private bool isDirty = false;
|
||||
|
||||
// Move to anim controller
|
||||
public void Update()
|
||||
{
|
||||
if ((Workspace.animationDef == null || AnimationController.Instance.stageTick == lastTick) && isDirty == false)
|
||||
|
@ -41,15 +45,17 @@ namespace RimWorldAnimationStudio
|
|||
if (headAngle < 0) headAngle = 360 - ((-1f * headAngle) % 360);
|
||||
if (headAngle > 360) headAngle %= 360;
|
||||
|
||||
int bodyFacing = (int)clip.BodyFacing.Evaluate(clipPercent);
|
||||
Vector3 headBob = new Vector3(0, 0, clip.HeadBob.Evaluate(clipPercent)) + PawnUtility.BaseHeadOffsetAt(bodyType, bodyFacing);
|
||||
|
||||
float headBob = clip.HeadBob.Evaluate(clipPercent);
|
||||
Vector3 bodyPos = new Vector3(deltaPos.x, deltaPos.z, 0);
|
||||
Vector3 headPos = new Vector3(headBob.x, headBob.z, 0);
|
||||
|
||||
float appendageRotation = clip.GenitalAngle.Evaluate(clipPercent);
|
||||
|
||||
positionXField.text = bodyPos.x.ToString("0.000");
|
||||
positionZField.text = bodyPos.y.ToString("0.000");
|
||||
rotationField.text = bodyAngle.ToString("0.000");
|
||||
headBobField.text = headBob.ToString("0.000");
|
||||
headRotationField.text = headAngle.ToString("0.000");
|
||||
appendageRotationField.text = appendageRotation.ToString("0.000");
|
||||
|
||||
lastTick = AnimationController.Instance.stageTick;
|
||||
isDirty = false;
|
||||
|
@ -57,12 +63,19 @@ namespace RimWorldAnimationStudio
|
|||
|
||||
public void OnValueChanged()
|
||||
{
|
||||
Workspace.animationDef.animationStages[Workspace.stageID].animationClips[Workspace.actorID].keyframes.FirstOrDefault(x => x.keyframeID == Workspace.keyframeID).bodyOffsetX = float.Parse(positionXField.text);
|
||||
Workspace.animationDef.animationStages[Workspace.stageID].animationClips[Workspace.actorID].keyframes.FirstOrDefault(x => x.keyframeID == Workspace.keyframeID).bodyOffsetZ = float.Parse(positionZField.text);
|
||||
Workspace.animationDef.animationStages[Workspace.stageID].animationClips[Workspace.actorID].keyframes.FirstOrDefault(x => x.keyframeID == Workspace.keyframeID).bodyAngle = float.Parse(rotationField.text);
|
||||
PawnKeyframe keyframe = Workspace.Instance.GetCurrentPawnKeyframe(true);
|
||||
|
||||
keyframe.bodyOffsetX = float.Parse(positionXField.text);
|
||||
keyframe.bodyOffsetZ = float.Parse(positionZField.text);
|
||||
keyframe.bodyAngle = float.Parse(rotationField.text);
|
||||
keyframe.headBob = float.Parse(headBobField.text);
|
||||
keyframe.headAngle = float.Parse(headRotationField.text);
|
||||
keyframe.genitalAngle = float.Parse(appendageRotationField.text);
|
||||
|
||||
Workspace.Instance.GetPawnAnimationClip(Workspace.actorID).BuildSimpleCurves();
|
||||
isDirty = true;
|
||||
|
||||
Workspace.Instance.MakeDirty();
|
||||
}
|
||||
}
|
||||
}
|
28
Assets/Scripts/GUI/AddSoundDefButton.cs
Normal file
28
Assets/Scripts/GUI/AddSoundDefButton.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace RimWorldAnimationStudio
|
||||
{
|
||||
public class AddSoundDefButton : MonoBehaviour
|
||||
{
|
||||
private Text text;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
text = GetComponentInChildren<Text>();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
PawnKeyframe keyframe = Workspace.Instance.GetCurrentPawnKeyframe();
|
||||
|
||||
if (keyframe != null)
|
||||
{ text.text = keyframe.soundEffect == null || keyframe.soundEffect == "" ? "None" : keyframe.soundEffect; }
|
||||
|
||||
else
|
||||
{ text.text = "None"; }
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Scripts/GUI/AddSoundDefButton.cs.meta
Normal file
11
Assets/Scripts/GUI/AddSoundDefButton.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 329a2ecc35813f94fa879c7f434c8fb7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
88
Assets/Scripts/GUI/DialogBoxes/SelectSoundDefDialog.cs
Normal file
88
Assets/Scripts/GUI/DialogBoxes/SelectSoundDefDialog.cs
Normal file
|
@ -0,0 +1,88 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace RimWorldAnimationStudio
|
||||
{
|
||||
public class SelectSoundDefDialog : DialogBox
|
||||
{
|
||||
public void Start()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public void AddSoundDef(InputField field)
|
||||
{
|
||||
if (field?.text == null || field.text == "")
|
||||
{ return; }
|
||||
|
||||
if (Workspace.soundDefs.Contains(field.text))
|
||||
{ field.text = ""; return; }
|
||||
|
||||
Workspace.soundDefs.Add(field.text);
|
||||
|
||||
Initialize(true);
|
||||
}
|
||||
|
||||
public void Initialize(bool addedNewTag = false)
|
||||
{
|
||||
if (Workspace.animationDef == null) return;
|
||||
|
||||
foreach (AnimationStage stage in Workspace.animationDef.animationStages)
|
||||
{
|
||||
foreach (PawnAnimationClip clip in stage.animationClips)
|
||||
{
|
||||
foreach (string soundDef in clip.keyframes.Select(x => x.soundEffect))
|
||||
{
|
||||
if (Workspace.soundDefs.Contains(soundDef) == false && soundDef != "")
|
||||
{ Workspace.soundDefs.Add(soundDef); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Transform contentWindow = transform.FindDeepChild("Content");
|
||||
Reset();
|
||||
|
||||
for (int i = 0; i < Workspace.soundDefs.Count; i++)
|
||||
{
|
||||
string soundDef = Workspace.soundDefs[i];
|
||||
|
||||
Transform _optionToggle = AddCloneObjectToParent(contentWindow).transform;
|
||||
_optionToggle.Find("Text").GetComponent<Text>().text = soundDef;
|
||||
|
||||
Toggle toggleComp = _optionToggle.GetComponent<Toggle>();
|
||||
toggleComp.isOn = Workspace.animationDef.sexTypes.Contains(soundDef);
|
||||
toggleComp.onValueChanged.AddListener(delegate {
|
||||
|
||||
PawnKeyframe keyframe = Workspace.Instance.GetCurrentPawnKeyframe();
|
||||
|
||||
if (keyframe != null)
|
||||
{ keyframe.soundEffect = soundDef; }
|
||||
|
||||
Workspace.Instance.MakeDirty();
|
||||
});
|
||||
|
||||
toggleComp.group = contentWindow.GetComponent<ToggleGroup>();
|
||||
|
||||
if (addedNewTag && i == Workspace.soundDefs.Count - 1)
|
||||
{ toggleComp.isOn = true; }
|
||||
}
|
||||
|
||||
Transform _optionField = AddCloneObjectToParent(contentWindow, 1).transform;
|
||||
_optionField.Find("Placeholder").GetComponent<Text>().text = "Enter new sound def...";
|
||||
|
||||
InputField fieldComp = _optionField.GetComponent<InputField>();
|
||||
fieldComp.onEndEdit.AddListener(delegate { AddSoundDef(fieldComp); });
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Transform contentWindow = transform.FindDeepChild("Content");
|
||||
RemoveCloneObjectsFromParent(contentWindow);
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Scripts/GUI/DialogBoxes/SelectSoundDefDialog.cs.meta
Normal file
11
Assets/Scripts/GUI/DialogBoxes/SelectSoundDefDialog.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7cc6afc7ba6ae0f42b3a2df6e73c5b16
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -14,6 +14,8 @@ namespace RimWorldAnimationStudio
|
|||
public AnimationTimeline timeline;
|
||||
public Transform ghostSliders;
|
||||
public Slider ghostSliderPrefab;
|
||||
public Image handleImage;
|
||||
public GameObject soundIcon;
|
||||
public int maxGhosts = 4;
|
||||
|
||||
public int actorID;
|
||||
|
@ -131,17 +133,25 @@ namespace RimWorldAnimationStudio
|
|||
{
|
||||
base.Update();
|
||||
|
||||
if (Workspace.keyframeID == keyframeID && AnimationController.Instance.stageTick == keyframe.atTick.Value)
|
||||
{ transform.FindDeepChild("Handle").GetComponent<Image>().color = Constants.ColorPurple; }
|
||||
if (keyframe.atTick.HasValue && Workspace.keyframeID == keyframeID && AnimationController.Instance.stageTick == keyframe.atTick.Value)
|
||||
{ handleImage.color = Constants.ColorPurple; }
|
||||
|
||||
else if (Workspace.keyframeID == keyframeID)
|
||||
{ transform.FindDeepChild("Handle").GetComponent<Image>().color = Constants.ColorCyan; }
|
||||
{ handleImage.color = Constants.ColorCyan; }
|
||||
|
||||
else if (AnimationController.Instance.stageTick == keyframe.atTick.Value)
|
||||
{ transform.FindDeepChild("Handle").GetComponent<Image>().color = Constants.ColorPink; }
|
||||
else if (keyframe.atTick.HasValue && AnimationController.Instance.stageTick == keyframe.atTick.Value)
|
||||
{ handleImage.color = Constants.ColorPink; }
|
||||
|
||||
else
|
||||
{ transform.FindDeepChild("Handle").GetComponent<Image>().color = Constants.ColorWhite; }
|
||||
{ handleImage.color = Constants.ColorWhite; }
|
||||
|
||||
string soundDef = Workspace.Instance.GetPawnKeyframe(actorID, keyframeID).soundEffect;
|
||||
|
||||
if (soundDef != null && soundDef != "" && soundDef != "None")
|
||||
{ soundIcon.SetActive(true); }
|
||||
|
||||
else
|
||||
{ soundIcon.SetActive(false); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
24
Assets/Scripts/GUI/QuiverToggle.cs
Normal file
24
Assets/Scripts/GUI/QuiverToggle.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace RimWorldAnimationStudio
|
||||
{
|
||||
public class QuiverToggle : MonoBehaviour
|
||||
{
|
||||
public void Update()
|
||||
{
|
||||
PawnKeyframe keyframe = Workspace.Instance.GetCurrentPawnKeyframe();
|
||||
GetComponent<Toggle>().isOn = keyframe != null && keyframe.quiver.HasValue && keyframe.quiver.Value;
|
||||
}
|
||||
|
||||
public void OnValueChanged()
|
||||
{
|
||||
PawnKeyframe keyframe = Workspace.Instance.GetCurrentPawnKeyframe();
|
||||
|
||||
if (keyframe != null)
|
||||
{ keyframe.quiver = GetComponent<Toggle>().isOn; }
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Scripts/GUI/QuiverToggle.cs.meta
Normal file
11
Assets/Scripts/GUI/QuiverToggle.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1aaf656fd4e788245accc65162730545
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -26,6 +26,8 @@ namespace RimWorldAnimationStudio
|
|||
public ActorCard actorCard;
|
||||
public Transform animationTimelines;
|
||||
public Transform actorBodies;
|
||||
public Toggle stretchkeyframesToggle;
|
||||
|
||||
|
||||
[Header("Prefabs")]
|
||||
public ActorBody actorBodyPrefab;
|
||||
|
@ -151,14 +153,20 @@ namespace RimWorldAnimationStudio
|
|||
Vector3 bodyPos = new Vector3(deltaPos.x, deltaPos.z, 0);
|
||||
Vector3 headPos = new Vector3(headBob.x, headBob.z, 0);
|
||||
|
||||
Vector3 appendagePos = PawnUtility.AppendageOffsetAt(bodyType, bodyFacing);
|
||||
|
||||
actorBody.transform.position = bodyPos;
|
||||
actorBody.transform.eulerAngles = new Vector3(0, 0, bodyAngle);
|
||||
|
||||
actorBody.headRenderer.transform.localPosition = headPos;
|
||||
actorBody.headRenderer.transform.eulerAngles = new Vector3(0, 0, headAngle);
|
||||
|
||||
actorBody.appendageRenderer.transform.localPosition = new Vector3(appendagePos.x, appendagePos.z, 0f);
|
||||
actorBody.appendageRenderer.transform.eulerAngles = new Vector3(0,0,clip.GenitalAngle.Evaluate(clipPercent));
|
||||
|
||||
actorBody.bodyRenderer.sprite = Resources.Load<Sprite>("Textures/Humanlike/Bodies/" + bodyType + bodyFacing);
|
||||
actorBody.headRenderer.sprite = Resources.Load<Sprite>("Textures/Humanlike/Heads/Head" + headFacing);
|
||||
actorBody.appendageRenderer.sprite = Resources.Load<Sprite>("Textures/Humanlike/Appendages/Appendage" + bodyFacing);
|
||||
|
||||
actorBody.bodyRenderer.sortingLayerName = clip.layer;
|
||||
actorBody.headRenderer.sortingLayerName = clip.layer;
|
||||
|
@ -351,34 +359,58 @@ namespace RimWorldAnimationStudio
|
|||
{
|
||||
if (Workspace.animationDef == null) return;
|
||||
|
||||
int.TryParse(animationClipLengthField.text, out int newstageWindowSize);
|
||||
newstageWindowSize = Mathf.Clamp(newstageWindowSize, Constants.minAnimationClipLength, Constants.maxAnimationClipLength);
|
||||
int.TryParse(animationClipLengthField.text, out int newStageWindowSize);
|
||||
newStageWindowSize = Mathf.Clamp(newStageWindowSize, Constants.minAnimationClipLength, Constants.maxAnimationClipLength);
|
||||
|
||||
Debug.Log("Resizing animation clip length to " + newstageWindowSize.ToString() + " ticks.");
|
||||
Debug.Log("Resizing animation clip length to " + newStageWindowSize.ToString() + " ticks.");
|
||||
|
||||
for (int i = 0; i < Workspace.animationDef.animationStages[Workspace.stageID].animationClips.Count; i++)
|
||||
if (stretchkeyframesToggle.isOn)
|
||||
{ StretchKeyframes(newStageWindowSize); }
|
||||
|
||||
else
|
||||
{
|
||||
PawnAnimationClip clip = Workspace.animationDef.animationStages[Workspace.stageID].animationClips[i];
|
||||
List<PawnKeyframe> keyframes = clip.keyframes.Where(x => x.atTick > newstageWindowSize)?.ToList();
|
||||
for (int i = 0; i < Workspace.animationDef.animationStages[Workspace.stageID].animationClips.Count; i++)
|
||||
{
|
||||
PawnAnimationClip clip = Workspace.animationDef.animationStages[Workspace.stageID].animationClips[i];
|
||||
List<PawnKeyframe> keyframes = clip.keyframes.Where(x => x.atTick > newStageWindowSize)?.ToList();
|
||||
|
||||
if (keyframes.NullOrEmpty())
|
||||
{ continue; }
|
||||
if (keyframes.NullOrEmpty())
|
||||
{ continue; }
|
||||
|
||||
foreach (PawnKeyframe keyframe in keyframes)
|
||||
{
|
||||
RemovePawnKeyframe(i, keyframe.keyframeID);
|
||||
foreach (PawnKeyframe keyframe in keyframes)
|
||||
{
|
||||
RemovePawnKeyframe(i, keyframe.keyframeID);
|
||||
|
||||
if (Workspace.animationDef.animationStages[Workspace.stageID].animationClips[i].keyframes.Count <= 2)
|
||||
{ break; }
|
||||
if (Workspace.animationDef.animationStages[Workspace.stageID].animationClips[i].keyframes.Count <= 2)
|
||||
{ break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animationClipLengthField.text = newstageWindowSize.ToString();
|
||||
Workspace.animationDef.animationStages[Workspace.stageID].stageWindowSize = newstageWindowSize;
|
||||
animationClipLengthField.text = newStageWindowSize.ToString();
|
||||
Workspace.animationDef.animationStages[Workspace.stageID].stageWindowSize = newStageWindowSize;
|
||||
|
||||
Workspace.Instance.MakeDirty();
|
||||
}
|
||||
|
||||
public void StretchKeyframes(int newStageWindowSize)
|
||||
{
|
||||
float scale = (float)newStageWindowSize / Workspace.StageWindowSize;
|
||||
|
||||
foreach (PawnAnimationClip clip in Workspace.animationDef.animationStages[Workspace.stageID].animationClips)
|
||||
{
|
||||
foreach (PawnKeyframe keyframe in clip.keyframes)
|
||||
{
|
||||
if (keyframe.atTick == 1) continue;
|
||||
|
||||
keyframe.tickDuration = Mathf.RoundToInt(keyframe.tickDuration * scale);
|
||||
keyframe.atTick = null;
|
||||
}
|
||||
|
||||
clip.BuildSimpleCurves();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnCycleNormalFieldChange()
|
||||
{
|
||||
if (Workspace.animationDef == null) return;
|
||||
|
|
|
@ -5,3 +5,10 @@
|
|||
Face,
|
||||
}
|
||||
|
||||
public enum ActorGender
|
||||
{
|
||||
Female,
|
||||
None,
|
||||
Male,
|
||||
}
|
||||
|
||||
|
|
|
@ -31,5 +31,34 @@ namespace RimWorldAnimationStudio
|
|||
default: return Vector3.zero;
|
||||
}
|
||||
}
|
||||
|
||||
public static Vector3 AppendageOffsetAt(string bodyType, int rotation)
|
||||
{
|
||||
if (rotation == 0 || rotation == 2)
|
||||
{
|
||||
switch (bodyType)
|
||||
{
|
||||
case "Male": return new Vector3(0.00f, 0f, -0.46f);
|
||||
case "Female": return new Vector3(0.00f, 0f, -0.51f);
|
||||
case "Thin": return new Vector3(0.00f, 0f, -0.46f);
|
||||
case "Hulk": return new Vector3(0.00f, 0f, -0.66f);
|
||||
case "Fat": return new Vector3(0.00f, 0f, -0.52f);
|
||||
default: return Vector3.zero;
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
switch (bodyType)
|
||||
{
|
||||
case "Male": return new Vector3(0.07f, 0f, -0.41f);
|
||||
case "Female": return new Vector3(0.07f, 0f, -0.47f);
|
||||
case "Thin": return new Vector3(-0.05f, 0f, -0.43f);
|
||||
case "Hulk": return new Vector3(-0.03f, 0f, -0.64f);
|
||||
case "Fat": return new Vector3(0.24f, 0f, -0.50f);
|
||||
default: return Vector3.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ namespace RimWorldAnimationStudio
|
|||
public static List<string> bodyDefTypes = new List<string>() { "Human", "Bird", "BeetleLike", "BeetleLikeWithClaw", "MechanicalCentipede", "MechanicalTermite", "Lancer", "Pikeman", "Monkey", "QuadrupedAnimalWithClawsTailAndJowl", "QuadrupedAnimalWithHooves", "QuadrupedAnimalWithHoovesAndHorn", "QuadrupedAnimalWithHoovesAndHump", "QuadrupedAnimalWithHoovesAndTusks", "QuadrupedAnimalWithHoovesTusksAndTrunk", "QuadrupedAnimalWithPaws", "QuadrupedAnimalWithPawsAndTail", "Scyther", "Snake", "TurtleLike" };
|
||||
public static List<string> sexTypes = new List<string>() { "None", "Vaginal", "Anal", "Oral", "Masturbation", "DoublePenetration", "Boobjob", "Handjob", "Footjob", "Fingering", "Scissoring", "MutualMasturbation", "Fisting", "MechImplant", "Rimming", "Fellatio", "Cunnilingus", "Sixtynine" };
|
||||
public static List<string> interactionDefTypes = new List<string>();
|
||||
public static List<string> soundDefs = new List<string>() { "None", "Sex", "Fuck", "Slimy", "Suck", "Cum" };
|
||||
|
||||
[SerializeField] private List<WorkspaceSnapShot> workspaceHistory = new List<WorkspaceSnapShot>();
|
||||
[SerializeField] private int historyIndex = 0;
|
||||
|
@ -26,7 +27,8 @@ namespace RimWorldAnimationStudio
|
|||
private bool isDirty = false;
|
||||
|
||||
public static ActorManipulationMode actorManipulationMode = ActorManipulationMode.Pan;
|
||||
|
||||
public static ActorBodyPart selectedBodyPart;
|
||||
|
||||
public static int StageWindowSize
|
||||
{
|
||||
get
|
||||
|
@ -47,12 +49,12 @@ namespace RimWorldAnimationStudio
|
|||
{ TrackChanges(); }
|
||||
}
|
||||
|
||||
public PawnKeyframe GetCurrentPawnKeyframe()
|
||||
public PawnKeyframe GetCurrentPawnKeyframe(bool makeKeyframe = false)
|
||||
{
|
||||
int stageTick = AnimationController.Instance.stageTick;
|
||||
PawnKeyframe keyframe = animationDef?.animationStages[stageID]?.animationClips[actorID]?.keyframes.FirstOrDefault(x => x.atTick == stageTick);
|
||||
|
||||
if (keyframe != null)
|
||||
if (keyframe != null || makeKeyframe == false)
|
||||
{ return keyframe; }
|
||||
|
||||
AnimationController.Instance.AddPawnKeyframe();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue