diff --git a/.github/workflows/development-buildandtestupmrelease.yml b/.github/workflows/development-buildandtestupmrelease.yml index a61bebac..42d05b25 100644 --- a/.github/workflows/development-buildandtestupmrelease.yml +++ b/.github/workflows/development-buildandtestupmrelease.yml @@ -3,7 +3,7 @@ name: Build and test UPM packages for platforms, all branches except main on: pull_request: branches-ignore: - - 'release' + - 'main' # Ignore PRs targeting main # Allows you to run this workflow manually from the Actions tab @@ -13,18 +13,79 @@ concurrency: group: ${{ github.ref }} cancel-in-progress: true +# Ensure default token scopes and inherit org-level secrets via env mapping +permissions: + contents: write + packages: read + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GIT_PAT: ${{ secrets.GIT_PAT }} + jobs: - # Check Unity version required by the package - # Run Unity build unit tests defined in the package for a single version for feature branches - Run-Partial-Unit-Tests: - name: Run Unity Unit Tests - if: github.ref != 'refs/heads/development' - uses: ./.github/workflows/rununitysinglebuild.yml - with: - unityversion: 2020.3 - - # Run Unity multi-version build unit tests defined in the package for the development branch - Run-Full-Unit-Tests: - name: Run Unity Unit Tests - if: github.ref == 'refs/heads/development' - uses: ./.github/workflows/rununitybuildmultiversion.yml \ No newline at end of file + test-unity-build: + name: Test Unity UPM Build (${{ matrix.os }}, Unity ${{ matrix.unity-version }}) + runs-on: ${{ matrix.os }} + if: always() + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + unity-version: + - '6000.0.x' + - '6000.1' + - '6000.2' + - '6000.3' + include: + - os: ubuntu-latest + build-targets: StandaloneLinux64, Android + - os: windows-latest + build-targets: StandaloneWindows64, Android + - os: macos-latest + build-targets: StandaloneOSX, iOS + steps: + - uses: buildalon/unity-setup@v2 + id: unity-setup + with: + version-file: "None" + unity-version: ${{ matrix.unity-version }} # overrides version in version-file + build-targets: ${{ matrix.build-targets }} + + - name: Inspect Unity setup outputs + shell: pwsh + run: | + echo "Step Outputs:" + echo "steps.unity-setup.unity-hub-path: '${{ steps.unity-setup.outputs.unity-hub-path }}'" + echo "steps.unity-setup.unity-editors: '${{ steps.unity-setup.outputs.unity-editors }}'" + echo "steps.unity-setup.unity-editor-path: '${{ steps.unity-setup.outputs.unity-editor-path }}'" + echo "steps.unity-setup.unity-project-path: '${{ steps.unity-setup.outputs.unity-project-path }}'" + + echo "Environment Variables:" + echo "UNITY_HUB_PATH: '$env:UNITY_HUB_PATH'" + echo "UNITY_EDITORS: '$env:UNITY_EDITORS'" + echo "UNITY_EDITOR_PATH: '$env:UNITY_EDITOR_PATH'" + echo "UNITY_PROJECT_PATH: '$env:UNITY_PROJECT_PATH'" + + - uses: buildalon/activate-unity-license@v2 + if: runner.environment == 'github-hosted' + with: + license: "Personal" # Choose license type to use [ Personal, Professional, Floating ] + username: ${{ secrets.UNITY_USER}} + password: ${{ secrets.UNITY_ACC}} + + - uses: buildalon/create-unity-project@v2 + id: create-unity-project + with: + project-name: P + + - name: Setup Unity UPM Build + uses: realitycollective/reality-collective-actions/setup-unity-upm-build@v1 + with: + unity-project-path: ${{ steps.create-unity-project.outputs.project-path }} + + - name: Run Unity Build + uses: realitycollective/reality-collective-actions/run-unity-multitarget-build@v1 + with: + unity-editor-path: ${{ steps.unity-setup.outputs.unity-editor-path }} + unity-project-path: ${{ steps.create-unity-project.outputs.project-path }} + build-targets: ${{ matrix.build-targets }} \ No newline at end of file diff --git a/.github/workflows/development-publish.yml b/.github/workflows/development-publish.yml deleted file mode 100644 index a770e9d8..00000000 --- a/.github/workflows/development-publish.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Publish development branch on Merge - -on: - pull_request: - types: - - closed - branches: - - development - # On close of PR targeting development - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -concurrency: - group: ${{ github.ref }} - cancel-in-progress: true - -jobs: - release_on_merge: - if: github.event.pull_request.merged == true - name: Tag and Publish UPM package - uses: ./.github/workflows/upversionandtagrelease.yml - with: - build-host: ubuntu-latest - build-type: pre-release - secrets: inherit \ No newline at end of file diff --git a/.github/workflows/getpackageversionfrompackage.yml b/.github/workflows/getpackageversionfrompackage.yml deleted file mode 100644 index 32c56da3..00000000 --- a/.github/workflows/getpackageversionfrompackage.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Get the Package version from a UPM Package.json file - -on: - workflow_call: - inputs: - build-host: - required: true - type: string - version-file-path: - description: 'Optional, specify a path to search for the upm package.json file. Use this if validation fails to find a valid package.json file.\n **Note, Version file MUST contain the attribute "Unity" with the full Unity version expected, e.g. "2020.2.3f1"' - type: string - required: false - outputs: - packageversion: - description: "Returns the version of the UPM package" - value: ${{ jobs.get_package_version.outputs.upmpackageversion }} - -jobs: - get_package_version: - name: Get required Package version from UPM Package - runs-on: ${{ inputs.build-host }} - outputs: - upmpackageversion: ${{ steps.getVersion.outputs.packageversion }} - steps: - - name: Script Version - run: | - echo "::group::Script Versioning" - $scriptVersion = "1.0.0" - echo "Build Script Version: $scriptVersion" - echo "::endgroup::" - shell: pwsh - - uses: actions/checkout@v3 - with: - submodules: recursive - clean: true - - id: getVersion - name: 'Get Package Version Number' - run: | - echo "::group::Validating input" - - $versionFile = "${{ inputs.version-file-path }}" - if([string]::IsNullOrEmpty($versionFile)) - { - echo 'version input was empty, using default' - $versionFile = 'package.json' - } - echo 'Checking for project json at $versionFile' - - if ( -not (Test-Path -Path $versionFile) ) { - Write-Error "Failed to find a valid package.json file" - exit 1 - } - - echo "::endgroup::" - - echo "::group::Package Version UPM check" - - $package_json = Get-Content -Path $versionFile | ConvertFrom-Json - $packageVersion = $package_json.version - - if([string]::IsNullOrEmpty($packageVersion)) { - Write-Error "Project.json version number does not exist or is empty" - exit 1 - } - - echo "packageversion=$packageVersion" >> $env:GITHUB_OUTPUT - - echo "Detected version is $packageVersion" - echo "::endgroup::" - shell: pwsh \ No newline at end of file diff --git a/.github/workflows/main-publish.yml b/.github/workflows/main-publish.yml deleted file mode 100644 index f667dc63..00000000 --- a/.github/workflows/main-publish.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: Publish main branch and increment version - -on: - pull_request: - types: - - closed - branches: - - release - -jobs: - # Get Version to tag and release the branch, no up-version - [no-ver] included in PR title - validate-environment: - if: github.event.pull_request.merged == true && contains(github.event.pull_request.title, 'no-ver') - name: Get Version from UPM package - uses: ./.github/workflows/getpackageversionfrompackage.yml - with: - build-host: ubuntu-latest - - # Perform tagging - release-Package-only: - needs: validate-environment - name: Release package only, no upversion - uses: ./.github/workflows/tagrelease.yml - with: - build-host: ubuntu-latest - version: ${{ needs.validate-environment.outputs.packageversion }} - secrets: inherit - - # Up version the release and publish major release - upversion-major-Package: - if: github.event.pull_request.merged == true && contains(github.event.pull_request.title, 'no-ver') == false && contains(github.event.pull_request.title, 'major-release') - name: Major Version package and release - uses: ./.github/workflows/upversionandtagrelease.yml - with: - build-host: ubuntu-latest - build-type: major - secrets: inherit - - # Up version the release and publish minor release - upversion-minor-Package: - if: github.event.pull_request.merged == true && contains(github.event.pull_request.title, 'no-ver') == false && contains(github.event.pull_request.title, 'minor-release') - name: Minor Version package and release - uses: ./.github/workflows/upversionandtagrelease.yml - with: - build-host: ubuntu-latest - build-type: minor - secrets: inherit - - # Up version the release and publish patch release (default) - upversion-patch-Package: - if: github.event.pull_request.merged == true && contains(github.event.pull_request.title, 'no-ver') == false && contains(github.event.pull_request.title, 'minor-release') == false && contains(github.event.pull_request.title, 'major-release') == false - name: Patch Version package and release - uses: ./.github/workflows/upversionandtagrelease.yml - with: - build-host: ubuntu-latest - build-type: patch-release - secrets: inherit - - release-Complete: - if: ${{ always() }} - needs: [upversion-major-Package, upversion-minor-Package, upversion-patch-Package, release-Package-only] - name: Release complete - runs-on: ubuntu-latest - steps: - - name: Script Version - run: echo "Release done, updating Development" - - # Refresh the development branch with the main publish - refresh-development: - if: ${{ always() }} - needs: [release-Complete] - name: Refresh development branch - uses: ./.github/workflows/refreshbranch.yml - with: - build-host: ubuntu-latest - target-branch: development - source-branch: main - secrets: inherit - - # Up version the development branch ready for future development - upversion-development: - if: ${{ always() }} - needs: [refresh-development] - name: UpVersion the development branch for the next release - uses: ./.github/workflows/upversionandtagrelease.yml - with: - build-host: ubuntu-latest - build-type: patch - target-branch: development - createTag: false - secrets: inherit \ No newline at end of file diff --git a/.github/workflows/refreshbranch.yml b/.github/workflows/refreshbranch.yml deleted file mode 100644 index fb8a0215..00000000 --- a/.github/workflows/refreshbranch.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Refresh branch - -on: - workflow_call: - inputs: - build-host: - required: true - type: string - target-branch: - required: true - type: string - source-branch: - required: true - type: string - -concurrency: - group: ${{ github.ref }} - cancel-in-progress: true - -jobs: - packageRelease: - name: Refresh ${{ inputs.target-branch }} branch from ${{ inputs.source-branch }} branch - runs-on: ${{ inputs.build-host }} - steps: - - name: Script Version - run: | - echo "::group::Script Versioning" - $scriptVersion = "1.0.1" - echo "Build Script Version: $scriptVersion" - echo "::endgroup::" - shell: pwsh - - uses: actions/checkout@v3 - with: - ref: ${{ inputs.target-branch }} - clean: true - token: ${{ secrets.GIT_PAT }} - - name: Refresh from Source Branch - run: | - git pull origin ${{ inputs.source-branch }} - git commit -m "Branch ${{ inputs.target-branch }} updated with changes from ${{ inputs.source-branch }} [skip ci]" - git push origin ${{ inputs.target-branch }} - echo "Branch ${{ inputs.target-branch }} updated with changes from ${{ inputs.source-branch }}" - shell: pwsh \ No newline at end of file diff --git a/.github/workflows/rununitybuildmultiversion.yml b/.github/workflows/rununitybuildmultiversion.yml deleted file mode 100644 index ee228ee9..00000000 --- a/.github/workflows/rununitybuildmultiversion.yml +++ /dev/null @@ -1,471 +0,0 @@ -name: Run Unity Builds - -on: - workflow_call: - inputs: - dependencies: - description: "json array of dependencies and their targets" - required: false - type: string - -jobs: - run_build: - name: Run Unity Build process - runs-on: ${{ matrix.os }} - strategy: - matrix: - include: - - os: windows - unityVersion: 2019.4 - build-target: Android - - os: macOS - unityVersion: 2019.4 - build-target: iOS - - os: windows - unityVersion: 2019.4 - build-target: StandaloneWindows64 - - os: windows - unityVersion: 2019.4 - build-target: WSAPlayer - - os: windows - unityVersion: 2020.3 - build-target: Android - - os: macOS - unityVersion: 2020.3 - build-target: iOS - - os: windows - unityVersion: 2020.3 - build-target: StandaloneWindows64 - - os: windows - unityVersion: 2020.3 - build-target: WSAPlayer - - os: windows - unityVersion: 2021.3 - build-target: Android - - os: macOS - unityVersion: 2021.3 - build-target: iOS - - os: windows - unityVersion: 2021.3 - build-target: StandaloneWindows64 - - os: windows - unityVersion: 2021.3 - build-target: WSAPlayer - - os: windows - unityVersion: 2022.3 - build-target: Android - - os: macOS - unityVersion: 2022.3 - build-target: iOS - - os: windows - unityVersion: 2022.3 - build-target: StandaloneWindows64 - - os: windows - unityVersion: 2022.3 - build-target: WSAPlayer - - os: windows - unityVersion: 2023.1 - build-target: Android - - os: macOS - unityVersion: 2023.1 - build-target: iOS - - os: windows - unityVersion: 2023.1 - build-target: StandaloneWindows64 - - os: windows - unityVersion: 2023.1 - build-target: WSAPlayer - steps: - - name: Script Version - run: | - echo "::group::Script Versioning" - $scriptVersion = "1.0.0" - echo "Build Script Version: $scriptVersion" - echo "::endgroup::" - shell: pwsh - - uses: actions/checkout@v3 - with: - submodules: recursive - clean: true - - - id: build - name: 'Run Unity Builds' - run: | - echo "::group::Set Hub and editor locations" - $unityVersion = '${{ matrix.unityVersion }}' - - echo "::group::Set Hub and editor locations" - - ## Set Hub and editor locations - if ( (-not $global:PSVersionTable.Platform) -or ($global:PSVersionTable.Platform -eq "Win32NT") ) - { - $hubPath = "C:\Program Files\Unity Hub\Unity Hub.exe" - $editorRootPath = "C:\Program Files\Unity\Hub\Editor\" - $editorFileEx = "\Editor\Unity.exe" - $directorySeparatorChar = "\" - - #"Unity Hub.exe" -- --headless help - #. 'C:\Program Files\Unity Hub\Unity Hub.exe' -- --headless help - function unity-hub - { - & $hubPath -- --headless $args.Split(" ") | Out-String -NoNewline - } - } - elseif ( $global:PSVersionTable.OS.Contains("Darwin") ) - { - $hubPath = "/Applications/Unity Hub.app/Contents/macOS/Unity Hub" - $editorRootPath = "/Applications/Unity/Hub/Editor/" - $editorFileEx = "/Unity.app/Contents/macOS/Unity" - $directorySeparatorChar = "/" - - # /Applications/Unity\ Hub.app/Contents/macOS/Unity\ Hub -- --headless help - function unity-hub - { - & $hubPath -- --headless $args.Split(" ") | Out-String -NoNewline - } - } - elseif ( $global:PSVersionTable.OS.Contains("Linux") ) - { - $hubPath = "$HOME/Unity Hub/UnityHub.AppImage" - $editorRootPath = "$HOME/Unity/Hub/Editor/" - $editorFileEx = "/Editor/Unity" - $directorySeparatorChar = "/" - - # /UnityHub.AppImage --headless help - # xvfb-run --auto-servernum "$HOME/Unity Hub/UnityHub.AppImage" --headless help - function unity-hub - { - xvfb-run --auto-servernum "$hubPath" --headless $args.Split(" ") - } - } - - echo "::endgroup::" - - echo "::group::Get String function to query a string for a value" - - function GetString($InputString, $InputPattern, $MatchIndex) - { - $regExResult = $InputString | Select-String -Pattern $InputPattern - if($regExResult.Length -gt 0) - { - return $regExResult.Matches[$MatchIndex].Value - } - else - { - return 0 - } - } - - function Get-LetterCount - { - Param ([string]$string) - return $string.Length - } - echo "::endgroup::" - - echo "::group::Get Installed Unity version based on Matrix" - echo "Unity hub path is {$hubPath}" - echo "Requested unity version is {$unityVersion}" - - $InstalledUnityVersions = unity-hub editors - $editorRootPath = unity-hub ip -g - echo "Installed unity versions are {$InstalledUnityVersions}" - echo "Unity install path is {$editorRootPath}" - - $versionLength = Get-LetterCount $unityVersion - if ($versionLength -eq 4) { - $queryUnityVersion = GetString $InstalledUnityVersions "$unityVersion.{4,7}" -MatchIndex 0 - } - elseif ($versionLength -eq 6) { - $queryUnityVersion = GetString $InstalledUnityVersions "$unityVersion.{4,5}" -MatchIndex 0 - } - else { - $queryUnityVersion = GetString $InstalledUnityVersions "$unityVersion" -MatchIndex 0 - } - - echo "Found unity version is {$queryUnityVersion} for input {$unityVersion}" - - if ($queryUnityVersion -ne 0) - { - $unityVersion = $queryUnityVersion.Trim(","," ").Trim() - echo "Long Unity version is $unityVersion" - } - else - { - echo "Unity $unityVersion not found on this machine, skipping" - exit 0 - } - - echo "::endgroup::" - - echo "::group::Search for Editor if not found" - $checkPath = Join-Path $editorRootPath $unityVersion - echo "Testing for editor at $checkPath" - while (-not (Test-Path "$checkPath")) { - $source = $unityVersion.Replace("f1","") - $newversion = $source.Split(".") - $newversion[2] = [int]$newversion[2] - 1 - if ([int]$newversion[2] -lt 1) { - echo "Unity ${{ matrix.unityVersion }} not found on this machine, skipping" - exit 0 - } - $newunityVersion = "{0}.{1}.{2}f1" -f $newversion[0],$newversion[1],$newversion[2] - echo "Unity $unityVersion not found on this machine, trying $newunityVersion" - $unityVersion = $newunityVersion - $checkPath = Join-Path $editorRootPath $unityVersion - } - echo "::endgroup::" - - echo "::group::Set editor locations" - - if ( (-not $global:PSVersionTable.Platform) -or ($global:PSVersionTable.Platform -eq "Win32NT") ) { - echo 'Building using Windows' - $editorFileEx = "/Editor/Unity.exe" - $editorrunpath = Join-Path $editorRootPath $unityVersion $editorFileEx - - function unity-editor { - #$p = Start-Process -Verbose -NoNewWindow -PassThru -Wait -FilePath "$editorrunpath" -ArgumentList (@(' -batchmode') + $args.Split(" ")) - & $editorrunpath -batchmode $args.Split(" ") | Out-String - } - } - elseif ( $global:PSVersionTable.OS.Contains("Darwin") ) { - echo 'Building using Mac' - $editorFileEx = "/Unity.app/Contents/macOS/Unity" - $editorrunpath = Join-Path $editorRootPath $unityVersion $editorFileEx - - function unity-editor { - #$p = Start-Process -Verbose -NoNewWindow -PassThru -Wait -FilePath "$editorrunpath" -ArgumentList (@(' -batchmode') + $args.Split(" ")) - & $editorrunpath -batchmode $args.Split(" ") | Out-String - } - } - elseif ( $global:PSVersionTable.OS.Contains("Linux") ) { - echo 'Building using Linux' - $editorFileEx = "/Editor/Unity" - $editorrunpath = Join-Path $editorRootPath $unityVersion $editorFileEx - - function unity-editor { - xvfb-run --auto-servernum "$editorrunpath" -batchmode $args.Split(" ") - } - } - else - { - echo 'Unknown build platform' - } - - echo "::endgroup::" - - echo "::group::Test Unity version is installed" - - if ( -not (Test-Path "$editorrunpath") ) - { - Write-Error "Editor not Found for $unityVersion" - exit 1 - } - else { - echo "Editor Path is {$editorrunpath}" - } - - echo "::endgroup::" - - echo "::group::Setup logging and run Unit tests" - - # Log detail - $logDirectory = "Logs" - if (Test-Path -Path $logDirectory) { - echo "Clearing logs from a previous run" - Remove-item $logDirectory -recurse - } - - $logDirectory = New-Item -ItemType Directory -Force -Path $logDirectory | Select-Object - - echo "Log Directory: $logDirectory" - $date = Get-Date -Format "yyyyMMddTHHmmss" - - # If run manually, the Refname is actually blank, so just use date - if([string]::IsNullOrEmpty(${GITHUB_REF_NAME})) { - $logName = "$logDirectory$directorySeparatorChar$date" - } - else{ - $logName = "$logDirectory$directorySeparatorChar${GITHUB_REF_NAME}-$date" - } - - $logPath = "$logName.log" - $testsLogPath = "$logName-tests.xml" - - echo "Logpath [$logPath]" - echo "TestsPath [$testsLogPath]" - echo "::endgroup::" - - echo "::group::Grouping Package in a UPM folder" - - $UPMFolderName = 'u' - - if ( -not (Test-Path '$UPMFolderName') ) - { - New-Item $UPMFolderName -ItemType Directory - } - - Move-Item -Path * -Destination $UPMFolderName -exclude $UPMFolderName - - echo "::endgroup::" - - echo "::group::Creating Temp Unity project" - - $TempUnityProjectName = 'P' - - unity-editor '-createProject' $TempUnityProjectName -quit - - $destinationPath = $TempUnityProjectName + $directorySeparatorChar + 'packages' - Move-Item -Path $UPMFolderName -Destination $destinationPath - - echo "::endgroup::" - - echo "::group::If required, clone dependencies in to test project" - - <# Dependency option requires specific inputs - - * A dependency input string in json format, listing each dependency by name and git url, e.g. - $dependencies = '[{"ASADependencies": "github.com/SimonDarksideJ/upmGithubActionsTests.git"}]' - *Note, remove the https:// portion to allow using a PAT to access the repo - The Name of the dependency should ALSO MATCH the name of the branch on the repo where the dependency is held (files intended for the packages folder) - - * Additionally, if Manifest entries are required, then a manifest file with those dependencies (and ONLY the new dependancies) should also be in the dependency branch named the same as the branch name - e.g. "ASADependencies.json" - keep the same structure, but only the dependancy entries - - !!Does NOT support additional scoped registries at this time! #> - - echo "---------------------------------------------" - echo "Read dependancy input value" - if([string]::IsNullOrEmpty('${{ inputs.dependencies }}')) - { - echo "No dependencies provided" - echo "input ${{ inputs.dependencies }}" - echo "------------------------------" - } - else { - echo "dependencies provided, validating" - - # Read dependancy input value - $dependencies = '${{ inputs.dependencies }}' - - if([string]::IsNullOrEmpty('${{ secrets.GIT_USER_NAME }}') -or [string]::IsNullOrEmpty('${{ secrets.GIT_PAT }}')){ - echo "" - echo "Secrets for GIT_USER_NAME or GIT_PAT missing, please register them with access to this runner" - echo "*Note, Organisation secrets are not accessible to Forked repos and need registering in the local fork" - exit 1 - } - - echo "---------------------------------------------" - echo "Read dependancy input values as json" - $JSONdependencies = $dependencies | ConvertFrom-Json - - echo $JSONdependencies - - # Read current Manifest json - $manifestPath = $destinationPath + $directorySeparatorChar + 'manifest.json' - $projectManifest = Get-Content -Path $manifestPath | ConvertFrom-Json - $strArray = $projectManifest.dependencies.PsObject.Properties | ForEach-Object {"$($_.Name)@$($_.Value),"} - - echo "---------------------------------------------" - echo "Loop through new dependancies and add them to the project Manifest" - foreach($dependency in $JSONdependencies){ - $dependency.PsObject.Properties | ForEach-Object -Process { - $dependencyName = $_.Name - $dependencyPath = $dependencyName.Replace("/","_") - $dependencyURL = $_.Value - echo "---------------------------------------------" - echo "Cloning dependency - Name [$dependencyName] - Path [$dependencyPath] - URL [$dependencyURL]" - $cloneURL = "https://${{ secrets.GIT_USER_NAME }}:${{ secrets.GIT_PAT }}@$dependencyURL" - - echo "cloning $dependencyName from $dependencyURL and moving to $destinationPath" - echo "git path - $cloneURL" - - # Clone Dependancy repo to destination folder - git clone -b $dependencyName --single-branch $cloneURL $dependencyPath - - if( -not (Test-Path -Path "$dependencyPath")){ - echo "Unable to clone $dependencyName from $dependencyURL" - exit 1 - } - - # Move files from clone path into packages folder, if the dependency contains a UPM package then move the entire folder and not just its contents - if (Test-Path -Path "$dependencyPath/package.json") { - $package_json = Get-Content -Path $dependencyPath/package.json | ConvertFrom-Json - $packageName = $package_json.name - Rename-Item $dependencyPath $packageName - echo "Moving whole $packageName UPM package to $destinationPath" - Move-Item -Path "$packageName" -Destination $destinationPath - } - else { - echo "Moving the contents of $dependencyName into the $destinationPath folder" - Move-Item -Path "$dependencyPath/*" -Destination $destinationPath - } - - # Get Dependency manifest entries (if applicable) - if (Test-Path -Path "$destinationPath/$dependencyName.json") { - $dependencyManifest = Get-Content -Path "$destinationPath/$dependencyName.json" | ConvertFrom-Json - $dependencyManifest.dependencies.PsObject.Properties | ForEach-Object { - $strArray += "$($_.Name)@$($_.Value)," - } - } - else{ - echo "No denendency json found called $destinationPath/$dependencyName.json, skipping adding additional manifest entries" - } - } - } - echo "---------------------------------------------" - - # Reformat combined dependancies list - $strArray = $strArray.Trim(",") | ConvertTo-Json - $strArray = $strArray.Replace("@",'":"').Replace("[","{").Replace("]","}") - $strArrayObject = $strArray | ConvertFrom-Json - - # Save manifest back to project - $projectManifest.dependencies = $strArrayObject - $projectManifest | ConvertTo-Json | Set-Content -Path "$destinationPath/manifest.json" - - echo "Project updated with the following dependencies" - echo "-----------------------------------------------" - echo $projectManifest - } - - echo "::endgroup::" - - echo "::group::Run build" - - echo "---------------------------------------------" - echo "Start Testing" - echo "Unity Command\n[unity-editor -projectPath $TempUnityProjectName -logfile $logPath -batchmode -nographics -quit -buildTarget ${{ matrix.build-target }}]" - - unity-editor -projectPath $TempUnityProjectName -logfile $logPath -batchmode -nographics -quit -buildTarget ${{ matrix.build-target }} - - echo "---------------------------------------------" - echo "::group::Unity Unit tests Results" - if (Test-Path $testsLogPath) { - echo "Test Run results for ${GITHUB_REPOSITORY} Branch ${GITHUB_REF}" - echo "" - Select-Xml -Path $testsLogPath -XPath '/test-run/test-suite' | ForEach-Object { "Name: " +$_.Node.name, ", Result: " + $_.Node.result, ", Total Tests: " + $_.Node.total, ", Passed: " + $_.Node.passed, ", Failed: " + $_.Node.failed, ", Skipped: " + $_.Node.skipped } - } - else { - echo "No test results found for ${GITHUB_REPOSITORY} Branch ${GITHUB_REF} at $testsLogPath" - echo "" - } - echo "::endgroup::" - - if($LASTEXITCODE -ne '0'){ - echo "::group::Unity Unit tests errors" - $exitCode = $testResult.ExitCode - Get-Content $logPath - echo "Build failed due to errors ($LASTEXITCODE), please check the log at $logPath" - echo "::endgroup::" - - exit $LASTEXITCODE - } - echo "::endgroup::" - - shell: pwsh - - uses: actions/upload-artifact@v3 - if: always() - with: - name: unity-build-log - path: Logs/** \ No newline at end of file diff --git a/.github/workflows/rununitysinglebuild.yml b/.github/workflows/rununitysinglebuild.yml deleted file mode 100644 index d6ba2836..00000000 --- a/.github/workflows/rununitysinglebuild.yml +++ /dev/null @@ -1,423 +0,0 @@ -name: Run Limited Unity Builds - -on: - workflow_call: - inputs: - unityVersion: - description: "The version of Unity to validate on" - required: true - type: string - dependencies: - description: "json array of dependencies and their targets" - required: false - type: string - -jobs: - run_build: - name: Run Unity Build process - runs-on: ${{ matrix.os }} - strategy: - matrix: - include: - - os: macOS - build-target: iOS - - os: windows - build-target: Android - - os: windows - build-target: StandaloneWindows64 - - os: windows - build-target: WSAPlayer - - steps: - - name: Script Version - run: | - echo "::group::Script Versioning" - $scriptVersion = "1.0.0" - echo "Build Script Version: $scriptVersion" - echo "::endgroup::" - shell: pwsh - - uses: actions/checkout@v3 - with: - submodules: recursive - clean: true - - id: build - name: 'Run Unity Builds' - run: | - echo "::group::Set Hub and editor locations" - $unityVersion = '${{ inputs.unityVersion }}' - - echo "::group::Set Hub and editor locations" - - ## Set Hub and editor locations - if ( (-not $global:PSVersionTable.Platform) -or ($global:PSVersionTable.Platform -eq "Win32NT") ) - { - $hubPath = "C:\Program Files\Unity Hub\Unity Hub.exe" - $editorRootPath = "C:\Program Files\Unity\Hub\Editor\" - $editorFileEx = "\Editor\Unity.exe" - $directorySeparatorChar = "\" - - #"Unity Hub.exe" -- --headless help - #. 'C:\Program Files\Unity Hub\Unity Hub.exe' -- --headless help - function unity-hub - { - & $hubPath -- --headless $args.Split(" ") | Out-String -NoNewline - } - } - elseif ( $global:PSVersionTable.OS.Contains("Darwin") ) - { - $hubPath = "/Applications/Unity Hub.app/Contents/MacOS/Unity Hub" - $editorRootPath = "/Applications/Unity/Hub/Editor/" - $editorFileEx = "/Unity.app/Contents/MacOS/Unity" - $directorySeparatorChar = "/" - - # /Applications/Unity\ Hub.app/Contents/MacOS/Unity\ Hub -- --headless help - function unity-hub - { - & $hubPath -- --headless $args.Split(" ") | Out-String -NoNewline - } - } - elseif ( $global:PSVersionTable.OS.Contains("Linux") ) - { - $hubPath = "$HOME/Unity Hub/UnityHub.AppImage" - $editorRootPath = "$HOME/Unity/Hub/Editor/" - $editorFileEx = "/Editor/Unity" - $directorySeparatorChar = "/" - - # /UnityHub.AppImage --headless help - # xvfb-run --auto-servernum "$HOME/Unity Hub/UnityHub.AppImage" --headless help - function unity-hub - { - xvfb-run --auto-servernum "$hubPath" --headless $args.Split(" ") - } - } - - echo "::endgroup::" - - echo "::group::Get String function to query a string for a value" - - function GetString($InputString, $InputPattern, $MatchIndex) - { - $regExResult = $InputString | Select-String -Pattern $InputPattern - if($regExResult.Length -gt 0) - { - return $regExResult.Matches[$MatchIndex].Value - } - else - { - return 0 - } - } - - function Get-LetterCount - { - Param ([string]$string) - return $string.Length - } - echo "::endgroup::" - - echo "::group::Find Installed Unity version based on input" - echo "Unity hub path is {$hubPath}" - echo "Requested unity version is {$unityVersion}" - - $InstalledUnityVersions = unity-hub editors - $editorRootPath = unity-hub ip -g - echo "Installed unity versions are {$InstalledUnityVersions}" - echo "Unity install path is {$editorRootPath}" - - $versionLength = Get-LetterCount $unityVersion - if ($versionLength -eq 4) { - $queryUnityVersion = GetString $InstalledUnityVersions "$unityVersion.{4,7}" -MatchIndex 0 - } - elseif ($versionLength -eq 6) { - $queryUnityVersion = GetString $InstalledUnityVersions "$unityVersion.{4,5}" -MatchIndex 0 - } - else { - $queryUnityVersion = GetString $InstalledUnityVersions "$unityVersion" -MatchIndex 0 - } - - echo "Found unity version is {$queryUnityVersion} for input {$unityVersion}" - - if ($queryUnityVersion -ne 0) - { - $unityVersion = $queryUnityVersion.Trim(","," ").Trim() - echo "Long Unity version is $unityVersion" - } - else - { - echo "Unity $unityVersion not found on this machine, skipping" - exit 0 - } - - echo "::endgroup::" - - echo "::group::Search for Editor if not found" - $checkPath = Join-Path $editorRootPath $unityVersion - echo "Testing for editor at $checkPath" - while (-not (Test-Path "$checkPath")) { - $source = $unityVersion.Replace("f1","") - $newversion = $source.Split(".") - $newversion[2] = [int]$newversion[2] - 1 - if ([int]$newversion[2] -lt 1) { - echo "Unity ${{ inputs.unityVersion }} not found on this machine, skipping" - exit 0 - } - $newunityVersion = "{0}.{1}.{2}f1" -f $newversion[0],$newversion[1],$newversion[2] - echo "Unity $unityVersion not found on this machine, trying $newunityVersion" - $unityVersion = $newunityVersion - $checkPath = Join-Path $editorRootPath $unityVersion - } - echo "::endgroup::" - - echo "::group::Set editor locations" - - if ( (-not $global:PSVersionTable.Platform) -or ($global:PSVersionTable.Platform -eq "Win32NT") ) { - echo 'Building using Windows' - $editorFileEx = "/Editor/Unity.exe" - $editorrunpath = Join-Path $editorRootPath $unityVersion $editorFileEx - - function unity-editor { - #$p = Start-Process -Verbose -NoNewWindow -PassThru -Wait -FilePath "$editorrunpath" -ArgumentList (@(' -batchmode') + $args.Split(" ")) - & $editorrunpath -batchmode $args.Split(" ") | Out-String - } - } - elseif ( $global:PSVersionTable.OS.Contains("Darwin") ) { - echo 'Building using Mac' - $editorFileEx = "/Unity.app/Contents/MacOS/Unity" - $editorrunpath = Join-Path $editorRootPath $unityVersion $editorFileEx - - function unity-editor { - #$p = Start-Process -Verbose -NoNewWindow -PassThru -Wait -FilePath "$editorrunpath" -ArgumentList (@(' -batchmode') + $args.Split(" ")) - & $editorrunpath -batchmode $args.Split(" ") | Out-String - } - } - elseif ( $global:PSVersionTable.OS.Contains("Linux") ) { - echo 'Building using Linux' - $editorFileEx = "/Editor/Unity" - $editorrunpath = Join-Path $editorRootPath $unityVersion $editorFileEx - - function unity-editor { - xvfb-run --auto-servernum "$editorrunpath" -batchmode $args.Split(" ") - } - } - else - { - echo 'Unknown build platform' - } - - echo "::endgroup::" - - echo "::group::Test Unity version is installed" - - if ( -not (Test-Path "$editorrunpath") ) - { - Write-Error "Editor not Found for $unityVersion" - exit 1 - } - else { - echo "Editor Path is {$editorrunpath}" - } - - echo "::endgroup::" - - echo "::group::Setup logging and run Unit tests" - - # Log detail - $logDirectory = "Logs" - if (Test-Path -Path $logDirectory) { - echo "Clearing logs from a previous run" - Remove-item $logDirectory -recurse - } - - $logDirectory = New-Item -ItemType Directory -Force -Path $logDirectory | Select-Object - - echo "Log Directory: $logDirectory" - $date = Get-Date -Format "yyyyMMddTHHmmss" - - # If run manually, the Refname is actually blank, so just use date - if([string]::IsNullOrEmpty(${GITHUB_REF_NAME})) { - $logName = "$logDirectory$directorySeparatorChar$date" - } - else{ - $logName = "$logDirectory$directorySeparatorChar${GITHUB_REF_NAME}-$date" - } - - $logPath = "$logName.log" - $testsLogPath = "$logName-tests.xml" - - echo "Logpath [$logPath]" - echo "TestsPath [$testsLogPath]" - echo "::endgroup::" - - echo "::group::Grouping Package in a UPM folder" - - $UPMFolderName = 'u' - - if ( -not (Test-Path '$UPMFolderName') ) - { - New-Item $UPMFolderName -ItemType Directory - } - - Move-Item -Path * -Destination $UPMFolderName -exclude $UPMFolderName - - echo "::endgroup::" - - echo "::group::Creating Temp Unity project" - - $TempUnityProjectName = 'P' - - unity-editor '-createProject' $TempUnityProjectName -quit - - $destinationPath = $TempUnityProjectName + $directorySeparatorChar + 'packages' - Move-Item -Path $UPMFolderName -Destination $destinationPath - - echo "::endgroup::" - - echo "::group::If required, clone dependencies in to test project" - - <# Dependency option requires specific inputs - - * A dependency input string in json format, listing each dependency by name and git url, e.g. - $dependencies = '[{"ASADependencies": "github.com/SimonDarksideJ/upmGithubActionsTests.git"}]' - *Note, remove the https:// portion to allow using a PAT to access the repo - The Name of the dependency should ALSO MATCH the name of the branch on the repo where the dependency is held (files intended for the packages folder) - - * Additionally, if Manifest entries are required, then a manifest file with those dependencies (and ONLY the new dependancies) should also be in the dependency branch named the same as the branch name - e.g. "ASADependencies.json" - keep the same structure, but only the dependancy entries - - !!Does NOT support additional scoped registries at this time! #> - - echo "---------------------------------------------" - echo "Read dependancy input value" - if([string]::IsNullOrEmpty('${{ inputs.dependencies }}')) - { - echo "No dependencies provided" - echo "input ${{ inputs.dependencies }}" - echo "------------------------------" - } - else { - echo "dependencies provided, validating" - - # Read dependancy input value - $dependencies = '${{ inputs.dependencies }}' - - if([string]::IsNullOrEmpty('${{ secrets.GIT_USER_NAME }}') -or [string]::IsNullOrEmpty('${{ secrets.GIT_PAT }}')){ - echo "" - echo "Secrets for GIT_USER_NAME or GIT_PAT missing, please register them with access to this runner" - echo "*Note, Organisation secrets are not accessible to Forked repos and need registering in the local fork" - exit 1 - } - - echo "---------------------------------------------" - echo "Read dependancy input values as json" - $JSONdependencies = $dependencies | ConvertFrom-Json - - echo $JSONdependencies - - # Read current Manifest json - $manifestPath = $destinationPath + $directorySeparatorChar + 'manifest.json' - $projectManifest = Get-Content -Path $manifestPath | ConvertFrom-Json - $strArray = $projectManifest.dependencies.PsObject.Properties | ForEach-Object {"$($_.Name)@$($_.Value),"} - - echo "---------------------------------------------" - echo "Loop through new dependancies and add them to the project Manifest" - foreach($dependency in $JSONdependencies){ - $dependency.PsObject.Properties | ForEach-Object -Process { - $dependencyName = $_.Name - $dependencyPath = $dependencyName.Replace("/","_") - $dependencyURL = $_.Value - echo "---------------------------------------------" - echo "Cloning dependency - Name [$dependencyName] - Path [$dependencyPath] - URL [$dependencyURL]" - $cloneURL = "https://${{ secrets.GIT_USER_NAME }}:${{ secrets.GIT_PAT }}@$dependencyURL" - - echo "cloning $dependencyName from $dependencyURL and moving to $destinationPath" - echo "git path - $cloneURL" - - # Clone Dependancy repo to destination folder - git clone -b $dependencyName --single-branch $cloneURL $dependencyPath - - if( -not (Test-Path -Path "$dependencyPath")){ - echo "Unable to clone $dependencyName from $dependencyURL" - exit 1 - } - - # Move files from clone path into packages folder, if the dependency contains a UPM package then move the entire folder and not just its contents - if (Test-Path -Path "$dependencyPath/package.json") { - $package_json = Get-Content -Path $dependencyPath/package.json | ConvertFrom-Json - $packageName = $package_json.name - Rename-Item $dependencyPath $packageName - echo "Moving whole $packageName UPM package to $destinationPath" - Move-Item -Path "$packageName" -Destination $destinationPath - } - else { - echo "Moving the contents of $dependencyName into the $destinationPath folder" - Move-Item -Path "$dependencyPath/*" -Destination $destinationPath - } - - # Get Dependency manifest entries (if applicable) - if (Test-Path -Path "$destinationPath/$dependencyName.json") { - $dependencyManifest = Get-Content -Path "$destinationPath/$dependencyName.json" | ConvertFrom-Json - $dependencyManifest.dependencies.PsObject.Properties | ForEach-Object { - $strArray += "$($_.Name)@$($_.Value)," - } - } - else{ - echo "No denendency json found called $destinationPath/$dependencyName.json, skipping adding additional manifest entries" - } - } - } - echo "---------------------------------------------" - - # Reformat combined dependancies list - $strArray = $strArray.Trim(",") | ConvertTo-Json - $strArray = $strArray.Replace("@",'":"').Replace("[","{").Replace("]","}") - $strArrayObject = $strArray | ConvertFrom-Json - - # Save manifest back to project - $projectManifest.dependencies = $strArrayObject - $projectManifest | ConvertTo-Json | Set-Content -Path "$destinationPath/manifest.json" - - echo "Project updated with the following dependencies" - echo "-----------------------------------------------" - echo $projectManifest - } - - echo "::endgroup::" - - echo "::group::Run build" - - echo "---------------------------------------------" - echo "Start Testing" - echo "Unity Command\n[unity-editor -projectPath $TempUnityProjectName -logfile $logPath -batchmode -nographics -quit -buildTarget ${{ matrix.build-target }}]" - - unity-editor -projectPath $TempUnityProjectName -logfile $logPath -batchmode -nographics -quit -buildTarget ${{ matrix.build-target }} - - echo "---------------------------------------------" - echo "::group::Unity Unit tests Results" - if (Test-Path $testsLogPath) { - echo "Test Run results for ${GITHUB_REPOSITORY} Branch ${GITHUB_REF}" - echo "" - Select-Xml -Path $testsLogPath -XPath '/test-run/test-suite' | ForEach-Object { "Name: " +$_.Node.name, ", Result: " + $_.Node.result, ", Total Tests: " + $_.Node.total, ", Passed: " + $_.Node.passed, ", Failed: " + $_.Node.failed, ", Skipped: " + $_.Node.skipped } - } - else { - echo "No test results found for ${GITHUB_REPOSITORY} Branch ${GITHUB_REF} at $testsLogPath" - echo "" - } - echo "::endgroup::" - - if($LASTEXITCODE -ne '0'){ - echo "::group::Unity Unit tests errors" - $exitCode = $testResult.ExitCode - Get-Content $logPath - echo "Build failed due to errors ($LASTEXITCODE), please check the log at $logPath" - echo "::endgroup::" - - exit $LASTEXITCODE - } - echo "::endgroup::" - - shell: pwsh - - uses: actions/upload-artifact@v3 - if: always() - with: - name: unity-build-log - path: Logs/** \ No newline at end of file diff --git a/.github/workflows/tagrelease.yml b/.github/workflows/tagrelease.yml deleted file mode 100644 index d572d768..00000000 --- a/.github/workflows/tagrelease.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: Tag Release - -on: - workflow_call: - inputs: - build-host: - required: true - type: string - version: - required: true - type: string - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -concurrency: - group: ${{ github.ref }} - cancel-in-progress: true - -jobs: - packageRelease: - name: Package UPM Project and tag - runs-on: ${{ inputs.build-host }} - outputs: - packageversion: ${{ steps.getpackageversion.outputs.packageversion }} - steps: - - name: Script Version - run: | - echo "::group::Script Versioning" - $scriptVersion = "1.0.2" - echo "Build Script Version: $scriptVersion" - echo "::endgroup::" - shell: pwsh - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - submodules: recursive - clean: true - token: ${{ secrets.GIT_PAT }} - - uses: actions/setup-node@v3 - - name: Set Github vars - run: | - if([string]::IsNullOrEmpty('${{ secrets.GIT_USER_NAME }}')){ - if([string]::IsNullOrEmpty('${{ secrets.GIT_USER_NAME }}')){ - $gitUser = "action" - $gitEmail = "action@github.com" - } - else { - $gitUser = "${GITHUB_ACTOR}" - $gitEmail = "$gitUser@users.noreply.github.com" - } - } - else { - $gitUser = "${{ secrets.GIT_USER_NAME }}" - $gitEmail = "$gitUser@users.noreply.github.com" - } - git config --global user.email "$gitUser@users.noreply.github.com" - git config --global user.name "$gitUser" - shell: pwsh - - name: Check if Tag Exists - run: | - $tagVersion = "${{ inputs.version }}" - $tags = git tag - echo "Tags found are [$tags], searching for [$tagVersion]" - foreach ($tag in $tags) - { - if($tag.Trim() -eq "$tagVersion") - { - Write-Error "$tagVersion tag already exists" - exit 1 - } - } - shell: pwsh - - name: Create tag and push - run: | - $tagVersion = "${{ inputs.version }}" - git tag -fa "v$tagVersion" "${GITHUB_SHA}" -m "v$tagVersion Release [skip ci]" - git push origin "v$tagVersion" --force - shell: pwsh \ No newline at end of file diff --git a/.github/workflows/upversionandtagrelease.yml b/.github/workflows/upversionandtagrelease.yml deleted file mode 100644 index 4e630ba9..00000000 --- a/.github/workflows/upversionandtagrelease.yml +++ /dev/null @@ -1,181 +0,0 @@ -name: UpVersion Package UPM project and create new tag - -on: - workflow_call: - inputs: - build-host: - required: true - type: string - build-type: - required: false - default: 'pre-release' - type: string -# options: -# - major -# - minor -# - patch -# - patch-release -# - pre-release -# - build - target-branch: - required: false - type: string - default: ${{ github.ref }} - createTag: - required: false - type: boolean - default: true - - outputs: - packageversion: - description: "Returns the version of Unity the UPM package requires" - value: ${{ jobs.packageRelease.outputs.packageversion }} - secrets: - GIT_PAT: - required: true - -jobs: - packageRelease: - name: Package UPM Project and tag - runs-on: ${{ inputs.build-host }} - outputs: - packageversion: ${{ steps.getpackageversion.outputs.packageversion }} - steps: - - name: Script Version - run: | - echo "::group::Script Versioning" - $scriptVersion = "1.0.2" - echo "Build Script Version: $scriptVersion" - echo "::endgroup::" - shell: pwsh - - uses: actions/checkout@v3 - with: - ref: ${{ inputs.target-branch }} - fetch-depth: 0 - submodules: recursive - clean: true - token: ${{ secrets.GIT_PAT }} - - uses: actions/setup-node@v3 - - name: Set Github vars - run: | - if([string]::IsNullOrEmpty('${{ secrets.GIT_USER_NAME }}')){ - if([string]::IsNullOrEmpty('${{ secrets.GIT_USER_NAME }}')){ - $gitUser = "action" - $gitEmail = "action@github.com" - } - else { - $gitUser = "${GITHUB_ACTOR}" - $gitEmail = "$gitUser@users.noreply.github.com" - } - } - else { - $gitUser = "${{ secrets.GIT_USER_NAME }}" - $gitEmail = "$gitUser@users.noreply.github.com" - } - git config --global user.email "$gitUser@users.noreply.github.com" - git config --global user.name "$gitUser" - shell: pwsh - - id: getpackageversion - name: Bump UPM Package version - run: | - function UpdateProjectVersionJSON { - param ( - [Parameter(Mandatory)] - $type, - $packageFile = 'package.json' - ) - <# - Type of build can be one of the following - - 'build' # Build release - 1.0.0-pre.0+1 - - 'pre-release' # Pre-Release - 1.0.0-pre.1 - - 'patch-release' # Patch release - 1.0.1 (reset preview version to current patch, no increase) - - 'patch' # Patch release - 1.0.1 - - 'minor' # Minor release - 1.1.0 - - 'major' # Major release - 2.0.0 - #> - - if ( -not (Test-Path -Path $packageFile) ) { - Write-Error "Failed to find a valid project manifest at `"$packageFile`"" - return $null - } - - $packageInfo = (Get-Content $packageFile -Raw) | ConvertFrom-Json - Write-Host "Detected Project Version:" $packageInfo.version - function IfNull($a, $b, $c) { if ($null -eq $a) { return $b } else { return $c } } - - $packageSemVer = [System.Management.Automation.SemanticVersion]$packageInfo.version - $majorVersion = if($null -eq $packageSemVer.Major) {0} else {$packageSemVer.Major} - $minorVersion = if($null -eq $packageSemVer.Minor) {0} else {$packageSemVer.Minor} - $patchVersion = if($null -eq $packageSemVer.Patch) {0} else {$packageSemVer.Patch} - $prereleaseVersion = if($null -eq $packageSemVer.PreReleaseLabel) {0} else {$packageSemVer.PreReleaseLabel.Replace('pre.','')} - $buildVersion = if($null -eq $packageSemVer.BuildLabel) {0} else {$packageSemVer.BuildLabel} - - # work out new version - switch ($type) { - 'build' { - [int]$buildVersion += 1 - $newPackageSemVer = [System.Management.Automation.SemanticVersion]::New($majorVersion, $minorVersion, $patchVersion, "pre." + $prereleaseVersion, $buildVersion) - } - 'pre-release' { - [int]$prereleaseVersion += 1 - $newPackageSemVer = [System.Management.Automation.SemanticVersion]::New($majorVersion, $minorVersion, $patchVersion, "pre." + $prereleaseVersion) - } - 'patch-release' { - $newPackageSemVer = [System.Management.Automation.SemanticVersion]::New($majorVersion, $minorVersion, $patchVersion) - } - 'patch' { - [int]$patchVersion += 1 - $newPackageSemVer = [System.Management.Automation.SemanticVersion]::New($majorVersion, $minorVersion, $patchVersion) - } - 'minor' { - [int]$minorVersion += 1 - $newPackageSemVer = [System.Management.Automation.SemanticVersion]::New($majorVersion, $minorVersion, 0) - } - 'major' { - [int]$majorVersion += 1 - $newPackageSemVer = [System.Management.Automation.SemanticVersion]::New($majorVersion, 0, 0) - } - } - - Write-Host "Upgrading project version [$packageSemVer] to [$newPackageSemVer]" - - # Write out updated package info - - $packageInfo.version = $newPackageSemVer.ToString() - $packageInfo | ConvertTo-Json | Set-Content $packageFile - - return $packageInfo.version - } - - $packageFile = 'package.json' - $result = UpdateProjectVersionJSON("${{ inputs.build-type }}","$packageFile") - if([string]::IsNullOrEmpty($result)) { - echo "Version patch failed" - exit 1 - } - git add "$packageFile" - git commit -m "Auto increment pre-release version to $result [skip ci]" - git push origin - echo "packageversion=$result" >> $env:GITHUB_OUTPUT - shell: pwsh - - name: Check if Tag Exists - run: | - $tagVersion = '${{steps.getpackageversion.outputs.packageversion }}' - $tags = git tag - echo "Tags found are [$tags], searching for [$tagVersion]" - foreach ($tag in $tags) - { - if($tag.Trim() -eq "$tagVersion") - { - Write-Error "$tagVersion tag already exists" - exit 1 - } - } - shell: pwsh - - name: Publish package tag - if: ${{inputs.createTag == true}} - run: | - $outputVersion = '${{steps.getpackageversion.outputs.packageversion }}' - git tag -fa "v$outputVersion" "${GITHUB_SHA}" -m "v$outputVersion Release" - git push origin "v$outputVersion" --force --tags - shell: pwsh \ No newline at end of file diff --git a/Editor/CUIImageEditor.cs b/Editor/CUIImageEditor.cs index a3eb6f1c..f3ac1839 100644 --- a/Editor/CUIImageEditor.cs +++ b/Editor/CUIImageEditor.cs @@ -68,11 +68,7 @@ protected override void OnSceneGUI() Handles.color = Color.gray; EditorGUI.BeginChangeCheck(); -#if UNITY_2022_1_OR_NEWER Vector3 newCornerPos = Handles.FreeMoveHandle(script.transform.TransformPoint(cornerPos), HandleUtility.GetHandleSize(script.transform.TransformPoint(cornerPos)) / 7, Vector3.one, Handles.SphereHandleCap); -#else - Vector3 newCornerPos = Handles.FreeMoveHandle(script.transform.TransformPoint(cornerPos), script.transform.rotation, HandleUtility.GetHandleSize(script.transform.TransformPoint(cornerPos)) / 7, Vector3.one, Handles.SphereHandleCap); -#endif Handles.Label(newCornerPos, string.Format("Corner Mover")); diff --git a/Editor/CanvasGroupActivator.cs b/Editor/CanvasGroupActivator.cs index cd716573..99cb48e0 100644 --- a/Editor/CanvasGroupActivator.cs +++ b/Editor/CanvasGroupActivator.cs @@ -29,11 +29,7 @@ void OnFocus() void ObtainCanvasGroups() { -#if UNITY_2023_1_OR_NEWER - canvasGroups = GameObject.FindObjectsByType(FindObjectsSortMode.None); -#else - canvasGroups = GameObject.FindObjectsOfType(); -#endif + canvasGroups = GameObject.FindObjectsByType(FindObjectsSortMode.None); } void OnGUI() diff --git a/Editor/CustomRectTransformEditor.cs b/Editor/CustomRectTransformEditor.cs new file mode 100644 index 00000000..4c089740 --- /dev/null +++ b/Editor/CustomRectTransformEditor.cs @@ -0,0 +1,310 @@ +///Credit Dmitry (mitay-walle) +///Sourced from - https://github.com/mitay-walle/com.mitay-walle.rect-transform-editor + +using System; +using System.Reflection; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace Plugins.UI.Editor +{ + [CustomEditor(typeof(RectTransform), true), CanEditMultipleObjects] + public class CustomRectTransformEditor : UnityEditor.Editor + { + private const string NATIVE_EDITOR_TYPE = "UnityEditor.RectTransformEditor"; + private static PropertyInfo _rectDrivenObject = typeof(RectTransform).GetProperty("drivenByObject" + , BindingFlags.Public | BindingFlags.Instance); + private UnityEditor.Editor editorInstance; + private static Type nativeEditorType; + private MethodInfo onSceneGui; + private MethodInfo onDisable; + private Action drawAnchorsOnSceneViewDelegate; + + private Rect _rect = new Rect(45, 65, 45, 18); + private Rect _rect2 = new Rect(70, 25, 20, 18); + private Rect _rect3 = new Rect(70, 45, 20, 18); + private Rect _rect4 = new Rect(0, 65, 45, 18); + + private void OnEnable() + { + if (targets.Length == 0 || targets[0] == null) return; + + Initialize(); + if (nativeEditorType == null) return; + if (editorInstance == null) return; + + InitOnSceneGUIFix(); + ExecuteOnSceneGUIFix(); + try + { + if (editorInstance.targets.Length > 0 && editorInstance.targets[0] != null) + { + nativeEditorType.GetMethod("OnEnable", + BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic) + ?.Invoke(editorInstance, null); + } + } + catch + { + } + } + + private void Initialize() + { + if (nativeEditorType == null) + { + nativeEditorType = Assembly.GetAssembly(typeof(UnityEditor.Editor)).GetType(NATIVE_EDITOR_TYPE); + } + + if (editorInstance) + { + CreateCachedEditor(targets, nativeEditorType, ref editorInstance); + } + else + { + editorInstance = CreateEditor(targets, nativeEditorType); + } + } + + private void InitOnSceneGUIFix() + { + // fix recursive SceneView.duringSceneGui subscription, that affects performance + onSceneGui = nativeEditorType.GetMethod("OnSceneGUI", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + onDisable = nativeEditorType.GetMethod("OnDisable", BindingFlags.NonPublic | BindingFlags.Instance); + var method = nativeEditorType.GetMethod("DrawAnchorsOnSceneView", BindingFlags.NonPublic | BindingFlags.Instance); + drawAnchorsOnSceneViewDelegate = (Action)method.CreateDelegate(typeof(Action), editorInstance); + } + + private void ExecuteOnSceneGUIFix() + { + SceneView.duringSceneGui -= drawAnchorsOnSceneViewDelegate; + } + + public override void OnInspectorGUI() + { + editorInstance.OnInspectorGUI(); + + bool needMoveY = NeedMoveY(); + + Rect rect = _rect; + rect.y += needMoveY ? 20 : 0; + + // Code here + if (GUI.Button(rect, "Snap")) + { + foreach (Object targ in targets) + { + if (targ is RectTransform rectTr) + { + Undo.RecordObject(targ, "Snap to parent"); + SnapToParent(rectTr); + EditorUtility.SetDirty(targ); + } + } + } + + rect = _rect4; + rect.y += needMoveY ? 20 : 0; + + if (GUI.Button(rect, "New")) + { + foreach (Object targ in targets) + { + if (targ is RectTransform rectTr) + { + CreateEmptyParentRect(rectTr); + } + } + } + + if (targets.Length > 1) + { + GUI.enabled = false; + } + + rect = _rect2; + rect.y += needMoveY ? 20 : 0; + + if (GUI.Button(rect, "C")) + { + ComponentUtility.CopyComponent(target as RectTransform); + } + + GUI.enabled = true; + + rect = _rect3; + rect.y += needMoveY ? 20 : 0; + + if (GUI.Button(rect, "P")) + { + foreach (Object targ in targets) + { + Undo.RecordObject(targ, "Paste"); + ComponentUtility.PasteComponentValues(targ as RectTransform); + EditorUtility.SetDirty(targ); + } + } + } + + private void OnSceneGUI() + { + if (!(bool)target) + return; + + onSceneGui.Invoke(editorInstance, null); + } + + private void OnDisable() + { + SceneView.duringSceneGui -= drawAnchorsOnSceneViewDelegate; + if (editorInstance) + { + onDisable?.Invoke(editorInstance, null); + DestroyImmediate(editorInstance); + } + } + + private static void SnapToParent(RectTransform rect) + { + rect.pivot = new Vector2(.5f, .5f); + rect.localScale = Vector3.one; + rect.localPosition = Vector3.zero; + rect.localRotation = Quaternion.identity; + rect.anchoredPosition = Vector2.zero; + rect.anchorMin = Vector2.zero; + rect.anchorMax = Vector2.one; + rect.sizeDelta = Vector2.zero; + } + + public static void CreateEmptyParentRect(RectTransform rect) + { + GameObject go = new GameObject("Create Empty Parent"); + + ComponentUtility.CopyComponent(rect); + ComponentUtility.PasteComponentAsNew(go); + + Undo.RecordObject(rect, "Create Empty, Reparent"); + + Undo.RegisterCreatedObjectUndo(go, "Create Empty Parent"); + + RectTransform rect2 = go.transform as RectTransform; + + PlaceSameAs(go.transform, rect, true, true, true); + ComponentUtility.PasteComponentValues(rect2); + Undo.SetTransformParent(rect, go.transform, "Create Empty, Reparent"); + SnapToParent(rect); + + EditorUtility.SetDirty(rect); + + Selection.activeGameObject = go; + EditorGUIUtility.PingObject(go); + } + + private static void PlaceSameAs(Transform target, Transform source, bool copyName = false, bool undo = false, + bool setDirty = false) + { + if (!source) return; + + if (undo && Application.isPlaying) + { + Undo.RecordObject(target, "PlaceSameAs"); + if (copyName) Undo.RecordObject(target.gameObject, "PlaceSameAs"); + } + + VectorArray matrix = new VectorArray(source, false); + matrix.Apply(target); + if (undo) + { + Undo.SetTransformParent(target, source.parent, "PlaceSameAs"); + } + else + { + target.SetParent(source.parent); + } + + target.SetSiblingIndex(source.GetSiblingIndex()); + + if (copyName) target.name = source.name; + + if (setDirty && Application.isPlaying) + { + EditorUtility.SetDirty(target); + if (copyName) EditorUtility.SetDirty(target.gameObject); + } + } + + // ReSharper disable Unity.PerformanceAnalysis + private bool NeedMoveY() + { + bool value = false; + foreach (RectTransform target in targets) + { + if (NeedMoveY(target)) + { + value = true; + break; + } + } + + return value; + } + + private bool NeedMoveY(RectTransform target) => _rectDrivenObject.GetValue(target) != null; + } + + [Serializable] + public struct VectorArray + { + [SerializeField] public bool local; + [SerializeField] public Vector3 Pos; + [SerializeField] public Quaternion Rot; + [SerializeField] public Vector3 Scale; + + public VectorArray(Transform tr, bool local) + { + this.local = local; + if (!tr) + { + Pos = default; + Rot = default; + Scale = default; + } + else + { + Scale = local ? tr.localScale : tr.lossyScale; + + if (local) + { + Pos = tr.localPosition; + Rot = tr.localRotation; + } + else + { + Pos = tr.position; + Rot = tr.rotation; + } + } + } + + public void Apply(Transform tr) + { + if (local) + { + tr.localPosition = Pos; + tr.localRotation = Rot; + } + else + { + tr.position = Pos; + tr.rotation = Rot; + } + + tr.localScale = Scale; +#if UNITY_EDITOR + if (!Application.isPlaying) EditorUtility.SetDirty(tr); +#endif + } + } +} diff --git a/Editor/CustomRectTransformEditor.cs.meta b/Editor/CustomRectTransformEditor.cs.meta new file mode 100644 index 00000000..43607d6f --- /dev/null +++ b/Editor/CustomRectTransformEditor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3692129da900a5648a912045e21f7531 \ No newline at end of file diff --git a/Editor/GridRawImage.meta b/Editor/GridRawImage.meta new file mode 100644 index 00000000..14a45e56 --- /dev/null +++ b/Editor/GridRawImage.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a28f78e67e4a86346b60c99c3419bddf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/GridRawImage/GridRawImageEditor.cs b/Editor/GridRawImage/GridRawImageEditor.cs new file mode 100644 index 00000000..1e2148d5 --- /dev/null +++ b/Editor/GridRawImage/GridRawImageEditor.cs @@ -0,0 +1,46 @@ +///Credit Dmitry (mitay-walle) +///Sourced from - https://github.com/mitay-walle/com.mitay-walle.grid-raw-image + +using System.Linq; +using UnityEditor; + +namespace UnityEngine.UI.Extensions +{ + [CustomEditor(typeof(GridRawImage))] + public class GridRawImageEditor : UnityEditor.Editor + { + public override void OnInspectorGUI() + { + serializedObject.UpdateIfRequiredOrScript(); + EditorGUI.BeginChangeCheck(); + DrawPropertiesExcludingCustom(serializedObject, "m_OnCullStateChanged", "m_RaycastPadding"); + if (EditorGUI.EndChangeCheck()) + { + serializedObject.ApplyModifiedProperties(); + } + } + + private static void DrawPropertiesExcludingCustom(SerializedObject obj, params string[] propertyToExclude) + { + SerializedProperty iterator = obj.GetIterator(); + bool enterChildren = true; + while (iterator.NextVisible(enterChildren)) + { + enterChildren = false; + if (!propertyToExclude.Contains(iterator.name)) + { + if (iterator.name == "m_Script") + { + GUI.enabled = false; + } + EditorGUILayout.PropertyField(iterator, true); + + if (iterator.name == "m_Script") + { + GUI.enabled = true; + } + } + } + } + } +} \ No newline at end of file diff --git a/Editor/GridRawImage/GridRawImageEditor.cs.meta b/Editor/GridRawImage/GridRawImageEditor.cs.meta new file mode 100644 index 00000000..b224d169 --- /dev/null +++ b/Editor/GridRawImage/GridRawImageEditor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: eb6c9d572fcbc4344b99dfa12a050bc4 \ No newline at end of file diff --git a/Editor/GridRawImage/GridShapeDrawer.cs b/Editor/GridRawImage/GridShapeDrawer.cs new file mode 100644 index 00000000..e2cc6561 --- /dev/null +++ b/Editor/GridRawImage/GridShapeDrawer.cs @@ -0,0 +1,59 @@ +///Credit Dmitry (mitay-walle) +///Sourced from - https://github.com/mitay-walle/com.mitay-walle.grid-raw-image + +using UnityEditor; + +namespace UnityEngine.UI.Extensions +{ + [CustomPropertyDrawer(typeof(GridShape))] + public class GridShapeDrawer : PropertyDrawer + { + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) => 0; + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + EditorGUILayout.PropertyField(property); + + //property.isExpanded = EditorGUILayout.BeginFoldoutHeaderGroup(property.isExpanded, label); + if (property.isExpanded) + { + EditorGUI.indentLevel += 2; + SerializedProperty size = property.FindPropertyRelative("_size"); + SerializedProperty prop = property.FindPropertyRelative("_bitArray"); + // EditorGUILayout.PropertyField(property.FindPropertyRelative("_readable")); + // EditorGUILayout.PropertyField(size); + + EditorGUI.BeginChangeCheck(); + + BitArray256 bitArray = (BitArray256)prop.boxedValue; + float last = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = 0; + for (int j = size.vector2IntValue.y - 1; j >= 0; j--) + { + GUILayout.BeginHorizontal(); + + for (int i = 0; i < size.vector2IntValue.x; i++) + { + int index = j * size.vector2IntValue.x + i; + bool value = bitArray[(uint)index]; + bitArray[(uint)index] = EditorGUILayout.Toggle(GUIContent.none, value, GUILayout.Width(20)); + } + GUILayout.EndHorizontal(); + } + + EditorGUIUtility.labelWidth = last; + if (EditorGUI.EndChangeCheck()) + { + foreach (Object targetObject in property.serializedObject.targetObjects) + { + Undo.RecordObject(targetObject, "GridShape flags"); + prop.boxedValue = bitArray; + } + } + EditorGUI.indentLevel -= 2; + } + + EditorGUILayout.EndFoldoutHeaderGroup(); + } + } +} diff --git a/Editor/GridRawImage/GridShapeDrawer.cs.meta b/Editor/GridRawImage/GridShapeDrawer.cs.meta new file mode 100644 index 00000000..a47d5aef --- /dev/null +++ b/Editor/GridRawImage/GridShapeDrawer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f6707ceb1aef81748beddf67fa73c2b6 \ No newline at end of file diff --git a/Editor/UIExtensionsMenuOptions.cs b/Editor/UIExtensionsMenuOptions.cs index ac4bbe42..52c8b6cd 100644 --- a/Editor/UIExtensionsMenuOptions.cs +++ b/Editor/UIExtensionsMenuOptions.cs @@ -1,18 +1,16 @@ -#if UNITY_2019_1_OR_NEWER && !ENABLE_LEGACY_INPUT_MANAGER +#if !ENABLE_LEGACY_INPUT_MANAGER #define NEW_INPUT_SYSTEM #endif using TMPro; -using UnityEngine; +using UnityEditor; using UnityEngine.EventSystems; -using UnityEngine.UI; -using UnityEngine.UI.Extensions; #if NEW_INPUT_SYSTEM using UnityEngine.InputSystem.UI; #endif -namespace UnityEditor.UI +namespace UnityEngine.UI.Extensions { /// /// This script adds the Extensions UI menu options to the Unity Editor. @@ -159,11 +157,7 @@ private static void CreateEventSystem(bool select) private static void CreateEventSystem(bool select, GameObject parent) { -#if UNITY_2023_1_OR_NEWER var esys = Object.FindFirstObjectByType(); -#else - var esys = Object.FindObjectOfType(); -#endif if (esys == null) { var eventSystem = new GameObject("EventSystem"); @@ -195,11 +189,7 @@ static public GameObject GetOrCreateCanvasGameObject() return canvas.gameObject; // No canvas in selection or its parents? Then use just any canvas.. -#if UNITY_2023_1_OR_NEWER canvas = Object.FindFirstObjectByType(); -#else - canvas = Object.FindObjectOfType(typeof(Canvas)) as Canvas; -#endif if (canvas != null && canvas.gameObject.activeInHierarchy) return canvas.gameObject; @@ -677,91 +667,91 @@ static public void AddUIVerticallScroller(MenuCommand menuCommand) Selection.activeGameObject = uiVerticalScrollerRoot; } - #endregion - - #region UIHorizontal Scroller - [MenuItem("GameObject/UI/Extensions/Layout/UI Horizontal Scroller", false)] - static public void AddUIHorizontalScroller(MenuCommand menuCommand) - { - GameObject uiHorizontalScrollerRoot = CreateUIElementRoot("UI Horizontal Scroller", menuCommand, s_ThickGUIElementSize); - - GameObject uiScrollerCenter = CreateUIObject("Center", uiHorizontalScrollerRoot); - - GameObject childContent = CreateUIObject("Content", uiHorizontalScrollerRoot); - - // Set RectTransform to stretch - RectTransform rectTransformScrollSnapRoot = uiHorizontalScrollerRoot.GetComponent(); - rectTransformScrollSnapRoot.anchorMin = new Vector2(0.5f, 0.5f); - rectTransformScrollSnapRoot.anchorMax = new Vector2(0.5f, 0.5f); - rectTransformScrollSnapRoot.anchoredPosition = Vector2.zero; - rectTransformScrollSnapRoot.sizeDelta = new Vector2(500f, 150f); - - // Add required ScrollRect - ScrollRect sr = uiHorizontalScrollerRoot.AddComponent(); - sr.vertical = false; - sr.horizontal = true; - sr.movementType = ScrollRect.MovementType.Unrestricted; - var uiscr = uiHorizontalScrollerRoot.AddComponent(); - - //Setup container center point - RectTransform rectTransformCenter = uiScrollerCenter.GetComponent(); - rectTransformCenter.anchorMin = new Vector2(0.3f,0f); - rectTransformCenter.anchorMax = new Vector2(0.6f,1f); - rectTransformCenter.sizeDelta = Vector2.zero; - - uiscr.Center = uiScrollerCenter.GetComponent(); - - //Setup Content container - RectTransform rectTransformContent = childContent.GetComponent(); - rectTransformContent.anchorMin = Vector2.zero; - rectTransformContent.anchorMax = new Vector2(1f, 1f); - rectTransformContent.sizeDelta = Vector2.zero; - - sr.content = rectTransformContent; - - // Add sample children - for (int i = 0; i < 10; i++) - { - GameObject childPage = CreateUIObject("Page_" + i, childContent); - - GameObject childText = CreateUIObject("Text", childPage); - - //Setup 1st Child - Image pageImage = childPage.AddComponent(); - pageImage.sprite = AssetDatabase.GetBuiltinExtraResource(kStandardSpritePath); - pageImage.type = Image.Type.Sliced; - pageImage.color = s_DefaultSelectableColor; - - RectTransform rectTransformPage = childPage.GetComponent(); - rectTransformPage.anchorMin = new Vector2(0.5f, 0); - rectTransformPage.anchorMax = new Vector2(0.5f, 1f); - rectTransformPage.sizeDelta = new Vector2(80f, 0f); - rectTransformPage.pivot = new Vector2(0.5f, 0.5f); - rectTransformPage.localPosition = new Vector3(80 * i, 0, 0); - childPage.AddComponent using System; using System.Collections.Generic; +using System.Buffers; +using UnityEngine.Pool; namespace UnityEngine.UI.Extensions { @@ -36,6 +38,9 @@ public class Gradient2 : BaseMeshEffect [SerializeField] UnityEngine.Gradient _effectGradient = new UnityEngine.Gradient() { colorKeys = new GradientColorKey[] { new GradientColorKey(Color.black, 0), new GradientColorKey(Color.white, 1) } }; + private GradientColorKey[] _colorKeys; + private GradientAlphaKey[] _alphaKeys; + #region Properties public Blend BlendMode { @@ -103,7 +108,7 @@ public override void ModifyMesh(VertexHelper helper) if (!IsActive() || helper.currentVertCount == 0) return; - List _vertexList = new List(); + List _vertexList = ListPool.Get(); helper.GetUIVertexStream(_vertexList); @@ -218,7 +223,7 @@ public override void ModifyMesh(VertexHelper helper) helper.AddVert(centralVertex); - for (int i = 1; i < steps; i++) helper.AddTriangle(i - 1, i, steps); + for (int i = 1; i < steps; i++) helper.AddTriangle(i, i - 1, steps); helper.AddTriangle(0, steps - 1, steps); } @@ -238,6 +243,8 @@ public override void ModifyMesh(VertexHelper helper) } break; } + + ListPool.Release(_vertexList); } Rect GetBounds(List vertices) @@ -264,7 +271,7 @@ Rect GetBounds(List vertices) void SplitTrianglesAtGradientStops(List _vertexList, Rect bounds, float zoomOffset, VertexHelper helper) { - List stops = FindStops(zoomOffset, bounds); + List stops = FindStops(zoomOffset, bounds, ListPool.Get()); if (stops.Count > 0) { helper.Clear(); @@ -272,10 +279,13 @@ void SplitTrianglesAtGradientStops(List _vertexList, Rect bounds, floa int nCount = _vertexList.Count; for (int i = 0; i < nCount; i += 3) { - float[] positions = GetPositions(_vertexList, i); - List originIndices = new List(3); - List starts = new List(3); - List ends = new List(2); + var positions = ArrayPool.Shared.Rent(3); + + GetPositions(_vertexList, i, ref positions); + + List originIndices = ListPool.Get(); + List starts = ListPool.Get(); + List ends = ListPool.Get(); for (int s = 0; s < stops.Count; s++) { @@ -403,13 +413,18 @@ void SplitTrianglesAtGradientStops(List _vertexList, Rect bounds, floa int vertexCount = helper.currentVertCount; helper.AddTriangle(vertexCount - 3, vertexCount - 2, vertexCount - 1); } + + ArrayPool.Shared.Return(positions); + ListPool.Release(originIndices); + ListPool.Release(starts); + ListPool.Release(ends); } } + ListPool.Release(stops); } - float[] GetPositions(List _vertexList, int index) + void GetPositions(List _vertexList, int index, ref float[] positions) { - float[] positions = new float[3]; if (GradientType == Type.Horizontal) { positions[0] = _vertexList[index].position.x; @@ -422,24 +437,27 @@ float[] GetPositions(List _vertexList, int index) positions[1] = _vertexList[index + 1].position.y; positions[2] = _vertexList[index + 2].position.y; } - return positions; } - List FindStops(float zoomOffset, Rect bounds) + List FindStops(float zoomOffset, Rect bounds, List stops) { - List stops = new List(); var offset = Offset * (1 - zoomOffset); var startBoundary = zoomOffset - offset; var endBoundary = (1 - zoomOffset) - offset; - foreach (var color in EffectGradient.colorKeys) + _colorKeys = EffectGradient.colorKeys; + + foreach (var color in _colorKeys) { if (color.time >= endBoundary) break; if (color.time > startBoundary) stops.Add((color.time - startBoundary) * Zoom); } - foreach (var alpha in EffectGradient.alphaKeys) + + _alphaKeys = _effectGradient.alphaKeys; + + foreach (var alpha in _alphaKeys) { if (alpha.time >= endBoundary) break; @@ -455,7 +473,13 @@ List FindStops(float zoomOffset, Rect bounds) size = bounds.height; } - stops.Sort(); + stops.Sort((x, y) => + { + if (x > y) return 1; + if (x == y) return 0; + return -1; + }); + for (int i = 0; i < stops.Count; i++) { stops[i] = (stops[i] * size) + min; diff --git a/Runtime/Scripts/Effects/LetterSpacing.cs b/Runtime/Scripts/Effects/LetterSpacing.cs deleted file mode 100644 index ce5e2b29..00000000 --- a/Runtime/Scripts/Effects/LetterSpacing.cs +++ /dev/null @@ -1,187 +0,0 @@ -/* - -Produces an simple tracking/letter-spacing effect on UI Text components. - -Set the spacing parameter to adjust letter spacing. - Negative values cuddle the text up tighter than normal. Go too far and it'll look odd. - Positive values spread the text out more than normal. This will NOT respect the text area you've defined. - Zero spacing will present the font with no changes. - -Relies on counting off characters in your Text component's text property and -matching those against the quads passed in via the verts array. This is really -rather primitive, but I can't see any better way at the moment. It means that -all sorts of things can break the effect... - -This component should be placed higher in component list than any other vertex -modifiers that alter the total number of vertices. EG, place this above Shadow -or Outline effects. If you don't, the outline/shadow won't match the position -of the letters properly. If you place the outline/shadow effect second however, -it will just work on the altered vertices from this component, and function -as expected. - -This component works best if you don't allow text to automatically wrap. It also -blows up outside of the given text area. Basically, it's a cheap and dirty effect, -not a clever text layout engine. It can't affect how Unity chooses to break up -your lines. If you manually use line breaks however, it should detect those and -function more or less as you'd expect. - -The spacing parameter is measured in pixels multiplied by the font size. This was -chosen such that when you adjust the font size, it does not change the visual spacing -that you've dialed in. There's also a scale factor of 1/100 in this number to -bring it into a comfortable adjustable range. There's no limit on this parameter, -but obviously some values will look quite strange. - -This component doesn't really work with Rich Text. You don't need to remember to -turn off Rich Text via the checkbox, but because it can't see what makes a -printable character and what doesn't, it will typically miscount characters when you -use HTML-like tags in your text. Try it out, you'll see what I mean. It doesn't -break down entirely, but it doesn't really do what you'd want either. - -*/ -#if !UNITY_2022_1_OR_NEWER -using System.Collections.Generic; -#endif - -using System; - -/// Credit Deeperbeige -/// Sourced from - http://forum.unity3d.com/threads/adjustable-character-spacing-free-script.288277/ -namespace UnityEngine.UI.Extensions -{ -#if UNITY_2022_1_OR_NEWER - [Obsolete("LetterSpacing is not supported in Unity 2022.1 or newer. Use TMPro instead.")] - public class LetterSpacing : BaseMeshEffect - { - public override void ModifyMesh(VertexHelper vh) - { - } - } -#else - [AddComponentMenu("UI/Effects/Extensions/Letter Spacing")] - ///Summary - /// Note, Vertex Count has changed in 5.2.1+, is now 6 (two tris) instead of 4 (tri strip). - public class LetterSpacing : BaseMeshEffect - { - [SerializeField] - private float m_spacing = 0f; - - protected LetterSpacing() { } - -#if UNITY_EDITOR - protected override void OnValidate() - { - spacing = m_spacing; - base.OnValidate(); - } -#endif - - public float spacing - { - get { return m_spacing; } - set - { - if (m_spacing == value) return; - m_spacing = value; - if (graphic != null) graphic.SetVerticesDirty(); - } - } - - protected override void Awake() - { - Debug.Log($"Awake, [{m_spacing}]"); - } - - public override void ModifyMesh(VertexHelper vh) - { - if (! IsActive()) return; - - List verts = new List(); - vh.GetUIVertexStream(verts); - - var text = GetComponent(); - - if (text == null) - { - Debug.LogWarning("LetterSpacing: Missing Text component"); - return; - } - - string[] lines = text.text.Split('\n'); - Vector3 pos; - float letterOffset = spacing * (float)text.fontSize / 100f; - float alignmentFactor = 0; - int glyphIdx = 0; - - switch (text.alignment) - { - case TextAnchor.LowerLeft: - case TextAnchor.MiddleLeft: - case TextAnchor.UpperLeft: - alignmentFactor = 0f; - break; - - case TextAnchor.LowerCenter: - case TextAnchor.MiddleCenter: - case TextAnchor.UpperCenter: - alignmentFactor = 0.5f; - break; - - case TextAnchor.LowerRight: - case TextAnchor.MiddleRight: - case TextAnchor.UpperRight: - alignmentFactor = 1f; - break; - } - - for (int lineIdx=0; lineIdx < lines.Length; lineIdx++) - { - string line = lines[lineIdx]; - float lineOffset = (line.Length -1) * letterOffset * alignmentFactor; - - for (int charIdx = 0; charIdx < line.Length; charIdx++) - { - int idx1 = glyphIdx * 6 + 0; - int idx2 = glyphIdx * 6 + 1; - int idx3 = glyphIdx * 6 + 2; - int idx4 = glyphIdx * 6 + 3; - int idx5 = glyphIdx * 6 + 4; - int idx6 = glyphIdx * 6 + 5; - - // Check for truncated text (doesn't generate verts for all characters) - if (idx6 > verts.Count - 1) return; - - UIVertex vert1 = verts[idx1]; - UIVertex vert2 = verts[idx2]; - UIVertex vert3 = verts[idx3]; - UIVertex vert4 = verts[idx4]; - UIVertex vert5 = verts[idx5]; - UIVertex vert6 = verts[idx6]; - - pos = Vector3.right * (letterOffset * charIdx - lineOffset); - - vert1.position += pos; - vert2.position += pos; - vert3.position += pos; - vert4.position += pos; - vert5.position += pos; - vert6.position += pos; - - verts[idx1] = vert1; - verts[idx2] = vert2; - verts[idx3] = vert3; - verts[idx4] = vert4; - verts[idx5] = vert5; - verts[idx6] = vert6; - - glyphIdx++; - } - - // Offset for carriage return character that still generates verts - glyphIdx++; - } - vh.Clear(); - vh.AddUIVertexTriangleStream(verts); - } - } -#endif -} \ No newline at end of file diff --git a/Runtime/Scripts/Effects/LetterSpacing.cs.meta b/Runtime/Scripts/Effects/LetterSpacing.cs.meta deleted file mode 100644 index 3de1972a..00000000 --- a/Runtime/Scripts/Effects/LetterSpacing.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 8ee10f5b9a0e16c40b25e079c03a17a2 -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Runtime/Scripts/Effects/MonoSpacing.cs b/Runtime/Scripts/Effects/MonoSpacing.cs deleted file mode 100644 index ab70127a..00000000 --- a/Runtime/Scripts/Effects/MonoSpacing.cs +++ /dev/null @@ -1,205 +0,0 @@ -/* - -Produces an simple mono-spacing effect on UI Text components. - -Set the spacing parameter to adjust mono spacing. - Negative values cuddle the text up tighter than normal. Go too far and it'll look odd. - Positive values spread the text out more than normal. This will NOT respect the text area you've defined. - Zero spacing will present the font with no changes. - -Relies on counting off characters in your Text component's text property and -matching those against the quads passed in via the verts array. This is really -rather primitive, but I can't see any better way at the moment. It means that -all sorts of things can break the effect... - -This component should be placed higher in component list than any other vertex -modifiers that alter the total number of vertices. EG, place this above Shadow -or Outline effects. If you don't, the outline/shadow won't match the position -of the letters properly. If you place the outline/shadow effect second however, -it will just work on the altered vertices from this component, and function -as expected. - -This component works best if you don't allow text to automatically wrap. It also -blows up outside of the given text area. Basically, it's a cheap and dirty effect, -not a clever text layout engine. It can't affect how Unity chooses to break up -your lines. If you manually use line breaks however, it should detect those and -function more or less as you'd expect. - -The spacing parameter is measured in pixels multiplied by the font size. This was -chosen such that when you adjust the font size, it does not change the visual spacing -that you've dialed in. There's also a scale factor of 1/100 in this number to -bring it into a comfortable adjustable range. There's no limit on this parameter, -but obviously some values will look quite strange. - -This component doesn't really work with Rich Text. You don't need to remember to -turn off Rich Text via the checkbox, but because it can't see what makes a -printable character and what doesn't, it will typically miscount characters when you -use HTML-like tags in your text. Try it out, you'll see what I mean. It doesn't -break down entirely, but it doesn't really do what you'd want either. - -*/ -#if !UNITY_2022_1_OR_NEWER -using System.Collections.Generic; -#endif - -using System; - -/// Credit herbst / derived from LetterSpacing by Deeperbeige -/// Sourced from - http://forum.unity3d.com/threads/adjustable-character-spacing-free-script.288277/ -namespace UnityEngine.UI.Extensions -{ -#if UNITY_2022_1_OR_NEWER - [Obsolete("MonoSpacing is not supported in Unity 2022.1 or newer. Use TMPro instead.")] - public class MonoSpacing : BaseMeshEffect - { - public override void ModifyMesh(VertexHelper vh) - { - } - } -#else - [AddComponentMenu("UI/Effects/Extensions/Mono Spacing")] - [RequireComponent(typeof(Text))] - [RequireComponent(typeof(RectTransform))] - ///Summary - /// Note, Vertex Count has changed in 5.2.1+, is now 6 (two tris) instead of 4 (tri strip). - public class MonoSpacing : BaseMeshEffect - { - [SerializeField] - private float m_spacing = 0f; - public float HalfCharWidth = 1; - public bool UseHalfCharWidth = false; - - private RectTransform rectTransform; - - private Text text; - - protected MonoSpacing() { } - - protected override void Awake() - { - text = GetComponent(); - - if (text == null) - { - Debug.LogWarning("MonoSpacing: Missing Text component"); - return; - } - rectTransform = text.GetComponent(); - } - -#if UNITY_EDITOR - protected override void OnValidate() - { - Spacing = m_spacing; - base.OnValidate(); - } -#endif - - public float Spacing - { - get { return m_spacing; } - set - { - if (m_spacing == value) return; - m_spacing = value; - if (graphic != null) graphic.SetVerticesDirty(); - } - } - - public override void ModifyMesh(VertexHelper vh) - { - if (! IsActive()) return; - - List verts = new List(); - vh.GetUIVertexStream(verts); - - string[] lines = text.text.Split('\n'); - // Vector3 pos; - float letterOffset = Spacing * (float)text.fontSize / 100f; - float alignmentFactor = 0; - int glyphIdx = 0; - - switch (text.alignment) - { - case TextAnchor.LowerLeft: - case TextAnchor.MiddleLeft: - case TextAnchor.UpperLeft: - alignmentFactor = 0f; - break; - - case TextAnchor.LowerCenter: - case TextAnchor.MiddleCenter: - case TextAnchor.UpperCenter: - alignmentFactor = 0.5f; - break; - - case TextAnchor.LowerRight: - case TextAnchor.MiddleRight: - case TextAnchor.UpperRight: - alignmentFactor = 1f; - break; - } - - for (int lineIdx=0; lineIdx < lines.Length; lineIdx++) - { - string line = lines[lineIdx]; - float lineOffset = (line.Length - 1) * letterOffset * (alignmentFactor) - (alignmentFactor - 0.5f) * rectTransform.rect.width; - - var offsetX = -lineOffset + letterOffset / 2 * (1 - alignmentFactor * 2); - - for (int charIdx = 0; charIdx < line.Length; charIdx++) - { - int idx1 = glyphIdx * 6 + 0; - int idx2 = glyphIdx * 6 + 1; - int idx3 = glyphIdx * 6 + 2; - int idx4 = glyphIdx * 6 + 3; - int idx5 = glyphIdx * 6 + 4; - int idx6 = glyphIdx * 6 + 5; - - // Check for truncated text (doesn't generate verts for all characters) - if (idx6 > verts.Count - 1) return; - - UIVertex vert1 = verts[idx1]; - UIVertex vert2 = verts[idx2]; - UIVertex vert3 = verts[idx3]; - UIVertex vert4 = verts[idx4]; - UIVertex vert5 = verts[idx5]; - UIVertex vert6 = verts[idx6]; - - // pos = Vector3.right * (letterOffset * (charIdx) - lineOffset); - float charWidth = (vert2.position - vert1.position).x; - var smallChar = UseHalfCharWidth && (charWidth < HalfCharWidth); - - var smallCharOffset = smallChar ? -letterOffset/4 : 0; - - vert1.position += new Vector3(-vert1.position.x + offsetX + -.5f * charWidth + smallCharOffset, 0, 0); - vert2.position += new Vector3(-vert2.position.x + offsetX + .5f * charWidth + smallCharOffset, 0, 0); - vert3.position += new Vector3(-vert3.position.x + offsetX + .5f * charWidth + smallCharOffset, 0, 0); - vert4.position += new Vector3(-vert4.position.x + offsetX + .5f * charWidth + smallCharOffset, 0, 0); - vert5.position += new Vector3(-vert5.position.x + offsetX + -.5f * charWidth + smallCharOffset, 0, 0); - vert6.position += new Vector3(-vert6.position.x + offsetX + -.5f * charWidth + smallCharOffset, 0, 0); - - if (smallChar) - offsetX += letterOffset / 2; - else - offsetX += letterOffset; - - verts[idx1] = vert1; - verts[idx2] = vert2; - verts[idx3] = vert3; - verts[idx4] = vert4; - verts[idx5] = vert5; - verts[idx6] = vert6; - - glyphIdx++; - } - - // Offset for carriage return character that still generates verts - glyphIdx++; - } - vh.Clear(); - vh.AddUIVertexTriangleStream(verts); - } - } -#endif - } \ No newline at end of file diff --git a/Runtime/Scripts/Effects/NicerOutline.cs b/Runtime/Scripts/Effects/NicerOutline.cs deleted file mode 100644 index 8c0d1c81..00000000 --- a/Runtime/Scripts/Effects/NicerOutline.cs +++ /dev/null @@ -1,211 +0,0 @@ - -#if !UNITY_2022_1_OR_NEWER -using System.Collections.Generic; -#endif - -using System; - -/// Credit Melang, Lee Hui -/// Sourced from - http://forum.unity3d.com/members/melang.593409/ -/// GC Alloc fix - https://bitbucket.org/UnityUIExtensions/unity-ui-extensions/pull-requests/130 -/// NOT supported in Unity 2022 -namespace UnityEngine.UI.Extensions -{ -#if UNITY_2022_1_OR_NEWER - [Obsolete("BestFitOutline is not supported in Unity 2022.1 or newer. Use TMPro instead.")] - public class NicerOutline : BaseMeshEffect - { - public override void ModifyMesh(VertexHelper vh) - { - } - } -#else - //An outline that looks a bit nicer than the default one. It has less "holes" in the outline by drawing more copies of the effect - [AddComponentMenu("UI/Effects/Extensions/Nicer Outline")] - public class NicerOutline : BaseMeshEffect - { - [SerializeField] - private Color m_EffectColor = new Color (0f, 0f, 0f, 0.5f); - - [SerializeField] - private Vector2 m_EffectDistance = new Vector2 (1f, -1f); - - [SerializeField] - private bool m_UseGraphicAlpha = true; - - private List < UIVertex > m_Verts = new List(); - - // - // Properties - // - public Color effectColor - { - get - { - return this.m_EffectColor; - } - set - { - this.m_EffectColor = value; - if (base.graphic != null) - { - base.graphic.SetVerticesDirty (); - } - } - } - - public Vector2 effectDistance - { - get - { - return this.m_EffectDistance; - } - set - { - if (value.x > 600f) - { - value.x = 600f; - } - if (value.x < -600f) - { - value.x = -600f; - } - if (value.y > 600f) - { - value.y = 600f; - } - if (value.y < -600f) - { - value.y = -600f; - } - if (this.m_EffectDistance == value) - { - return; - } - this.m_EffectDistance = value; - if (base.graphic != null) - { - base.graphic.SetVerticesDirty (); - } - } - } - - public bool useGraphicAlpha - { - get - { - return this.m_UseGraphicAlpha; - } - set - { - this.m_UseGraphicAlpha = value; - if (base.graphic != null) - { - base.graphic.SetVerticesDirty (); - } - } - } - - public override void ModifyMesh(VertexHelper vh) - { - if (!this.IsActive ()) - { - return; - } - - m_Verts.Clear(); - vh.GetUIVertexStream(m_Verts); - - Text foundtext = GetComponent(); - - float best_fit_adjustment = 1f; - - if (foundtext && foundtext.resizeTextForBestFit) - { - best_fit_adjustment = (float)foundtext.cachedTextGenerator.fontSizeUsedForBestFit / (foundtext.resizeTextMaxSize-1); //max size seems to be exclusive - - } - - float distanceX = this.effectDistance.x * best_fit_adjustment; - float distanceY = this.effectDistance.y * best_fit_adjustment; - - vh.Clear(); - - int start = 0; - - // Apply Outline - start += this.ApplyOutlineNoGC(m_Verts, this.effectColor, distanceX, distanceY, vh, start); - start += this.ApplyOutlineNoGC(m_Verts, this.effectColor, distanceX, -distanceY, vh, start); - start += this.ApplyOutlineNoGC(m_Verts, this.effectColor, -distanceX, distanceY, vh, start); - start += this.ApplyOutlineNoGC(m_Verts, this.effectColor, -distanceX, -distanceY, vh, start); - start += this.ApplyOutlineNoGC(m_Verts, this.effectColor, distanceX, 0, vh, start); - start += this.ApplyOutlineNoGC(m_Verts, this.effectColor, -distanceX, 0, vh, start); - start += this.ApplyOutlineNoGC(m_Verts, this.effectColor, 0, distanceY, vh, start); - start += this.ApplyOutlineNoGC(m_Verts, this.effectColor, 0, -distanceY, vh, start); - - // Apply self Text stuff - start += ApplyText(m_Verts, vh, start); - } - - private int ApplyOutlineNoGC(List verts, Color32 color, float x, float y, VertexHelper vh, int startIndex) - { - int length = verts.Count; - for (int i = 0; i < length; ++i) - { - UIVertex vt = verts[i]; - - Vector3 v = vt.position; - v.x += x; - v.y += y; - vt.position = v; - var newColor = color; - if (m_UseGraphicAlpha) - newColor.a = (byte)((newColor.a * verts[i].color.a) / 255); - vt.color = newColor; - - // Tips: Since two triangles share same two vertices, in theory vertices can reduce to 4 / 6 - // But VertexHelper.FillMesh forbid, so leave it be. - - vh.AddVert(vt); - } - - int triangleCount = length / 3; - for(int i=0; i verts, VertexHelper vh, int startIndex) - { - int length = verts.Count; - - for (int i = 0; i < length; ++i) - { - vh.AddVert(verts[i]); - } - - int triangleCount = length / 3; - for (int i = 0; i < triangleCount; ++i) - { - int start = startIndex + 3 * i; - vh.AddTriangle(start + 0, start + 1, start + 2); - } - - return length; - } - - -#if UNITY_EDITOR - protected override void OnValidate () - { - this.effectDistance = this.m_EffectDistance; - base.OnValidate (); - } -#endif - } -#endif - } \ No newline at end of file diff --git a/Runtime/Scripts/Effects/NicerOutline.cs.meta b/Runtime/Scripts/Effects/NicerOutline.cs.meta deleted file mode 100644 index ff5558e2..00000000 --- a/Runtime/Scripts/Effects/NicerOutline.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: db125c7de00668f4e98849d0aaf366d7 -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Runtime/Scripts/Effects/SoftMaskScript.cs b/Runtime/Scripts/Effects/SoftMaskScript.cs index 2fd650e6..fbd86476 100644 --- a/Runtime/Scripts/Effects/SoftMaskScript.cs +++ b/Runtime/Scripts/Effects/SoftMaskScript.cs @@ -47,11 +47,7 @@ void Start() MaskArea = GetComponent(); } -#if UNITY_2022_1_OR_NEWER var text = GetComponent(); -#else - var text = GetComponent(); -#endif if (text != null) { mat = new Material(ShaderLibrary.GetShaderInstance("UI Extensions/SoftMaskShader")); diff --git a/Runtime/Scripts/Effects/UIParticleSystem.cs b/Runtime/Scripts/Effects/UIParticleSystem.cs index a004b7b4..68e5821a 100644 --- a/Runtime/Scripts/Effects/UIParticleSystem.cs +++ b/Runtime/Scripts/Effects/UIParticleSystem.cs @@ -5,6 +5,25 @@ namespace UnityEngine.UI.Extensions { #if UNITY_5_3_OR_NEWER + /// + /// Controls how the advances its simulation, in particular how it + /// handles a large time-step such as the one produced when the application returns from the + /// background. Mirrors the naming of Unity's ParticleSystemCullingMode options. For this manual + /// simulation the four options collapse to two behaviours: Automatic/Pause clamp the step + /// (freeze-safe), while PauseAndCatchup/AlwaysSimulate replay the full elapsed time. + /// + public enum UIParticleSystemCullingMode + { + /// Default. Simulates continuously and clamps a catastrophic catch-up (e.g. returning from the background) to maxSimulationDeltaTime so it cannot freeze the app (issue #486). Mirrors Unity's default culling mode. + Automatic, + /// Clamp the simulation step (see maxSimulationDeltaTime); behaves the same as Automatic for this manual simulation. + Pause, + /// Replay the full elapsed time on the next frame. Original behaviour - can freeze the app after a long pause (issue #486). + PauseAndCatchup, + /// Replay the full elapsed time; behaves the same as PauseAndCatchup for this manual simulation. + AlwaysSimulate + } + [ExecuteInEditMode] [RequireComponent(typeof(CanvasRenderer), typeof(ParticleSystem))] [AddComponentMenu("UI/Effects/Extensions/UIParticleSystem")] @@ -18,7 +37,16 @@ public class UIParticleSystem : MaskableGraphic [Tooltip("Enables using Renderer.lengthScale parameter")] public bool _useLengthScale = false; - + + [Tooltip("Use scaled time (Time.deltaTime) instead of unscaled time. When disabled (the default) the effect keeps animating while Time.timeScale is 0, matching the original behaviour.")] + public bool useTimeScale = false; + + [Tooltip("How to handle a large simulation step, e.g. when the app resumes from the background.\n- Automatic / Pause: clamp the step (default, prevents freezes - see issue #486).\n- PauseAndCatchup / AlwaysSimulate: replay the full elapsed time (original behaviour, may freeze after a long pause).")] + public UIParticleSystemCullingMode cullingMode = UIParticleSystemCullingMode.Automatic; + + [Tooltip("Maximum time (in seconds) the particle system may simulate in a single frame when using the Pause or Automatic culling mode. This caps the catch-up after a stall (e.g. returning from the background) so it cannot freeze the app - mirroring how Time.deltaTime is already capped by the project's 'Maximum Allowed Timestep'. Set to 0 to track that project setting (Time.maximumDeltaTime). Default 0.333.")] + public float maxSimulationDeltaTime = 0.333f; + private Transform _transform; private ParticleSystem pSystem; private ParticleSystem.Particle[] particles; @@ -94,7 +122,7 @@ protected bool Initialize() pRenderer = pSystem.GetComponent(); if (pRenderer != null) pRenderer.enabled = false; - + if (material == null) { var foundShader = ShaderLibrary.GetShaderInstance("UI Extensions/Particles/Additive"); @@ -235,11 +263,7 @@ protected override void OnPopulateMesh(VertexHelper vh) frame = Mathf.FloorToInt(frameProgress * textureSheetAnimation.numTilesX); int row = textureSheetAnimation.rowIndex; -#if UNITY_2019_1_OR_NEWER if (textureSheetAnimation.rowMode == ParticleSystemAnimationRowMode.Random) -#else - if (textureSheetAnimation.useRandomRow) -#endif { // FIXME - is this handled internally by rowIndex? row = Mathf.Abs((int)particle.randomSeed % textureSheetAnimation.numTilesY); } @@ -281,7 +305,7 @@ protected override void OnPopulateMesh(VertexHelper vh) _quad[3].color = color; _quad[3].uv0 = temp; - + float rotation = -particle.rotation * Mathf.Deg2Rad; var lengthScale = pRenderer.lengthScale; if (_useLengthScale) @@ -294,7 +318,7 @@ protected override void OnPopulateMesh(VertexHelper vh) { lengthScale = 1f; } - + float rotation90 = rotation + Mathf.PI / 2; if (rotation == 0) @@ -370,11 +394,32 @@ protected override void OnPopulateMesh(VertexHelper vh) } } + /// + /// Returns the time-step used to advance the particle simulation this frame. + /// In the clamping culling modes the step is capped so that resuming from a long + /// background pause does not replay the whole elapsed time in a single frame, which + /// would freeze the application (issue #486). + /// + private float GetSimulationDeltaTime() + { + float deltaTime = useTimeScale ? Time.deltaTime : Time.unscaledDeltaTime; + + if (cullingMode == UIParticleSystemCullingMode.Pause || cullingMode == UIParticleSystemCullingMode.Automatic) + { + // A value of 0 (including components serialized before this field existed) tracks the + // project's "Maximum Allowed Timestep", the same cap Unity already applies to Time.deltaTime. + float maxStep = maxSimulationDeltaTime > 0f ? maxSimulationDeltaTime : Time.maximumDeltaTime; + deltaTime = Mathf.Min(deltaTime, maxStep); + } + + return deltaTime; + } + private void Update() { if (!fixedTime && Application.isPlaying) { - pSystem.Simulate(Time.unscaledDeltaTime, false, false, true); + pSystem.Simulate(GetSimulationDeltaTime(), false, false, true); SetAllDirty(); if ((currentMaterial != null && currentTexture != currentMaterial.mainTexture) || @@ -396,7 +441,7 @@ private void LateUpdate() { if (fixedTime) { - pSystem.Simulate(Time.unscaledDeltaTime, false, false, true); + pSystem.Simulate(GetSimulationDeltaTime(), false, false, true); SetAllDirty(); if ((currentMaterial != null && currentTexture != currentMaterial.mainTexture) || (material != null && currentMaterial != null && material.shader != currentMaterial.shader)) diff --git a/Runtime/Scripts/Layout/CardUI/2D Cards/CardPopup2D.cs b/Runtime/Scripts/Layout/CardUI/2D Cards/CardPopup2D.cs index 14ce7899..a3c4c68d 100644 --- a/Runtime/Scripts/Layout/CardUI/2D Cards/CardPopup2D.cs +++ b/Runtime/Scripts/Layout/CardUI/2D Cards/CardPopup2D.cs @@ -57,8 +57,12 @@ void Update() { isFalling = false; rbody.useGravity = false; - rbody.velocity = Vector3.zero; - transform.position = new Vector3(0, 8, startZPos); +#if UNITY_6000_0_OR_NEWER + rbody.linearVelocity = Vector3.zero; +#else + rbody.velocity = Vector3.zero; +#endif + transform.position = new Vector3(0, 8, startZPos); if (singleScene) { CardEnter(); diff --git a/Runtime/Scripts/Layout/CurvedLayout.cs b/Runtime/Scripts/Layout/CurvedLayout.cs index c3fddc26..024a9d97 100644 --- a/Runtime/Scripts/Layout/CurvedLayout.cs +++ b/Runtime/Scripts/Layout/CurvedLayout.cs @@ -34,6 +34,7 @@ protected override void OnDisable() { m_Tracker.Clear(); LayoutRebuilder.MarkLayoutForRebuild(rectTransform); + base.OnDisable(); } public override void SetLayoutHorizontal() { diff --git a/Runtime/Scripts/Layout/FlowLayoutGroup.cs b/Runtime/Scripts/Layout/FlowLayoutGroup.cs index fd16a25d..a949d0c2 100644 --- a/Runtime/Scripts/Layout/FlowLayoutGroup.cs +++ b/Runtime/Scripts/Layout/FlowLayoutGroup.cs @@ -6,7 +6,6 @@ /// Vertical Flow by Ramon Molossi using System.Collections.Generic; -using System.Text; namespace UnityEngine.UI.Extensions { @@ -18,14 +17,20 @@ public class FlowLayoutGroup : LayoutGroup { public enum Axis { Horizontal = 0, Vertical = 1 } - private float _layoutHeight; - private float _layoutWidth; + private float _layoutHeight; + private float _layoutWidth; - public float SpacingX = 0f; + public float SpacingX = 0f; public float SpacingY = 0f; public bool ExpandHorizontalSpacing = false; public bool ChildForceExpandWidth = false; public bool ChildForceExpandHeight = false; + + [Tooltip("When enabled, children fill the container on BOTH axes by distributing any leftover space on the wrap (cross) axis evenly across the bars, instead of each bar only matching its largest child. " + + "Requires the cross-axis force expand to also be enabled: ChildForceExpandHeight when StartAxis is Horizontal, or ChildForceExpandWidth when StartAxis is Vertical. " + + "Has no effect when the cross axis is already constrained to content (e.g. by a ContentSizeFitter), as there is no leftover space to distribute.")] + public bool ChildForceExpandCrossAxis = false; + public bool invertOrder = false; [SerializeField] @@ -150,6 +155,22 @@ public float SetLayout(int axis, bool layoutInput) spacingBetweenElements = SpacingY; } + float extraBarSpace = 0f; + bool crossForceExpand = StartAxis == Axis.Horizontal ? ChildForceExpandHeight : ChildForceExpandWidth; + if (!layoutInput && ChildForceExpandCrossAxis && crossForceExpand) + { + MeasureBars(workingSize, spacingBetweenElements, spacingBetweenBars, offset, counterOffset, out int barCount, out float contentCrossSize); + if (barCount > 0) + { + float containerCrossSize = StartAxis == Axis.Horizontal ? groupHeight : groupWidth; + float leftover = containerCrossSize - contentCrossSize; + if (leftover > 0f) + { + extraBarSpace = leftover / barCount; + } + } + } + var currentBarSize = 0f; var currentBarSpace = 0f; @@ -194,21 +215,23 @@ public float SetLayout(int axis, bool layoutInput) { if (StartAxis == Axis.Horizontal) { - float newOffset = CalculateRowVerticalOffset(groupSize, offset, currentBarSpace); - LayoutRow(_itemList, currentBarSize, currentBarSpace, workingSize, padding.left, newOffset, axis); + float barThickness = currentBarSpace + extraBarSpace; + float newOffset = extraBarSpace > 0f && IsMiddleAlign ? offset : CalculateRowVerticalOffset(groupSize, offset, barThickness); + LayoutRow(_itemList, currentBarSize, barThickness, workingSize, padding.left, newOffset, axis); } else if (StartAxis == Axis.Vertical) { - float newOffset = CalculateColHorizontalOffset(groupSize, offset, currentBarSpace); - LayoutCol(_itemList, currentBarSpace, currentBarSize, workingSize, newOffset, padding.top, axis); + float barThickness = currentBarSpace + extraBarSpace; + float newOffset = extraBarSpace > 0f && IsCenterAlign ? offset : CalculateColHorizontalOffset(groupSize, offset, barThickness); + LayoutCol(_itemList, barThickness, currentBarSize, workingSize, newOffset, padding.top, axis); } } // Clear existing bar _itemList.Clear(); - // Add the current bar space to total barSpace accumulator, and reset to 0 for the next row - offset += currentBarSpace; + // Add the current bar space (plus any cross-axis fill) to total barSpace accumulator, and reset to 0 for the next row + offset += currentBarSpace + extraBarSpace; offset += spacingBetweenBars; currentBarSpace = 0; @@ -229,15 +252,17 @@ public float SetLayout(int axis, bool layoutInput) { if (StartAxis == Axis.Horizontal) { - float newOffset = CalculateRowVerticalOffset(groupHeight, offset, currentBarSpace); + float barThickness = currentBarSpace + extraBarSpace; + float newOffset = extraBarSpace > 0f && IsMiddleAlign ? offset : CalculateRowVerticalOffset(groupHeight, offset, barThickness); currentBarSize -= spacingBetweenElements; - LayoutRow(_itemList, currentBarSize, currentBarSpace, workingSize, padding.left, newOffset, axis); + LayoutRow(_itemList, currentBarSize, barThickness, workingSize, padding.left, newOffset, axis); } else if (StartAxis == Axis.Vertical) { - float newOffset = CalculateColHorizontalOffset(groupWidth, offset, currentBarSpace); + float barThickness = currentBarSpace + extraBarSpace; + float newOffset = extraBarSpace > 0f && IsCenterAlign ? offset : CalculateColHorizontalOffset(groupWidth, offset, barThickness); currentBarSize -= spacingBetweenElements; - LayoutCol(_itemList, currentBarSpace, currentBarSize, workingSize, newOffset, padding.top, axis); + LayoutCol(_itemList, barThickness, currentBarSize, workingSize, newOffset, padding.top, axis); } } @@ -254,6 +279,75 @@ public float SetLayout(int axis, bool layoutInput) return offset; } + /// + /// Measures how many bars the children wrap into and the natural size they occupy on the + /// wrap (cross) axis, using the exact same wrapping rules as . + /// Used to evenly share leftover cross-axis space across the bars when + /// is enabled. + /// + private void MeasureBars(float workingSize, float spacingBetweenElements, float spacingBetweenBars, float leadingPadding, float trailingPadding, out int barCount, out float contentCrossSize) + { + barCount = 0; + contentCrossSize = 0f; + if (rectChildren.Count == 0) + { + return; + } + + barCount = 1; + float sumBarSpace = 0f; + float currentBarSize = 0f; + float currentBarSpace = 0f; + + for (var i = 0; i < rectChildren.Count; i++) + { + int index = i; + var child = rectChildren[index]; + float childSize; + float childOtherSize; + + if (StartAxis == Axis.Horizontal) + { + if (invertOrder) + { + index = IsLowerAlign ? rectChildren.Count - 1 - i : i; + } + child = rectChildren[index]; + childSize = LayoutUtility.GetPreferredSize(child, 0); + childSize = Mathf.Min(childSize, workingSize); + childOtherSize = LayoutUtility.GetPreferredSize(child, 1); + } + else + { + if (invertOrder) + { + index = IsRightAlign ? rectChildren.Count - 1 - i : i; + } + child = rectChildren[index]; + childSize = LayoutUtility.GetPreferredSize(child, 1); + childSize = Mathf.Min(childSize, workingSize); + childOtherSize = LayoutUtility.GetPreferredSize(child, 0); + } + + if (currentBarSize + childSize > workingSize) + { + barCount++; + sumBarSpace += currentBarSpace; + currentBarSpace = 0f; + currentBarSize = 0f; + } + + currentBarSize += childSize; + currentBarSpace = childOtherSize > currentBarSpace ? childOtherSize : currentBarSpace; + currentBarSize += spacingBetweenElements; + } + + // Account for the final bar, which is laid out after the loop in SetLayout. + sumBarSpace += currentBarSpace; + + contentCrossSize = leadingPadding + trailingPadding + sumBarSpace + spacingBetweenBars * (barCount - 1); + } + private float CalculateRowVerticalOffset(float groupHeight, float yOffset, float currentRowHeight) { if (IsLowerAlign) @@ -471,10 +565,17 @@ public float GetGreatestMinimumChildHeigth() return max; } + protected override void OnEnable() + { + base.OnEnable(); + LayoutRebuilder.ForceRebuildLayoutImmediate(rectTransform); + } + protected override void OnDisable() { m_Tracker.Clear(); LayoutRebuilder.MarkLayoutForRebuild(rectTransform); + base.OnDisable(); } } } \ No newline at end of file diff --git a/Runtime/Scripts/Layout/HorizontalScrollSnap.cs b/Runtime/Scripts/Layout/HorizontalScrollSnap.cs index 59877a0d..a9d137f9 100644 --- a/Runtime/Scripts/Layout/HorizontalScrollSnap.cs +++ b/Runtime/Scripts/Layout/HorizontalScrollSnap.cs @@ -80,7 +80,7 @@ public void DistributePages() float _offset = 0; float _dimension = 0; - Rect panelDimensions = gameObject.GetComponent().rect; + panelDimensions = gameObject.GetComponent().rect; float currentXPosition = 0; var pageStepValue = _childSize = (int)panelDimensions.width * ((PageStep == 0) ? 3 : PageStep); @@ -96,6 +96,7 @@ public void DistributePages() _dimension = currentXPosition + _offset * -1; _screensContainer.GetComponent().offsetMax = new Vector2(_dimension, 0f); + _screensContainer.GetComponent().offsetMin = Vector2.zero; } /// @@ -216,7 +217,8 @@ public void RemoveAllChildren(bool WorldPositionStays, out GameObject[] Children private void SetScrollContainerPosition() { _scrollStartPosition = _screensContainer.anchoredPosition.x; - _scroll_rect.horizontalNormalizedPosition = (float)(_currentPage) / (_screens - 1); + //Guard against a single (or no) page producing a divide-by-zero / NaN normalized position. + _scroll_rect.horizontalNormalizedPosition = _screens > 1 ? (float)(_currentPage) / (_screens - 1) : 0; OnCurrentScreenChange(_currentPage); } diff --git a/Runtime/Scripts/Layout/RadialLayout.cs b/Runtime/Scripts/Layout/RadialLayout.cs index fb90cb57..fc07fac0 100644 --- a/Runtime/Scripts/Layout/RadialLayout.cs +++ b/Runtime/Scripts/Layout/RadialLayout.cs @@ -58,8 +58,9 @@ protected override void OnValidate() protected override void OnDisable() { - m_Tracker.Clear(); // key change - do not restore - false + m_Tracker.Clear(); LayoutRebuilder.MarkLayoutForRebuild(rectTransform); + base.OnDisable(); } void CalculateRadial() diff --git a/Runtime/Scripts/Layout/ScrollSnap.cs b/Runtime/Scripts/Layout/ScrollSnap.cs index 800d3ee9..878b3b77 100644 --- a/Runtime/Scripts/Layout/ScrollSnap.cs +++ b/Runtime/Scripts/Layout/ScrollSnap.cs @@ -17,7 +17,7 @@ namespace UnityEngine.UI.Extensions [ExecuteInEditMode] [RequireComponent(typeof(ScrollRect))] [AddComponentMenu("UI/Extensions/Scroll Snap")] - public class ScrollSnap : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollSnap + public class ScrollSnap : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler, IScrollSnap { // needed because of reversed behaviour of axis Y compared to X // (positions of children lower in children list in horizontal directions grows when in vertical it gets smaller) @@ -154,12 +154,12 @@ public void UpdateListItemsSize() if (direction == ScrollSnap.ScrollDirection.Horizontal) { size = _scrollRectTransform.rect.width / ItemsVisibleAtOnce; - currentSize = _listContainerRectTransform.rect.width / _itemsCount; + currentSize = _itemsCount > 0 ? _listContainerRectTransform.rect.width / _itemsCount : 0; } else { size = _scrollRectTransform.rect.height / ItemsVisibleAtOnce; - currentSize = _listContainerRectTransform.rect.height / _itemsCount; + currentSize = _itemsCount > 0 ? _listContainerRectTransform.rect.height / _itemsCount : 0; } _itemSize = size; @@ -348,20 +348,32 @@ void LateUpdate() { UpdateScrollbar(false); - _listContainerTransform.localPosition = Vector3.Lerp(_listContainerTransform.localPosition, _lerpTarget, 7.5f * Time.deltaTime); + var nextPosition = Vector3.Lerp(_listContainerTransform.localPosition, _lerpTarget, 7.5f * Time.deltaTime); - if (Vector3.Distance(_listContainerTransform.localPosition, _lerpTarget) < 0.001f) + //Guard against a corrupt (NaN/Infinity) lerp value being latched into the transform. + //Once content.localPosition becomes NaN the RectTransform bounds are NaN and the state never recovers. + if (!IsFinite(nextPosition)) { - _listContainerTransform.localPosition = _lerpTarget; _lerp = false; - UpdateScrollbar(LinkScrolbarSteps); } - - //change the info bullets at the bottom of the screen. Just for visual effect - if (Vector3.Distance(_listContainerTransform.localPosition, _lerpTarget) < 10f) + else { - PageChanged(CurrentPage()); + _listContainerTransform.localPosition = nextPosition; + + if (Vector3.Distance(_listContainerTransform.localPosition, _lerpTarget) < 0.001f) + { + _listContainerTransform.localPosition = _lerpTarget; + _lerp = false; + + UpdateScrollbar(LinkScrolbarSteps); + } + + //change the info bullets at the bottom of the screen. Just for visual effect + if (Vector3.Distance(_listContainerTransform.localPosition, _lerpTarget) < 10f) + { + PageChanged(CurrentPage()); + } } } @@ -447,6 +459,13 @@ public int CurrentPage() pos = Mathf.Clamp(pos, 0, _listContainerSize); } + //Guard against degenerate item sizing (e.g. a Content Size Fitter momentarily reporting a + //zero-sized viewport/content) which would otherwise produce a 0/0 = NaN page value. + if (_itemSize <= 0) + { + return 0; + } + float page = pos / _itemSize; return Mathf.Clamp(Mathf.RoundToInt(page), 0, _pages); @@ -566,7 +585,25 @@ public void OnDrag(PointerEventData eventData) } } + public void OnScroll(PointerEventData eventData) + { + //A mouse-wheel / trackpad scroll is handled directly by the ScrollRect and never raises OnDrag, + //so without this the snap would keep lerping to its target and fight the user's scroll input + //(the source of the "scroll is unresponsive after pressing a button" behaviour and the NaN race). + _lerp = false; + } + public void StartScreenChange() { } #endregion + + /// + /// Returns true only when every component of the vector is a real, finite number (no NaN / Infinity). + /// + private static bool IsFinite(Vector3 value) + { + return !float.IsNaN(value.x) && !float.IsInfinity(value.x) + && !float.IsNaN(value.y) && !float.IsInfinity(value.y) + && !float.IsNaN(value.z) && !float.IsInfinity(value.z); + } } } \ No newline at end of file diff --git a/Runtime/Scripts/Layout/ScrollSnapBase.cs b/Runtime/Scripts/Layout/ScrollSnapBase.cs index d200bef0..fab8d891 100644 --- a/Runtime/Scripts/Layout/ScrollSnapBase.cs +++ b/Runtime/Scripts/Layout/ScrollSnapBase.cs @@ -94,12 +94,22 @@ public int CurrentPage { get { + //When an Infinite Scroll is attached, children are relocated and re-ordered at runtime, + //so the cached page (derived from cumulative scroll displacement) no longer maps to the + //child actually on screen. Resolve the page from the centred child instead. See issue #254. + if (_isInfinite) + { + return GetClosestPage(); + } return _currentPage; } internal set { - if (_isInfinite) + //Guard childCount > 0: when content has been cleared at runtime (e.g. RemoveAllChildren) + //the integer divide/modulo below would throw DivideByZeroException. With no children the + //window calculation is meaningless, so skip it and let value fall through as 0. See issue #237. + if (_isInfinite && _screensContainer.childCount > 0) { //Work out which infinite window we are in float infWindow = (float)value / (float)_screensContainer.childCount; @@ -189,6 +199,11 @@ void Awake() _screensContainer = _scroll_rect.content; + //ScrollRect.content RT anchors has to be stretched first in order for HSS/VSS.DistributePages() to have the correct result + _screensContainer.anchorMin = Vector2.zero; + _screensContainer.anchorMax = Vector2.one; + _screensContainer.sizeDelta = Vector2.zero; + InitialiseChildObjects(); if (NextButton) @@ -289,7 +304,7 @@ internal void UpdateVisible() } //Set the active items active - for (int i = CurrentPage - _bottomItem; i < CurrentPage + _topItem; i++) + for (int i = _currentPage - _bottomItem; i < _currentPage + _topItem; i++) { try { @@ -302,9 +317,9 @@ internal void UpdateVisible() } //Deactivate items out of visibility at the bottom of the ScrollRect Mask (only on scroll) - if (_currentPage > _halfNoVisibleItems) ChildObjects[CurrentPage - _bottomItem].SetActive(false); + if (_currentPage > _halfNoVisibleItems) ChildObjects[_currentPage - _bottomItem].SetActive(false); //Deactivate items out of visibility at the top of the ScrollRect Mask (only on scroll) - if (_screensContainer.childCount - _currentPage > _topItem) ChildObjects[CurrentPage + _topItem].SetActive(false); + if (_screensContainer.childCount - _currentPage > _topItem) ChildObjects[_currentPage + _topItem].SetActive(false); } //Function for switching screens with buttons @@ -317,13 +332,15 @@ public void NextScreen() _lerp = true; if (_isInfinite) { - CurrentPage = GetPageforPosition(_screensContainer.anchoredPosition) + 1; + int targetPage = GetPageforPosition(_screensContainer.anchoredPosition) + 1; + CurrentPage = targetPage; + GetInfinitePositionforPage(targetPage, ref _lerp_target); } else { CurrentPage = _currentPage + 1; + GetPositionforPage(_currentPage, ref _lerp_target); } - GetPositionforPage(_currentPage, ref _lerp_target); ScreenChange(); } @@ -339,13 +356,15 @@ public void PreviousScreen() _lerp = true; if (_isInfinite) { - CurrentPage = GetPageforPosition(_screensContainer.anchoredPosition) - 1; + int targetPage = GetPageforPosition(_screensContainer.anchoredPosition) - 1; + CurrentPage = targetPage; + GetInfinitePositionforPage(targetPage, ref _lerp_target); } else { CurrentPage = _currentPage - 1; + GetPositionforPage(_currentPage, ref _lerp_target); } - GetPositionforPage(_currentPage, ref _lerp_target); ScreenChange(); } } @@ -381,6 +400,42 @@ internal int GetPageforPosition(Vector3 pos) (int)Math.Round((_scrollStartPosition - pos.x) / _childSize); } + /// + /// Resolves the page index of the child currently closest to the centre of the viewport. + /// Unlike , which assumes children keep their original sibling + /// order and grid positions, this inspects the live child positions. This is required for + /// Infinite Scroll, where UI_InfiniteScroll relocates and re-orders children at runtime. + /// + /// Sibling index of the child nearest the current scroll position + internal int GetClosestPage() + { + if (_screensContainer == null || _screensContainer.childCount == 0) + { + return _currentPage; + } + + //The container-local coordinate that is currently centred in the viewport. + //Matches the numerator GetPageforPosition uses, so a settled page lands exactly on a child. + float targetPosition = _isVertical ? + _scrollStartPosition - _screensContainer.anchoredPosition.y : + _scrollStartPosition - _screensContainer.anchoredPosition.x; + + int closestPage = 0; + float closestDistance = float.MaxValue; + for (int i = 0; i < _screensContainer.childCount; i++) + { + RectTransform child = (RectTransform)_screensContainer.GetChild(i); + float childPosition = _isVertical ? child.anchoredPosition.y : child.anchoredPosition.x; + float distance = Math.Abs(targetPosition - childPosition); + if (distance < closestDistance) + { + closestDistance = distance; + closestPage = i; + } + } + return closestPage; + } + /// /// Validates if the current Scroll Rect container position is within the bounds for a page /// @@ -415,6 +470,30 @@ internal void GetPositionforPage(int page, ref Vector3 target) } } + /// + /// Returns the local position for an absolute "displacement page" when an Infinite Scroll is + /// attached. The content moves under an Unrestricted ScrollRect, so the displacement page + /// (as produced by ) maps straight back to a content position - + /// this is the exact inverse of that getter, so button navigation lands on the same grid the + /// drag-settle logic uses. Unlike it does NOT rebuild the + /// target from a wrapped page + _infiniteWindow + a sign test, which is what caused button + /// navigation to "take the long way" across a window boundary. See issue #257. + /// + /// Absolute (un-wrapped) displacement page to move to + /// Outputs the local position for that page + internal void GetInfinitePositionforPage(int displacementPage, ref Vector3 target) + { + float position = _scrollStartPosition - _childSize * displacementPage; + if (_isVertical) + { + target.y = position; + } + else + { + target.x = position; + } + } + /// /// Updates the _Lerp target to the closest page and updates the pagination bullets. Each control's update loop will then handle the move. /// @@ -547,7 +626,7 @@ public void StartScreenChange() /// internal void ScreenChange() { - OnSelectionPageChangedEvent.Invoke(_currentPage); + OnSelectionPageChangedEvent.Invoke(CurrentPage); } /// @@ -562,7 +641,7 @@ internal void EndScreenChange() _endEventCalled = true; _startEventCalled = false; _settled = true; - OnSelectionChangeEndEvent.Invoke(_currentPage); + OnSelectionChangeEndEvent.Invoke(CurrentPage); } } diff --git a/Runtime/Scripts/Layout/TableLayoutGroup.cs b/Runtime/Scripts/Layout/TableLayoutGroup.cs index 83ade4ed..663857f6 100644 --- a/Runtime/Scripts/Layout/TableLayoutGroup.cs +++ b/Runtime/Scripts/Layout/TableLayoutGroup.cs @@ -285,10 +285,17 @@ public override void SetLayoutVertical() preferredRowHeights = null; } + protected override void OnEnable() + { + base.OnEnable(); + LayoutRebuilder.ForceRebuildLayoutImmediate(rectTransform); + } + protected override void OnDisable() { - m_Tracker.Clear(); // key change - do not restore - false + m_Tracker.Clear(); LayoutRebuilder.MarkLayoutForRebuild(rectTransform); + base.OnDisable(); } } } \ No newline at end of file diff --git a/Runtime/Scripts/Layout/UIHorizontalScroller.cs b/Runtime/Scripts/Layout/UIHorizontalScroller.cs index ed79ade5..1c1bfdd4 100644 --- a/Runtime/Scripts/Layout/UIHorizontalScroller.cs +++ b/Runtime/Scripts/Layout/UIHorizontalScroller.cs @@ -158,7 +158,7 @@ public void UpdateChildren(int startingIndex = -1, GameObject[] arrayOfElements RectTransform r = this.arrayOfElements[i].GetComponent(); r.anchorMax = r.anchorMin = r.pivot = new Vector2(0.5f, 0.5f); - r.localPosition = new Vector2(i * elementSize.rect.size.x,0); + r.localPosition = new Vector2(i * elementSize.rect.size.x, 0); r.sizeDelta = elementSize.rect.size; } @@ -209,19 +209,11 @@ public void Update() if (minDistance == distance[i]) { FocusedElementIndex = i; -#if UNITY_2022_1_OR_NEWER var textComponentTxtMeshPro = arrayOfElements[i].GetComponentInChildren(); if (textComponentTxtMeshPro != null) { Result = textComponentTxtMeshPro.text; } -#else - var textComponent = arrayOfElements[i].GetComponentInChildren(); - if (textComponent != null) - { - Result = textComponent.text; - } -#endif } } diff --git a/Runtime/Scripts/Layout/UIVerticalScroller.cs b/Runtime/Scripts/Layout/UIVerticalScroller.cs index 167a8302..030363c4 100644 --- a/Runtime/Scripts/Layout/UIVerticalScroller.cs +++ b/Runtime/Scripts/Layout/UIVerticalScroller.cs @@ -68,11 +68,14 @@ public class UIVerticalScroller : MonoBehaviour public int FocusedElementIndex { get; private set; } public RectTransform Center { get => center; set => center = value; } + public RectTransform ElementSize { get => elementSize; set => elementSize = value; } + public ScrollRect ScrollRectComponent { get => scrollRect; set => scrollRect = value; } + public GameObject[] ArrayOfElements { get => arrayOfElements; set => arrayOfElements = value; } public string Result { get; private set; } //Scrollable area (content of desired ScrollRect) - public RectTransform ScrollingPanel{ get { return scrollRect.content; } } + public RectTransform ScrollingPanel { get { return scrollRect.content; } } /// /// Constructor when not used as component but called from other script @@ -94,29 +97,6 @@ public void Awake() { scrollRect = GetComponent(); } - - if (!center) - { - Debug.LogError("Please define the RectTransform for the Center viewport of the scrollable area"); - } - - if (!elementSize) - { - elementSize = center; - } - - if (arrayOfElements == null || arrayOfElements.Length == 0) - { - var childCount = ScrollingPanel.childCount; - if (childCount > 0) - { - arrayOfElements = new GameObject[childCount]; - for (int i = 0; i < childCount; i++) - { - arrayOfElements[i] = ScrollingPanel.GetChild(i).gameObject; - } - } - } } /// @@ -172,6 +152,30 @@ private void AddListener(GameObject button, int index) public void Start() { + if (!center) + { + Debug.LogError("Please define the RectTransform for the Center viewport of the scrollable area"); + return; + } + + if (!elementSize) + { + elementSize = center; + } + + if (arrayOfElements == null || arrayOfElements.Length == 0) + { + var childCount = ScrollingPanel.childCount; + if (childCount > 0) + { + arrayOfElements = new GameObject[childCount]; + for (int i = 0; i < childCount; i++) + { + arrayOfElements[i] = ScrollingPanel.GetChild(i).gameObject; + } + } + } + if (scrollUpButton) { scrollUpButton.GetComponent - public void RefreshTooltipSize() { - if (tooltipTriggersCanForceCanvasUpdate) { + public void RefreshTooltipSize() + { + if (tooltipTriggersCanForceCanvasUpdate) + { Canvas.ForceUpdateCanvases(); - if (_layoutGroup) { + if (_layoutGroup) + { _layoutGroup.enabled = false; _layoutGroup.enabled = true; } - + } - + } /// /// Runs the appropriate tooltip placement method, according to the parent canvas's render mode /// /// - public void ContextualTooltipUpdate(bool refreshCanvasesBeforeGettingSize = false) { - switch (guiMode) { + public void ContextualTooltipUpdate(bool refreshCanvasesBeforeGettingSize = false) + { + switch (guiMode) + { case RenderMode.ScreenSpaceCamera: OnScreenSpaceCamera(refreshCanvasesBeforeGettingSize); break; @@ -251,7 +250,8 @@ public void OnScreenSpaceCamera(bool refreshCanvasesBeforeGettingSize = false) //check for lower edge of the screen borderTest = (adjustedNewTTPos.y - height / 2); - if (borderTest < screenLowerLeft.y) { + if (borderTest < screenLowerLeft.y) + { shifterForBorders.y = screenLowerLeft.y - borderTest; adjustedNewTTPos.y += shifterForBorders.y; } @@ -277,7 +277,8 @@ public void OnScreenSpaceCamera(bool refreshCanvasesBeforeGettingSize = false) //main tooltip edge of screen guard and movement - overlay - public void OnScreenSpaceOverlay(bool refreshCanvasesBeforeGettingSize = false) { + public void OnScreenSpaceOverlay(bool refreshCanvasesBeforeGettingSize = false) + { shiftingVector.x = xShift; shiftingVector.y = YShift; newTTPos = (baseTooltipPos - shiftingVector) / canvas.scaleFactor; @@ -297,13 +298,15 @@ public void OnScreenSpaceOverlay(bool refreshCanvasesBeforeGettingSize = false) //check for right edge of screen borderTest = (newTTPos.x + width / 2); - if (borderTest > screenUpperRight.x) { + if (borderTest > screenUpperRight.x) + { shifterForBorders.x = borderTest - screenUpperRight.x; adjustedNewTTPos.x -= shifterForBorders.x; } //check for left edge of screen borderTest = (adjustedNewTTPos.x - width / 2); - if (borderTest < screenLowerLeft.x) { + if (borderTest < screenLowerLeft.x) + { shifterForBorders.x = screenLowerLeft.x - borderTest; adjustedNewTTPos.x += shifterForBorders.x; } @@ -312,14 +315,16 @@ public void OnScreenSpaceOverlay(bool refreshCanvasesBeforeGettingSize = false) //check for lower edge of the screen borderTest = (adjustedNewTTPos.y - height / 2); - if (borderTest < screenLowerLeft.y) { + if (borderTest < screenLowerLeft.y) + { shifterForBorders.y = screenLowerLeft.y - borderTest; adjustedNewTTPos.y += shifterForBorders.y; } //check for upper edge of the screen borderTest = (adjustedNewTTPos.y + height / 2); - if (borderTest > screenUpperRight.y) { + if (borderTest > screenUpperRight.y) + { shifterForBorders.y = borderTest - screenUpperRight.y; adjustedNewTTPos.y -= shifterForBorders.y; } diff --git a/Runtime/Scripts/Utilities/ExtensionsToggle.cs b/Runtime/Scripts/Utilities/ExtensionsToggle.cs index 70653da0..8b3ee255 100644 --- a/Runtime/Scripts/Utilities/ExtensionsToggle.cs +++ b/Runtime/Scripts/Utilities/ExtensionsToggle.cs @@ -89,12 +89,7 @@ protected override void OnValidate() base.OnValidate(); Set(m_IsOn, false); PlayEffect(toggleTransition == ToggleTransition.None); -#if UNITY_2018_3_OR_NEWER if (!Application.isPlaying) -#else - var prefabType = UnityEditor.PrefabUtility.GetPrefabType(this); - if (prefabType != UnityEditor.PrefabType.Prefab && !Application.isPlaying) -#endif { CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild(this); } diff --git a/Runtime/Scripts/Utilities/PPIViewer.cs b/Runtime/Scripts/Utilities/PPIViewer.cs index e0880020..1fa6be51 100644 --- a/Runtime/Scripts/Utilities/PPIViewer.cs +++ b/Runtime/Scripts/Utilities/PPIViewer.cs @@ -6,27 +6,15 @@ Simply place the script on A Text control in the scene to display the current PP namespace UnityEngine.UI.Extensions { -#if UNITY_2022_1_OR_NEWER [RequireComponent(typeof(TMPro.TMP_Text))] -#else - [RequireComponent(typeof(Text))] -#endif [AddComponentMenu("UI/Extensions/PPIViewer")] public class PPIViewer : MonoBehaviour { -#if UNITY_2022_1_OR_NEWER private TMPro.TMP_Text label; -#else - private Text label; -#endif void Awake() { -#if UNITY_2022_1_OR_NEWER label = GetComponentInChildren(); -#else - label = GetComponentInChildren(); -#endif } void Start() diff --git a/Runtime/Scripts/Utilities/UIExtensionMethods.cs b/Runtime/Scripts/Utilities/UIExtensionMethods.cs index 3472139b..8f96de87 100644 --- a/Runtime/Scripts/Utilities/UIExtensionMethods.cs +++ b/Runtime/Scripts/Utilities/UIExtensionMethods.cs @@ -65,5 +65,31 @@ public static Camera GetEventCamera(this Canvas input) return input.worldCamera == null ? Camera.main : input.worldCamera; } + + /// + /// Sets the displayed text on a child named , supporting either a + /// TextMeshPro (TMP_Text) or a legacy Unity UI Text component. Safe no-op if neither is found. + /// + public static void SetChildTextValue(this Transform parent, string childName, string value) + { + if (parent == null) { return; } + Transform child = parent.Find(childName); + if (child == null) { return; } + +#if UNITY_6000_0_OR_NEWER + TMPro.TMP_Text tmpText = child.GetComponent(); + if (tmpText != null) + { + tmpText.text = value; + return; + } +#endif + + Text uiText = child.GetComponent(); + if (uiText != null) + { + uiText.text = value; + } + } } } diff --git a/Runtime/Scripts/Utilities/UIExtensionsInputManager.cs b/Runtime/Scripts/Utilities/UIExtensionsInputManager.cs index b82e292d..64a364c6 100644 --- a/Runtime/Scripts/Utilities/UIExtensionsInputManager.cs +++ b/Runtime/Scripts/Utilities/UIExtensionsInputManager.cs @@ -1,7 +1,7 @@ /// Credit SimonDarksideJ /// Sourced from: https://bitbucket.org/UnityUIExtensions/unity-ui-extensions/issues/348/menu-manager-does-not-work-with-the-new -#if UNITY_2019_1_OR_NEWER && !ENABLE_LEGACY_INPUT_MANAGER +#if !ENABLE_LEGACY_INPUT_MANAGER #define NEW_INPUT_SYSTEM #endif diff --git a/Runtime/Scripts/Utilities/UILineConnector.cs b/Runtime/Scripts/Utilities/UILineConnector.cs index 77a3fc93..77bc63ee 100644 --- a/Runtime/Scripts/Utilities/UILineConnector.cs +++ b/Runtime/Scripts/Utilities/UILineConnector.cs @@ -12,32 +12,55 @@ public class UILineConnector : MonoBehaviour // The elements between which line segments should be drawn public RectTransform[] transforms; private Vector3[] previousPositions; - private RectTransform canvas; + private Vector3 previousLrPos; + private Vector3 previousGlobalScale; private RectTransform rt; private UILineRenderer lr; private void Awake() { - var canvasParent = GetComponentInParent().GetParentCanvas(); - if (canvasParent != null) - { - canvas = canvasParent.GetComponent(); - } rt = GetComponent(); lr = GetComponent(); } - // Update is called once per frame - void Update() + private void OnEnable() { if (transforms == null || transforms.Length < 1) { return; } - //Performance check to only redraw when the child transforms move - if (previousPositions != null && previousPositions.Length == transforms.Length) + + CalculateLinePoints(); + } + + private void Update() + { + if (lr.RelativeSize) + { + Debug.LogWarning("While using UILineConnector, UILineRenderer should not use relative size, so that even if this RectTransform has a zero-size Rect, the positions of the points can still be calculated"); + lr.RelativeSize = false; + } + + if (transforms == null || transforms.Length < 1) + { + return; + } + + // Get world position of UILineRenderer + Vector3 lrWorldPos = rt.position; + + /*Performance check to only redraw when the child transforms move, + or the world position of UILineRenderer moves */ + bool updateLine = lrWorldPos != previousLrPos; + updateLine = rt.lossyScale != previousGlobalScale; + + if (!updateLine) + { + updateLine = previousPositions.Length != transforms.Length; + } + + if (!updateLine && previousPositions != null) { - bool updateLine = false; for (int i = 0; i < transforms.Length; i++) { if (transforms[i] == null) @@ -47,56 +70,48 @@ void Update() if (!updateLine && previousPositions[i] != transforms[i].position) { updateLine = true; + break; } } - if (!updateLine) return; - } + } + if (!updateLine) return; - // Get the pivot points - Vector2 thisPivot = rt.pivot; - Vector2 canvasPivot = canvas.pivot; - // Set up some arrays of coordinates in various reference systems - Vector3[] worldSpaces = new Vector3[transforms.Length]; - Vector3[] canvasSpaces = new Vector3[transforms.Length]; - Vector2[] points = new Vector2[transforms.Length]; + // Calculate delta from the local position + CalculateLinePoints(); - // First, convert the pivot to worldspace + + //save previous states + previousLrPos = lrWorldPos; + previousGlobalScale = rt.lossyScale; + previousPositions = new Vector3[transforms.Length]; for (int i = 0; i < transforms.Length; i++) { if (transforms[i] == null) { continue; } - worldSpaces[i] = transforms[i].TransformPoint(thisPivot); - } - - // Then, convert to canvas space - for (int i = 0; i < transforms.Length; i++) - { - canvasSpaces[i] = canvas.InverseTransformPoint(worldSpaces[i]); - } - - // Calculate delta from the canvas pivot point - for (int i = 0; i < transforms.Length; i++) - { - points[i] = new Vector2(canvasSpaces[i].x, canvasSpaces[i].y); + previousPositions[i] = transforms[i].position; } + } - // And assign the converted points to the line renderer - lr.Points = points; - lr.RelativeSize = false; - lr.drivenExternally = true; - - previousPositions = new Vector3[transforms.Length]; + private void CalculateLinePoints() + { + Vector2[] points = new Vector2[transforms.Length]; for (int i = 0; i < transforms.Length; i++) { if (transforms[i] == null) { continue; } - previousPositions[i] = transforms[i].position; + var offsetPos = rt.InverseTransformPoint(transforms[i].position); + points[i] = new Vector2(offsetPos.x, offsetPos.y); } + + // And assign the converted points to the line renderer + lr.Points = points; + lr.RelativeSize = false; + lr.drivenExternally = true; } } } \ No newline at end of file diff --git a/Runtime/Scripts/Utilities/UIVertexPool.cs b/Runtime/Scripts/Utilities/UIVertexPool.cs new file mode 100644 index 00000000..82979954 --- /dev/null +++ b/Runtime/Scripts/Utilities/UIVertexPool.cs @@ -0,0 +1,67 @@ +///Credit Dmitry (mitay-walle) +///Sourced from - https://github.com/mitay-walle/com.mitay-walle.ui-graphic-sector + +using System.Collections.Generic; + +namespace UnityEngine.UI.Extensions +{ + public class UIVertexPool + { + Stack _pool; + List _used; + + public UIVertexPool(int size) + { + _pool = new Stack(size); + _used = new List(); + + for (int i = 0; i < size; i++) + { + _pool.Push(new UIVertex[4]); + } + } + + public UIVertex[] Get() + { + UIVertex[] item = null; + if (_pool.Count > 0) + { + item = _pool.Pop(); + } + else + { + item = new UIVertex[4]; + for (int i = 0; i < 4; i++) + { + item[i] = UIVertex.simpleVert; + } + } + + _used.Add(item); + return item; + } + + public void ReleaseAll() + { + int count = _used.Count; + for (int i = 0; i < count; i++) + { + Release(_used[i]); + } + _used.Clear(); + } + + public void Release(UIVertex[] vrtx) + { + if (vrtx != null) + { + _pool.Push(vrtx); + } + } + + public void Clear() + { + _pool.Clear(); + } + } +} \ No newline at end of file diff --git a/Runtime/Scripts/Utilities/UIVertexPool.cs.meta b/Runtime/Scripts/Utilities/UIVertexPool.cs.meta new file mode 100644 index 00000000..7dbe6ca3 --- /dev/null +++ b/Runtime/Scripts/Utilities/UIVertexPool.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c33f02f63dd7455a9dc6b9f47cc0a139 +timeCreated: 1719301767 \ No newline at end of file diff --git a/Runtime/Scripts/Utilities/UI_InfiniteScroll.cs b/Runtime/Scripts/Utilities/UI_InfiniteScroll.cs index dbcdd3a2..444d858f 100644 --- a/Runtime/Scripts/Utilities/UI_InfiniteScroll.cs +++ b/Runtime/Scripts/Utilities/UI_InfiniteScroll.cs @@ -77,6 +77,10 @@ public virtual void SetNewItems(ref List newItems) private void SetItems() { + //Clear first so Init() can be safely re-run after dynamic content changes, + //otherwise stale (destroyed) items remain in the list. See issue #237. + items.Clear(); + //Remove Pivots from content as they mess up translation foreach (RectTransform transform in _scrollRect.content.transform) { @@ -96,6 +100,9 @@ public void Init() if (GetComponent() != null) { _scrollRect = GetComponent(); + //Remove first to keep Init() idempotent - re-initialising after dynamic content + //changes would otherwise register OnScroll multiple times. See issue #237. + _scrollRect.onValueChanged.RemoveListener(OnScroll); _scrollRect.onValueChanged.AddListener(OnScroll); _scrollRect.movementType = ScrollRect.MovementType.Unrestricted; @@ -159,6 +166,11 @@ public void OnScroll(Vector2 pos) if (!_hasDisabledGridComponents) DisableGridComponents(); + //Nothing to reposition (e.g. content was cleared at runtime); avoid GetChild() on + //an empty container, which would throw. See issue #237. + if (items.Count == 0) + return; + var firstChild = _scrollRect.content.GetChild(0).GetComponent(); var lastChild = _scrollRect.content.GetChild(_itemCount - 1).GetComponent(); diff --git a/Runtime/Scripts/Utilities/UI_ScrollRectOcclusion.cs b/Runtime/Scripts/Utilities/UI_ScrollRectOcclusion.cs index 189c84e5..e3b535b9 100644 --- a/Runtime/Scripts/Utilities/UI_ScrollRectOcclusion.cs +++ b/Runtime/Scripts/Utilities/UI_ScrollRectOcclusion.cs @@ -1,6 +1,6 @@ /// Credit Tomasz Schelenz /// Sourced from - https://bitbucket.org/SimonDarksideJ/unity-ui-extensions/issues/82/scrollrectocclusion -/// Demo - https://youtu.be/uVTV7Udx78k?t=39s ScrollRectOcclusion - disables the objects outside of the scrollrect viewport. Useful for scrolls with lots of content, reduces geometry and drawcalls (if content is not batched) In some cases it might create a bit of spikes, especially if you have lots of UI.Text objects in the childs. In that case consider to Add CanvasGroup to your childs and instead of calling setActive on game object change CanvasGroup.alpha value. At 0 it is not being rendered hence will also optimize the performance. +/// Demo - https://youtu.be/uVTV7Udx78k?t=39s ScrollRectOcclusion - disables the objects outside of the scrollrect viewport. Useful for scrolls with lots of content, reduces geometry and drawcalls (if content is not batched) In some cases it might create a bit of spikes, especially if you have lots of UI. objects in the childs. In that case consider to Add CanvasGroup to your childs and instead of calling setActive on game object change CanvasGroup.alpha value. At 0 it is not being rendered hence will also optimize the performance. using System.Collections.Generic; @@ -15,7 +15,7 @@ namespace UnityEngine.UI.Extensions /// by calling Init() method /// /// Notes - /// - In some cases it might create a bit of spikes, especially if you have lots of UI.Text objects in the child's. In that case consider to Add + /// - In some cases it might create a bit of spikes, especially if you have lots of UI. objects in the child's. In that case consider to Add /// CanvasGroup to your child's and instead of calling setActive on game object change CanvasGroup.alpha value. At 0 it is not being rendered hence will /// also optimize the performance. /// - works for both vertical and horizontal scrolls, even at the same time (grid layout) diff --git a/package.json b/package.json index 591a6824..ec166e1c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "com.unity.uiextensions", "displayName": "Unity UI Extensions", - "version": "2.3.2", + "version": "3.0.0-pre.1", "description": "An extension project for the Unity3D UI system, all crafted and contributed by the awesome Unity community", "author": "Simon darkside Jackson <@SimonDarksideJ>", "contributors": [ @@ -10,7 +10,7 @@ "twitter": "@SimonDarksideJ" } ], - "unity": "2020.3", + "unity": "6000.0", "repository": { "type": "git", "url": "git+https://github.com/Unity-UI-Extensions/com.unity.uiextensions.git" @@ -31,5 +31,8 @@ "path": "Examples~" } ], - "license": "BSD3" + "license": "BSD-3-Clause", + "dependencies": { + "com.unity.ugui": "2.0.0" + } }