【Unity 实战篇】文生图

前言

大家好,我是Mark。 近年来,大型模型在技术领域备受瞩目,各行各业的人们都在热议这些模型。笔者对新技术的热情也颇高闲暇之余也尝试做了些示例。关于如何接入文本大模型的教程网上已经非常丰富,而本文将另辟蹊径,从图像大模型出发,手把手教大家如何从零开始构建一个文生图功能。本文将结合ComfyUI 实现文生图效果,在此之前要想流畅运行ComfyUI可能需要台高配电脑(笔者电脑3060显卡供参考),如果电脑性能较差同学可以考虑云电脑。

一、ComfyUI 篇

1. ComfyUI 基础介绍

ComfyUI 是一个基于节点的图形用户界面,专为 Stable Diffusion 设计,提供高度灵活的图像生成工作流构建体验。它适合数字艺术家、AI 研究人员、内容创作者、教育者、游戏开发者和产品设计师等多种用户群体,通过直观的节点式操作,实现复杂图像生成、深度编辑和模型测试。相比 WebUI,虽然学习曲线稍陡,但一旦掌握,ComfyUI 能大幅提升创作效率和定制化能力,是追求高效、专业生成图像流程的强大工具。

2. 安装方式

ComfyUI 常见的安装方式有两种:

    1. 使用官方提供的整合包
    1. 使用秋叶的一键启动器(不想折腾用整合包) 本文使用秋叶整合包,下载并解压后。打开 ComfyUI 的根目录,找到启动器文件并打开。 image.png 无需其它操作,点击【一键启动】等待浏览器弹出编辑页面。 image.png 微信截图_20250109213029.png 若在控制台看到 Starting server 消息,但浏览器并未打开编辑界面时请自行使用浏览器访问标注框中的地址。 image.png

3. 初识工作流节点

==注:本文只会简单介绍所用到的节点,详细了解各个节点请自行查阅== 大模型(Checkpoint):作为工作流的起始点,此节点负责选择合适的大型预训练模型,为后续图像生成过程奠定基础。

CLIP文本编码器:该节点负责连接正向和反向提示词,通过CLIP模型将文本提示转换为可用于图像生成的嵌入向量。

空 Latent:在此节点,建立潜在空间图像,主要用于控制图像的尺寸和批处理数量,为图像生成过程提供初始条件。

K采样器:作为Stable Diffusion出图流程的核心节点,K采样器承担着整合载入的模型、输入的提示词以及潜在空间图像数据,进行采样计算,最终输出目标图像的关键作用。

VAE解码器:在潜在空间图像与像素图像之间的转换过程中,VAE解码器节点负责将潜在空间的图像数据解码为最终的像素图像。

图像保存:此节点将生成的图像存储至指定路径(ComfyUI根目录/output)下,以便于后续的使用和管理。 image.png

4. 测试工作流

当你看到编辑界面则表示ComfyUI顺利启动啦~ComfyUI会默认展示一个文生图片的基础工作流。本文教程也会基于该工作流进行后续的功能开发。

在正式开始前我们需要确保该工作流能正常运行,点击右侧的功能栏中的【添加提示词队列(Queue Prompt)】试一下能否正常出图。

如果工作流正常你会看到图片被生成的界面就像这样 image.png 当然你也可能会遇到以下情况,这是因为 Checkpoint 模型加载失败导致的。笔者会提供基础模型供大家使用 1736430822404.jpg 将笔者提供的模型下载并放置到ComfyUI/models/checkpoints文件夹下 image.png 接着刷新浏览器 Checkpoints 会自己加载该模型,如果有多个模型点击 ckpt_name 可展示多个模型点击对应模型切换 image.png 完成上述操作后,再次点击右侧的功能栏中的【添加提示词队列(Queue Prompt)】就能看到生图效果啦~

5. 启用开发者模式

完成上述操作后,再次点击右侧的功能栏右上角的小齿轮打开设置界面,并启用 开发者模式(Dev Mode) 开启后右侧功能栏会多出 保存API格式(Save API Format) 的按钮。 image.png

旧版UI 启用开发者模式

image.png

新版UI 启用开发者模式

点击 保存API格式(Save API Format)按钮可以将该工作流保存成Json格式方便后续在 Unity 中使用。 image.png

旧版UI 保存API

image.png

新版UI 保存API

image.png

API 文件

6. 开启节点ID

