Publish verified live restoration source snapshot and known limits
This commit is contained in:
@@ -0,0 +1 @@
|
||||
|
||||
+86
-80
@@ -1,100 +1,106 @@
|
||||
name: Build Windows Player and Android APK
|
||||
name: Build and release Windows IL2CPP
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: "Optional release tag, for example v1.0.0 (leave empty for build only)"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: unity-windows-il2cpp-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
buildForPlatform:
|
||||
name: build Player
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
buildName:
|
||||
- UmaViewer
|
||||
projectPath:
|
||||
- ./
|
||||
unityVersion:
|
||||
- 2022.3.56f1
|
||||
targetPlatform:
|
||||
- Android # Build an Android APK.
|
||||
- StandaloneWindows64 # Build a Windows 64-bit standalone
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
lfs: true
|
||||
build:
|
||||
name: Windows x64 IL2CPP
|
||||
# unity-builder@v4 uses a Docker-based Unity image, so it must run on Linux.
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 120
|
||||
|
||||
- uses: game-ci/unity-builder@v4
|
||||
steps:
|
||||
- name: Verify Unity license secrets
|
||||
shell: pwsh
|
||||
env:
|
||||
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
||||
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
|
||||
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
||||
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
||||
run: |
|
||||
$missing = @()
|
||||
if ([string]::IsNullOrWhiteSpace($env:UNITY_LICENSE) -and [string]::IsNullOrWhiteSpace($env:UNITY_SERIAL)) {
|
||||
$missing += "UNITY_LICENSE or UNITY_SERIAL"
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($env:UNITY_EMAIL)) { $missing += "UNITY_EMAIL" }
|
||||
if ([string]::IsNullOrWhiteSpace($env:UNITY_PASSWORD)) { $missing += "UNITY_PASSWORD" }
|
||||
if ($missing.Count -gt 0) {
|
||||
Write-Error "Missing repository secrets: $($missing -join ', '). See .github/UNITY_LICENSE_SETUP.md."
|
||||
exit 1
|
||||
}
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
lfs: true
|
||||
|
||||
- name: Cache Unity Library
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: Library
|
||||
key: Library-${{ runner.os }}-StandaloneWindows64-${{ hashFiles('Assets/**', 'Packages/**', 'ProjectSettings/**') }}
|
||||
restore-keys: |
|
||||
Library-${{ runner.os }}-StandaloneWindows64-
|
||||
|
||||
- name: Build Windows player with IL2CPP
|
||||
uses: game-ci/unity-builder@v4
|
||||
env:
|
||||
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
||||
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
|
||||
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
||||
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
||||
with:
|
||||
buildName: ${{ matrix.buildName }}
|
||||
projectPath: ${{ matrix.projectPath }}
|
||||
unityVersion: ${{ matrix.unityVersion }}
|
||||
targetPlatform: ${{ matrix.targetPlatform }}
|
||||
projectPath: .
|
||||
unityVersion: 2022.3.62f3
|
||||
targetPlatform: StandaloneWindows64
|
||||
buildName: UmaViewer
|
||||
buildsPath: build
|
||||
|
||||
- name: Zip Binary
|
||||
if: matrix.targetPlatform == 'StandaloneWindows64'
|
||||
run: zip -r UmaViewer_${{ matrix.targetPlatform }}.zip build/${{ matrix.targetPlatform }}/*
|
||||
- name: Package Windows build
|
||||
shell: pwsh
|
||||
run: >-
|
||||
Compress-Archive
|
||||
-Path "build/StandaloneWindows64/*"
|
||||
-DestinationPath "UmaViewer-Windows-x64.zip"
|
||||
-CompressionLevel Optimal
|
||||
|
||||
- name: Upload Artifact for Android
|
||||
if: matrix.targetPlatform == 'Android'
|
||||
- name: Upload workflow artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Build-${{ matrix.targetPlatform }}
|
||||
path: build/${{ matrix.targetPlatform }}/*
|
||||
name: UmaViewer-Windows-x64
|
||||
path: UmaViewer-Windows-x64.zip
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
compression-level: 0
|
||||
|
||||
- name: Upload Artifact for Windows
|
||||
if: matrix.targetPlatform == 'StandaloneWindows64'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Build-${{ matrix.targetPlatform }}
|
||||
path: UmaViewer_StandaloneWindows64.zip
|
||||
|
||||
createRelease:
|
||||
needs: buildForPlatform
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Download Artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: build/
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
- name: Publish GitHub Release
|
||||
if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && inputs.release_tag != '')
|
||||
shell: pwsh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
with:
|
||||
tag_name: AutoBuild_${{ github.event.head_commit.id }}
|
||||
release_name: UmaViewer_${{ github.event.head_commit.id }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Upload Windows Release Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./build/Build-StandaloneWindows64/UmaViewer_StandaloneWindows64.zip
|
||||
asset_name: UmaViewer.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Upload Android Release Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./build/Build-Android/UmaViewer.apk
|
||||
asset_name: UmaViewer_Unstable.apk
|
||||
asset_content_type: application/vnd.android.package-archive
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || github.ref_name }}
|
||||
run: |
|
||||
gh release create $env:RELEASE_TAG `
|
||||
"UmaViewer-Windows-x64.zip#UmaViewer Windows x64 (IL2CPP)" `
|
||||
--target $env:GITHUB_SHA `
|
||||
--title "UmaViewer $env:RELEASE_TAG" `
|
||||
--generate-notes
|
||||
|
||||
+71
-48
@@ -1,8 +1,4 @@
|
||||
# This .gitignore file should be placed at the root of your Unity project directory
|
||||
#
|
||||
# Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore
|
||||
#
|
||||
.utmp/
|
||||
# Unity generated directories
|
||||
/[Ll]ibrary/
|
||||
/[Tt]emp/
|
||||
/[Oo]bj/
|
||||
@@ -10,28 +6,17 @@
|
||||
/[Bb]uilds/
|
||||
/[Ll]ogs/
|
||||
/[Mm]emoryCaptures/
|
||||
/Assets/AmplifyShaderEditor/
|
||||
/Ignore/
|
||||
/Screenshots/
|
||||
/[Rr]ecordings/
|
||||
|
||||
# Local Unity/user state
|
||||
/[Uu]ser[Ss]ettings/
|
||||
*.log
|
||||
/.utmp/
|
||||
/.vs/
|
||||
/.vscode/
|
||||
/.idea/
|
||||
/.gradle/
|
||||
|
||||
# Asset meta data should only be ignored when the corresponding asset is also ignored
|
||||
!/[Aa]ssets/**/*.meta
|
||||
|
||||
# Uncomment this line if you wish to ignore the asset store tools plugin
|
||||
# /[Aa]ssets/AssetStoreTools*
|
||||
|
||||
# Autogenerated Jetbrains Rider plugin
|
||||
[Aa]ssets/Plugins/Editor/JetBrains*
|
||||
|
||||
# Visual Studio cache directory
|
||||
.vs/
|
||||
|
||||
# Gradle cache directory
|
||||
.gradle/
|
||||
|
||||
# Autogenerated VS/MD/Consulo solution and project files
|
||||
# Generated IDE files
|
||||
ExportedObj/
|
||||
.consulo/
|
||||
*.csproj
|
||||
@@ -49,38 +34,76 @@ ExportedObj/
|
||||
*.opendb
|
||||
*.VC.db
|
||||
|
||||
# Unity3D generated meta files
|
||||
# Unity generated metadata and crash files
|
||||
*.pidb.meta
|
||||
*.pdb.meta
|
||||
*.mdb.meta
|
||||
|
||||
# Unity3D generated file on crash reports
|
||||
sysinfo.txt
|
||||
|
||||
# Builds
|
||||
# Build packages
|
||||
*.apk
|
||||
*.aab
|
||||
*.unitypackage
|
||||
|
||||
# Crashlytics generated file
|
||||
crashlytics-build.properties
|
||||
|
||||
/Assets/Plugins/RootMotion/PuppetMaster/_Integration
|
||||
/VMDRecords
|
||||
Config.json
|
||||
/gltf
|
||||
/Assets/Plugins/RootMotion/FinalIK/_Integration
|
||||
/Poses
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# UmaViewer local/generated data
|
||||
/Assets/AmplifyShaderEditor/
|
||||
/Assets/AmplifyShaderEditor.meta
|
||||
/BuildDev
|
||||
/UserSettings/Layouts/default-2022.dwlt
|
||||
/UserSettings/EditorUserSettings.asset
|
||||
/Assets/Plugins/Editor/JetBrains*
|
||||
/Assets/Plugins/RootMotion/PuppetMaster/_Integration/
|
||||
/Assets/Plugins/RootMotion/FinalIK/_Integration/
|
||||
/Ignore/
|
||||
/Screenshots/
|
||||
/VMDRecords/
|
||||
/Poses/
|
||||
/gltf/
|
||||
/BuildDev/
|
||||
|
||||
# Unity auto-generated on open
|
||||
/Packages/packages-lock.json
|
||||
/ProjectSettings/ShaderGraphSettings.asset
|
||||
# Contains machine-specific game paths and preferences
|
||||
/Config.json
|
||||
|
||||
# Investigation/build scripts (not part of Unity project)
|
||||
umaviewer-effect-investigation.md
|
||||
umamusume.csproj
|
||||
/bin/
|
||||
/obj/
|
||||
# Local web viewer dependencies
|
||||
/web/pmx-viewer/node_modules/
|
||||
|
||||
# Local validation, extracted game data, and investigation outputs
|
||||
/tmp/
|
||||
/captures/
|
||||
/exports/
|
||||
/_pr_backup_*/
|
||||
**/__pycache__/
|
||||
*.pyc
|
||||
*.log
|
||||
*.dmp
|
||||
*.dump
|
||||
/tools/**/bin/
|
||||
/tools/**/obj/
|
||||
|
||||
# Game authorization payloads, native plugin, and extracted images
|
||||
/Assets/StreamingAssets/cri_auth*
|
||||
/Assets/StreamingAssets/sound_proj.acf*
|
||||
/Assets/Plugins/CySpringPlugin.dll*
|
||||
/Assets/Resources/text2D/still_*
|
||||
/Assets/Resources/text2D/bg_*
|
||||
/Assets/Resources/text2D/tex_env_*
|
||||
/Assets/Resources/UI/Common/utx_*
|
||||
/Assets/Resources/UI/Common/jukebox select square.png*
|
||||
/Assets/Resources/UI/Screenshot *.png*
|
||||
|
||||
# Shader bundles and encrypted/runtime extraction artifacts (not native .a libraries)
|
||||
shader.a
|
||||
shader_extracted.a
|
||||
global-metadata*.dat
|
||||
*.assetbundle
|
||||
*.dmp
|
||||
.env
|
||||
.env.*
|
||||
*.pem
|
||||
*.pfx
|
||||
*.p12
|
||||
|
||||
# Handwritten tool project; Unity-generated root IDE projects stay ignored
|
||||
!/tools/TargetMethodLookup/TargetMethodLookup.csproj
|
||||
|
||||
@@ -56,10 +56,10 @@ Shader "Custom/CyalumeSimpleOfficialLike"
|
||||
{
|
||||
fixed4 tex = tex2D(_MainTex, i.uv);
|
||||
|
||||
// 锟斤拷锟斤拷 discard锟斤拷锟斤拷锟斤拷 alpha clip
|
||||
// ���� discard������ alpha clip
|
||||
fixed3 rgb = tex.rgb * _Tint.rgb * _Intensity;
|
||||
|
||||
// additive 锟斤拷 alpha 锟斤拷锟斤拷锟斤拷锟斤拷要锟斤拷锟斤拷 1 锟斤拷锟斤拷
|
||||
// additive �� alpha ��������Ҫ���� 1 ����
|
||||
return fixed4(rgb, 1.0);
|
||||
}
|
||||
ENDCG
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 312ecc39ee9d48b4789a1d4f2dfcf36e
|
||||
guid: 9c22d58608b730b428538bc5e262ce60
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
public class DumpFaceShaderProperties : MonoBehaviour
|
||||
{
|
||||
void Start()
|
||||
{
|
||||
foreach (var r in GetComponentsInChildren<Renderer>(true))
|
||||
{
|
||||
foreach (var m in r.sharedMaterials)
|
||||
{
|
||||
if (!m || !m.shader) continue;
|
||||
|
||||
string rn = r.name.ToLower();
|
||||
string mn = m.name.ToLower();
|
||||
string sn = m.shader.name.ToLower();
|
||||
|
||||
if (!rn.Contains("face") && !mn.Contains("face") && !sn.Contains("face")
|
||||
&& !rn.Contains("head") && !mn.Contains("head"))
|
||||
continue;
|
||||
|
||||
Debug.Log($"==== Renderer: {r.name} ====");
|
||||
Debug.Log($"Material: {m.name}");
|
||||
Debug.Log($"Shader: {m.shader.name}");
|
||||
Debug.Log($"receiveShadows={r.receiveShadows}, shadowCastingMode={r.shadowCastingMode}");
|
||||
|
||||
var shader = m.shader;
|
||||
int count = shader.GetPropertyCount();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
string prop = shader.GetPropertyName(i);
|
||||
var type = shader.GetPropertyType(i);
|
||||
|
||||
string lower = prop.ToLower();
|
||||
bool important =
|
||||
lower.Contains("shadow") ||
|
||||
lower.Contains("light") ||
|
||||
lower.Contains("face") ||
|
||||
lower.Contains("head") ||
|
||||
lower.Contains("toon") ||
|
||||
lower.Contains("rim") ||
|
||||
lower.Contains("normal") ||
|
||||
lower.Contains("camera") ||
|
||||
lower.Contains("view");
|
||||
|
||||
if (!important) continue;
|
||||
|
||||
Debug.Log($"[{type}] {prop}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 577536d7b04577e4f97cd0c33c4c54d9
|
||||
guid: b990bda30f04d40a7b0a111835fddd08
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
public class DumpLoadedBundleAssets : MonoBehaviour
|
||||
{
|
||||
[TextArea]
|
||||
public string[] keywords = new string[]
|
||||
{
|
||||
"pfb_env_live10108_blinklight_wash_roof_a_circle",
|
||||
"pfb_env_live10108_blinklight_wash_wall_a_circle",
|
||||
"anm_env_live10108_blinklight_wash_wall_a_circle",
|
||||
"Take 001",
|
||||
"wash_roof",
|
||||
"wash_wall",
|
||||
"live10108"
|
||||
};
|
||||
|
||||
[ContextMenu("Dump Loaded AssetBundles")]
|
||||
public void DumpLoadedAssetBundles()
|
||||
{
|
||||
int bundleCount = 0;
|
||||
int hitCount = 0;
|
||||
|
||||
foreach (var bundle in AssetBundle.GetAllLoadedAssetBundles())
|
||||
{
|
||||
if (bundle == null) continue;
|
||||
bundleCount++;
|
||||
|
||||
string bundleName = bundle.name;
|
||||
if (string.IsNullOrEmpty(bundleName))
|
||||
bundleName = "<unnamed bundle>";
|
||||
|
||||
string[] assetNames = null;
|
||||
try
|
||||
{
|
||||
assetNames = bundle.GetAllAssetNames();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Log($"[DumpLoadedBundleAssets] Failed reading asset names from bundle {bundleName}: {e.Message}");
|
||||
continue;
|
||||
}
|
||||
|
||||
bool bundleHeaderPrinted = false;
|
||||
|
||||
if (assetNames != null)
|
||||
{
|
||||
foreach (var assetName in assetNames)
|
||||
{
|
||||
if (string.IsNullOrEmpty(assetName)) continue;
|
||||
|
||||
if (IsMatch(assetName))
|
||||
{
|
||||
if (!bundleHeaderPrinted)
|
||||
{
|
||||
Debug.Log($"[DumpLoadedBundleAssets] HIT BUNDLE = {bundleName}");
|
||||
bundleHeaderPrinted = true;
|
||||
}
|
||||
|
||||
Debug.Log($"[DumpLoadedBundleAssets] Asset = {assetName}");
|
||||
hitCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"[DumpLoadedBundleAssets] Scan finished. Loaded bundles = {bundleCount}, Hits = {hitCount}");
|
||||
}
|
||||
|
||||
private bool IsMatch(string assetName)
|
||||
{
|
||||
string lower = assetName.ToLowerInvariant();
|
||||
|
||||
foreach (var k in keywords)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(k)) continue;
|
||||
if (lower.Contains(k.ToLowerInvariant()))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
DumpLoadedAssetBundles();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9459a406c0ff58349bd3255f1827c124
|
||||
guid: 6ec9e248b5330f046bbc78de329486b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,220 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using Gallop.RenderPipeline;
|
||||
|
||||
// Isolated GPU contract tests for the shipped shaders, not a whole-Live parity test.
|
||||
// Expected equations come from local DXBC (documented in LIVE_RENDER_RESTORATION.md).
|
||||
public static class LiveBloomDiffusionRegression
|
||||
{
|
||||
static void Check(bool condition, string message)
|
||||
{
|
||||
if (!condition) throw new Exception("LiveBloomDiffusionRegression: " + message);
|
||||
}
|
||||
|
||||
static void Near(Color actual, Color expected, string label)
|
||||
{
|
||||
for (int c = 0; c < 3; ++c)
|
||||
Check(!float.IsNaN(actual[c]) && Math.Abs(actual[c] - expected[c]) < 0.00015f,
|
||||
label + " channel " + c + " actual=" + actual[c] + " expected=" + expected[c]);
|
||||
}
|
||||
|
||||
static RenderTexture Texture(int width, int height)
|
||||
{
|
||||
var rt = new RenderTexture(width, height, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear);
|
||||
rt.filterMode = FilterMode.Bilinear;
|
||||
rt.wrapMode = TextureWrapMode.Clamp;
|
||||
rt.Create();
|
||||
return rt;
|
||||
}
|
||||
|
||||
static void Fill(RenderTexture rt, Color color)
|
||||
{
|
||||
RenderTexture.active = rt;
|
||||
GL.Clear(false, true, color);
|
||||
}
|
||||
|
||||
static Color Read(RenderTexture rt, Texture2D read)
|
||||
{
|
||||
RenderTexture.active = rt;
|
||||
read.ReadPixels(new Rect(rt.width / 2, rt.height / 2, 1, 1), 0, 0);
|
||||
read.Apply();
|
||||
return read.GetPixel(0, 0);
|
||||
}
|
||||
|
||||
static Color Composite(Color source, Color bloom, float weight, bool screen)
|
||||
{
|
||||
Color result = source;
|
||||
for (int c = 0; c < 3; ++c)
|
||||
result[c] = screen ? 1 - (1 - source[c]) * (1 - bloom[c] * weight) : source[c] + bloom[c] * weight;
|
||||
return result;
|
||||
}
|
||||
|
||||
static Color Diffusion(Color composite, Color blur, Vector4 parameters)
|
||||
{
|
||||
Color result = composite;
|
||||
for (int c = 0; c < 3; ++c)
|
||||
{
|
||||
float square = composite[c] * composite[c];
|
||||
result[c] = Mathf.Max(composite[c], Mathf.Max(0, square + blur[c] - blur[c] * square - parameters.w)) * parameters.x;
|
||||
}
|
||||
float luminance = result.r * 0.2125f + result.g * 0.7154f + result.b * 0.0721f;
|
||||
for (int c = 0; c < 3; ++c)
|
||||
result[c] = (luminance + parameters.y * (result[c] - luminance) - 0.5f) * parameters.z + 0.5f;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void RunGpu()
|
||||
{
|
||||
Check(Application.isBatchMode, "run only in an isolated batch-mode project");
|
||||
string path = Environment.GetEnvironmentVariable("UMA_POSTFILM_TEST_BUNDLE");
|
||||
Check(File.Exists(path), "missing local shader bundle");
|
||||
var bundle = AssetBundle.LoadFromFile(path);
|
||||
Check(bundle != null, "bundle load");
|
||||
var previous = RenderTexture.active;
|
||||
var source = Texture(64, 32);
|
||||
var low = Texture(16, 8);
|
||||
var vertical = Texture(16, 8);
|
||||
var bloom = Texture(16, 8);
|
||||
var target = Texture(64, 32);
|
||||
var read = new Texture2D(1, 1, TextureFormat.RGBAFloat, false, true);
|
||||
Material fast = null, post = null, diffusion = null, diffusionDof = null;
|
||||
string[] floats = { "_bloomDofWeight", "_BloomIsScreenBlend" };
|
||||
float[] oldFloats = Array.ConvertAll(floats, Shader.GetGlobalFloat);
|
||||
string[] vectors = { "_Parameter", "_ColorParam", "_PixelSize" };
|
||||
Vector4[] oldVectors = Array.ConvertAll(vectors, Shader.GetGlobalVector);
|
||||
var oldBloom = Shader.GetGlobalTexture("_Bloom");
|
||||
var oldRgb = Shader.GetGlobalTexture("_RgbTex");
|
||||
var oldLowBackground = Shader.GetGlobalTexture("_TapLowBackground");
|
||||
try
|
||||
{
|
||||
foreach (var shader in bundle.LoadAllAssets<Shader>())
|
||||
{
|
||||
if (shader.name == "Gallop/ImageEffects/FastBloom") fast = new Material(shader);
|
||||
if (shader.name == "Gallop/ImageEffects/Rich/PostBloom_Rich") post = new Material(shader);
|
||||
if (shader.name == "Gallop/ImageEffects/Rich/PostDiffusionBloom_Rich") diffusion = new Material(shader);
|
||||
if (shader.name == "Gallop/ImageEffects/Rich/PostDiffusionDofBloom_Rich") diffusionDof = new Material(shader);
|
||||
}
|
||||
Check(fast && post && diffusion && diffusionDof, "four shipped shaders present");
|
||||
Check(fast.shader.isSupported && post.shader.isSupported && diffusion.shader.isSupported &&
|
||||
diffusionDof.shader.isSupported, "shaders supported");
|
||||
post.SetColor("_DimmerColor", Color.white);
|
||||
Color input = new Color(0.05f, 0.4f, 1.4f, 1);
|
||||
Fill(source, input);
|
||||
foreach (float threshold in new[] { 0f, 0.1f, 0.23f })
|
||||
foreach (float intensity in new[] { 0f, 0.5f, 0.9f, 2f })
|
||||
{
|
||||
Color expected = input;
|
||||
for (int c = 0; c < 3; ++c) expected[c] = Mathf.Max(0, input[c] - threshold) * intensity;
|
||||
// Zero-radius native path: threshold/intensity both in downsample.
|
||||
Shader.SetGlobalVector("_Parameter", new Vector4(0, 0, threshold, intensity));
|
||||
Graphics.Blit(source, bloom, fast, 1);
|
||||
Near(Read(bloom, read), expected, "zero-radius extraction");
|
||||
// Positive radius path: neutral reduction, threshold only in vertical,
|
||||
// intensity only in horizontal. Both Gaussian kernels sum to one.
|
||||
Shader.SetGlobalVector("_Parameter", new Vector4(0, 0, 0, 1));
|
||||
Graphics.Blit(source, low, fast, 1);
|
||||
Shader.SetGlobalVector("_Parameter", new Vector4(1f / 1024, 1f / 512, threshold, intensity));
|
||||
Graphics.Blit(low, vertical, fast, 2);
|
||||
Graphics.Blit(vertical, bloom, fast, 3);
|
||||
Near(Read(bloom, read), expected, "two-axis extraction (no double threshold/intensity)");
|
||||
}
|
||||
var bloomColor = new Color(0.1f, 0.3f, 0.8f, 1);
|
||||
var blurColor = new Color(0.2f, 0.6f, 0.9f, 1);
|
||||
Fill(bloom, bloomColor);
|
||||
Fill(low, blurColor);
|
||||
Shader.SetGlobalTexture("_Bloom", bloom);
|
||||
Shader.SetGlobalTexture("_RgbTex", low);
|
||||
foreach (bool screen in new[] { false, true, false })
|
||||
foreach (float weight in new[] { 0f, 0.5f, 1f })
|
||||
{
|
||||
Shader.SetGlobalFloat("_bloomDofWeight", weight);
|
||||
Shader.SetGlobalFloat("_BloomIsScreenBlend", screen ? 1 : 0);
|
||||
var composite = Composite(input, bloomColor, weight, screen);
|
||||
Graphics.Blit(source, target, post, 0);
|
||||
Near(Read(target, read), composite, "Bloom Add/Screen HDR");
|
||||
foreach (var parameters in new[] { new Vector4(1, 1, 1, 0), new Vector4(0.8f, 0.5f, 1.1f, 0.2f), new Vector4(1.2f, 0, 0.9f, 0.1f) })
|
||||
{
|
||||
Shader.SetGlobalVector("_ColorParam", parameters);
|
||||
Graphics.Blit(source, target, diffusion, 2);
|
||||
Near(Read(target, read), Diffusion(composite, blurColor, parameters), "Diffusion composite and color order");
|
||||
}
|
||||
}
|
||||
// Pass 1 combines Bloom and the first film layer. Pass 3 consumes
|
||||
// the previous output without sampling Bloom again.
|
||||
var film = new Gallop.RenderPipeline.ScreenOverlayRender.Render();
|
||||
var first = Gallop.RenderPipeline.ScreenOverlayRender.Parameter.Default();
|
||||
first.PostFilmMode = Gallop.ImageEffect.ScreenOverlay.Overlay.PostFilmMode.Add;
|
||||
first.PostFilmPower = 0.5f;
|
||||
first.PostFilmColor0 = first.PostFilmColor1 = first.PostFilmColor2 = first.PostFilmColor3 =
|
||||
new Color(0.4f, 0.2f, 0.1f, 1f);
|
||||
var later = Gallop.RenderPipeline.ScreenOverlayRender.Parameter.Default();
|
||||
later.PostFilmMode = Gallop.ImageEffect.ScreenOverlay.Overlay.PostFilmMode.Lerp;
|
||||
later.PostFilmPower = 0.5f;
|
||||
later.PostFilmColor0 = later.PostFilmColor1 = later.PostFilmColor2 = later.PostFilmColor3 = Color.black;
|
||||
Fill(source, new Color(0.2f, 0.3f, 0.4f, 1f));
|
||||
Fill(bloom, new Color(0.1f, 0.2f, 0.3f, 1f));
|
||||
Shader.SetGlobalTexture("_Bloom", bloom);
|
||||
Shader.SetGlobalFloat("_bloomDofWeight", 0.5f);
|
||||
Shader.SetGlobalFloat("_BloomIsScreenBlend", 0f);
|
||||
post.SetColor("_DimmerColor", Color.white);
|
||||
using (var cmd = new CommandBuffer())
|
||||
{
|
||||
film.Blit(ref first, cmd, RenderTextureHandle.Make(source), RenderTextureHandle.Make(target), post, 1);
|
||||
Graphics.ExecuteCommandBuffer(cmd);
|
||||
cmd.Clear();
|
||||
Near(Read(target, read), new Color(0.45f, 0.5f, 0.6f, 1f), "Bloom plus first film layer");
|
||||
film.Blit(ref later, cmd, RenderTextureHandle.Make(target), RenderTextureHandle.Make(source), post, 3);
|
||||
Graphics.ExecuteCommandBuffer(cmd);
|
||||
cmd.Clear();
|
||||
Near(Read(source, read), new Color(0.225f, 0.25f, 0.3f, 1f), "second film layer without repeated Bloom");
|
||||
film.Blit(ref later, cmd, RenderTextureHandle.Make(source), RenderTextureHandle.Make(target), post, 3);
|
||||
Graphics.ExecuteCommandBuffer(cmd);
|
||||
Near(Read(target, read), new Color(0.1125f, 0.125f, 0.15f, 1f), "third film layer without repeated Bloom");
|
||||
}
|
||||
// The DiffusionDofBloom mode can remain selected when its Bloom flag
|
||||
// is off. Execute binds black before the conditional extraction; verify
|
||||
// that its first film pass does not reuse the previous frame's bloom.
|
||||
var noBloomFilm = Gallop.RenderPipeline.ScreenOverlayRender.Parameter.Default();
|
||||
noBloomFilm.PostFilmMode = Gallop.ImageEffect.ScreenOverlay.Overlay.PostFilmMode.Lerp;
|
||||
noBloomFilm.PostFilmPower = 0.5f;
|
||||
noBloomFilm.PostFilmColor0 = noBloomFilm.PostFilmColor1 =
|
||||
noBloomFilm.PostFilmColor2 = noBloomFilm.PostFilmColor3 = Color.black;
|
||||
diffusionDof.SetColor("_DimmerColor", Color.white);
|
||||
Fill(source, new Color(0.2f, 0.3f, 0.4f, 1f));
|
||||
Fill(low, new Color(0.2f, 0.3f, 0.4f, 1f));
|
||||
Fill(bloom, new Color(0.9f, 0.9f, 0.9f, 1f));
|
||||
// Suppress diffusion's extra lift so this fixture isolates stale Bloom.
|
||||
Shader.SetGlobalVector("_ColorParam", new Vector4(1f, 1f, 1f, 1f));
|
||||
Shader.SetGlobalTexture("_TapLowBackground", low);
|
||||
Shader.SetGlobalTexture("_Bloom", bloom);
|
||||
Shader.SetGlobalTexture("_Bloom", Texture2D.blackTexture);
|
||||
using (var cmd = new CommandBuffer())
|
||||
{
|
||||
film.Blit(ref noBloomFilm, cmd, RenderTextureHandle.Make(source),
|
||||
RenderTextureHandle.Make(target), diffusionDof, 11);
|
||||
Graphics.ExecuteCommandBuffer(cmd);
|
||||
}
|
||||
Near(Read(target, read), new Color(0.1f, 0.15f, 0.2f, 1f),
|
||||
"DiffusionDofBloom first film pass with Bloom disabled");
|
||||
Debug.Log("LiveBloomDiffusionRegression GPU PASS: 24 extraction, 9 Bloom and 27 diffusion equations; HDR/Add/Screen, three-layer Bloom-once PostFilm, and Bloom-disabled DiffusionDofBloom first film pass");
|
||||
}
|
||||
finally
|
||||
{
|
||||
for (int i = 0; i < floats.Length; ++i) Shader.SetGlobalFloat(floats[i], oldFloats[i]);
|
||||
for (int i = 0; i < vectors.Length; ++i) Shader.SetGlobalVector(vectors[i], oldVectors[i]);
|
||||
Shader.SetGlobalTexture("_Bloom", oldBloom);
|
||||
Shader.SetGlobalTexture("_RgbTex", oldRgb);
|
||||
Shader.SetGlobalTexture("_TapLowBackground", oldLowBackground);
|
||||
RenderTexture.active = previous;
|
||||
foreach (var rt in new[] { source, low, vertical, bloom, target }) { rt.Release(); UnityEngine.Object.DestroyImmediate(rt); }
|
||||
UnityEngine.Object.DestroyImmediate(read);
|
||||
if (fast) UnityEngine.Object.DestroyImmediate(fast);
|
||||
if (post) UnityEngine.Object.DestroyImmediate(post);
|
||||
if (diffusion) UnityEngine.Object.DestroyImmediate(diffusion);
|
||||
if (diffusionDof) UnityEngine.Object.DestroyImmediate(diffusionDof);
|
||||
bundle.Unload(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9872ed211ac4fa4bdbde21cc709704e
|
||||
@@ -0,0 +1,81 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
// Reference manifests describe timeline frames; video extraction offsets must NOT
|
||||
// be applied a second time when seeking the Live Director.
|
||||
[Serializable]
|
||||
public sealed class LiveCapturePlan
|
||||
{
|
||||
public int songId = 1001;
|
||||
public string stageId = "10102";
|
||||
public string[] characters;
|
||||
public int fps = 60, width = 1920, height = 1080;
|
||||
public int[] frames;
|
||||
|
||||
public static LiveCapturePlan Default()
|
||||
{
|
||||
return new LiveCapturePlan {
|
||||
characters = new[] { "1068_00", "1006_00", "1030_00" }
|
||||
.Concat(Enumerable.Repeat("1003_00", 15)).ToArray(),
|
||||
frames = new[] { 1710, 1732, 1740, 1755, 3000 }
|
||||
};
|
||||
}
|
||||
public void Validate()
|
||||
{
|
||||
if (songId <= 0 || string.IsNullOrEmpty(stageId) || fps != 60 ||
|
||||
width < 1 || height < 1 || width > 8192 || height > 8192 ||
|
||||
frames == null || frames.Length == 0 || frames.Any(x => x < 0) ||
|
||||
frames.Distinct().Count() != frames.Length || characters == null || characters.Length == 0)
|
||||
throw new InvalidOperationException("Invalid Live capture manifest (60fps timeline required).");
|
||||
foreach (string character in characters)
|
||||
{
|
||||
ParseCharacterIdentity(character, out _, out _);
|
||||
}
|
||||
}
|
||||
// The first separator belongs to the capture identity; remaining separators
|
||||
// are part of a generic costume ID (e.g. 0001_00), not extra characters.
|
||||
public static void ParseCharacterIdentity(string identity, out int id, out string costume)
|
||||
{
|
||||
int separator = (identity ?? "").IndexOf('_');
|
||||
id = 0;
|
||||
costume = separator >= 0 ? identity.Substring(separator + 1) : "";
|
||||
if (separator <= 0 || !int.TryParse(identity.Substring(0, separator), out id) || id <= 0 ||
|
||||
costume.Split('_').Any(part => part.Length == 0 || part.Any(c => c < '0' || c > '9')))
|
||||
throw new InvalidOperationException("Invalid reference character/costume identity.");
|
||||
}
|
||||
|
||||
public void SelectFrames(int[] selected)
|
||||
{
|
||||
if (selected == null || selected.Length == 0 || selected.Any(x => !frames.Contains(x)))
|
||||
throw new InvalidOperationException("Requested capture frames must exist in the reference manifest.");
|
||||
var previous = frames;
|
||||
frames = selected;
|
||||
try { Validate(); } catch { frames = previous; throw; }
|
||||
}
|
||||
public bool MatchesIdentity(int song, string stage, string[] cast)
|
||||
{
|
||||
return song == songId && stage == stageId && cast != null && characters.SequenceEqual(cast);
|
||||
}
|
||||
}
|
||||
|
||||
// Require consecutive renders at the intended timeline frame, not editor update
|
||||
// ticks. Duplicate camera callbacks in one Unity frame cannot advance the gate.
|
||||
public sealed class LiveCaptureStabilityGate
|
||||
{
|
||||
readonly int required;
|
||||
int count, lastUnityFrame = -1;
|
||||
public LiveCaptureStabilityGate(int requiredFrames = 60) { required = requiredFrames; }
|
||||
public void Reset() { count = 0; lastUnityFrame = -1; }
|
||||
public bool Observe(float seconds, int timelineFrame, int fps, int unityFrame, bool ready)
|
||||
{
|
||||
if (unityFrame == lastUnityFrame) return false;
|
||||
lastUnityFrame = unityFrame;
|
||||
if (!ready || float.IsNaN(seconds) || float.IsInfinity(seconds) ||
|
||||
Math.Abs((double)seconds * fps - timelineFrame) > .1)
|
||||
{ count = 0; return false; }
|
||||
return ++count >= required;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d316dcf24d44b9c853e3db15e3d802d
|
||||
@@ -0,0 +1,105 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using Gallop.RenderPipeline;
|
||||
using Overlay = Gallop.ImageEffect.ScreenOverlay.Overlay;
|
||||
|
||||
public static class LiveCaptureContractRegression
|
||||
{
|
||||
static void Check(bool ok, string message)
|
||||
{ if (!ok) throw new Exception("LiveCaptureContractRegression: " + message); }
|
||||
static void Reject(Action action, string message)
|
||||
{
|
||||
bool rejected = false;
|
||||
try { action(); } catch (InvalidOperationException) { rejected = true; }
|
||||
Check(rejected, message);
|
||||
}
|
||||
public static void Run()
|
||||
{
|
||||
Check(Application.isBatchMode, "isolated batch only");
|
||||
var plan = LiveCapturePlan.Default(); plan.Validate();
|
||||
LiveCapturePlan.ParseCharacterIdentity("1068_0001_00", out int genericId, out string genericCostume);
|
||||
Check(genericId == 1068 && genericCostume == "0001_00", "full generic costume preserved");
|
||||
LiveCapturePlan.ParseCharacterIdentity("1068_00", out int normalId, out string normalCostume);
|
||||
Check(normalId == 1068 && normalCostume == "00", "short costume preserved");
|
||||
foreach (string bad in new[] { "", "1068", "1068_", "1068__00", "1068_00_", "1068_../00", "0_00" })
|
||||
Reject(() => LiveCapturePlan.ParseCharacterIdentity(bad, out _, out _), "reject malformed identity");
|
||||
var genericPlan = LiveCapturePlan.Default();
|
||||
genericPlan.characters[0] = "1068_0001_00";
|
||||
genericPlan.Validate();
|
||||
var genericRoundTrip = JsonUtility.FromJson<LiveCapturePlan>(JsonUtility.ToJson(genericPlan));
|
||||
genericRoundTrip.Validate();
|
||||
Check(genericRoundTrip.MatchesIdentity(genericPlan.songId, genericPlan.stageId, genericPlan.characters), "generic identity survives reload");
|
||||
Check(!genericRoundTrip.MatchesIdentity(genericPlan.songId, genericPlan.stageId, LiveCapturePlan.Default().characters), "generic costume not truncated during comparison");
|
||||
Check(plan.characters.Length == 18 && plan.MatchesIdentity(1001, "10102", plan.characters), "default aligned identity");
|
||||
Check(!plan.MatchesIdentity(1176, "10102", plan.characters) && !plan.MatchesIdentity(1001, "other", plan.characters), "reject wrong song/stage");
|
||||
var cast = (string[])plan.characters.Clone(); cast[0] = "1068_02";
|
||||
Check(!plan.MatchesIdentity(1001, "10102", cast), "reject wrong costume");
|
||||
Reject(() => plan.SelectFrames(new[] { 1 }), "reject reference-missing frame");
|
||||
Reject(() => plan.SelectFrames(new[] { 1740, 1740 }), "reject duplicate and restore plan");
|
||||
Check(plan.frames.Length == 5, "bad subset must not mutate plan");
|
||||
plan.SelectFrames(new[] { 3000, 1740 });
|
||||
Check(plan.frames[0] == 3000, "backseek capture order retained");
|
||||
var restored = JsonUtility.FromJson<LiveCapturePlan>(JsonUtility.ToJson(plan));
|
||||
restored.Validate(); Check(restored.frames[1] == 1740 && restored.characters[2] == "1030_00", "domain-reload plan roundtrip");
|
||||
foreach (string name in new[] { "UMA_CAPTURE_TEST_REFERENCE1001", "UMA_CAPTURE_TEST_REFERENCE1176" })
|
||||
{
|
||||
string path = Environment.GetEnvironmentVariable(name);
|
||||
Check(Path.GetFileName(path) == "manifest.json" && File.Exists(path), "explicit real reference manifest");
|
||||
var reference = JsonUtility.FromJson<LiveCapturePlan>(File.ReadAllText(path));
|
||||
reference.Validate();
|
||||
Check(reference.songId == (name.EndsWith("1176") ? 1176 : 1001), "actual song manifest identity");
|
||||
reference.SelectFrames(new[] { reference.frames[1], reference.frames[reference.frames.Length - 1] });
|
||||
}
|
||||
var gate = new LiveCaptureStabilityGate();
|
||||
for (int i = 0; i < 59; ++i)
|
||||
{
|
||||
Check(!gate.Observe(50, 3000, 60, i, true), "needs sixty distinct renders");
|
||||
Check(!gate.Observe(50, 3000, 60, i, true), "duplicate callback doesn't advance");
|
||||
}
|
||||
Check(gate.Observe(50, 3000, 60, 59, true), "sixtieth correct render ready");
|
||||
gate.Reset();
|
||||
for (int i = 0; i < 80; ++i) Check(!gate.Observe(49, 3000, 60, i, true), "old frame cannot be mislabeled");
|
||||
Check(!gate.Observe(float.NaN, 3000, 60, 80, true), "invalid clock");
|
||||
for (int i = 81; i < 140; ++i) Check(!gate.Observe(50, 3000, 60, i, true), "settle after mismatch");
|
||||
Check(!gate.Observe(50, 3000, 60, 140, false), "shader/feature unavailable clears settle");
|
||||
for (int i = 141; i < 200; ++i) Check(!gate.Observe(50, 3000, 60, i, true), "settle after readiness loss");
|
||||
Check(gate.Observe(50, 3000, 60, 200, true), "stable readiness restored");
|
||||
// Simulate the exact scoped noFilm parameter mutation without a Live camera.
|
||||
// The URP 14 callback order is begin -> AddRenderPasses -> Execute -> end;
|
||||
// this contract tests struct write-back and restoration, not GPU pixels.
|
||||
var runtime = PostImageEffectFeature.RuntimeParameter;
|
||||
var old = runtime.DofDiffuionBloomOverlay;
|
||||
try
|
||||
{
|
||||
var film = old;
|
||||
film.Overlay1.PostFilmMode = Overlay.PostFilmMode.Add;
|
||||
film.Overlay1.PostFilmPower = .31f;
|
||||
film.Overlay2.PostFilmMode = Overlay.PostFilmMode.Mul;
|
||||
film.Overlay2.PostFilmPower = .42f;
|
||||
film.Overlay3.PostFilmMode = Overlay.PostFilmMode.ScreenBlend;
|
||||
film.Overlay3.PostFilmPower = .53f;
|
||||
runtime.DofDiffuionBloomOverlay = film;
|
||||
LiveRenderCapture.SuppressFilm();
|
||||
var suppressed = runtime.DofDiffuionBloomOverlay;
|
||||
Check(!suppressed.IsEnableOverlay && suppressed.Overlay1.PostFilmPower == .31f &&
|
||||
suppressed.Overlay2.PostFilmPower == .42f && suppressed.Overlay3.PostFilmPower == .53f,
|
||||
"noFilm must suppress all three overlay validity flags without changing strengths");
|
||||
Reject(() => LiveRenderCapture.SuppressFilm(), "nested film suppression must fail");
|
||||
suppressed.BloomIntensity = .67f; // another callback can change unrelated timeline state
|
||||
runtime.DofDiffuionBloomOverlay = suppressed;
|
||||
LiveRenderCapture.RestoreFilm();
|
||||
var restoredFilm = runtime.DofDiffuionBloomOverlay;
|
||||
Check(restoredFilm.Overlay1.PostFilmMode == Overlay.PostFilmMode.Add &&
|
||||
restoredFilm.Overlay2.PostFilmMode == Overlay.PostFilmMode.Mul &&
|
||||
restoredFilm.Overlay3.PostFilmMode == Overlay.PostFilmMode.ScreenBlend &&
|
||||
restoredFilm.BloomIntensity == .67f,
|
||||
"restoration must preserve other timeline fields and all three modes");
|
||||
LiveRenderCapture.RestoreFilm(); // harmless after an early cancellation
|
||||
}
|
||||
finally { LiveRenderCapture.RestoreFilm(); runtime.DofDiffuionBloomOverlay = old; }
|
||||
Debug.Log("LiveCaptureContractRegression PASS: two actual reference manifests, identity/subset/reload, stale-frame/duplicate/readiness gates. No full Live was loaded.");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6aeed533117c4250b51792e7cee86d78
|
||||
@@ -0,0 +1,193 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using Gallop.Live.Cutt;
|
||||
using Gallop.RenderPipeline;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using Overlay = Gallop.ImageEffect.ScreenOverlay.Overlay;
|
||||
|
||||
// Film-only attribution on already captured real pixels. This is NOT a full Live replay:
|
||||
// input is before DOF/Bloom/diffusion; actual depth is absent. Synced BlinkLight RGB
|
||||
// is available only when explicitly recovered from same-frame runtime metadata.
|
||||
// Raw-depth sweeps are explicitly counterfactual, not estimated scene depth.
|
||||
public static class LiveCapturedFilmAttribution
|
||||
{
|
||||
[Serializable] public class CurveKey { public float time, value, inSlope, outSlope, inWeight, outWeight; public int weightedMode; }
|
||||
[Serializable] public class Row { public LiveTimelineKeyPostFilmData key; public CurveKey[] curveKeys; public int preWrap, postWrap; }
|
||||
[Serializable] public class Track { public string name; public int attribute, playMode; public Row[] rows; }
|
||||
[Serializable] public class Frame { public int frame; public string imagePath, sha256; public float[] expectedPowers; public Color rawBlinkRgb; public Color[] expectedColor0; public string blinkLookup; }
|
||||
[Serializable] public class Input { public Track[] tracks; public Frame[] frames; }
|
||||
[Serializable] public class Stats {
|
||||
public string image, stage; public int frame, layer, omittedLayer; public float rawDepth;
|
||||
public float[] mean, maximum; public float anyChannelOverOne, allChannelsNearWhite;
|
||||
public float shaderParityMaxError;
|
||||
}
|
||||
[Serializable] public class ParameterRecord {
|
||||
public int frame, layer; public string mode; public float power, depthPower, depthClip;
|
||||
public bool valid, inverse; public Vector2 offset; public Vector4 options; public Color[] colors;
|
||||
}
|
||||
[Serializable] public class Report {
|
||||
public string limitation, colorSpace, inputPath; public List<ParameterRecord> parameters = new List<ParameterRecord>();
|
||||
public List<Stats> stages = new List<Stats>();
|
||||
}
|
||||
static void Check(bool ok, string message) { if (!ok) throw new Exception("CapturedFilmAttribution: " + message); }
|
||||
static RenderTexture RT(int w, int h, RenderTextureFormat format) {
|
||||
var rt = new RenderTexture(w, h, 0, format, RenderTextureReadWrite.Default) { filterMode = FilterMode.Bilinear, wrapMode = TextureWrapMode.Clamp };
|
||||
rt.Create(); return rt;
|
||||
}
|
||||
static Color[] Read(RenderTexture rt) {
|
||||
var old = RenderTexture.active;
|
||||
var read = new Texture2D(rt.width, rt.height, TextureFormat.RGBAFloat, false, true);
|
||||
try { RenderTexture.active = rt; read.ReadPixels(new Rect(0,0,rt.width,rt.height),0,0); read.Apply(); return read.GetPixels(); }
|
||||
finally { RenderTexture.active = old; UnityEngine.Object.DestroyImmediate(read); }
|
||||
}
|
||||
static void Save(RenderTexture rt, string path) {
|
||||
var old = RenderTexture.active;
|
||||
var read = new Texture2D(rt.width, rt.height, TextureFormat.RGBA32, false, true);
|
||||
try { RenderTexture.active = rt; read.ReadPixels(new Rect(0,0,rt.width,rt.height),0,0); read.Apply(); File.WriteAllBytes(path, read.EncodeToPNG()); }
|
||||
finally { RenderTexture.active = old; UnityEngine.Object.DestroyImmediate(read); }
|
||||
}
|
||||
static Stats Measure(Color[] pixels, int frame, float depth, int layer, string stage, int omitted = 0) {
|
||||
var sums = new double[3]; var maximum = new float[3]; int over = 0, white = 0;
|
||||
foreach (var p in pixels) {
|
||||
bool any = false, all = true;
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
Check(!float.IsNaN(p[c]) && !float.IsInfinity(p[c]), "non-finite output");
|
||||
sums[c] += p[c]; maximum[c] = Mathf.Max(maximum[c], p[c]);
|
||||
any |= p[c] > 1.0001f; all &= p[c] >= 250f / 255f;
|
||||
}
|
||||
if (any) ++over; if (all) ++white;
|
||||
}
|
||||
return new Stats { frame=frame, rawDepth=depth, layer=layer, stage=stage, omittedLayer=omitted,
|
||||
mean=Array.ConvertAll(sums, x => (float)(x / pixels.Length)), maximum=maximum,
|
||||
anyChannelOverOne=over/(float)pixels.Length, allChannelsNearWhite=white/(float)pixels.Length };
|
||||
}
|
||||
static void Draw(ScreenOverlayRender.Render renderer, ref ScreenOverlayRender.Parameter param,
|
||||
Texture source, RenderTexture target, Material material, int pass) {
|
||||
using (var cmd = new CommandBuffer()) {
|
||||
if (source is RenderTexture rt)
|
||||
renderer.Blit(ref param, cmd, RenderTextureHandle.Make(rt), RenderTextureHandle.Make(target), material, pass);
|
||||
else throw new Exception("Source must have an explicit RT descriptor");
|
||||
Graphics.ExecuteCommandBuffer(cmd);
|
||||
}
|
||||
}
|
||||
public static void Run() {
|
||||
Check(Application.isBatchMode, "isolated batch-mode only; do not use user's Editor");
|
||||
Check(QualitySettings.activeColorSpace == ColorSpace.Gamma, "captured Gamma project required");
|
||||
string inputPath = Environment.GetEnvironmentVariable("UMA_FILM_ATTRIBUTION_INPUT");
|
||||
string output = Environment.GetEnvironmentVariable("UMA_FILM_ATTRIBUTION_OUTPUT");
|
||||
string bundlePath = Environment.GetEnvironmentVariable("UMA_POSTFILM_TEST_BUNDLE");
|
||||
Check(File.Exists(inputPath) && File.Exists(bundlePath) && !string.IsNullOrEmpty(output), "missing input environment");
|
||||
Directory.CreateDirectory(output);
|
||||
var input = JsonUtility.FromJson<Input>(File.ReadAllText(inputPath));
|
||||
Check(input.tracks.Length == 3 && input.frames.Length == 3, "three authored tracks and three real frames");
|
||||
var tracks = new LiveTimelineKeyPostFilmDataList[3];
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
var t = input.tracks[i];
|
||||
tracks[i] = new LiveTimelineKeyPostFilmDataList { attribute=(LiveTimelineKeyDataListAttr)t.attribute, playMode=(TimelineKeyPlayMode)t.playMode };
|
||||
foreach (var row in t.rows) {
|
||||
var keys = Array.ConvertAll(row.curveKeys, x => new Keyframe(x.time,x.value,x.inSlope,x.outSlope,x.inWeight,x.outWeight) { weightedMode=(WeightedMode)x.weightedMode });
|
||||
row.key.curve = new AnimationCurve(keys) { preWrapMode=(WrapMode)row.preWrap, postWrapMode=(WrapMode)row.postWrap };
|
||||
tracks[i].thisList.Add(row.key);
|
||||
}
|
||||
}
|
||||
var report = new Report { inputPath=inputPath, colorSpace=QualitySettings.activeColorSpace.ToString(),
|
||||
limitation="Film-only, before-DOF/Bloom real captured input. Raw hardware-depth sweep is counterfactual; no captured depth, captured BlinkLight RGB when present, no color correction or game-reference parity. Float probes measure shader outputs before ARGB32 clamping; next layer consumes ARGB32 just as native film ping-pong." };
|
||||
var bundle = AssetBundle.LoadFromFile(bundlePath);
|
||||
Check(bundle != null, "load official shader bundle");
|
||||
Material film=null, combined=null;
|
||||
var oldDepth=Shader.GetGlobalTexture("_CameraDepthTexture"); var oldScroll=Shader.GetGlobalVector("_GlobalScreenUVScrollParam");
|
||||
var oldInverseSize=Shader.GetGlobalVector("_InvRenderTargetSize");
|
||||
var depth = new Texture2D(1,1,TextureFormat.RFloat,false,true) { filterMode=FilterMode.Point };
|
||||
try {
|
||||
foreach (var shader in bundle.LoadAllAssets<Shader>()) {
|
||||
if (shader.name == "Gallop/ImageEffects/Rich/PostBlit_Rich") film = new Material(shader);
|
||||
if (shader.name == "Gallop/ImageEffects/Rich/PostDiffusionDofBloom_Rich") combined = new Material(shader);
|
||||
}
|
||||
Check(film != null && film.shader.isSupported && combined != null && combined.shader.isSupported, "shipped shaders supported");
|
||||
film.SetColor("_DimmerColor",Color.white); combined.SetColor("_DimmerColor",Color.white);
|
||||
Shader.DisableKeyword("COLOR_ONLY"); // Match production's color-layer Apply (no enabled blend keyword).
|
||||
Shader.SetGlobalVector("_GlobalScreenUVScrollParam",Vector4.zero);
|
||||
var renderer = new ScreenOverlayRender.Render();
|
||||
foreach (var f in input.frames) {
|
||||
using (var hash = SHA256.Create()) {
|
||||
string actual = BitConverter.ToString(hash.ComputeHash(File.ReadAllBytes(f.imagePath))).Replace("-","").ToLowerInvariant();
|
||||
Check(actual==f.sha256,"input hash " + f.frame);
|
||||
}
|
||||
var image = new Texture2D(2,2,TextureFormat.RGBA32,false,true);
|
||||
Check(image.LoadImage(File.ReadAllBytes(f.imagePath),false),"load real pixels");
|
||||
var source = RT(image.width,image.height,RenderTextureFormat.ARGB32);
|
||||
var a = RT(image.width,image.height,RenderTextureFormat.ARGB32);
|
||||
var b = RT(image.width,image.height,RenderTextureFormat.ARGB32);
|
||||
var probe = RT(image.width,image.height,RenderTextureFormat.ARGBFloat);
|
||||
var alternate = RT(image.width,image.height,RenderTextureFormat.ARGBFloat);
|
||||
try {
|
||||
Graphics.Blit(image,source);
|
||||
Shader.SetGlobalVector("_InvRenderTargetSize",new Vector4(1f/image.width,1f/image.height,image.width,image.height));
|
||||
var param = new ScreenOverlayRender.Parameter[3];
|
||||
for (int i=0;i<3;++i) {
|
||||
var overlay = new Overlay();
|
||||
if (f.expectedColor0 != null && f.expectedColor0.Length == 3) {
|
||||
// Captured current-stage raw RGB, recovered only from a unit-brightness
|
||||
// sync layer. Native key brightness is applied inside Evaluate.
|
||||
LivePostFilmTimeline.BlinkColorResolver resolve = delegate(string name,int hash,int index,out Color raw) {
|
||||
raw = f.rawBlinkRgb; return true;
|
||||
};
|
||||
LivePostFilmTimeline.Evaluate(tracks[i],f.frame,TimelinePlayerMode.Default,overlay,resolve);
|
||||
var expected=f.expectedColor0[i]; var actual=overlay.postFilmColor0;
|
||||
Check(Mathf.Abs(actual.r-expected.r)<0.0002f && Mathf.Abs(actual.g-expected.g)<0.0002f &&
|
||||
Mathf.Abs(actual.b-expected.b)<0.0002f && Mathf.Abs(actual.a-expected.a)<0.0002f,
|
||||
"captured synced color0 frame="+f.frame+" layer="+(i+1)+" actual="+actual+" expected="+expected);
|
||||
} else LivePostFilmTimeline.Evaluate(tracks[i],f.frame,TimelinePlayerMode.Default,overlay);
|
||||
param[i]=ScreenOverlayRender.Parameter.Default(); param[i].Setup(overlay);
|
||||
Check(Mathf.Abs(param[i].PostFilmPower-f.expectedPowers[i])<0.0002f,"evaluated power vs captured runtime frame="+f.frame+" layer="+(i+1)+" actual="+param[i].PostFilmPower+" expected="+f.expectedPowers[i]);
|
||||
report.parameters.Add(new ParameterRecord { frame=f.frame, layer=i+1, mode=param[i].PostFilmMode.ToString(),power=param[i].PostFilmPower,depthPower=param[i].DepthPower,depthClip=param[i].DepthClip,valid=param[i].IsValidity,inverse=param[i].InverseVignette,offset=param[i].PostFilmOffsetParam,options=param[i].PostFilmOptionParam,colors=new[]{param[i].PostFilmColor0,param[i].PostFilmColor1,param[i].PostFilmColor2,param[i].PostFilmColor3} });
|
||||
}
|
||||
report.stages.Add(Measure(Read(source),f.frame,-1,0,"input"));
|
||||
foreach (float raw in new[]{0f,0.02f,0.1f,1f}) {
|
||||
depth.SetPixel(0,0,new Color(raw,0,0,1)); depth.Apply(); Shader.SetGlobalTexture("_CameraDepthTexture",depth);
|
||||
RenderTexture current=source;
|
||||
for (int i=0;i<3;++i) {
|
||||
RenderTexture next = current==a ? b : a;
|
||||
if (param[i].IsValidity) {
|
||||
Draw(renderer,ref param[i],current,probe,film,i==0?1:3);
|
||||
// Production later-pass arithmetic should match plain PostBlit on identical inputs.
|
||||
Draw(renderer,ref param[i],current,alternate,combined,13);
|
||||
var values=Read(probe); var other=Read(alternate); float maxError=0;
|
||||
for (int n=0;n<values.Length;++n) for (int c=0;c<3;++c) maxError=Mathf.Max(maxError,Mathf.Abs(values[n][c]-other[n][c]));
|
||||
Check(maxError<0.002f,"film variant parity " + maxError);
|
||||
var measured=Measure(values,f.frame,raw,i+1,"before-output-clamp"); measured.shaderParityMaxError=maxError; report.stages.Add(measured);
|
||||
Draw(renderer,ref param[i],current,next,film,i==0?1:3);
|
||||
} else Graphics.Blit(current,next);
|
||||
string name="frame"+f.frame.ToString("D6")+"_rawdepth"+raw.ToString("0.00",CultureInfo.InvariantCulture)+"_layer"+(i+1)+".png";
|
||||
Save(next,Path.Combine(output,name)); var s=Measure(Read(next),f.frame,raw,i+1,"ARGB32"); s.image=name; report.stages.Add(s); current=next;
|
||||
}
|
||||
// Leave-one-layer-out experiments retain actual authored powers/order of all other layers.
|
||||
for (int omitted=1;omitted<=3;++omitted) {
|
||||
current=source;
|
||||
for (int i=0;i<3;++i) {
|
||||
RenderTexture next=current==a ? b : a;
|
||||
if (i+1!=omitted && param[i].IsValidity) Draw(renderer,ref param[i],current,next,film,i==0?1:3);
|
||||
else Graphics.Blit(current,next);
|
||||
current=next;
|
||||
}
|
||||
string name="frame"+f.frame.ToString("D6")+"_rawdepth"+raw.ToString("0.00",CultureInfo.InvariantCulture)+"_omit"+omitted+".png";
|
||||
Save(current,Path.Combine(output,name)); var s=Measure(Read(current),f.frame,raw,3,"counterfactual-omit",omitted); s.image=name; report.stages.Add(s);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
foreach (var rt in new[]{source,a,b,probe,alternate}) { rt.Release(); UnityEngine.Object.DestroyImmediate(rt); }
|
||||
UnityEngine.Object.DestroyImmediate(image);
|
||||
}
|
||||
}
|
||||
File.WriteAllText(Path.Combine(output,"report.json"),JsonUtility.ToJson(report,true));
|
||||
Debug.Log("LiveCapturedFilmAttribution PASS: real PNG hashes, nine exact runtime powers, captured sync RGB when provided, authored curves, film variant parity, counterfactual depth sweeps and omit-one layers. NOT full Live parity. Output="+output);
|
||||
} finally {
|
||||
Shader.SetGlobalTexture("_CameraDepthTexture",oldDepth); Shader.SetGlobalVector("_GlobalScreenUVScrollParam",oldScroll); Shader.SetGlobalVector("_InvRenderTargetSize",oldInverseSize);
|
||||
UnityEngine.Object.DestroyImmediate(depth); if(film!=null)UnityEngine.Object.DestroyImmediate(film); if(combined!=null)UnityEngine.Object.DestroyImmediate(combined); bundle.Unload(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d017b2b13aaf198478d9d166f535042e
|
||||
guid: eb50b061425a55a418609340033b5c03
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,313 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Gallop.Live.Cutt;
|
||||
using Gallop.RenderPipeline;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
public static class LiveColorCorrectionRegression
|
||||
{
|
||||
static void Check(bool condition, string message)
|
||||
{
|
||||
if (!condition) throw new Exception("LiveColorCorrectionRegression: " + message);
|
||||
}
|
||||
static void Draw(Material material, Texture lut, float saturation, Texture source, RenderTexture output)
|
||||
{
|
||||
using (var cmd = new CommandBuffer())
|
||||
{
|
||||
// Deliberately poison uniforms: production must establish all draw state.
|
||||
Shader.SetGlobalFloat("_PostFilmPower", 1);
|
||||
Shader.SetGlobalFloat("_Saturation", -8);
|
||||
SimpleColorCorrectionPass.Render(cmd, material, lut, saturation,
|
||||
new RenderTargetIdentifier(source), new RenderTargetIdentifier(output));
|
||||
Graphics.ExecuteCommandBuffer(cmd);
|
||||
Check(Shader.GetGlobalTexture("_RgbTex") == Texture2D.blackTexture, "release LUT global binding");
|
||||
}
|
||||
}
|
||||
static void RunSchedule()
|
||||
{
|
||||
int checks = 0;
|
||||
foreach (int evt in new[] { 500, 550, 600 })
|
||||
for (int mask = 1; mask < 8; ++mask)
|
||||
{
|
||||
bool dof = (mask & 1) != 0, radial = (mask & 2) != 0, color = (mask & 4) != 0;
|
||||
var schedule = PostImageEffectFeature.GetColorChainSchedule(dof, radial, color,
|
||||
(UnityEngine.Rendering.Universal.RenderPassEvent)evt);
|
||||
// Model source ownership in actual event order; a camera resolve consumes temps.
|
||||
var steps = new System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<int, string>>();
|
||||
Action<int, string> add = (e, name) => steps.Add(new System.Collections.Generic.KeyValuePair<int, string>(e, name));
|
||||
add(schedule.FirstEvent, "source");
|
||||
if (dof) add(evt, "dof");
|
||||
if (radial) add(evt, "radial");
|
||||
if (color) add(550, "color");
|
||||
if (schedule.EarlyResolve) add(550, "resolve");
|
||||
if (schedule.FinalResolve) add(schedule.LastEvent, "resolve");
|
||||
// LINQ OrderBy is stable, like URP's pass event sort.
|
||||
bool sourceIsCamera = true, initialized = false;
|
||||
foreach (var step in System.Linq.Enumerable.OrderBy(steps, x => x.Key))
|
||||
{
|
||||
if (step.Value == "source") { initialized = true; continue; }
|
||||
Check(initialized, "source setup must run first");
|
||||
if ((step.Value == "dof" || step.Value == "radial") && step.Key > 550)
|
||||
Check(sourceIsCamera, "late effects must not discard an unresolved color temp");
|
||||
if (step.Value == "color" || step.Value == "radial" || (step.Value == "dof" && step.Key <= 550))
|
||||
sourceIsCamera = false;
|
||||
if (step.Value == "resolve") sourceIsCamera = true;
|
||||
}
|
||||
Check(sourceIsCamera, "final result must resolve to camera");
|
||||
checks++;
|
||||
}
|
||||
Debug.Log("LiveColorCorrection schedule PASS: " + checks + " pass combinations/event boundaries (ownership model, not full URP execution)");
|
||||
}
|
||||
static void RunTimeline(LiveTimelineWorkSheet sheet)
|
||||
{
|
||||
RunSchedule();
|
||||
var groups = sheet.colorCorrectionDataLists;
|
||||
var keys = groups[0].keys.thisList;
|
||||
Func<float, SimpleColorCorrectionPass.Parameter> sample = frame =>
|
||||
LiveColorCorrectionTimeline.Evaluate(groups, frame, TimelinePlayerMode.Default);
|
||||
Check(!sample(-1).Enabled && !sample(float.NaN).Enabled && !sample(float.PositiveInfinity).Enabled,
|
||||
"pre-first and invalid time bypass");
|
||||
Check(!LiveColorCorrectionTimeline.Evaluate(null, 0, TimelinePlayerMode.Default).Enabled, "absent worksheet clears");
|
||||
using (var pass = new SimpleColorCorrectionPass())
|
||||
{
|
||||
Check((int)pass.renderPassEvent == 550, "native color event");
|
||||
foreach (int i in new[] { 0, 1, 2, 3, 4, 5, 3, 0, 5 })
|
||||
{
|
||||
var value = sample(keys[i].frame);
|
||||
Check(value.IsValid && value.Red == keys[i].redCurve, "exact cut/seek " + i);
|
||||
if (i > 0) Check(sample(keys[i].frame - .25f).Red == keys[i-1].redCurve, "hold until cut " + i);
|
||||
int before = pass.LutUploadCount;
|
||||
Check(pass.Prepare(value), "prepare valid");
|
||||
Check(pass.LutUploadCount == before + 1, "cut/seek uploads once");
|
||||
var texture = pass.LutTexture;
|
||||
for (int j = 0; j < 20; ++j) Check(pass.Prepare(value), "repeat valid");
|
||||
value.Saturation = .7f; pass.Prepare(value);
|
||||
Check(pass.LutUploadCount == before + 1 && texture == pass.LutTexture, "stable/saturation avoids upload");
|
||||
}
|
||||
Check(!pass.Prepare(default), "disable");
|
||||
int uploads = pass.LutUploadCount;
|
||||
pass.Prepare(sample(keys[5].frame));
|
||||
Check(pass.LutUploadCount == uploads + 1, "reenable refresh");
|
||||
pass.Dispose(); Check(pass.LutTexture == null, "pass LUT dispose");
|
||||
pass.Prepare(sample(0)); Check(pass.LutTexture != null, "pass recreate");
|
||||
}
|
||||
var list = groups[0].keys;
|
||||
var oldAttribute = list.attribute;
|
||||
list.attribute = LiveTimelineKeyDataListAttr.Disable;
|
||||
Check(!sample(0).Enabled, "disabled list"); list.attribute = oldAttribute;
|
||||
var key = keys[0];
|
||||
key.enable = 0; Check(!sample(0).Enabled, "disabled key"); key.enable = 1;
|
||||
key.mode = 1; Check(!sample(0).Enabled, "advanced bypass"); key.mode = 0;
|
||||
key.selective = 1; Check(!sample(0).Enabled, "selective bypass"); key.selective = 0;
|
||||
groups.Add(groups[0]); Check(!sample(0).Enabled, "ambiguous groups bypass"); groups.RemoveAt(1);
|
||||
PostImageEffectFeature.RuntimeParameter.ColorCorrection = sample(0);
|
||||
var copy = new PostImageEffectFeature.Parameter();
|
||||
copy.ShallowCopyFrom(PostImageEffectFeature.RuntimeParameter);
|
||||
Check(copy.ColorCorrection.IsValid && copy.ColorCorrection.Red == keys[0].redCurve, "parameter copy preserves color");
|
||||
PostImageEffectFeature.ResetRuntimeParameter();
|
||||
Check(!PostImageEffectFeature.RuntimeParameter.ColorCorrection.Enabled, "live teardown reset");
|
||||
Debug.Log("LiveColorCorrection timeline/cache PASS: six real cuts, reverse seek, unsupported modes, reset, LUT lifecycle");
|
||||
}
|
||||
// D3D normalized sampling: pixel centers at (i+.5)/width, exact RGB row centers.
|
||||
// Do not use Texture2D.GetPixelBilinear as a GPU oracle (CPU coordinate convention).
|
||||
static float SampleRow(Texture2D lut, float u, int row)
|
||||
{
|
||||
float x = Mathf.Clamp(u * 256f - 0.5f, 0, 255);
|
||||
int a = Mathf.FloorToInt(x), b = Mathf.Min(a + 1, 255);
|
||||
return Mathf.Lerp(lut.GetPixel(a, row).r, lut.GetPixel(b, row).r, x - a);
|
||||
}
|
||||
static Color Expected(Texture2D lut, Color source, float saturation)
|
||||
{
|
||||
var color = new Color(SampleRow(lut, source.r, 0),
|
||||
SampleRow(lut, source.g, 1), SampleRow(lut, source.b, 2), source.a);
|
||||
float luminance = color.r * 0.2126729f + color.g * 0.7151522f + color.b * 0.072175f;
|
||||
for (int c = 0; c < 3; ++c) color[c] = luminance + saturation * (color[c] - luminance);
|
||||
return color;
|
||||
}
|
||||
// Spatial fixture: all 256 LUT intervals, distinct RGB/alpha ramps, HDR edges.
|
||||
// Also checks the observed native ordering (Bloom -> curves), not only isolated color swatches.
|
||||
static void RunSpatial(AssetBundle bundle, Material material,
|
||||
System.Collections.Generic.List<LiveTimelineKeyColorCorrectionData> keys, ColorCorrectionCurveLut lut)
|
||||
{
|
||||
const int width = 257, height = 9;
|
||||
var pixels = new Color[width * height];
|
||||
for (int y = 0; y < height; ++y)
|
||||
for (int x = 0; x < width; ++x)
|
||||
{
|
||||
float r = x / 256f;
|
||||
float g = ((x * 73 + y * 29) % 257) / 256f;
|
||||
float b = ((x * 31 + y * 67) % 257) / 256f;
|
||||
if (y == 0) { r -= .2f; g += .4f; }
|
||||
pixels[y * width + x] = new Color(r, g, b, (y * width + x) / (float)(pixels.Length - 1));
|
||||
}
|
||||
var source = new Texture2D(width, height, TextureFormat.RGBAFloat, false, true);
|
||||
var read = new Texture2D(width, height, TextureFormat.RGBAFloat, false, true);
|
||||
var bloom = new Texture2D(1, 1, TextureFormat.RGBAFloat, false, true);
|
||||
var intermediate = new RenderTexture(width, height, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear);
|
||||
var output = new RenderTexture(width, height, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear);
|
||||
var previous = RenderTexture.active;
|
||||
var oldAniso = QualitySettings.anisotropicFiltering;
|
||||
var oldBloom = Shader.GetGlobalTexture("_Bloom");
|
||||
float oldWeight = Shader.GetGlobalFloat("_bloomDofWeight"), oldScreen = Shader.GetGlobalFloat("_BloomIsScreenBlend");
|
||||
Material post = null;
|
||||
int checks = 0, orderWitnesses = 0;
|
||||
Color bloomColor = new Color(.16f, .08f, .24f, 1);
|
||||
try
|
||||
{
|
||||
foreach (var shader in bundle.LoadAllAssets<Shader>())
|
||||
if (shader.name == "Gallop/ImageEffects/Rich/PostBloom_Rich") post = new Material(shader);
|
||||
Check(post && post.shader.isSupported, "spatial Bloom shader");
|
||||
QualitySettings.anisotropicFiltering = AnisotropicFiltering.ForceEnable;
|
||||
Check(lut.Texture.anisoLevel == 0, "data LUT must opt out of forced anisotropic filtering");
|
||||
post.SetColor("_DimmerColor", Color.white);
|
||||
source.filterMode = FilterMode.Point; source.wrapMode = TextureWrapMode.Clamp;
|
||||
source.SetPixels(pixels); source.Apply(false, false);
|
||||
bloom.SetPixel(0, 0, bloomColor); bloom.Apply(false, false);
|
||||
intermediate.Create(); output.Create();
|
||||
Shader.SetGlobalTexture("_Bloom", bloom);
|
||||
Shader.SetGlobalFloat("_bloomDofWeight", .7f);
|
||||
foreach (var key in keys)
|
||||
{
|
||||
lut.Update(key.redCurve, key.greenCurve, key.blueCurve);
|
||||
Shader.SetGlobalTexture("_RgbTex", lut.Texture);
|
||||
// -1 = curves alone, 0 = additive Bloom then curves, 1 = screen Bloom then curves.
|
||||
foreach (int blend in new[] { -1, 0, 1 })
|
||||
foreach (float saturation in new[] { 1f, 0f, 1.4f })
|
||||
{
|
||||
Shader.SetGlobalFloat("_Saturation", saturation);
|
||||
if (blend >= 0)
|
||||
{
|
||||
Shader.SetGlobalFloat("_BloomIsScreenBlend", blend);
|
||||
Graphics.Blit(source, intermediate, post, 0);
|
||||
Draw(material, lut.Texture, saturation, intermediate, output);
|
||||
}
|
||||
else Draw(material, lut.Texture, saturation, source, output);
|
||||
RenderTexture.active = output;
|
||||
read.ReadPixels(new Rect(0, 0, width, height), 0, 0); read.Apply(false, false);
|
||||
var actual = read.GetPixels();
|
||||
for (int i = 0; i < pixels.Length; ++i)
|
||||
{
|
||||
Color composed = pixels[i];
|
||||
if (blend >= 0)
|
||||
{
|
||||
for (int c = 0; c < 4; ++c)
|
||||
composed[c] = blend == 0 ? composed[c] + bloomColor[c] * .7f :
|
||||
1 - (1 - composed[c]) * (1 - bloomColor[c] * .7f);
|
||||
// Native PostBloom_Rich applies Add/Screen to all four channels.
|
||||
}
|
||||
Color expected = Expected(lut.Texture, composed, saturation);
|
||||
for (int c = 0; c < 4; ++c)
|
||||
if (float.IsNaN(actual[i][c]) || Mathf.Abs(actual[i][c] - expected[c]) >= .0015f)
|
||||
throw new Exception("LiveColorCorrectionRegression spatial frame=" + key.frame +
|
||||
" blend=" + blend + " saturation=" + saturation + " pixel=" + i +
|
||||
" channel=" + c + " got=" + actual[i][c] + " expected=" + expected[c] + " source=" + pixels[i].ToString("F7") + " rgba=" + actual[i].ToString("F7") + " expectedRGBA=" + expected.ToString("F7"));
|
||||
if (blend == 0 && saturation == 1)
|
||||
{
|
||||
Color reversed = Expected(lut.Texture, pixels[i], saturation);
|
||||
if (Mathf.Abs(expected.r - (reversed.r + bloomColor.r * .7f)) > .02f) orderWitnesses++;
|
||||
}
|
||||
checks++;
|
||||
}
|
||||
}
|
||||
}
|
||||
Check(orderWitnesses > 100, "fixture must detect accidentally moving curves before Bloom");
|
||||
Debug.Log("LiveColorCorrectionRegression spatial PASS: " + checks +
|
||||
" RGBA pixels; LUT ramps/HDR/alpha and Bloom-before-curves Add/Screen; order witnesses=" + orderWitnesses);
|
||||
}
|
||||
finally
|
||||
{
|
||||
QualitySettings.anisotropicFiltering = oldAniso;
|
||||
Shader.SetGlobalTexture("_Bloom", oldBloom);
|
||||
Shader.SetGlobalFloat("_bloomDofWeight", oldWeight); Shader.SetGlobalFloat("_BloomIsScreenBlend", oldScreen);
|
||||
RenderTexture.active = previous;
|
||||
intermediate.Release(); output.Release();
|
||||
UnityEngine.Object.DestroyImmediate(intermediate); UnityEngine.Object.DestroyImmediate(output);
|
||||
UnityEngine.Object.DestroyImmediate(source); UnityEngine.Object.DestroyImmediate(read); UnityEngine.Object.DestroyImmediate(bloom);
|
||||
if (post) UnityEngine.Object.DestroyImmediate(post);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RunGpu()
|
||||
{
|
||||
Check(Application.isBatchMode, "isolated batch mode only");
|
||||
string path = Environment.GetEnvironmentVariable("UMA_COLOR_TEST_KEYS");
|
||||
Check(File.Exists(path), "UMA_COLOR_TEST_KEYS must contain the local Live1176 worksheet fixture");
|
||||
var sheet = ScriptableObject.CreateInstance<LiveTimelineWorkSheet>();
|
||||
AssetBundle bundle = null;
|
||||
Material material = null;
|
||||
var source = new RenderTexture(16, 16, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear);
|
||||
var output = new RenderTexture(16, 16, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear);
|
||||
var read = new Texture2D(16, 16, TextureFormat.RGBAFloat, false, true);
|
||||
var previous = RenderTexture.active;
|
||||
var oldRgb = Shader.GetGlobalTexture("_RgbTex");
|
||||
float oldSaturation = Shader.GetGlobalFloat("_Saturation"), oldPower = Shader.GetGlobalFloat("_PostFilmPower");
|
||||
using (var lut = new ColorCorrectionCurveLut())
|
||||
try
|
||||
{
|
||||
JsonUtility.FromJsonOverwrite(File.ReadAllText(path), sheet);
|
||||
Check(sheet.colorCorrectionDataLists.Count == 1, "real group deserialization");
|
||||
var keys = sheet.colorCorrectionDataLists[0].keys.thisList;
|
||||
Check(keys.Count == 6 && keys[0].enable == 1 && keys[5].frame == 7066, "six actual enabled cuts");
|
||||
RunTimeline(sheet);
|
||||
Check(!keys[0].IsInterpolateKey(), "shipped keys do not have interpolation fields");
|
||||
Check(keys[0].redCurve.length == 2 && Math.Abs(keys[0].redCurve.keys[0].outTangent - 0.7630331f) < 0.00001f,
|
||||
"native AnimationCurve tangents survive deserialization");
|
||||
Check(ColorCorrectionCurveLut.Quantize(-1) == 0 && ColorCorrectionCurveLut.Quantize(2) == 255 &&
|
||||
ColorCorrectionCurveLut.Quantize(0.5f) == 127, "truncating native byte conversion");
|
||||
var identity = AnimationCurve.Linear(0, 0, 1, 1);
|
||||
var zero = AnimationCurve.Constant(0, 1, 0);
|
||||
var one = AnimationCurve.Constant(0, 1, 1);
|
||||
var pixels = new Color32[1024];
|
||||
ColorCorrectionCurveLut.Fill(pixels, zero, one, identity, one, zero, identity, 0.5f);
|
||||
Check(pixels[0].r == 127 && pixels[256].g == 127 && pixels[767].b == 255 && pixels[768].a == 0,
|
||||
"row layout, endpoint, pre-quantization curve blend, unused row");
|
||||
bundle = AssetBundle.LoadFromFile(Environment.GetEnvironmentVariable("UMA_POSTFILM_TEST_BUNDLE"));
|
||||
foreach (var shader in bundle.LoadAllAssets<Shader>())
|
||||
if (shader.name == "Gallop/ImageEffects/ColorCorrectionCurvesSimple") material = new Material(shader);
|
||||
Check(material && material.shader.isSupported, "shipped simple shader supported");
|
||||
// Mask power=0 means full correction in DXBC (not correction disabled).
|
||||
Shader.SetGlobalFloat("_PostFilmPower", 0);
|
||||
source.Create(); output.Create();
|
||||
int checks = 0;
|
||||
Texture2D firstTexture = null;
|
||||
foreach (var key in keys)
|
||||
{
|
||||
lut.Update(key.redCurve, key.greenCurve, key.blueCurve);
|
||||
if (firstTexture == null) firstTexture = lut.Texture;
|
||||
Check(firstTexture == lut.Texture && lut.Texture.mipmapCount == 1, "reuse LUT without mips");
|
||||
foreach (float saturation in new[] { 0f, 1f, 1.4f })
|
||||
foreach (Color color in new[] { new Color(0.2f, 0.4f, 0.8f, 0.35f), new Color(0, 0.5f, 1, 0.8f), new Color(-0.2f, 1.4f, 0.7f, 1) })
|
||||
{
|
||||
Shader.SetGlobalTexture("_RgbTex", lut.Texture);
|
||||
Shader.SetGlobalFloat("_Saturation", saturation);
|
||||
RenderTexture.active = source; GL.Clear(false, true, color);
|
||||
Draw(material, lut.Texture, saturation, source, output);
|
||||
RenderTexture.active = output;
|
||||
read.ReadPixels(new Rect(0, 0, 16, 16), 0, 0); read.Apply();
|
||||
Color actual = read.GetPixel(8, 8), expected = Expected(lut.Texture, color, saturation);
|
||||
for (int c = 0; c < 4; ++c)
|
||||
Check(!float.IsNaN(actual[c]) && Mathf.Abs(actual[c] - expected[c]) < 0.0015f,
|
||||
"GPU curve row/saturation/alpha input=" + color + " saturation=" + saturation + " frame=" + key.frame + " channel=" + c + " got=" + actual[c] + " expected=" + expected[c]);
|
||||
checks++;
|
||||
}
|
||||
}
|
||||
Debug.Log("Color LUT sampling diagnostics aniso=" + lut.Texture.anisoLevel + " filter=" + lut.Texture.filterMode + " qualityAniso=" + QualitySettings.anisotropicFiltering);
|
||||
RunSpatial(bundle, material, keys, lut);
|
||||
lut.Dispose(); Check(lut.Texture == null, "LUT released");
|
||||
lut.Update(identity, identity, identity); Check(lut.Texture != null, "LUT recreated after dispose");
|
||||
Debug.Log("LiveColorCorrectionRegression PASS: actual Live1176 six curve keys, byte packing and " + checks + " GPU color/alpha checks; production command-buffer draw verified; full Live render-context parity not tested");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Shader.SetGlobalTexture("_RgbTex", oldRgb);
|
||||
Shader.SetGlobalFloat("_Saturation", oldSaturation); Shader.SetGlobalFloat("_PostFilmPower", oldPower);
|
||||
RenderTexture.active = previous;
|
||||
source.Release(); output.Release();
|
||||
UnityEngine.Object.DestroyImmediate(source); UnityEngine.Object.DestroyImmediate(output);
|
||||
UnityEngine.Object.DestroyImmediate(read); UnityEngine.Object.DestroyImmediate(sheet);
|
||||
if (material) UnityEngine.Object.DestroyImmediate(material);
|
||||
if (bundle) bundle.Unload(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1725d4c85dcb4a06ac079f31d7dbd6c8
|
||||
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Gallop;
|
||||
using Gallop.Live;
|
||||
using Gallop.Live.Cutt;
|
||||
using Gallop.RenderPipeline;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
// Runs the production Director -> image-effect adapter -> render-parameter path.
|
||||
public static class LiveDofFocusRegression
|
||||
{
|
||||
private const BindingFlags PrivateInstance = BindingFlags.Instance | BindingFlags.NonPublic;
|
||||
private static void Require(bool value, string message)
|
||||
{
|
||||
if (!value) throw new InvalidOperationException("[LiveDofFocusRegression] " + message);
|
||||
}
|
||||
private static void Set(object obj, string field, object value)
|
||||
{
|
||||
obj.GetType().GetField(field, PrivateInstance).SetValue(obj, value);
|
||||
}
|
||||
private static void SetProperty(object obj, string name, object value)
|
||||
{
|
||||
Set(obj, "<" + name + ">k__BackingField", value);
|
||||
}
|
||||
|
||||
private static void RequirePreparedFocus(float expectedDepth)
|
||||
{
|
||||
// Exercise the real shader-parameter preparation, not a duplicated formula.
|
||||
var pass = new DofDiffusionBloomOverlayPass(
|
||||
UnityEngine.Rendering.Universal.RenderPassEvent.BeforeRenderingPostProcessing);
|
||||
using (var command = new UnityEngine.Rendering.CommandBuffer())
|
||||
{
|
||||
var state = PostImageEffectFeature.RuntimeParameter.DofDiffuionBloomOverlay;
|
||||
object[] args = { default(UnityEngine.Rendering.ScriptableRenderContext), command,
|
||||
state, RenderTextureHandle.Make(Shader.PropertyToID("_DofTestSource"), 1920, 1080) };
|
||||
typeof(DofDiffusionBloomOverlayPass).GetMethod("PrepareDofParam", PrivateInstance)
|
||||
.Invoke(pass, args);
|
||||
Require(Mathf.Abs(DofDiffusionBloomOverlayPass.LastDofFocalDepth - expectedDepth) < 0.001f,
|
||||
"Prepared focal depth must use the selected focus, not the default 1m value.");
|
||||
float sharpEnd = DofDiffusionBloomOverlayPass.LastDofFocalEnd *
|
||||
DofDiffusionBloomOverlayPass.LastDofDepthRange;
|
||||
Require(Mathf.Abs(sharpEnd - expectedDepth - state.DofFocalSize * 0.5f) < 0.001f,
|
||||
"Prepared sharp interval must be centered on the selected focal depth.");
|
||||
}
|
||||
}
|
||||
|
||||
[MenuItem("UmaViewer/Rendering/Validate DOF focus routing")]
|
||||
public static void Run()
|
||||
{
|
||||
if (EditorApplication.isPlaying || Director.instance != null)
|
||||
throw new InvalidOperationException("Run outside Live/Play Mode; validation must not replace active Live state.");
|
||||
var previousRuntime = PostImageEffectFeature.RuntimeParameter;
|
||||
bool previousDof = GallopImageEffect.UserDofEnabled;
|
||||
var root = new GameObject("DofFocusRegression");
|
||||
root.SetActive(false);
|
||||
try
|
||||
{
|
||||
var director = root.AddComponent<Director>();
|
||||
var timeline = root.AddComponent<LiveTimelineControl>();
|
||||
director._liveTimelineControl = timeline;
|
||||
var cameraObject = new GameObject("DofTestCamera");
|
||||
cameraObject.transform.SetParent(root.transform);
|
||||
var camera = cameraObject.AddComponent<Camera>();
|
||||
camera.nearClipPlane = 0.8f;
|
||||
camera.farClipPlane = 1000f;
|
||||
camera.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity);
|
||||
Set(director, "_cameraObjects", new[] { camera });
|
||||
Set(director, "_activeCameraIndex", 0);
|
||||
typeof(Director).GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic).SetValue(null, director);
|
||||
var effect = cameraObject.AddComponent<GallopImageEffect>();
|
||||
effect.DofDiffusionBloomOverlayParam.IsEnableBloom = true;
|
||||
effect.DofDiffusionBloomOverlayParam.IsEnableDiffusion = true;
|
||||
effect.DofDiffusionBloomOverlayParam.BloomIntensity = 0.63f;
|
||||
SetProperty(timeline, "DofTrackCamera", camera);
|
||||
SetProperty(timeline, "DofLookAtCamera", camera);
|
||||
SetProperty(timeline, "HasDofCameraLookAt", true);
|
||||
Vector3 focus = new Vector3(0, 1, 25);
|
||||
SetProperty(timeline, "DofCameraLookAt", focus);
|
||||
var key = new LiveTimelineKeyPostEffectDOFData
|
||||
{
|
||||
frame = 0, attribute = (LiveTimelineKeyAttribute)0x50000,
|
||||
dofFocalPoint = 1f, forcalSize = 30f, blurSpread = 1.75f,
|
||||
dofSmoothness = 0.5f, dofQuality = 1, charactor = 1
|
||||
};
|
||||
var update = typeof(Director).GetMethod("OnUpdatePostEffect_Dof", PrivateInstance);
|
||||
Action apply = () => update.Invoke(director, new object[] { key });
|
||||
PostImageEffectFeature.ResetRuntimeParameter();
|
||||
GallopImageEffect.SetUserDofEnabled(true);
|
||||
apply();
|
||||
var state = PostImageEffectFeature.RuntimeParameter.DofDiffuionBloomOverlay;
|
||||
Require(state.IsEnableDof, "Valid camera look-at must enable DOF.");
|
||||
Require(state.DofFocalType == DepthBlurAndBloom.DofFocalType.Position,
|
||||
"Camera look-at was overwritten by the 1m fallback setter (expected Position, got " + state.DofFocalType + ").");
|
||||
Require(state.DofFocalPosition == focus, "Camera target must reach renderer unchanged.");
|
||||
Require(PostImageEffectFeature.RuntimeParameter.IsUseDepthTexture, "DOF must request depth.");
|
||||
RequirePreparedFocus(25f);
|
||||
|
||||
// A subsequent metric-distance key must deliberately switch modes.
|
||||
key.attribute = (LiveTimelineKeyAttribute)0x70000;
|
||||
key.dofFocalPoint = 8f;
|
||||
apply();
|
||||
state = PostImageEffectFeature.RuntimeParameter.DofDiffuionBloomOverlay;
|
||||
Require(state.DofFocalType == DepthBlurAndBloom.DofFocalType.Point &&
|
||||
Mathf.Approximately(state.DofFocalPoint, 8f), "Metric-distance focus must be retained.");
|
||||
|
||||
RequirePreparedFocus(8f - camera.nearClipPlane);
|
||||
|
||||
// Switch back on a later cut, with a different world-space target.
|
||||
key.attribute = (LiveTimelineKeyAttribute)0x50000;
|
||||
key.dofFocalPoint = 1f;
|
||||
focus = new Vector3(1, 2, 4);
|
||||
SetProperty(timeline, "DofCameraLookAt", focus);
|
||||
apply();
|
||||
state = PostImageEffectFeature.RuntimeParameter.DofDiffuionBloomOverlay;
|
||||
Require(state.DofFocalType == DepthBlurAndBloom.DofFocalType.Position &&
|
||||
state.DofFocalPosition == focus, "Cut back to positional focus must not retain metric mode.");
|
||||
|
||||
RequirePreparedFocus(4f);
|
||||
|
||||
// Invalid target bypasses only DOF, rather than falling back to 1m.
|
||||
SetProperty(timeline, "HasDofCameraLookAt", false);
|
||||
apply();
|
||||
state = PostImageEffectFeature.RuntimeParameter.DofDiffuionBloomOverlay;
|
||||
Require(!state.IsEnableDof && state.IsEnableBloom && state.IsEnableDiffusion,
|
||||
"Missing focus must preserve Bloom/Diffusion and disable DOF.");
|
||||
SetProperty(timeline, "HasDofCameraLookAt", true);
|
||||
apply();
|
||||
GallopImageEffect.SetUserDofEnabled(false);
|
||||
state = PostImageEffectFeature.RuntimeParameter.DofDiffuionBloomOverlay;
|
||||
Require(!state.IsEnableDof && state.UseDofDiffusionBloomType ==
|
||||
Gallop.ImageEffect.DofDiffusionBloomOverlayParam.DofDiffusionBloomType.DiffusionBloom,
|
||||
"User gate must bypass only DOF immediately.");
|
||||
Require(Mathf.Approximately(state.BloomIntensity, 0.63f), "DOF gate must not alter Bloom intensity.");
|
||||
GallopImageEffect.SetUserDofEnabled(true);
|
||||
state = PostImageEffectFeature.RuntimeParameter.DofDiffuionBloomOverlay;
|
||||
Require(state.IsEnableDof && state.DofFocalType == DepthBlurAndBloom.DofFocalType.Position,
|
||||
"Re-enabling while paused must restore positional focus.");
|
||||
Debug.Log("[LiveDofFocusRegression] PASS: startup look-at, metric focus, camera cut, missing target, depth request, prepared focal depths/sharp intervals, DOF gate, paused restore.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(root);
|
||||
typeof(Director).GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic).SetValue(null, null);
|
||||
typeof(PostImageEffectFeature).GetField("_runtimeParameter", BindingFlags.Static | BindingFlags.NonPublic)
|
||||
.SetValue(null, previousRuntime);
|
||||
GallopImageEffect.SetUserDofEnabled(previousDof);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d4ad2f55d984032a979b59ec86be287
|
||||
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Gallop;
|
||||
using Gallop.ImageEffect;
|
||||
using Gallop.RenderPipeline;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
// GPU regression for the recovered CoC bridge. No song/game assets required.
|
||||
public static class LiveDofForegroundRegression
|
||||
{
|
||||
[MenuItem("UmaViewer/Rendering/Validate DOF foreground (GPU)")]
|
||||
public static void Run()
|
||||
{
|
||||
if (EditorApplication.isPlaying) throw new InvalidOperationException("Run outside Play Mode.");
|
||||
if (SystemInfo.graphicsDeviceType == GraphicsDeviceType.Null)
|
||||
throw new InvalidOperationException("GPU test requires graphics; do not pass -nographics.");
|
||||
var root = new GameObject("DofForegroundRegression");
|
||||
var camera = root.AddComponent<Camera>();
|
||||
camera.enabled = false;
|
||||
camera.nearClipPlane = 0.8f;
|
||||
camera.farClipPlane = 1000f;
|
||||
var pass = new DofDiffusionBloomOverlayPass(RenderPassEvent.BeforeRenderingPostProcessing);
|
||||
var shader = Resources.Load<Shader>("RenderPipeline/LiveDofCoC");
|
||||
if (shader == null || !shader.isSupported || ShaderUtil.ShaderHasError(shader))
|
||||
throw new InvalidOperationException("LiveDofCoC shader unavailable or failed compilation.");
|
||||
float[] depths = { 5, 15, 25, 29.23675f, 31.04587f, 34.40935f, 40.81587f, 55.9f, 65, 90 };
|
||||
var depth = new Texture2D(depths.Length, 1, TextureFormat.RFloat, false, true) { filterMode = FilterMode.Point };
|
||||
var source = new Texture2D(depths.Length, 1, TextureFormat.RGBAFloat, false, true) { filterMode = FilterMode.Point };
|
||||
var result = new RenderTexture(depths.Length, 1, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear);
|
||||
var readback = new Texture2D(depths.Length, 1, TextureFormat.RGBAFloat, false, true);
|
||||
var previousRT = RenderTexture.active;
|
||||
var oldDepth = Shader.GetGlobalTexture("_CameraDepthTexture");
|
||||
string[] vectors = { "_ZBufferParams", "_CurveParams", "_GlobalScreenUVScrollParam", "_InvRenderTargetSize" };
|
||||
var oldVectors = Array.ConvertAll(vectors, name => Shader.GetGlobalVector(name));
|
||||
float oldStart = Shader.GetGlobalFloat("_GallopDofFocalStart");
|
||||
float oldSize = Shader.GetGlobalFloat("_dofForegroundSize");
|
||||
float oldWeight = Shader.GetGlobalFloat("_bloomDofWeight");
|
||||
const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
|
||||
try
|
||||
{
|
||||
result.Create();
|
||||
// Synthetic raw depth uses the same ZBufferParams as the shader, both depth conventions.
|
||||
foreach (bool reversed in new[] { false, true })
|
||||
foreach (float size in new[] { 30f, 0f })
|
||||
foreach (float foreground in new[] { 1f, 0f })
|
||||
{
|
||||
float ratio = camera.farClipPlane / camera.nearClipPlane;
|
||||
float x = reversed ? ratio - 1 : 1 - ratio;
|
||||
float y = reversed ? 1 : ratio;
|
||||
for (int i = 0; i < depths.Length; i++)
|
||||
{
|
||||
depth.SetPixel(i, 0, new Color((camera.farClipPlane / depths[i] - y) / x, 0, 0, 0));
|
||||
source.SetPixel(i, 0, new Color(0.2f, 0.4f, 0.6f, 0.7f));
|
||||
}
|
||||
depth.Apply(); source.Apply();
|
||||
var parameter = new DofDiffusionBloomOverlayPass.Parameter
|
||||
{
|
||||
TargetCamera = camera, DofFocalType = DepthBlurAndBloom.DofFocalType.Position,
|
||||
DofFocalPosition = new Vector3(0, 0, 40.81587f), DofFocalSize = size,
|
||||
DofSmoothness = 0.5f, DofForegroundSize = foreground,
|
||||
DofQualityType = (DepthBlurAndBloom.DofQuality)5
|
||||
};
|
||||
var src = RenderTextureHandle.Make(new RenderTargetIdentifier(source));
|
||||
src.Width = depths.Length; src.Height = 1;
|
||||
var dst = RenderTextureHandle.Make(new RenderTargetIdentifier(result));
|
||||
using (var cmd = new CommandBuffer())
|
||||
{
|
||||
typeof(DofDiffusionBloomOverlayPass).GetMethod("PrepareDofParam", flags).Invoke(pass,
|
||||
new object[] { default(ScriptableRenderContext), cmd, parameter, src });
|
||||
cmd.SetGlobalVector("_ZBufferParams", new Vector4(x, y, x / 1000, y / 1000));
|
||||
cmd.SetGlobalVector("_GlobalScreenUVScrollParam", Vector4.zero);
|
||||
cmd.SetGlobalTexture("_CameraDepthTexture", depth);
|
||||
cmd.SetGlobalFloat("_dofForegroundSize", foreground);
|
||||
typeof(DofDiffusionBloomOverlayPass).GetMethod("BlitDofCoc", flags).Invoke(pass,
|
||||
new object[] { cmd, parameter, src, dst, null, 0 });
|
||||
Graphics.ExecuteCommandBuffer(cmd);
|
||||
}
|
||||
RenderTexture.active = result;
|
||||
readback.ReadPixels(new Rect(0, 0, depths.Length, 1), 0, 0); readback.Apply();
|
||||
float range = camera.farClipPlane - camera.nearClipPlane;
|
||||
float focus = 40.81587f / range, half = size * 0.5f / range;
|
||||
for (int i = 0; i < depths.Length; i++)
|
||||
{
|
||||
float z = depths[i] / 1000;
|
||||
float bg = Mathf.Clamp01((z - focus - half) / (focus * 0.5f));
|
||||
float fg = Mathf.Clamp01((Mathf.Max(0, focus - half) - z) / (focus * 0.5f)) * foreground;
|
||||
var actual = readback.GetPixel(i, 0);
|
||||
if (Mathf.Abs(actual.a - Mathf.Max(bg, fg)) > 0.002f ||
|
||||
Mathf.Abs(actual.r - 0.2f) > 0.002f || Mathf.Abs(actual.g - 0.4f) > 0.002f || Mathf.Abs(actual.b - 0.6f) > 0.002f)
|
||||
throw new InvalidOperationException($"CoC mismatch depth={depths[i]} size={size} reversed={reversed}: {actual}");
|
||||
if (size == 30 && foreground == 1 && !reversed)
|
||||
Debug.Log($"[LiveDofForegroundRegression] depth={depths[i]:F2}m alpha={actual.a:F4}");
|
||||
}
|
||||
}
|
||||
Debug.Log("[LiveDofForegroundRegression] PASS: 80 GPU samples, frame3000 focus, near/far CoC, zero-width band, foreground off, RGB preserved, normal/reversed Z.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
RenderTexture.active = previousRT;
|
||||
Shader.SetGlobalTexture("_CameraDepthTexture", oldDepth);
|
||||
for (int i = 0; i < vectors.Length; i++) Shader.SetGlobalVector(vectors[i], oldVectors[i]);
|
||||
Shader.SetGlobalFloat("_GallopDofFocalStart", oldStart);
|
||||
Shader.SetGlobalFloat("_dofForegroundSize", oldSize);
|
||||
Shader.SetGlobalFloat("_bloomDofWeight", oldWeight);
|
||||
pass.Dispose(); result.Release();
|
||||
foreach (UnityEngine.Object obj in new UnityEngine.Object[] { root, depth, source, result, readback })
|
||||
UnityEngine.Object.DestroyImmediate(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27698a601219c0c429243d86175f2d93
|
||||
guid: d0b9deaed4d44dddbfe159cbdd9dcd2e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,196 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Gallop.Live.Cutt;
|
||||
using Gallop.RenderPipeline;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEditor;
|
||||
using Overlay = Gallop.ImageEffect.ScreenOverlay.Overlay;
|
||||
|
||||
public static class LivePostFilmRegression
|
||||
{
|
||||
static void Check(bool ok, string message) { if (!ok) throw new Exception("PostFilm: " + message); }
|
||||
static void Near(float a, float b, string message) { Check(Mathf.Abs(a-b)<0.002f, message + ": " + a + " vs " + b); }
|
||||
[MenuItem("UmaViewer/Rendering/Validate PostFilm timeline")]
|
||||
public static void Run()
|
||||
{
|
||||
var keys = new LiveTimelineKeyPostFilmDataList();
|
||||
var a = new LiveTimelineKeyPostFilmData { frame=10, filmMode=PostFilmMode.Add, filmPower=2,
|
||||
attribute=(LiveTimelineKeyAttribute)0x100000, color0=Color.red, color1=Color.green,
|
||||
color2=Color.blue, color3=Color.white, depthPower=-0.75f, DepthClip=2, FilmScale=Vector2.one };
|
||||
var b = new LiveTimelineKeyPostFilmData { frame=30, filmMode=PostFilmMode.Mul, filmPower=4,
|
||||
interpolateType=LiveCameraInterpolateType.Linear, color0=Color.red, color1=Color.green,
|
||||
color2=Color.blue, color3=Color.white, depthPower=-0.25f, DepthClip=2, FilmScale=Vector2.one };
|
||||
keys.thisList.Add(a); keys.thisList.Add(b);
|
||||
var output = new Overlay();
|
||||
foreach (PostColorType type in Enum.GetValues(typeof(PostColorType)))
|
||||
{
|
||||
a.colorType=type;
|
||||
LivePostFilmTimeline.Evaluate(keys,20,TimelinePlayerMode.Default,output);
|
||||
Near(output.postFilmPower,3,"raw unclamped power"); Near(output.depthPower,-0.5f,"signed depth");
|
||||
Check(output.inverseVignette && output.IsEnableDepth,"inverse is not depth disable");
|
||||
Check(output.postFilmMode==Overlay.PostFilmMode.Add,"mode must not switch before cut");
|
||||
Check(output.postFilmColor0==Color.red,"corner0");
|
||||
Check(output.postFilmColor1==(type==PostColorType.ColorAll || type==PostColorType.Color2TopBottom ? Color.red:Color.green),"corner1");
|
||||
Check(output.postFilmColor2==(type==PostColorType.ColorAll || type==PostColorType.Color2LeftRight ? Color.red:type==PostColorType.Color2TopBottom ? Color.green:Color.blue),"corner2");
|
||||
Check(output.postFilmColor3==(type==PostColorType.ColorAll ? Color.red:type==PostColorType.Color4 ? Color.white:Color.green),"corner3");
|
||||
}
|
||||
LivePostFilmTimeline.Evaluate(keys,30,TimelinePlayerMode.Default,output);
|
||||
Check(output.postFilmMode==Overlay.PostFilmMode.Mul && !output.inverseVignette,"exact cut");
|
||||
LivePostFilmTimeline.Evaluate(keys,20,TimelinePlayerMode.Default,output); Near(output.postFilmPower,3,"seek backward");
|
||||
LivePostFilmTimeline.Evaluate(keys,0,TimelinePlayerMode.Default,output); Check(!output.IsValid(),"before first key");
|
||||
keys.attribute=LiveTimelineKeyDataListAttr.Disable;
|
||||
LivePostFilmTimeline.Evaluate(keys,20,TimelinePlayerMode.Default,output); Check(!output.IsValid(),"disabled track");
|
||||
keys.attribute=0; a.layerMode=LiveTimelineKeyPostFilmData.LayerMode.UVMovie;
|
||||
LivePostFilmTimeline.Evaluate(keys,20,TimelinePlayerMode.Default,output); Check(!output.IsValid(),"unsupported movie bypass");
|
||||
a.layerMode=LiveTimelineKeyPostFilmData.LayerMode.Color; a.FilmScale=Vector2.zero; b.FilmScale=Vector2.zero;
|
||||
LivePostFilmTimeline.Evaluate(keys,20,TimelinePlayerMode.Default,output); Near(output.ScaleParameter.x,1,"safe scale");
|
||||
LivePostFilmTimeline.Evaluate(null,20,TimelinePlayerMode.Default,output); Check(!output.IsValid(),"missing track reset");
|
||||
var sheet=ScriptableObject.CreateInstance<LiveTimelineWorkSheet>();
|
||||
try {
|
||||
JsonUtility.FromJsonOverwrite("{\"postFilmKeys\":{\"thisList\":[{\"frame\":0,\"filmMode\":5,\"attribute\":1048576,\"filmPower\":0.1}]},\"postFilm2Keys\":{\"thisList\":[]},\"postFilm3Keys\":{\"thisList\":[]}}",sheet);
|
||||
Check(sheet.postFilmKeys.Count==1 && sheet.postFilmKeys.thisList[0].filmMode==PostFilmMode.VignetteAdd,"worksheet deserialization");
|
||||
} finally { UnityEngine.Object.DestroyImmediate(sheet); }
|
||||
ValidateBlinkSync();
|
||||
Debug.Log("LivePostFilmRegression timeline PASS");
|
||||
}
|
||||
|
||||
private static void ValidateBlinkSync()
|
||||
{
|
||||
var keys=new LiveTimelineKeyPostFilmDataList();
|
||||
var a=new LiveTimelineKeyPostFilmData { frame=10, filmMode=PostFilmMode.Add,
|
||||
layerMode=LiveTimelineKeyPostFilmData.LayerMode.Color, colorType=PostColorType.Color4,
|
||||
attribute=(LiveTimelineKeyAttribute)0x1e00000,
|
||||
BlinkLightName="stage_beam", BlinkLightContainerIndex=2,
|
||||
BlinkLightBrightnessPower=1f, IsAdjustedBlinkLightColor=false,
|
||||
color0=new Color(.1f,.2f,.3f,.11f), color1=new Color(.1f,.2f,.3f,.22f),
|
||||
color2=new Color(.1f,.2f,.3f,.33f), color3=new Color(.1f,.2f,.3f,.44f), FilmScale=Vector2.one };
|
||||
var b=new LiveTimelineKeyPostFilmData { frame=30, filmMode=PostFilmMode.Add,
|
||||
interpolateType=LiveCameraInterpolateType.Linear, colorType=PostColorType.Color4,
|
||||
color0=Color.white, color1=Color.white, color2=Color.white, color3=Color.white,
|
||||
FilmScale=Vector2.one };
|
||||
keys.thisList.Add(a); keys.thisList.Add(b);
|
||||
var output=new Overlay();
|
||||
int lookups=0;
|
||||
LivePostFilmTimeline.BlinkColorResolver resolve=delegate(string name,int hash,int index,out Color raw) {
|
||||
Check(name=="stage_beam" && index==2,"lookup identity");
|
||||
lookups++; raw=new Color(.2f,.4f,.6f,.9f); return true;
|
||||
};
|
||||
LivePostFilmTimeline.Evaluate(keys,20,TimelinePlayerMode.Default,output,resolve);
|
||||
Check(lookups==1,"one lookup shared by all four corners");
|
||||
foreach(Color c in new[]{output.postFilmColor0,output.postFilmColor1,output.postFilmColor2,output.postFilmColor3})
|
||||
Near(c.r,.6f,"synced R interpolates to next unsynced color");
|
||||
Near(output.postFilmColor0.a,.555f,"synced corner0 preserves authored alpha");
|
||||
Near(output.postFilmColor1.a,.61f,"synced corner1 preserves authored alpha");
|
||||
Near(output.postFilmColor2.a,.665f,"synced corner2 preserves authored alpha");
|
||||
Near(output.postFilmColor3.a,.72f,"synced corner3 preserves authored alpha");
|
||||
b.attribute=(LiveTimelineKeyAttribute)0x1e00000;
|
||||
LivePostFilmTimeline.Evaluate(keys,20,TimelinePlayerMode.Default,output,resolve);
|
||||
foreach(Color c in new[]{output.postFilmColor0,output.postFilmColor1,output.postFilmColor2,output.postFilmColor3})
|
||||
Near(c.r,.2f,"next sync holds current resolved RGB");
|
||||
Near(output.postFilmColor3.a,.44f,"next sync holds current authored alpha");
|
||||
a.attribute=0;
|
||||
LivePostFilmTimeline.Evaluate(keys,20,TimelinePlayerMode.Default,output,resolve);
|
||||
Near(output.postFilmColor0.r,.1f,"next sync holds authored current without lookup");
|
||||
a.attribute=(LiveTimelineKeyAttribute)0x200000;
|
||||
b.attribute=0;
|
||||
a.BlinkLightBrightnessPower=0f; a.IsAdjustedBlinkLightColor=true;
|
||||
LivePostFilmTimeline.Evaluate(keys,10,TimelinePlayerMode.Default,output,resolve);
|
||||
Near(output.postFilmColor0.r,1f,"key brightness zero plus adjusted color");
|
||||
Near(output.postFilmColor1.r,.1f,"only selected corner synced");
|
||||
Near(output.postFilmColor0.a,.11f,"raw stage alpha not copied");
|
||||
LivePostFilmTimeline.BlinkColorResolver missing=delegate(string name,int hash,int index,out Color raw) {
|
||||
raw=Color.black; return false;
|
||||
};
|
||||
LivePostFilmTimeline.Evaluate(keys,10,TimelinePlayerMode.Default,output,missing);
|
||||
Near(output.postFilmColor0.r,.1f,"failed lookup retains authored color");
|
||||
a.BlinkLightBrightnessPower=3f; a.IsAdjustedBlinkLightColor=false;
|
||||
LivePostFilmTimeline.Evaluate(keys,10,TimelinePlayerMode.Default,output,resolve);
|
||||
Check(output.postFilmColor0.b>1f,"key brightness can produce HDR color");
|
||||
Debug.Log("LivePostFilmRegression blink sync PASS");
|
||||
}
|
||||
|
||||
// Only for an isolated batch-mode project: this test changes global shader state.
|
||||
public static void RunGpu()
|
||||
{
|
||||
Check(Application.isBatchMode,"GPU regression must run in isolated batch mode");
|
||||
Run();
|
||||
string bundlePath=Environment.GetEnvironmentVariable("UMA_POSTFILM_TEST_BUNDLE");
|
||||
Check(File.Exists(bundlePath),"set UMA_POSTFILM_TEST_BUNDLE to the locally extracted shader bundle");
|
||||
var bundle=AssetBundle.LoadFromFile(bundlePath);
|
||||
Material material=null;
|
||||
var source=new RenderTexture(32,32,0,RenderTextureFormat.ARGBFloat,RenderTextureReadWrite.Linear);
|
||||
var target=new RenderTexture(32,32,0,RenderTextureFormat.ARGBFloat,RenderTextureReadWrite.Linear);
|
||||
var read=new Texture2D(32,32,TextureFormat.RGBAFloat,false,true);
|
||||
var previous=RenderTexture.active;
|
||||
try {
|
||||
Shader shader=null;
|
||||
foreach(var candidate in bundle.LoadAllAssets<Shader>()) if(candidate.name=="Gallop/ImageEffects/Rich/PostBlit_Rich") shader=candidate;
|
||||
Check(shader!=null && shader.isSupported,"official shader supported");
|
||||
material=new Material(shader);
|
||||
source.Create(); target.Create();
|
||||
RenderTexture.active=source; GL.Clear(false,true,new Color(0.2f,0.3f,0.4f,1));
|
||||
var overlay=new Overlay(); LivePostFilmTimeline.Reset(overlay);
|
||||
overlay.postFilmMode=Overlay.PostFilmMode.Add; overlay.postFilmPower=0.5f;
|
||||
overlay.postFilmColor0=overlay.postFilmColor1=overlay.postFilmColor2=overlay.postFilmColor3=new Color(0.4f,0.2f,0.1f,1);
|
||||
overlay.IsEnableDepth=true;
|
||||
var renderer=new ScreenOverlayRender.Render();
|
||||
var param=new ScreenOverlayRender.Parameter(); param.Setup(overlay);
|
||||
foreach(bool explicitColor in new[]{false,true}) {
|
||||
using(var cmd=new CommandBuffer()) {
|
||||
if(explicitColor) cmd.EnableShaderKeyword("COLOR_ONLY"); else cmd.DisableShaderKeyword("COLOR_ONLY");
|
||||
renderer.Blit(ref param,cmd,RenderTextureHandle.Make(source),RenderTextureHandle.Make(target),material,3);
|
||||
Graphics.ExecuteCommandBuffer(cmd);
|
||||
}
|
||||
RenderTexture.active=target; read.ReadPixels(new Rect(0,0,32,32),0,0); read.Apply();
|
||||
var pixel=read.GetPixel(16,16);
|
||||
Debug.Log("PostFilm GPU COLOR_ONLY="+explicitColor+" pixel="+pixel.ToString("F5"));
|
||||
Near(pixel.r,0.4f,"Add red"); Near(pixel.g,0.4f,"Add green"); Near(pixel.b,0.45f,"Add blue");
|
||||
}
|
||||
// Both first/later passes must execute the requested inverse-vignette variant.
|
||||
overlay.postFilmMode=Overlay.PostFilmMode.VignetteAdd;
|
||||
overlay.postFilmOptionParam=new Vector4(0.3f,0.8f,0,0);
|
||||
overlay.postFilmOffsetParam=new Vector2(0,0.58f);
|
||||
Color[] normal=null;
|
||||
foreach(int pass in new[]{1,3})
|
||||
foreach(bool inverse in new[]{false,true}) {
|
||||
overlay.inverseVignette=inverse; param.Setup(overlay);
|
||||
using(var cmd=new CommandBuffer()) {
|
||||
cmd.DisableShaderKeyword("COLOR_ONLY");
|
||||
renderer.Blit(ref param,cmd,RenderTextureHandle.Make(source),RenderTextureHandle.Make(target),material,pass);
|
||||
Graphics.ExecuteCommandBuffer(cmd);
|
||||
}
|
||||
RenderTexture.active=target; read.ReadPixels(new Rect(0,0,32,32),0,0); read.Apply();
|
||||
var pixels=read.GetPixels(); float maxDifference=0;
|
||||
for(int i=0;i<pixels.Length;i++) {
|
||||
Check(!float.IsNaN(pixels[i].r) && !float.IsInfinity(pixels[i].r),"finite vignette output");
|
||||
if(inverse) maxDifference=Mathf.Max(maxDifference,Mathf.Abs(pixels[i].r-normal[i].r));
|
||||
}
|
||||
if(!inverse) normal=pixels;
|
||||
else Check(maxDifference>0.01f,"inverse pass must change the spatial mask");
|
||||
Debug.Log("PostFilm vignette pass="+pass+" inverse="+inverse+" difference="+maxDifference);
|
||||
}
|
||||
// Sequential layers use the previous layer's pixels, not the original source.
|
||||
overlay.inverseVignette=false; overlay.postFilmMode=Overlay.PostFilmMode.Add; param.Setup(overlay);
|
||||
using(var cmd=new CommandBuffer()) {
|
||||
renderer.Blit(ref param,cmd,RenderTextureHandle.Make(source),RenderTextureHandle.Make(target),material,3);
|
||||
overlay.postFilmMode=Overlay.PostFilmMode.Lerp; overlay.postFilmPower=0.5f;
|
||||
overlay.postFilmColor0=overlay.postFilmColor1=overlay.postFilmColor2=overlay.postFilmColor3=Color.black;
|
||||
param.Setup(overlay);
|
||||
renderer.Blit(ref param,cmd,RenderTextureHandle.Make(target),RenderTextureHandle.Make(source),material,3);
|
||||
Graphics.ExecuteCommandBuffer(cmd);
|
||||
}
|
||||
RenderTexture.active=source; read.ReadPixels(new Rect(0,0,32,32),0,0); read.Apply();
|
||||
var stacked=read.GetPixel(16,16);
|
||||
Near(stacked.r,0.2f,"ordered Add then Lerp red"); Near(stacked.g,0.2f,"ordered Add then Lerp green"); Near(stacked.b,0.225f,"ordered Add then Lerp blue");
|
||||
Debug.Log("LivePostFilmRegression GPU PASS");
|
||||
} finally {
|
||||
RenderTexture.active=previous;
|
||||
if(material!=null) UnityEngine.Object.DestroyImmediate(material);
|
||||
UnityEngine.Object.DestroyImmediate(source); UnityEngine.Object.DestroyImmediate(target); UnityEngine.Object.DestroyImmediate(read);
|
||||
if(bundle!=null) bundle.Unload(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4234cd02b8e84df5ad22e7939cc3c8cc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using Gallop.Live.Cutt;
|
||||
using Gallop.RenderPipeline;
|
||||
|
||||
public static class LiveRadialBlurRegression
|
||||
{
|
||||
static void Check(bool ok, string message) { if (!ok) throw new Exception("RadialBlur: " + message); }
|
||||
static void Near(float a, float b, string message) { Check(Mathf.Abs(a-b)<0.002f,message+" actual="+a+" expected="+b); }
|
||||
public static void Run()
|
||||
{
|
||||
var sheet=ScriptableObject.CreateInstance<LiveTimelineWorkSheet>();
|
||||
try {
|
||||
JsonUtility.FromJsonOverwrite("{\"radialBlurKeys\":{\"thisList\":[{\"frame\":10,\"moveBlurType\":2,\"attribute\":65536,\"radialBlurDownsample\":3,\"radialBlurIteration\":2,\"radialBlurPower\":0.5,\"depthPowerBack\":38},{\"frame\":20,\"moveBlurType\":0}]}}",sheet);
|
||||
var keys=sheet.radialBlurKeys;
|
||||
Check(keys.Count==2,"worksheet track deserialization");
|
||||
var p=LiveRadialBlurTimeline.Evaluate(keys,10,TimelinePlayerMode.Default);
|
||||
Check(p.Type==2 && p.IsEnabledDepth && p.Downsample==3 && p.Iteration==2,"discrete key values");
|
||||
Near(p.Power,0.5f,"raw strength"); Near(p.DepthPowerBack,38,"authored distance");
|
||||
Check(!LiveRadialBlurTimeline.Evaluate(keys,20,TimelinePlayerMode.Default).IsValid,"off key");
|
||||
Check(LiveRadialBlurTimeline.Evaluate(keys,10,TimelinePlayerMode.Default).IsValid,"reverse seek");
|
||||
Check(!LiveRadialBlurTimeline.Evaluate(keys,0,TimelinePlayerMode.Default).IsValid,"pre first");
|
||||
keys.attribute=LiveTimelineKeyDataListAttr.Disable;
|
||||
Check(!LiveRadialBlurTimeline.Evaluate(keys,10,TimelinePlayerMode.Default).IsValid,"disabled");
|
||||
Check(!LiveRadialBlurTimeline.Evaluate(null,10,TimelinePlayerMode.Default).IsValid,"absent track reset");
|
||||
for(int mode=1;mode<=5;mode++) Check(RadialBlurPass.BlurPassIndex(mode)==2*(mode-1),"native pass mapping");
|
||||
Check(RadialBlurPass.AreaDifference(0,0)==0.0001f,"zero interval guard");
|
||||
Check(RadialBlurPass.AreaDifference(1,0.99999f)<0,"signed interval guard");
|
||||
p.DepthCancelRect=new Vector4(0.2f,0.3f,0.4f,0.5f); p.IsExpandDepthCancelRect=true; p.DepthCancelBlendLength=0.1f;
|
||||
var bounds=RadialBlurPass.CancelBounds(p); Near(bounds.x,0.1f,"expanded min"); Near(bounds.w,0.9f,"expanded max");
|
||||
var feature=new PostImageEffectFeature.Parameter(); feature.RadialBlur=p;
|
||||
Check(feature.IsUseDepthTexture,"radial requests depth independently");
|
||||
var copy=new PostImageEffectFeature.Parameter(); copy.ShallowCopyParameters(feature);
|
||||
Check(copy.RadialBlur.Type==2,"parameter copy");
|
||||
} finally { UnityEngine.Object.DestroyImmediate(sheet); }
|
||||
string fixture=Environment.GetEnvironmentVariable("UMA_RADIAL_TEST_KEYS");
|
||||
if(!string.IsNullOrEmpty(fixture)) {
|
||||
var realKeys=JsonUtility.FromJson<LiveTimelineKeyRadialBlurDataList>(File.ReadAllText(fixture));
|
||||
Check(realKeys.Count==28,"Live1001 actual key count");
|
||||
Check(LiveRadialBlurTimeline.Evaluate(realKeys,728,TimelinePlayerMode.Default).Type==0,"real before onset");
|
||||
var onset=LiveRadialBlurTimeline.Evaluate(realKeys,729,TimelinePlayerMode.Default);
|
||||
Check(onset.Type==2&&onset.IsEnabledDepth,"real onset");Near(onset.DepthPowerBack,38,"real mask distance");
|
||||
Check(LiveRadialBlurTimeline.Evaluate(realKeys,988,TimelinePlayerMode.Default).Type==0,"real off cut");
|
||||
Check(LiveRadialBlurTimeline.Evaluate(realKeys,3965,TimelinePlayerMode.Default).Type==1,"real radial impact");
|
||||
for(int f=0;f<=7900;f++) {
|
||||
var sample=LiveRadialBlurTimeline.Evaluate(realKeys,f,TimelinePlayerMode.Default);
|
||||
Check(sample.Type==0||sample.IsValid,"real full track finite frame="+f);
|
||||
}
|
||||
Debug.Log("LiveRadialBlurRegression real Live1001 7901 timeline samples PASS");
|
||||
}
|
||||
Debug.Log("LiveRadialBlurRegression timeline PASS");
|
||||
}
|
||||
static float Difference(Color[] a, Color[] b)
|
||||
{
|
||||
float sum=0;
|
||||
for(int i=0;i<a.Length;i++) {
|
||||
Check(!float.IsNaN(a[i].r)&&!float.IsInfinity(a[i].r)&&!float.IsNaN(a[i].g)&&!float.IsInfinity(a[i].g),"finite pixels");
|
||||
sum+=Mathf.Abs(a[i].r-b[i].r)+Mathf.Abs(a[i].g-b[i].g)+Mathf.Abs(a[i].b-b[i].b);
|
||||
}
|
||||
return sum/(a.Length*3);
|
||||
}
|
||||
public static void RunGpu()
|
||||
{
|
||||
Check(Application.isBatchMode,"isolated batch mode required"); Run();
|
||||
var bundle=AssetBundle.LoadFromFile(Environment.GetEnvironmentVariable("UMA_POSTFILM_TEST_BUNDLE"));
|
||||
Material material=null;
|
||||
var source=new RenderTexture(64,64,0,RenderTextureFormat.ARGBFloat,RenderTextureReadWrite.Linear);
|
||||
var target=new RenderTexture(64,64,0,RenderTextureFormat.ARGBFloat,RenderTextureReadWrite.Linear);
|
||||
var texture=new Texture2D(64,64,TextureFormat.RGBAFloat,false,true);
|
||||
var read=new Texture2D(64,64,TextureFormat.RGBAFloat,false,true);
|
||||
var cameraObject=new GameObject("RadialRegressionCamera"); var camera=cameraObject.AddComponent<Camera>();
|
||||
camera.nearClipPlane=0.3f;camera.farClipPlane=100f;
|
||||
var previous=RenderTexture.active;
|
||||
var depthTexture=new Texture2D(64,64,TextureFormat.RFloat,false,true) { filterMode=FilterMode.Point };
|
||||
var previousDepth=Shader.GetGlobalTexture("_CameraDepthTexture");
|
||||
var previousZ=Shader.GetGlobalVector("_ZBufferParams");
|
||||
var previousScroll=Shader.GetGlobalVector("_GlobalScreenUVScrollParam");
|
||||
try {
|
||||
Shader shader=null; foreach(var candidate in bundle.LoadAllAssets<Shader>()) if(candidate.name=="Cygames/ImageEffects/RadialBlur") shader=candidate;
|
||||
Check(shader!=null&&shader.isSupported,"official shader available"); material=new Material(shader);
|
||||
source.Create();target.Create();
|
||||
var pixels=new Color[64*64];
|
||||
for(int y=0;y<64;y++) for(int x=0;x<64;x++) pixels[y*64+x]=new Color((x%8<4)?0.8f:0.1f,(y%8<4)?0.7f:0.2f,0.3f,1);
|
||||
texture.SetPixels(pixels);texture.Apply();Graphics.Blit(texture,source);
|
||||
Func<RadialBlurPass.Parameter,Color[]> render=p=>{
|
||||
using(var cmd=new CommandBuffer()) {
|
||||
RadialBlurPass.Render(cmd,material,p,camera,source,target,source.descriptor);
|
||||
Graphics.ExecuteCommandBuffer(cmd);
|
||||
}
|
||||
RenderTexture.active=target;read.ReadPixels(new Rect(0,0,64,64),0,0);read.Apply();return read.GetPixels();
|
||||
};
|
||||
var p=RadialBlurPass.Parameter.Default();p.Type=1;p.Power=0;
|
||||
var baseline=render(p);Near(Difference(baseline,pixels),0,"zero power identity");
|
||||
for(int mode=1;mode<=5;mode++) {
|
||||
p.Type=mode;p.Power=2;p.StartArea=0;p.EndArea=0;p.Downsample=2;p.Iteration=2;p.RollEulerAngles=35;
|
||||
var result=render(p);float difference=Difference(result,baseline);
|
||||
Check(difference>0.005f,"mode "+mode+" actually changes source");
|
||||
Debug.Log("Radial GPU mode="+mode+" meanDifference="+difference);
|
||||
}
|
||||
// Real shader's depth gate: front < z <= back preserves source, farther pixels blur.
|
||||
p.Type=2;p.Downsample=1;p.Iteration=1;p.Power=2;p.IsEnabledDepth=true;
|
||||
p.DepthPowerFront=0;p.DepthPowerBack=38;
|
||||
foreach(bool reversed in new[]{false,true}) {
|
||||
float ratio=camera.farClipPlane/camera.nearClipPlane;
|
||||
float zx=reversed?ratio-1:1-ratio, zy=reversed?1:ratio;
|
||||
Shader.SetGlobalVector("_ZBufferParams",new Vector4(zx,zy,zx/100,zy/100));
|
||||
Shader.SetGlobalVector("_GlobalScreenUVScrollParam",Vector4.zero);
|
||||
for(int y=0;y<64;y++) for(int x=0;x<64;x++) {
|
||||
float distance=x<32?10:60;
|
||||
depthTexture.SetPixel(x,y,new Color((100/distance-zy)/zx,0,0,0));
|
||||
}
|
||||
depthTexture.Apply();Shader.SetGlobalTexture("_CameraDepthTexture",depthTexture);
|
||||
var gated=render(p);float protectedError=0,farDifference=0;
|
||||
for(int y=4;y<60;y++) for(int x=4;x<60;x++) {
|
||||
float d=Mathf.Abs(gated[y*64+x].r-baseline[y*64+x].r);
|
||||
if(x<28)protectedError=Mathf.Max(protectedError,d);
|
||||
if(x>36)farDifference+=d;
|
||||
}
|
||||
Near(protectedError,0,"near object depth protection reversed="+reversed);
|
||||
Check(farDifference>1,"far background blur reversed="+reversed);
|
||||
p.IsEnabledDepthCancelRect=true;p.DepthCancelRect=new Vector4(0.1f,0.1f,0.3f,0.8f);
|
||||
p.DepthCancelBlendLength=0.02f;
|
||||
var cancelled=render(p);float restoredBlur=0;
|
||||
for(int y=12;y<52;y++)for(int x=10;x<22;x++)restoredBlur+=Mathf.Abs(cancelled[y*64+x].r-gated[y*64+x].r);
|
||||
Check(restoredBlur>1,"cancel rectangle overrides depth protection");
|
||||
p.IsEnabledDepthCancelRect=false;
|
||||
Debug.Log("Radial depth GPU reversed="+reversed+" protectedError="+protectedError+" farDifference="+farDifference+" rectDifference="+restoredBlur);
|
||||
}
|
||||
p.IsEnabledDepth=true;p.IsEnabledDepthCancelRect=true;p.DepthPowerFront=4;p.DepthPowerBack=38;
|
||||
p.DepthCancelRect=new Vector4(0.2f,0.3f,0.4f,0.5f);p.DepthCancelBlendLength=0.1f;
|
||||
RadialBlurPass.ApplyMaterial(material,p,camera);
|
||||
Near(material.GetFloat("_DepthPowerFront"),(4-0.3f)/99.7f,"normalized front");
|
||||
Near(material.GetFloat("_DepthPowerBack"),(38-0.3f)/99.7f,"normalized back");
|
||||
Check(material.IsKeywordEnabled("ENABLE_DEPTH")&&material.IsKeywordEnabled("ENABLE_DEPTH_CANCEL_RECT"),"depth keywords");
|
||||
p.IsEnabledDepth=false;RadialBlurPass.ApplyMaterial(material,p,camera);
|
||||
Check(!material.IsKeywordEnabled("ENABLE_DEPTH")&&!material.IsKeywordEnabled("ENABLE_DEPTH_CANCEL_RECT"),"stale keyword reset");
|
||||
Near(material.GetFloat("_DepthPowerBack"),0,"stale distance reset");
|
||||
p.Downsample=0;p.Iteration=0;Difference(render(p),baseline);
|
||||
Debug.Log("LiveRadialBlurRegression GPU PASS");
|
||||
} finally {
|
||||
RenderTexture.active=previous;
|
||||
Shader.SetGlobalTexture("_CameraDepthTexture",previousDepth);
|
||||
Shader.SetGlobalVector("_ZBufferParams",previousZ);
|
||||
Shader.SetGlobalVector("_GlobalScreenUVScrollParam",previousScroll);
|
||||
UnityEngine.Object.DestroyImmediate(depthTexture);
|
||||
UnityEngine.Object.DestroyImmediate(material);UnityEngine.Object.DestroyImmediate(source);UnityEngine.Object.DestroyImmediate(target);
|
||||
UnityEngine.Object.DestroyImmediate(texture);UnityEngine.Object.DestroyImmediate(read);UnityEngine.Object.DestroyImmediate(cameraObject);
|
||||
if(bundle!=null) bundle.Unload(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa08d67efb5849ba86a6209017d814b6
|
||||
@@ -0,0 +1,407 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Gallop;
|
||||
using Gallop.Live;
|
||||
using Gallop.RenderPipeline;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
/// <summary>Opt-in graphics smoke test. Does not alter saved scenes or user configuration.</summary>
|
||||
[InitializeOnLoad]
|
||||
public static class LiveRenderCapture
|
||||
{
|
||||
const string Armed = "UmaViewer.RenderCapture.Armed";
|
||||
static double deadline;
|
||||
static bool requested;
|
||||
static int captureIndex;
|
||||
const string PlanState = Armed + ".Plan", ExitState = Armed + ".Exit", FilmIsolationState = Armed + ".FilmIsolation";
|
||||
static LiveCapturePlan plan;
|
||||
static readonly LiveCaptureStabilityGate gate = new LiveCaptureStabilityGate();
|
||||
static bool exitOnFinish;
|
||||
static bool filmIsolation;
|
||||
static bool filmSuppressed;
|
||||
static Gallop.ImageEffect.ScreenOverlay.Overlay.PostFilmMode savedMode1, savedMode2, savedMode3;
|
||||
static bool suppressedThisRender;
|
||||
static int CaptureSlots => filmIsolation ? 3 : 2;
|
||||
static int CaptureFrame => plan.frames[captureIndex / CaptureSlots];
|
||||
static int CaptureVariant => captureIndex % CaptureSlots;
|
||||
static string VariantName => CaptureVariant == 0 ? "off" : filmIsolation && CaptureVariant == 1 ? "noFilm" : "on";
|
||||
static float previousProgress;
|
||||
static bool previousSync, previousTouched, previousOuted;
|
||||
static Director capturedDirector;
|
||||
static Camera camera;
|
||||
static RenderTexture target, previousTarget;
|
||||
static PostImageEffectFeature feature;
|
||||
static bool originalActive;
|
||||
static bool originalRendering;
|
||||
static string output;
|
||||
static bool finishing;
|
||||
static CapturePreview preview;
|
||||
|
||||
static LiveRenderCapture()
|
||||
{
|
||||
EditorApplication.update += Tick;
|
||||
RenderPipelineManager.beginCameraRendering += BeforeCamera;
|
||||
RenderPipelineManager.endCameraRendering += AfterCamera;
|
||||
AssemblyReloadEvents.beforeAssemblyReload += () => {
|
||||
if (SessionState.GetBool(Armed, false) && capturedDirector != null)
|
||||
Finish("Cancelled before script reload", 0);
|
||||
if (finishing) RestoreCaptureState();
|
||||
};
|
||||
}
|
||||
|
||||
static void Configure(bool exit)
|
||||
{
|
||||
string reference = Environment.GetEnvironmentVariable("UMA_LIVE_CAPTURE_REFERENCE");
|
||||
if (string.IsNullOrEmpty(reference)) plan = LiveCapturePlan.Default();
|
||||
else
|
||||
{
|
||||
// Only a capture manifest, never arbitrary application configuration.
|
||||
if (!string.Equals(Path.GetFileName(reference), "manifest.json", StringComparison.OrdinalIgnoreCase))
|
||||
throw new InvalidOperationException("UMA_LIVE_CAPTURE_REFERENCE must name a reference manifest.json.");
|
||||
plan = JsonUtility.FromJson<LiveCapturePlan>(File.ReadAllText(reference));
|
||||
}
|
||||
plan.Validate();
|
||||
string frames = Environment.GetEnvironmentVariable("UMA_LIVE_CAPTURE_FRAMES");
|
||||
if (!string.IsNullOrWhiteSpace(frames)) plan.SelectFrames(frames.Split(',').Select(int.Parse).ToArray());
|
||||
SessionState.SetString(PlanState, JsonUtility.ToJson(plan));
|
||||
SessionState.SetBool(ExitState, exit);
|
||||
exitOnFinish = exit;
|
||||
filmIsolation = false;
|
||||
SessionState.SetBool(FilmIsolationState, false);
|
||||
captureIndex = 0; gate.Reset(); requested = false; deadline = 0;
|
||||
}
|
||||
|
||||
public static void Start()
|
||||
{
|
||||
if (!Application.isBatchMode)
|
||||
throw new InvalidOperationException("Automatic scene-loading capture is restricted to an isolated batch Editor. Use Capture current Live in an interactive Editor.");
|
||||
if (SessionState.GetBool(Armed, false) || finishing) throw new InvalidOperationException("A capture is already running or restoring state.");
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
throw new InvalidOperationException("Start capture from edit mode.");
|
||||
Configure(true);
|
||||
LiveRenderSetup.Install();
|
||||
SessionState.SetBool(Armed, true);
|
||||
EditorSceneManager.OpenScene("Assets/Scenes/Version2.unity");
|
||||
EditorApplication.isPlaying = true;
|
||||
}
|
||||
|
||||
[MenuItem("UmaViewer/Rendering/Capture current Live diagnostic frames (308,868,3848)")]
|
||||
public static void CaptureCurrentDiagnosticFrames()
|
||||
{
|
||||
BeginCurrentLiveCapture(true);
|
||||
}
|
||||
|
||||
[MenuItem("UmaViewer/Rendering/Capture current Live BlinkFilm diagnostic frames (900,1240,3900)")]
|
||||
public static void CaptureCurrentBlinkFilmFrames()
|
||||
{
|
||||
// Live1176/stage10148 authored PostFilm sync intervals; report still requires
|
||||
// identity/parameter checks if a different song is currently loaded.
|
||||
BeginCurrentLiveCapture(true, new[] { 900, 1240, 3900 });
|
||||
}
|
||||
|
||||
[MenuItem("UmaViewer/Rendering/Capture current Live off/noFilm/on (900,1240,3900)")]
|
||||
public static void CaptureCurrentFilmIsolation()
|
||||
{
|
||||
BeginCurrentLiveCapture(true, new[] { 900, 1240, 3900 });
|
||||
filmIsolation = true;
|
||||
SessionState.SetBool(FilmIsolationState, true);
|
||||
}
|
||||
|
||||
[MenuItem("UmaViewer/Rendering/Capture current Live post-effects off-on")]
|
||||
public static void CaptureCurrentLive()
|
||||
{
|
||||
BeginCurrentLiveCapture(false);
|
||||
}
|
||||
|
||||
private static void BeginCurrentLiveCapture(bool diagnostic, int[] diagnosticFrames = null)
|
||||
{
|
||||
if (SessionState.GetBool(Armed, false) || finishing) throw new InvalidOperationException("A capture is already running or restoring state.");
|
||||
var director = Director.instance;
|
||||
if (!EditorApplication.isPlaying || director == null || !director._isLiveSetup)
|
||||
throw new InvalidOperationException("Load the reference Live and characters first.");
|
||||
if (director.IsRecordVMD) throw new InvalidOperationException("Capture cannot seek while VMD recording is active.");
|
||||
Configure(false);
|
||||
if (diagnostic)
|
||||
{
|
||||
// A same-session off/on experiment, NOT a matched game-reference capture.
|
||||
// Use actual cast identity instead of silently imposing the song1001 preset.
|
||||
plan = new LiveCapturePlan {
|
||||
songId = director.live.MusicId,
|
||||
stageId = director.live.BackGroundId,
|
||||
characters = director.CharaContainerScript.Select(x => x.CharaEntry.Id + "_" + x.VarCostumeIdLong).ToArray(),
|
||||
frames = diagnosticFrames ?? new[] { 308, 868, 3848 }
|
||||
};
|
||||
plan.Validate();
|
||||
if (plan.frames.Any(x => x / (float)plan.fps >= director.totalTime))
|
||||
throw new InvalidOperationException("Diagnostic frame exceeds the loaded Live duration.");
|
||||
SessionState.SetString(PlanState, JsonUtility.ToJson(plan));
|
||||
}
|
||||
requested = true;
|
||||
deadline = EditorApplication.timeSinceStartup + 180;
|
||||
captureIndex = 0; gate.Reset();
|
||||
capturedDirector = director;
|
||||
previousProgress = director.UI.ProgressBar.value;
|
||||
previousSync = director._syncTime;
|
||||
previousTouched = director.sliderControl.is_Touched;
|
||||
previousOuted = director.sliderControl.is_Outed;
|
||||
SessionState.SetBool(Armed, true);
|
||||
preview = ScriptableObject.CreateInstance<CapturePreview>();
|
||||
preview.titleContent = new GUIContent("Live capture preview");
|
||||
preview.minSize = new Vector2(560, 380);
|
||||
preview.ShowUtility();
|
||||
Debug.Log("[LiveRenderCapture] Capturing off-screen: Live time is held temporarily. Use the preview to cancel; playback is restored on completion.");
|
||||
}
|
||||
|
||||
[MenuItem("UmaViewer/Rendering/Cancel Live capture")]
|
||||
public static void CancelCapture()
|
||||
{
|
||||
if (SessionState.GetBool(Armed, false)) Finish("Cancelled by user", 0);
|
||||
}
|
||||
|
||||
private sealed class CapturePreview : EditorWindow
|
||||
{
|
||||
void OnInspectorUpdate() { Repaint(); }
|
||||
void OnGUI()
|
||||
{
|
||||
EditorGUILayout.HelpBox("Capture uses an off-screen camera target. The Game view may say 'No cameras rendering'. Live time is held while each frame settles; this preview shows the capture target.", MessageType.Info);
|
||||
if (plan != null && captureIndex < plan.frames.Length * CaptureSlots)
|
||||
GUILayout.Label("Frame " + CaptureFrame + " / effects " + VariantName + " / saved " + captureIndex + " of " + plan.frames.Length * CaptureSlots);
|
||||
if (GUILayout.Button("Cancel capture and restore playback")) CancelCapture();
|
||||
var rect = GUILayoutUtility.GetRect(100, 100, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
|
||||
if (target != null) GUI.DrawTexture(rect, target, ScaleMode.ScaleToFit, false);
|
||||
}
|
||||
void OnDisable() { if (!finishing) CancelCapture(); }
|
||||
}
|
||||
|
||||
static void Tick()
|
||||
{
|
||||
if (!SessionState.GetBool(Armed, false)) return;
|
||||
// Entering play mode reloads statics. Restore intent rather than hanging
|
||||
// the isolated Editor after a successful batch capture.
|
||||
if (plan == null)
|
||||
{
|
||||
plan = JsonUtility.FromJson<LiveCapturePlan>(SessionState.GetString(PlanState, ""));
|
||||
exitOnFinish = SessionState.GetBool(ExitState, false);
|
||||
filmIsolation = SessionState.GetBool(FilmIsolationState, false);
|
||||
if (plan == null) { Finish("Capture plan missing after reload", 1); return; }
|
||||
}
|
||||
if (deadline == 0) deadline = EditorApplication.timeSinceStartup + System.Math.Max(600, plan.frames.Length * 20);
|
||||
if (EditorApplication.timeSinceStartup > deadline) { Finish("Timed out loading/capturing Live", 1); return; }
|
||||
if (!EditorApplication.isPlaying) return;
|
||||
try
|
||||
{
|
||||
var director = Director.instance;
|
||||
if (director == null || !director._isLiveSetup)
|
||||
{
|
||||
if (requested) return;
|
||||
var main = UmaViewerMain.Instance;
|
||||
var ui = UmaViewerUI.Instance;
|
||||
if (main == null || ui == null || !ShaderManager.IsShaderBundleReady) return;
|
||||
var live = main.Lives.FirstOrDefault(x => x.MusicId == plan.songId);
|
||||
if (live == null || main.Characters.Count < live.MemberCount) return;
|
||||
var selections = new List<LiveCharacterSelect>();
|
||||
if (live.MemberCount != plan.characters.Length || live.BackGroundId != plan.stageId)
|
||||
throw new InvalidOperationException("Reference song/stage/member count differs from loaded Live metadata.");
|
||||
for (int i = 0; i < live.MemberCount; ++i)
|
||||
{
|
||||
var item = new GameObject("CaptureSelection_" + i).AddComponent<LiveCharacterSelect>();
|
||||
LiveCapturePlan.ParseCharacterIdentity(plan.characters[i], out int id, out string costume);
|
||||
item.CharaEntry = main.Characters.First(x => !x.IsMob && x.Id == id);
|
||||
item.CostumeId = costume;
|
||||
selections.Add(item);
|
||||
}
|
||||
ui.LiveMode = 1;
|
||||
ui.isRequireStage = true;
|
||||
ui.isRecordVMD = false;
|
||||
requested = true;
|
||||
Debug.Log("[LiveRenderCapture] loading live" + plan.songId + " characters=" + string.Join(",", selections.Select(x => x.CharaEntry.Id + "_" + x.CostumeId)));
|
||||
UmaViewerBuilder.Instance.LoadLive(live, selections);
|
||||
return;
|
||||
}
|
||||
if (capturedDirector == null)
|
||||
{
|
||||
if (director.IsRecordVMD) throw new InvalidOperationException("Capture cannot seek while VMD recording is active.");
|
||||
capturedDirector = director;
|
||||
previousProgress = director.UI.ProgressBar.value;
|
||||
previousSync = director._syncTime;
|
||||
previousTouched = director.sliderControl.is_Touched;
|
||||
previousOuted = director.sliderControl.is_Outed;
|
||||
}
|
||||
if (!plan.MatchesIdentity(director.live.MusicId, director.live.BackGroundId,
|
||||
director.CharaContainerScript.Select(x => x.CharaEntry.Id + "_" + x.VarCostumeIdLong).ToArray()))
|
||||
throw new InvalidOperationException("Loaded Live cast/costumes/song/stage do not match capture reference.");
|
||||
if (plan.frames.Any(x => x / (float)plan.fps >= director.totalTime))
|
||||
throw new InvalidOperationException("Reference capture frame lies outside this Live timeline.");
|
||||
director._syncTime = true;
|
||||
director.sliderControl.is_Outed = false;
|
||||
director.sliderControl.is_Touched = true;
|
||||
director.UI.ProgressBar.SetValueWithoutNotify(CaptureFrame / ((float)plan.fps * director.totalTime));
|
||||
if (target == null)
|
||||
{
|
||||
output = Path.GetFullPath("captures/render-smoke/" + DateTime.Now.ToString("yyyyMMdd_HHmmss_fff"));
|
||||
Directory.CreateDirectory(output);
|
||||
var renderer = AssetDatabase.LoadAssetAtPath<UniversalRendererData>("Assets/Resources/RenderPipeline/UMAUniversalRenderPipelineAsset_Renderer.asset");
|
||||
feature = renderer.rendererFeatures.OfType<PostImageEffectFeature>().Single();
|
||||
originalActive = feature.isActive;
|
||||
originalRendering = feature.EnableRendering;
|
||||
target = new RenderTexture(plan.width, plan.height, 24, RenderTextureFormat.ARGB32);
|
||||
target.Create();
|
||||
feature.SetActive(true);
|
||||
feature.EnableRendering = false;
|
||||
File.WriteAllText(Path.Combine(output, "capture-plan.json"), JsonUtility.ToJson(plan, true));
|
||||
Debug.Log("[LiveRenderCapture] output=" + output);
|
||||
}
|
||||
if (camera != director.MainRenderCamera)
|
||||
{
|
||||
if (camera != null) camera.targetTexture = previousTarget;
|
||||
camera = director.MainRenderCamera;
|
||||
if (camera == null) return;
|
||||
previousTarget = camera.targetTexture;
|
||||
camera.targetTexture = target;
|
||||
gate.Reset();
|
||||
}
|
||||
}
|
||||
catch (Exception e) { Finish(e.ToString(), 1); }
|
||||
}
|
||||
|
||||
static void BeforeCamera(ScriptableRenderContext context, Camera renderedCamera)
|
||||
{
|
||||
if (!SessionState.GetBool(Armed, false) || !filmIsolation || CaptureVariant != 1 ||
|
||||
target == null || renderedCamera != camera || feature == null || !feature.isActive || filmSuppressed) return;
|
||||
// Scoped to one camera's URP render. The timeline owns these values; never
|
||||
// mutate them outside begin/endCameraRendering or persist them to scene assets.
|
||||
SuppressFilm();
|
||||
suppressedThisRender = true;
|
||||
}
|
||||
|
||||
// Kept separate from the SRP callback so the exact struct write/restore
|
||||
// contract can be checked in an isolated Editor without loading a Live.
|
||||
internal static void SuppressFilm()
|
||||
{
|
||||
if (filmSuppressed) throw new InvalidOperationException("Film capture suppression is already active.");
|
||||
var runtime = PostImageEffectFeature.RuntimeParameter;
|
||||
var p = runtime.DofDiffuionBloomOverlay;
|
||||
savedMode1 = p.Overlay1.PostFilmMode;
|
||||
savedMode2 = p.Overlay2.PostFilmMode;
|
||||
savedMode3 = p.Overlay3.PostFilmMode;
|
||||
p.Overlay1.PostFilmMode = Gallop.ImageEffect.ScreenOverlay.Overlay.PostFilmMode.None;
|
||||
p.Overlay2.PostFilmMode = Gallop.ImageEffect.ScreenOverlay.Overlay.PostFilmMode.None;
|
||||
p.Overlay3.PostFilmMode = Gallop.ImageEffect.ScreenOverlay.Overlay.PostFilmMode.None;
|
||||
runtime.DofDiffuionBloomOverlay = p;
|
||||
filmSuppressed = true;
|
||||
}
|
||||
|
||||
internal static void RestoreFilm()
|
||||
{
|
||||
if (!filmSuppressed) return;
|
||||
var runtime = PostImageEffectFeature.RuntimeParameter;
|
||||
var p = runtime.DofDiffuionBloomOverlay;
|
||||
// Restore only the fields we changed. Other timeline-owned parameters
|
||||
// must survive even if another camera callback refreshed them.
|
||||
p.Overlay1.PostFilmMode = savedMode1;
|
||||
p.Overlay2.PostFilmMode = savedMode2;
|
||||
p.Overlay3.PostFilmMode = savedMode3;
|
||||
runtime.DofDiffuionBloomOverlay = p;
|
||||
filmSuppressed = false;
|
||||
}
|
||||
|
||||
static void AfterCamera(ScriptableRenderContext context, Camera renderedCamera)
|
||||
{
|
||||
if (!SessionState.GetBool(Armed, false) || target == null || renderedCamera != camera) return;
|
||||
bool wasSuppressed = filmSuppressed && suppressedThisRender;
|
||||
RestoreFilm();
|
||||
suppressedThisRender = false;
|
||||
var director = Director.instance;
|
||||
if (director == null || director != capturedDirector || camera != director.MainRenderCamera) { gate.Reset(); return; }
|
||||
if (!gate.Observe(director._liveCurrentTime, CaptureFrame, plan.fps,
|
||||
Time.frameCount, ShaderManager.IsShaderBundleReady && feature.isActive && feature.EnableRendering == (CaptureVariant != 0) &&
|
||||
(CaptureVariant != 1 || !filmIsolation || wasSuppressed))) return;
|
||||
Texture2D image = null;
|
||||
var previous = RenderTexture.active;
|
||||
try
|
||||
{
|
||||
context.Submit();
|
||||
RenderTexture.active = target;
|
||||
image = new Texture2D(target.width, target.height, TextureFormat.RGB24, false);
|
||||
image.ReadPixels(new Rect(0, 0, target.width, target.height), 0, 0);
|
||||
image.Apply();
|
||||
string name = "frame" + CaptureFrame.ToString("D6") + "_" + VariantName;
|
||||
File.WriteAllBytes(Path.Combine(output, name + ".png"), image.EncodeToPNG());
|
||||
var p = PostImageEffectFeature.RuntimeParameter.DofDiffuionBloomOverlay;
|
||||
File.WriteAllText(Path.Combine(output, name + ".txt"),
|
||||
$"requestedFrame={CaptureFrame}\nfps={plan.fps}\ntime={Director.instance._liveCurrentTime:R}\ncamera={camera.name}\nmode={p.UseDofDiffusionBloomType}\nfeature={feature.isActive}\nimageEffects={feature.EnableRendering}\nfilmIsolation={filmIsolation && CaptureVariant == 1}\nshaderReady={ShaderManager.IsShaderBundleReady}\nbloomEnabled={p.IsEnableBloom}\ndiffusionEnabled={p.IsEnableDiffusion}\nbloomWeight={p.BloomDofWeight:R}\nintensity={p.BloomIntensity:R}\nthreshold={p.BloomThreshold:R}\nbloomRadius={p.BloomBlurSize:R}\nbloomBlend={p.BloomBlendMode}\ndiffusionBright={p.DiffusionBright:R}\ndiffusionThreshold={p.DiffusionThreshold:R}\ndiffusionSaturation={p.DiffusionSaturation:R}\ndiffusionContrast={p.DiffusionContrast:R}\nfilm1={p.Overlay1.PostFilmMode}:{p.Overlay1.PostFilmPower:R}\nfilm2={p.Overlay2.PostFilmMode}:{p.Overlay2.PostFilmPower:R}\nfilm3={p.Overlay3.PostFilmMode}:{p.Overlay3.PostFilmPower:R}\nfilmBlinkLookup={Director.instance._liveTimelineControl.PostFilmBlinkLookupStatus}\nfilmColor0={FilmColor0(p.Overlay1.PostFilmColor0)}|{FilmColor0(p.Overlay2.PostFilmColor0)}|{FilmColor0(p.Overlay3.PostFilmColor0)}\nformat={target.graphicsFormat}\ndof={p.IsEnableDof}\nfocusType={p.DofFocalType}\nfocusPosition={p.DofFocalPosition}\nfocalPoint={p.DofFocalPoint:R}\nlookAtValid={Director.instance._liveTimelineControl.HasDofCameraLookAt}\nlookAt={Director.instance._liveTimelineControl.DofCameraLookAt}\ncharacters={string.Join(",", Director.instance.CharaContainerScript.Select(x => x.CharaEntry.Id + "_" + x.VarCostumeIdLong))}\n");
|
||||
Debug.Log("[LiveRenderCapture] saved " + name);
|
||||
captureIndex++;
|
||||
gate.Reset();
|
||||
if (captureIndex == plan.frames.Length * CaptureSlots) { Finish("Completed: " + output, 0); return; }
|
||||
feature.EnableRendering = CaptureVariant != 0;
|
||||
}
|
||||
catch (Exception e) { Finish(e.ToString(), 1); }
|
||||
finally
|
||||
{
|
||||
RenderTexture.active = previous;
|
||||
if (image != null) UnityEngine.Object.DestroyImmediate(image);
|
||||
}
|
||||
}
|
||||
|
||||
private static string FilmColor0(Color c) => string.Format(CultureInfo.InvariantCulture,
|
||||
"{0:R},{1:R},{2:R},{3:R}", c.r, c.g, c.b, c.a);
|
||||
|
||||
static void Finish(string message, int code)
|
||||
{
|
||||
if (finishing) return;
|
||||
finishCode = code;
|
||||
RestoreFilm();
|
||||
suppressedThisRender = false;
|
||||
SessionState.SetBool(Armed, false);
|
||||
if (feature != null)
|
||||
{
|
||||
feature.EnableRendering = originalRendering;
|
||||
feature.SetActive(originalActive);
|
||||
}
|
||||
if (camera != null) camera.targetTexture = previousTarget;
|
||||
Debug.Log("[LiveRenderCapture] " + message);
|
||||
finishing = true;
|
||||
// Leave the render callback before releasing render targets.
|
||||
EditorApplication.delayCall += RestoreCaptureState;
|
||||
}
|
||||
|
||||
static void RestoreCaptureState()
|
||||
{
|
||||
if (!finishing) return;
|
||||
EditorApplication.delayCall -= RestoreCaptureState;
|
||||
if (capturedDirector != null)
|
||||
{
|
||||
capturedDirector.UI.ProgressBar.SetValueWithoutNotify(previousProgress);
|
||||
capturedDirector._syncTime = previousSync;
|
||||
capturedDirector.sliderControl.is_Touched = previousTouched;
|
||||
capturedDirector.sliderControl.is_Outed = previousOuted;
|
||||
}
|
||||
if (target != null) { target.Release(); UnityEngine.Object.DestroyImmediate(target); }
|
||||
if (preview != null) preview.Close();
|
||||
preview = null;
|
||||
target = null;
|
||||
camera = null;
|
||||
feature = null;
|
||||
capturedDirector = null;
|
||||
deadline = 0;
|
||||
plan = null;
|
||||
SessionState.EraseString(PlanState);
|
||||
SessionState.EraseBool(ExitState);
|
||||
SessionState.EraseBool(FilmIsolationState);
|
||||
filmIsolation = false;
|
||||
suppressedThisRender = false;
|
||||
finishing = false;
|
||||
if (exitOnFinish) EditorApplication.Exit(finishCode);
|
||||
}
|
||||
static int finishCode;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29bffc7886f81234fb049722c9554849
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Gallop.RenderPipeline;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
/// <summary>Idempotent renderer registration using Unity serialization, not YAML edits.</summary>
|
||||
public static class LiveRenderSetup
|
||||
{
|
||||
[MenuItem("UmaViewer/Live/Install recovered post effects")]
|
||||
public static void Install()
|
||||
{
|
||||
const string path = "Assets/Resources/RenderPipeline/UMAUniversalRenderPipelineAsset_Renderer.asset";
|
||||
var renderer = AssetDatabase.LoadAssetAtPath<UniversalRendererData>(path);
|
||||
if (renderer == null) throw new InvalidOperationException("Missing Live renderer: " + path);
|
||||
var feature = renderer.rendererFeatures.OfType<PostImageEffectFeature>().FirstOrDefault();
|
||||
if (feature == null)
|
||||
{
|
||||
feature = ScriptableObject.CreateInstance<PostImageEffectFeature>();
|
||||
feature.name = "Gallop Live Post Effects";
|
||||
AssetDatabase.AddObjectToAsset(feature, renderer);
|
||||
renderer.rendererFeatures.Add(feature);
|
||||
}
|
||||
feature.SetActive(true);
|
||||
renderer.SetDirty();
|
||||
EditorUtility.SetDirty(feature);
|
||||
EditorUtility.SetDirty(renderer);
|
||||
AssetDatabase.SaveAssets();
|
||||
Debug.Log("[LiveRenderSetup] Installed recovered post effects: " + path);
|
||||
}
|
||||
|
||||
// Batch-mode contract checks. These validate state routing, not rendered pixels.
|
||||
public static void Validate()
|
||||
{
|
||||
Install();
|
||||
Install();
|
||||
var renderer = AssetDatabase.LoadAssetAtPath<UniversalRendererData>(
|
||||
"Assets/Resources/RenderPipeline/UMAUniversalRenderPipelineAsset_Renderer.asset");
|
||||
Require(renderer.rendererFeatures.OfType<PostImageEffectFeature>().Count() == 1,
|
||||
"Renderer registration must be idempotent");
|
||||
var source = new Gallop.ImageEffect.DofDiffusionBloomOverlayParam();
|
||||
var state = DofDiffusionBloomOverlayPass.Parameter.Default();
|
||||
state.Setup(source);
|
||||
state.DecideDrawType(false);
|
||||
Require(!state.IsValidity, "Default must be a lossless bypass");
|
||||
source.IsEnableBloom = true;
|
||||
source.BloomIntensity = 0.63f;
|
||||
state.Setup(source);
|
||||
state.DecideDrawType(false);
|
||||
Require(state.UseDofDiffusionBloomType == Gallop.ImageEffect.DofDiffusionBloomOverlayParam.DofDiffusionBloomType.Bloom,
|
||||
"Bloom timeline must select Bloom mode");
|
||||
Require(Mathf.Approximately(state.BloomIntensity, 0.63f), "Timeline intensity must not be rescaled");
|
||||
source.IsEnableDiffusion = true;
|
||||
state.Setup(source);
|
||||
state.DecideDrawType(false);
|
||||
Require(state.UseDofDiffusionBloomType == Gallop.ImageEffect.DofDiffusionBloomOverlayParam.DofDiffusionBloomType.DiffusionBloom,
|
||||
"Diffusion must select the diffusion composition");
|
||||
source.IsEnableDof = true;
|
||||
source.DofFocalType = Gallop.DepthBlurAndBloom.DofFocalType.Position;
|
||||
source.DofFocalPosition = new Vector3(1, 2, 3);
|
||||
state.Setup(source);
|
||||
state.DecideDrawType(true);
|
||||
Require(new PostImageEffectFeature.Parameter { DofDiffuionBloomOverlay = state }.IsUseDepthTexture,
|
||||
"URP DOF mode must request depth even without overlays");
|
||||
Require(state.UseDofDiffusionBloomType == Gallop.ImageEffect.DofDiffusionBloomOverlayParam.DofDiffusionBloomType.DiffusionDofBloom,
|
||||
"DOF must compose with existing Bloom/Diffusion");
|
||||
Require(state.DofFocalPosition == source.DofFocalPosition, "World-space focus must reach renderer");
|
||||
source.IsEnableBloom = false;
|
||||
source.IsEnableDiffusion = false;
|
||||
state.Setup(source);
|
||||
state.DecideDrawType(true);
|
||||
Require(new PostImageEffectFeature.Parameter { DofDiffuionBloomOverlay = state }.IsUseDepthTexture,
|
||||
"Standalone DOF must request depth without Bloom or overlays");
|
||||
source.IsEnableDof = false;
|
||||
source.IsEnableBloom = false;
|
||||
source.IsEnableDiffusion = false;
|
||||
state.Setup(source);
|
||||
state.DecideDrawType(false);
|
||||
Require(!state.IsValidity, "Disabled key must clear the previous mode");
|
||||
var previous = PostImageEffectFeature.RuntimeParameter;
|
||||
previous.DofDiffuionBloomOverlay = state;
|
||||
PostImageEffectFeature.ResetRuntimeParameter();
|
||||
Require(!ReferenceEquals(previous, PostImageEffectFeature.RuntimeParameter), "New Live must reset shared state");
|
||||
Require(!PostImageEffectFeature.RuntimeParameter.DofDiffuionBloomOverlay.IsValidity, "New Live must not inherit effects");
|
||||
Debug.Log("[LiveRenderValidation] PASS: registration, default bypass, Bloom, Diffusion, disable, intensity, reset");
|
||||
}
|
||||
|
||||
private static void Require(bool condition, string message)
|
||||
{
|
||||
if (!condition) throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ee873031d455cb439a61c7e6670811e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,80 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using Gallop.Live;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
public static class Master5PhysicsProfileTests
|
||||
{
|
||||
[MenuItem("UmaViewer/Physics/Test master5 Live profile")]
|
||||
public static void Run()
|
||||
{
|
||||
GameObject root = new GameObject("Master5PhysicsProfileTest");
|
||||
root.hideFlags = HideFlags.HideAndDontSave;
|
||||
try
|
||||
{
|
||||
CheckScale(null, 1f);
|
||||
CheckScale("Hip_Skirt_L", 0.2f);
|
||||
CheckScale("hip_mskirt_r", 0.2f);
|
||||
CheckScale("Hip_Skirt", 1f);
|
||||
CheckScale("Hip_Tail_L", 1f);
|
||||
CheckScale("Thigh_MSkirt_L", 0.8f);
|
||||
CheckScale("Thigh_Skirt_L", 1f);
|
||||
var character = root.AddComponent<UmaContainerCharacter>();
|
||||
character.IsLive = true;
|
||||
var hip = root.AddComponent<DynamicBoneCollider>();
|
||||
hip.ColliderName = "Hip_Skirt_L";
|
||||
hip.m_Radius = 0.5f;
|
||||
hip.m_Height = 1.4f;
|
||||
var leg = root.AddComponent<DynamicBoneCollider>();
|
||||
leg.ColliderName = "Thigh_MSkirt_R";
|
||||
leg.m_Radius = 0.1f;
|
||||
var other = root.AddComponent<DynamicBoneCollider>();
|
||||
other.ColliderName = "Hip_Tail_L";
|
||||
other.m_Radius = 0.25f;
|
||||
var skirtBone = root.AddComponent<DynamicBone>();
|
||||
skirtBone.m_ContinuousCollision = true;
|
||||
var skirt = NewParticle();
|
||||
skirtBone.Particles.Add(skirt);
|
||||
var otherBone = root.AddComponent<DynamicBone>();
|
||||
var hair = NewParticle();
|
||||
otherBone.Particles.Add(hair);
|
||||
var profile = root.AddComponent<Master5LivePhysicsProfile>();
|
||||
for (int i = 0; i < 5; ++i) profile.ApplyToNewPhysics();
|
||||
Near(hip.m_Radius, 0.1f, "hip radius / repeat application");
|
||||
Near(leg.m_Radius, 0.08f, "leg radius / repeat application");
|
||||
Near(other.m_Radius, 0.25f, "unrelated collider");
|
||||
Near(hip.m_Height, 1.4f, "capsule height preserved");
|
||||
Near(skirt.m_Damping, 0.3f, "skirt damping");
|
||||
Near(skirt.m_Elasticity, 0.15f, "skirt elasticity");
|
||||
Near(skirt.m_Stiffness, 0.2f, "skirt stiffness");
|
||||
Near(skirt.m_Inert, 0.11f, "skirt inertia");
|
||||
Near(hair.m_Damping, 0.9f, "other damping");
|
||||
Near(hair.m_Stiffness, 0.833f, "other rigidity");
|
||||
Near(hair.m_Elasticity, 0.1925f, "other rebound cap");
|
||||
Near(hair.m_Inert, 0.121f, "other inertia");
|
||||
// Newly rebuilt particles must receive the profile, but colliders must not shrink again.
|
||||
skirtBone.Particles.Clear();
|
||||
var rebuilt = NewParticle();
|
||||
skirtBone.Particles.Add(rebuilt);
|
||||
profile.ApplyToNewPhysics();
|
||||
Near(rebuilt.m_Damping, 0.3f, "rebuilt particles");
|
||||
Near(hip.m_Radius, 0.1f, "rebuild preserves radius");
|
||||
Debug.Log("[PhysicsMigration] master5 profile regression checks PASSED.");
|
||||
}
|
||||
finally { UnityEngine.Object.DestroyImmediate(root); }
|
||||
}
|
||||
|
||||
private static DynamicBone.Particle NewParticle() => new DynamicBone.Particle
|
||||
{
|
||||
m_Damping = 0.1f, m_Elasticity = 0.1f, m_Stiffness = 0.1f, m_Inert = 0.1f
|
||||
};
|
||||
private static void CheckScale(string name, float expected) =>
|
||||
Near(Master5LivePhysicsProfile.RadiusScale(name), expected, "filter: " + name);
|
||||
private static void Near(float actual, float expected, string label)
|
||||
{
|
||||
if (Mathf.Abs(actual - expected) > 0.00001f)
|
||||
throw new Exception($"[PhysicsMigration] {label}: expected {expected}, got {actual}");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 63c0f0a6306e44749fed26ef04f57036
|
||||
@@ -0,0 +1,583 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Unity.Collections;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
public static class CyalumeMeshDiagnostics
|
||||
{
|
||||
[MenuItem("Tools/Cyalume/Dump Selected Mesh Filters")]
|
||||
private static void DumpSelectedMeshFilters()
|
||||
{
|
||||
var filters = CollectSelectedMeshFilters();
|
||||
if (filters.Count == 0)
|
||||
{
|
||||
Debug.LogWarning("CyalumeMeshDiagnostics: Select one or more GameObjects with MeshFilter components.");
|
||||
return;
|
||||
}
|
||||
|
||||
var report = new StringBuilder();
|
||||
report.AppendLine($"Cyalume mesh dump: {filters.Count} selection(s)");
|
||||
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
report.AppendLine(BuildMeshReport(filter));
|
||||
report.AppendLine(new string('-', 80));
|
||||
}
|
||||
|
||||
Debug.Log(report.ToString());
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Cyalume/Compare First Two Selected Mesh Filters")]
|
||||
private static void CompareFirstTwoSelectedMeshFilters()
|
||||
{
|
||||
var filters = CollectSelectedMeshFilters();
|
||||
if (filters.Count < 2)
|
||||
{
|
||||
Debug.LogWarning("CyalumeMeshDiagnostics: Select at least two GameObjects with MeshFilter components.");
|
||||
return;
|
||||
}
|
||||
|
||||
var left = filters[0];
|
||||
var right = filters[1];
|
||||
var leftMesh = left.sharedMesh;
|
||||
var rightMesh = right.sharedMesh;
|
||||
|
||||
var report = new StringBuilder();
|
||||
report.AppendLine("Cyalume mesh compare");
|
||||
report.AppendLine($"Left : {left.name}");
|
||||
report.AppendLine($"Right: {right.name}");
|
||||
report.AppendLine();
|
||||
report.AppendLine(BuildMeshReport(left));
|
||||
report.AppendLine(new string('=', 80));
|
||||
report.AppendLine(BuildMeshReport(right));
|
||||
report.AppendLine(new string('=', 80));
|
||||
report.AppendLine(BuildComparison(leftMesh, rightMesh));
|
||||
|
||||
Debug.Log(report.ToString());
|
||||
}
|
||||
|
||||
private static List<MeshFilter> CollectSelectedMeshFilters()
|
||||
{
|
||||
var results = new List<MeshFilter>();
|
||||
var seen = new HashSet<MeshFilter>();
|
||||
|
||||
foreach (var gameObject in Selection.gameObjects)
|
||||
{
|
||||
if (gameObject == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var filter = gameObject.GetComponent<MeshFilter>();
|
||||
if (filter != null && seen.Add(filter))
|
||||
{
|
||||
results.Add(filter);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static string BuildMeshReport(MeshFilter filter)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"GameObject : {filter.name}");
|
||||
|
||||
var renderer = filter.GetComponent<Renderer>();
|
||||
sb.AppendLine($"Renderer : {(renderer != null ? renderer.enabled.ToString() : "missing")}");
|
||||
|
||||
var mesh = filter.sharedMesh;
|
||||
if (mesh == null)
|
||||
{
|
||||
sb.AppendLine("Mesh : <null>");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
sb.AppendLine($"Mesh : {mesh.name}");
|
||||
sb.AppendLine($"Readable : {SafeRead(() => mesh.isReadable, false)}");
|
||||
sb.AppendLine($"Vertices : {SafeRead(() => mesh.vertexCount, -1)}");
|
||||
sb.AppendLine($"SubMeshes : {SafeRead(() => mesh.subMeshCount, -1)}");
|
||||
sb.AppendLine($"Bounds : {SafeBounds(mesh)}");
|
||||
sb.AppendLine($"AssetPath : {AssetDatabase.GetAssetPath(mesh)}");
|
||||
sb.AppendLine($"Importer : {DescribeImporter(mesh)}");
|
||||
sb.AppendLine($"Attrs : {BuildAttributeSummary(mesh)}");
|
||||
sb.AppendLine($"AttrLayout : {BuildAttributeLayout(mesh)}");
|
||||
sb.AppendLine($"RawData : {BuildRawAttributeSummary(mesh)}");
|
||||
|
||||
var uv = SafeArrayRead(() => mesh.uv);
|
||||
sb.AppendLine($"UV Count : {(uv != null ? uv.Length.ToString() : "<unavailable>")}");
|
||||
|
||||
var colors32 = SafeArrayRead(() => mesh.colors32);
|
||||
if (colors32 == null)
|
||||
{
|
||||
sb.AppendLine("Colors32 : <unavailable>");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine($"Colors32 : {colors32.Length}");
|
||||
sb.AppendLine($"ColorStats : {BuildColorStats(colors32)}");
|
||||
sb.AppendLine($"ColorSample: {BuildColorSample(colors32, 8)}");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string BuildComparison(Mesh left, Mesh right)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (left == null || right == null)
|
||||
{
|
||||
sb.AppendLine("Comparison unavailable: one of the meshes is null.");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
sb.AppendLine("Quick compare");
|
||||
sb.AppendLine($"Readable equal : {SafeRead(() => left.isReadable, false) == SafeRead(() => right.isReadable, false)}");
|
||||
sb.AppendLine($"Vertex count : {SafeRead(() => left.vertexCount, -1)} vs {SafeRead(() => right.vertexCount, -1)}");
|
||||
sb.AppendLine($"UV count : {SafeArrayRead(() => left.uv)?.Length ?? -1} vs {SafeArrayRead(() => right.uv)?.Length ?? -1}");
|
||||
sb.AppendLine($"Attrs : {BuildAttributeSummary(left)} vs {BuildAttributeSummary(right)}");
|
||||
sb.AppendLine($"AttrLayout L : {BuildAttributeLayout(left)}");
|
||||
sb.AppendLine($"AttrLayout R : {BuildAttributeLayout(right)}");
|
||||
sb.AppendLine($"RawData L : {BuildRawAttributeSummary(left)}");
|
||||
sb.AppendLine($"RawData R : {BuildRawAttributeSummary(right)}");
|
||||
|
||||
var leftColors = SafeArrayRead(() => left.colors32);
|
||||
var rightColors = SafeArrayRead(() => right.colors32);
|
||||
|
||||
sb.AppendLine($"Colors count : {leftColors?.Length ?? -1} vs {rightColors?.Length ?? -1}");
|
||||
|
||||
if (leftColors != null && rightColors != null)
|
||||
{
|
||||
sb.AppendLine($"Alpha stats : {BuildAlphaSummary(leftColors)} vs {BuildAlphaSummary(rightColors)}");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string DescribeImporter(Mesh mesh)
|
||||
{
|
||||
var path = AssetDatabase.GetAssetPath(mesh);
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return "<no importer>";
|
||||
}
|
||||
|
||||
var importer = AssetImporter.GetAtPath(path);
|
||||
if (importer == null)
|
||||
{
|
||||
return "<missing importer>";
|
||||
}
|
||||
|
||||
if (importer is ModelImporter modelImporter)
|
||||
{
|
||||
return $"ModelImporter(readable={modelImporter.isReadable})";
|
||||
}
|
||||
|
||||
return importer.GetType().Name;
|
||||
}
|
||||
|
||||
private static string BuildRawAttributeSummary(Mesh mesh)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var dataArray = Mesh.AcquireReadOnlyMeshData(mesh);
|
||||
if (dataArray.Length == 0)
|
||||
{
|
||||
return "empty";
|
||||
}
|
||||
|
||||
var data = dataArray[0];
|
||||
if (data.vertexCount <= 0)
|
||||
{
|
||||
return "vertexCount=0";
|
||||
}
|
||||
|
||||
var descriptors = mesh.GetVertexAttributes();
|
||||
if (descriptors == null || descriptors.Length == 0)
|
||||
{
|
||||
return "no descriptors";
|
||||
}
|
||||
|
||||
var offsetsByStream = new Dictionary<int, int>();
|
||||
var parts = new List<string>();
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
if (!offsetsByStream.TryGetValue(descriptor.stream, out var offset))
|
||||
{
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
var part = DescribeRawAttribute(data, descriptor, offset);
|
||||
parts.Add(part);
|
||||
offsetsByStream[descriptor.stream] = offset + GetFormatSize(descriptor.format) * descriptor.dimension;
|
||||
}
|
||||
|
||||
return string.Join(" | ", parts);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"<error: {ex.Message}>";
|
||||
}
|
||||
}
|
||||
|
||||
private static string DescribeRawAttribute(Mesh.MeshData data, VertexAttributeDescriptor descriptor, int offset)
|
||||
{
|
||||
var stride = data.GetVertexBufferStride(descriptor.stream);
|
||||
var raw = data.GetVertexData<byte>(descriptor.stream);
|
||||
if (!raw.IsCreated || raw.Length == 0)
|
||||
{
|
||||
return $"{descriptor.attribute}:<no data>";
|
||||
}
|
||||
|
||||
switch (descriptor.attribute)
|
||||
{
|
||||
case VertexAttribute.Color:
|
||||
return $"{descriptor.attribute}:{DescribeColor(raw, data.vertexCount, stride, offset)}";
|
||||
case VertexAttribute.TexCoord0:
|
||||
case VertexAttribute.TexCoord1:
|
||||
case VertexAttribute.TexCoord2:
|
||||
return $"{descriptor.attribute}:{DescribeVector2(raw, data.vertexCount, stride, offset, descriptor.format)}";
|
||||
default:
|
||||
return $"{descriptor.attribute}:ok";
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildAttributeSummary(Mesh mesh)
|
||||
{
|
||||
return
|
||||
$"pos={SafeHasAttr(mesh, VertexAttribute.Position)} " +
|
||||
$"nrm={SafeHasAttr(mesh, VertexAttribute.Normal)} " +
|
||||
$"tan={SafeHasAttr(mesh, VertexAttribute.Tangent)} " +
|
||||
$"col={SafeHasAttr(mesh, VertexAttribute.Color)} " +
|
||||
$"uv0={SafeHasAttr(mesh, VertexAttribute.TexCoord0)} " +
|
||||
$"uv1={SafeHasAttr(mesh, VertexAttribute.TexCoord1)} " +
|
||||
$"uv2={SafeHasAttr(mesh, VertexAttribute.TexCoord2)} " +
|
||||
$"uv3={SafeHasAttr(mesh, VertexAttribute.TexCoord3)}";
|
||||
}
|
||||
|
||||
private static string BuildAttributeLayout(Mesh mesh)
|
||||
{
|
||||
try
|
||||
{
|
||||
var attributes = mesh.GetVertexAttributes();
|
||||
if (attributes == null || attributes.Length == 0)
|
||||
{
|
||||
return "empty";
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
for (var i = 0; i < attributes.Length; i++)
|
||||
{
|
||||
var attribute = attributes[i];
|
||||
if (i > 0)
|
||||
{
|
||||
sb.Append(" | ");
|
||||
}
|
||||
|
||||
var stride = SafeRead(() => mesh.GetVertexBufferStride(attribute.stream), -1);
|
||||
sb.Append(
|
||||
$"{attribute.attribute}:{attribute.format}x{attribute.dimension}@s{attribute.stream}/stride{stride}");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"<error: {ex.Message}>";
|
||||
}
|
||||
}
|
||||
|
||||
private static string DescribeColor(NativeArray<byte> raw, int vertexCount, int stride, int offset)
|
||||
{
|
||||
byte minR = byte.MaxValue;
|
||||
byte minG = byte.MaxValue;
|
||||
byte minB = byte.MaxValue;
|
||||
byte minA = byte.MaxValue;
|
||||
byte maxR = byte.MinValue;
|
||||
byte maxG = byte.MinValue;
|
||||
byte maxB = byte.MinValue;
|
||||
byte maxA = byte.MinValue;
|
||||
var zeroAlpha = 0;
|
||||
|
||||
var sample = new StringBuilder();
|
||||
var sampleCount = Mathf.Min(vertexCount, 4);
|
||||
|
||||
for (var i = 0; i < vertexCount; i++)
|
||||
{
|
||||
var baseIndex = i * stride + offset;
|
||||
var r = raw[baseIndex + 0];
|
||||
var g = raw[baseIndex + 1];
|
||||
var b = raw[baseIndex + 2];
|
||||
var a = raw[baseIndex + 3];
|
||||
|
||||
if (r < minR) minR = r;
|
||||
if (g < minG) minG = g;
|
||||
if (b < minB) minB = b;
|
||||
if (a < minA) minA = a;
|
||||
if (r > maxR) maxR = r;
|
||||
if (g > maxG) maxG = g;
|
||||
if (b > maxB) maxB = b;
|
||||
if (a > maxA) maxA = a;
|
||||
if (a == 0) zeroAlpha++;
|
||||
|
||||
if (i < sampleCount)
|
||||
{
|
||||
if (sample.Length > 0)
|
||||
{
|
||||
sample.Append(",");
|
||||
}
|
||||
|
||||
sample.Append($"({r},{g},{b},{a})");
|
||||
}
|
||||
}
|
||||
|
||||
return $"R[{minR},{maxR}] G[{minG},{maxG}] B[{minB},{maxB}] A[{minA},{maxA}] zeroA={zeroAlpha}/{vertexCount} sample={sample}";
|
||||
}
|
||||
|
||||
private static string DescribeVector2(NativeArray<byte> raw, int vertexCount, int stride, int offset, VertexAttributeFormat format)
|
||||
{
|
||||
var minX = float.PositiveInfinity;
|
||||
var minY = float.PositiveInfinity;
|
||||
var maxX = float.NegativeInfinity;
|
||||
var maxY = float.NegativeInfinity;
|
||||
var sample = new StringBuilder();
|
||||
var sampleCount = Mathf.Min(vertexCount, 4);
|
||||
|
||||
for (var i = 0; i < vertexCount; i++)
|
||||
{
|
||||
var baseIndex = i * stride + offset;
|
||||
float x;
|
||||
float y;
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case VertexAttributeFormat.Float16:
|
||||
x = HalfToFloat((ushort)(raw[baseIndex + 0] | (raw[baseIndex + 1] << 8)));
|
||||
y = HalfToFloat((ushort)(raw[baseIndex + 2] | (raw[baseIndex + 3] << 8)));
|
||||
break;
|
||||
case VertexAttributeFormat.Float32:
|
||||
x = BitConverter.ToSingle(new[]
|
||||
{
|
||||
raw[baseIndex + 0],
|
||||
raw[baseIndex + 1],
|
||||
raw[baseIndex + 2],
|
||||
raw[baseIndex + 3]
|
||||
}, 0);
|
||||
y = BitConverter.ToSingle(new[]
|
||||
{
|
||||
raw[baseIndex + 4],
|
||||
raw[baseIndex + 5],
|
||||
raw[baseIndex + 6],
|
||||
raw[baseIndex + 7]
|
||||
}, 0);
|
||||
break;
|
||||
default:
|
||||
return $"unsupported({format})";
|
||||
}
|
||||
|
||||
if (x < minX) minX = x;
|
||||
if (y < minY) minY = y;
|
||||
if (x > maxX) maxX = x;
|
||||
if (y > maxY) maxY = y;
|
||||
|
||||
if (i < sampleCount)
|
||||
{
|
||||
if (sample.Length > 0)
|
||||
{
|
||||
sample.Append(",");
|
||||
}
|
||||
|
||||
sample.Append($"({x:0.###},{y:0.###})");
|
||||
}
|
||||
}
|
||||
|
||||
return $"x[{minX:0.###},{maxX:0.###}] y[{minY:0.###},{maxY:0.###}] sample={sample}";
|
||||
}
|
||||
|
||||
private static int GetFormatSize(VertexAttributeFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case VertexAttributeFormat.Float32:
|
||||
case VertexAttributeFormat.UInt32:
|
||||
case VertexAttributeFormat.SInt32:
|
||||
return 4;
|
||||
case VertexAttributeFormat.Float16:
|
||||
case VertexAttributeFormat.UNorm16:
|
||||
case VertexAttributeFormat.SNorm16:
|
||||
case VertexAttributeFormat.UInt16:
|
||||
case VertexAttributeFormat.SInt16:
|
||||
return 2;
|
||||
case VertexAttributeFormat.UNorm8:
|
||||
case VertexAttributeFormat.SNorm8:
|
||||
case VertexAttributeFormat.UInt8:
|
||||
case VertexAttributeFormat.SInt8:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static float HalfToFloat(ushort value)
|
||||
{
|
||||
var sign = (value >> 15) & 0x1;
|
||||
var exponent = (value >> 10) & 0x1F;
|
||||
var mantissa = value & 0x3FF;
|
||||
|
||||
if (exponent == 0)
|
||||
{
|
||||
if (mantissa == 0)
|
||||
{
|
||||
return sign == 0 ? 0f : -0f;
|
||||
}
|
||||
|
||||
return (float)((sign == 0 ? 1 : -1) * Math.Pow(2, -14) * (mantissa / 1024.0));
|
||||
}
|
||||
|
||||
if (exponent == 31)
|
||||
{
|
||||
if (mantissa == 0)
|
||||
{
|
||||
return sign == 0 ? float.PositiveInfinity : float.NegativeInfinity;
|
||||
}
|
||||
|
||||
return float.NaN;
|
||||
}
|
||||
|
||||
return (float)((sign == 0 ? 1 : -1) * Math.Pow(2, exponent - 15) * (1.0 + mantissa / 1024.0));
|
||||
}
|
||||
|
||||
private static string SafeBounds(Mesh mesh)
|
||||
{
|
||||
try
|
||||
{
|
||||
var bounds = mesh.bounds;
|
||||
return $"center={bounds.center}, extents={bounds.extents}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"<error: {ex.Message}>";
|
||||
}
|
||||
}
|
||||
|
||||
private static T SafeRead<T>(Func<T> getter, T fallback)
|
||||
{
|
||||
try
|
||||
{
|
||||
return getter();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
private static T[] SafeArrayRead<T>(Func<T[]> getter)
|
||||
{
|
||||
try
|
||||
{
|
||||
return getter();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool SafeHasAttr(Mesh mesh, VertexAttribute attribute)
|
||||
{
|
||||
try
|
||||
{
|
||||
return mesh.HasVertexAttribute(attribute);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildColorStats(Color32[] colors)
|
||||
{
|
||||
if (colors.Length == 0)
|
||||
{
|
||||
return "empty";
|
||||
}
|
||||
|
||||
byte minR = byte.MaxValue;
|
||||
byte minG = byte.MaxValue;
|
||||
byte minB = byte.MaxValue;
|
||||
byte minA = byte.MaxValue;
|
||||
byte maxR = byte.MinValue;
|
||||
byte maxG = byte.MinValue;
|
||||
byte maxB = byte.MinValue;
|
||||
byte maxA = byte.MinValue;
|
||||
var zeroAlphaCount = 0;
|
||||
|
||||
foreach (var color in colors)
|
||||
{
|
||||
if (color.r < minR) minR = color.r;
|
||||
if (color.g < minG) minG = color.g;
|
||||
if (color.b < minB) minB = color.b;
|
||||
if (color.a < minA) minA = color.a;
|
||||
if (color.r > maxR) maxR = color.r;
|
||||
if (color.g > maxG) maxG = color.g;
|
||||
if (color.b > maxB) maxB = color.b;
|
||||
if (color.a > maxA) maxA = color.a;
|
||||
if (color.a == 0) zeroAlphaCount++;
|
||||
}
|
||||
|
||||
return $"R[{minR},{maxR}] G[{minG},{maxG}] B[{minB},{maxB}] A[{minA},{maxA}] zeroA={zeroAlphaCount}/{colors.Length}";
|
||||
}
|
||||
|
||||
private static string BuildAlphaSummary(Color32[] colors)
|
||||
{
|
||||
if (colors.Length == 0)
|
||||
{
|
||||
return "empty";
|
||||
}
|
||||
|
||||
byte minA = byte.MaxValue;
|
||||
byte maxA = byte.MinValue;
|
||||
var zeroAlphaCount = 0;
|
||||
|
||||
foreach (var color in colors)
|
||||
{
|
||||
if (color.a < minA) minA = color.a;
|
||||
if (color.a > maxA) maxA = color.a;
|
||||
if (color.a == 0) zeroAlphaCount++;
|
||||
}
|
||||
|
||||
return $"A[{minA},{maxA}] zeroA={zeroAlphaCount}/{colors.Length}";
|
||||
}
|
||||
|
||||
private static string BuildColorSample(Color32[] colors, int maxCount)
|
||||
{
|
||||
if (colors.Length == 0)
|
||||
{
|
||||
return "empty";
|
||||
}
|
||||
|
||||
var count = Mathf.Min(colors.Length, maxCount);
|
||||
var sb = new StringBuilder();
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var color = colors[i];
|
||||
if (i > 0)
|
||||
{
|
||||
sb.Append(" | ");
|
||||
}
|
||||
|
||||
sb.Append($"#{i}:({color.r},{color.g},{color.b},{color.a})");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e66762c01b96f7c4280248196942465e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,111 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
public static class PhysicsCollisionCacheTests
|
||||
{
|
||||
[MenuItem("UmaViewer/Physics/Toggle collision geometry cache")]
|
||||
public static void ToggleCache()
|
||||
{
|
||||
DynamicBoneCollider.UseGeometryCache = !DynamicBoneCollider.UseGeometryCache;
|
||||
Debug.Log("[PhysicsPerformance] Collision geometry cache: " + DynamicBoneCollider.UseGeometryCache);
|
||||
}
|
||||
|
||||
[MenuItem("UmaViewer/Physics/Test collision cache equivalence")]
|
||||
public static void Run()
|
||||
{
|
||||
bool previous = DynamicBoneCollider.UseGeometryCache;
|
||||
var root = new GameObject("CollisionCacheTest") { hideFlags = HideFlags.HideAndDontSave };
|
||||
int checks = 0;
|
||||
try
|
||||
{
|
||||
var collider = root.AddComponent<DynamicBoneCollider>();
|
||||
var random = new System.Random(1709);
|
||||
var samples = new Vector3[512];
|
||||
for (int i = 0; i < samples.Length; ++i)
|
||||
samples[i] = new Vector3((float)random.NextDouble() * 4 - 2,
|
||||
(float)random.NextDouble() * 4 - 2, (float)random.NextDouble() * 4 - 2);
|
||||
for (int shape = 0; shape < 2; ++shape)
|
||||
for (int bound = 0; bound < 2; ++bound)
|
||||
for (int axis = 0; axis < 3; ++axis)
|
||||
for (int pose = 0; pose < 4; ++pose)
|
||||
{
|
||||
root.transform.position = new Vector3(pose * 0.1f, -0.2f, 0.3f);
|
||||
root.transform.rotation = Quaternion.Euler(pose * 23, pose * 11, -17);
|
||||
root.transform.localScale = new Vector3(pose == 3 ? -1.2f : 1.2f, 0.7f + pose * 0.1f, 1.5f);
|
||||
collider.m_Radius = 0.4f + pose * 0.02f;
|
||||
collider.m_Height = shape == 0 ? 0 : 2;
|
||||
collider.m_Center = new Vector3(0.1f, -0.15f, 0.2f);
|
||||
collider.m_Direction = (DynamicBoneColliderBase.Direction)axis;
|
||||
collider.m_Bound = (DynamicBoneColliderBase.Bound)bound;
|
||||
using (DynamicBoneCollider.BeginCollisionBatch())
|
||||
{
|
||||
foreach (Vector3 sample in samples)
|
||||
{
|
||||
Check(collider, sample, 0.015f);
|
||||
++checks;
|
||||
}
|
||||
using (DynamicBoneCollider.BeginCollisionBatch())
|
||||
Check(collider, samples[0], 0.015f);
|
||||
Check(collider, samples[1], 0.015f);
|
||||
}
|
||||
// No batch: a moved collider must be read immediately, not from the last cache.
|
||||
root.transform.position += Vector3.up * 0.2f;
|
||||
Check(collider, samples[2], 0.03f);
|
||||
}
|
||||
collider.m_Bound = DynamicBoneColliderBase.Bound.Outside;
|
||||
collider.m_Height = 2;
|
||||
const int rounds = 1024;
|
||||
// Warm both paths and use two orders to reduce ordering/JIT bias.
|
||||
Measure(collider, samples, 8, false);
|
||||
Measure(collider, samples, 8, true);
|
||||
double uncached = Measure(collider, samples, rounds, false);
|
||||
double cached = Measure(collider, samples, rounds, true);
|
||||
cached += Measure(collider, samples, rounds, true);
|
||||
uncached += Measure(collider, samples, rounds, false);
|
||||
string report = $"[PhysicsPerformance] PASS: {checks} point comparisons + nested scopes and moved colliders. " +
|
||||
$"Synthetic {rounds * samples.Length * 2} queries/path: uncached={uncached:F2}ms, cached={cached:F2}ms, " +
|
||||
$"ratio={uncached / Math.Max(cached, 0.001):F2}x. This is NOT a Live frame-time benchmark.";
|
||||
Debug.Log(report);
|
||||
string output = Environment.GetEnvironmentVariable("UMA_PHYSICS_TEST_REPORT");
|
||||
if (!string.IsNullOrEmpty(output)) File.WriteAllText(output, report);
|
||||
}
|
||||
finally
|
||||
{
|
||||
DynamicBoneCollider.UseGeometryCache = previous;
|
||||
UnityEngine.Object.DestroyImmediate(root);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Check(DynamicBoneCollider collider, Vector3 input, float radius)
|
||||
{
|
||||
Vector3 expected = input, actual = input;
|
||||
DynamicBoneCollider.UseGeometryCache = false;
|
||||
bool expectedHit = collider.Collide(ref expected, radius);
|
||||
DynamicBoneCollider.UseGeometryCache = true;
|
||||
bool actualHit = collider.Collide(ref actual, radius);
|
||||
if (expectedHit != actualHit || (actual - expected).sqrMagnitude > 1e-12f ||
|
||||
float.IsNaN(actual.x) || float.IsNaN(actual.y) || float.IsNaN(actual.z))
|
||||
throw new Exception($"Collision cache mismatch: expected {expectedHit}/{expected}, actual {actualHit}/{actual}");
|
||||
}
|
||||
|
||||
private static double Measure(DynamicBoneCollider collider, Vector3[] samples, int rounds, bool cached)
|
||||
{
|
||||
DynamicBoneCollider.UseGeometryCache = cached;
|
||||
var timer = Stopwatch.StartNew();
|
||||
for (int i = 0; i < rounds; ++i)
|
||||
using (DynamicBoneCollider.BeginCollisionBatch())
|
||||
foreach (var sample in samples)
|
||||
{
|
||||
Vector3 point = sample;
|
||||
collider.Collide(ref point, 0.015f);
|
||||
}
|
||||
timer.Stop();
|
||||
return timer.Elapsed.TotalMilliseconds;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4111da92019549a083a824c4a20b7e8c
|
||||
@@ -0,0 +1,52 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Gallop;
|
||||
using Gallop.Live;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>Checks loaded characters without changing their pose or stopping playback.</summary>
|
||||
[InitializeOnLoad]
|
||||
public static class PhysicsMigrationValidation
|
||||
{
|
||||
static PhysicsMigrationValidation() { EditorApplication.delayCall += ValidateIfPlaying; }
|
||||
static void ValidateIfPlaying() { if (EditorApplication.isPlaying) Validate(); }
|
||||
|
||||
[MenuItem("UmaViewer/Physics/Validate master5 migration")]
|
||||
public static void Validate()
|
||||
{
|
||||
var characters = UnityEngine.Object.FindObjectsOfType<UmaContainerCharacter>();
|
||||
foreach (var character in characters.Where(c => !c.IsMini))
|
||||
{
|
||||
var bones = character.GetComponentsInChildren<DynamicBone>(true);
|
||||
var surface = character.GetComponent<SkirtSurfaceCollisionSolver>();
|
||||
var oldController = character.GetComponent<CySpringController>();
|
||||
var oldSkirt = character.GetComponent<SkirtController>();
|
||||
string report = $"[PhysicsMigration] character={character.name} dynamicBones={bones.Length} " +
|
||||
$"rings={(surface != null ? surface.RingCount : 0)} " +
|
||||
$"thighColliders={(surface != null ? surface.ColliderCount : 0)} " +
|
||||
$"oldCySpring={(oldController != null)} oldSkirt={(oldSkirt != null)}";
|
||||
if (oldController != null || oldSkirt != null || bones.Length == 0)
|
||||
Debug.LogWarning(report + " Reload this character/Live after script migration.");
|
||||
else Debug.Log(report);
|
||||
if (character.IsLive)
|
||||
{
|
||||
var profile = character.GetComponent<Master5LivePhysicsProfile>();
|
||||
if (profile == null)
|
||||
Debug.LogWarning("[PhysicsMigration] Missing master5 Live profile; reload Live to apply radius tuning.");
|
||||
else
|
||||
Debug.Log($"[PhysicsMigration] master5 profile colliders={profile.TunedColliderCount} horizontalStiffness={surface?.HorizontalStiffness}");
|
||||
foreach (var collider in character.GetComponentsInChildren<DynamicBoneCollider>(true))
|
||||
{
|
||||
float scale = Master5LivePhysicsProfile.RadiusScale(collider.ColliderName);
|
||||
if (scale == 1f) continue;
|
||||
Debug.Log($"[PhysicsMigration] {collider.ColliderName}: localRadius={collider.m_Radius:F5}, " +
|
||||
$"height={collider.m_Height:F5}, lossyScale={collider.transform.lossyScale}, expectedRadiusMultiplier={scale:F2}", collider);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (characters.Length == 0) Debug.Log("[PhysicsMigration] No loaded characters; load a character to validate runtime binding.");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a970a346d5902f34cac957aba3135a41
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,126 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class LiveBloomProbe : MonoBehaviour
|
||||
{
|
||||
[Header("Bloom 测试参数")]
|
||||
[Range(0f, 10f)]
|
||||
public float bloomIntensity = 2.5f;
|
||||
|
||||
[Range(0f, 5f)]
|
||||
public float bloomThreshold = 0.7f;
|
||||
|
||||
[Range(0f, 1f)]
|
||||
public float bloomScatter = 0.75f;
|
||||
|
||||
[Header("曝光测试")]
|
||||
[Range(-3f, 3f)]
|
||||
public float postExposure = 0.5f;
|
||||
|
||||
private Volume _volume;
|
||||
private VolumeProfile _runtimeProfile;
|
||||
|
||||
private Bloom _bloom;
|
||||
private ColorAdjustments _colorAdjustments;
|
||||
private Tonemapping _tonemapping;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
EnablePostProcessingOnAllCameras();
|
||||
CreateRuntimeVolume();
|
||||
ApplyParameters();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
// 你的 Live 会不断切换启用中的 Camera,
|
||||
// 所以测试阶段持续保证所有 Live Camera 都启用 HDR/Post Processing。
|
||||
EnablePostProcessingOnAllCameras();
|
||||
ApplyParameters();
|
||||
}
|
||||
|
||||
private void EnablePostProcessingOnAllCameras()
|
||||
{
|
||||
Camera[] cameras = FindObjectsOfType<Camera>(true);
|
||||
|
||||
for (int i = 0; i < cameras.Length; i++)
|
||||
{
|
||||
Camera camera = cameras[i];
|
||||
|
||||
if (camera == null ||
|
||||
!camera.gameObject.scene.IsValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
camera.allowHDR = true;
|
||||
|
||||
UniversalAdditionalCameraData cameraData =
|
||||
camera.GetComponent<UniversalAdditionalCameraData>();
|
||||
|
||||
if (cameraData == null)
|
||||
{
|
||||
cameraData = camera.gameObject.AddComponent<
|
||||
UniversalAdditionalCameraData>();
|
||||
}
|
||||
|
||||
cameraData.renderPostProcessing = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateRuntimeVolume()
|
||||
{
|
||||
_volume = GetComponent<Volume>();
|
||||
|
||||
if (_volume == null)
|
||||
_volume = gameObject.AddComponent<Volume>();
|
||||
|
||||
_volume.isGlobal = true;
|
||||
_volume.priority = 1000f;
|
||||
_volume.weight = 1f;
|
||||
|
||||
_runtimeProfile =
|
||||
ScriptableObject.CreateInstance<VolumeProfile>();
|
||||
|
||||
_volume.sharedProfile = _runtimeProfile;
|
||||
|
||||
_bloom = _runtimeProfile.Add<Bloom>(true);
|
||||
_colorAdjustments =
|
||||
_runtimeProfile.Add<ColorAdjustments>(true);
|
||||
_tonemapping =
|
||||
_runtimeProfile.Add<Tonemapping>(true);
|
||||
}
|
||||
|
||||
private void ApplyParameters()
|
||||
{
|
||||
if (_bloom != null)
|
||||
{
|
||||
_bloom.active = true;
|
||||
_bloom.intensity.Override(bloomIntensity);
|
||||
_bloom.threshold.Override(bloomThreshold);
|
||||
_bloom.scatter.Override(bloomScatter);
|
||||
_bloom.clamp.Override(1000f);
|
||||
_bloom.highQualityFiltering.Override(true);
|
||||
}
|
||||
|
||||
if (_colorAdjustments != null)
|
||||
{
|
||||
_colorAdjustments.active = true;
|
||||
_colorAdjustments.postExposure.Override(postExposure);
|
||||
}
|
||||
|
||||
if (_tonemapping != null)
|
||||
{
|
||||
_tonemapping.active = true;
|
||||
_tonemapping.mode.Override(TonemappingMode.ACES);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_runtimeProfile != null)
|
||||
Destroy(_runtimeProfile);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 093a9d00e66b14ee2aa312cc2a47e438
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,147 @@
|
||||
Shader "Gallop/3D/Live/Cyalume/MobShadowOfficialLike"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
_MainTex ("Mob Shadow Texture", 2D) = "white" {}
|
||||
_ClipThreshold ("Clip Threshold", Range(0,1)) = 0.01
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"RenderPipeline"="UniversalPipeline"
|
||||
"Queue"="AlphaTest"
|
||||
"RenderType"="Opaque"
|
||||
}
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "UniversalForward"
|
||||
Tags { "LightMode"="UniversalForward" }
|
||||
|
||||
Cull Off
|
||||
ZWrite On
|
||||
ZTest LEqual
|
||||
|
||||
HLSLPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma target 4.5
|
||||
|
||||
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
||||
|
||||
TEXTURE2D(_MainTex);
|
||||
SAMPLER(sampler_MainTex);
|
||||
|
||||
CBUFFER_START(UnityPerMaterial)
|
||||
float4 _MainTex_ST;
|
||||
float _ClipThreshold;
|
||||
CBUFFER_END
|
||||
|
||||
float4 _MobColor;
|
||||
float4 _MaskPosArray[11];
|
||||
float4x4 _CyalumeGroupMatrix[11];
|
||||
|
||||
struct Attributes
|
||||
{
|
||||
float4 positionOS : POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
};
|
||||
|
||||
struct Varyings
|
||||
{
|
||||
float4 positionCS : SV_POSITION;
|
||||
float3 worldPos : TEXCOORD0;
|
||||
float2 uv : TEXCOORD1;
|
||||
};
|
||||
|
||||
Varyings vert(Attributes IN)
|
||||
{
|
||||
Varyings OUT;
|
||||
VertexPositionInputs posInputs = GetVertexPositionInputs(IN.positionOS.xyz);
|
||||
OUT.positionCS = posInputs.positionCS;
|
||||
OUT.worldPos = posInputs.positionWS;
|
||||
OUT.uv = TRANSFORM_TEX(IN.uv, _MainTex);
|
||||
return OUT;
|
||||
}
|
||||
|
||||
float ComputeMaskAttenuation(int idx, float3 worldPos)
|
||||
{
|
||||
float4 m = _MaskPosArray[idx];
|
||||
if (m.z <= 0.0001)
|
||||
return 1.0;
|
||||
|
||||
float2 d = worldPos.xz - m.xy;
|
||||
float dist = length(d);
|
||||
|
||||
float radius = m.z;
|
||||
float softness = max(m.w, 0.0001);
|
||||
|
||||
return 1.0 - smoothstep(radius - softness, radius, dist);
|
||||
}
|
||||
|
||||
float3 WorldToGroupLocal(float4x4 m, float3 worldPos)
|
||||
{
|
||||
float3 translation = float3(m[0][3], m[1][3], m[2][3]);
|
||||
|
||||
float3 axisX = float3(m[0][0], m[1][0], m[2][0]);
|
||||
float3 axisY = float3(m[0][1], m[1][1], m[2][1]);
|
||||
float3 axisZ = float3(m[0][2], m[1][2], m[2][2]);
|
||||
|
||||
float3 d = worldPos - translation;
|
||||
|
||||
float sx2 = max(dot(axisX, axisX), 1e-8);
|
||||
float sy2 = max(dot(axisY, axisY), 1e-8);
|
||||
float sz2 = max(dot(axisZ, axisZ), 1e-8);
|
||||
|
||||
return float3(
|
||||
dot(d, axisX) / sx2,
|
||||
dot(d, axisY) / sy2,
|
||||
dot(d, axisZ) / sz2
|
||||
);
|
||||
}
|
||||
|
||||
float4 frag(Varyings IN) : SV_Target
|
||||
{
|
||||
float bestAlpha = 0.0;
|
||||
float3 bestRgb = 1.0.xxx;
|
||||
|
||||
[unroll]
|
||||
for (int i = 0; i < 11; i++)
|
||||
{
|
||||
float4x4 groupMtx = _CyalumeGroupMatrix[i];
|
||||
float3 localPos = WorldToGroupLocal(groupMtx, IN.worldPos);
|
||||
|
||||
float2 projUV = localPos.xz + 0.5;
|
||||
|
||||
bool inside =
|
||||
projUV.x >= 0.0 && projUV.x <= 1.0 &&
|
||||
projUV.y >= 0.0 && projUV.y <= 1.0;
|
||||
|
||||
if (!inside)
|
||||
continue;
|
||||
|
||||
float4 texCol = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, projUV);
|
||||
|
||||
float maskAtten = ComputeMaskAttenuation(i, IN.worldPos);
|
||||
float a = texCol.a * maskAtten;
|
||||
|
||||
if (a > bestAlpha)
|
||||
{
|
||||
bestAlpha = a;
|
||||
bestRgb = texCol.rgb;
|
||||
}
|
||||
}
|
||||
|
||||
float4 finalColor;
|
||||
finalColor.rgb = bestRgb * _MobColor.rgb;
|
||||
finalColor.a = bestAlpha * _MobColor.a;
|
||||
|
||||
clip(finalColor.a - _ClipThreshold);
|
||||
return finalColor;
|
||||
}
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38174debbaebae343a07ad1ae46e1038
|
||||
guid: 31a91d96e9d0032419e4753658a987a7
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e487cf3ec7419243a47a197ca9f7062
|
||||
guid: 4c8b4b04d48ad4b97aa4a9d9f2fdba75
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
Binary file not shown.
@@ -0,0 +1,47 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d6ab3648065b48a3b3161af5a1f993f
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
: Any
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
Exclude Android: 0
|
||||
Exclude Editor: 1
|
||||
Exclude Linux64: 1
|
||||
Exclude OSXUniversal: 1
|
||||
Exclude Win: 1
|
||||
Exclude Win64: 1
|
||||
Exclude iOS: 1
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: ARM64
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DefaultValueInitialized: true
|
||||
OS: AnyOS
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,80 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9d87eadf9dc1483aa113082dd5e8a97
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
: Any
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
Exclude Android: 0
|
||||
Exclude Editor: 1
|
||||
Exclude Linux64: 1
|
||||
Exclude OSXUniversal: 1
|
||||
Exclude Win: 1
|
||||
Exclude Win64: 1
|
||||
Exclude iOS: 1
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: ARM64
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DefaultValueInitialized: true
|
||||
OS: AnyOS
|
||||
- first:
|
||||
Standalone: Linux64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86_64
|
||||
- first:
|
||||
Standalone: OSXUniversal
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: None
|
||||
- first:
|
||||
Standalone: Win
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86
|
||||
- first:
|
||||
Standalone: Win64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86_64
|
||||
- first:
|
||||
iPhone: iOS
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
AddToEmbeddedBinaries: false
|
||||
CPU: AnyCPU
|
||||
CompileFlags:
|
||||
FrameworkDependencies:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ecfe64f6a78444342a929112bd228b60
|
||||
guid: b5952eb1d863e4e0bb8661bff5fc2b48
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
@@ -0,0 +1,21 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a50bd9a009c8dfc4ebd88cc8101225a7
|
||||
labels:
|
||||
- Tween
|
||||
- Tweening
|
||||
- Animation
|
||||
- HOTween
|
||||
- Paths
|
||||
- iTween
|
||||
- DFTween
|
||||
- LeanTween
|
||||
- Ease
|
||||
- Easing
|
||||
- Shake
|
||||
- Punch
|
||||
- 2DToolkit
|
||||
- TextMeshPro
|
||||
- Text
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34192c5e0d14aee43a0e86cc4823268a
|
||||
TextScriptImporter:
|
||||
userData:
|
||||
Binary file not shown.
@@ -0,0 +1,22 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a811bde74b26b53498b4f6d872b09b6d
|
||||
PluginImporter:
|
||||
serializedVersion: 1
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
platformData:
|
||||
Any:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
Editor:
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
WindowsStoreApps:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b27f58ae5d5c33a4bb2d1f4f34bd036d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
@@ -0,0 +1,189 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>DOTweenEditor</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="T:DG.DOTweenEditor.EditorCompatibilityUtils">
|
||||
<summary>
|
||||
Contains compatibility methods taken from DemiEditor (for when DOTween is without it)
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.EditorCompatibilityUtils.FindObjectOfType``1(System.Boolean)">
|
||||
<summary>
|
||||
Warning: some versions of this method don't have the includeInactive parameter so it won't be taken into account
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.EditorCompatibilityUtils.FindObjectOfType(System.Type,System.Boolean)">
|
||||
<summary>
|
||||
Warning: some versions of this method don't have the includeInactive parameter so it won't be taken into account
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.EditorCompatibilityUtils.FindObjectsOfType``1(System.Boolean)">
|
||||
<summary>
|
||||
Warning: some versions of this method don't have the includeInactive parameter so it won't be taken into account
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.EditorCompatibilityUtils.FindObjectsOfType(System.Type,System.Boolean)">
|
||||
<summary>
|
||||
Warning: some versions of this method don't have the includeInactive parameter so it won't be taken into account
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.DOTweenEditorPreview.Start(System.Action)">
|
||||
<summary>
|
||||
Starts the update loop of tween in the editor. Has no effect during playMode.
|
||||
</summary>
|
||||
<param name="onPreviewUpdated">Eventual callback to call after every update</param>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.DOTweenEditorPreview.Stop(System.Boolean,System.Boolean)">
|
||||
<summary>
|
||||
Stops the update loop and clears the onPreviewUpdated callback.
|
||||
</summary>
|
||||
<param name="resetTweenTargets">If TRUE also resets the tweened objects to their original state.
|
||||
Note that this works by calling Rewind on all tweens, so it will work correctly
|
||||
only if you have a single tween type per object and it wasn't killed</param>
|
||||
<param name="clearTweens">If TRUE also kills any cached tween</param>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.DOTweenEditorPreview.PrepareTweenForPreview(DG.Tweening.Tween,System.Boolean,System.Boolean,System.Boolean)">
|
||||
<summary>
|
||||
Readies the tween for editor preview by setting its UpdateType to Manual plus eventual extra settings.
|
||||
</summary>
|
||||
<param name="t">The tween to ready</param>
|
||||
<param name="clearCallbacks">If TRUE (recommended) removes all callbacks (OnComplete/Rewind/etc)</param>
|
||||
<param name="preventAutoKill">If TRUE prevents the tween from being auto-killed at completion</param>
|
||||
<param name="andPlay">If TRUE starts playing the tween immediately</param>
|
||||
</member>
|
||||
<member name="F:DG.DOTweenEditor.EditorVersion.Version">
|
||||
<summary>Full major version + first minor version (ex: 2018.1f)</summary>
|
||||
</member>
|
||||
<member name="F:DG.DOTweenEditor.EditorVersion.MajorVersion">
|
||||
<summary>Major version</summary>
|
||||
</member>
|
||||
<member name="F:DG.DOTweenEditor.EditorVersion.MinorVersion">
|
||||
<summary>First minor version (ex: in 2018.1 it would be 1)</summary>
|
||||
</member>
|
||||
<member name="P:DG.DOTweenEditor.EditorUtils.adbPrefix">
|
||||
<summary>With final slash. "Packages/" if this is a package, "Assets/" otherwise</summary>
|
||||
</member>
|
||||
<member name="P:DG.DOTweenEditor.EditorUtils.demigiantDir">
|
||||
<summary>With final slash (system based) - might be NULL in case users are not using a parent Demigiant folder</summary>
|
||||
</member>
|
||||
<member name="P:DG.DOTweenEditor.EditorUtils.dotweenDir">
|
||||
<summary>With final slash (system based). If this is a package, represents the package's Runtime dir</summary>
|
||||
</member>
|
||||
<member name="P:DG.DOTweenEditor.EditorUtils.editorDir">
|
||||
<summary>With final slash (system based). If this is a package, represents the package's Editor dir</summary>
|
||||
</member>
|
||||
<member name="P:DG.DOTweenEditor.EditorUtils.editorADBDir">
|
||||
<summary>DOTween editor path from Assets (not included) with final slash, in AssetDatabase format (/)</summary>
|
||||
</member>
|
||||
<member name="P:DG.DOTweenEditor.EditorUtils.dotweenProEditorDir">
|
||||
<summary>With final slash (system based)</summary>
|
||||
</member>
|
||||
<member name="P:DG.DOTweenEditor.EditorUtils.dotweenModulesDir">
|
||||
<summary>With final slash (system based)</summary>
|
||||
</member>
|
||||
<member name="P:DG.DOTweenEditor.EditorUtils.dotweenTimelineDir">
|
||||
<summary>With final slash (system based)</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.EditorUtils.SetEditorTexture(UnityEngine.Texture2D,UnityEngine.FilterMode,System.Int32)">
|
||||
<summary>
|
||||
Checks that the given editor texture use the correct import settings,
|
||||
and applies them if they're incorrect.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.EditorUtils.DOTweenSetupRequired">
|
||||
<summary>
|
||||
Returns TRUE if setup is required
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.EditorUtils.AssetExists(System.String)">
|
||||
<summary>
|
||||
Returns TRUE if the file/directory at the given path exists.
|
||||
</summary>
|
||||
<param name="adbPath">Path, relative to Unity's project folder</param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.EditorUtils.ADBPathToFullPath(System.String)">
|
||||
<summary>
|
||||
Converts the given project-relative path to a full path,
|
||||
with backward (\) slashes).
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.EditorUtils.FullPathToADBPath(System.String)">
|
||||
<summary>
|
||||
Converts the given full path to a path usable with AssetDatabase methods
|
||||
(relative to Unity's project folder, and with the correct Unity forward (/) slashes).
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.EditorUtils.ConnectToSourceAsset``1(System.String,System.Boolean)">
|
||||
<summary>
|
||||
Connects to a <see cref="T:UnityEngine.ScriptableObject"/> asset.
|
||||
If the asset already exists at the given path, loads it and returns it.
|
||||
Otherwise, either returns NULL or automatically creates it before loading and returning it
|
||||
(depending on the given parameters).
|
||||
</summary>
|
||||
<typeparam name="T">Asset type</typeparam>
|
||||
<param name="adbFilePath">File path (relative to Unity's project folder)</param>
|
||||
<param name="createIfMissing">If TRUE and the requested asset doesn't exist, forces its creation</param>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.EditorUtils.GetAssemblyFilePath(System.Reflection.Assembly)">
|
||||
<summary>
|
||||
Full path for the given loaded assembly, assembly file included
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.EditorUtils.AddGlobalDefine(System.String)">
|
||||
<summary>
|
||||
Adds the given global define if it's not already present
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.EditorUtils.RemoveGlobalDefine(System.String)">
|
||||
<summary>
|
||||
Removes the given global define if it's present
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.EditorUtils.HasGlobalDefine(System.String,System.Nullable{UnityEditor.BuildTargetGroup})">
|
||||
<summary>
|
||||
Returns TRUE if the given global define is present in all the <see cref="T:UnityEditor.BuildTargetGroup"/>
|
||||
or only in the given <see cref="T:UnityEditor.BuildTargetGroup"/>, depending on passed parameters.<para/>
|
||||
</summary>
|
||||
<param name="id"></param>
|
||||
<param name="buildTargetGroup"><see cref="T:UnityEditor.BuildTargetGroup"/>to use. Leave NULL to check in all of them.</param>
|
||||
</member>
|
||||
<member name="T:DG.DOTweenEditor.DOTweenDefines">
|
||||
<summary>
|
||||
Not used as menu item anymore, but as a utility function
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.DOTweenDefines.RefreshDOTweenSettings(DG.Tweening.Core.DOTweenSettings)">
|
||||
<summary>Sets the modules bool values to the current defines state</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.DOTweenDefines.RefreshAll">
|
||||
<summary>Refreshes the enabled state of all defines</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.DOTweenDefines.ApplyGUIEnabledToAll">
|
||||
<summary>Applies the guiEnabled value state to each define, adding or removing them based on that</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.DOTweenDefines.RemoveAll">
|
||||
<summary>Removes all DOTween defines including the ones for external assets</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.DOTweenDefines.RemoveAllLegacy">
|
||||
<summary>Removes all legacy defines</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.DOTweenDefines.Def.Remove">
|
||||
<summary>Removes the define if present</summary>
|
||||
</member>
|
||||
<member name="M:DG.DOTweenEditor.DOTweenDefines.Def.Add">
|
||||
<summary>Adds the define if it's not already present</summary>
|
||||
</member>
|
||||
<member name="F:DG.DOTweenEditor.UnityEditorVersion.Version">
|
||||
<summary>Full major version + first minor version (ex: 2018.1f)</summary>
|
||||
</member>
|
||||
<member name="F:DG.DOTweenEditor.UnityEditorVersion.MajorVersion">
|
||||
<summary>Major version</summary>
|
||||
</member>
|
||||
<member name="F:DG.DOTweenEditor.UnityEditorVersion.MinorVersion">
|
||||
<summary>First minor version (ex: in 2018.1 it would be 1)</summary>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
||||
@@ -0,0 +1,4 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e2c6224d345d9249acfa6e8ef40bb2d
|
||||
TextScriptImporter:
|
||||
userData:
|
||||
Binary file not shown.
@@ -0,0 +1,22 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 45d5034162d6cf04dbe46da84fc7d074
|
||||
PluginImporter:
|
||||
serializedVersion: 1
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
platformData:
|
||||
Any:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
Editor:
|
||||
enabled: 1
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
WindowsStoreApps:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0034ebae0c2a9344e897db1160d71b6d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,47 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8da095e39e9b4df488dfd436f81116d6
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 128
|
||||
textureSettings:
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 319 B |
@@ -0,0 +1,68 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 61521df2e071645488ba3d05e49289ae
|
||||
timeCreated: 1602317874
|
||||
licenseType: Store
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 4
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -1
|
||||
wrapMode: -1
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1,47 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7051dba417b3d53409f2918f1ea4938d
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 256
|
||||
textureSettings:
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1,47 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 519694efe2bb2914788b151fbd8c01f4
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 1
|
||||
linearTexture: 0
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -1
|
||||
maxTextureSize: 1024
|
||||
textureSettings:
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -1
|
||||
wrapMode: -1
|
||||
nPOTScale: 1
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 0
|
||||
textureType: -1
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,47 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78a59ca99f8987941adb61f9e14a06a7
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
serializedVersion: 2
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
linearTexture: 1
|
||||
correctGamma: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: .25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: -3
|
||||
maxTextureSize: 512
|
||||
textureSettings:
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: -1
|
||||
wrapMode: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: .5, y: .5}
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spritePixelsToUnits: 100
|
||||
alphaIsTransparency: 1
|
||||
textureType: 2
|
||||
buildTargetSettings: []
|
||||
spriteSheet:
|
||||
sprites: []
|
||||
spritePackingTag:
|
||||
userData:
|
||||
@@ -0,0 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 143604b8bad857d47a6f7cc7a533e2dc
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
userData:
|
||||
@@ -0,0 +1,198 @@
|
||||
// Author: Daniele Giardini - http://www.demigiant.com
|
||||
// Created: 2018/07/13
|
||||
|
||||
#if !DOTWEEN_NOAUDIO // MODULE_MARKER
|
||||
using System;
|
||||
using DG.Tweening.Core;
|
||||
using DG.Tweening.Plugins.Options;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Audio; // Required for AudioMixer
|
||||
|
||||
#pragma warning disable 1591
|
||||
namespace DG.Tweening
|
||||
{
|
||||
public static class DOTweenModuleAudio
|
||||
{
|
||||
#region Shortcuts
|
||||
|
||||
#region Audio
|
||||
|
||||
/// <summary>Tweens an AudioSource's volume to the given value.
|
||||
/// Also stores the AudioSource as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach (0 to 1)</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<float, float, FloatOptions> DOFade(this AudioSource target, float endValue, float duration)
|
||||
{
|
||||
if (endValue < 0) endValue = 0;
|
||||
else if (endValue > 1) endValue = 1;
|
||||
TweenerCore<float, float, FloatOptions> t = DOTween.To(() => target.volume, x => target.volume = x, endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens an AudioSource's pitch to the given value.
|
||||
/// Also stores the AudioSource as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<float, float, FloatOptions> DOPitch(this AudioSource target, float endValue, float duration)
|
||||
{
|
||||
TweenerCore<float, float, FloatOptions> t = DOTween.To(() => target.pitch, x => target.pitch = x, endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AudioMixer
|
||||
|
||||
/// <summary>Tweens an AudioMixer's exposed float to the given value.
|
||||
/// Also stores the AudioMixer as the tween's target so it can be used for filtered operations.
|
||||
/// Note that you need to manually expose a float in an AudioMixerGroup in order to be able to tween it from an AudioMixer.</summary>
|
||||
/// <param name="floatName">Name given to the exposed float to set</param>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<float, float, FloatOptions> DOSetFloat(this AudioMixer target, string floatName, float endValue, float duration)
|
||||
{
|
||||
TweenerCore<float, float, FloatOptions> t = DOTween.To(()=> {
|
||||
float currVal;
|
||||
target.GetFloat(floatName, out currVal);
|
||||
return currVal;
|
||||
}, x=> target.SetFloat(floatName, x), endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
#region Operation Shortcuts
|
||||
|
||||
/// <summary>
|
||||
/// Completes all tweens that have this target as a reference
|
||||
/// (meaning tweens that were started from this target, or that had this target added as an Id)
|
||||
/// and returns the total number of tweens completed
|
||||
/// (meaning the tweens that don't have infinite loops and were not already complete)
|
||||
/// </summary>
|
||||
/// <param name="withCallbacks">For Sequences only: if TRUE also internal Sequence callbacks will be fired,
|
||||
/// otherwise they will be ignored</param>
|
||||
public static int DOComplete(this AudioMixer target, bool withCallbacks = false)
|
||||
{
|
||||
return DOTween.Complete(target, withCallbacks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Kills all tweens that have this target as a reference
|
||||
/// (meaning tweens that were started from this target, or that had this target added as an Id)
|
||||
/// and returns the total number of tweens killed.
|
||||
/// </summary>
|
||||
/// <param name="complete">If TRUE completes the tween before killing it</param>
|
||||
public static int DOKill(this AudioMixer target, bool complete = false)
|
||||
{
|
||||
return DOTween.Kill(target, complete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flips the direction (backwards if it was going forward or viceversa) of all tweens that have this target as a reference
|
||||
/// (meaning tweens that were started from this target, or that had this target added as an Id)
|
||||
/// and returns the total number of tweens flipped.
|
||||
/// </summary>
|
||||
public static int DOFlip(this AudioMixer target)
|
||||
{
|
||||
return DOTween.Flip(target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends to the given position all tweens that have this target as a reference
|
||||
/// (meaning tweens that were started from this target, or that had this target added as an Id)
|
||||
/// and returns the total number of tweens involved.
|
||||
/// </summary>
|
||||
/// <param name="to">Time position to reach
|
||||
/// (if higher than the whole tween duration the tween will simply reach its end)</param>
|
||||
/// <param name="andPlay">If TRUE will play the tween after reaching the given position, otherwise it will pause it</param>
|
||||
public static int DOGoto(this AudioMixer target, float to, bool andPlay = false)
|
||||
{
|
||||
return DOTween.Goto(target, to, andPlay);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pauses all tweens that have this target as a reference
|
||||
/// (meaning tweens that were started from this target, or that had this target added as an Id)
|
||||
/// and returns the total number of tweens paused.
|
||||
/// </summary>
|
||||
public static int DOPause(this AudioMixer target)
|
||||
{
|
||||
return DOTween.Pause(target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays all tweens that have this target as a reference
|
||||
/// (meaning tweens that were started from this target, or that had this target added as an Id)
|
||||
/// and returns the total number of tweens played.
|
||||
/// </summary>
|
||||
public static int DOPlay(this AudioMixer target)
|
||||
{
|
||||
return DOTween.Play(target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays backwards all tweens that have this target as a reference
|
||||
/// (meaning tweens that were started from this target, or that had this target added as an Id)
|
||||
/// and returns the total number of tweens played.
|
||||
/// </summary>
|
||||
public static int DOPlayBackwards(this AudioMixer target)
|
||||
{
|
||||
return DOTween.PlayBackwards(target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plays forward all tweens that have this target as a reference
|
||||
/// (meaning tweens that were started from this target, or that had this target added as an Id)
|
||||
/// and returns the total number of tweens played.
|
||||
/// </summary>
|
||||
public static int DOPlayForward(this AudioMixer target)
|
||||
{
|
||||
return DOTween.PlayForward(target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restarts all tweens that have this target as a reference
|
||||
/// (meaning tweens that were started from this target, or that had this target added as an Id)
|
||||
/// and returns the total number of tweens restarted.
|
||||
/// </summary>
|
||||
public static int DORestart(this AudioMixer target)
|
||||
{
|
||||
return DOTween.Restart(target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rewinds all tweens that have this target as a reference
|
||||
/// (meaning tweens that were started from this target, or that had this target added as an Id)
|
||||
/// and returns the total number of tweens rewinded.
|
||||
/// </summary>
|
||||
public static int DORewind(this AudioMixer target)
|
||||
{
|
||||
return DOTween.Rewind(target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Smoothly rewinds all tweens that have this target as a reference
|
||||
/// (meaning tweens that were started from this target, or that had this target added as an Id)
|
||||
/// and returns the total number of tweens rewinded.
|
||||
/// </summary>
|
||||
public static int DOSmoothRewind(this AudioMixer target)
|
||||
{
|
||||
return DOTween.SmoothRewind(target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the paused state (plays if it was paused, pauses if it was playing) of all tweens that have this target as a reference
|
||||
/// (meaning tweens that were started from this target, or that had this target added as an Id)
|
||||
/// and returns the total number of tweens involved.
|
||||
/// </summary>
|
||||
public static int DOTogglePause(this AudioMixer target)
|
||||
{
|
||||
return DOTween.TogglePause(target);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b766d08851589514b97afb23c6f30a70
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,146 @@
|
||||
using UnityEngine;
|
||||
|
||||
#if DOTWEEN_EPO || EPO_DOTWEEN // MODULE_MARKER
|
||||
|
||||
using EPOOutline;
|
||||
using DG.Tweening.Plugins.Options;
|
||||
using DG.Tweening;
|
||||
using DG.Tweening.Core;
|
||||
|
||||
namespace DG.Tweening
|
||||
{
|
||||
public static class DOTweenModuleEPOOutline
|
||||
{
|
||||
public static int DOKill(this SerializedPass target, bool complete)
|
||||
{
|
||||
return DOTween.Kill(target, complete);
|
||||
}
|
||||
|
||||
public static TweenerCore<float, float, FloatOptions> DOFloat(this SerializedPass target, string propertyName, float endValue, float duration)
|
||||
{
|
||||
var tweener = DOTween.To(() => target.GetFloat(propertyName), x => target.SetFloat(propertyName, x), endValue, duration);
|
||||
tweener.SetOptions(true).SetTarget(target);
|
||||
return tweener;
|
||||
}
|
||||
|
||||
public static TweenerCore<Color, Color, ColorOptions> DOFade(this SerializedPass target, string propertyName, float endValue, float duration)
|
||||
{
|
||||
var tweener = DOTween.ToAlpha(() => target.GetColor(propertyName), x => target.SetColor(propertyName, x), endValue, duration);
|
||||
tweener.SetOptions(true).SetTarget(target);
|
||||
return tweener;
|
||||
}
|
||||
|
||||
public static TweenerCore<Color, Color, ColorOptions> DOColor(this SerializedPass target, string propertyName, Color endValue, float duration)
|
||||
{
|
||||
var tweener = DOTween.To(() => target.GetColor(propertyName), x => target.SetColor(propertyName, x), endValue, duration);
|
||||
tweener.SetOptions(false).SetTarget(target);
|
||||
return tweener;
|
||||
}
|
||||
|
||||
public static TweenerCore<Vector4, Vector4, VectorOptions> DOVector(this SerializedPass target, string propertyName, Vector4 endValue, float duration)
|
||||
{
|
||||
var tweener = DOTween.To(() => target.GetVector(propertyName), x => target.SetVector(propertyName, x), endValue, duration);
|
||||
tweener.SetOptions(false).SetTarget(target);
|
||||
return tweener;
|
||||
}
|
||||
|
||||
public static TweenerCore<float, float, FloatOptions> DOFloat(this SerializedPass target, int propertyId, float endValue, float duration)
|
||||
{
|
||||
var tweener = DOTween.To(() => target.GetFloat(propertyId), x => target.SetFloat(propertyId, x), endValue, duration);
|
||||
tweener.SetOptions(true).SetTarget(target);
|
||||
return tweener;
|
||||
}
|
||||
|
||||
public static TweenerCore<Color, Color, ColorOptions> DOFade(this SerializedPass target, int propertyId, float endValue, float duration)
|
||||
{
|
||||
var tweener = DOTween.ToAlpha(() => target.GetColor(propertyId), x => target.SetColor(propertyId, x), endValue, duration);
|
||||
tweener.SetOptions(true).SetTarget(target);
|
||||
return tweener;
|
||||
}
|
||||
|
||||
public static TweenerCore<Color, Color, ColorOptions> DOColor(this SerializedPass target, int propertyId, Color endValue, float duration)
|
||||
{
|
||||
var tweener = DOTween.To(() => target.GetColor(propertyId), x => target.SetColor(propertyId, x), endValue, duration);
|
||||
tweener.SetOptions(false).SetTarget(target);
|
||||
return tweener;
|
||||
}
|
||||
|
||||
public static TweenerCore<Vector4, Vector4, VectorOptions> DOVector(this SerializedPass target, int propertyId, Vector4 endValue, float duration)
|
||||
{
|
||||
var tweener = DOTween.To(() => target.GetVector(propertyId), x => target.SetVector(propertyId, x), endValue, duration);
|
||||
tweener.SetOptions(false).SetTarget(target);
|
||||
return tweener;
|
||||
}
|
||||
|
||||
public static int DOKill(this Outlinable.OutlineProperties target, bool complete = false)
|
||||
{
|
||||
return DOTween.Kill(target, complete);
|
||||
}
|
||||
|
||||
public static int DOKill(this Outliner target, bool complete = false)
|
||||
{
|
||||
return DOTween.Kill(target, complete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controls the alpha (transparency) of the outline
|
||||
/// </summary>
|
||||
public static TweenerCore<Color, Color, ColorOptions> DOFade(this Outlinable.OutlineProperties target, float endValue, float duration)
|
||||
{
|
||||
var tweener = DOTween.ToAlpha(() => target.Color, x => target.Color = x, endValue, duration);
|
||||
tweener.SetOptions(true).SetTarget(target);
|
||||
return tweener;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controls the color of the outline
|
||||
/// </summary>
|
||||
public static TweenerCore<Color, Color, ColorOptions> DOColor(this Outlinable.OutlineProperties target, Color endValue, float duration)
|
||||
{
|
||||
var tweener = DOTween.To(() => target.Color, x => target.Color = x, endValue, duration);
|
||||
tweener.SetOptions(false).SetTarget(target);
|
||||
return tweener;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controls the amount of blur applied to the outline
|
||||
/// </summary>
|
||||
public static TweenerCore<float, float, FloatOptions> DOBlurShift(this Outlinable.OutlineProperties target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
var tweener = DOTween.To(() => target.BlurShift, x => target.BlurShift = x, endValue, duration);
|
||||
tweener.SetOptions(snapping).SetTarget(target);
|
||||
return tweener;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controls the amount of blur applied to the outline
|
||||
/// </summary>
|
||||
public static TweenerCore<float, float, FloatOptions> DOBlurShift(this Outliner target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
var tweener = DOTween.To(() => target.BlurShift, x => target.BlurShift = x, endValue, duration);
|
||||
tweener.SetOptions(snapping).SetTarget(target);
|
||||
return tweener;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controls the amount of dilation applied to the outline
|
||||
/// </summary>
|
||||
public static TweenerCore<float, float, FloatOptions> DODilateShift(this Outlinable.OutlineProperties target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
var tweener = DOTween.To(() => target.DilateShift, x => target.DilateShift = x, endValue, duration);
|
||||
tweener.SetOptions(snapping).SetTarget(target);
|
||||
return tweener;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controls the amount of dilation applied to the outline
|
||||
/// </summary>
|
||||
public static TweenerCore<float, float, FloatOptions> DODilateShift(this Outliner target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
var tweener = DOTween.To(() => target.DilateShift, x => target.DilateShift = x, endValue, duration);
|
||||
tweener.SetOptions(snapping).SetTarget(target);
|
||||
return tweener;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e944529dcaee98f4e9498d80e541d93e
|
||||
timeCreated: 1602593330
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,216 @@
|
||||
// Author: Daniele Giardini - http://www.demigiant.com
|
||||
// Created: 2018/07/13
|
||||
|
||||
#if !DOTWEEN_NOPHYSICS // MODULE_MARKER
|
||||
using System;
|
||||
using DG.Tweening.Core;
|
||||
using DG.Tweening.Core.Enums;
|
||||
using DG.Tweening.Plugins;
|
||||
using DG.Tweening.Plugins.Core.PathCore;
|
||||
using DG.Tweening.Plugins.Options;
|
||||
using UnityEngine;
|
||||
|
||||
#pragma warning disable 1591
|
||||
namespace DG.Tweening
|
||||
{
|
||||
public static class DOTweenModulePhysics
|
||||
{
|
||||
#region Shortcuts
|
||||
|
||||
#region Rigidbody
|
||||
|
||||
/// <summary>Tweens a Rigidbody's position to the given value.
|
||||
/// Also stores the rigidbody as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector3, Vector3, VectorOptions> DOMove(this Rigidbody target, Vector3 endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector3, Vector3, VectorOptions> t = DOTween.To(() => target.position, target.MovePosition, endValue, duration);
|
||||
t.SetOptions(snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a Rigidbody's X position to the given value.
|
||||
/// Also stores the rigidbody as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector3, Vector3, VectorOptions> DOMoveX(this Rigidbody target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector3, Vector3, VectorOptions> t = DOTween.To(() => target.position, target.MovePosition, new Vector3(endValue, 0, 0), duration);
|
||||
t.SetOptions(AxisConstraint.X, snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a Rigidbody's Y position to the given value.
|
||||
/// Also stores the rigidbody as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector3, Vector3, VectorOptions> DOMoveY(this Rigidbody target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector3, Vector3, VectorOptions> t = DOTween.To(() => target.position, target.MovePosition, new Vector3(0, endValue, 0), duration);
|
||||
t.SetOptions(AxisConstraint.Y, snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a Rigidbody's Z position to the given value.
|
||||
/// Also stores the rigidbody as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector3, Vector3, VectorOptions> DOMoveZ(this Rigidbody target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector3, Vector3, VectorOptions> t = DOTween.To(() => target.position, target.MovePosition, new Vector3(0, 0, endValue), duration);
|
||||
t.SetOptions(AxisConstraint.Z, snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a Rigidbody's rotation to the given value.
|
||||
/// Also stores the rigidbody as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="mode">Rotation mode</param>
|
||||
public static TweenerCore<Quaternion, Vector3, QuaternionOptions> DORotate(this Rigidbody target, Vector3 endValue, float duration, RotateMode mode = RotateMode.Fast)
|
||||
{
|
||||
TweenerCore<Quaternion, Vector3, QuaternionOptions> t = DOTween.To(() => target.rotation, target.MoveRotation, endValue, duration);
|
||||
t.SetTarget(target);
|
||||
t.plugOptions.rotateMode = mode;
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a Rigidbody's rotation so that it will look towards the given position.
|
||||
/// Also stores the rigidbody as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="towards">The position to look at</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="axisConstraint">Eventual axis constraint for the rotation</param>
|
||||
/// <param name="up">The vector that defines in which direction up is (default: Vector3.up)</param>
|
||||
public static TweenerCore<Quaternion, Vector3, QuaternionOptions> DOLookAt(this Rigidbody target, Vector3 towards, float duration, AxisConstraint axisConstraint = AxisConstraint.None, Vector3? up = null)
|
||||
{
|
||||
TweenerCore<Quaternion, Vector3, QuaternionOptions> t = DOTween.To(() => target.rotation, target.MoveRotation, towards, duration)
|
||||
.SetTarget(target).SetSpecialStartupMode(SpecialStartupMode.SetLookAt);
|
||||
t.plugOptions.axisConstraint = axisConstraint;
|
||||
t.plugOptions.up = (up == null) ? Vector3.up : (Vector3)up;
|
||||
return t;
|
||||
}
|
||||
|
||||
#region Special
|
||||
|
||||
/// <summary>Tweens a Rigidbody's position to the given value, while also applying a jump effect along the Y axis.
|
||||
/// Returns a Sequence instead of a Tweener.
|
||||
/// Also stores the Rigidbody as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param>
|
||||
/// <param name="jumpPower">Power of the jump (the max height of the jump is represented by this plus the final Y offset)</param>
|
||||
/// <param name="numJumps">Total number of jumps</param>
|
||||
/// <param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static Sequence DOJump(this Rigidbody target, Vector3 endValue, float jumpPower, int numJumps, float duration, bool snapping = false)
|
||||
{
|
||||
if (numJumps < 1) numJumps = 1;
|
||||
float startPosY = 0;
|
||||
float offsetY = -1;
|
||||
bool offsetYSet = false;
|
||||
Sequence s = DOTween.Sequence();
|
||||
Tween yTween = DOTween.To(() => target.position, target.MovePosition, new Vector3(0, jumpPower, 0), duration / (numJumps * 2))
|
||||
.SetOptions(AxisConstraint.Y, snapping).SetEase(Ease.OutQuad).SetRelative()
|
||||
.SetLoops(numJumps * 2, LoopType.Yoyo)
|
||||
.OnStart(() => startPosY = target.position.y);
|
||||
s.Append(DOTween.To(() => target.position, target.MovePosition, new Vector3(endValue.x, 0, 0), duration)
|
||||
.SetOptions(AxisConstraint.X, snapping).SetEase(Ease.Linear)
|
||||
).Join(DOTween.To(() => target.position, target.MovePosition, new Vector3(0, 0, endValue.z), duration)
|
||||
.SetOptions(AxisConstraint.Z, snapping).SetEase(Ease.Linear)
|
||||
).Join(yTween)
|
||||
.SetTarget(target).SetEase(DOTween.defaultEaseType);
|
||||
yTween.OnUpdate(() => {
|
||||
if (!offsetYSet) {
|
||||
offsetYSet = true;
|
||||
offsetY = s.isRelative ? endValue.y : endValue.y - startPosY;
|
||||
}
|
||||
Vector3 pos = target.position;
|
||||
pos.y += DOVirtual.EasedValue(0, offsetY, yTween.ElapsedPercentage(), Ease.OutQuad);
|
||||
target.MovePosition(pos);
|
||||
});
|
||||
return s;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a Rigidbody's position through the given path waypoints, using the chosen path algorithm.
|
||||
/// Also stores the Rigidbody as the tween's target so it can be used for filtered operations.
|
||||
/// <para>NOTE: to tween a rigidbody correctly it should be set to kinematic at least while being tweened.</para>
|
||||
/// <para>BEWARE: doesn't work on Windows Phone store (waiting for Unity to fix their own bug).
|
||||
/// If you plan to publish there you should use a regular transform.DOPath.</para></summary>
|
||||
/// <param name="path">The waypoints to go through</param>
|
||||
/// <param name="duration">The duration of the tween</param>
|
||||
/// <param name="pathType">The type of path: Linear (straight path), CatmullRom (curved CatmullRom path) or CubicBezier (curved with control points)</param>
|
||||
/// <param name="pathMode">The path mode: 3D, side-scroller 2D, top-down 2D</param>
|
||||
/// <param name="resolution">The resolution of the path (useless in case of Linear paths): higher resolutions make for more detailed curved paths but are more expensive.
|
||||
/// Defaults to 10, but a value of 5 is usually enough if you don't have dramatic long curves between waypoints</param>
|
||||
/// <param name="gizmoColor">The color of the path (shown when gizmos are active in the Play panel and the tween is running)</param>
|
||||
public static TweenerCore<Vector3, Path, PathOptions> DOPath(
|
||||
this Rigidbody target, Vector3[] path, float duration, PathType pathType = PathType.Linear,
|
||||
PathMode pathMode = PathMode.Full3D, int resolution = 10, Color? gizmoColor = null
|
||||
)
|
||||
{
|
||||
if (resolution < 1) resolution = 1;
|
||||
TweenerCore<Vector3, Path, PathOptions> t = DOTween.To(PathPlugin.Get(), () => target.position, target.MovePosition, new Path(pathType, path, resolution, gizmoColor), duration)
|
||||
.SetTarget(target).SetUpdate(UpdateType.Fixed);
|
||||
|
||||
t.plugOptions.isRigidbody = true;
|
||||
t.plugOptions.mode = pathMode;
|
||||
return t;
|
||||
}
|
||||
/// <summary>Tweens a Rigidbody's localPosition through the given path waypoints, using the chosen path algorithm.
|
||||
/// Also stores the Rigidbody as the tween's target so it can be used for filtered operations
|
||||
/// <para>NOTE: to tween a rigidbody correctly it should be set to kinematic at least while being tweened.</para>
|
||||
/// <para>BEWARE: doesn't work on Windows Phone store (waiting for Unity to fix their own bug).
|
||||
/// If you plan to publish there you should use a regular transform.DOLocalPath.</para></summary>
|
||||
/// <param name="path">The waypoint to go through</param>
|
||||
/// <param name="duration">The duration of the tween</param>
|
||||
/// <param name="pathType">The type of path: Linear (straight path), CatmullRom (curved CatmullRom path) or CubicBezier (curved with control points)</param>
|
||||
/// <param name="pathMode">The path mode: 3D, side-scroller 2D, top-down 2D</param>
|
||||
/// <param name="resolution">The resolution of the path: higher resolutions make for more detailed curved paths but are more expensive.
|
||||
/// Defaults to 10, but a value of 5 is usually enough if you don't have dramatic long curves between waypoints</param>
|
||||
/// <param name="gizmoColor">The color of the path (shown when gizmos are active in the Play panel and the tween is running)</param>
|
||||
public static TweenerCore<Vector3, Path, PathOptions> DOLocalPath(
|
||||
this Rigidbody target, Vector3[] path, float duration, PathType pathType = PathType.Linear,
|
||||
PathMode pathMode = PathMode.Full3D, int resolution = 10, Color? gizmoColor = null
|
||||
)
|
||||
{
|
||||
if (resolution < 1) resolution = 1;
|
||||
Transform trans = target.transform;
|
||||
TweenerCore<Vector3, Path, PathOptions> t = DOTween.To(PathPlugin.Get(), () => trans.localPosition, x => target.MovePosition(trans.parent == null ? x : trans.parent.TransformPoint(x)), new Path(pathType, path, resolution, gizmoColor), duration)
|
||||
.SetTarget(target).SetUpdate(UpdateType.Fixed);
|
||||
|
||||
t.plugOptions.isRigidbody = true;
|
||||
t.plugOptions.mode = pathMode;
|
||||
t.plugOptions.useLocalPosition = true;
|
||||
return t;
|
||||
}
|
||||
// Used by path editor when creating the actual tween, so it can pass a pre-compiled path
|
||||
internal static TweenerCore<Vector3, Path, PathOptions> DOPath(
|
||||
this Rigidbody target, Path path, float duration, PathMode pathMode = PathMode.Full3D
|
||||
)
|
||||
{
|
||||
TweenerCore<Vector3, Path, PathOptions> t = DOTween.To(PathPlugin.Get(), () => target.position, target.MovePosition, path, duration)
|
||||
.SetTarget(target);
|
||||
|
||||
t.plugOptions.isRigidbody = true;
|
||||
t.plugOptions.mode = pathMode;
|
||||
return t;
|
||||
}
|
||||
internal static TweenerCore<Vector3, Path, PathOptions> DOLocalPath(
|
||||
this Rigidbody target, Path path, float duration, PathMode pathMode = PathMode.Full3D
|
||||
)
|
||||
{
|
||||
Transform trans = target.transform;
|
||||
TweenerCore<Vector3, Path, PathOptions> t = DOTween.To(PathPlugin.Get(), () => trans.localPosition, x => target.MovePosition(trans.parent == null ? x : trans.parent.TransformPoint(x)), path, duration)
|
||||
.SetTarget(target);
|
||||
|
||||
t.plugOptions.isRigidbody = true;
|
||||
t.plugOptions.mode = pathMode;
|
||||
t.plugOptions.useLocalPosition = true;
|
||||
return t;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dae9aa560b4242648a3affa2bfabc365
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,193 @@
|
||||
// Author: Daniele Giardini - http://www.demigiant.com
|
||||
// Created: 2018/07/13
|
||||
|
||||
#if !DOTWEEN_NOPHYSICS2D // MODULE_MARKER
|
||||
using System;
|
||||
using DG.Tweening.Core;
|
||||
using DG.Tweening.Plugins;
|
||||
using DG.Tweening.Plugins.Core.PathCore;
|
||||
using DG.Tweening.Plugins.Options;
|
||||
using UnityEngine;
|
||||
|
||||
#pragma warning disable 1591
|
||||
namespace DG.Tweening
|
||||
{
|
||||
public static class DOTweenModulePhysics2D
|
||||
{
|
||||
#region Shortcuts
|
||||
|
||||
#region Rigidbody2D Shortcuts
|
||||
|
||||
/// <summary>Tweens a Rigidbody2D's position to the given value.
|
||||
/// Also stores the Rigidbody2D as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOMove(this Rigidbody2D target, Vector2 endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t = DOTween.To(() => target.position, target.MovePosition, endValue, duration);
|
||||
t.SetOptions(snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a Rigidbody2D's X position to the given value.
|
||||
/// Also stores the Rigidbody2D as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOMoveX(this Rigidbody2D target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t = DOTween.To(() => target.position, target.MovePosition, new Vector2(endValue, 0), duration);
|
||||
t.SetOptions(AxisConstraint.X, snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a Rigidbody2D's Y position to the given value.
|
||||
/// Also stores the Rigidbody2D as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOMoveY(this Rigidbody2D target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t = DOTween.To(() => target.position, target.MovePosition, new Vector2(0, endValue), duration);
|
||||
t.SetOptions(AxisConstraint.Y, snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a Rigidbody2D's rotation to the given value.
|
||||
/// Also stores the Rigidbody2D as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<float, float, FloatOptions> DORotate(this Rigidbody2D target, float endValue, float duration)
|
||||
{
|
||||
TweenerCore<float, float, FloatOptions> t = DOTween.To(() => target.rotation, target.MoveRotation, endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
#region Special
|
||||
|
||||
/// <summary>Tweens a Rigidbody2D's position to the given value, while also applying a jump effect along the Y axis.
|
||||
/// Returns a Sequence instead of a Tweener.
|
||||
/// Also stores the Rigidbody2D as the tween's target so it can be used for filtered operations.
|
||||
/// <para>IMPORTANT: a rigidbody2D can't be animated in a jump arc using MovePosition, so the tween will directly set the position</para></summary>
|
||||
/// <param name="endValue">The end value to reach</param>
|
||||
/// <param name="jumpPower">Power of the jump (the max height of the jump is represented by this plus the final Y offset)</param>
|
||||
/// <param name="numJumps">Total number of jumps</param>
|
||||
/// <param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static Sequence DOJump(this Rigidbody2D target, Vector2 endValue, float jumpPower, int numJumps, float duration, bool snapping = false)
|
||||
{
|
||||
if (numJumps < 1) numJumps = 1;
|
||||
float startPosY = 0;
|
||||
float offsetY = -1;
|
||||
bool offsetYSet = false;
|
||||
Sequence s = DOTween.Sequence();
|
||||
Tween yTween = DOTween.To(() => target.position, x => target.position = x, new Vector2(0, jumpPower), duration / (numJumps * 2))
|
||||
.SetOptions(AxisConstraint.Y, snapping).SetEase(Ease.OutQuad).SetRelative()
|
||||
.SetLoops(numJumps * 2, LoopType.Yoyo)
|
||||
.OnStart(() => startPosY = target.position.y);
|
||||
s.Append(DOTween.To(() => target.position, x => target.position = x, new Vector2(endValue.x, 0), duration)
|
||||
.SetOptions(AxisConstraint.X, snapping).SetEase(Ease.Linear)
|
||||
).Join(yTween)
|
||||
.SetTarget(target).SetEase(DOTween.defaultEaseType);
|
||||
yTween.OnUpdate(() => {
|
||||
if (!offsetYSet) {
|
||||
offsetYSet = true;
|
||||
offsetY = s.isRelative ? endValue.y : endValue.y - startPosY;
|
||||
}
|
||||
Vector3 pos = target.position;
|
||||
pos.y += DOVirtual.EasedValue(0, offsetY, yTween.ElapsedPercentage(), Ease.OutQuad);
|
||||
target.MovePosition(pos);
|
||||
});
|
||||
return s;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a Rigidbody2D's position through the given path waypoints, using the chosen path algorithm.
|
||||
/// Also stores the Rigidbody2D as the tween's target so it can be used for filtered operations.
|
||||
/// <para>NOTE: to tween a Rigidbody2D correctly it should be set to kinematic at least while being tweened.</para>
|
||||
/// <para>BEWARE: doesn't work on Windows Phone store (waiting for Unity to fix their own bug).
|
||||
/// If you plan to publish there you should use a regular transform.DOPath.</para></summary>
|
||||
/// <param name="path">The waypoints to go through</param>
|
||||
/// <param name="duration">The duration of the tween</param>
|
||||
/// <param name="pathType">The type of path: Linear (straight path), CatmullRom (curved CatmullRom path) or CubicBezier (curved with control points)</param>
|
||||
/// <param name="pathMode">The path mode: 3D, side-scroller 2D, top-down 2D</param>
|
||||
/// <param name="resolution">The resolution of the path (useless in case of Linear paths): higher resolutions make for more detailed curved paths but are more expensive.
|
||||
/// Defaults to 10, but a value of 5 is usually enough if you don't have dramatic long curves between waypoints</param>
|
||||
/// <param name="gizmoColor">The color of the path (shown when gizmos are active in the Play panel and the tween is running)</param>
|
||||
public static TweenerCore<Vector3, Path, PathOptions> DOPath(
|
||||
this Rigidbody2D target, Vector2[] path, float duration, PathType pathType = PathType.Linear,
|
||||
PathMode pathMode = PathMode.Full3D, int resolution = 10, Color? gizmoColor = null
|
||||
)
|
||||
{
|
||||
if (resolution < 1) resolution = 1;
|
||||
int len = path.Length;
|
||||
Vector3[] path3D = new Vector3[len];
|
||||
for (int i = 0; i < len; ++i) path3D[i] = path[i];
|
||||
TweenerCore<Vector3, Path, PathOptions> t = DOTween.To(PathPlugin.Get(), () => target.position, x => target.MovePosition(x), new Path(pathType, path3D, resolution, gizmoColor), duration)
|
||||
.SetTarget(target).SetUpdate(UpdateType.Fixed);
|
||||
|
||||
t.plugOptions.isRigidbody2D = true;
|
||||
t.plugOptions.mode = pathMode;
|
||||
return t;
|
||||
}
|
||||
/// <summary>Tweens a Rigidbody2D's localPosition through the given path waypoints, using the chosen path algorithm.
|
||||
/// Also stores the Rigidbody2D as the tween's target so it can be used for filtered operations
|
||||
/// <para>NOTE: to tween a Rigidbody2D correctly it should be set to kinematic at least while being tweened.</para>
|
||||
/// <para>BEWARE: doesn't work on Windows Phone store (waiting for Unity to fix their own bug).
|
||||
/// If you plan to publish there you should use a regular transform.DOLocalPath.</para></summary>
|
||||
/// <param name="path">The waypoint to go through</param>
|
||||
/// <param name="duration">The duration of the tween</param>
|
||||
/// <param name="pathType">The type of path: Linear (straight path), CatmullRom (curved CatmullRom path) or CubicBezier (curved with control points)</param>
|
||||
/// <param name="pathMode">The path mode: 3D, side-scroller 2D, top-down 2D</param>
|
||||
/// <param name="resolution">The resolution of the path: higher resolutions make for more detailed curved paths but are more expensive.
|
||||
/// Defaults to 10, but a value of 5 is usually enough if you don't have dramatic long curves between waypoints</param>
|
||||
/// <param name="gizmoColor">The color of the path (shown when gizmos are active in the Play panel and the tween is running)</param>
|
||||
public static TweenerCore<Vector3, Path, PathOptions> DOLocalPath(
|
||||
this Rigidbody2D target, Vector2[] path, float duration, PathType pathType = PathType.Linear,
|
||||
PathMode pathMode = PathMode.Full3D, int resolution = 10, Color? gizmoColor = null
|
||||
)
|
||||
{
|
||||
if (resolution < 1) resolution = 1;
|
||||
int len = path.Length;
|
||||
Vector3[] path3D = new Vector3[len];
|
||||
for (int i = 0; i < len; ++i) path3D[i] = path[i];
|
||||
Transform trans = target.transform;
|
||||
TweenerCore<Vector3, Path, PathOptions> t = DOTween.To(PathPlugin.Get(), () => trans.localPosition, x => target.MovePosition(trans.parent == null ? x : trans.parent.TransformPoint(x)), new Path(pathType, path3D, resolution, gizmoColor), duration)
|
||||
.SetTarget(target).SetUpdate(UpdateType.Fixed);
|
||||
|
||||
t.plugOptions.isRigidbody2D = true;
|
||||
t.plugOptions.mode = pathMode;
|
||||
t.plugOptions.useLocalPosition = true;
|
||||
return t;
|
||||
}
|
||||
// Used by path editor when creating the actual tween, so it can pass a pre-compiled path
|
||||
internal static TweenerCore<Vector3, Path, PathOptions> DOPath(
|
||||
this Rigidbody2D target, Path path, float duration, PathMode pathMode = PathMode.Full3D
|
||||
)
|
||||
{
|
||||
TweenerCore<Vector3, Path, PathOptions> t = DOTween.To(PathPlugin.Get(), () => target.position, x => target.MovePosition(x), path, duration)
|
||||
.SetTarget(target);
|
||||
|
||||
t.plugOptions.isRigidbody2D = true;
|
||||
t.plugOptions.mode = pathMode;
|
||||
return t;
|
||||
}
|
||||
internal static TweenerCore<Vector3, Path, PathOptions> DOLocalPath(
|
||||
this Rigidbody2D target, Path path, float duration, PathMode pathMode = PathMode.Full3D
|
||||
)
|
||||
{
|
||||
Transform trans = target.transform;
|
||||
TweenerCore<Vector3, Path, PathOptions> t = DOTween.To(PathPlugin.Get(), () => trans.localPosition, x => target.MovePosition(trans.parent == null ? x : trans.parent.TransformPoint(x)), path, duration)
|
||||
.SetTarget(target);
|
||||
|
||||
t.plugOptions.isRigidbody2D = true;
|
||||
t.plugOptions.mode = pathMode;
|
||||
t.plugOptions.useLocalPosition = true;
|
||||
return t;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 230fe34542e175245ba74b4659dae700
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,93 @@
|
||||
// Author: Daniele Giardini - http://www.demigiant.com
|
||||
// Created: 2018/07/13
|
||||
|
||||
#if !DOTWEEN_NOSPRITES // MODULE_MARKER
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using DG.Tweening.Core;
|
||||
using DG.Tweening.Plugins.Options;
|
||||
|
||||
#pragma warning disable 1591
|
||||
namespace DG.Tweening
|
||||
{
|
||||
public static class DOTweenModuleSprite
|
||||
{
|
||||
#region Shortcuts
|
||||
|
||||
#region SpriteRenderer
|
||||
|
||||
/// <summary>Tweens a SpriteRenderer's color to the given value.
|
||||
/// Also stores the spriteRenderer as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<Color, Color, ColorOptions> DOColor(this SpriteRenderer target, Color endValue, float duration)
|
||||
{
|
||||
TweenerCore<Color, Color, ColorOptions> t = DOTween.To(() => target.color, x => target.color = x, endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a Material's alpha color to the given value.
|
||||
/// Also stores the spriteRenderer as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<Color, Color, ColorOptions> DOFade(this SpriteRenderer target, float endValue, float duration)
|
||||
{
|
||||
TweenerCore<Color, Color, ColorOptions> t = DOTween.ToAlpha(() => target.color, x => target.color = x, endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a SpriteRenderer's color using the given gradient
|
||||
/// (NOTE 1: only uses the colors of the gradient, not the alphas - NOTE 2: creates a Sequence, not a Tweener).
|
||||
/// Also stores the image as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="gradient">The gradient to use</param><param name="duration">The duration of the tween</param>
|
||||
public static Sequence DOGradientColor(this SpriteRenderer target, Gradient gradient, float duration)
|
||||
{
|
||||
Sequence s = DOTween.Sequence();
|
||||
GradientColorKey[] colors = gradient.colorKeys;
|
||||
int len = colors.Length;
|
||||
for (int i = 0; i < len; ++i) {
|
||||
GradientColorKey c = colors[i];
|
||||
if (i == 0 && c.time <= 0) {
|
||||
target.color = c.color;
|
||||
continue;
|
||||
}
|
||||
float colorDuration = i == len - 1
|
||||
? duration - s.Duration(false) // Verifies that total duration is correct
|
||||
: duration * (i == 0 ? c.time : c.time - colors[i - 1].time);
|
||||
s.Append(target.DOColor(c.color, colorDuration).SetEase(Ease.Linear));
|
||||
}
|
||||
s.SetTarget(target);
|
||||
return s;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Blendables
|
||||
|
||||
#region SpriteRenderer
|
||||
|
||||
/// <summary>Tweens a SpriteRenderer's color to the given value,
|
||||
/// in a way that allows other DOBlendableColor tweens to work together on the same target,
|
||||
/// instead than fight each other as multiple DOColor would do.
|
||||
/// Also stores the SpriteRenderer as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The value to tween to</param><param name="duration">The duration of the tween</param>
|
||||
public static Tweener DOBlendableColor(this SpriteRenderer target, Color endValue, float duration)
|
||||
{
|
||||
endValue = endValue - target.color;
|
||||
Color to = new Color(0, 0, 0, 0);
|
||||
return DOTween.To(() => to, x => {
|
||||
Color diff = x - to;
|
||||
to = x;
|
||||
target.color += diff;
|
||||
}, endValue, duration)
|
||||
.Blendable().SetTarget(target);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 188918ab119d93148aa0de59ccf5286b
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,662 @@
|
||||
// Author: Daniele Giardini - http://www.demigiant.com
|
||||
// Created: 2018/07/13
|
||||
|
||||
#if !DOTWEEN_NOUI // MODULE_MARKER
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using DG.Tweening.Core;
|
||||
using DG.Tweening.Core.Enums;
|
||||
using DG.Tweening.Plugins;
|
||||
using DG.Tweening.Plugins.Options;
|
||||
using Outline = UnityEngine.UI.Outline;
|
||||
using Text = UnityEngine.UI.Text;
|
||||
|
||||
#pragma warning disable 1591
|
||||
namespace DG.Tweening
|
||||
{
|
||||
public static class DOTweenModuleUI
|
||||
{
|
||||
#region Shortcuts
|
||||
|
||||
#region CanvasGroup
|
||||
|
||||
/// <summary>Tweens a CanvasGroup's alpha color to the given value.
|
||||
/// Also stores the canvasGroup as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<float, float, FloatOptions> DOFade(this CanvasGroup target, float endValue, float duration)
|
||||
{
|
||||
TweenerCore<float, float, FloatOptions> t = DOTween.To(() => target.alpha, x => target.alpha = x, endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Graphic
|
||||
|
||||
/// <summary>Tweens an Graphic's color to the given value.
|
||||
/// Also stores the image as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<Color, Color, ColorOptions> DOColor(this Graphic target, Color endValue, float duration)
|
||||
{
|
||||
TweenerCore<Color, Color, ColorOptions> t = DOTween.To(() => target.color, x => target.color = x, endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens an Graphic's alpha color to the given value.
|
||||
/// Also stores the image as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<Color, Color, ColorOptions> DOFade(this Graphic target, float endValue, float duration)
|
||||
{
|
||||
TweenerCore<Color, Color, ColorOptions> t = DOTween.ToAlpha(() => target.color, x => target.color = x, endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Image
|
||||
|
||||
/// <summary>Tweens an Image's color to the given value.
|
||||
/// Also stores the image as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<Color, Color, ColorOptions> DOColor(this Image target, Color endValue, float duration)
|
||||
{
|
||||
TweenerCore<Color, Color, ColorOptions> t = DOTween.To(() => target.color, x => target.color = x, endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens an Image's alpha color to the given value.
|
||||
/// Also stores the image as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<Color, Color, ColorOptions> DOFade(this Image target, float endValue, float duration)
|
||||
{
|
||||
TweenerCore<Color, Color, ColorOptions> t = DOTween.ToAlpha(() => target.color, x => target.color = x, endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens an Image's fillAmount to the given value.
|
||||
/// Also stores the image as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach (0 to 1)</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<float, float, FloatOptions> DOFillAmount(this Image target, float endValue, float duration)
|
||||
{
|
||||
if (endValue > 1) endValue = 1;
|
||||
else if (endValue < 0) endValue = 0;
|
||||
TweenerCore<float, float, FloatOptions> t = DOTween.To(() => target.fillAmount, x => target.fillAmount = x, endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens an Image's colors using the given gradient
|
||||
/// (NOTE 1: only uses the colors of the gradient, not the alphas - NOTE 2: creates a Sequence, not a Tweener).
|
||||
/// Also stores the image as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="gradient">The gradient to use</param><param name="duration">The duration of the tween</param>
|
||||
public static Sequence DOGradientColor(this Image target, Gradient gradient, float duration)
|
||||
{
|
||||
Sequence s = DOTween.Sequence();
|
||||
GradientColorKey[] colors = gradient.colorKeys;
|
||||
int len = colors.Length;
|
||||
for (int i = 0; i < len; ++i) {
|
||||
GradientColorKey c = colors[i];
|
||||
if (i == 0 && c.time <= 0) {
|
||||
target.color = c.color;
|
||||
continue;
|
||||
}
|
||||
float colorDuration = i == len - 1
|
||||
? duration - s.Duration(false) // Verifies that total duration is correct
|
||||
: duration * (i == 0 ? c.time : c.time - colors[i - 1].time);
|
||||
s.Append(target.DOColor(c.color, colorDuration).SetEase(Ease.Linear));
|
||||
}
|
||||
s.SetTarget(target);
|
||||
return s;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region LayoutElement
|
||||
|
||||
/// <summary>Tweens an LayoutElement's flexibleWidth/Height to the given value.
|
||||
/// Also stores the LayoutElement as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOFlexibleSize(this LayoutElement target, Vector2 endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t = DOTween.To(() => new Vector2(target.flexibleWidth, target.flexibleHeight), x => {
|
||||
target.flexibleWidth = x.x;
|
||||
target.flexibleHeight = x.y;
|
||||
}, endValue, duration);
|
||||
t.SetOptions(snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens an LayoutElement's minWidth/Height to the given value.
|
||||
/// Also stores the LayoutElement as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOMinSize(this LayoutElement target, Vector2 endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t = DOTween.To(() => new Vector2(target.minWidth, target.minHeight), x => {
|
||||
target.minWidth = x.x;
|
||||
target.minHeight = x.y;
|
||||
}, endValue, duration);
|
||||
t.SetOptions(snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens an LayoutElement's preferredWidth/Height to the given value.
|
||||
/// Also stores the LayoutElement as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOPreferredSize(this LayoutElement target, Vector2 endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t = DOTween.To(() => new Vector2(target.preferredWidth, target.preferredHeight), x => {
|
||||
target.preferredWidth = x.x;
|
||||
target.preferredHeight = x.y;
|
||||
}, endValue, duration);
|
||||
t.SetOptions(snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Outline
|
||||
|
||||
/// <summary>Tweens a Outline's effectColor to the given value.
|
||||
/// Also stores the Outline as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<Color, Color, ColorOptions> DOColor(this Outline target, Color endValue, float duration)
|
||||
{
|
||||
TweenerCore<Color, Color, ColorOptions> t = DOTween.To(() => target.effectColor, x => target.effectColor = x, endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a Outline's effectColor alpha to the given value.
|
||||
/// Also stores the Outline as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<Color, Color, ColorOptions> DOFade(this Outline target, float endValue, float duration)
|
||||
{
|
||||
TweenerCore<Color, Color, ColorOptions> t = DOTween.ToAlpha(() => target.effectColor, x => target.effectColor = x, endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a Outline's effectDistance to the given value.
|
||||
/// Also stores the Outline as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOScale(this Outline target, Vector2 endValue, float duration)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t = DOTween.To(() => target.effectDistance, x => target.effectDistance = x, endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region RectTransform
|
||||
|
||||
/// <summary>Tweens a RectTransform's anchoredPosition to the given value.
|
||||
/// Also stores the RectTransform as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOAnchorPos(this RectTransform target, Vector2 endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t = DOTween.To(() => target.anchoredPosition, x => target.anchoredPosition = x, endValue, duration);
|
||||
t.SetOptions(snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
/// <summary>Tweens a RectTransform's anchoredPosition X to the given value.
|
||||
/// Also stores the RectTransform as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOAnchorPosX(this RectTransform target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t = DOTween.To(() => target.anchoredPosition, x => target.anchoredPosition = x, new Vector2(endValue, 0), duration);
|
||||
t.SetOptions(AxisConstraint.X, snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
/// <summary>Tweens a RectTransform's anchoredPosition Y to the given value.
|
||||
/// Also stores the RectTransform as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOAnchorPosY(this RectTransform target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t = DOTween.To(() => target.anchoredPosition, x => target.anchoredPosition = x, new Vector2(0, endValue), duration);
|
||||
t.SetOptions(AxisConstraint.Y, snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a RectTransform's anchoredPosition3D to the given value.
|
||||
/// Also stores the RectTransform as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector3, Vector3, VectorOptions> DOAnchorPos3D(this RectTransform target, Vector3 endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector3, Vector3, VectorOptions> t = DOTween.To(() => target.anchoredPosition3D, x => target.anchoredPosition3D = x, endValue, duration);
|
||||
t.SetOptions(snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
/// <summary>Tweens a RectTransform's anchoredPosition3D X to the given value.
|
||||
/// Also stores the RectTransform as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector3, Vector3, VectorOptions> DOAnchorPos3DX(this RectTransform target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector3, Vector3, VectorOptions> t = DOTween.To(() => target.anchoredPosition3D, x => target.anchoredPosition3D = x, new Vector3(endValue, 0, 0), duration);
|
||||
t.SetOptions(AxisConstraint.X, snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
/// <summary>Tweens a RectTransform's anchoredPosition3D Y to the given value.
|
||||
/// Also stores the RectTransform as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector3, Vector3, VectorOptions> DOAnchorPos3DY(this RectTransform target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector3, Vector3, VectorOptions> t = DOTween.To(() => target.anchoredPosition3D, x => target.anchoredPosition3D = x, new Vector3(0, endValue, 0), duration);
|
||||
t.SetOptions(AxisConstraint.Y, snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
/// <summary>Tweens a RectTransform's anchoredPosition3D Z to the given value.
|
||||
/// Also stores the RectTransform as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector3, Vector3, VectorOptions> DOAnchorPos3DZ(this RectTransform target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector3, Vector3, VectorOptions> t = DOTween.To(() => target.anchoredPosition3D, x => target.anchoredPosition3D = x, new Vector3(0, 0, endValue), duration);
|
||||
t.SetOptions(AxisConstraint.Z, snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a RectTransform's anchorMax to the given value.
|
||||
/// Also stores the RectTransform as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOAnchorMax(this RectTransform target, Vector2 endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t = DOTween.To(() => target.anchorMax, x => target.anchorMax = x, endValue, duration);
|
||||
t.SetOptions(snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a RectTransform's anchorMin to the given value.
|
||||
/// Also stores the RectTransform as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOAnchorMin(this RectTransform target, Vector2 endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t = DOTween.To(() => target.anchorMin, x => target.anchorMin = x, endValue, duration);
|
||||
t.SetOptions(snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a RectTransform's pivot to the given value.
|
||||
/// Also stores the RectTransform as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOPivot(this RectTransform target, Vector2 endValue, float duration)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t = DOTween.To(() => target.pivot, x => target.pivot = x, endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
/// <summary>Tweens a RectTransform's pivot X to the given value.
|
||||
/// Also stores the RectTransform as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOPivotX(this RectTransform target, float endValue, float duration)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t = DOTween.To(() => target.pivot, x => target.pivot = x, new Vector2(endValue, 0), duration);
|
||||
t.SetOptions(AxisConstraint.X).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
/// <summary>Tweens a RectTransform's pivot Y to the given value.
|
||||
/// Also stores the RectTransform as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOPivotY(this RectTransform target, float endValue, float duration)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t = DOTween.To(() => target.pivot, x => target.pivot = x, new Vector2(0, endValue), duration);
|
||||
t.SetOptions(AxisConstraint.Y).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a RectTransform's sizeDelta to the given value.
|
||||
/// Also stores the RectTransform as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOSizeDelta(this RectTransform target, Vector2 endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t = DOTween.To(() => target.sizeDelta, x => target.sizeDelta = x, endValue, duration);
|
||||
t.SetOptions(snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Punches a RectTransform's anchoredPosition towards the given direction and then back to the starting one
|
||||
/// as if it was connected to the starting position via an elastic.
|
||||
/// Also stores the RectTransform as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="punch">The direction and strength of the punch (added to the RectTransform's current position)</param>
|
||||
/// <param name="duration">The duration of the tween</param>
|
||||
/// <param name="vibrato">Indicates how much will the punch vibrate</param>
|
||||
/// <param name="elasticity">Represents how much (0 to 1) the vector will go beyond the starting position when bouncing backwards.
|
||||
/// 1 creates a full oscillation between the punch direction and the opposite direction,
|
||||
/// while 0 oscillates only between the punch and the start position</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static Tweener DOPunchAnchorPos(this RectTransform target, Vector2 punch, float duration, int vibrato = 10, float elasticity = 1, bool snapping = false)
|
||||
{
|
||||
return DOTween.Punch(() => target.anchoredPosition, x => target.anchoredPosition = x, punch, duration, vibrato, elasticity)
|
||||
.SetTarget(target).SetOptions(snapping);
|
||||
}
|
||||
|
||||
/// <summary>Shakes a RectTransform's anchoredPosition with the given values.
|
||||
/// Also stores the RectTransform as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="duration">The duration of the tween</param>
|
||||
/// <param name="strength">The shake strength</param>
|
||||
/// <param name="vibrato">Indicates how much will the shake vibrate</param>
|
||||
/// <param name="randomness">Indicates how much the shake will be random (0 to 180 - values higher than 90 kind of suck, so beware).
|
||||
/// Setting it to 0 will shake along a single direction.</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
/// <param name="fadeOut">If TRUE the shake will automatically fadeOut smoothly within the tween's duration, otherwise it will not</param>
|
||||
/// <param name="randomnessMode">Randomness mode</param>
|
||||
public static Tweener DOShakeAnchorPos(this RectTransform target, float duration, float strength = 100, int vibrato = 10, float randomness = 90, bool snapping = false, bool fadeOut = true, ShakeRandomnessMode randomnessMode = ShakeRandomnessMode.Full)
|
||||
{
|
||||
return DOTween.Shake(() => target.anchoredPosition, x => target.anchoredPosition = x, duration, strength, vibrato, randomness, true, fadeOut, randomnessMode)
|
||||
.SetTarget(target).SetSpecialStartupMode(SpecialStartupMode.SetShake).SetOptions(snapping);
|
||||
}
|
||||
/// <summary>Shakes a RectTransform's anchoredPosition with the given values.
|
||||
/// Also stores the RectTransform as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="duration">The duration of the tween</param>
|
||||
/// <param name="strength">The shake strength on each axis</param>
|
||||
/// <param name="vibrato">Indicates how much will the shake vibrate</param>
|
||||
/// <param name="randomness">Indicates how much the shake will be random (0 to 180 - values higher than 90 kind of suck, so beware).
|
||||
/// Setting it to 0 will shake along a single direction.</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
/// <param name="fadeOut">If TRUE the shake will automatically fadeOut smoothly within the tween's duration, otherwise it will not</param>
|
||||
/// <param name="randomnessMode">Randomness mode</param>
|
||||
public static Tweener DOShakeAnchorPos(this RectTransform target, float duration, Vector2 strength, int vibrato = 10, float randomness = 90, bool snapping = false, bool fadeOut = true, ShakeRandomnessMode randomnessMode = ShakeRandomnessMode.Full)
|
||||
{
|
||||
return DOTween.Shake(() => target.anchoredPosition, x => target.anchoredPosition = x, duration, strength, vibrato, randomness, fadeOut, randomnessMode)
|
||||
.SetTarget(target).SetSpecialStartupMode(SpecialStartupMode.SetShake).SetOptions(snapping);
|
||||
}
|
||||
|
||||
#region Special
|
||||
|
||||
/// <summary>Tweens a RectTransform's anchoredPosition to the given value, while also applying a jump effect along the Y axis.
|
||||
/// Returns a Sequence instead of a Tweener.
|
||||
/// Also stores the RectTransform as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param>
|
||||
/// <param name="jumpPower">Power of the jump (the max height of the jump is represented by this plus the final Y offset)</param>
|
||||
/// <param name="numJumps">Total number of jumps</param>
|
||||
/// <param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static Sequence DOJumpAnchorPos(this RectTransform target, Vector2 endValue, float jumpPower, int numJumps, float duration, bool snapping = false)
|
||||
{
|
||||
if (numJumps < 1) numJumps = 1;
|
||||
float startPosY = 0;
|
||||
float offsetY = -1;
|
||||
bool offsetYSet = false;
|
||||
|
||||
// Separate Y Tween so we can elaborate elapsedPercentage on that insted of on the Sequence
|
||||
// (in case users add a delay or other elements to the Sequence)
|
||||
Sequence s = DOTween.Sequence();
|
||||
Tween yTween = DOTween.To(() => target.anchoredPosition, x => target.anchoredPosition = x, new Vector2(0, jumpPower), duration / (numJumps * 2))
|
||||
.SetOptions(AxisConstraint.Y, snapping).SetEase(Ease.OutQuad).SetRelative()
|
||||
.SetLoops(numJumps * 2, LoopType.Yoyo)
|
||||
.OnStart(()=> startPosY = target.anchoredPosition.y);
|
||||
s.Append(DOTween.To(() => target.anchoredPosition, x => target.anchoredPosition = x, new Vector2(endValue.x, 0), duration)
|
||||
.SetOptions(AxisConstraint.X, snapping).SetEase(Ease.Linear)
|
||||
).Join(yTween)
|
||||
.SetTarget(target).SetEase(DOTween.defaultEaseType);
|
||||
s.OnUpdate(() => {
|
||||
if (!offsetYSet) {
|
||||
offsetYSet = true;
|
||||
offsetY = s.isRelative ? endValue.y : endValue.y - startPosY;
|
||||
}
|
||||
Vector2 pos = target.anchoredPosition;
|
||||
pos.y += DOVirtual.EasedValue(0, offsetY, s.ElapsedDirectionalPercentage(), Ease.OutQuad);
|
||||
target.anchoredPosition = pos;
|
||||
});
|
||||
return s;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScrollRect
|
||||
|
||||
/// <summary>Tweens a ScrollRect's horizontal/verticalNormalizedPosition to the given value.
|
||||
/// Also stores the ScrollRect as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static Tweener DONormalizedPos(this ScrollRect target, Vector2 endValue, float duration, bool snapping = false)
|
||||
{
|
||||
return DOTween.To(() => new Vector2(target.horizontalNormalizedPosition, target.verticalNormalizedPosition),
|
||||
x => {
|
||||
target.horizontalNormalizedPosition = x.x;
|
||||
target.verticalNormalizedPosition = x.y;
|
||||
}, endValue, duration)
|
||||
.SetOptions(snapping).SetTarget(target);
|
||||
}
|
||||
/// <summary>Tweens a ScrollRect's horizontalNormalizedPosition to the given value.
|
||||
/// Also stores the ScrollRect as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static Tweener DOHorizontalNormalizedPos(this ScrollRect target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
return DOTween.To(() => target.horizontalNormalizedPosition, x => target.horizontalNormalizedPosition = x, endValue, duration)
|
||||
.SetOptions(snapping).SetTarget(target);
|
||||
}
|
||||
/// <summary>Tweens a ScrollRect's verticalNormalizedPosition to the given value.
|
||||
/// Also stores the ScrollRect as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static Tweener DOVerticalNormalizedPos(this ScrollRect target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
return DOTween.To(() => target.verticalNormalizedPosition, x => target.verticalNormalizedPosition = x, endValue, duration)
|
||||
.SetOptions(snapping).SetTarget(target);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Slider
|
||||
|
||||
/// <summary>Tweens a Slider's value to the given value.
|
||||
/// Also stores the Slider as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<float, float, FloatOptions> DOValue(this Slider target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<float, float, FloatOptions> t = DOTween.To(() => target.value, x => target.value = x, endValue, duration);
|
||||
t.SetOptions(snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Text
|
||||
|
||||
/// <summary>Tweens a Text's color to the given value.
|
||||
/// Also stores the Text as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<Color, Color, ColorOptions> DOColor(this Text target, Color endValue, float duration)
|
||||
{
|
||||
TweenerCore<Color, Color, ColorOptions> t = DOTween.To(() => target.color, x => target.color = x, endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tweens a Text's text from one integer to another, with options for thousands separators
|
||||
/// </summary>
|
||||
/// <param name="fromValue">The value to start from</param>
|
||||
/// <param name="endValue">The end value to reach</param>
|
||||
/// <param name="duration">The duration of the tween</param>
|
||||
/// <param name="addThousandsSeparator">If TRUE (default) also adds thousands separators</param>
|
||||
/// <param name="culture">The <see cref="CultureInfo"/> to use (InvariantCulture if NULL)</param>
|
||||
public static TweenerCore<int, int, NoOptions> DOCounter(
|
||||
this Text target, int fromValue, int endValue, float duration, bool addThousandsSeparator = true, CultureInfo culture = null
|
||||
){
|
||||
int v = fromValue;
|
||||
CultureInfo cInfo = !addThousandsSeparator ? null : culture ?? CultureInfo.InvariantCulture;
|
||||
TweenerCore<int, int, NoOptions> t = DOTween.To(() => v, x => {
|
||||
v = x;
|
||||
target.text = addThousandsSeparator
|
||||
? v.ToString("N0", cInfo)
|
||||
: v.ToString();
|
||||
}, endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a Text's alpha color to the given value.
|
||||
/// Also stores the Text as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<Color, Color, ColorOptions> DOFade(this Text target, float endValue, float duration)
|
||||
{
|
||||
TweenerCore<Color, Color, ColorOptions> t = DOTween.ToAlpha(() => target.color, x => target.color = x, endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a Text's text to the given value.
|
||||
/// Also stores the Text as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end string to tween to</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="richTextEnabled">If TRUE (default), rich text will be interpreted correctly while animated,
|
||||
/// otherwise all tags will be considered as normal text</param>
|
||||
/// <param name="scrambleMode">The type of scramble mode to use, if any</param>
|
||||
/// <param name="scrambleChars">A string containing the characters to use for scrambling.
|
||||
/// Use as many characters as possible (minimum 10) because DOTween uses a fast scramble mode which gives better results with more characters.
|
||||
/// Leave it to NULL (default) to use default ones</param>
|
||||
public static TweenerCore<string, string, StringOptions> DOText(this Text target, string endValue, float duration, bool richTextEnabled = true, ScrambleMode scrambleMode = ScrambleMode.None, string scrambleChars = null)
|
||||
{
|
||||
if (endValue == null) {
|
||||
if (Debugger.logPriority > 0) Debugger.LogWarning("You can't pass a NULL string to DOText: an empty string will be used instead to avoid errors");
|
||||
endValue = "";
|
||||
}
|
||||
TweenerCore<string, string, StringOptions> t = DOTween.To(() => target.text, x => target.text = x, endValue, duration);
|
||||
t.SetOptions(richTextEnabled, scrambleMode, scrambleChars)
|
||||
.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Blendables
|
||||
|
||||
#region Graphic
|
||||
|
||||
/// <summary>Tweens a Graphic's color to the given value,
|
||||
/// in a way that allows other DOBlendableColor tweens to work together on the same target,
|
||||
/// instead than fight each other as multiple DOColor would do.
|
||||
/// Also stores the Graphic as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The value to tween to</param><param name="duration">The duration of the tween</param>
|
||||
public static Tweener DOBlendableColor(this Graphic target, Color endValue, float duration)
|
||||
{
|
||||
endValue = endValue - target.color;
|
||||
Color to = new Color(0, 0, 0, 0);
|
||||
return DOTween.To(() => to, x => {
|
||||
Color diff = x - to;
|
||||
to = x;
|
||||
target.color += diff;
|
||||
}, endValue, duration)
|
||||
.Blendable().SetTarget(target);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Image
|
||||
|
||||
/// <summary>Tweens a Image's color to the given value,
|
||||
/// in a way that allows other DOBlendableColor tweens to work together on the same target,
|
||||
/// instead than fight each other as multiple DOColor would do.
|
||||
/// Also stores the Image as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The value to tween to</param><param name="duration">The duration of the tween</param>
|
||||
public static Tweener DOBlendableColor(this Image target, Color endValue, float duration)
|
||||
{
|
||||
endValue = endValue - target.color;
|
||||
Color to = new Color(0, 0, 0, 0);
|
||||
return DOTween.To(() => to, x => {
|
||||
Color diff = x - to;
|
||||
to = x;
|
||||
target.color += diff;
|
||||
}, endValue, duration)
|
||||
.Blendable().SetTarget(target);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Text
|
||||
|
||||
/// <summary>Tweens a Text's color BY the given value,
|
||||
/// in a way that allows other DOBlendableColor tweens to work together on the same target,
|
||||
/// instead than fight each other as multiple DOColor would do.
|
||||
/// Also stores the Text as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The value to tween to</param><param name="duration">The duration of the tween</param>
|
||||
public static Tweener DOBlendableColor(this Text target, Color endValue, float duration)
|
||||
{
|
||||
endValue = endValue - target.color;
|
||||
Color to = new Color(0, 0, 0, 0);
|
||||
return DOTween.To(() => to, x => {
|
||||
Color diff = x - to;
|
||||
to = x;
|
||||
target.color += diff;
|
||||
}, endValue, duration)
|
||||
.Blendable().SetTarget(target);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region Shapes
|
||||
|
||||
/// <summary>Tweens a RectTransform's anchoredPosition so that it draws a circle around the given center.
|
||||
/// Also stores the RectTransform as the tween's target so it can be used for filtered operations.<para/>
|
||||
/// IMPORTANT: SetFrom(value) requires a <see cref="Vector2"/> instead of a float, where the X property represents the "from degrees value"</summary>
|
||||
/// <param name="center">Circle-center/pivot around which to rotate (in UI anchoredPosition coordinates)</param>
|
||||
/// <param name="endValueDegrees">The end value degrees to reach (to rotate counter-clockwise pass a negative value)</param>
|
||||
/// <param name="duration">The duration of the tween</param>
|
||||
/// <param name="relativeCenter">If TRUE the <see cref="center"/> coordinates will be considered as relative to the target's current anchoredPosition</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector2, Vector2, CircleOptions> DOShapeCircle(
|
||||
this RectTransform target, Vector2 center, float endValueDegrees, float duration, bool relativeCenter = false, bool snapping = false
|
||||
)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, CircleOptions> t = DOTween.To(
|
||||
CirclePlugin.Get(), () => target.anchoredPosition, x => target.anchoredPosition = x, center, duration
|
||||
);
|
||||
t.SetOptions(endValueDegrees, relativeCenter, snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
// █████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
|
||||
// ███ INTERNAL CLASSES ████████████████████████████████████████████████████████████████████████████████████████████████
|
||||
// █████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
|
||||
|
||||
public static class Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts the anchoredPosition of the first RectTransform to the second RectTransform,
|
||||
/// taking into consideration offset, anchors and pivot, and returns the new anchoredPosition
|
||||
/// </summary>
|
||||
public static Vector2 SwitchToRectTransform(RectTransform from, RectTransform to)
|
||||
{
|
||||
Vector2 localPoint;
|
||||
Vector2 fromPivotDerivedOffset = new Vector2(from.rect.width * 0.5f + from.rect.xMin, from.rect.height * 0.5f + from.rect.yMin);
|
||||
Vector2 screenP = RectTransformUtility.WorldToScreenPoint(null, from.position);
|
||||
screenP += fromPivotDerivedOffset;
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(to, screenP, null, out localPoint);
|
||||
Vector2 pivotDerivedOffset = new Vector2(to.rect.width * 0.5f + to.rect.xMin, to.rect.height * 0.5f + to.rect.yMin);
|
||||
return to.anchoredPosition + localPoint - pivotDerivedOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a060394c03331a64392db53a10e7f2d1
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,161 @@
|
||||
// Author: Daniele Giardini - http://www.demigiant.com
|
||||
// Created: 2026/01/08
|
||||
|
||||
#if DOTWEEN_UITOOLKIT && UNITY_2021_3_OR_NEWER // MODULE_MARKER
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using DG.Tweening.Core;
|
||||
using DG.Tweening.Core.Enums;
|
||||
using DG.Tweening.Plugins.Options;
|
||||
|
||||
#pragma warning disable 1591
|
||||
namespace DG.Tweening
|
||||
{
|
||||
public static class DOTweenModuleUIToolkit
|
||||
{
|
||||
#region Shortcuts
|
||||
|
||||
#region VisualElement
|
||||
|
||||
/// <summary>Tweens a VisualElement's position (via style.translate) to the given value.
|
||||
/// Also stores the VisualElement as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector3, Vector3, VectorOptions> DOMove(this VisualElement target, Vector3 endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector3, Vector3, VectorOptions> t
|
||||
= DOTween.To(() => target.resolvedStyle.translate, x => target.style.translate = new Translate(x.x, x.y, x.z), endValue, duration);
|
||||
t.SetOptions(snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
/// <summary>Tweens a VisualElement's position (via style.translate) to the given value.
|
||||
/// Also stores the VisualElement as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOMove(this VisualElement target, Vector2 endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t
|
||||
= DOTween.To(() => (Vector2)target.resolvedStyle.translate, x => target.style.translate = new Translate(x.x, x.y, 0), endValue, duration);
|
||||
t.SetOptions(snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
/// <summary>Tweens a VisualElement's X position (via style.translate) to the given value.
|
||||
/// Also stores the VisualElement as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector3, Vector3, VectorOptions> DOMoveX(this VisualElement target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector3, Vector3, VectorOptions> t
|
||||
= DOTween.To(() => target.resolvedStyle.translate, x => target.style.translate = new Translate(x.x, x.y, x.z), new Vector3(endValue, 0, 0), duration);
|
||||
t.SetOptions(AxisConstraint.X, snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
/// <summary>Tweens a VisualElement's Y position (via style.translate) to the given value.
|
||||
/// Also stores the VisualElement as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector3, Vector3, VectorOptions> DOMoveY(this VisualElement target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector3, Vector3, VectorOptions> t
|
||||
= DOTween.To(() => target.resolvedStyle.translate, x => target.style.translate = new Translate(x.x, x.y, x.z), new Vector3(0, endValue, 0), duration);
|
||||
t.SetOptions(AxisConstraint.Y, snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
/// <summary>Tweens a VisualElement's Z position (via style.translate) to the given value.
|
||||
/// Also stores the VisualElement as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static TweenerCore<Vector3, Vector3, VectorOptions> DOMoveZ(this VisualElement target, float endValue, float duration, bool snapping = false)
|
||||
{
|
||||
TweenerCore<Vector3, Vector3, VectorOptions> t
|
||||
= DOTween.To(() => target.resolvedStyle.translate, x => target.style.translate = new Translate(x.x, x.y, x.z), new Vector3(0, 0, endValue), duration);
|
||||
t.SetOptions(AxisConstraint.Z, snapping).SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a VisualElement's scale (via style.scale) to the given value.
|
||||
/// Also stores the VisualElement as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOScale(this VisualElement target, Vector2 endValue, float duration)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t
|
||||
= DOTween.To(() => (Vector2)target.resolvedStyle.scale.value, x => target.style.scale = new Scale(x), endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
/// <summary>Tweens a VisualElement's scale (via style.scale) to the given value.
|
||||
/// Also stores the VisualElement as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOScale(this VisualElement target, float endValue, float duration)
|
||||
{
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t
|
||||
= DOTween.To(() => (Vector2)target.resolvedStyle.scale.value, x => target.style.scale = new Scale(x), new Vector2(endValue, endValue), duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a VisualElement's rotation (via style.rotate) to the given value.
|
||||
/// Also stores the VisualElement as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param><param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<float, float, FloatOptions> DORotate(this VisualElement target, float endValue, float duration)
|
||||
{
|
||||
TweenerCore<float, float, FloatOptions> t
|
||||
= DOTween.To(() => target.resolvedStyle.rotate.angle.value, x => target.style.rotate = new Rotate(x), endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Punches a VisualElement's position towards the given direction and then back to the starting one
|
||||
/// as if it was connected to the starting position via an elastic.
|
||||
/// Also stores the VisualElement as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="punch">The direction and strength of the punch (added to the VisualElement's current position)</param>
|
||||
/// <param name="duration">The duration of the tween</param>
|
||||
/// <param name="vibrato">Indicates how much will the punch vibrate</param>
|
||||
/// <param name="elasticity">Represents how much (0 to 1) the vector will go beyond the starting position when bouncing backwards.
|
||||
/// 1 creates a full oscillation between the punch direction and the opposite direction,
|
||||
/// while 0 oscillates only between the punch and the start position</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
public static Tweener DOPunch(this VisualElement target, Vector3 punch, float duration, int vibrato = 10, float elasticity = 1, bool snapping = false)
|
||||
{
|
||||
return DOTween.Punch(() => target.resolvedStyle.translate, x => target.style.translate = new Translate(x.x, x.y, x.z), punch, duration, vibrato, elasticity)
|
||||
.SetTarget(target).SetOptions(snapping);
|
||||
}
|
||||
|
||||
/// <summary>Shakes a VisualElement's position with the given values.
|
||||
/// Also stores the VisualElement as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="duration">The duration of the tween</param>
|
||||
/// <param name="strength">The shake strength</param>
|
||||
/// <param name="vibrato">Indicates how much will the shake vibrate</param>
|
||||
/// <param name="randomness">Indicates how much the shake will be random (0 to 180 - values higher than 90 kind of suck, so beware).
|
||||
/// Setting it to 0 will shake along a single direction.</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
/// <param name="fadeOut">If TRUE the shake will automatically fadeOut smoothly within the tween's duration, otherwise it will not</param>
|
||||
/// <param name="randomnessMode">Randomness mode</param>
|
||||
public static Tweener DOShake(this VisualElement target, float duration, float strength = 100, int vibrato = 10, float randomness = 90, bool snapping = false, bool fadeOut = true, ShakeRandomnessMode randomnessMode = ShakeRandomnessMode.Full)
|
||||
{
|
||||
return DOTween.Shake(() => target.resolvedStyle.translate, x => target.style.translate = new Translate(x.x, x.y, x.z), duration, strength, vibrato, randomness, true, fadeOut, randomnessMode)
|
||||
.SetTarget(target).SetSpecialStartupMode(SpecialStartupMode.SetShake).SetOptions(snapping);
|
||||
}
|
||||
/// <summary>Shakes a VisualElement's position with the given values.
|
||||
/// Also stores the VisualElement as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="duration">The duration of the tween</param>
|
||||
/// <param name="strength">The shake strength on each axis</param>
|
||||
/// <param name="vibrato">Indicates how much will the shake vibrate</param>
|
||||
/// <param name="randomness">Indicates how much the shake will be random (0 to 180 - values higher than 90 kind of suck, so beware).
|
||||
/// Setting it to 0 will shake along a single direction.</param>
|
||||
/// <param name="snapping">If TRUE the tween will smoothly snap all values to integers</param>
|
||||
/// <param name="fadeOut">If TRUE the shake will automatically fadeOut smoothly within the tween's duration, otherwise it will not</param>
|
||||
/// <param name="randomnessMode">Randomness mode</param>
|
||||
public static Tweener DOShake(this VisualElement target, float duration, Vector2 strength, int vibrato = 10, float randomness = 90, bool snapping = false, bool fadeOut = true, ShakeRandomnessMode randomnessMode = ShakeRandomnessMode.Full)
|
||||
{
|
||||
return DOTween.Shake(() => target.resolvedStyle.translate, x => target.style.translate = new Translate(x.x, x.y, x.z), duration, strength, vibrato, randomness, fadeOut, randomnessMode)
|
||||
.SetTarget(target).SetSpecialStartupMode(SpecialStartupMode.SetShake).SetOptions(snapping);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c0a6d2683fb84e4193f2aa968264881
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,389 @@
|
||||
// Author: Daniele Giardini - http://www.demigiant.com
|
||||
// Created: 2018/07/13
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using DG.Tweening.Core;
|
||||
using DG.Tweening.Plugins.Options;
|
||||
//#if UNITY_2018_1_OR_NEWER && (NET_4_6 || NET_STANDARD_2_0)
|
||||
//using Task = System.Threading.Tasks.Task;
|
||||
//#endif
|
||||
|
||||
#pragma warning disable 1591
|
||||
namespace DG.Tweening
|
||||
{
|
||||
/// <summary>
|
||||
/// Shortcuts/functions that are not strictly related to specific Modules
|
||||
/// but are available only on some Unity versions
|
||||
/// </summary>
|
||||
public static class DOTweenModuleUnityVersion
|
||||
{
|
||||
#region Material
|
||||
|
||||
/// <summary>Tweens a Material's color using the given gradient
|
||||
/// (NOTE 1: only uses the colors of the gradient, not the alphas - NOTE 2: creates a Sequence, not a Tweener).
|
||||
/// Also stores the image as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="gradient">The gradient to use</param><param name="duration">The duration of the tween</param>
|
||||
public static Sequence DOGradientColor(this Material target, Gradient gradient, float duration)
|
||||
{
|
||||
Sequence s = DOTween.Sequence();
|
||||
GradientColorKey[] colors = gradient.colorKeys;
|
||||
int len = colors.Length;
|
||||
for (int i = 0; i < len; ++i) {
|
||||
GradientColorKey c = colors[i];
|
||||
if (i == 0 && c.time <= 0) {
|
||||
target.color = c.color;
|
||||
continue;
|
||||
}
|
||||
float colorDuration = i == len - 1
|
||||
? duration - s.Duration(false) // Verifies that total duration is correct
|
||||
: duration * (i == 0 ? c.time : c.time - colors[i - 1].time);
|
||||
s.Append(target.DOColor(c.color, colorDuration).SetEase(Ease.Linear));
|
||||
}
|
||||
s.SetTarget(target);
|
||||
return s;
|
||||
}
|
||||
/// <summary>Tweens a Material's named color property using the given gradient
|
||||
/// (NOTE 1: only uses the colors of the gradient, not the alphas - NOTE 2: creates a Sequence, not a Tweener).
|
||||
/// Also stores the image as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="gradient">The gradient to use</param>
|
||||
/// <param name="property">The name of the material property to tween (like _Tint or _SpecColor)</param>
|
||||
/// <param name="duration">The duration of the tween</param>
|
||||
public static Sequence DOGradientColor(this Material target, Gradient gradient, string property, float duration)
|
||||
{
|
||||
Sequence s = DOTween.Sequence();
|
||||
GradientColorKey[] colors = gradient.colorKeys;
|
||||
int len = colors.Length;
|
||||
for (int i = 0; i < len; ++i) {
|
||||
GradientColorKey c = colors[i];
|
||||
if (i == 0 && c.time <= 0) {
|
||||
target.SetColor(property, c.color);
|
||||
continue;
|
||||
}
|
||||
float colorDuration = i == len - 1
|
||||
? duration - s.Duration(false) // Verifies that total duration is correct
|
||||
: duration * (i == 0 ? c.time : c.time - colors[i - 1].time);
|
||||
s.Append(target.DOColor(c.color, property, colorDuration).SetEase(Ease.Linear));
|
||||
}
|
||||
s.SetTarget(target);
|
||||
return s;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region CustomYieldInstructions
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="CustomYieldInstruction"/> that waits until the tween is killed or complete.
|
||||
/// It can be used inside a coroutine as a yield.
|
||||
/// <para>Example usage:</para><code>yield return myTween.WaitForCompletion(true);</code>
|
||||
/// </summary>
|
||||
public static CustomYieldInstruction WaitForCompletion(this Tween t, bool returnCustomYieldInstruction)
|
||||
{
|
||||
if (!t.active) {
|
||||
if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t);
|
||||
return null;
|
||||
}
|
||||
return new DOTweenCYInstruction.WaitForCompletion(t);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="CustomYieldInstruction"/> that waits until the tween is killed or rewinded.
|
||||
/// It can be used inside a coroutine as a yield.
|
||||
/// <para>Example usage:</para><code>yield return myTween.WaitForRewind();</code>
|
||||
/// </summary>
|
||||
public static CustomYieldInstruction WaitForRewind(this Tween t, bool returnCustomYieldInstruction)
|
||||
{
|
||||
if (!t.active) {
|
||||
if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t);
|
||||
return null;
|
||||
}
|
||||
return new DOTweenCYInstruction.WaitForRewind(t);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="CustomYieldInstruction"/> that waits until the tween is killed.
|
||||
/// It can be used inside a coroutine as a yield.
|
||||
/// <para>Example usage:</para><code>yield return myTween.WaitForKill();</code>
|
||||
/// </summary>
|
||||
public static CustomYieldInstruction WaitForKill(this Tween t, bool returnCustomYieldInstruction)
|
||||
{
|
||||
if (!t.active) {
|
||||
if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t);
|
||||
return null;
|
||||
}
|
||||
return new DOTweenCYInstruction.WaitForKill(t);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="CustomYieldInstruction"/> that waits until the tween is killed or has gone through the given amount of loops.
|
||||
/// It can be used inside a coroutine as a yield.
|
||||
/// <para>Example usage:</para><code>yield return myTween.WaitForElapsedLoops(2);</code>
|
||||
/// </summary>
|
||||
/// <param name="elapsedLoops">Elapsed loops to wait for</param>
|
||||
public static CustomYieldInstruction WaitForElapsedLoops(this Tween t, int elapsedLoops, bool returnCustomYieldInstruction)
|
||||
{
|
||||
if (!t.active) {
|
||||
if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t);
|
||||
return null;
|
||||
}
|
||||
return new DOTweenCYInstruction.WaitForElapsedLoops(t, elapsedLoops);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="CustomYieldInstruction"/> that waits until the tween is killed
|
||||
/// or has reached the given time position (loops included, delays excluded).
|
||||
/// It can be used inside a coroutine as a yield.
|
||||
/// <para>Example usage:</para><code>yield return myTween.WaitForPosition(2.5f);</code>
|
||||
/// </summary>
|
||||
/// <param name="position">Position (loops included, delays excluded) to wait for</param>
|
||||
public static CustomYieldInstruction WaitForPosition(this Tween t, float position, bool returnCustomYieldInstruction)
|
||||
{
|
||||
if (!t.active) {
|
||||
if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t);
|
||||
return null;
|
||||
}
|
||||
return new DOTweenCYInstruction.WaitForPosition(t, position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="CustomYieldInstruction"/> that waits until the tween is killed or started
|
||||
/// (meaning when the tween is set in a playing state the first time, after any eventual delay).
|
||||
/// It can be used inside a coroutine as a yield.
|
||||
/// <para>Example usage:</para><code>yield return myTween.WaitForStart();</code>
|
||||
/// </summary>
|
||||
public static CustomYieldInstruction WaitForStart(this Tween t, bool returnCustomYieldInstruction)
|
||||
{
|
||||
if (!t.active) {
|
||||
if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t);
|
||||
return null;
|
||||
}
|
||||
return new DOTweenCYInstruction.WaitForStart(t);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
#region Unity 2018.1 or Newer
|
||||
|
||||
#region Material
|
||||
|
||||
/// <summary>Tweens a Material's named texture offset property with the given ID to the given value.
|
||||
/// Also stores the material as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param>
|
||||
/// <param name="propertyID">The ID of the material property to tween (also called nameID in Unity's manual)</param>
|
||||
/// <param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOOffset(this Material target, Vector2 endValue, int propertyID, float duration)
|
||||
{
|
||||
if (!target.HasProperty(propertyID)) {
|
||||
if (Debugger.logPriority > 0) Debugger.LogMissingMaterialProperty(propertyID);
|
||||
return null;
|
||||
}
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t = DOTween.To(() => target.GetTextureOffset(propertyID), x => target.SetTextureOffset(propertyID, x), endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>Tweens a Material's named texture scale property with the given ID to the given value.
|
||||
/// Also stores the material as the tween's target so it can be used for filtered operations</summary>
|
||||
/// <param name="endValue">The end value to reach</param>
|
||||
/// <param name="propertyID">The ID of the material property to tween (also called nameID in Unity's manual)</param>
|
||||
/// <param name="duration">The duration of the tween</param>
|
||||
public static TweenerCore<Vector2, Vector2, VectorOptions> DOTiling(this Material target, Vector2 endValue, int propertyID, float duration)
|
||||
{
|
||||
if (!target.HasProperty(propertyID)) {
|
||||
if (Debugger.logPriority > 0) Debugger.LogMissingMaterialProperty(propertyID);
|
||||
return null;
|
||||
}
|
||||
TweenerCore<Vector2, Vector2, VectorOptions> t = DOTween.To(() => target.GetTextureScale(propertyID), x => target.SetTextureScale(propertyID, x), endValue, duration);
|
||||
t.SetTarget(target);
|
||||
return t;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region .NET 4.6 or Newer
|
||||
|
||||
#if UNITY_2018_1_OR_NEWER && (NET_4_6 || NET_STANDARD_2_0)
|
||||
|
||||
#region Async Instructions
|
||||
|
||||
/// <summary>
|
||||
/// Returns an async <see cref="System.Threading.Tasks.Task"/> that waits until the tween is killed or complete.
|
||||
/// It can be used inside an async operation.
|
||||
/// <para>Example usage:</para><code>await myTween.WaitForCompletion();</code>
|
||||
/// </summary>
|
||||
public static async System.Threading.Tasks.Task AsyncWaitForCompletion(this Tween t)
|
||||
{
|
||||
if (!t.active) {
|
||||
if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t);
|
||||
return;
|
||||
}
|
||||
while (t.active && !t.IsComplete()) await System.Threading.Tasks.Task.Yield();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an async <see cref="System.Threading.Tasks.Task"/> that waits until the tween is killed or rewinded.
|
||||
/// It can be used inside an async operation.
|
||||
/// <para>Example usage:</para><code>await myTween.AsyncWaitForRewind();</code>
|
||||
/// </summary>
|
||||
public static async System.Threading.Tasks.Task AsyncWaitForRewind(this Tween t)
|
||||
{
|
||||
if (!t.active) {
|
||||
if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t);
|
||||
return;
|
||||
}
|
||||
while (t.active && (!t.playedOnce || t.position * (t.CompletedLoops() + 1) > 0)) await System.Threading.Tasks.Task.Yield();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an async <see cref="System.Threading.Tasks.Task"/> that waits until the tween is killed.
|
||||
/// It can be used inside an async operation.
|
||||
/// <para>Example usage:</para><code>await myTween.AsyncWaitForKill();</code>
|
||||
/// </summary>
|
||||
public static async System.Threading.Tasks.Task AsyncWaitForKill(this Tween t)
|
||||
{
|
||||
if (!t.active) {
|
||||
if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t);
|
||||
return;
|
||||
}
|
||||
while (t.active) await System.Threading.Tasks.Task.Yield();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an async <see cref="System.Threading.Tasks.Task"/> that waits until the tween is killed or has gone through the given amount of loops.
|
||||
/// It can be used inside an async operation.
|
||||
/// <para>Example usage:</para><code>await myTween.AsyncWaitForElapsedLoops();</code>
|
||||
/// </summary>
|
||||
/// <param name="elapsedLoops">Elapsed loops to wait for</param>
|
||||
public static async System.Threading.Tasks.Task AsyncWaitForElapsedLoops(this Tween t, int elapsedLoops)
|
||||
{
|
||||
if (!t.active) {
|
||||
if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t);
|
||||
return;
|
||||
}
|
||||
while (t.active && t.CompletedLoops() < elapsedLoops) await System.Threading.Tasks.Task.Yield();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an async <see cref="System.Threading.Tasks.Task"/> that waits until the tween is killed or started
|
||||
/// (meaning when the tween is set in a playing state the first time, after any eventual delay).
|
||||
/// It can be used inside an async operation.
|
||||
/// <para>Example usage:</para><code>await myTween.AsyncWaitForPosition();</code>
|
||||
/// </summary>
|
||||
/// <param name="position">Position (loops included, delays excluded) to wait for</param>
|
||||
public static async System.Threading.Tasks.Task AsyncWaitForPosition(this Tween t, float position)
|
||||
{
|
||||
if (!t.active) {
|
||||
if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t);
|
||||
return;
|
||||
}
|
||||
while (t.active && t.position * (t.CompletedLoops() + 1) < position) await System.Threading.Tasks.Task.Yield();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an async <see cref="System.Threading.Tasks.Task"/> that waits until the tween is killed.
|
||||
/// It can be used inside an async operation.
|
||||
/// <para>Example usage:</para><code>await myTween.AsyncWaitForKill();</code>
|
||||
/// </summary>
|
||||
public static async System.Threading.Tasks.Task AsyncWaitForStart(this Tween t)
|
||||
{
|
||||
if (!t.active) {
|
||||
if (Debugger.logPriority > 0) Debugger.LogInvalidTween(t);
|
||||
return;
|
||||
}
|
||||
while (t.active && !t.playedOnce) await System.Threading.Tasks.Task.Yield();
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
#endif
|
||||
}
|
||||
|
||||
// █████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
|
||||
// ███ CLASSES █████████████████████████████████████████████████████████████████████████████████████████████████████████
|
||||
// █████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
|
||||
|
||||
public static class DOTweenCYInstruction
|
||||
{
|
||||
public class WaitForCompletion : CustomYieldInstruction
|
||||
{
|
||||
public override bool keepWaiting { get {
|
||||
return t.active && !t.IsComplete();
|
||||
}}
|
||||
readonly Tween t;
|
||||
public WaitForCompletion(Tween tween)
|
||||
{
|
||||
t = tween;
|
||||
}
|
||||
}
|
||||
|
||||
public class WaitForRewind : CustomYieldInstruction
|
||||
{
|
||||
public override bool keepWaiting { get {
|
||||
return t.active && (!t.playedOnce || t.position * (t.CompletedLoops() + 1) > 0);
|
||||
}}
|
||||
readonly Tween t;
|
||||
public WaitForRewind(Tween tween)
|
||||
{
|
||||
t = tween;
|
||||
}
|
||||
}
|
||||
|
||||
public class WaitForKill : CustomYieldInstruction
|
||||
{
|
||||
public override bool keepWaiting { get {
|
||||
return t.active;
|
||||
}}
|
||||
readonly Tween t;
|
||||
public WaitForKill(Tween tween)
|
||||
{
|
||||
t = tween;
|
||||
}
|
||||
}
|
||||
|
||||
public class WaitForElapsedLoops : CustomYieldInstruction
|
||||
{
|
||||
public override bool keepWaiting { get {
|
||||
return t.active && t.CompletedLoops() < elapsedLoops;
|
||||
}}
|
||||
readonly Tween t;
|
||||
readonly int elapsedLoops;
|
||||
public WaitForElapsedLoops(Tween tween, int elapsedLoops)
|
||||
{
|
||||
t = tween;
|
||||
this.elapsedLoops = elapsedLoops;
|
||||
}
|
||||
}
|
||||
|
||||
public class WaitForPosition : CustomYieldInstruction
|
||||
{
|
||||
public override bool keepWaiting { get {
|
||||
return t.active && t.position * (t.CompletedLoops() + 1) < position;
|
||||
}}
|
||||
readonly Tween t;
|
||||
readonly float position;
|
||||
public WaitForPosition(Tween tween, float position)
|
||||
{
|
||||
t = tween;
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
public class WaitForStart : CustomYieldInstruction
|
||||
{
|
||||
public override bool keepWaiting { get {
|
||||
return t.active && !t.playedOnce;
|
||||
}}
|
||||
readonly Tween t;
|
||||
public WaitForStart(Tween tween)
|
||||
{
|
||||
t = tween;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 63c02322328255542995bd02b47b0457
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,174 @@
|
||||
// Author: Daniele Giardini - http://www.demigiant.com
|
||||
// Created: 2018/07/13
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using DG.Tweening.Core;
|
||||
using DG.Tweening.Plugins.Core.PathCore;
|
||||
using DG.Tweening.Plugins.Options;
|
||||
|
||||
#pragma warning disable 1591
|
||||
namespace DG.Tweening
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility functions that deal with available Modules and rewrite them.
|
||||
/// Modules defines:
|
||||
/// - DOTAUDIO
|
||||
/// - DOTPHYSICS
|
||||
/// - DOTPHYSICS2D
|
||||
/// - DOTSPRITE
|
||||
/// - DOTUI
|
||||
/// Extra defines set and used for implementation of external assets:
|
||||
/// - DOTWEEN_TMP ► TextMesh Pro
|
||||
/// - DOTWEEN_TK2D ► 2D Toolkit
|
||||
/// </summary>
|
||||
public static class DOTweenModuleUtils
|
||||
{
|
||||
#pragma warning disable UDR0001
|
||||
static bool _initialized;
|
||||
#pragma warning restore UDR0001
|
||||
|
||||
#region Reflection
|
||||
|
||||
/// <summary>
|
||||
/// Called via Reflection by DOTweenComponent on Awake
|
||||
/// </summary>
|
||||
#pragma warning disable UDR0001
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
[UnityEngine.Scripting.Preserve]
|
||||
#endif
|
||||
public static void Init()
|
||||
{
|
||||
if (_initialized) return;
|
||||
|
||||
_initialized = true;
|
||||
DOTweenExternalCommand.SetOrientationOnPath -= Physics.SetOrientationOnPath;
|
||||
DOTweenExternalCommand.SetOrientationOnPath += Physics.SetOrientationOnPath;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
#if UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_5 || UNITY_2017_1
|
||||
UnityEditor.EditorApplication.playmodeStateChanged -= PlaymodeStateChanged;
|
||||
UnityEditor.EditorApplication.playmodeStateChanged += PlaymodeStateChanged;
|
||||
#else
|
||||
UnityEditor.EditorApplication.playModeStateChanged -= PlaymodeStateChanged;
|
||||
UnityEditor.EditorApplication.playModeStateChanged += PlaymodeStateChanged;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#pragma warning restore UDR0001
|
||||
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
#pragma warning disable
|
||||
[UnityEngine.Scripting.Preserve]
|
||||
// Just used to preserve methods when building, never called
|
||||
static void Preserver()
|
||||
{
|
||||
Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
MethodInfo mi = typeof(MonoBehaviour).GetMethod("Stub");
|
||||
}
|
||||
#pragma warning restore
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// Fires OnApplicationPause in DOTweenComponent even when Editor is paused (otherwise it's only fired at runtime)
|
||||
#if UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_5 || UNITY_2017_1
|
||||
static void PlaymodeStateChanged()
|
||||
#else
|
||||
static void PlaymodeStateChanged(UnityEditor.PlayModeStateChange state)
|
||||
#endif
|
||||
{
|
||||
if (DOTween.instance == null) return;
|
||||
DOTween.instance.OnApplicationPause(UnityEditor.EditorApplication.isPaused);
|
||||
}
|
||||
#endif
|
||||
|
||||
// █████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
|
||||
// ███ INTERNAL CLASSES ████████████████████████████████████████████████████████████████████████████████████████████████
|
||||
// █████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
|
||||
|
||||
public static class Physics
|
||||
{
|
||||
// Called via DOTweenExternalCommand callback
|
||||
public static void SetOrientationOnPath(PathOptions options, Tween t, Quaternion newRot, Transform trans)
|
||||
{
|
||||
#if !DOTWEEN_NOPHYSICS // PHYSICS_MARKER
|
||||
if (options.isRigidbody) ((Rigidbody)t.target).rotation = newRot;
|
||||
else trans.rotation = newRot;
|
||||
#else
|
||||
trans.rotation = newRot;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Returns FALSE if the DOTween's Physics2D Module is disabled, or if there's no Rigidbody2D attached
|
||||
public static bool HasRigidbody2D(Component target)
|
||||
{
|
||||
#if !DOTWEEN_NOPHYSICS2D // PHYSICS2D_MARKER
|
||||
return target.GetComponent<Rigidbody2D>() != null;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#region Called via Reflection
|
||||
|
||||
|
||||
// Called via Reflection by DOTweenPathInspector
|
||||
// Returns FALSE if the DOTween's Physics Module is disabled, or if there's no rigidbody attached
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
[UnityEngine.Scripting.Preserve]
|
||||
#endif
|
||||
public static bool HasRigidbody(Component target)
|
||||
{
|
||||
#if !DOTWEEN_NOPHYSICS // PHYSICS_MARKER
|
||||
return target.GetComponent<Rigidbody>() != null;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Called via Reflection by DOTweenPath
|
||||
#if UNITY_2018_1_OR_NEWER
|
||||
[UnityEngine.Scripting.Preserve]
|
||||
#endif
|
||||
public static TweenerCore<Vector3, Path, PathOptions> CreateDOTweenPathTween(
|
||||
MonoBehaviour target, bool tweenRigidbody, bool isLocal, Path path, float duration, PathMode pathMode
|
||||
){
|
||||
TweenerCore<Vector3, Path, PathOptions> t = null;
|
||||
bool rBodyFoundAndTweened = false;
|
||||
#if !DOTWEEN_NOPHYSICS // PHYSICS_MARKER
|
||||
if (tweenRigidbody) {
|
||||
Rigidbody rBody = target.GetComponent<Rigidbody>();
|
||||
if (rBody != null) {
|
||||
rBodyFoundAndTweened = true;
|
||||
t = isLocal
|
||||
? rBody.DOLocalPath(path, duration, pathMode)
|
||||
: rBody.DOPath(path, duration, pathMode);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if !DOTWEEN_NOPHYSICS2D // PHYSICS2D_MARKER
|
||||
if (!rBodyFoundAndTweened && tweenRigidbody) {
|
||||
Rigidbody2D rBody2D = target.GetComponent<Rigidbody2D>();
|
||||
if (rBody2D != null) {
|
||||
rBodyFoundAndTweened = true;
|
||||
t = isLocal
|
||||
? rBody2D.DOLocalPath(path, duration, pathMode)
|
||||
: rBody2D.DOPath(path, duration, pathMode);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!rBodyFoundAndTweened) {
|
||||
t = isLocal
|
||||
? target.transform.DOLocalPath(path, duration, pathMode)
|
||||
: target.transform.DOPath(path, duration, pathMode);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7bcaf917d9cf5b84090421a5a2abe42e
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,31 @@
|
||||
DOTween and DOTween Pro are copyright (c) 2014-2026 Daniele Giardini - Demigiant
|
||||
|
||||
|
||||
// IMPORTANT!!! /////////////////////////////////////////////
|
||||
// Upgrading DOTween from versions older than 1.2.815 ///////
|
||||
// or DOTween Pro older than 1.0.405 ////////////////////////
|
||||
// (February 6 2026) ////////////////////////////////////////
|
||||
-------------------------------------------------------------
|
||||
1) Import the new version in the same folder as the previous one (Plugins/Demigiant), overwriting old files. A lot of errors will appear but don't worry.
|
||||
2) Close and reopen Unity (and your project) without entering safe mode. This is fundamental: skipping this step will cause a bloodbath
|
||||
3) Open DOTween's Utility Panel (Tools > Demigiant > DOTween Utility Panel) if it doesn't open automatically, then press "Setup DOTween...": this will run the upgrade setup
|
||||
4) From the Add/Remove Modules panel that opens, activate/deactivate Modules for Unity systems and for external assets (Pro version only)
|
||||
|
||||
|
||||
// GET STARTED //////////////////////////////////////////////
|
||||
|
||||
- After importing a new DOTween update, select DOTween's Utility Panel from the "Tools/Demigiant" menu (if it doesn't open automatically) and press the "Setup DOTween..." button to activate/deactivate Modules. You can also access a Preferences Tab from there to choose default settings for DOTween.
|
||||
- In your code, add "using DG.Tweening" to each class where you want to use DOTween.
|
||||
- You're ready to tween. Check out the links below for full documentation and license info.
|
||||
|
||||
|
||||
// LINKS ///////////////////////////////////////////////////////
|
||||
|
||||
DOTween website (documentation, examples, etc): http://dotween.demigiant.com
|
||||
DOTween license: http://dotween.demigiant.com/license.php
|
||||
DOTween repository (GitHub): https://github.com/Demigiant/dotween
|
||||
Demigiant website: http://www.demigiant.com
|
||||
|
||||
// NOTES //////////////////////////////////////////////////////
|
||||
|
||||
- DOTween's Utility Panel can be found under "Tools > Demigiant > DOTween Utility Panel" and also contains other useful options, plus a tab to set DOTween's preferences
|
||||
@@ -0,0 +1,4 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fccfc62abf2eb0a4db614853430894fd
|
||||
TextScriptImporter:
|
||||
userData:
|
||||
@@ -677,12 +677,10 @@ namespace NAudio.Lame.DLL
|
||||
|
||||
internal static class NativeMethods
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
#if UNITY_EDITOR_OSX
|
||||
const string libname = @"__Internal";
|
||||
#else
|
||||
#if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
|
||||
const string libname = @"lame";
|
||||
#elif UNITY_EDITOR
|
||||
const string libname = @"libmp3lame.dll";
|
||||
#endif
|
||||
#elif UNITY_IOS
|
||||
const string libname = @"__Internal";
|
||||
#elif UNITY_ANDROID
|
||||
|
||||
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 57e25b4a578dba94c9353f4633b20549
|
||||
guid: ecd7dab9b385ae34c8c077615f561b9f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace SFB {
|
||||
namespace SFB.LegacyWindows {
|
||||
public interface IStandaloneFileBrowser {
|
||||
string[] OpenFilePanel(string title, string directory, ExtensionFilter[] extensions, bool multiselect);
|
||||
string[] OpenFolderPanel(string title, string directory, bool multiselect);
|
||||
@@ -11,3 +11,4 @@ namespace SFB {
|
||||
void SaveFilePanelAsync(string title, string directory, string defaultName, ExtensionFilter[] extensions, Action<string> cb);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7609f7b6787a54496aa41a3053fcc76a
|
||||
guid: 3f930ddac4d3ac04b9a3b236446ad59c
|
||||
timeCreated: 1483902788
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ddc4e7b83981f244ba9a26b88c18cb67
|
||||
guid: 3b296afa4ee50cb43be9c7e1e023027f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 110fdfb459db4fc448a2ccd37e200fa4
|
||||
guid: 99fb74d0dc9a71b4bac7e5a132fb0e5d
|
||||
folderAsset: yes
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 265aaf20a6d564e0fb00a9c4a7a9c300
|
||||
guid: 4622f638922a1e741b11260cda2fad29
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace SFB {
|
||||
namespace SFB.LegacyWindows {
|
||||
public struct ExtensionFilter {
|
||||
public string Name;
|
||||
public string[] Extensions;
|
||||
@@ -15,14 +15,13 @@ namespace SFB {
|
||||
private static IStandaloneFileBrowser _platformWrapper = null;
|
||||
|
||||
static StandaloneFileBrowser() {
|
||||
#if UNITY_STANDALONE_OSX
|
||||
_platformWrapper = new StandaloneFileBrowserMac();
|
||||
#elif UNITY_STANDALONE_WIN
|
||||
#if UNITY_STANDALONE_WIN
|
||||
_platformWrapper = new StandaloneFileBrowserWindows();
|
||||
#elif UNITY_STANDALONE_LINUX
|
||||
_platformWrapper = new StandaloneFileBrowserLinux();
|
||||
#elif UNITY_EDITOR
|
||||
_platformWrapper = new StandaloneFileBrowserEditor();
|
||||
#else
|
||||
// This historical implementation is retained only for Windows compatibility.
|
||||
throw new PlatformNotSupportedException("SFB.LegacyWindows supports only Windows players. Use SFB for cross-platform dialogs.");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -151,3 +150,4 @@ namespace SFB {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user