diff --git a/.github/workflows/release.yml-sample b/.github/workflows/release.yml similarity index 100% rename from .github/workflows/release.yml-sample rename to .github/workflows/release.yml diff --git a/.github/workflows/test_pull_request.yml-sample b/.github/workflows/test_pull_request.yml similarity index 100% rename from .github/workflows/test_pull_request.yml-sample rename to .github/workflows/test_pull_request.yml diff --git a/README.md b/README.md index 6cfc702..194ee28 100644 --- a/README.md +++ b/README.md @@ -1,190 +1,88 @@ -

Unity AI Tools Template

+

Unity AI Particle System

-Stats +
-Template for AI MCP Tools for [AI Game Developer (Unity-MCP)](https://github.com/IvanMurzak/Unity-MCP). Use this template to create your custom MCP tools for Unity Engine in 30 minutes. Read more about custom MCP tools [here](https://github.com/IvanMurzak/Unity-MCP?tab=readme-ov-file#add-custom-mcp-tool). +[![MCP](https://badge.mcpx.dev 'MCP Server')](https://modelcontextprotocol.io/introduction) +[![OpenUPM](https://img.shields.io/npm/v/com.ivanmurzak.unity.mcp.particlesystem?label=OpenUPM®istry_uri=https://package.openupm.com&labelColor=333A41 'OpenUPM package')](https://openupm.com/packages/com.ivanmurzak.unity.mcp.particlesystem/) +[![Unity Editor](https://img.shields.io/badge/Editor-X?style=flat&logo=unity&labelColor=333A41&color=2A2A2A 'Unity Editor supported')](https://unity.com/releases/editor/archive) +[![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg 'Tests Passed')](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml)
+[![Discord](https://img.shields.io/badge/Discord-Join-7289da?logo=discord&logoColor=white&labelColor=333A41 'Join')](https://discord.gg/cfbdMZX99G) +[![Stars](https://img.shields.io/github/stars/IvanMurzak/Unity-AI-ParticleSystem 'Stars')](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/stargazers) +[![License](https://img.shields.io/github/license/IvanMurzak/Unity-AI-ParticleSystem?label=License&labelColor=333A41)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/blob/main/LICENSE) +[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua) -This template repository is designed to be easily updated into a real Unity package. Please follow the instruction bellow, it will help you to go through the entire process of package creation, distribution and installing. +
-# Steps to make your package +Promo -#### 1️⃣ Click the button to create new repository on GitHub using this template. +AI-powered tools for Unity ParticleSystem workflow. Inspect and modify ParticleSystem components directly through natural language commands. Configure emission settings, shape modules, velocity curves, color gradients, and all 24 particle system modules without manual inspector navigation. Ideal for rapid prototyping, procedural effects generation, and streamlining complex particle setups. Built on top of the [AI Game Developer](https://github.com/IvanMurzak/Unity-MCP) platform. -[![create new repository](https://user-images.githubusercontent.com/9135028/198753285-3d3c9601-0711-43c7-a8f2-d40ec42393a2.png)](https://github.com/IvanMurzak/Unity-AI-Tools-Template/generate) +### How to use -#### 2️⃣ Clone your new repository and open it in Unity Editor +- [Instructions](https://github.com/IvanMurzak/Unity-MCP?tab=readme-ov-file#step-2-install-mcp-client) +- [Video Tutorial for Visual Studio Code](https://www.youtube.com/watch?v=ZhP7Ju91mOE) +- [Video Tutorial for Visual Studio](https://www.youtube.com/watch?v=RGdak4T69mc) -#### 3️⃣ Initialize Project +[![DOWNLOAD INSTALLER](https://github.com/IvanMurzak/Unity-MCP/blob/main/docs/img/button/button_download.svg?raw=true)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/releases/download/1.0.0/AI-ParticleSystem-Installer.unitypackage) -Use the initialization script to rename the package and replace all placeholders. +### Stability status -```powershell -.\commands\init.ps1 -PackageId "com.company.package" -PackageName "My Package" -``` - -This script will: -- Rename directories and files. -- Replace `com.IvanMurzak.Unity.MCP.ParticleSystem`, `AI Particle System`, etc. in all files. - -#### 4️⃣ Manual Configuration - -1. **Update `package.json`** - Open `Unity-Package/Assets/root/package.json` and update: - - `description` - - `author` - - `keywords` - - `unity` (minimum supported Unity version) - -2. **Generate Meta Files** - To ensure all Unity meta files are correctly generated: - - Open Unity Hub. - - Add the `Installer` folder as a project. - - Add the `Unity-Package` folder as a project. - - Open both projects in Unity Editor. This will generate the necessary `.meta` files. - -#### 5️⃣ Add MCP Tools - -Decide what type of MCP tool you need: - -- **MCP tool for Unity Editor** - - ✔️ Works in Unity Editor (Edit Mode) - - ✔️ Works in Unity Editor (Play Mode) - - ✔️ Has access to Editor API - - ❌ Available in a game build -- **MCP tool for Unity Runtime** - - ✔️ Works in Unity Editor (Edit Mode) - - ✔️ Works in Unity Editor (Play Mode) - - ❌ Has access to Editor API - - ✔️ Available in a game build - -Based on your choice create script at the location - -- Editor: `Unity-Package/Assets/root/Editor` -- Runtime: `Unity-Package/Assets/root/Runtime` - -> Read detailed instructions about custom tool development [here](https://github.com/IvanMurzak/Unity-MCP?tab=readme-ov-file#add-custom-mcp-tool). - -```csharp -[McpPluginToolType] -public static class MyCustomTool -{ - [McpPluginTool("my-custom-feature", Title = "Do my custom feature")] - [Description("Put here the tool description for LLM.")] - public static Task DoTurn( - [Description("Add description to the input property, help LLM better understand it.)] - int figureId, - [Description("Add description to the input property, help LLM better understand it.)] - Vector2Int position) - { - // do any logic in background thread here - return MainThread.Instance.RunAsync(() => - { - // do any logic in main thread here - - return true; - }); - } -} -``` - - ---- - -## Optional improvements - -Next steps are not required to make everything to work, but they could be a great improvement for your new package. - -### Optional - Setup CI/CD - -To enable automatic testing and deployment: - -1. **Configure GitHub Secrets** - Go to `Settings` > `Secrets and variables` > `Actions` > `New repository secret` and add: - - `UNITY_EMAIL`: Your Unity account email. - - `UNITY_PASSWORD`: Your Unity account password. - - `UNITY_LICENSE`: Content of your `Unity_lic.ulf` file. - - Windows: `C:/ProgramData/Unity/Unity_lic.ulf` - - Mac: `/Library/Application Support/Unity/Unity_lic.ulf` - - Linux: `~/.local/share/unity3d/Unity/Unity_lic.ulf` - -2. **Enable Workflows** - Rename the sample workflow files to enable them: - - `.github/workflows/release.yml-sample` ➡️ `.github/workflows/release.yml` - - `.github/workflows/test_pull_request.yml-sample` ➡️ `.github/workflows/test_pull_request.yml` - -3. **Update Unity Version** - Open both `.yml` files and update the `UNITY_VERSION` (or similar variable) to match your project's Unity Editor version. - -4. **Automatic Deployment** - The release workflow triggers automatically when you push to the `main` branch with an incremented version in `package.json`. - -### Optional - Add files into `Assets/root` folder - -[Unity guidelines](https://docs.unity3d.com/Manual/cus-layout.html) about organizing files into the package root directory - -```text - - ├── package.json - ├── README.md - ├── CHANGELOG.md - ├── LICENSE.md - ├── Third Party Notices.md - ├── Editor - │ ├── [company-name].[package-name].Editor.asmdef - │ └── EditorExample.cs - ├── Runtime - │ ├── [company-name].[package-name].asmdef - │ └── RuntimeExample.cs - ├── Tests - │ ├── Editor - │ │ ├── [company-name].[package-name].Editor.Tests.asmdef - │ │ └── EditorExampleTest.cs - │ └── Runtime - │ ├── [company-name].[package-name].Tests.asmdef - │ └── RuntimeExampleTest.cs - ├── Samples~ - │ ├── SampleFolder1 - │ ├── SampleFolder2 - │ └── ... - └── Documentation~ - └── [package-name].md -``` +| Unity Version | Editmode | Playmode | Standalone | +| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 2022.3.62f3 | [![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg?job=test-unity-2022-3-62f3-editmode)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml) | [![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg?job=test-unity-2022-3-62f3-playmode)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml) | [![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg?job=test-unity-2022-3-62f3-standalone)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml) | +| 2023.2.22f1 | [![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg?job=test-unity-2023-2-22f1-editmode)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml) | [![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg?job=test-unity-2023-2-22f1-playmode)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml) | [![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg?job=test-unity-2023-2-22f1-standalone)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml) | +| 6000.3.1f1 | [![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg?job=test-unity-6000-3-1f1-editmode)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml) | [![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg?job=test-unity-6000-3-1f1-playmode)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml) | [![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg?job=test-unity-6000-3-1f1-standalone)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml) | -#### 8️⃣ Optional - Version Management +## AI Particle System Tools -To update the package version across all files (package.json, Installer.cs, etc.), use the bump version script: +ParticleSystem tools: -```powershell -.\commands\bump-version.ps1 -NewVersion "1.0.1" -``` - -##### Final polishing - -- Update the `README.md` file (this file) with information about your package. -- Copy the updated `README.md` to `Assets/root` as well. +- `particle-system-get` - Get ParticleSystem component data (state, modules, renderer settings) +- `particle-system-modify` - Modify ParticleSystem component (update any module properties) -> ⚠️ Everything outside of the `root` folder won't be added to your package. But still could be used for testing or showcasing your package at your repository. +Supported modules (24 total): -#### 9️⃣ Deploy to any registry you like +| Module | Description | +| ------ | ----------- | +| Main | Duration, looping, start lifetime, speed, size, rotation, color | +| Emission | Rate over time/distance, bursts | +| Shape | Emitter shape (sphere, cone, box, mesh, etc.) | +| Velocity Over Lifetime | Velocity changes over particle lifetime | +| Limit Velocity Over Lifetime | Speed limits and damping | +| Inherit Velocity | Velocity inheritance from emitter movement | +| Lifetime By Emitter Speed | Particle lifetime based on emitter speed | +| Force Over Lifetime | External forces applied to particles | +| Color Over Lifetime | Color gradient over particle lifetime | +| Color By Speed | Color based on particle speed | +| Size Over Lifetime | Size curve over particle lifetime | +| Size By Speed | Size based on particle speed | +| Rotation Over Lifetime | Angular velocity over lifetime | +| Rotation By Speed | Rotation based on particle speed | +| External Forces | Wind zone and force field influence | +| Noise | Turbulence and noise-based movement | +| Collision | World and plane collision | +| Trigger | Trigger zone interactions | +| Sub Emitters | Child particle systems on events | +| Texture Sheet Animation | Sprite sheet animation | +| Lights | Real-time lights attached to particles | +| Trails | Particle trail rendering | +| Custom Data | Custom per-particle data streams | +| Renderer | Material, render mode, sorting, shadows | -- [Deploy to OpenUPM](https://github.com/IvanMurzak/Unity-Package-Template/blob/main/Docs/Deploy-OpenUPM.md) (recommended) -- [Deploy using GitHub](https://github.com/IvanMurzak/Unity-Package-Template/blob/main/Docs/Deploy-GitHub.md) -- [Deploy to npmjs.com](https://github.com/IvanMurzak/Unity-Package-Template/blob/main/Docs/Deploy-npmjs.md) +## Installation -# Install your package into Unity Project +### Option 1 - Installer -When your package is distributed, you can install it into any Unity project. +- **[Download Installer](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/releases/download/1.0.0/AI-ParticleSystem-Installer.unitypackage)** +- **Import installer into Unity project** + > - You can double-click on the file - Unity will open it automatically + > - OR: Open Unity Editor first, then click on `Assets/Import Package/Custom Package`, and choose the file -> Don't install into the same Unity project, please use another one. +### Option 2 - OpenUPM-CLI - [Install OpenUPM-CLI](https://github.com/openupm/openupm-cli#installation) -- Open a command line at the root of Unity project (the folder which contains `Assets`) -- Execute the command (for `OpenUPM` hosted package) +- Open the command line in your Unity project folder - ```bash - openupm add AI Particle System - ``` - -# Final view in Unity Package Manager - -![image](https://user-images.githubusercontent.com/9135028/198777922-fdb71949-aee7-49c8-800f-7db885de9453.png) +```bash +openupm add com.ivanmurzak.unity.mcp.particlesystem +``` diff --git a/Unity-Package/Assets/Resources/AI-Game-Developer-Config.json b/Unity-Package/Assets/Resources/AI-Game-Developer-Config.json index 7c2d3f4..cbb1dbc 100644 --- a/Unity-Package/Assets/Resources/AI-Game-Developer-Config.json +++ b/Unity-Package/Assets/Resources/AI-Game-Developer-Config.json @@ -1,14 +1,6 @@ { "logLevel": 3, "tools": [ - { - "name": "particlesystem-get", - "enabled": true - }, - { - "name": "particlesystem-modify", - "enabled": true - }, { "name": "assets-copy", "enabled": true @@ -25,6 +17,10 @@ "name": "assets-find", "enabled": true }, + { + "name": "assets-find-built-in", + "enabled": true + }, { "name": "assets-get-data", "enabled": true @@ -204,6 +200,14 @@ { "name": "tests-run", "enabled": true + }, + { + "name": "particle-system-get", + "enabled": true + }, + { + "name": "particle-system-modify", + "enabled": true } ], "prompts": [ @@ -400,5 +404,5 @@ ], "host": "http://localhost:53451", "timeoutMs": 10000, - "keepConnected": true + "keepConnected": false } \ No newline at end of file diff --git a/Unity-Package/Assets/root/Editor/Scripts/Tools/ParticleSystem.Get.cs b/Unity-Package/Assets/root/Editor/Scripts/Tools/ParticleSystem.Get.cs index 3b9f786..20b1a07 100644 --- a/Unity-Package/Assets/root/Editor/Scripts/Tools/ParticleSystem.Get.cs +++ b/Unity-Package/Assets/root/Editor/Scripts/Tools/ParticleSystem.Get.cs @@ -22,7 +22,7 @@ namespace com.IvanMurzak.Unity.MCP.ParticleSystem.Editor.API { public partial class Tool_ParticleSystem { - public const string ParticleSystemGetToolId = "particlesystem-get"; + public const string ParticleSystemGetToolId = "particle-system-get"; [McpPluginTool ( diff --git a/Unity-Package/Assets/root/Editor/Scripts/Tools/ParticleSystem.Modify.cs b/Unity-Package/Assets/root/Editor/Scripts/Tools/ParticleSystem.Modify.cs index a36e6c9..3ca254a 100644 --- a/Unity-Package/Assets/root/Editor/Scripts/Tools/ParticleSystem.Modify.cs +++ b/Unity-Package/Assets/root/Editor/Scripts/Tools/ParticleSystem.Modify.cs @@ -25,7 +25,7 @@ namespace com.IvanMurzak.Unity.MCP.ParticleSystem.Editor.API { public partial class Tool_ParticleSystem { - public const string ParticleSystemModifyToolId = "particlesystem-modify"; + public const string ParticleSystemModifyToolId = "particle-system-modify"; [McpPluginTool ( diff --git a/Unity-Package/Assets/root/README.md b/Unity-Package/Assets/root/README.md new file mode 100644 index 0000000..194ee28 --- /dev/null +++ b/Unity-Package/Assets/root/README.md @@ -0,0 +1,88 @@ +

Unity AI Particle System

+ +
+ +[![MCP](https://badge.mcpx.dev 'MCP Server')](https://modelcontextprotocol.io/introduction) +[![OpenUPM](https://img.shields.io/npm/v/com.ivanmurzak.unity.mcp.particlesystem?label=OpenUPM®istry_uri=https://package.openupm.com&labelColor=333A41 'OpenUPM package')](https://openupm.com/packages/com.ivanmurzak.unity.mcp.particlesystem/) +[![Unity Editor](https://img.shields.io/badge/Editor-X?style=flat&logo=unity&labelColor=333A41&color=2A2A2A 'Unity Editor supported')](https://unity.com/releases/editor/archive) +[![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg 'Tests Passed')](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml)
+[![Discord](https://img.shields.io/badge/Discord-Join-7289da?logo=discord&logoColor=white&labelColor=333A41 'Join')](https://discord.gg/cfbdMZX99G) +[![Stars](https://img.shields.io/github/stars/IvanMurzak/Unity-AI-ParticleSystem 'Stars')](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/stargazers) +[![License](https://img.shields.io/github/license/IvanMurzak/Unity-AI-ParticleSystem?label=License&labelColor=333A41)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/blob/main/LICENSE) +[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua) + +
+ +Promo + +AI-powered tools for Unity ParticleSystem workflow. Inspect and modify ParticleSystem components directly through natural language commands. Configure emission settings, shape modules, velocity curves, color gradients, and all 24 particle system modules without manual inspector navigation. Ideal for rapid prototyping, procedural effects generation, and streamlining complex particle setups. Built on top of the [AI Game Developer](https://github.com/IvanMurzak/Unity-MCP) platform. + +### How to use + +- [Instructions](https://github.com/IvanMurzak/Unity-MCP?tab=readme-ov-file#step-2-install-mcp-client) +- [Video Tutorial for Visual Studio Code](https://www.youtube.com/watch?v=ZhP7Ju91mOE) +- [Video Tutorial for Visual Studio](https://www.youtube.com/watch?v=RGdak4T69mc) + +[![DOWNLOAD INSTALLER](https://github.com/IvanMurzak/Unity-MCP/blob/main/docs/img/button/button_download.svg?raw=true)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/releases/download/1.0.0/AI-ParticleSystem-Installer.unitypackage) + +### Stability status + +| Unity Version | Editmode | Playmode | Standalone | +| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 2022.3.62f3 | [![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg?job=test-unity-2022-3-62f3-editmode)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml) | [![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg?job=test-unity-2022-3-62f3-playmode)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml) | [![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg?job=test-unity-2022-3-62f3-standalone)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml) | +| 2023.2.22f1 | [![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg?job=test-unity-2023-2-22f1-editmode)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml) | [![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg?job=test-unity-2023-2-22f1-playmode)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml) | [![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg?job=test-unity-2023-2-22f1-standalone)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml) | +| 6000.3.1f1 | [![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg?job=test-unity-6000-3-1f1-editmode)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml) | [![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg?job=test-unity-6000-3-1f1-playmode)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml) | [![r](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/workflows/release/badge.svg?job=test-unity-6000-3-1f1-standalone)](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/actions/workflows/release.yml) | + +## AI Particle System Tools + +ParticleSystem tools: + +- `particle-system-get` - Get ParticleSystem component data (state, modules, renderer settings) +- `particle-system-modify` - Modify ParticleSystem component (update any module properties) + +Supported modules (24 total): + +| Module | Description | +| ------ | ----------- | +| Main | Duration, looping, start lifetime, speed, size, rotation, color | +| Emission | Rate over time/distance, bursts | +| Shape | Emitter shape (sphere, cone, box, mesh, etc.) | +| Velocity Over Lifetime | Velocity changes over particle lifetime | +| Limit Velocity Over Lifetime | Speed limits and damping | +| Inherit Velocity | Velocity inheritance from emitter movement | +| Lifetime By Emitter Speed | Particle lifetime based on emitter speed | +| Force Over Lifetime | External forces applied to particles | +| Color Over Lifetime | Color gradient over particle lifetime | +| Color By Speed | Color based on particle speed | +| Size Over Lifetime | Size curve over particle lifetime | +| Size By Speed | Size based on particle speed | +| Rotation Over Lifetime | Angular velocity over lifetime | +| Rotation By Speed | Rotation based on particle speed | +| External Forces | Wind zone and force field influence | +| Noise | Turbulence and noise-based movement | +| Collision | World and plane collision | +| Trigger | Trigger zone interactions | +| Sub Emitters | Child particle systems on events | +| Texture Sheet Animation | Sprite sheet animation | +| Lights | Real-time lights attached to particles | +| Trails | Particle trail rendering | +| Custom Data | Custom per-particle data streams | +| Renderer | Material, render mode, sorting, shadows | + +## Installation + +### Option 1 - Installer + +- **[Download Installer](https://github.com/IvanMurzak/Unity-AI-ParticleSystem/releases/download/1.0.0/AI-ParticleSystem-Installer.unitypackage)** +- **Import installer into Unity project** + > - You can double-click on the file - Unity will open it automatically + > - OR: Open Unity Editor first, then click on `Assets/Import Package/Custom Package`, and choose the file + +### Option 2 - OpenUPM-CLI + +- [Install OpenUPM-CLI](https://github.com/openupm/openupm-cli#installation) +- Open the command line in your Unity project folder + +```bash +openupm add com.ivanmurzak.unity.mcp.particlesystem +``` diff --git a/Unity-Package/Assets/root/README.md.meta b/Unity-Package/Assets/root/README.md.meta new file mode 100644 index 0000000..fadb4b6 --- /dev/null +++ b/Unity-Package/Assets/root/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8bc6fd3a14a58344aa5cb3c92647bd0a +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity-Package/Assets/root/Tests/Editor/BaseTest.cs b/Unity-Package/Assets/root/Tests/Editor/BaseTest.cs new file mode 100644 index 0000000..f9cc9e7 --- /dev/null +++ b/Unity-Package/Assets/root/Tests/Editor/BaseTest.cs @@ -0,0 +1,58 @@ +/* +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Author: Ivan Murzak (https://github.com/IvanMurzak) │ +│ Repository: GitHub (https://github.com/IvanMurzak/Unity-AI-ParticleSystem) │ +│ Copyright (c) 2025 Ivan Murzak │ +│ Licensed under the MIT License. │ +│ See the LICENSE file in the project root for more information. │ +└─────────────────────────────────────────────────────────────────────────────┘ +*/ + +#nullable enable +using System.Collections.Generic; +using System.Text.Json; +using com.IvanMurzak.McpPlugin.Common.Model; +using com.IvanMurzak.ReflectorNet; +using NUnit.Framework; +using UnityEngine; + +namespace com.IvanMurzak.Unity.MCP.ParticleSystem.Editor.Tests +{ + public class BaseTest : com.IvanMurzak.Unity.MCP.Editor.Tests.BaseTest + { + protected const string GO_ParticleSystemName = "TestParticleSystem"; + + protected virtual ResponseData RunToolAllowWarnings(string toolName, string json) + { + var reflector = McpPlugin.McpPlugin.Instance!.McpManager.Reflector; + + Debug.Log($"{toolName} Started with JSON:\n{json}"); + + var parameters = JsonSerializer.Deserialize>(json); + var request = new RequestCallTool(toolName, parameters!); + var task = McpPlugin.McpPlugin.Instance.McpManager.ToolManager!.RunCallTool(request); + var result = task.Result; + + Debug.Log($"{toolName} Completed"); + + var jsonResult = result.ToJson(reflector); + Debug.Log($"{toolName} Result:\n{jsonResult}"); + + Assert.IsFalse(result.Status == ResponseStatus.Error, $"Tool call failed with error status: {result.Message}"); + Assert.IsNotNull(result.Message, $"Tool call returned null message"); + Assert.IsFalse(result.Message!.Contains("[Error]"), $"Tool call failed with error: {result.Message}"); + Assert.IsNotNull(result.Value, $"Tool call returned null value"); + Assert.IsFalse(result.Value!.Status == ResponseStatus.Error, $"Tool call failed"); + Assert.IsFalse(jsonResult!.Contains("[Error]"), $"Tool call failed with error in JSON: {jsonResult}"); + + return result; + } + + protected static GameObject CreateGameObjectWithParticleSystem(string name = "TestParticleSystem") + { + var go = new GameObject(name); + go.AddComponent(); + return go; + } + } +} diff --git a/Unity-Package/Assets/root/Tests/Editor/DemoTest.cs.meta b/Unity-Package/Assets/root/Tests/Editor/BaseTest.cs.meta similarity index 83% rename from Unity-Package/Assets/root/Tests/Editor/DemoTest.cs.meta rename to Unity-Package/Assets/root/Tests/Editor/BaseTest.cs.meta index 403ca80..33d472b 100644 --- a/Unity-Package/Assets/root/Tests/Editor/DemoTest.cs.meta +++ b/Unity-Package/Assets/root/Tests/Editor/BaseTest.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: ef990a1b24aff3641a80ebae262c9c05 +guid: bafc0976b0f13a043b58a6a999e8d88e MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Unity-Package/Assets/root/Tests/Editor/DemoTest.cs b/Unity-Package/Assets/root/Tests/Editor/DemoTest.cs deleted file mode 100644 index 0d124b1..0000000 --- a/Unity-Package/Assets/root/Tests/Editor/DemoTest.cs +++ /dev/null @@ -1,45 +0,0 @@ -/* -┌─────────────────────────────────────────────────────────────────────────────┐ -│ Author: Ivan Murzak (https://github.com/IvanMurzak) │ -│ Repository: GitHub (https://github.com/IvanMurzak/Unity-AI-ParticleSystem) │ -│ Copyright (c) 2025 Ivan Murzak │ -│ Licensed under the MIT License. │ -│ See the LICENSE file in the project root for more information. │ -└─────────────────────────────────────────────────────────────────────────────┘ -*/ - -#nullable enable -using System.Collections; -using NUnit.Framework; -using UnityEditor; -using UnityEngine; -using UnityEngine.TestTools; - -namespace com.IvanMurzak.Unity.MCP.ParticleSystem.Editor.Tests -{ - public partial class DemoTest - { - [UnitySetUp] - public IEnumerator SetUp() - { - Debug.Log($"[{nameof(DemoTest)}] SetUp"); - yield return null; - } - [UnityTearDown] - public IEnumerator TearDown() - { - Debug.Log($"[{nameof(DemoTest)}] TearDown"); - yield return null; - } - - [UnityTest] - public IEnumerator Always_Valid_Test() - { - Debug.Log($"[{nameof(DemoTest)}] Test Log Message ABC"); - Debug.Log($"[{nameof(DemoTest)}] Test Log Message ABC 123"); - - Assert.IsTrue(true, "This test is a placeholder and should be replaced with actual test logic."); - yield return null; - } - } -} diff --git a/Unity-Package/Assets/root/Tests/Editor/TestToolParticleSystemGet.cs b/Unity-Package/Assets/root/Tests/Editor/TestToolParticleSystemGet.cs new file mode 100644 index 0000000..965b1db --- /dev/null +++ b/Unity-Package/Assets/root/Tests/Editor/TestToolParticleSystemGet.cs @@ -0,0 +1,311 @@ +/* +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Author: Ivan Murzak (https://github.com/IvanMurzak) │ +│ Repository: GitHub (https://github.com/IvanMurzak/Unity-AI-ParticleSystem) │ +│ Copyright (c) 2025 Ivan Murzak │ +│ Licensed under the MIT License. │ +│ See the LICENSE file in the project root for more information. │ +└─────────────────────────────────────────────────────────────────────────────┘ +*/ + +#nullable enable +using System.Collections; +using com.IvanMurzak.Unity.MCP.ParticleSystem.Editor.API; +using com.IvanMurzak.Unity.MCP.Runtime.Data; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace com.IvanMurzak.Unity.MCP.ParticleSystem.Editor.Tests +{ + public class TestToolParticleSystemGet : BaseTest + { + + #region Get Tool - Direct API Tests + + [UnityTest] + public IEnumerator Get_WithInstanceID_ReturnsMainModule() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + var ps = go.GetComponent(); + Assert.IsNotNull(ps, "ParticleSystem component should exist"); + + var tool = new Tool_ParticleSystem(); + var result = tool.Get( + gameObjectRef: new GameObjectRef(go.GetInstanceID()), + includeMain: true + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsNotNull(result.gameObjectRef, "GameObjectRef should not be null"); + Assert.IsNotNull(result.componentRef, "ComponentRef should not be null"); + Assert.IsNotNull(result.main, "Main module should not be null"); + Assert.IsTrue(result.componentIndex >= 0, "Component index should be valid"); + + yield return null; + } + + [UnityTest] + public IEnumerator Get_WithPath_ReturnsMainModule() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + + var tool = new Tool_ParticleSystem(); + var result = tool.Get( + gameObjectRef: new GameObjectRef { Path = GO_ParticleSystemName }, + includeMain: true + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsNotNull(result.main, "Main module should not be null"); + + yield return null; + } + + [UnityTest] + public IEnumerator Get_WithName_ReturnsMainModule() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + + var tool = new Tool_ParticleSystem(); + var result = tool.Get( + gameObjectRef: new GameObjectRef { Name = GO_ParticleSystemName }, + includeMain: true + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsNotNull(result.main, "Main module should not be null"); + + yield return null; + } + + [UnityTest] + public IEnumerator Get_IncludeEmission_ReturnsEmissionModule() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + + var tool = new Tool_ParticleSystem(); + var result = tool.Get( + gameObjectRef: new GameObjectRef(go.GetInstanceID()), + includeMain: false, + includeEmission: true + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsNull(result.main, "Main module should be null when not requested"); + Assert.IsNotNull(result.emission, "Emission module should not be null"); + + yield return null; + } + + [UnityTest] + public IEnumerator Get_IncludeShape_ReturnsShapeModule() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + + var tool = new Tool_ParticleSystem(); + var result = tool.Get( + gameObjectRef: new GameObjectRef(go.GetInstanceID()), + includeMain: false, + includeShape: true + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsNull(result.main, "Main module should be null when not requested"); + Assert.IsNotNull(result.shape, "Shape module should not be null"); + + yield return null; + } + + [UnityTest] + public IEnumerator Get_IncludeColorOverLifetime_ReturnsColorModule() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + + var tool = new Tool_ParticleSystem(); + var result = tool.Get( + gameObjectRef: new GameObjectRef(go.GetInstanceID()), + includeMain: false, + includeColorOverLifetime: true + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsNotNull(result.colorOverLifetime, "ColorOverLifetime module should not be null"); + + yield return null; + } + + [UnityTest] + public IEnumerator Get_IncludeNoise_ReturnsNoiseModule() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + + var tool = new Tool_ParticleSystem(); + var result = tool.Get( + gameObjectRef: new GameObjectRef(go.GetInstanceID()), + includeMain: false, + includeNoise: true + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsNotNull(result.noise, "Noise module should not be null"); + + yield return null; + } + + [UnityTest] + public IEnumerator Get_IncludeRenderer_ReturnsRendererModule() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + + var tool = new Tool_ParticleSystem(); + var result = tool.Get( + gameObjectRef: new GameObjectRef(go.GetInstanceID()), + includeMain: false, + includeRenderer: true + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsNotNull(result.renderer, "Renderer module should not be null"); + + yield return null; + } + + [UnityTest] + public IEnumerator Get_IncludeAll_ReturnsAllModules() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + + var tool = new Tool_ParticleSystem(); + var result = tool.Get( + gameObjectRef: new GameObjectRef(go.GetInstanceID()), + includeAll: true + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsNotNull(result.main, "Main module should not be null"); + Assert.IsNotNull(result.emission, "Emission module should not be null"); + Assert.IsNotNull(result.shape, "Shape module should not be null"); + Assert.IsNotNull(result.velocityOverLifetime, "VelocityOverLifetime module should not be null"); + Assert.IsNotNull(result.colorOverLifetime, "ColorOverLifetime module should not be null"); + Assert.IsNotNull(result.sizeOverLifetime, "SizeOverLifetime module should not be null"); + Assert.IsNotNull(result.noise, "Noise module should not be null"); + Assert.IsNotNull(result.collision, "Collision module should not be null"); + Assert.IsNotNull(result.trails, "Trails module should not be null"); + Assert.IsNotNull(result.renderer, "Renderer module should not be null"); + + yield return null; + } + + [UnityTest] + public IEnumerator Get_ReturnsParticleSystemState() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + var ps = go.GetComponent(); + + // Stop the particle system to have a predictable state + ps.Stop(true, UnityEngine.ParticleSystemStopBehavior.StopEmittingAndClear); + + var tool = new Tool_ParticleSystem(); + var result = tool.Get( + gameObjectRef: new GameObjectRef(go.GetInstanceID()), + includeMain: true + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsFalse(result.isPlaying, "ParticleSystem should not be playing after Stop"); + Assert.IsTrue(result.isStopped, "ParticleSystem should be stopped"); + Assert.AreEqual(0, result.particleCount, "Particle count should be 0 after clear"); + + yield return null; + } + + #endregion + + #region Get Tool - JSON API Tests + + [UnityTest] + public IEnumerator GetJson_WithInstanceID_ReturnsMainModule() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + + var json = $@"{{ + ""gameObjectRef"": {{ + ""instanceID"": {go.GetInstanceID()} + }}, + ""includeMain"": true + }}"; + + var result = RunToolAllowWarnings(Tool_ParticleSystem.ParticleSystemGetToolId, json); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsNotNull(result.Value, "Result value should not be null"); + + yield return null; + } + + [UnityTest] + public IEnumerator GetJson_WithPath_ReturnsMainModule() + { + CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + + var json = $@"{{ + ""gameObjectRef"": {{ + ""path"": ""{GO_ParticleSystemName}"" + }}, + ""includeMain"": true + }}"; + + var result = RunToolAllowWarnings(Tool_ParticleSystem.ParticleSystemGetToolId, json); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsNotNull(result.Value, "Result value should not be null"); + + yield return null; + } + + [UnityTest] + public IEnumerator GetJson_IncludeMultipleModules() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + + var json = $@"{{ + ""gameObjectRef"": {{ + ""instanceID"": {go.GetInstanceID()} + }}, + ""includeMain"": true, + ""includeEmission"": true, + ""includeShape"": true + }}"; + + var result = RunToolAllowWarnings(Tool_ParticleSystem.ParticleSystemGetToolId, json); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsNotNull(result.Value, "Result value should not be null"); + + yield return null; + } + + [UnityTest] + public IEnumerator GetJson_IncludeAll() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + + var json = $@"{{ + ""gameObjectRef"": {{ + ""instanceID"": {go.GetInstanceID()} + }}, + ""includeAll"": true + }}"; + + var result = RunToolAllowWarnings(Tool_ParticleSystem.ParticleSystemGetToolId, json); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsNotNull(result.Value, "Result value should not be null"); + + yield return null; + } + + #endregion + } +} diff --git a/Unity-Package/Assets/root/Tests/Editor/TestToolParticleSystemGet.cs.meta b/Unity-Package/Assets/root/Tests/Editor/TestToolParticleSystemGet.cs.meta new file mode 100644 index 0000000..6e509df --- /dev/null +++ b/Unity-Package/Assets/root/Tests/Editor/TestToolParticleSystemGet.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0016c08bd05d3df4db35f5fc60e47dfd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity-Package/Assets/root/Tests/Editor/TestToolParticleSystemModify.cs b/Unity-Package/Assets/root/Tests/Editor/TestToolParticleSystemModify.cs new file mode 100644 index 0000000..9de7ce0 --- /dev/null +++ b/Unity-Package/Assets/root/Tests/Editor/TestToolParticleSystemModify.cs @@ -0,0 +1,631 @@ +/* +┌─────────────────────────────────────────────────────────────────────────────┐ +│ Author: Ivan Murzak (https://github.com/IvanMurzak) │ +│ Repository: GitHub (https://github.com/IvanMurzak/Unity-AI-ParticleSystem) │ +│ Copyright (c) 2025 Ivan Murzak │ +│ Licensed under the MIT License. │ +│ See the LICENSE file in the project root for more information. │ +└─────────────────────────────────────────────────────────────────────────────┘ +*/ + +#nullable enable +using System.Collections; +using com.IvanMurzak.ReflectorNet.Model; +using com.IvanMurzak.Unity.MCP.ParticleSystem.Editor.API; +using com.IvanMurzak.Unity.MCP.Runtime.Data; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace com.IvanMurzak.Unity.MCP.ParticleSystem.Editor.Tests +{ + public class TestToolParticleSystemModify : BaseTest + { + #region Modify Tool - Direct API Tests + + [UnityTest] + public IEnumerator Modify_MainModule_Duration() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + var ps = go.GetComponent(); + var reflector = McpPlugin.McpPlugin.Instance!.McpManager.Reflector; + + var mainModule = ps.main; + var newDuration = 10.0f; + + // Create the main module diff with new duration + // Note: Pass null as value to avoid serializing all module properties. + // Only add the specific properties we want to modify. + var mainDiff = SerializedMember.FromValue( + reflector: reflector, + name: nameof(ps.main), + type: typeof(UnityEngine.ParticleSystem.MainModule), + value: null) + .AddProperty(SerializedMember.FromValue( + reflector: reflector, + name: nameof(mainModule.duration), + value: newDuration)); + + var tool = new Tool_ParticleSystem(); + var result = tool.Modify( + gameObjectRef: new GameObjectRef(go.GetInstanceID()), + main: mainDiff + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsTrue(result.success, "Modification should be successful"); + Assert.AreEqual(newDuration, ps.main.duration, 0.001f, "Duration should be modified"); + + yield return null; + } + + [UnityTest] + public IEnumerator Modify_MainModule_MaxParticles() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + var ps = go.GetComponent(); + var reflector = McpPlugin.McpPlugin.Instance!.McpManager.Reflector; + + var mainModule = ps.main; + var newMaxParticles = 500; + + var mainDiff = SerializedMember.FromValue( + reflector: reflector, + name: nameof(ps.main), + type: typeof(UnityEngine.ParticleSystem.MainModule), + value: null) + .AddProperty(SerializedMember.FromValue( + reflector: reflector, + name: nameof(mainModule.maxParticles), + value: newMaxParticles)); + + var tool = new Tool_ParticleSystem(); + var result = tool.Modify( + gameObjectRef: new GameObjectRef(go.GetInstanceID()), + main: mainDiff + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsTrue(result.success, "Modification should be successful"); + Assert.AreEqual(newMaxParticles, ps.main.maxParticles, "MaxParticles should be modified"); + + yield return null; + } + + [UnityTest] + public IEnumerator Modify_MainModule_Loop() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + var ps = go.GetComponent(); + var reflector = McpPlugin.McpPlugin.Instance!.McpManager.Reflector; + + var mainModule = ps.main; + var originalLoop = mainModule.loop; + var newLoop = !originalLoop; + + var mainDiff = SerializedMember.FromValue( + reflector: reflector, + name: nameof(ps.main), + type: typeof(UnityEngine.ParticleSystem.MainModule), + value: null) + .AddProperty(SerializedMember.FromValue( + reflector: reflector, + name: nameof(mainModule.loop), + value: newLoop)); + + var tool = new Tool_ParticleSystem(); + var result = tool.Modify( + gameObjectRef: new GameObjectRef(go.GetInstanceID()), + main: mainDiff + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsTrue(result.success, "Modification should be successful"); + Assert.AreEqual(newLoop, ps.main.loop, "Loop should be modified"); + + yield return null; + } + + [UnityTest] + public IEnumerator Modify_EmissionModule_Enabled() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + var ps = go.GetComponent(); + var reflector = McpPlugin.McpPlugin.Instance!.McpManager.Reflector; + + var emissionModule = ps.emission; + var originalEnabled = emissionModule.enabled; + var newEnabled = !originalEnabled; + + var emissionDiff = SerializedMember.FromValue( + reflector: reflector, + name: nameof(ps.emission), + type: typeof(UnityEngine.ParticleSystem.EmissionModule), + value: null) + .AddProperty(SerializedMember.FromValue( + reflector: reflector, + name: nameof(emissionModule.enabled), + value: newEnabled)); + + var tool = new Tool_ParticleSystem(); + var result = tool.Modify( + gameObjectRef: new GameObjectRef(go.GetInstanceID()), + emission: emissionDiff + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsTrue(result.success, "Modification should be successful"); + Assert.AreEqual(newEnabled, ps.emission.enabled, "Emission enabled should be modified"); + + yield return null; + } + + [UnityTest] + public IEnumerator Modify_ShapeModule_Enabled() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + var ps = go.GetComponent(); + var reflector = McpPlugin.McpPlugin.Instance!.McpManager.Reflector; + + var shapeModule = ps.shape; + var originalEnabled = shapeModule.enabled; + var newEnabled = !originalEnabled; + + var shapeDiff = SerializedMember.FromValue( + reflector: reflector, + name: nameof(ps.shape), + type: typeof(UnityEngine.ParticleSystem.ShapeModule), + value: null) + .AddProperty(SerializedMember.FromValue( + reflector: reflector, + name: nameof(shapeModule.enabled), + value: newEnabled)); + + var tool = new Tool_ParticleSystem(); + var result = tool.Modify( + gameObjectRef: new GameObjectRef(go.GetInstanceID()), + shape: shapeDiff + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsTrue(result.success, "Modification should be successful"); + Assert.AreEqual(newEnabled, ps.shape.enabled, "Shape enabled should be modified"); + + yield return null; + } + + [UnityTest] + public IEnumerator Modify_ShapeModule_Radius() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + var ps = go.GetComponent(); + var reflector = McpPlugin.McpPlugin.Instance!.McpManager.Reflector; + + var shapeModule = ps.shape; + var newRadius = 5.0f; + + var shapeDiff = SerializedMember.FromValue( + reflector: reflector, + name: nameof(ps.shape), + type: typeof(UnityEngine.ParticleSystem.ShapeModule), + value: null) + .AddProperty(SerializedMember.FromValue( + reflector: reflector, + name: nameof(shapeModule.radius), + value: newRadius)); + + var tool = new Tool_ParticleSystem(); + var result = tool.Modify( + gameObjectRef: new GameObjectRef(go.GetInstanceID()), + shape: shapeDiff + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsTrue(result.success, "Modification should be successful"); + Assert.AreEqual(newRadius, ps.shape.radius, 0.001f, "Shape radius should be modified"); + + yield return null; + } + + [UnityTest] + public IEnumerator Modify_NoiseModule_Enabled() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + var ps = go.GetComponent(); + var reflector = McpPlugin.McpPlugin.Instance!.McpManager.Reflector; + + var noiseModule = ps.noise; + var newEnabled = true; + + var noiseDiff = SerializedMember.FromValue( + reflector: reflector, + name: nameof(ps.noise), + type: typeof(UnityEngine.ParticleSystem.NoiseModule), + value: null) + .AddProperty(SerializedMember.FromValue( + reflector: reflector, + name: nameof(noiseModule.enabled), + value: newEnabled)); + + var tool = new Tool_ParticleSystem(); + var result = tool.Modify( + gameObjectRef: new GameObjectRef(go.GetInstanceID()), + noise: noiseDiff + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsTrue(result.success, "Modification should be successful"); + Assert.AreEqual(newEnabled, ps.noise.enabled, "Noise enabled should be modified"); + + yield return null; + } + + [UnityTest] + public IEnumerator Modify_TrailsModule_Enabled() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + var ps = go.GetComponent(); + var reflector = McpPlugin.McpPlugin.Instance!.McpManager.Reflector; + + var trailsModule = ps.trails; + var newEnabled = true; + + var trailsDiff = SerializedMember.FromValue( + reflector: reflector, + name: nameof(ps.trails), + type: typeof(UnityEngine.ParticleSystem.TrailModule), + value: null) + .AddProperty(SerializedMember.FromValue( + reflector: reflector, + name: nameof(trailsModule.enabled), + value: newEnabled)); + + var tool = new Tool_ParticleSystem(); + var result = tool.Modify( + gameObjectRef: new GameObjectRef(go.GetInstanceID()), + trails: trailsDiff + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsTrue(result.success, "Modification should be successful"); + Assert.AreEqual(newEnabled, ps.trails.enabled, "Trails enabled should be modified"); + + yield return null; + } + + [UnityTest] + public IEnumerator Modify_MultipleModules() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + var ps = go.GetComponent(); + var reflector = McpPlugin.McpPlugin.Instance!.McpManager.Reflector; + + var mainModule = ps.main; + var shapeModule = ps.shape; + var noiseModule = ps.noise; + + var newDuration = 15.0f; + var newShapeRadius = 3.0f; + var noiseEnabled = true; + + var mainDiff = SerializedMember.FromValue( + reflector: reflector, + name: nameof(ps.main), + type: typeof(UnityEngine.ParticleSystem.MainModule), + value: null) + .AddProperty(SerializedMember.FromValue( + reflector: reflector, + name: nameof(mainModule.duration), + value: newDuration)); + + var shapeDiff = SerializedMember.FromValue( + reflector: reflector, + name: nameof(ps.shape), + type: typeof(UnityEngine.ParticleSystem.ShapeModule), + value: null) + .AddProperty(SerializedMember.FromValue( + reflector: reflector, + name: nameof(shapeModule.radius), + value: newShapeRadius)); + + var noiseDiff = SerializedMember.FromValue( + reflector: reflector, + name: nameof(ps.noise), + type: typeof(UnityEngine.ParticleSystem.NoiseModule), + value: null) + .AddProperty(SerializedMember.FromValue( + reflector: reflector, + name: nameof(noiseModule.enabled), + value: noiseEnabled)); + + var tool = new Tool_ParticleSystem(); + var result = tool.Modify( + gameObjectRef: new GameObjectRef(go.GetInstanceID()), + main: mainDiff, + shape: shapeDiff, + noise: noiseDiff + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsTrue(result.success, "Modification should be successful"); + Assert.AreEqual(newDuration, ps.main.duration, 0.001f, "Duration should be modified"); + Assert.AreEqual(newShapeRadius, ps.shape.radius, 0.001f, "Shape radius should be modified"); + Assert.AreEqual(noiseEnabled, ps.noise.enabled, "Noise enabled should be modified"); + + yield return null; + } + + [UnityTest] + public IEnumerator Modify_WithPath_Works() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + var ps = go.GetComponent(); + var reflector = McpPlugin.McpPlugin.Instance!.McpManager.Reflector; + + var mainModule = ps.main; + var newDuration = 20.0f; + + var mainDiff = SerializedMember.FromValue( + reflector: reflector, + name: nameof(ps.main), + type: typeof(UnityEngine.ParticleSystem.MainModule), + value: null) + .AddProperty(SerializedMember.FromValue( + reflector: reflector, + name: nameof(mainModule.duration), + value: newDuration)); + + var tool = new Tool_ParticleSystem(); + var result = tool.Modify( + gameObjectRef: new GameObjectRef { Path = GO_ParticleSystemName }, + main: mainDiff + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsTrue(result.success, "Modification should be successful"); + Assert.AreEqual(newDuration, ps.main.duration, 0.001f, "Duration should be modified"); + + yield return null; + } + + [UnityTest] + public IEnumerator Modify_ReturnsLogs() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + var ps = go.GetComponent(); + var reflector = McpPlugin.McpPlugin.Instance!.McpManager.Reflector; + + var mainModule = ps.main; + + var mainDiff = SerializedMember.FromValue( + reflector: reflector, + name: nameof(ps.main), + type: typeof(UnityEngine.ParticleSystem.MainModule), + value: null) + .AddProperty(SerializedMember.FromValue( + reflector: reflector, + name: nameof(mainModule.duration), + value: 5.0f)); + + var tool = new Tool_ParticleSystem(); + var result = tool.Modify( + gameObjectRef: new GameObjectRef(go.GetInstanceID()), + main: mainDiff + ); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsNotNull(result.logs, "Logs should not be null"); + Assert.IsTrue(result.logs!.Length > 0, "Should have at least one log entry"); + + yield return null; + } + + #endregion + + #region Modify Tool - JSON API Tests + + [UnityTest] + public IEnumerator ModifyJson_MainModule_Duration() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + var ps = go.GetComponent(); + + var newDuration = 25.0f; + + var json = $@"{{ + ""gameObjectRef"": {{ + ""instanceID"": {go.GetInstanceID()} + }}, + ""main"": {{ + ""typeName"": ""UnityEngine.ParticleSystem+MainModule"", + ""props"": [ + {{ + ""name"": ""duration"", + ""typeName"": ""System.Single"", + ""value"": {newDuration} + }} + ] + }} + }}"; + + var result = RunToolAllowWarnings(Tool_ParticleSystem.ParticleSystemModifyToolId, json); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.AreEqual(newDuration, ps.main.duration, 0.001f, "Duration should be modified"); + + yield return null; + } + + [UnityTest] + public IEnumerator ModifyJson_MainModule_MaxParticles() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + var ps = go.GetComponent(); + + var newMaxParticles = 2000; + + var json = $@"{{ + ""gameObjectRef"": {{ + ""instanceID"": {go.GetInstanceID()} + }}, + ""main"": {{ + ""typeName"": ""UnityEngine.ParticleSystem+MainModule"", + ""props"": [ + {{ + ""name"": ""maxParticles"", + ""typeName"": ""System.Int32"", + ""value"": {newMaxParticles} + }} + ] + }} + }}"; + + var result = RunToolAllowWarnings(Tool_ParticleSystem.ParticleSystemModifyToolId, json); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.AreEqual(newMaxParticles, ps.main.maxParticles, "MaxParticles should be modified"); + + yield return null; + } + + [UnityTest] + public IEnumerator ModifyJson_ShapeModule_Radius() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + var ps = go.GetComponent(); + + var newRadius = 7.5f; + + var json = $@"{{ + ""gameObjectRef"": {{ + ""instanceID"": {go.GetInstanceID()} + }}, + ""shape"": {{ + ""typeName"": ""UnityEngine.ParticleSystem+ShapeModule"", + ""props"": [ + {{ + ""name"": ""radius"", + ""typeName"": ""System.Single"", + ""value"": {newRadius} + }} + ] + }} + }}"; + + var result = RunToolAllowWarnings(Tool_ParticleSystem.ParticleSystemModifyToolId, json); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.AreEqual(newRadius, ps.shape.radius, 0.001f, "Shape radius should be modified"); + + yield return null; + } + + [UnityTest] + public IEnumerator ModifyJson_NoiseModule_Enable() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + var ps = go.GetComponent(); + + var json = $@"{{ + ""gameObjectRef"": {{ + ""instanceID"": {go.GetInstanceID()} + }}, + ""noise"": {{ + ""typeName"": ""UnityEngine.ParticleSystem+NoiseModule"", + ""props"": [ + {{ + ""name"": ""enabled"", + ""typeName"": ""System.Boolean"", + ""value"": true + }} + ] + }} + }}"; + + var result = RunToolAllowWarnings(Tool_ParticleSystem.ParticleSystemModifyToolId, json); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.IsTrue(ps.noise.enabled, "Noise should be enabled"); + + yield return null; + } + + [UnityTest] + public IEnumerator ModifyJson_MultipleModules() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + var ps = go.GetComponent(); + + var newDuration = 30.0f; + var newRadius = 10.0f; + + var json = $@"{{ + ""gameObjectRef"": {{ + ""instanceID"": {go.GetInstanceID()} + }}, + ""main"": {{ + ""typeName"": ""UnityEngine.ParticleSystem+MainModule"", + ""props"": [ + {{ + ""name"": ""duration"", + ""typeName"": ""System.Single"", + ""value"": {newDuration} + }} + ] + }}, + ""shape"": {{ + ""typeName"": ""UnityEngine.ParticleSystem+ShapeModule"", + ""props"": [ + {{ + ""name"": ""radius"", + ""typeName"": ""System.Single"", + ""value"": {newRadius} + }} + ] + }} + }}"; + + var result = RunToolAllowWarnings(Tool_ParticleSystem.ParticleSystemModifyToolId, json); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.AreEqual(newDuration, ps.main.duration, 0.001f, "Duration should be modified"); + Assert.AreEqual(newRadius, ps.shape.radius, 0.001f, "Shape radius should be modified"); + + yield return null; + } + + [UnityTest] + public IEnumerator ModifyJson_WithPath() + { + var go = CreateGameObjectWithParticleSystem(GO_ParticleSystemName); + var ps = go.GetComponent(); + + var newDuration = 35.0f; + + var json = $@"{{ + ""gameObjectRef"": {{ + ""path"": ""{GO_ParticleSystemName}"" + }}, + ""main"": {{ + ""typeName"": ""UnityEngine.ParticleSystem+MainModule"", + ""props"": [ + {{ + ""name"": ""duration"", + ""typeName"": ""System.Single"", + ""value"": {newDuration} + }} + ] + }} + }}"; + + var result = RunToolAllowWarnings(Tool_ParticleSystem.ParticleSystemModifyToolId, json); + + Assert.IsNotNull(result, "Result should not be null"); + Assert.AreEqual(newDuration, ps.main.duration, 0.001f, "Duration should be modified"); + + yield return null; + } + + #endregion + } +} diff --git a/Unity-Package/Assets/root/Tests/Editor/TestToolParticleSystemModify.cs.meta b/Unity-Package/Assets/root/Tests/Editor/TestToolParticleSystemModify.cs.meta new file mode 100644 index 0000000..b6fdd8b --- /dev/null +++ b/Unity-Package/Assets/root/Tests/Editor/TestToolParticleSystemModify.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bb08a947fb562a74f8781a01cd9b970b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity-Package/Assets/root/package.json b/Unity-Package/Assets/root/package.json index 2459333..c12a600 100644 --- a/Unity-Package/Assets/root/package.json +++ b/Unity-Package/Assets/root/package.json @@ -6,14 +6,29 @@ "url": "https://github.com/IvanMurzak" }, "keywords": [ + "unity", "particlesystem", - "unity" + "particle-system", + "particles", + "vfx", + "visual-effects", + "ai", + "mcp", + "natural-language", + "procedural", + "prototyping", + "emission", + "shape", + "velocity", + "gradient", + "renderer", + "editor-tooling" ], "version": "1.0.0", "unity": "2022.3", - "description": "Some description", + "description": "AI-powered tools for Unity ParticleSystem workflow. Inspect and modify ParticleSystem components via natural language commands—configure emission, shape, velocity curves, color gradients, and all modules without manual inspector navigation. Ideal for rapid prototyping and procedural effects generation. Built on top of the AI Game Developer (Unity-MCP) platform.", "dependencies": { - "com.ivanmurzak.unity.mcp": "0.39.0", + "com.ivanmurzak.unity.mcp": "0.39.2", "com.unity.modules.particlesystem": "1.0.0" }, "scopedRegistries": [ diff --git a/Unity-Package/Packages/manifest.json b/Unity-Package/Packages/manifest.json index b6a5310..0466963 100644 --- a/Unity-Package/Packages/manifest.json +++ b/Unity-Package/Packages/manifest.json @@ -1,6 +1,6 @@ { "dependencies": { - "com.ivanmurzak.unity.mcp": "0.39.0", + "com.ivanmurzak.unity.mcp": "0.39.2", "com.unity.ide.visualstudio": "2.0.26", "com.unity.test-framework": "1.1.33", "com.unity.modules.assetbundle": "1.0.0", @@ -32,4 +32,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/Unity-Package/Packages/packages-lock.json b/Unity-Package/Packages/packages-lock.json index 98568bd..a47a706 100644 --- a/Unity-Package/Packages/packages-lock.json +++ b/Unity-Package/Packages/packages-lock.json @@ -1,7 +1,7 @@ { "dependencies": { "com.ivanmurzak.unity.mcp": { - "version": "0.39.0", + "version": "0.39.2", "depth": 0, "source": "registry", "dependencies": { diff --git a/docs/img/GitHub Promo.jpg b/docs/img/GitHub Promo.jpg new file mode 100644 index 0000000..a3ed889 Binary files /dev/null and b/docs/img/GitHub Promo.jpg differ diff --git a/docs/img/GitHub Promo.png b/docs/img/GitHub Promo.png new file mode 100644 index 0000000..e56acf2 Binary files /dev/null and b/docs/img/GitHub Promo.png differ diff --git a/docs/img/particle-system-glitch.gif b/docs/img/particle-system-glitch.gif new file mode 100644 index 0000000..083204e Binary files /dev/null and b/docs/img/particle-system-glitch.gif differ