为了方便在API中快速定位并修改指定节点 ,我们还需要打开编辑器的节点ID(新版ComfyUI默认是打开状态)。

点击侧边栏中管理器(Manager)按钮,在此界面下找到标签(Badge) 将其切换到 ID+名称(ID Nickname)

注:如果是官方整合包则需要自己安装管理器(Manager)插件 image.png

开启节点ID

image.png

旧版节点ID

image.png

新版节点ID

image.png

对照关系

二、Unity 篇

1. 搭建UI界面

使用UGUI搭建界面(可根据自己的偏好自定义)参考如下 image.png

界面参考

2. 修改 API 文件

修改 Comfy UI API 文件==(调用API都需要添加字段)==,加入client_idprompt两个字段。可参考下述Json格式

{

    "client_id": "1234567890",
    "prompt": {
        "3": {
            "inputs": {
                "seed": 945470077923209,
                "steps": 20,
                "cfg": 8,
                "sampler_name": "euler",
                "scheduler": "normal",
                "denoise": 1,
                "model": [
                    "4",
                    0
                ],
                "positive": [
                    "6",
                    0
                ],

                "negative": [
                    "7",
                    0
                ],

                "latent_image": [
                    "5",
                    0
                ]
            },

            "class_type": "KSampler",
            "_meta": {
                "title": "K采样器"
            }
        },
        "4": {
            "inputs": {
                "ckpt_name": "dreamshaper_8.safetensors"
            },
            "class_type": "CheckpointLoaderSimple",
            "_meta": {
                "title": "Checkpoint加载器(简易)"
            }
        },
        "5": {
            "inputs": {
                "width": 512,
                "height": 512,
                "batch_size": 1
            },

            "class_type": "EmptyLatentImage",
            "_meta": {
                "title": "空Latent"
            }
        },
        "6": {
            "inputs": {
                "text": "beautiful scenery nature glass bottle landscape, , purple galaxy bottle,",
                "clip": [
                    "4",
                    1
                ]
            },
            "class_type": "CLIPTextEncode",
            "_meta": {
                "title": "CLIP文本编码器"
            }
        },
        "7": {
            "inputs": {
                "text": "text, watermark",
                "clip": [
                    "4",
                    1
                ]
            },
            "class_type": "CLIPTextEncode",
            "_meta": {
                "title": "CLIP文本编码器"
            }
        },
        "8": {
            "inputs": {
                "samples": [
                    "3",
                    0
                ],
                "vae": [
                    "4",
                    2
                ]
            },
            "class_type": "VAEDecode",
            "_meta": {
                "title": "VAE解码"
            }
        },
        "9": {
            "inputs": {
                "filename_prefix": "ComfyUI",
                "images": [
                    "8",
                    0
                ]
            },
            "class_type": "SaveImage",
            "_meta": {
                "title": "保存图像"
            }
        }
    }
}

3. 读取 API 文件

创建 StreamingAssets 文件夹并将 Json 放入该文件夹下,也可将其放置在服务器通过网络请求获取数据。 image.png 创建脚本命名为ComfyUIAPI(可自定义),编写读取Json的方法

private string GetApiJson(string fileName)  
{  
    // 检查文件名是否为空或 null    if (string.IsNullOrEmpty(fileName))  
    {        Debug.LogError("File name cannot be null or empty.");  
        return null;  
    }  
    // 获取 StreamingAssets 文件夹的路径,并拼接文件路径  
    string filePath = Path.Combine(Application.streamingAssetsPath, fileName);  
  
    // 检查文件是否存在  
    if (!File.Exists(filePath))  
    {        Debug.LogError("JSON file not found at path: " + filePath);  
        return null;  
    }  
    try  
    {  
        // 使用 StreamReader 读取文件内容  
        // using 语句确保 StreamReader 在使用完毕后自动释放资源  
        using (StreamReader reader = new StreamReader(filePath))  
        {            // 读取文件的全部内容  
            string jsonData = reader.ReadToEnd();  
            return jsonData;  
        }    
    }    
    catch (Exception ex)  
    {        // 捕获并记录读取文件时可能发生的异常  
        Debug.LogError("An error occurred while reading the JSON file: " + ex.Message);  
        return null;  
    }
}

代码功能概述:

GetApiJson 方法用于从 Unity 的 StreamingAssets 文件夹中读取指定 JSON 文件的内容,并返回文件内容的字符串形式。如果文件不存在或读取过程中发生错误,方法会记录错误并返回 null。

