Add support for exporting prop, compatible with the new version, add the EnableRootMotion option (#143)

* update BillboardBuilder

* add a new enum value

* Add the EnableRootMotion option to support scenarios where the character is not within the camera view

* Add support for exporting prop

* update

* compatible with the new version 2

* update

---------

Co-authored-by: 左右看 <1918891542@qq.com>
This commit is contained in:
CroakFang
2025-09-28 18:10:18 +08:00
committed by GitHub
parent 2f2f06eadc
commit d66991c2ef
33 changed files with 6620 additions and 390 deletions
Binary file not shown.
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 654feed001747154f9fc3ac6f1ba11a7
guid: 26e1318ece7f74447bfe09802527f226
PluginImporter:
externalObjects: {}
serializedVersion: 2
@@ -14,20 +14,14 @@ PluginImporter:
- first:
Any:
second:
enabled: 0
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 1
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Standalone: OSXUniversal
second:
enabled: 1
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -29,7 +29,7 @@ AnimatorState:
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: motion_1
m_Name: motion_p
m_Speed: 1
m_CycleOffset: 0
m_Transitions:
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

@@ -0,0 +1,114 @@
fileFormatVersion: 2
guid: 0f7f1a85919ef4c419dfcd825b113de6
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

@@ -0,0 +1,114 @@
fileFormatVersion: 2
guid: 64ce9962a1619a948a98ccddacd0416c
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 23, y: 27, z: 23, w: 21}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 1537655665
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
+3601 -166
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
using System;
using System.IO;
public static class AssetBundleDecryptor
{
public static byte[] DecryptFileToBytes(string inputFilePath, byte[] Keys)
{
if (string.IsNullOrEmpty(inputFilePath))
throw new ArgumentNullException(nameof(inputFilePath));
if (!File.Exists(inputFilePath))
throw new FileNotFoundException("Input file not found", inputFilePath);
if (Keys == null)
throw new ArgumentException("Keys must not be null or empty", nameof(Keys));
byte[] data = File.ReadAllBytes(inputFilePath);
if (data.Length <= 256)
return data;
for (int i = 256; i < data.Length; ++i)
{
data[i] ^= Keys[i % Keys.Length];
}
return data;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c9ca478441998cb449da2a875dc99b51
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+89 -2
View File
@@ -1,6 +1,8 @@
using System;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;
public class Config
@@ -9,6 +11,15 @@ public class Config
public static Config Instance;
public string Version = "";
public string DBBaseKeyTip = "Base key to read game database files";
public string DBBaseKeyText;
public string DBKeyTip = "Key to read game database files";
public string DBKeyText;
public string ABKeyTip = "Key to read asset bundles";
public string ABKeyText;
public string LanguageTip = "Affects Uma names on the list. Language options: 0 - En, 1 - Jp";
public Language Language = Language.En;
@@ -94,6 +105,29 @@ public class Config
new MorphConvertConfig("口横狭い", new string[] { "Mouth_55_0" })
};
[NonSerialized]
public byte[] DBBaseKey = new byte[]
{
0xF1, 0x70, 0xCE, 0xA4, 0xDF, 0xCE, 0xA3, 0xE1,
0xA5, 0xD8, 0xC7, 0x0B, 0xD1, 0x00, 0x00, 0x00
};
[NonSerialized]
public byte[] DBKey = new byte[]
{
0x6D, 0x5B, 0x65, 0x33, 0x63, 0x36,
0x63, 0x25, 0x54, 0x71, 0x2D, 0x73,
0x50, 0x53, 0x63, 0x38, 0x6D, 0x34,
0x37, 0x7B, 0x35, 0x63, 0x70, 0x23,
0x37, 0x34, 0x53, 0x29, 0x73, 0x43,
0x36, 0x33
};
[NonSerialized]
public byte[] ABKey = new byte[]
{
0x53, 0x2B, 0x46, 0x31, 0xE4, 0xA7, 0xB9, 0x47, 0x3E, 0x7C, 0xFB
};
public Config()
{
@@ -111,6 +145,9 @@ public class Config
if (!File.Exists(configPath))
{
DBBaseKeyText = ByteArrayToHex(DBBaseKey);
DBKeyText = ByteArrayToHex(DBKey);
ABKeyText = ByteArrayToHex(ABKey);
File.WriteAllText(configPath, JsonUtility.ToJson(this, true));
}
else
@@ -118,6 +155,31 @@ public class Config
try
{
JsonUtility.FromJsonOverwrite(File.ReadAllText(configPath), this);
if (string.IsNullOrEmpty(DBBaseKeyText))
{
DBBaseKeyText = ByteArrayToHex(DBBaseKey);
}
else
{
DBBaseKey = HexToByteArray(DBBaseKeyText);
}
if (string.IsNullOrEmpty(DBKeyText))
{
DBKeyText = ByteArrayToHex(DBKey);
}
else
{
DBKey = HexToByteArray(DBKeyText);
}
if (string.IsNullOrEmpty(ABKeyText))
{
ABKeyText = ByteArrayToHex(ABKey);
}
else
{
ABKey = HexToByteArray(ABKeyText);
}
File.WriteAllText(configPath, JsonUtility.ToJson(this, true));
}
catch (Exception ex)
@@ -126,7 +188,6 @@ public class Config
MainPath = $@"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "Low"}\Cygames\umamusume";
}
}
Instance = this;
}
@@ -138,6 +199,32 @@ public class Config
UmaViewerUI.Instance.ShowMessage("The configuration has changed. Please restart the application.", UIMessageType.Default);
}
}
private string ByteArrayToHex(byte[] byteArray)
{
if (byteArray == null) return string.Empty;
StringBuilder sb = new StringBuilder(byteArray.Length * 2);
foreach (byte b in byteArray)
{
sb.AppendFormat("{0:X2}", b);
}
return sb.ToString();
}
private byte[] HexToByteArray(string hex)
{
if (string.IsNullOrEmpty(hex) || hex.Length % 2 != 0)
return new byte[0];
byte[] result = new byte[hex.Length / 2];
for (int i = 0; i < result.Length; i++)
{
string byteString = hex.Substring(i * 2, 2);
result[i] = byte.Parse(byteString, System.Globalization.NumberStyles.HexNumber);
}
return result;
}
}
public enum Language
+126 -49
View File
@@ -4,59 +4,85 @@ using UnityEngine;
public class BillboardBuilder : MonoBehaviour
{
public static Mesh CreateSphereMesh(float radius, float uvRadius, Vector2 uvCenter, int longitude = 6, int latitude = 6)
//修改(Billboard的导出相关)
public static Mesh CreateSphereMesh(float radius, int subdiv, float uvRadius, Vector2 uvCenter)
{
Mesh mesh = new Mesh();
Vector3[] vertices = new Vector3[(longitude + 1) * (latitude + 1)];
Vector2[] uvs = new Vector2[vertices.Length];
int[] triangles = new int[longitude * latitude * 6];
int vert = 0, tri = 0;
for (int lat = 0; lat <= latitude; lat++)
var t = (1f + Mathf.Sqrt(5f)) / 2f;
var baseVerts = new List<Vector3>
{
float a1 = Mathf.PI * lat / latitude;
float y = Mathf.Cos(a1);
float sin1 = Mathf.Sin(a1);
for (int lon = 0; lon <= longitude; lon++)
{
float a2 = 2 * Mathf.PI * lon / longitude;
float x = sin1 * Mathf.Cos(a2);
float z = sin1 * Mathf.Sin(a2);
vertices[vert] = new Vector3(x, y, z) * radius;
//uvs[vert] = new Vector2((float)lon / longitude, (float)lat / latitude);
Vector3 p = vertices[vert].normalized;
uvs[vert] = new Vector2(p.x, p.z) * uvRadius + uvCenter;
//uvs[vert] = new Vector2(p.x, p.z) * 0.5f + new Vector2(0.5f, 0.5f);
if (lat < latitude && lon < longitude)
{
int current = vert;
int next = vert + longitude + 1;
triangles[tri++] = current;
triangles[tri++] = current + 1;
triangles[tri++] = next;
triangles[tri++] = current + 1;
triangles[tri++] = next + 1;
triangles[tri++] = next;
}
vert++;
}
new Vector3(-1, t, 0), new Vector3( 1, t, 0), new Vector3(-1, -t, 0), new Vector3( 1, -t, 0),
new Vector3( 0, -1, t), new Vector3( 0, 1, t), new Vector3( 0, -1, -t), new Vector3( 0, 1, -t),
new Vector3( t, 0, -1), new Vector3( t, 0, 1), new Vector3(-t, 0, -1), new Vector3(-t, 0, 1),
};
var faces = new List<int[]>
{
new[]{0,11,5}, new[]{0,5,1}, new[]{0,1,7}, new[]{0,7,10}, new[]{0,10,11},
new[]{1,5,9}, new[]{5,11,4}, new[]{11,10,2}, new[]{10,7,6}, new[]{7,1,8},
new[]{3,9,4}, new[]{3,4,2}, new[]{3,2,6}, new[]{3,6,8}, new[]{3,8,9},
new[]{4,9,5}, new[]{2,4,11}, new[]{6,2,10}, new[]{8,6,7}, new[]{9,8,1}
};
for (int i = 0; i < baseVerts.Count; i++) baseVerts[i] = baseVerts[i].normalized;
var vertList = new List<Vector3>(baseVerts);
var triList = new List<int>();
foreach (var f in faces) { triList.Add(f[0]); triList.Add(f[1]); triList.Add(f[2]); }
var midpointCache = new Dictionary<long, int>();
int GetMidpoint(int a, int b)
{
long key = a < b ? ((long)a << 32) + b : ((long)b << 32) + a;
if (midpointCache.TryGetValue(key, out var idx)) return idx;
var m = (vertList[a] + vertList[b]).normalized;
idx = vertList.Count;
vertList.Add(m);
midpointCache[key] = idx;
return idx;
}
mesh.vertices = vertices;
mesh.uv = uvs;
mesh.triangles = triangles;
mesh.RecalculateNormals();
for (int s = 0; s < subdiv; s++)
{
var newTris = new List<int>(triList.Count * 4);
for (int i = 0; i < triList.Count; i += 3)
{
int v0 = triList[i], v1 = triList[i+1], v2 = triList[i+2];
int a = GetMidpoint(v0, v1);
int b = GetMidpoint(v1, v2);
int c = GetMidpoint(v2, v0);
newTris.AddRange(new[]{ v0, a, c, v1, b, a, v2, c, b, a, b, c });
}
triList = newTris;
}
Vector3[] vertices = new Vector3[vertList.Count];
Vector3[] normals = new Vector3[vertList.Count];
Vector2[] uvs = new Vector2[vertList.Count];
for (int i = 0; i < vertList.Count; i++)
{
Vector3 p = vertList[i];
vertices[i] = p * radius;
normals[i] = p;
uvs[i] = new Vector2(p.x, p.z) * uvRadius + uvCenter;
}
var mesh = new Mesh();
if (vertices.Length > 65535) mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
mesh.vertices = vertices;
mesh.normals = normals;
mesh.uv = uvs;
mesh.triangles = triList.ToArray();
mesh.RecalculateBounds();
return mesh;
}
public static void BuildBillboard(UmaContainerCharacter container)
{
int groupSize = 4;
@@ -119,16 +145,27 @@ public class BillboardBuilder : MonoBehaviour
// Create
Mesh sphereMesh;
//修改(Billboard的导出相关)
if (radius < 0.003)
{
sphereMesh = CreateSphereMesh(radius, uv_radius, uv_center);
sphereMesh = CreateSphereMesh(radius, 1, uv_radius, uv_center);
}
else
{
sphereMesh = CreateSphereMesh(radius, uv_radius, uv_center, 12, 9);
sphereMesh = CreateSphereMesh(radius, 2, uv_radius, uv_center);
}
int vertexOffset = finalVertices.Count;
foreach (Vector3 v in sphereMesh.vertices)
@@ -165,6 +202,14 @@ public class BillboardBuilder : MonoBehaviour
newSMR.material = s.sharedMaterial;
Debug.Log($"Generated {sphereCount} small spheres and combined them into a single mesh.");
//修改(Billboard的导出相关)
//ApplyBillboardUVOffset(s);
}
}
}
@@ -179,4 +224,36 @@ public class BillboardBuilder : MonoBehaviour
}
}
}
//修改(Billboard的导出相关)
private static void ApplyBillboardUVOffset(SkinnedMeshRenderer smr)
{
var mesh = smr.sharedMesh;
if (mesh == null || mesh.uv2 == null || mesh.uv2.Length == 0) return;
var newMesh = UnityEngine.Object.Instantiate(mesh);
var vertices = newMesh.vertices;
var uv2 = newMesh.uv2;
Matrix4x4 worldToLocal = smr.transform.worldToLocalMatrix;
Vector3 localXAxis = worldToLocal.MultiplyVector(Vector3.right).normalized;
Vector3 localYAxis = worldToLocal.MultiplyVector(Vector3.up).normalized;
for (int i = 0; i < vertices.Length; i++)
{
Vector2 offset = uv2[i];
vertices[i] += localXAxis * offset.x + localYAxis * offset.y;
}
newMesh.vertices = vertices;
newMesh.RecalculateNormals();
newMesh.RecalculateBounds();
smr.sharedMesh = newMesh;
}
}
+58 -8
View File
@@ -31,7 +31,7 @@ public class ModelExporter
BuildBillboard(container);
var textures = TextureExporter.ExportAllTexture(Path.GetDirectoryName(path), container.gameObject);
var model = ReadPMXModel(container.CharaEntry, container, textures);
var model = ReadPMXModel(container, textures, container.CharaEntry);
FileStream fileStream = new FileStream(path, FileMode.Create);
BinaryWriter writer = new BinaryWriter(fileStream);
@@ -46,15 +46,45 @@ public class ModelExporter
UmaViewerUI.Instance.ShowMessage($"PMX Save at {path}", UIMessageType.Success);
}
public static RawMMDModel ReadPMXModel(CharaEntry entry, UmaContainer container, string[] textures)
public static void ExportModel(UmaContainer container, string path)
{
var textures = TextureExporter.ExportAllTexture(Path.GetDirectoryName(path), container.gameObject);
var model = ReadPMXModel(container, textures);
FileStream fileStream = new FileStream(path, FileMode.Create);
BinaryWriter writer = new BinaryWriter(fileStream);
var config = new ModelConfig() { GlobalToonPath = "Toon" };
PMXWriter.Write(writer, model, config);
writer.Close();
fileStream.Close();
UmaViewerUI.Instance.ShowMessage($"PMX Save at {path}", UIMessageType.Success);
}
public static RawMMDModel ReadPMXModel(UmaContainer container, string[] textures, CharaEntry entry = null)
{
RawMMDModel model = new RawMMDModel();
model.Name = entry.Name;
model.NameEn = entry.GetName();
model.Description = model.DescriptionEn = $"{entry.GetName()}\n{modelInfo}";
if (entry != null)
{
model.Name = entry.Name;
model.NameEn = entry.GetName();
model.Description = model.DescriptionEn = $"{entry.GetName()}\n{modelInfo}";
}
else
{
model.Name = container.gameObject.name;
model.NameEn = container.gameObject.name;
model.Description = model.DescriptionEn = $"{container.gameObject.name}\n{modelInfo}";
}
//Rean Bones
var rootBone = container.transform.Find("Position");
if (rootBone == null)
{
rootBone = container.transform;
}
List<Transform> bones = new List<Transform>(rootBone.GetComponentsInChildren<Transform>());
bones.RemoveAll(o => o.name.Contains("Col_"));
@@ -275,7 +305,10 @@ public class ModelExporter
{
mesh = ((SkinnedMeshRenderer)renderer).sharedMesh;
}
if (!mesh.isReadable)
{
continue;
}
var materials = new List<Material>(renderer.sharedMaterials);
for (int i = 0; i < mesh.subMeshCount; i++)
{
@@ -293,8 +326,15 @@ public class ModelExporter
mat.DrawSelfShadow = true;
mat.EdgeColor = Color.black;
mat.EdgeSize = 0.4f;
var tex = model.TextureList.Find(t => t.TexturePath.Contains($"/{material.mainTexture.name}.png"));
mat.Texture = (tex ?? model.TextureList[0]);
if (material.HasProperty("_MainTex"))
{
var tex = model.TextureList.Find(t => t.TexturePath.Contains($"/{material.mainTexture.name}.png"));
mat.Texture = (tex ?? model.TextureList[0]);
}
else
{
mat.Texture = model.TextureList[0];
}
mat.MetaInfo = "";
part.BaseShift = baseShift + mesh.GetSubMesh(i).indexStart;
part.TriangleIndexNum = mesh.GetSubMesh(i).indexCount;
@@ -317,6 +357,11 @@ public class ModelExporter
var meshfilter = mr.GetComponent<MeshFilter>();
//Cache this data to avoid copying arrays.
var mesh = meshfilter.mesh;
if (!mesh.isReadable)
{
UmaViewerUI.Instance.ShowMessage($"Export {mesh.name} Failed : Mesh is not readable", UIMessageType.Error);
continue;
}
var vertices = mesh.vertices;
var normals = mesh.normals;
var uv = mesh.uv;
@@ -355,6 +400,11 @@ public class ModelExporter
else if (renderer is SkinnedMeshRenderer smr)
{
var mesh = smr.sharedMesh;
if (!mesh.isReadable)
{
UmaViewerUI.Instance.ShowMessage($"Export {mesh.name} Failed : Mesh is not readable", UIMessageType.Error);
continue;
}
var vertices = mesh.vertices;
var normals = mesh.normals;
var uv = mesh.uv;
@@ -118,6 +118,13 @@ public class UISettingsAnimation : MonoBehaviour
}
}
public void EnableRootMotion(bool enable)
{
var container = Builder.CurrentUMAContainer;
if (!container || !container.UmaAnimator) return;
container.UmaAnimator.SetLayerWeight(2, enable ? 1 : 0);
}
public static string ToFrameFormat(float time, float frameRate)
{
int frames = Mathf.FloorToInt(time % 1 * frameRate);
+10 -2
View File
@@ -66,7 +66,11 @@ public class UISettingsAssets : MonoBehaviour
if (File.Exists(asset.FilePath) && !File.Exists(outName))
{
Directory.CreateDirectory(Path.GetDirectoryName(outName));
File.Copy(asset.FilePath, outName);
//修改(导出解密ab包)
var decryptedData = AssetBundleDecryptor.DecryptFileToBytes(asset.FilePath, asset.FKey);
File.WriteAllBytes(outName, decryptedData);
}
}
UmaViewerUI.Instance.ShowMessage($"{LoadedAssetEntries.Count} files copied", UIMessageType.Success);
@@ -96,7 +100,11 @@ public class UISettingsAssets : MonoBehaviour
if (File.Exists(asset.FilePath) && !File.Exists(outName))
{
Directory.CreateDirectory(Path.GetDirectoryName(outName));
File.Copy(asset.FilePath, outName);
//修改(导出解密ab包)
var decryptedData = AssetBundleDecryptor.DecryptFileToBytes(asset.FilePath, asset.FKey);
File.WriteAllBytes(outName, decryptedData);
}
}
UmaViewerUI.Instance.ShowMessage($"{assets.Count} files copied", UIMessageType.Success);
@@ -110,6 +110,17 @@ public class UISettingsModel : MonoBehaviour
ModelExporter.ExportModel(container, path);
}
}
var prop_container = Builder.CurrentOtherContainer;
if (prop_container)
{
var path = StandaloneFileBrowser.SaveFilePanel("Save PMX File", Config.Instance.MainPath, $"{prop_container}", "pmx");
if (!string.IsNullOrEmpty(path))
{
ModelExporter.ExportModel(prop_container, path);
}
}
#else
UmaViewerUI.Instance.ShowMessage("Not supported on this platform", UIMessageType.Warning);
#endif
+10 -1
View File
@@ -132,7 +132,16 @@ public class UmaAssetManager : MonoBehaviour
}
else
{
AssetBundle bundle = AssetBundle.LoadFromFile(filePath);
AssetBundle bundle;
if (string.IsNullOrEmpty(entry.Key) || long.Parse(entry.Key) == 0)
{
bundle = AssetBundle.LoadFromFile(filePath);
}
else
{
var data = AssetBundleDecryptor.DecryptFileToBytes(filePath, entry.FKey);
bundle = AssetBundle.LoadFromMemory(data);
}
if (!bundle)
{
Debug.Log(filePath + " exists and doesn't work");
+143 -21
View File
@@ -608,6 +608,9 @@ public class UmaContainerCharacter : UmaContainer
}
//BodyAlapha's shader need to change manually.
//修改(载入通用服装ColorSet相关)
if (m.name.Contains("bdy"))
{
if (m.name.Contains("Alpha"))
@@ -616,17 +619,33 @@ public class UmaContainerCharacter : UmaContainer
}
else
{
//some costume use area texture
var areaTex = UmaViewerMain.Instance.AbChara.FirstOrDefault(a => a.Name.StartsWith(UmaDatabaseController.BodyPath + $"bdy{costumeIdShort}/textures") && a.Name.EndsWith("area"));
if (areaTex != null)
var areaTexCandidates = UmaViewerMain.Instance.AbChara.Where(a => a.Name.StartsWith(UmaDatabaseController.BodyPath + $"bdy{costumeIdShort}/textures") && a.Name.EndsWith("area")).ToList();
if (areaTexCandidates.Count > 0)
{
LoadTextures(areaTex);
m.SetTexture("_MaskColorTex", textures.Load(t => t.name.Contains(costumeIdShort) && t.name.EndsWith("area")));
SetMaskColor(m, IsMob ? MobDressColor : CharaData, IsMob);
foreach (var areaEntry in areaTexCandidates)
{
LoadTextures(areaEntry);
}
var defaultAreaTex = GenericBodyTextures.Load(t => t.name.Contains(costumeIdShort) && t.name.EndsWith("0_area"));
if (defaultAreaTex != null)
{
m.SetTexture("_MaskColorTex", defaultAreaTex);
}
if (IsMob)
{
SetMaskColor(m, MobDressColor, true);
}
else
{
ApplyDressColorAndArea(m);
}
}
}
}
switch (costumeIdShort.Split('_')[0]) //costume ID
{
case "0001":
@@ -922,6 +941,17 @@ public class UmaContainerCharacter : UmaContainer
}
}
//修改(载入专用尾巴相关)
public void LoadExclusiveTail(UmaDatabaseEntry entry)
{
GameObject go = entry.Get<GameObject>();
Tail = Instantiate(go, transform);
}
public void LoadTail(UmaDatabaseEntry entry)
{
Tail = InstantiateEntry(entry, transform);
@@ -956,6 +986,92 @@ public class UmaContainerCharacter : UmaContainer
}
}
//修改(载入通用服装ColorSet相关)
public void RefreshDressColor()
{
foreach (var materialHelper in Materials)
{
var mat = materialHelper.Mat;
if (mat == null) continue;
if (mat.name.Contains("bdy") && !mat.name.Contains("Alpha"))
{
if (mat.HasProperty("_MaskColorTex"))
{
if (IsMob)
{
SetMaskColor(mat, MobDressColor, true);
}
else
{
ApplyDressColorAndArea(mat);
}
}
}
}
}
//修改(载入通用服装ColorSet相关)
private void ApplyDressColorAndArea(Material mat)
{
var uma = Builder.CurrentUMAContainer;
if (uma == null || string.IsNullOrEmpty(uma.VarCostumeIdShort)) return;
string selectedId = UmaViewerUI.CurrentSelectedColorSetId;
var parts = uma.VarCostumeIdShort.Split('_');
if (parts.Length < 2) return;
if (!int.TryParse(parts[0], out int bodyType)) return;
if (!int.TryParse(parts[1], out int bodyTypeSub)) return;
DataRow charaColor = null;
var dressRow = UmaDatabaseController.Instance.DressData.FirstOrDefault(row =>
int.Parse(row["body_type"].ToString()) == bodyType &&
int.Parse(row["body_type_sub"].ToString()) == bodyTypeSub);
if (dressRow != null && dressRow["body_setting"].ToString() == "4")
{
string dressId = dressRow["id"].ToString();
var colorSetRows = UmaDatabaseController.Instance.CharaDressColor
.Where(row => row["dress_id"].ToString() == dressId).ToList();
charaColor = !string.IsNullOrEmpty(selectedId)
? colorSetRows.FirstOrDefault(row => row["id"].ToString() == selectedId)
: null;
if (charaColor == null && colorSetRows.Count > 0) charaColor = colorSetRows.First();
}
else
{
charaColor = !string.IsNullOrEmpty(selectedId)
? UmaDatabaseController.Instance.CharaDressColor.FirstOrDefault(row => row["id"].ToString() == selectedId)
: null;
if (charaColor == null) charaColor = UmaDatabaseController.Instance.CharaDressColor.FirstOrDefault();
}
if (charaColor == null) return;
Texture2D areaTex = null;
try
{
if (charaColor.Table.Columns.Contains("area_map_id"))
{
int areaMapId = Convert.ToInt32(charaColor["area_map_id"]);
string suffix = areaMapId.ToString("D2") + "_area";
areaTex = GenericBodyTextures.Load(t => t.name.EndsWith(suffix));
}
}
catch { }
if (areaTex != null) mat.SetTexture("_MaskColorTex", areaTex);
SetMaskColor(mat, charaColor, false);
}
private void SetMaskColor(Material mat, DataRow colordata, bool IsMob)
{
mat.EnableKeyword("USE_MASK_COLOR");
@@ -977,19 +1093,23 @@ public class UmaContainerCharacter : UmaContainer
}
else
{
ColorUtility.TryParseHtmlString("#" + colordata["image_color_main"].ToString(), out c1);
ColorUtility.TryParseHtmlString("#" + colordata["image_color_sub"].ToString(), out c2);
ColorUtility.TryParseHtmlString("#" + colordata["ui_color_sub"].ToString(), out c3);
ColorUtility.TryParseHtmlString("#" + colordata["ui_color_sub"].ToString(), out c4);
ColorUtility.TryParseHtmlString("#" + colordata["ui_training_color_1"].ToString(), out c5);
ColorUtility.TryParseHtmlString("#" + colordata["ui_training_color_2"].ToString(), out c6);
float toonstrength = 0.8f;
t1 = c1 * toonstrength;
t2 = c2 * toonstrength;
t3 = c3 * toonstrength;
t4 = c4 * toonstrength;
t5 = c5 * toonstrength;
t6 = c6 * toonstrength;
//修改(载入通用服装ColorSet相关)
ColorUtility.TryParseHtmlString(colordata["color_r1"].ToString(), out c1);
ColorUtility.TryParseHtmlString(colordata["color_r2"].ToString(), out c2);
ColorUtility.TryParseHtmlString(colordata["color_g1"].ToString(), out c3);
ColorUtility.TryParseHtmlString(colordata["color_g2"].ToString(), out c4);
ColorUtility.TryParseHtmlString(colordata["color_b1"].ToString(), out c5);
ColorUtility.TryParseHtmlString(colordata["color_b2"].ToString(), out c6);
ColorUtility.TryParseHtmlString(colordata["toon_color_r1"].ToString(), out t1);
ColorUtility.TryParseHtmlString(colordata["toon_color_r2"].ToString(), out t2);
ColorUtility.TryParseHtmlString(colordata["toon_color_g1"].ToString(), out t3);
ColorUtility.TryParseHtmlString(colordata["toon_color_g2"].ToString(), out t4);
ColorUtility.TryParseHtmlString(colordata["toon_color_b1"].ToString(), out t5);
ColorUtility.TryParseHtmlString(colordata["toon_color_b2"].ToString(), out t6);
}
mat.SetColor("_MaskColorR1", c1);
@@ -1103,7 +1223,7 @@ public class UmaContainerCharacter : UmaContainer
{
if (IsMini) return;
OverrideController["clip_p"] = clip;
UmaAnimator.Play("motion_1", 2, 0);
UmaAnimator.Play("motion_p", 2, 0);
}
else if (clip.name.EndsWith("_cam"))
{
@@ -1148,7 +1268,7 @@ public class UmaContainerCharacter : UmaContainer
Builder.SetPreviewCamera(null);
OverrideController["clip_1"] = OverrideController["clip_2"];
OverrideController["clip_2"] = clip;
UmaAnimator.Play("motion_1", -1, 0);
UmaAnimator.Play("motion_1", 0, 0);
UmaAnimator.SetTrigger((motion_s != null && motion_e != null) ? "next_e" : ((motion_s != null) ? "next_s" : "next"));
}
else
@@ -1159,7 +1279,9 @@ public class UmaContainerCharacter : UmaContainer
isAnimatorControl = false;
}
UpBodyReset();
var pos_weight = UmaAnimator.GetLayerWeight(2);
UmaAnimator.Rebind();
UmaAnimator.SetLayerWeight(2, pos_weight); //keep position layer weight
OverrideController["clip_2"] = clip;
// If Cut-in, play immediately without state interpolation
+364
View File
@@ -0,0 +1,364 @@
// Sqlite3MC.cs
// Unity C# wrapper for sqlite3mc_x64.dll (Windows x64).
// Put sqlite3mc_x64.dll into Assets/Plugins (set platform to Windows x86_64).
using System;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine;
public static class Sqlite3MC
{
// ---- DLL name: change if you use different file name / arch ----
private const string DLL = "sqlite3mc_x64"; // ensure sqlite3mc_x64.dll is available for x64 build
// ---- Common SQLite constants ----
public const int SQLITE_OK = 0;
public const int SQLITE_ROW = 100;
public const int SQLITE_DONE = 101;
public const int SQLITE_OPEN_READWRITE = 0x00000002;
public const int SQLITE_OPEN_CREATE = 0x00000004;
// ---- Native Imports ----
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_open_v2")]
private static extern int sqlite3_open_v2([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr ppDb, int flags, IntPtr zVfs);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_close")]
private static extern int sqlite3_close(IntPtr db);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_errmsg")]
private static extern IntPtr sqlite3_errmsg_ptr(IntPtr db);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_exec")]
private static extern int sqlite3_exec(IntPtr db, [MarshalAs(UnmanagedType.LPStr)] string sql, IntPtr callback, IntPtr arg, out IntPtr errMsg);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_free")]
private static extern void sqlite3_free(IntPtr ptr);
// sqlite3mc plugin-specific
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3mc_config")]
private static extern int sqlite3mc_config(IntPtr db, [MarshalAs(UnmanagedType.LPStr)] string paramName, int newValue);
// Key: provide both byte[] and IntPtr signatures (use whichever convenient)
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_key")]
private static extern int sqlite3_key_bytes(IntPtr db, byte[] pKey, int nKey);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_key")]
private static extern int sqlite3_key_ptr(IntPtr db, IntPtr pKey, int nKey);
// Prepare/step/finalize
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_prepare_v2")]
private static extern int sqlite3_prepare_v2(IntPtr db, [MarshalAs(UnmanagedType.LPStr)] string zSql, int nByte, out IntPtr ppStmt, IntPtr pzTail);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_step")]
private static extern int sqlite3_step(IntPtr stmt);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_finalize")]
private static extern int sqlite3_finalize(IntPtr stmt);
// Column accessors
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_column_text")]
private static extern IntPtr sqlite3_column_text(IntPtr stmt, int iCol);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_column_blob")]
private static extern IntPtr sqlite3_column_blob(IntPtr stmt, int iCol);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_column_bytes")]
private static extern int sqlite3_column_bytes(IntPtr stmt, int iCol);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_column_int")]
private static extern int sqlite3_column_int(IntPtr stmt, int iCol);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_column_int64")]
private static extern long sqlite3_column_int64(IntPtr stmt, int iCol);
// Binders
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_bind_text")]
private static extern int sqlite3_bind_text(IntPtr stmt, int idx, byte[] text, int n, IntPtr destructor);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_bind_blob")]
private static extern int sqlite3_bind_blob(IntPtr stmt, int idx, byte[] blob, int n, IntPtr destructor);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_bind_int")]
private static extern int sqlite3_bind_int(IntPtr stmt, int idx, int val);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_bind_int64")]
private static extern int sqlite3_bind_int64(IntPtr stmt, int idx, long val);
// Backup API
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_backup_init")]
private static extern IntPtr sqlite3_backup_init(IntPtr pDest, [MarshalAs(UnmanagedType.LPStr)] string zDestName, IntPtr pSource, [MarshalAs(UnmanagedType.LPStr)] string zSourceName);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_backup_step")]
private static extern int sqlite3_backup_step(IntPtr backup, int nPage);
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_backup_finish")]
private static extern int sqlite3_backup_finish(IntPtr backup);
// ---- Helper: convert native C string (UTF-8) to C# string ----
private static string PtrToStringUTF8(IntPtr ptr)
{
if (ptr == IntPtr.Zero) return null;
// find length
int len = 0;
while (Marshal.ReadByte(ptr, len) != 0) len++;
if (len == 0) return string.Empty;
byte[] buffer = new byte[len];
Marshal.Copy(ptr, buffer, 0, len);
return Encoding.UTF8.GetString(buffer);
}
// Get error message for DB handle
public static string GetErrMsg(IntPtr db)
{
IntPtr p = sqlite3_errmsg_ptr(db);
return PtrToStringUTF8(p) ?? $"(null errmsg, db={db})";
}
// ---- High-level wrappers ----
/// <summary>Open a database. Throws Exception on failure. Flags default to read-write | create.</summary>
public static IntPtr Open(string path, int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)
{
int rc = sqlite3_open_v2(path, out IntPtr db, flags, IntPtr.Zero);
if (rc != SQLITE_OK || db == IntPtr.Zero)
{
string em = db != IntPtr.Zero ? GetErrMsg(db) : "(no db handle)";
throw new InvalidOperationException($"sqlite3_open_v2('{path}') failed rc={rc} errmsg={em}");
}
return db;
}
/// <summary>Close DB. Returns rc.</summary>
public static int Close(IntPtr db)
{
if (db == IntPtr.Zero) return SQLITE_OK;
return sqlite3_close(db);
}
/// <summary>Execute SQL (no binder). Return code; if errorMsg != null it contains message (freed automatically).</summary>
public static int Exec(IntPtr db, string sql, out string errorMsg)
{
errorMsg = null;
int rc = sqlite3_exec(db, sql, IntPtr.Zero, IntPtr.Zero, out IntPtr errPtr);
if (errPtr != IntPtr.Zero)
{
errorMsg = PtrToStringUTF8(errPtr);
// free the error string allocated by sqlite3_exec
sqlite3_free(errPtr);
}
return rc;
}
/// <summary>Set sqlite3mc config parameter (plugin). e.g. sqlite3mc_config(db, "cipher", index).</summary>
public static int MC_Config(IntPtr db, string paramName, int newValue)
{
return sqlite3mc_config(db, paramName, newValue);
}
/// <summary>Set key from raw bytes.</summary>
public static int Key_SetBytes(IntPtr db, byte[] keyBytes)
{
if (keyBytes == null) return sqlite3_key_bytes(db, null, 0);
return sqlite3_key_bytes(db, keyBytes, keyBytes.Length);
}
/// <summary>Set key from pointer (unsafe). Rarely needed.</summary>
public static int Key_SetPtr(IntPtr db, IntPtr pKey, int len)
{
return sqlite3_key_ptr(db, pKey, len);
}
/// <summary>Validate whether DB can be read (try SELECT on sqlite_master).</summary>
public static bool ValidateReadable(IntPtr db, out string errmsg)
{
int rc = Exec(db, "SELECT name FROM sqlite_master LIMIT 1;", out errmsg);
return rc == SQLITE_OK;
}
// Prepare / Step utility: simple single-row single-column string fetch
public static string QuerySingleString(IntPtr db, string sql)
{
int rc = sqlite3_prepare_v2(db, sql, -1, out IntPtr stmt, IntPtr.Zero);
if (rc != SQLITE_OK) throw new InvalidOperationException($"prepare failed rc={rc} errmsg={GetErrMsg(db)} sql={sql}");
try
{
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW)
{
IntPtr txt = sqlite3_column_text(stmt, 0);
return PtrToStringUTF8(txt);
}
else if (rc == SQLITE_DONE)
{
return null;
}
else
{
throw new InvalidOperationException($"step failed rc={rc} errmsg={GetErrMsg(db)}");
}
}
finally
{
sqlite3_finalize(stmt);
}
}
// Helper: prepare/step loop with callback
public delegate void RowCallback(IntPtr stmt);
public static void ForEachRow(string sql, IntPtr db, RowCallback rowCallback)
{
int rc = sqlite3_prepare_v2(db, sql, -1, out IntPtr stmt, IntPtr.Zero);
if (rc != SQLITE_OK)
throw new InvalidOperationException($"prepare failed rc={rc} errmsg={GetErrMsg(db)} sql={sql}");
try
{
while (true)
{
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW)
{
rowCallback(stmt);
}
else if (rc == SQLITE_DONE)
{
break;
}
else
{
throw new InvalidOperationException($"step failed rc={rc} errmsg={GetErrMsg(db)}");
}
}
}
finally
{
sqlite3_finalize(stmt);
}
}
// Column getters (UTF-8 text or blob)
public static string ColumnText(IntPtr stmt, int col)
{
IntPtr p = sqlite3_column_text(stmt, col);
return PtrToStringUTF8(p);
}
public static byte[] ColumnBlob(IntPtr stmt, int col)
{
IntPtr p = sqlite3_column_blob(stmt, col);
if (p == IntPtr.Zero) return null;
int len = sqlite3_column_bytes(stmt, col);
if (len <= 0) return new byte[0];
byte[] buf = new byte[len];
Marshal.Copy(p, buf, 0, len);
return buf;
}
public static int ColumnInt(IntPtr stmt, int col) => sqlite3_column_int(stmt, col);
public static long ColumnInt64(IntPtr stmt, int col) => sqlite3_column_int64(stmt, col);
// Bind helpers (use SQLITE_TRANSIENT to let sqlite copy data)
private static readonly IntPtr SQLITE_TRANSIENT = new IntPtr(-1);
public static int BindText(IntPtr stmt, int idx, string text)
{
if (text == null) return sqlite3_bind_text(stmt, idx, null, 0, IntPtr.Zero);
byte[] utf8 = Encoding.UTF8.GetBytes(text);
return sqlite3_bind_text(stmt, idx, utf8, utf8.Length, SQLITE_TRANSIENT);
}
public static int BindBlob(IntPtr stmt, int idx, byte[] blob)
{
if (blob == null) return sqlite3_bind_blob(stmt, idx, null, 0, IntPtr.Zero);
return sqlite3_bind_blob(stmt, idx, blob, blob.Length, SQLITE_TRANSIENT);
}
public static int BindInt(IntPtr stmt, int idx, int v) => sqlite3_bind_int(stmt, idx, v);
public static int BindInt64(IntPtr stmt, int idx, long v) => sqlite3_bind_int64(stmt, idx, v);
// Backup: copy entire main DB from source to destination path (destination will be created/opened by function)
// Returns SQLITE_OK on success. Throws on fatal open errors.
public static int BackupToFile(IntPtr srcDb, string dstPath, int pagesPerStep = 5)
{
// Open destination DB (readwrite | create)
int rcOpen = sqlite3_open_v2(dstPath, out IntPtr dstDb, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, IntPtr.Zero);
if (rcOpen != SQLITE_OK)
{
throw new InvalidOperationException($"sqlite3_open_v2(dst) failed rc={rcOpen} errmsg={GetErrMsg(dstDb)}");
}
IntPtr backup = sqlite3_backup_init(dstDb, "main", srcDb, "main");
if (backup == IntPtr.Zero)
{
int rcClose = sqlite3_close(dstDb);
throw new InvalidOperationException($"sqlite3_backup_init returned NULL; errmsg(dst)={GetErrMsg(dstDb)} rcClose={rcClose}");
}
int rc = SQLITE_OK;
try
{
while (true)
{
rc = sqlite3_backup_step(backup, pagesPerStep);
if (rc == SQLITE_DONE)
{
rc = sqlite3_backup_finish(backup);
break;
}
else if (rc == SQLITE_OK)
{
// continue
continue;
}
else
{
// error
rc = sqlite3_backup_finish(backup);
break;
}
}
}
finally
{
// ensure destination db closed
sqlite3_close(dstDb);
}
return rc;
}
// Simple convenience: try to open source with key (raw bytes) and then backup to dstPath (unencrypted).
// Throws exceptions on fatal errors; returns final sqlite rc from backup.
public static int DecryptDbToPlainFile(string srcPath, string dstPath, byte[] keyBytes, int cipherIndex = -1)
{
IntPtr srcDb = IntPtr.Zero;
try
{
srcDb = Open(srcPath, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
if (cipherIndex >= 0)
{
int cfg = MC_Config(srcDb, "cipher", cipherIndex);
// cfg may return various codes depending implementation; we don't treat non-zero as fatal here
}
int rcKey = Key_SetBytes(srcDb, keyBytes);
if (rcKey != SQLITE_OK)
{
throw new InvalidOperationException($"sqlite3_key returned rc={rcKey} errmsg={GetErrMsg(srcDb)}");
}
if (!ValidateReadable(srcDb, out string vErr))
{
throw new InvalidOperationException($"DB did not validate after key. errmsg={vErr}");
}
int rcBackup = BackupToFile(srcDb, dstPath);
return rcBackup;
}
finally
{
if (srcDb != IntPtr.Zero) sqlite3_close(srcDb);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 15e1a8619eefb3944b564456cbd5f403
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+2 -1
View File
@@ -181,7 +181,8 @@ public enum UmaFileType
challengematch,
collectevent,
ratingrace,
jobs
jobs,
manualdownloadatlas,
}
public enum TranslationTables
@@ -45,6 +45,11 @@ public class UmaDatabaseController
public List<DataRow> LiveData;
public List<DataRow> DressData;
//修改(载入通用服装ColorSet相关)
public List<DataRow> CharaDressColor;
/// <summary> Meta Database Connection </summary>
public SqliteConnection metaDb;
/// <summary> Master Database Connection </summary>
@@ -67,8 +72,25 @@ public class UmaDatabaseController
masterDb = new SqliteConnection($@"Data Source={Config.Instance.MainPath}/master/master.mdb;");
}
metaDb.Open();
MetaEntries = ReadMeta(metaDb);
try
{
metaDb.Open();
MetaEntries = ReadMeta(metaDb);
}
catch (Exception e)
{
try
{
var dbPath = $@"{Config.Instance.MainPath}/meta";
var key = GenFinalKey(Config.Instance.DBKey);
MetaEntries = ReadMetaFromEncryptedDb(dbPath, key, 3);
}
catch (Exception)
{
throw;
}
}
masterDb.Open();
CharaData = ReadCharaMaster(masterDb);
@@ -76,6 +98,12 @@ public class UmaDatabaseController
FaceTypeData = ReadFaceTypeData(masterDb);
LiveData = ReadAllLiveData(masterDb);
DressData = ReadAllDressData(masterDb);
//修改(载入通用服装ColorSet相关)
CharaDressColor = ReadAllCharaDressColor(masterDb);
#if UNITY_STANDALONE_WIN
if (!Config.Instance.RegionDetectionPassed)
{
@@ -100,16 +128,26 @@ public class UmaDatabaseController
}
else
{
msg += "\nPlease install the dmm game client";
msg += "\nPlease install the dmm game client or check the key settings.";
}
UmaViewerUI.Instance.ShowMessage(msg, UIMessageType.Error);
}
}
//修改(载入通用服装ColorSet相关)
static List<DataRow> ReadAllCharaDressColor(SqliteConnection conn)
{
return ReadMaster(conn, "SELECT * FROM chara_dress_color_set");
}
static Dictionary<string,UmaDatabaseEntry> ReadMeta(SqliteConnection conn)
{
SqliteCommand sqlite_cmd = conn.CreateCommand();
sqlite_cmd.CommandText = "SELECT m,n,h,d FROM a WHERE d IS NOT NULL";
sqlite_cmd.CommandText = "SELECT m,n,h,d FROM a WHERE d IS NOT NULL"; //filter out manifest entries, whitch have null prerequisites
SqliteDataReader sqlite_datareader = sqlite_cmd.ExecuteReader();
Dictionary<string, UmaDatabaseEntry> meta = new Dictionary<string, UmaDatabaseEntry>();
while (sqlite_datareader.Read())
@@ -143,8 +181,8 @@ public class UmaDatabaseController
}
}
catch(Exception e)
{
Debug.LogError($"Error caught while reading Entry : \n {e}");
{
Debug.LogError($"Error caught while reading Entry : \n {e}");
}
if (entry != null && !meta.ContainsKey(entry.Name)) {
@@ -155,6 +193,108 @@ public class UmaDatabaseController
return meta;
}
public static Dictionary<string, UmaDatabaseEntry> ReadMetaFromEncryptedDb(string dbPath, byte[] keyBytes, int cipherIndex = -1)
{
var meta = new Dictionary<string, UmaDatabaseEntry>(StringComparer.Ordinal);
IntPtr db = IntPtr.Zero;
try
{
db = Sqlite3MC.Open(dbPath);
if (cipherIndex >= 0)
{
try
{
int cfgRc = Sqlite3MC.MC_Config(db, "cipher", cipherIndex);
}
catch (Exception e)
{
Debug.LogError($"MC_Config thrown: {e}");
}
}
int rcKey = Sqlite3MC.Key_SetBytes(db, keyBytes);
if (rcKey != Sqlite3MC.SQLITE_OK)
{
string em = Sqlite3MC.GetErrMsg(db);
throw new InvalidOperationException($"sqlite3_key returned rc={rcKey}, errmsg={em}");
}
if (!Sqlite3MC.ValidateReadable(db, out string validateErr))
{
Debug.LogError($"DB validation after key failed: {validateErr}");
throw new InvalidOperationException("DB validation after key failed: " + validateErr);
}
string sql = "SELECT m,n,h,c,d,e FROM a";
Sqlite3MC.ForEachRow(sql, db, (stmt) =>
{
try
{
string m = Sqlite3MC.ColumnText(stmt, 0);
string n = Sqlite3MC.ColumnText(stmt, 1);
string h = Sqlite3MC.ColumnText(stmt, 2);
string c = Sqlite3MC.ColumnText(stmt, 3);
string d = Sqlite3MC.ColumnText(stmt, 4);
string e = Sqlite3MC.ColumnText(stmt, 5);
if (string.IsNullOrEmpty(m))
{
Debug.LogWarning("Skipping row: empty type string (m).");
return;
}
if (!Enum.TryParse<UmaFileType>(m, /*ignoreCase*/ false, out UmaFileType type))
{
Debug.LogWarning($"Unrecognized EntryType Enum Value :{m}");
return;
}
if (string.IsNullOrEmpty(n))
{
Debug.LogError($"Invalid entry name '{n}' or URL '{h}'. Skipping row.");
return;
}
var entry = new UmaDatabaseEntry
{
Type = type,
Name = n,
Url = h,
Checksum = c,
Prerequisites = d,
Key = e
};
if (!meta.ContainsKey(entry.Name))
{
meta.Add(entry.Name, entry);
}
}
catch (Exception exRow)
{
Debug.LogError("Error caught while reading row: " + exRow);
}
});
}
catch (Exception ex)
{
Debug.LogError("ReadMetaFromEncryptedDb failed: " + ex);
throw;
}
finally
{
if (db != IntPtr.Zero)
{
try { Sqlite3MC.Close(db); }
catch (Exception e) { Debug.LogError("Closing DB failed: " + e); }
}
}
return meta;
}
static void SetDefaultGameRegion(SqliteConnection conn)
{
var str = ReadMaster(conn, "SELECT text FROM text_data LIMIT 1");
@@ -307,4 +447,15 @@ public class UmaDatabaseController
GC.WaitForPendingFinalizers();
}
public byte[] GenFinalKey(byte[] key)
{
var baseKey = Config.Instance.DBBaseKey;
if (baseKey.Length < 13)
throw new Exception("Invalid Base Key length");
for (int i = 0; i < key.Length; i++)
{
key[i] = (byte)(key[i] ^ baseKey[i % 13]);
}
return key;
}
}
@@ -12,10 +12,41 @@ public class UmaDatabaseEntry
public string Url;
public string Checksum;
public string Prerequisites;
public string Key;
private byte[] fKey;
[NonSerialized]
public List<UmaDatabaseEntry> CachedPrerequisites = null;
public byte[] FKey
{
get
{
if (fKey == null && !string.IsNullOrEmpty(Key))
{
var baseKeys = Config.Instance.ABKey;
int baseLen = baseKeys.Length;
int keysLen = baseLen * 8;
byte[] keys = new byte[keysLen];
for (int i = 0; i < baseLen; ++i)
{
byte[] keyBytes = BitConverter.GetBytes(long.Parse(Key));
if (!BitConverter.IsLittleEndian)
Array.Reverse(keyBytes);
byte b = baseKeys[i];
int baseOffset = i << 3; // i * 8
for (int j = 0; j < 8; ++j)
{
keys[baseOffset + j] = (byte)(b ^ keyBytes[j]);
}
}
fKey = keys;
}
return fKey;
}
}
public string FilePath
{
get
+59 -15
View File
@@ -270,32 +270,57 @@ public class UmaViewerBuilder : MonoBehaviour
}
}
//修改(载入专用尾巴相关)
if (tailId > 0)
{
string tailName = $"tail{tailId.ToString().PadLeft(4, '0')}_00";
string tailPath = $"3d/chara/tail/{tailName}/";
string tailPfb = tailPath + $"pfb_{tailName}";
asset = null;
Main.AbList.TryGetValue(tailPfb, out asset);
if (asset != null)
string costumePrefixForTail = $"{id}_{costumeId}";
string exclusiveTailName = $"tail{costumePrefixForTail}";
string exclusiveTailPath = $"3d/chara/tail/{exclusiveTailName}/";
string exclusiveTailPfb = $"{exclusiveTailPath}pfb_{exclusiveTailName}";
if (Main.AbList.TryGetValue(exclusiveTailPfb, out asset))
{
foreach (var asset2 in UmaViewerMain.Instance.AbChara.Where(a => a.Name.StartsWith($"{tailPath}textures/tex_{tailName}_{head_id}") || a.Name.StartsWith($"{tailPath}textures/tex_{tailName}_0000")))
umaContainer.LoadExclusiveTail(asset);
string clothPath = $"{exclusiveTailPath}clothes/pfb_{exclusiveTailName}_cloth00";
if (Main.AbList.TryGetValue(clothPath, out var clothAsset))
{
umaContainer.LoadTextures(asset2);
umaContainer.LoadPhysics(clothAsset);
}
umaContainer.LoadTail(asset);
//Load Physics
var asset1 = Main.AbList[$"{tailPath}clothes/pfb_{tailName}_cloth00"];
umaContainer.LoadPhysics(asset1);
}
else
{
Debug.Log("no tail");
string tailName = $"tail{tailId.ToString().PadLeft(4, '0')}_00";
string tailPath = $"3d/chara/tail/{tailName}/";
string tailPfb = $"{tailPath}pfb_{tailName}";
asset = null;
if (Main.AbList.TryGetValue(tailPfb, out asset) && asset != null)
{
foreach (var asset2 in UmaViewerMain.Instance.AbChara.Where(a => a.Name.StartsWith($"{tailPath}textures/tex_{tailName}_{head_id}") || a.Name.StartsWith($"{tailPath}textures/tex_{tailName}_0000")))
{
umaContainer.LoadTextures(asset2);
}
umaContainer.LoadTail(asset);
string clothPath = $"{tailPath}clothes/pfb_{tailName}_cloth00";
if (Main.AbList.TryGetValue(clothPath, out var clothAsset))
{
umaContainer.LoadPhysics(clothAsset);
}
}
else
{
Debug.Log("no tail");
}
}
}
umaContainer.LoadPhysics();
umaContainer.SetDynamicBoneEnable(ModelSettings.DynamicBoneEnable);
umaContainer.LoadFaceMorph(id, costumeId);
@@ -316,6 +341,13 @@ public class UmaViewerBuilder : MonoBehaviour
umaContainer.LoadAnimation(entry);
}
}
//修改(载入通用服装ColorSet相关)
UI.ClearColorSetButtons();
UI.LoadColorSetButtons();
}
private void LoadMobUma(UmaContainerCharacter umaContainer, CharaEntry chara, string costumeId, int bodyid = -1, bool loadMotion = false)
@@ -539,6 +571,12 @@ public class UmaViewerBuilder : MonoBehaviour
umaContainer.LoadAnimation(entry);
}
}
//修改(载入通用服装ColorSet相关)
UI.ClearColorSetButtons();
}
private void LoadMiniUma(UmaContainerCharacter umaContainer, CharaEntry chara, string costumeId)
@@ -678,6 +716,12 @@ public class UmaViewerBuilder : MonoBehaviour
{
umaContainer.LoadAnimation(asset);
}
//修改(载入通用服装ColorSet相关)
UI.ClearColorSetButtons();
}
public void LoadProp(UmaDatabaseEntry entry)
+6
View File
@@ -59,6 +59,12 @@ public class UmaViewerMain : MonoBehaviour
var MobCharaData = UmaDatabaseController.Instance.MobCharaData;
var loadingUI = UmaSceneController.instance;
//修改(载入通用服装ColorSet相关)
var charaDressColor = UmaDatabaseController.Instance.CharaDressColor;
//EN Translations
loadingUI.LoadingProgressChange(loadingStep++, loadingStepsTotal, "Downloading Translations");
if (Config.Instance.Language == Language.En)
+221 -7
View File
@@ -9,6 +9,8 @@ using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Rendering;
using UnityEngine.UI;
//修改(载入通用服装ColorSet相关)(保存当前选中的颜色配置到TXT文件)
using System.Text;
public class UmaViewerUI : MonoBehaviour
{
@@ -46,6 +48,11 @@ public class UmaViewerUI : MonoBehaviour
public PoseManager PoseManager;
public HandleManager HandleManager;
//修改(载入通用服装ColorSet相关)
public ScrollRect DressColorSetList;
[Header("lists")]
public ScrollRect EmotionList;
public Transform FacialList;
@@ -102,6 +109,12 @@ public class UmaViewerUI : MonoBehaviour
public bool LiveTime = false;
public bool isRecordVMD;
public bool isRequireStage = true;
//修改(Live演出的显隐控制和位置变换相关)
public bool isControlMode = true;
public Dropdown LiveRecoedToggle;
public int LiveMode = 1;
@@ -372,6 +385,9 @@ public class UmaViewerUI : MonoBehaviour
action(UmaContainerSliderPrefab, target, targetMorph, OtherList);
}
//修改(Mini角色表情滑条修复)
public void LoadFacialPanelsMini(Material Eye, Material MayuL, Material MayuR, Material Mouth)
{
currentFaceDrivenKeyTarget = null;
@@ -387,24 +403,37 @@ public class UmaViewerUI : MonoBehaviour
container.Name = name;
container.Slider.wholeNumbers = true;
container.Slider.value = 0;
container.Slider.maxValue = 15;
if (type == 0)
container.Slider.maxValue = 18;
else if (type == 1)
container.Slider.maxValue = 14;
else if (type == 2)
container.Slider.maxValue = 14;
else if (type == 3)
container.Slider.maxValue = 8;
container.Slider.minValue = 0;
container.Slider.onValueChanged.AddListener((a) =>
{
var val = Convert.ToInt32(a);
if (type == 0)
{
mat.mainTextureOffset = new Vector2(0.25f * (val % 4), -0.25f * (val / 4));
mat.mainTextureOffset = new Vector2(0.25f * (val % 4), -0.125f * (val / 4));
}
else if (type == 1)
{
var vector = mat.GetVector("_UVOffset");
mat.SetVector("_UVOffset", new Vector4(0.25f * (val % 4), -0.25f * (val / 4), vector.z, vector.w));
mat.SetVector("_UVOffset", new Vector4(0.25f * (val % 4), -0.125f * (val / 4), vector.z, vector.w));
}
else if (type == 2)
{
var vector = mat.GetVector("_UVOffset");
mat.SetVector("_UVOffset", new Vector4(vector.x, vector.y, 0.25f * (val % 4), -0.25f * (val / 4)));
mat.SetVector("_UVOffset", new Vector4(vector.x, vector.y, 0.25f * (val % 4), -0.125f * (val / 4)));
}
else if (type == 3)
{
mat.mainTextureOffset = new Vector2(0.25f * (val % 4), -0.25f * (val / 4));
}
});
});
@@ -412,10 +441,12 @@ public class UmaViewerUI : MonoBehaviour
action(EyeList, "Eye_L_Select", Eye, 2);
action(EyeList, "Eye_R_Select", Eye, 1);
action(MouthList, "Mouth_Select", Mouth, 0);
action(EyeBrowList, "Mayu_L_Select", MayuL, 0);
action(EyeBrowList, "Mayu_R_Select", MayuR, 0);
action(EyeBrowList, "Mayu_L_Select", MayuL, 3);
action(EyeBrowList, "Mayu_R_Select", MayuR, 3);
}
public void UpdateFacialPanels()
{
if (currentFaceDrivenKeyTarget)
@@ -1113,6 +1144,12 @@ public class UmaViewerUI : MonoBehaviour
public void SetLiveRequireStage(bool val) { isRequireStage = val; }
//修改(Live演出的显隐控制和位置变换相关)
public void SetLiveControlMode(bool val) { isControlMode = val; }
public void ShowMessage(string msg, UIMessageType type)
{
if (!MessageText) return;
@@ -1133,4 +1170,181 @@ public class UmaViewerUI : MonoBehaviour
{
go.SetActive(!go.activeSelf);
}
}
//修改(载入通用服装ColorSet相关)(颜色组UI)
public static string CurrentSelectedColorSetId;
public void LoadColorSetButtons()
{
ClearColorSetButtons();
var uma = Builder.CurrentUMAContainer;
if (uma == null || string.IsNullOrEmpty(uma.VarCostumeIdShort)) return;
var parts = uma.VarCostumeIdShort.Split('_');
if (parts.Length < 2) return;
if (!int.TryParse(parts[0], out int bodyType)) return;
if (!int.TryParse(parts[1], out int bodyTypeSub)) return;
if (bodyType == 7 || bodyType == 8)
{
var colorSetIds = UmaDatabaseController.Instance.CharaDressColor
.Select(row => row["id"].ToString());
foreach (var id in colorSetIds)
{
var buttonObj = Instantiate(UmaContainerPrefab, DressColorSetList.content);
var container = buttonObj.GetComponent<UmaUIContainer>();
container.Name = id;
var buttonText = buttonObj.GetComponentInChildren<Text>();
if (buttonText != null) buttonText.text = id;
container.Button.onClick.AddListener(() =>
{
HighlightChildImage(DressColorSetList.content, container);
CurrentSelectedColorSetId = id;
Builder.CurrentUMAContainer?.RefreshDressColor();
});
if (container.ToggleImage != null)
container.ToggleImage.enabled = (id == CurrentSelectedColorSetId);
}
return;
}
var dressRow = UmaDatabaseController.Instance.DressData.FirstOrDefault(row =>
int.Parse(row["body_type"].ToString()) == bodyType &&
int.Parse(row["body_type_sub"].ToString()) == bodyTypeSub);
if (dressRow == null || dressRow["body_setting"].ToString() != "4") return;
string dressId = dressRow["id"].ToString();
var colorSetRows = UmaDatabaseController.Instance.CharaDressColor
.Where(row => row["dress_id"].ToString() == dressId);
foreach (var row in colorSetRows)
{
string id = row["id"].ToString();
var buttonObj = Instantiate(UmaContainerPrefab, DressColorSetList.content);
var container = buttonObj.GetComponent<UmaUIContainer>();
container.Name = id;
var buttonText = buttonObj.GetComponentInChildren<Text>();
if (buttonText != null) buttonText.text = id;
container.Button.onClick.AddListener(() =>
{
HighlightChildImage(DressColorSetList.content, container);
CurrentSelectedColorSetId = id;
Builder.CurrentUMAContainer?.RefreshDressColor();
});
if (container.ToggleImage != null)
container.ToggleImage.enabled = (id == CurrentSelectedColorSetId);
}
}
//修改(载入通用服装ColorSet相关)(清理所有颜色组按钮)
public void ClearColorSetButtons()
{
foreach (Transform child in DressColorSetList.content)
{
Destroy(child.gameObject);
}
}
//修改(载入通用服装ColorSet相关)(解析颜色字符串)
private static bool TryParseHexColor(
string hex,
out Tuple<byte, byte, byte> rgb255,
out Tuple<float, float, float> rgbNormalized)
{
rgb255 = null;
rgbNormalized = null;
if (string.IsNullOrWhiteSpace(hex) || !hex.StartsWith("#") || hex.Length != 7)
return false;
byte r, g, b;
bool okR = byte.TryParse(hex.Substring(1, 2), System.Globalization.NumberStyles.HexNumber, null, out r);
bool okG = byte.TryParse(hex.Substring(3, 2), System.Globalization.NumberStyles.HexNumber, null, out g);
bool okB = byte.TryParse(hex.Substring(5, 2), System.Globalization.NumberStyles.HexNumber, null, out b);
if (!okR || !okG || !okB)
return false;
rgb255 = Tuple.Create(r, g, b);
rgbNormalized = Tuple.Create(r / 255f, g / 255f, b / 255f);
return true;
}
//修改(载入通用服装ColorSet相关)(保存当前选中的颜色配置到TXT文件)
public static void SaveCurrentColorSetRow()
{
if (string.IsNullOrEmpty(CurrentSelectedColorSetId))
{
UmaViewerUI.Instance.ShowMessage("No color set ID selected!", UIMessageType.Error);
return;
}
var charaColor = UmaDatabaseController.Instance.CharaDressColor
.FirstOrDefault(row => row["id"].ToString() == CurrentSelectedColorSetId);
if (charaColor == null)
{
UmaViewerUI.Instance.ShowMessage($"Color set ID {CurrentSelectedColorSetId} not found!", UIMessageType.Error);
return;
}
#if UNITY_ANDROID && !UNITY_EDITOR
string fileDirectory = Application.persistentDataPath + "/../ColorCode/";
#else
string fileDirectory = Application.dataPath + "/../ColorCode/";
#endif
Directory.CreateDirectory(fileDirectory);
string idValue = charaColor["id"]?.ToString() ?? "unknown";
string fileName = $"{idValue}.txt";
string fullPath = Path.GetFullPath(Path.Combine(fileDirectory, fileName));
List<string> lines = new List<string>();
foreach (var column in charaColor.Table.Columns)
{
string columnName = column.ToString();
string value = charaColor[columnName]?.ToString() ?? "";
if (TryParseHexColor(value, out var rgb255, out var rgbNormalized))
{
string prefix = (columnName + ":").PadRight(17);
lines.Add(
$"{prefix}\t{value}\t" +
$"({rgb255.Item1,3}, {rgb255.Item2,3}, {rgb255.Item3,3})\t" +
$"({rgbNormalized.Item1:0.####}, {rgbNormalized.Item2:0.####}, {rgbNormalized.Item3:0.####})"
);
}
else
{
lines.Add($"{columnName}: {value}");
}
}
string content = string.Join("\n", lines);
File.WriteAllText(fullPath, content, Encoding.UTF8);
UmaViewerUI.Instance.ShowMessage($"ColorSet data saved: {fullPath}", UIMessageType.Success);
}
}
@@ -24,6 +24,7 @@ namespace Gallop
public bool IsEnableHipMoveParam;
public float HipMoveInfluenceDistance;
public float HipMoveInfluenceMaxDistance;
public bool UseCorrectScaleCalc;
public List<DynamicBone> DynamicBones = new List<DynamicBone>();
@@ -43,6 +44,39 @@ namespace Gallop
child.transform.localScale = Vector3.one;
colliders.Add(child.name, child.transform);
//修改(动骨与碰撞相关)
if (collider._collisionName == "Col_B_Hip_Tail")
{
collider._radius *= 0.96f;
}
else if (collider._collisionName == "Col_B_Chest_Tail")
{
collider._radius *= 1.14f;
}
else if (collider._collisionName.Contains("Col_B_Hip_Skirt"))
{
collider._radius *= 0.8f;
}
else if (collider._collisionName.Contains("Col_B_Hip_Jacket"))
{
collider._radius *= 1f;
}
else if (collider._collisionName == "Col_Elbow_R_Hair")
{
collider._radius *= 0.3f;
}
else if (collider._collisionName == "Col_Elbow_L_Hair")
{
collider._radius *= 0.3f;
}
else
{
collider._radius *= 0.9f;
}
switch (collider._type)
{
case CySpringCollisionData.CollisionType.Capsule:
@@ -77,72 +111,117 @@ namespace Gallop
return colliders;
}
//修改(动骨与碰撞相关)
public void InitializePhysics(Dictionary<string, Transform> bones, Dictionary<string, Transform> colliders)
{
DynamicBones.Clear();
// 获取对象名称并判断类型
string nameLower = gameObject.name.ToLower();
bool isTailObject = nameLower.Contains("tail"); // 判断是否为尾巴
bool isClothObject = !isTailObject && nameLower.Contains("chr"); // 判断是否为头发,排除尾巴避免重复匹配
foreach (CySpringParamDataElement spring in springParam)
{
if (bones.TryGetValue(spring._boneName,out Transform bone))
if (!bones.TryGetValue(spring._boneName, out Transform bone)) continue;
var dynamic = bone.gameObject.AddComponent<DynamicBone>();
dynamic.m_Root = bone;
// 设置重力
dynamic.m_Gravity = new Vector3(0, Mathf.Clamp01(-30f / spring._gravity), 0);
dynamic.m_LimitAngel_Min = spring._limitAngleMin;
dynamic.m_LimitAngel_Max = spring._limitAngleMax;
// 主参数设置
if (isTailObject)
{
bool isTail = gameObject.name.Contains("tail");//The tail needs less traction
var dynamic = bone.gameObject.AddComponent<DynamicBone>();
dynamic.m_Root = bone;
dynamic.m_Gravity = new Vector3(0, Mathf.Clamp01(-30 / spring._gravity), 0);
dynamic.m_LimitAngel_Min = spring._limitAngleMin;
dynamic.m_LimitAngel_Max = spring._limitAngleMax;
if (isTail)
// 尾巴主骨骼
dynamic.m_Damping = 0.05f;
dynamic.m_Stiffness = Mathf.Clamp01(20f / spring._stiffnessForce);
dynamic.m_Elasticity = Mathf.Clamp01(20f / spring._dragForce);
dynamic.m_Radius = spring._collisionRadius * 0.9f;
dynamic.m_Inert = spring.MoveSpringApplyRate / 4f;
}
else if (isClothObject)
{
// 头发主骨骼
dynamic.m_Damping = 0.1f;
dynamic.m_Stiffness = Mathf.Clamp01(15f / spring._stiffnessForce);
dynamic.m_Elasticity = Mathf.Clamp01(20f / spring._dragForce);
dynamic.m_Radius = spring._collisionRadius * 0.9f;
dynamic.m_Inert = spring.MoveSpringApplyRate / 2.5f;
}
else
{
// 衣服主骨骼
dynamic.m_Damping = 0.1f;
dynamic.m_Stiffness = Mathf.Clamp01(20f / spring._stiffnessForce);
dynamic.m_Elasticity = Mathf.Clamp01(30f / spring._dragForce);
dynamic.m_Radius = spring._collisionRadius * 0.8f;
dynamic.m_Inert = spring.MoveSpringApplyRate / 3f;
}
dynamic.SetupParticles();
DynamicBones.Add(dynamic);
// 主碰撞器添加
foreach (string collisionName in spring._collisionNameList)
{
if (colliders.TryGetValue(collisionName, out Transform tmp))
{
dynamic.m_Damping = 0.1f;
dynamic.m_Stiffness = Mathf.Clamp01(10 / spring._stiffnessForce);
dynamic.m_Elasticity = Mathf.Clamp01(10 / spring._dragForce);
dynamic.Particles[0].m_Colliders.Add(tmp.GetComponent<DynamicBoneColliderBase>());
}
}
// 子参数设置
foreach (var child in spring._childElements)
{
var tempParticle = dynamic.Particles.Find(p => p.m_Transform.gameObject.name == child._boneName);
if (tempParticle == null) continue;
if (isTailObject)
{
// 尾巴子粒子
tempParticle.m_Damping = 0.05f;
tempParticle.m_Stiffness = Mathf.Clamp01(20f / child._stiffnessForce);
tempParticle.m_Elasticity = Mathf.Clamp01(20f / child._dragForce);
tempParticle.m_Radius = child._collisionRadius * 1f;
tempParticle.m_Inert = child.MoveSpringApplyRate / 4f;
}
else if (isClothObject)
{
// 头发子粒子
tempParticle.m_Damping = 0.1f;
tempParticle.m_Stiffness = Mathf.Clamp01(15f / child._stiffnessForce);
tempParticle.m_Elasticity = Mathf.Clamp01(20f / child._dragForce);
tempParticle.m_Radius = child._collisionRadius * 1f;
tempParticle.m_Inert = child.MoveSpringApplyRate / 2.5f;
}
else
{
dynamic.m_Damping = 0.2f;
dynamic.m_Stiffness = Mathf.Clamp01(45 / spring._stiffnessForce);
dynamic.m_Elasticity = Mathf.Clamp01(45 / spring._dragForce);
// 衣服子粒子
tempParticle.m_Damping = 0.1f;
tempParticle.m_Stiffness = Mathf.Clamp01(20f / child._stiffnessForce);
tempParticle.m_Elasticity = Mathf.Clamp01(30f / child._dragForce);
tempParticle.m_Radius = child._collisionRadius * 0.8f;
tempParticle.m_Inert = child.MoveSpringApplyRate / 2f;
}
dynamic.m_Radius = spring._collisionRadius;
dynamic.m_Inert = spring.MoveSpringApplyRate / 3;
dynamic.SetupParticles();
DynamicBones.Add(dynamic);
foreach (string collisionName in spring._collisionNameList)
tempParticle.m_LimitAngel_Min = child._limitAngleMin;
tempParticle.m_LimitAngel_Max = child._limitAngleMax;
// 子碰撞器添加
foreach (string collisionName in child._collisionNameList)
{
if (colliders.TryGetValue(collisionName,out Transform tmp))
if (colliders.TryGetValue(collisionName, out Transform tmp))
{
dynamic.Particles[0].m_Colliders.Add(tmp.GetComponent<DynamicBoneColliderBase>());
}
}
foreach (CySpringParamDataChildElement Childcollision in spring._childElements)
{
var tempParticle = dynamic.Particles.Find(a => { return a.m_Transform.gameObject.name == Childcollision._boneName; });
if (tempParticle != null)
{
if (isTail)
var collider = tmp.GetComponent<DynamicBoneColliderBase>();
if (collider != null)
{
tempParticle.m_Damping = 0.1f;
tempParticle.m_Stiffness = Mathf.Clamp01(10 / Childcollision._stiffnessForce);
tempParticle.m_Elasticity = Mathf.Clamp01(10 / Childcollision._dragForce);
}
else
{
tempParticle.m_Damping = 0.2f;
tempParticle.m_Stiffness = Mathf.Clamp01(45 / Childcollision._stiffnessForce);
tempParticle.m_Elasticity = Mathf.Clamp01(45 / Childcollision._dragForce);
}
tempParticle.m_Inert = Childcollision.MoveSpringApplyRate / 3;
tempParticle.m_Radius = Childcollision._collisionRadius;
tempParticle.m_LimitAngel_Min = Childcollision._limitAngleMin;
tempParticle.m_LimitAngel_Max = Childcollision._limitAngleMax;
foreach (string collisionName in Childcollision._collisionNameList)
{
if (colliders.TryGetValue(collisionName,out Transform tmp))
{
tempParticle.m_Colliders.Add(tmp.GetComponent<DynamicBoneColliderBase>());
}
tempParticle.m_Colliders.Add(collider);
}
}
}
@@ -150,6 +229,8 @@ namespace Gallop
}
}
public void EnablePhysics(bool isOn)
{
foreach(DynamicBone dynamic in DynamicBones)
@@ -616,72 +616,101 @@ namespace Gallop.Live.Cutt
return LiveTimelineEasing.GetValue(liveTimelineKeyWithInterpolate2.easingType, curFrame - (float)from.frame, 0f, 1f, (float)num);
}
//修改(Live演出的显隐控制和位置变换相关)
public void LateUpdateFormationOffset_Transform(int targetIndex, LiveTimelineKeyIndex curKeyIndex, float time)
{
bool ControlMode = UmaViewerUI.Instance != null && UmaViewerUI.Instance.isControlMode;
LiveTimelineKeyFormationOffsetData curKey = curKeyIndex.key as LiveTimelineKeyFormationOffsetData;
LiveTimelineKeyFormationOffsetData nextKey = curKeyIndex.nextKey as LiveTimelineKeyFormationOffsetData;
var chara = Director.instance.CharaContainerScript[targetIndex];
if (!chara) return;
if(chara.LiveVisible != curKey.visible)
{
chara.Materials.ForEach(m =>
{
foreach (var key in m.Renderers.Keys)
{
if (key.gameObject.activeSelf != curKey.visible)
key.gameObject.SetActive(curKey.visible);
}
});
chara.LiveVisible = curKey.visible;
}
if (curKey.visible || IsRecordVMD)
if (ControlMode)
{
if (!string.IsNullOrEmpty(curKey.ParentObjectName))
if (chara.LiveVisible != curKey.visible)
{
var parent_transform = curKey.GetParentObjectTransform(this);
if (parent_transform)
chara.Materials.ForEach(m =>
{
if (chara.transform.parent != parent_transform)
foreach (var key in m.Renderers.Keys)
{
if (key.gameObject.activeSelf != curKey.visible)
key.gameObject.SetActive(curKey.visible);
}
});
chara.LiveVisible = curKey.visible;
}
if (curKey.visible || IsRecordVMD)
{
if (!string.IsNullOrEmpty(curKey.ParentObjectName))
{
var parent_transform = curKey.GetParentObjectTransform(this);
if (parent_transform)
{
if (chara.transform.parent != parent_transform)
{
chara.transform.SetParent(parent_transform);
}
}
}
else if (chara.transform.parent)
{
chara.transform.SetParent(null);
}
if (nextKey != null && nextKey.interpolateType != LiveCameraInterpolateType.None)
{
float ratio = CalculateInterpolationValue(curKey, nextKey, time * 60);
chara.transform.localPosition = Vector3.Lerp(curKey.Position, nextKey.Position, ratio);
var x = chara.transform.eulerAngles.x;
var z = chara.transform.eulerAngles.z;
chara.transform.eulerAngles = new Vector3(x, Mathf.Lerp(curKey.RotationY, nextKey.RotationY, ratio), z);
var local_x = chara.Position.localEulerAngles.x;
var local_z = chara.Position.localEulerAngles.z;
chara.Position.localEulerAngles = new Vector3(local_x, Mathf.Lerp(curKey.LocalRotationY, nextKey.LocalRotationY, ratio), local_z);
}
else
{
chara.transform.localPosition = curKey.Position;
var x = chara.transform.eulerAngles.x;
var z = chara.transform.eulerAngles.z;
chara.transform.eulerAngles = new Vector3(x, curKey.RotationY, z);
var local_x = chara.Position.localEulerAngles.x;
var local_z = chara.Position.localEulerAngles.z;
chara.Position.localEulerAngles = new Vector3(local_x, curKey.LocalRotationY, local_z);
}
}
}
else
{
if (curKey.visible || IsRecordVMD)
{
if (!string.IsNullOrEmpty(curKey.ParentObjectName))
{
var parent_transform = curKey.GetParentObjectTransform(this);
if (parent_transform && chara.transform.parent != parent_transform)
{
chara.transform.SetParent(parent_transform);
}
}
}
else if (chara.transform.parent)
{
chara.transform.SetParent(null);
}
if (nextKey != null && nextKey.interpolateType != LiveCameraInterpolateType.None)
{
float ratio = CalculateInterpolationValue(curKey, nextKey, time * 60);
chara.transform.localPosition = Vector3.Lerp(curKey.Position, nextKey.Position, ratio);
var x = chara.transform.eulerAngles.x;
var z = chara.transform.eulerAngles.z;
chara.transform.eulerAngles = new Vector3(x, Mathf.Lerp(curKey.RotationY, nextKey.RotationY, ratio), z);
var local_x = chara.Position.localEulerAngles.x;
var local_z = chara.Position.localEulerAngles.z;
chara.Position.localEulerAngles = new Vector3(local_x, Mathf.Lerp(curKey.LocalRotationY, nextKey.LocalRotationY, ratio), local_z);
}
else
{
chara.transform.localPosition = curKey.Position;
var x = chara.transform.eulerAngles.x;
var z = chara.transform.eulerAngles.z;
chara.transform.eulerAngles = new Vector3(x, curKey.RotationY, z);
var local_x = chara.Position.localEulerAngles.x;
var local_z = chara.Position.localEulerAngles.z;
chara.Position.localEulerAngles = new Vector3(local_x, curKey.LocalRotationY, local_z);
else if (chara.transform.parent)
{
chara.transform.SetParent(null);
}
}
}
}
public static void FindTimelineKeyCurrent(out LiveTimelineKey curKey, ILiveTimelineKeyDataList keys, float curFrame)
{
LiveTimelineKey nextKey;
@@ -18,6 +18,9 @@ public class CySpringCollisionData
[SerializeField]
public string _targetObjectName;
[SerializeField]
public bool _isOtherTarget;
[SerializeField]
public Vector3 _offset;
@@ -27,12 +30,17 @@ public class CySpringCollisionData
[SerializeField]
public float _radius;
[SerializeField]
public float _distance;
[SerializeField]
public Vector3 _normal;
[SerializeField]
public CollisionType _type;
public Transform _transform;
public float _distance;
public Vector3 _normal;
[SerializeField]
public bool _isInner;
public Transform _transform;
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 13dfe63a6961723469de3873728ddf8e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,21 @@
fileFormatVersion: 2
guid: ea8076455073e4d4d836f95d66cfeaca
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontNames:
- UMAMUSUME-RodinWanpaku Pro
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant: