iT邦幫忙

2025 iThome 鐵人賽

DAY 27
0
Software Development

Vibe Unity - AI時代的遊戲開發工作流系列 第 27

Day 27 - 幫寵物換衣服 - 圖片生成!

  • 分享至 

  • xImage
  •  

這一章要來實現 Nano Banana 幫北極熊換衣服的功能

我們先繼續產生3個不同的衣服進行測試

然後在遊戲裡幫北極熊換衣服:

image.png

Prompt:

實作代碼: 鏡子換裝的功能
1. 是一個在場景中的鏡子道具
2. 點擊之後會打開一個換裝的 UI Panel
3. 上面會有3件不同的衣服Btn, 點一下可以換衣服
3. 換衣服的時候, 顯示在一個 UI Image上就好
4. 換衣服的時候, 呼叫 GeminiCore 的 EditImages 功能
8. Inspector 中可以輸入 北極熊 和 衣服圖片的 Sprites
9. 換衣的時候, 打開一個 UI Loading Obj, 換完之後再關閉
10. 這個UI Panel 上有一個關閉Panel的Btn

Cursor 產生出來的核心代碼:

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using PolarAI.Scripts.Core.Gemini;

// 換裝面板:顯示北極熊、三件衣服按鈕、預覽與 Loading,並可關閉
public class MirrorDressUpPanel : MonoBehaviour
{
    [Header("Core")]
    public GeminiCore geminiCore;

    [Header("Sprites - 來源素材")]
    public Sprite bearSprite; // 北極熊原圖
    public Sprite clothSpriteA;
    public Sprite clothSpriteB;
    public Sprite clothSpriteC;

    [Header("UI 綁定")]
    public Button btnClothA;
    public Button btnClothB;
    public Button btnClothC;
    public Button btnClose;
    public Image previewImage; // 僅在 UI Image 上顯示生成結果
    public GameObject loadingRoot; // UI Loading 物件

    [Header("指令設定")]
    [TextArea]
    public string dressInstruction = "Dress the bear with the provided cloth, produce a clean full-body sprite.";

    private void Awake()
    {
        if (btnClothA) btnClothA.onClick.AddListener(() => OnClickCloth(clothSpriteA));
        if (btnClothB) btnClothB.onClick.AddListener(() => OnClickCloth(clothSpriteB));
        if (btnClothC) btnClothC.onClick.AddListener(() => OnClickCloth(clothSpriteC));
        if (btnClose) btnClose.onClick.AddListener(ClosePanel);
    }

    private void OnEnable()
    {
        if (loadingRoot) loadingRoot.SetActive(false);
    }

    private void OnDestroy()
    {
        if (btnClothA) btnClothA.onClick.RemoveAllListeners();
        if (btnClothB) btnClothB.onClick.RemoveAllListeners();
        if (btnClothC) btnClothC.onClick.RemoveAllListeners();
        if (btnClose) btnClose.onClick.RemoveAllListeners();
    }

    public void ClosePanel()
    {
        if (loadingRoot) loadingRoot.SetActive(false);
        gameObject.SetActive(false);
        if (Time.timeScale == 0f) Time.timeScale = 1f;
    }

    private void OnClickCloth(Sprite clothSprite)
    {
        if (geminiCore == null || bearSprite == null || clothSprite == null) return;
        if (loadingRoot) loadingRoot.SetActive(true);

        var base64List = new List<string>();
        var b64Bear = SpriteToBase64(bearSprite);
        var b64Cloth = SpriteToBase64(clothSprite);
        if (!string.IsNullOrEmpty(b64Bear)) base64List.Add(b64Bear);
        if (!string.IsNullOrEmpty(b64Cloth)) base64List.Add(b64Cloth);

        geminiCore.EditImagesAsync(base64List, dressInstruction, OnImageReturn);
    }

    private void OnImageReturn(Texture2D tex)
    {
        if (tex != null && previewImage)
        {
            var sprite = TextureToSprite(tex);
            if (sprite != null) previewImage.sprite = sprite;
        }
        if (loadingRoot) loadingRoot.SetActive(false);
    }

    private static string SpriteToBase64(Sprite sprite)
    {
        if (sprite == null || sprite.texture == null) return null;
        Texture2D cropped = null;
        try
        {
            cropped = ExtractSpriteTexture(sprite);
            if (cropped == null) return null;
            var bytes = cropped.EncodeToPNG();
            return Convert.ToBase64String(bytes);
        }
        catch
        {
            return null;
        }
        finally
        {
            if (cropped != null)
            {
                UnityEngine.Object.Destroy(cropped);
            }
        }
    }

    private static Sprite TextureToSprite(Texture2D tex)
    {
        if (tex == null) return null;
        return Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), 100f);
    }

    private static Texture2D ExtractSpriteTexture(Sprite sprite)
    {
        var src = sprite.texture;
        int fullW = src.width;
        int fullH = src.height;

        var rt = RenderTexture.GetTemporary(fullW, fullH, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
        var prev = RenderTexture.active;
        try
        {
            Graphics.Blit(src, rt);
            RenderTexture.active = rt;
            var fullTex = new Texture2D(fullW, fullH, TextureFormat.RGBA32, false, true);
            fullTex.ReadPixels(new Rect(0, 0, fullW, fullH), 0, 0);
            fullTex.Apply();

            var r = sprite.textureRect;
            int x = Mathf.RoundToInt(r.x);
            int y = Mathf.RoundToInt(r.y);
            int w = Mathf.RoundToInt(r.width);
            int h = Mathf.RoundToInt(r.height);

            var pixels = fullTex.GetPixels(x, y, w, h);
            var cropped = new Texture2D(w, h, TextureFormat.RGBA32, false);
            cropped.SetPixels(pixels);
            cropped.Apply();

            UnityEngine.Object.Destroy(fullTex);
            return cropped;
        }
        catch
        {
            return null;
        }
        finally
        {
            RenderTexture.active = prev;
            RenderTexture.ReleaseTemporary(rt);
        }
    }
}



接下來我們需要自己排版一下換衣服的UI

你可以就這樣建議的排版, 放上圖片的按鈕即可:

image.png

排好之後, 在上面掛上 Mirror Dress Up Panel 的代碼即可:

image.png

我稍微設計調整了一下畫面的排版:

image.png

然後在地面上放一個鏡子的道具, 點擊之後就打開這個 Panel 即可


最終效果:

20251008-1132-07.4873555.mp4

Nano Banana 生成出來的圖片不會自己去背

如果要再進行去背, 我們可以把生成出來的圖片,

再放到 Fal AI 去除圖片背景的模型, 然後再顯示出來

我這裡, 就直接先墊一個白色的背景進行展示 ~


上一篇
Day 26 - 把對話讀出來 TTS
系列文
Vibe Unity - AI時代的遊戲開發工作流27
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言