4. 修改节点数据

根据上一章节的学习,我们了解到要生成与描述相符的图片则需要对正向提示词进行修改。在修改之前我们需要知道工作流中正向提示词节点的ID,这样就可以通过修改提示词来得到我们想要的图片~ image.png

正向提示词ID

在编写脚本时,可以将任务名称和正向提示词作为参数传递,这样就可以通过不同的提示词来控制执行特定的任务。此外,其他参数也可以根据需要进行调整,剩下的就留给大家自行探索和测试咯~ 代码如下:

public void SubmitTask(string taskName, string prompt)  
{    
	// 获取指定任务名称对应的 JSON 文件内容  
    var apiJson = GetApiJson($"{taskName}.json");    
    // 检查 JSON 文件内容是否成功加载  
    if (apiJson != null)    
    {          
		// 将 JSON 字符串反序列化为 JObject 对象,以便进行修改  
        var jsonObject = JsonConvert.DeserializeObject<JObject>(apiJson);    
        // 获取当前时间的 Unix 时间戳(秒级)  
        var seed = GetTimestampSeconds();    
        // 修改 JSON 对象中指定路径的字段值  
        // 1. 修改 key 为 "3" 的对象的 "seed" 字段  
        ModifyJsonField(jsonObject, "3", "seed", seed.ToString());    
        // 2. 修改 key 为 "6" 的对象的 "text" 字段  
        ModifyJsonField(jsonObject, "6", "text", prompt);  
    }
}  
  
  
private void ModifyJsonField(JObject jsonObject, string key, string field, string newValue)  
{  
    // 检查 JSON 对象中是否存在指定的路径:  
    // 1. 检查 jsonObject["prompt"] 是否存在。  
    // 2. 检查 jsonObject["prompt"][key] 是否存在。  
    // 3. 检查 jsonObject["prompt"][key]["inputs"] 是否存在。  
    // 4. 检查 jsonObject["prompt"][key]["inputs"][field] 是否存在。  
    // 如果路径存在,则继续执行;否则,记录错误日志。  
    if (jsonObject["prompt"][key]?["inputs"]?[field] != null)  
    {        // 如果路径存在,将指定字段的值修改为 newValue        jsonObject["prompt"][key]["inputs"][field] = newValue;  
    }    
    else  
    {  
        // 如果路径不存在,记录错误日志,提示字段未找到  
        Debug.LogError($"Field '{field}' not found in key '{key}'.");  
    }
}  
  
private int GetTimestampSeconds()  
{    
	// 获取当前时间的 Unix 时间戳(秒级)  
    return (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds();  
}

代码概述:

SubmitTask 提交一个任务,根据任务名称加载对应的 JSON 文件,并动态修改其中的特定字段(随机种子(seed)的目的是为了确保每次生成的结果具有不同的随机性,从而每次执行时得到不一样的结果)。修改后的 JSON 数据可用于后续处理。

ModifyJsonField 修改 JSON 对象中指定路径的字段值。如果路径不存在,则记录错误日志。

GetTimestampSeconds 获取当前时间的 Unix 时间戳(秒级),用于生成唯一标识(如 seed 值)。

5. 封装 POST 请求

接下来可以通过 UnityWebRequest 或 HttpClient 发起网络请求提交图像生成任务

private void Post(string url, string postData, Action<bool, string> requestComplete = null)  
{  
    // 使用协程发起POST请求,避免阻塞主线程  
    StartCoroutine(PostRequestCoroutine(url, postData, requestComplete));  
}  
  
// 协程方法,用于处理POST请求  
private IEnumerator PostRequestCoroutine(string url, string postData, Action<bool, string> requestComplete)  
{  
    // 打印请求的URL和请求体,方便调试  
    Debug.Log("Request URL: " + url);  
    Debug.Log("Request Body: " + postData);  
  
    // 使用UnityWebRequest类创建一个POST请求  
    using (UnityWebRequest webRequest = new UnityWebRequest(url, "POST"))  
    {        // 将请求体转换为UTF-8编码的字节数组  
        byte[] bodyRaw = Encoding.UTF8.GetBytes(postData);  
  
        // 设置上传处理器,将字节数组作为请求体发送  
        webRequest.uploadHandler = new UploadHandlerRaw(bodyRaw);  
  
        // 设置下载处理器,用于接收服务器返回的数据  
        webRequest.downloadHandler = new DownloadHandlerBuffer();  
  
        // 设置请求头中的Content-Type为application/json  
        webRequest.SetRequestHeader("Content-Type", "application/json");  
  
        // 发送请求并等待响应  
        yield return webRequest.SendWebRequest();  
  
        // 检查请求结果  
        if (webRequest.result == UnityWebRequest.Result.Success)  
        {            // 请求成功,打印成功信息并调用回调函数  
            Debug.Log("请求成功");  
            requestComplete?.Invoke(true, webRequest.downloadHandler.text);  
        }        
        else  
        {  
            // 请求失败,打印错误信息和响应体,并调用回调函数  
            Debug.LogError($"请求失败, 错误: {webRequest.error}");  
            Debug.LogError($"响应体: {webRequest.downloadHandler.text}");  
            requestComplete?.Invoke(false, webRequest.error);  
        }    
    }
}

代码概述:

Post 封装了请求的调用逻辑,通过协程 PostRequestCoroutine 发起异步请求,避免阻塞主线程。协程方法负责实际的请求逻辑,包括设置请求体、请求头、发送请求并处理响应。如果请求成功,会通过回调函数返回 true 和响应数据;如果失败,则返回 false 和错误信息。代码适用于需要发送 JSON 数据并异步处理 HTTP 请求的场景,同时通过 using 语句确保资源释放,避免内存泄漏。

代码就是个简单的Post请求且都有注释,笔者就不过多赘述。有不懂的同学可以留言

6. 调用生图接口

恭喜你~完成了上述所有功能,现在你可以通过调用 SubmitTask 方法来实现生图,让我们来试试效果吧。 以下是完整代码

using System;  
using System.Collections;  
using System.IO;  
using System.Text;  
using Newtonsoft.Json;  
using Newtonsoft.Json.Linq;  
using UnityEngine;  
using UnityEngine.Networking;  
  
public class ComfyUIAPI : MonoBehaviour  
{  
    public void SubmitTask(string taskName, string prompt, Action callback)  
    {        // 获取指定任务名称对应的 JSON 文件内容  
        var apiJson = GetApiJson($"{taskName}.json");  
  
        // 检查 JSON 文件内容是否成功加载  
        if (apiJson != null)  
        {            // 将 JSON 字符串反序列化为 JObject 对象,以便进行修改  
            var jsonObject = JsonConvert.DeserializeObject<JObject>(apiJson);  
  
            // 获取当前时间的 Unix 时间戳(秒级)  
            var seed = GetTimestampSeconds();  
  
            // 修改 JSON 对象中指定路径的字段值  
            // 1. 修改 key 为 "3" 的对象的 "seed" 字段  
            ModifyJsonField(jsonObject, "3", "seed", seed.ToString());  
  
            // 2. 修改 key 为 "6" 的对象的 "text" 字段  
            ModifyJsonField(jsonObject, "6", "text", prompt);  
  
            // 合成任务发送请求  
            var task = JsonConvert.SerializeObject(jsonObject, Formatting.Indented);  
            Post("http://127.0.0.1:8188/prompt", task, (isComplete, data) =>  
            {  
                if (isComplete)  
                {                    Debug.Log("发送成功");  
                    callback?.Invoke();  
                }            
            });        
        }    
    }  
    private string GetApiJson(string fileName)  
    {        if (string.IsNullOrEmpty(fileName))  
        {            Debug.LogError("File name cannot be null or empty.");  
            return null;  
        }  
        // 获取StreamingAssets文件夹的路径    
string filePath = Path.Combine(Application.streamingAssetsPath, fileName);  
  
        // 检查文件是否存在    
if (!File.Exists(filePath))  
        {            Debug.LogError("JSON file not found at path: " + filePath);  
            return null;  
        }  
        try  
        {  
            // 读取JSON文件内容    
using (StreamReader reader = new StreamReader(filePath))  
            {                string jsonData = reader.ReadToEnd();  
                return jsonData;  
            }        
        }        
        catch (Exception ex)  
        {            Debug.LogError("An error occurred while reading the JSON file: " + ex.Message);  
            return null;  
        }    
    }  
    private void ModifyJsonField(JObject jsonObject, string key, string field, string newValue)  
    {        // 检查 JSON 对象中是否存在指定的路径:  
        // 1. 检查 jsonObject["prompt"] 是否存在。  
        // 2. 检查 jsonObject["prompt"][key] 是否存在。  
        // 3. 检查 jsonObject["prompt"][key]["inputs"] 是否存在。  
        // 4. 检查 jsonObject["prompt"][key]["inputs"][field] 是否存在。  
        // 如果路径存在,则继续执行;否则,记录错误日志。  
        if (jsonObject["prompt"][key]?["inputs"]?[field] != null)  
        {            // 如果路径存在,将指定字段的值修改为 newValue            jsonObject["prompt"][key]["inputs"][field] = newValue;  
        }        
        else  
        {  
            // 如果路径不存在,记录错误日志,提示字段未找到  
            Debug.LogError($"Field '{field}' not found in key '{key}'.");  
        }    
    }  
    private int GetTimestampSeconds()  
    {        // 获取当前时间的 Unix 时间戳(秒级)  
        return (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds();  
    }  
  
    private void Post(string url, string postData, Action<bool, string> requestComplete = null)  
    {        // 使用协程发起POST请求,避免阻塞主线程  
        StartCoroutine(PostRequestCoroutine(url, postData, requestComplete));  
    }  
    // 协程方法,用于处理POST请求  
    private IEnumerator PostRequestCoroutine(string url, string postData, Action<bool, string> requestComplete)  
    {        // 打印请求的URL和请求体,方便调试  
        Debug.Log("Request URL: " + url);  
        Debug.Log("Request Body: " + postData);  
  
        // 使用UnityWebRequest类创建一个POST请求  
        using (UnityWebRequest webRequest = new UnityWebRequest(url, "POST"))  
        {            // 将请求体转换为UTF-8编码的字节数组  
            byte[] bodyRaw = Encoding.UTF8.GetBytes(postData);  
  
            // 设置上传处理器,将字节数组作为请求体发送  
            webRequest.uploadHandler = new UploadHandlerRaw(bodyRaw);  
  
            // 设置下载处理器,用于接收服务器返回的数据  
            webRequest.downloadHandler = new DownloadHandlerBuffer();  
  
            // 设置请求头中的Content-Type为application/json  
            webRequest.SetRequestHeader("Content-Type", "application/json");  
  
            // 发送请求并等待响应  
            yield return webRequest.SendWebRequest();  
  
            // 检查请求结果  
            if (webRequest.result == UnityWebRequest.Result.Success)  
            {                // 请求成功,打印成功信息并调用回调函数  
                Debug.Log("请求成功");  
                requestComplete?.Invoke(true, webRequest.downloadHandler.text);  
            }            
            else  
            {  
                // 请求失败,打印错误信息和响应体,并调用回调函数  
                Debug.LogError($"请求失败, 错误: {webRequest.error}");  
                Debug.LogError($"响应体: {webRequest.downloadHandler.text}");  
                requestComplete?.Invoke(false, webRequest.error);  
            }        
        }    
    }
}

7. 验证生图接口

using System.Collections;  
using System.Collections.Generic;  
using UnityEngine;  
using UnityEngine.UI;  
  
public class Test : MonoBehaviour  
{  
    [SerializeField] private Button submitBtn;  
    [SerializeField] private InputField promptInputField;  
    [SerializeField] private ComfyUIAPI _comfyUiapi;  
  
    private void Start()  
    {        submitBtn.onClick.AddListener(() =>  
        {  
            _comfyUiapi.SubmitTask("TextToImag_api", promptInputField.text,  
                () =>  
                {  
                    print($"发布生图任务-{promptInputField.text}");  
                    promptInputField.text = "";  
                });        
            });    
        }
    }

通过上述代码,我们验证了调用 SubmitTask 方法可以实现提交绘图任务。恭喜你离成功只有一步之遥啦~ image.png

8. 图片展示

通过上述步骤, 我们实现了文生图的基本功能。接下来,为了在界面上展示这张图片,我们需要确认图片是否已经生成。一旦确认图片已生成,我们便可以从相应的文件夹中读取图片,并将其展示在界面上。 代码如下:

// 定义一个协程方法,用于加载图片文件  
    IEnumerator LoadImageFile(string path, Action complete = null)  
    {        // 无限循环,直到满足条件退出  
        while (true)  
        {            // 等待1秒  
            yield return new WaitForSeconds(1);  
  
            // 创建一个 UnityWebRequest 对象,用于发送 HTTP GET 请求  
            UnityWebRequest www = UnityWebRequest.Get("http://127.0.0.1:8188/prompt");  
  
            // 发送请求并等待响应  
            yield return www.SendWebRequest();  
  
            // 检查请求是否成功  
            if (www.result == UnityWebRequest.Result.Success)  
            {                // 解析返回的 JSON 数据  
                JObject jsonObject = JObject.Parse(www.downloadHandler.text);  
  
                // 从 JSON 数据中获取 queue_remaining 的值  
                int queueRemaining = (int)jsonObject["exec_info"]?["queue_remaining"];  
  
                // 如果 queue_remaining 小于等于 0,表示队列中没有任务  
                if (queueRemaining <= 0)  
                {                    // 调用 LoadNewImage 方法加载最新的图片,并传入回调函数  
                    LoadNewImage(path, complete);  
  
                    // 退出协程  
                    yield break;  
                }            
            }            
            else  
            {  
                // 如果请求失败,输出错误信息  
                Debug.Log("Error: " + www.error);  
  
                // 退出协程  
                yield break;  
            }  
            yield return null;  
        }    
    }  
// 定义一个方法,用于加载指定路径下的最新图片  
    void LoadNewImage(string path, Action complete)  
    {        // 获取指定文件夹中的所有 PNG 图片文件  
        string[] imageFiles = Directory.GetFiles(path, "*.png"); // 假设图片格式为 PNG  
        // 如果没有找到任何图片文件,输出提示信息并返回  
        if (imageFiles.Length == 0)  
        {            Debug.Log("No image files found in the folder.");  
            return;  
        }  
        // 初始化一个变量,用于记录最新的文件时间  
        DateTime latestTime = DateTime.MinValue;  
  
        // 初始化一个变量,用于记录最新的文件路径  
        string latestFile = null;  
  
        // 遍历所有图片文件  
        foreach (string file in imageFiles)  
        {            
	        // 获取文件信息  
            FileInfo fileInfo = new FileInfo(file);  
            
            // 如果当前文件的修改时间比 latestTime 更新,则更新 latestTime 和 latestFile   if (fileInfo.LastWriteTime > latestTime)  
            {                
	            latestTime = fileInfo.LastWriteTime;  
                latestFile = file;            
            }        
        }  
        // 如果找到了最新的文件  
        if (latestFile != null)  
        {            // 输出最新文件的路径  
            Debug.Log("Latest image file: " + latestFile);  
  
            // 读取最新文件的二进制数据  
            byte[] fileData = File.ReadAllBytes(latestFile);  
  
            // 创建一个新的 Texture2D 对象  
            Texture2D texture = new Texture2D(2, 2);  
  
            // 将二进制数据加载到纹理中,自动调整纹理大小  
            texture.LoadImage(fileData);  
  
            // 将纹理转换为 Sprite            Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height),  
                new Vector2(0.5f, 0.5f));  
  
            // 将 Sprite 赋值给 Image 组件  
            resultImage.sprite = sprite;  
  
            // 调用回调函数(如果存在)  
            complete?.Invoke();  
        }        
        else  
        {  
            // 如果没有找到最新的文件,输出提示信息  
            Debug.Log("No latest image file found.");  
        }   
    }

代码概述:

LoadImageFile 是一个协程方法,它会每隔1秒发送一个 HTTP GET 请求,检查队列中是否有任务。如果队列中没有任务(queue_remaining <= 0),则调用 LoadNewImage 方法加载最新的图片文件。

LoadNewImage 方法会遍历指定路径下的所有 PNG 图片文件,找到最新的文件并加载它。加载完成后,将图片显示在 _resultImage 组件上,并调用回调函数(如果存在)。 image.png

图片展示

9. UI 界面完整代码

using System;  
using System.Collections;  
using System.IO;  
using Newtonsoft.Json.Linq;  
using UnityEngine;  
using UnityEngine.Networking;  
using UnityEngine.UI;  
  
public class DrawingPanel : MonoBehaviour  
{  
    [SerializeField] private Button submitBtn;  
    [SerializeField] private InputField promptInputField;  
    [SerializeField] private Image resultImage;  
    [SerializeField] private GameObject tipsObj;  
    [SerializeField] private ComfyUIAPI comfyUiapi;  
  
    private void Start()  
    {        tipsObj.SetActive(false);  
        submitBtn.onClick.AddListener(() =>  
        {  
            tipsObj.SetActive(true);  
            comfyUiapi.SubmitTask("TextToImag_api", promptInputField.text, () =>  
            {  
                print($"发布生图任务-{promptInputField.text}");  
                StartCoroutine(LoadImageFile("F:\\AI\\ComfyUI-aki\\ComfyUI-aki-v1.3\\output",  
                    () => { tipsObj.SetActive(false); }));  
                promptInputField.text = "";  
            });        
        });    
    }  
  
    // 定义一个协程方法,用于加载图片文件  
    IEnumerator LoadImageFile(string path, Action complete = null)  
    {        // 无限循环,直到满足条件退出  
        while (true)  
        {            // 等待1秒  
            yield return new WaitForSeconds(1);  
  
            // 创建一个 UnityWebRequest 对象,用于发送 HTTP GET 请求  
            UnityWebRequest www = UnityWebRequest.Get("http://127.0.0.1:8188/prompt");  
  
            // 发送请求并等待响应  
            yield return www.SendWebRequest();  
  
            // 检查请求是否成功  
            if (www.result == UnityWebRequest.Result.Success)  
            {                // 解析返回的 JSON 数据  
                JObject jsonObject = JObject.Parse(www.downloadHandler.text);  
  
                // 从 JSON 数据中获取 queue_remaining 的值  
                int queueRemaining = (int)jsonObject["exec_info"]?["queue_remaining"];  
  
                // 如果 queue_remaining 小于等于 0,表示队列中没有任务  
                if (queueRemaining <= 0)  
                {                    // 调用 LoadNewImage 方法加载最新的图片,并传入回调函数  
                    LoadNewImage(path, complete);  
  
                    // 退出协程  
                    yield break;  
                }            
            }            
            else  
            {  
                // 如果请求失败,输出错误信息  
                Debug.Log("Error: " + www.error);  
  
                // 退出协程  
                yield break;  
            }  
            yield return null;  
        }    
    }  
// 定义一个方法,用于加载指定路径下的最新图片  
    void LoadNewImage(string path, Action complete)  
    {        // 获取指定文件夹中的所有 PNG 图片文件  
        string[] imageFiles = Directory.GetFiles(path, "*.png"); // 假设图片格式为 PNG  
        // 如果没有找到任何图片文件,输出提示信息并返回  
        if (imageFiles.Length == 0)  
        {            
	        Debug.Log("No image files found in the folder.");  
            return;  
        }  
        // 初始化一个变量,用于记录最新的文件时间  
        DateTime latestTime = DateTime.MinValue;  
  
        // 初始化一个变量,用于记录最新的文件路径  
        string latestFile = null;  
  
        // 遍历所有图片文件  
        foreach (string file in imageFiles)  
        {            
	        // 获取文件信息  
            FileInfo fileInfo = new FileInfo(file);  
  
            // 如果当前文件的修改时间比 latestTime 更新,则更新 latestTime 和 latestFile  if (fileInfo.LastWriteTime > latestTime)  
            {                latestTime = fileInfo.LastWriteTime;  
                latestFile = file;            
            }        
        }  
        // 如果找到了最新的文件  
        if (latestFile != null)  
        {            
	        // 输出最新文件的路径  
            Debug.Log("Latest image file: " + latestFile);  
  
            // 读取最新文件的二进制数据  
            byte[] fileData = File.ReadAllBytes(latestFile);  
  
            // 创建一个新的 Texture2D 对象  
            Texture2D texture = new Texture2D(2, 2);  
  
            // 将二进制数据加载到纹理中,自动调整纹理大小  
            texture.LoadImage(fileData);  
  
            // 将纹理转换为 Sprite            Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height),  
                new Vector2(0.5f, 0.5f));  
  
            // 将 Sprite 赋值给 Image 组件  
            resultImage.sprite = sprite;  
  
            // 调用回调函数(如果存在)  
            complete?.Invoke();  
        }        
        else  
        {  
            // 如果没有找到最新的文件,输出提示信息  
            Debug.Log("No latest image file found.");  
        }    
    }
}

三、结束

End~

若大家喜欢这期教程还请点赞、关注、收藏,有问题记得留言哈~

原创不易,若转载请注明出处,感谢大家~

Logo © 2025 Mark All Rights Reserved. 陕ICP备2025083152号