Unity3D仿星露谷物語開發59之定制角色襯衫

1、目標

自定義角色襯衫、褲子、手臂顏色。

2、概念

在Assets -> Sprites -> Output Textures下,Customised_farmer為目前角色所用的精靈表。

如果上面是輸出紋理,那么輸入紋理是什么呢?它位于Assets/Sprites/Sprite Textures/Character/Farmer/farmerCharacter.png

我們可以看到它沒有任何的襯衫,手臂是通用的顏色,右邊的褲子沒有應用任何顏色,只應用了灰色的陰影。

我們在定制腳本中要做的是,輸入這種character sheet(角色表單,代表的是游戲內用來存儲和管理玩家角色或者非玩家角色NPC相關屬性的數據結構或者界面),然后覆蓋在玩家上面進行自定義。

查看Assets/Sprites/Sprite Textures/Character/Farmer/farmerShirts.png,

我們可以看到2件不同顏色的襯衫,我們要做的是取用戶選擇的襯衫然后將它覆蓋在上面,將襯衫覆蓋在這些sprite上面。

我們需要考慮的事情:

1)玩家面對的是哪個方向?

我們的方法是建立一個二維數組,記錄玩家所面對的方向,從sprite表的左下角開始索引。

2)玩家的位置。

我們可以看到右邊的精靈比左邊低一個像素。

為了能夠將襯衫應用到正確的位置,使它適合角色,我們將跟蹤角色如何移動。

3)手臂重新上色

選哪件襯衫,為了與襯衫匹配就給所有的手臂重新上色。

unity提供了重新給Sprite上色的方法。

綜上所述,我們需要一個數組來表示玩家面對的方向,一個二維數據來說明位置調整。

3、修改Enums.cs腳本

添加枚舉值:

public enum Facing
{none,front,back,right
}

4、創建ApplyCharacterCustomisation.cs腳本

在Assets/Scripts/Animation下創建腳本ApplyCharacterCustomisation.cs。

在下面6列的farmer紋理中,我們將在每一個位置放置一件襯衫。

在代碼中,我們將取襯衫紋理并合并到這個角色的紋理的頂部。

在顏色交換列表中,當我們用不同的像素重新上色時,我們會添加那些想要重新上色的列表,然后處理顏色交換的方法會遍歷這個列表,然后改變像素的顏色基于此列表中的顏色交換。

在Target arm colours for color replacement這塊,這些手臂需要恢復取決于它選了哪件襯衫。

為了做到這一點,我們需要知道每個像素的RGB顏色。然后當我們交換顏色的時候,這些就像從from顏色到to顏色。我們把目標顏色定義在變量中。這幾個顏色對應如下內容:

在Calculate coordinates for shirt pixels中,就是計算出如下襯衫的像素坐標。

精靈圖的width為144,每件襯衫(colum)占9個像素,總共可存放144/9=16件襯衫。其中紅色為inputShirtStyleNo=0,綠色為inputShirtStyleNo=1。

shirtTextureHeight=36即為4*9,表示每件襯衫有4個圖片。

selectedShirt就是紅色或綠色兩種,從上往下分別是frontShirt, rightShirt, leftShirt, backShirt。

在Get arm pixels to recolor中,288是如下右邊所有的圖片集的寬度。

代碼為:

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;[System.Serializable]
public class colorSwap
{public Color fromColor;public Color toColor;public colorSwap(Color fromColor, Color toColor){this.fromColor = fromColor;this.toColor = toColor;}
}public class ApplyCharacterCustomisation : MonoBehaviour
{// Input Textures[Header("Base Textures")][SerializeField] private Texture2D maleFarmerBaseTexture = null;[SerializeField] private Texture2D femaleFarmerBaseTexture = null;[SerializeField] private Texture2D shirtsBaseTexture = null;private Texture2D farmerBaseTexture;// Created Textures[Header("OutputBase Texture To Be Used For Animation")][SerializeField] private Texture2D farmerBaseCustomised = null;private Texture2D farmerBaseShirtsUpdated;private Texture2D selectedShirt;// Select Shirt style[Header("Select Shirt Style")][Range(0, 1)]   // 紅色襯衫或者綠色襯衫[SerializeField] private int inputShirtStyleNo = 0;// Selecct Sex[Header("Select Sex:0=Male, 1=female")][Range(0, 1)][SerializeField] private int inputSex = 0;private Facing[,] bodyFacingArray;private Vector2Int[,] bodyShirtOffsetArray;// 定義精靈表的維度信息private int bodyRows = 21;private int bodyColumns = 6;private int farmerSpriteWidth = 16;private int farmerSpriteHeight = 32;private int shirtTextureWidth = 9;private int shirtTextureHeight = 36;private int shirtSpriteWidth = 9;private int shirtSpriteHeight = 9;private int shirtStylesInSpriteWidth = 16;  // 紋理可以容納襯衫的件數// 顏色交換列表private List<colorSwap> colorSwapList;// Target arm colours for color replacementprivate Color32 armTargetColor1 = new Color32(77, 13, 13, 255); // darkestprivate Color32 armTargetColor2 = new Color32(138, 41, 41, 255); // next darkestprivate Color32 armTargetColor3 = new Color32(172, 50, 50, 255); // lightestprivate void Awake(){// Initialise color swap listcolorSwapList = new List<colorSwap>();// Process CustomisationProcessCustomisation();}private void ProcessCustomisation(){ProcessGender();ProcessShirt();ProcessArms();MergeCustomisations();}private void ProcessGender(){// Set base spritesheet by genderif(inputSex == 0){farmerBaseTexture = maleFarmerBaseTexture;}else if(inputSex == 1){farmerBaseTexture = femaleFarmerBaseTexture;}// Get base pixelsColor[] farmerBasePixels = farmerBaseTexture.GetPixels();// Set changed base pixelsfarmerBaseCustomised.SetPixels(farmerBasePixels);farmerBaseCustomised.Apply();}private void ProcessShirt(){// Initialise body facing shirt arraybodyFacingArray = new Facing[bodyColumns, bodyRows];// Populate body facing shirt arrayPopulateBodyFacingArray();// Initialise body shirt offset arraybodyShirtOffsetArray = new Vector2Int[bodyColumns, bodyRows];// Populate body shirt offset arrayPopulateBodyShirtOffsetArray();// Create Selected Shirt TextureAddShirtToTexture(inputShirtStyleNo);// Apply shirt texture to baseApplyShirtTextureToBase();}private void ProcessArms(){// Get arm pixels to recolorColor[] farmerPixelsToRecolor = farmerBaseTexture.GetPixels(0, 0, 288, farmerBaseTexture.height);// Populate arm color swap listPopulateArmColorSwapList();// Change arm colorsChangePixelColors(farmerPixelsToRecolor, colorSwapList);// Set recolored pixelsfarmerBaseCustomised.SetPixels(0, 0, 288, farmerBaseTexture.height, farmerPixelsToRecolor);// Apply texture changesfarmerBaseCustomised.Apply();}private void MergeCustomisations(){// Farmer Shirt pixelsColor[] farmerShirtPixels = farmerBaseShirtsUpdated.GetPixels(0, 0, bodyColumns * farmerSpriteWidth, farmerBaseTexture.height);// Farmer Trouser PixelsColor[] farmerTrouserPixelsSelection = farmerBaseTexture.GetPixels(288, 0, 96, farmerBaseTexture.height);// Farmer Body PixelsColor[] farmerBodyPixels = farmerBaseCustomised.GetPixels(0, 0, bodyColumns * farmerSpriteWidth, farmerBaseTexture.height);MergeColorArray(farmerBodyPixels, farmerTrouserPixelsSelection);MergeColorArray(farmerBodyPixels, farmerShirtPixels);// Paste merged pixelsfarmerBaseCustomised.SetPixels(0, 0, bodyColumns * farmerSpriteWidth, farmerBaseTexture.height, farmerBodyPixels);// Apply texture changesfarmerBaseCustomised.Apply();}private void MergeColorArray(Color[] baseArray, Color[] mergeArray){for(int i = 0; i < baseArray.Length; i++){if (mergeArray[i].a > 0){// Merge array has colorif (mergeArray[i].a >= 1){// Fully replacebaseArray[i] = mergeArray[i];}else{// Interpolate colorsfloat alpha = mergeArray[i].a;baseArray[i].r += (mergeArray[i].r - baseArray[i].r) * alpha;baseArray[i].g += (mergeArray[i].g - baseArray[i].g) * alpha;baseArray[i].b += (mergeArray[i].b - baseArray[i].b) * alpha;baseArray[i].a += mergeArray[i].a;}}}}private void PopulateArmColorSwapList(){// Clear color swap listcolorSwapList.Clear();// Arms replacement colorscolorSwapList.Add(new colorSwap(armTargetColor1, selectedShirt.GetPixel(0, 7)));colorSwapList.Add(new colorSwap(armTargetColor2, selectedShirt.GetPixel(0, 6)));colorSwapList.Add(new colorSwap(armTargetColor3, selectedShirt.GetPixel(0, 5)));}private void ChangePixelColors(Color[] baseArray, List<colorSwap> colorSwapList){for(int i = 0; i < baseArray.Length; i++){// Loop through color swap listif(colorSwapList.Count > 0){for(int j = 0; j < colorSwapList.Count; j++){if (isSameColor(baseArray[i], colorSwapList[j].fromColor)){baseArray[i] = colorSwapList[j].toColor;}}}}}private bool isSameColor(Color color1, Color color2){if ((color1.r == color2.r) && (color1.g == color2.g) && (color1.b == color2.b) && (color1.a == color2.a)){return true;}else{return false;}}private void AddShirtToTexture(int shirtStyleNo){// Create shirt textureselectedShirt = new Texture2D(shirtTextureWidth, shirtTextureHeight);selectedShirt.filterMode = FilterMode.Point;// Calculate coordinates for shirt pixelsint y = (shirtStyleNo / shirtStylesInSpriteWidth) * shirtTextureHeight;int x = (shirtStyleNo % shirtStylesInSpriteWidth) * shirtTextureWidth;// Get shirts pixelsColor[] shirtPixels = shirtsBaseTexture.GetPixels(x, y, shirtTextureWidth, shirtTextureHeight);// Apply selected shirt pixels to textureselectedShirt.SetPixels(shirtPixels);selectedShirt.Apply();}private void ApplyShirtTextureToBase(){// Create new shirt base texturefarmerBaseShirtsUpdated = new Texture2D(farmerBaseTexture.width, farmerBaseTexture.height);farmerBaseShirtsUpdated.filterMode = FilterMode.Point;// Set shirt base texture to transparentSetTextureToTransparent(farmerBaseShirtsUpdated);Color[] frontShirtPixels;Color[] backShirtPixels;Color[] rightShirtPixels;frontShirtPixels = selectedShirt.GetPixels(0, shirtSpriteHeight * 3, shirtSpriteWidth, shirtSpriteHeight);backShirtPixels = selectedShirt.GetPixels(0, shirtSpriteHeight * 0, shirtSpriteWidth, shirtSpriteHeight);rightShirtPixels = selectedShirt.GetPixels(0, shirtSpriteHeight * 2, shirtSpriteWidth, shirtSpriteHeight);// Loop through base texture and apply shirt pixelsfor(int x = 0; x < bodyColumns; x++){for(int y = 0; y < bodyRows; y++){int pixelX = x * farmerSpriteWidth;int pixelY = y * farmerSpriteHeight;if (bodyShirtOffsetArray[x, y] != null){if (bodyShirtOffsetArray[x, y].x == 99 && bodyShirtOffsetArray[x, y].y == 99) // do not populate with shirtcontinue;pixelX += bodyShirtOffsetArray[x, y].x;pixelY += bodyShirtOffsetArray[x, y].y;}// Switch on facing directionswitch(bodyFacingArray[x, y]){case Facing.none:break;case Facing.front:// populate front shirt pixelsfarmerBaseShirtsUpdated.SetPixels(pixelX, pixelY, shirtSpriteWidth, shirtSpriteHeight, frontShirtPixels);break;case Facing.back:// populate back shirt pixelsfarmerBaseShirtsUpdated.SetPixels(pixelX, pixelY, shirtSpriteWidth, shirtSpriteHeight, backShirtPixels);break;case Facing.right:// populate right shirt pixelsfarmerBaseShirtsUpdated.SetPixels(pixelX, pixelY, shirtSpriteWidth, shirtSpriteHeight, rightShirtPixels);break;default:break;}}}// Apply shirt texture pixelsfarmerBaseShirtsUpdated.Apply();}private void SetTextureToTransparent(Texture2D texture2D){// fill texture with transparencyColor[] fill = new Color[texture2D.height * texture2D.width];for(int i = 0; i < fill.Length; i++){fill[i] = Color.clear;}texture2D.SetPixels(fill);}private void PopulateBodyFacingArray(){bodyFacingArray[0, 0] = Facing.none;bodyFacingArray[1, 0] = Facing.none;bodyFacingArray[2, 0] = Facing.none;bodyFacingArray[3, 0] = Facing.none;bodyFacingArray[4, 0] = Facing.none;bodyFacingArray[5, 0] = Facing.none;bodyFacingArray[0, 1] = Facing.none;bodyFacingArray[1, 1] = Facing.none;bodyFacingArray[2, 1] = Facing.none;bodyFacingArray[3, 1] = Facing.none;bodyFacingArray[4, 1] = Facing.none;bodyFacingArray[5, 1] = Facing.none;bodyFacingArray[0, 2] = Facing.none;bodyFacingArray[1, 2] = Facing.none;bodyFacingArray[2, 2] = Facing.none;bodyFacingArray[3, 2] = Facing.none;bodyFacingArray[4, 2] = Facing.none;bodyFacingArray[5, 2] = Facing.none;bodyFacingArray[0, 3] = Facing.none;bodyFacingArray[1, 3] = Facing.none;bodyFacingArray[2, 3] = Facing.none;bodyFacingArray[3, 3] = Facing.none;bodyFacingArray[4, 3] = Facing.none;bodyFacingArray[5, 3] = Facing.none;bodyFacingArray[0, 4] = Facing.none;bodyFacingArray[1, 4] = Facing.none;bodyFacingArray[2, 4] = Facing.none;bodyFacingArray[3, 4] = Facing.none;bodyFacingArray[4, 4] = Facing.none;bodyFacingArray[5, 4] = Facing.none;bodyFacingArray[0, 5] = Facing.none;bodyFacingArray[1, 5] = Facing.none;bodyFacingArray[2, 5] = Facing.none;bodyFacingArray[3, 5] = Facing.none;bodyFacingArray[4, 5] = Facing.none;bodyFacingArray[5, 5] = Facing.none;bodyFacingArray[0, 6] = Facing.none;bodyFacingArray[1, 6] = Facing.none;bodyFacingArray[2, 6] = Facing.none;bodyFacingArray[3, 6] = Facing.none;bodyFacingArray[4, 6] = Facing.none;bodyFacingArray[5, 6] = Facing.none;bodyFacingArray[0, 7] = Facing.none;bodyFacingArray[1, 7] = Facing.none;bodyFacingArray[2, 7] = Facing.none;bodyFacingArray[3, 7] = Facing.none;bodyFacingArray[4, 7] = Facing.none;bodyFacingArray[5, 7] = Facing.none;bodyFacingArray[0, 8] = Facing.none;bodyFacingArray[1, 8] = Facing.none;bodyFacingArray[2, 8] = Facing.none;bodyFacingArray[3, 8] = Facing.none;bodyFacingArray[4, 8] = Facing.none;bodyFacingArray[5, 8] = Facing.none;bodyFacingArray[0, 9] = Facing.none;bodyFacingArray[1, 9] = Facing.none;bodyFacingArray[2, 9] = Facing.none;bodyFacingArray[3, 9] = Facing.none;bodyFacingArray[4, 9] = Facing.none;bodyFacingArray[5, 9] = Facing.none;bodyFacingArray[0, 10] = Facing.back;bodyFacingArray[1, 10] = Facing.back;bodyFacingArray[2, 10] = Facing.right;bodyFacingArray[3, 10] = Facing.right;bodyFacingArray[4, 10] = Facing.right;bodyFacingArray[5, 10] = Facing.right;bodyFacingArray[0, 11] = Facing.front;bodyFacingArray[1, 11] = Facing.front;bodyFacingArray[2, 11] = Facing.front;bodyFacingArray[3, 11] = Facing.front;bodyFacingArray[4, 11] = Facing.back;bodyFacingArray[5, 11] = Facing.back;bodyFacingArray[0, 12] = Facing.back;bodyFacingArray[1, 12] = Facing.back;bodyFacingArray[2, 12] = Facing.right;bodyFacingArray[3, 12] = Facing.right;bodyFacingArray[4, 12] = Facing.right;bodyFacingArray[5, 12] = Facing.right;bodyFacingArray[0, 13] = Facing.front;bodyFacingArray[1, 13] = Facing.front;bodyFacingArray[2, 13] = Facing.front;bodyFacingArray[3, 13] = Facing.front;bodyFacingArray[4, 13] = Facing.back;bodyFacingArray[5, 13] = Facing.back;bodyFacingArray[0, 14] = Facing.back;bodyFacingArray[1, 14] = Facing.back;bodyFacingArray[2, 14] = Facing.right;bodyFacingArray[3, 14] = Facing.right;bodyFacingArray[4, 14] = Facing.right;bodyFacingArray[5, 14] = Facing.right;bodyFacingArray[0, 15] = Facing.front;bodyFacingArray[1, 15] = Facing.front;bodyFacingArray[2, 15] = Facing.front;bodyFacingArray[3, 15] = Facing.front;bodyFacingArray[4, 15] = Facing.back;bodyFacingArray[5, 15] = Facing.back;bodyFacingArray[0, 16] = Facing.back;bodyFacingArray[1, 16] = Facing.back;bodyFacingArray[2, 16] = Facing.right;bodyFacingArray[3, 16] = Facing.right;bodyFacingArray[4, 16] = Facing.right;bodyFacingArray[5, 16] = Facing.right;bodyFacingArray[0, 17] = Facing.front;bodyFacingArray[1, 17] = Facing.front;bodyFacingArray[2, 17] = Facing.front;bodyFacingArray[3, 17] = Facing.front;bodyFacingArray[4, 17] = Facing.back;bodyFacingArray[5, 17] = Facing.back;bodyFacingArray[0, 18] = Facing.back;bodyFacingArray[1, 18] = Facing.back;bodyFacingArray[2, 18] = Facing.back;bodyFacingArray[3, 18] = Facing.right;bodyFacingArray[4, 18] = Facing.right;bodyFacingArray[5, 18] = Facing.right;bodyFacingArray[0, 19] = Facing.right;bodyFacingArray[1, 19] = Facing.right;bodyFacingArray[2, 19] = Facing.right;bodyFacingArray[3, 19] = Facing.front;bodyFacingArray[4, 19] = Facing.front;bodyFacingArray[5, 19] = Facing.front;bodyFacingArray[0, 20] = Facing.front;bodyFacingArray[1, 20] = Facing.front;bodyFacingArray[2, 20] = Facing.front;bodyFacingArray[3, 20] = Facing.back;bodyFacingArray[4, 20] = Facing.back;bodyFacingArray[5, 20] = Facing.back;}private void PopulateBodyShirtOffsetArray(){bodyShirtOffsetArray[0, 0] = new Vector2Int(99, 99);bodyShirtOffsetArray[1, 0] = new Vector2Int(99, 99);bodyShirtOffsetArray[2, 0] = new Vector2Int(99, 99);bodyShirtOffsetArray[3, 0] = new Vector2Int(99, 99);bodyShirtOffsetArray[4, 0] = new Vector2Int(99, 99);bodyShirtOffsetArray[5, 0] = new Vector2Int(99, 99);bodyShirtOffsetArray[0, 1] = new Vector2Int(99, 99);bodyShirtOffsetArray[1, 1] = new Vector2Int(99, 99);bodyShirtOffsetArray[2, 1] = new Vector2Int(99, 99);bodyShirtOffsetArray[3, 1] = new Vector2Int(99, 99);bodyShirtOffsetArray[4, 1] = new Vector2Int(99, 99);bodyShirtOffsetArray[5, 1] = new Vector2Int(99, 99);bodyShirtOffsetArray[0, 2] = new Vector2Int(99, 99);bodyShirtOffsetArray[1, 2] = new Vector2Int(99, 99);bodyShirtOffsetArray[2, 2] = new Vector2Int(99, 99);bodyShirtOffsetArray[3, 2] = new Vector2Int(99, 99);bodyShirtOffsetArray[4, 2] = new Vector2Int(99, 99);bodyShirtOffsetArray[5, 2] = new Vector2Int(99, 99);bodyShirtOffsetArray[0, 3] = new Vector2Int(99, 99);bodyShirtOffsetArray[1, 3] = new Vector2Int(99, 99);bodyShirtOffsetArray[2, 3] = new Vector2Int(99, 99);bodyShirtOffsetArray[3, 3] = new Vector2Int(99, 99);bodyShirtOffsetArray[4, 3] = new Vector2Int(99, 99);bodyShirtOffsetArray[5, 3] = new Vector2Int(99, 99);bodyShirtOffsetArray[0, 4] = new Vector2Int(99, 99);bodyShirtOffsetArray[1, 4] = new Vector2Int(99, 99);bodyShirtOffsetArray[2, 4] = new Vector2Int(99, 99);bodyShirtOffsetArray[3, 4] = new Vector2Int(99, 99);bodyShirtOffsetArray[4, 4] = new Vector2Int(99, 99);bodyShirtOffsetArray[5, 4] = new Vector2Int(99, 99);bodyShirtOffsetArray[0, 5] = new Vector2Int(99, 99);bodyShirtOffsetArray[1, 5] = new Vector2Int(99, 99);bodyShirtOffsetArray[2, 5] = new Vector2Int(99, 99);bodyShirtOffsetArray[3, 5] = new Vector2Int(99, 99);bodyShirtOffsetArray[4, 5] = new Vector2Int(99, 99);bodyShirtOffsetArray[5, 5] = new Vector2Int(99, 99);bodyShirtOffsetArray[0, 6] = new Vector2Int(99, 99);bodyShirtOffsetArray[1, 6] = new Vector2Int(99, 99);bodyShirtOffsetArray[2, 6] = new Vector2Int(99, 99);bodyShirtOffsetArray[3, 6] = new Vector2Int(99, 99);bodyShirtOffsetArray[4, 6] = new Vector2Int(99, 99);bodyShirtOffsetArray[5, 6] = new Vector2Int(99, 99);bodyShirtOffsetArray[0, 7] = new Vector2Int(99, 99);bodyShirtOffsetArray[1, 7] = new Vector2Int(99, 99);bodyShirtOffsetArray[2, 7] = new Vector2Int(99, 99);bodyShirtOffsetArray[3, 7] = new Vector2Int(99, 99);bodyShirtOffsetArray[4, 7] = new Vector2Int(99, 99);bodyShirtOffsetArray[5, 7] = new Vector2Int(99, 99);bodyShirtOffsetArray[0, 8] = new Vector2Int(99, 99);bodyShirtOffsetArray[1, 8] = new Vector2Int(99, 99);bodyShirtOffsetArray[2, 8] = new Vector2Int(99, 99);bodyShirtOffsetArray[3, 8] = new Vector2Int(99, 99);bodyShirtOffsetArray[4, 8] = new Vector2Int(99, 99);bodyShirtOffsetArray[5, 8] = new Vector2Int(99, 99);bodyShirtOffsetArray[0, 9] = new Vector2Int(99, 99);bodyShirtOffsetArray[1, 9] = new Vector2Int(99, 99);bodyShirtOffsetArray[2, 9] = new Vector2Int(99, 99);bodyShirtOffsetArray[3, 9] = new Vector2Int(99, 99);bodyShirtOffsetArray[4, 9] = new Vector2Int(99, 99);bodyShirtOffsetArray[5, 9] = new Vector2Int(99, 99);bodyShirtOffsetArray[0, 10] = new Vector2Int(4, 11);bodyShirtOffsetArray[1, 10] = new Vector2Int(4, 10);bodyShirtOffsetArray[2, 10] = new Vector2Int(4, 11);bodyShirtOffsetArray[3, 10] = new Vector2Int(4, 12);bodyShirtOffsetArray[4, 10] = new Vector2Int(4, 11);bodyShirtOffsetArray[5, 10] = new Vector2Int(4, 10);bodyShirtOffsetArray[0, 11] = new Vector2Int(4, 11);bodyShirtOffsetArray[1, 11] = new Vector2Int(4, 12);bodyShirtOffsetArray[2, 11] = new Vector2Int(4, 11);bodyShirtOffsetArray[3, 11] = new Vector2Int(4, 10);bodyShirtOffsetArray[4, 11] = new Vector2Int(4, 11);bodyShirtOffsetArray[5, 11] = new Vector2Int(4, 12);bodyShirtOffsetArray[0, 12] = new Vector2Int(3, 9);bodyShirtOffsetArray[1, 12] = new Vector2Int(3, 9);bodyShirtOffsetArray[2, 12] = new Vector2Int(4, 10);bodyShirtOffsetArray[3, 12] = new Vector2Int(4, 9);bodyShirtOffsetArray[4, 12] = new Vector2Int(4, 9);bodyShirtOffsetArray[5, 12] = new Vector2Int(4, 9);bodyShirtOffsetArray[0, 13] = new Vector2Int(4, 10);bodyShirtOffsetArray[1, 13] = new Vector2Int(4, 9);bodyShirtOffsetArray[2, 13] = new Vector2Int(5, 9);bodyShirtOffsetArray[3, 13] = new Vector2Int(5, 9);bodyShirtOffsetArray[4, 13] = new Vector2Int(4, 10);bodyShirtOffsetArray[5, 13] = new Vector2Int(4, 9);bodyShirtOffsetArray[0, 14] = new Vector2Int(4, 9);bodyShirtOffsetArray[1, 14] = new Vector2Int(4, 12);bodyShirtOffsetArray[2, 14] = new Vector2Int(5, 7);bodyShirtOffsetArray[3, 14] = new Vector2Int(5, 5);bodyShirtOffsetArray[4, 14] = new Vector2Int(4, 9);bodyShirtOffsetArray[5, 14] = new Vector2Int(4, 12);bodyShirtOffsetArray[0, 15] = new Vector2Int(4, 8);bodyShirtOffsetArray[1, 15] = new Vector2Int(4, 5);bodyShirtOffsetArray[2, 15] = new Vector2Int(4, 9);bodyShirtOffsetArray[3, 15] = new Vector2Int(4, 12);bodyShirtOffsetArray[4, 15] = new Vector2Int(4, 8);bodyShirtOffsetArray[5, 15] = new Vector2Int(4, 5);bodyShirtOffsetArray[0, 16] = new Vector2Int(4, 9);bodyShirtOffsetArray[1, 16] = new Vector2Int(4, 10);bodyShirtOffsetArray[2, 16] = new Vector2Int(4, 7);bodyShirtOffsetArray[3, 16] = new Vector2Int(4, 8);bodyShirtOffsetArray[4, 16] = new Vector2Int(4, 9);bodyShirtOffsetArray[5, 16] = new Vector2Int(4, 10);bodyShirtOffsetArray[0, 17] = new Vector2Int(4, 7);bodyShirtOffsetArray[1, 17] = new Vector2Int(4, 8);bodyShirtOffsetArray[2, 17] = new Vector2Int(4, 9);bodyShirtOffsetArray[3, 17] = new Vector2Int(4, 10);bodyShirtOffsetArray[4, 17] = new Vector2Int(4, 7);bodyShirtOffsetArray[5, 17] = new Vector2Int(4, 8);bodyShirtOffsetArray[0, 18] = new Vector2Int(4, 10);bodyShirtOffsetArray[1, 18] = new Vector2Int(4, 9);bodyShirtOffsetArray[2, 18] = new Vector2Int(4, 9);bodyShirtOffsetArray[3, 18] = new Vector2Int(4, 10);bodyShirtOffsetArray[4, 18] = new Vector2Int(4, 9);bodyShirtOffsetArray[5, 18] = new Vector2Int(4, 9);bodyShirtOffsetArray[0, 19] = new Vector2Int(4, 10);bodyShirtOffsetArray[1, 19] = new Vector2Int(4, 9);bodyShirtOffsetArray[2, 19] = new Vector2Int(4, 9);bodyShirtOffsetArray[3, 19] = new Vector2Int(4, 10);bodyShirtOffsetArray[4, 19] = new Vector2Int(4, 9);bodyShirtOffsetArray[5, 19] = new Vector2Int(4, 9);bodyShirtOffsetArray[0, 20] = new Vector2Int(4, 10);bodyShirtOffsetArray[1, 20] = new Vector2Int(4, 9);bodyShirtOffsetArray[2, 20] = new Vector2Int(4, 9);bodyShirtOffsetArray[3, 20] = new Vector2Int(4, 10);bodyShirtOffsetArray[4, 20] = new Vector2Int(4, 9);bodyShirtOffsetArray[5, 20] = new Vector2Int(4, 9);}}

上面代碼用于實現角色的自定義功能,包括選擇角色性別、襯衫樣式、以及對角色手臂顏色進行替換,最后將這些自定義效果合并到一個紋理上。代碼解讀如下:

(1)命名空間和類的定義

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;[System.Serializable]
public class colorSwap
{public Color fromColor;public Color toColor;public colorSwap(Color fromColor, Color toColor){this.fromColor = fromColor;this.toColor = toColor;}
}public class ApplyCharacterCustomisation : MonoBehaviour
{// ...
}

colorSwap類:這是一個可序列化的類,用于存儲顏色替換信息。

ApplyCharacterCustomisation 類:用于處理角色自定義的邏輯

(2)成員變量的定義

// Input Textures
[Header("Base Textures")]
[SerializeField] private Texture2D maleFarmerBaseTexture = null;
[SerializeField] private Texture2D femaleFarmerBaseTexture = null;
[SerializeField] private Texture2D shirtsBaseTexture = null;
private Texture2D farmerBaseTexture;// Created Textures
[Header("OutputBase Texture To Be Used For Animation")]
[SerializeField] private Texture2D farmerBaseCustomised = null;
private Texture2D farmerBaseShirtsUpdated;
private Texture2D selectedShirt;// Select Shirt style
[Header("Select Shirt Style")]
[Range(0, 1)]   // 紅色襯衫或者綠色襯衫
[SerializeField] private int inputShirtStyleNo = 0;// Selecct Sex
[Header("Select Sex:0=Male, 1=female")]
[Range(0, 1)]
[SerializeField] private int inputSex = 0;private Facing[,] bodyFacingArray;
private Vector2Int[,] bodyShirtOffsetArray;// 定義精靈表的維度信息
private int bodyRows = 21;
private int bodyColumns = 6;
private int farmerSpriteWidth = 16;
private int farmerSpriteHeight = 32;private int shirtTextureWidth = 9;
private int shirtTextureHeight = 36;
private int shirtSpriteWidth = 9;
private int shirtSpriteHeight = 9;
private int shirtStylesInSpriteWidth = 16;  // 紋理可以容納襯衫的件數// 顏色交換列表
private List<colorSwap> colorSwapList;// Target arm colours for color replacement
private Color32 armTargetColor1 = new Color32(77, 13, 13, 255); // darkest
private Color32 armTargetColor2 = new Color32(138, 41, 41, 255); // next darkest
private Color32 armTargetColor3 = new Color32(172, 50, 50, 255); // lightest

1)輸入紋理

maleFarmerBaseTexture和femaleFarmerBaseTexture分別是男性和女性farmer的基礎紋理,shirtsBaseTexture是襯衫的基礎紋理。

2)輸出紋理

farmerBaseCustomised是最終自定義后的角色紋理,farmerBaseShirtsUpdated是更新了襯衫后的紋理,selectedShirt是選中的襯衫紋理

3)自定義選項

inputShirtStyleNo用于選擇襯衫樣式

inputSex用于選擇角色性別

4)精靈表信息

定義了角色精靈表和襯衫精靈表的維度信息

5)顏色交換列表

colorSwapList用于存儲顏色替換信息

6)目標手臂顏色

armTargetColor1~3是需要替換的手臂顏色

(3)ProcessGender方法

private void ProcessGender()
{// Set base spritesheet by genderif(inputSex == 0){farmerBaseTexture = maleFarmerBaseTexture;}else if(inputSex == 1){farmerBaseTexture = femaleFarmerBaseTexture;}// Get base pixelsColor[] farmerBasePixels = farmerBaseTexture.GetPixels();// Set changed base pixelsfarmerBaseCustomised.SetPixels(farmerBasePixels);farmerBaseCustomised.Apply();
}

根據inputSex的值選擇男性或女性farmer的基礎紋理。

獲取基礎紋理的像素,并將其應用到farmerBaseCustomised紋理上。

(4)ProcessShirt方法

private void ProcessShirt()
{// Initialise body facing shirt arraybodyFacingArray = new Facing[bodyColumns, bodyRows];// Populate body facing shirt arrayPopulateBodyFacingArray();// Initialise body shirt offset arraybodyShirtOffsetArray = new Vector2Int[bodyColumns, bodyRows];// Populate body shirt offset arrayPopulateBodyShirtOffsetArray();// Create Selected Shirt TextureAddShirtToTexture(inputShirtStyleNo);// Apply shirt texture to baseApplyShirtTextureToBase();
}

初始化 bodyFacingArray 和 bodyShirtOffsetArray 數組,并調用 PopulateBodyFacingArray 和 PopulateBodyShirtOffsetArray 方法填充數組。bodyFacingArray?二維數組存儲了角色每個精靈的朝向信息,?bodyShirtOffsetArray?二維數組存儲了角色每個精靈上襯衫的偏移信息。

調用 AddShirtToTexture 方法創建選中的襯衫紋理。該方法用于從?shirtsBaseTexture?中提取指定樣式的襯衫紋理,并將其應用到?selectedShirt?紋理上。

private void AddShirtToTexture(int shirtStyleNo)
{// Create shirt textureselectedShirt = new Texture2D(shirtTextureWidth, shirtTextureHeight);selectedShirt.filterMode = FilterMode.Point;// Calculate coordinates for shirt pixelsint y = (shirtStyleNo / shirtStylesInSpriteWidth) * shirtTextureHeight;int x = (shirtStyleNo % shirtStylesInSpriteWidth) * shirtTextureWidth;// Get shirts pixelsColor[] shirtPixels = shirtsBaseTexture.GetPixels(x, y, shirtTextureWidth, shirtTextureHeight);// Apply selected shirt pixels to textureselectedShirt.SetPixels(shirtPixels);selectedShirt.Apply();
}

shirtPixels得到的一列4件襯衫的像素信息。

調用?selectedShirt.SetPixels?方法將襯衫像素應用到?selectedShirt?紋理上,并調用?selectedShirt.Apply()?方法應用紋理更改。

(5)ProcessArms 方法

private void ProcessArms()
{// Get arm pixels to recolorColor[] farmerPixelsToRecolor = farmerBaseTexture.GetPixels(0, 0, 288, farmerBaseTexture.height);// Populate arm color swap listPopulateArmColorSwapList();// Change arm colorsChangePixelColors(farmerPixelsToRecolor, colorSwapList);// Set recolored pixelsfarmerBaseCustomised.SetPixels(0, 0, 288, farmerBaseTexture.height, farmerPixelsToRecolor);// Apply texture changesfarmerBaseCustomised.Apply();
}

其中:

Color[] farmerPixelsToRecolor = farmerBaseTexture.GetPixels(0, 0, 288, farmerBaseTexture.height);

這行代碼從?farmerBaseTexture?紋理中獲取從坐標?(0, 0)?開始,寬度為 288 像素,高度為?farmerBaseTexture?紋理高度的所有像素顏色,并將其存儲在?farmerPixelsToRecolor?數組中。

PopulateArmColorSwapList();private void PopulateArmColorSwapList()
{// Clear color swap listcolorSwapList.Clear();// Arms replacement colorscolorSwapList.Add(new colorSwap(armTargetColor1, selectedShirt.GetPixel(0, 7)));colorSwapList.Add(new colorSwap(armTargetColor2, selectedShirt.GetPixel(0, 6)));colorSwapList.Add(new colorSwap(armTargetColor3, selectedShirt.GetPixel(0, 5)));
}

調用?PopulateArmColorSwapList?方法,該方法會清空?colorSwapList?列表,并根據?selectedShirt?紋理上的特定位置的顏色,添加需要替換的手臂顏色信息到?colorSwapList?中。

ChangePixelColors(farmerPixelsToRecolor, colorSwapList);private void ChangePixelColors(Color[] baseArray, List<colorSwap> colorSwapList)
{for(int i = 0; i < baseArray.Length; i++){// Loop through color swap listif(colorSwapList.Count > 0){for(int j = 0; j < colorSwapList.Count; j++){if (isSameColor(baseArray[i], colorSwapList[j].fromColor)){baseArray[i] = colorSwapList[j].toColor;}}}}
}

調用?ChangePixelColors?方法,該方法會遍歷?farmerPixelsToRecolor?數組中的每個像素,并根據?colorSwapList?中的顏色替換信息,將符合條件的像素顏色進行替換。

farmerBaseCustomised.SetPixels(0, 0, 288, farmerBaseTexture.height, farmerPixelsToRecolor);

將重新著色后的像素顏色數組?farmerPixelsToRecolor?應用到?farmerBaseCustomised?紋理的指定區域,該區域從坐標?(0, 0)?開始,寬度為 288 像素,高度為?farmerBaseTexture?紋理高度。

(6)MergeCustomisations方法

private void MergeColorArray(Color[] baseArray, Color[] mergeArray)
{for(int i = 0; i < baseArray.Length; i++){if (mergeArray[i].a > 0){// Merge array has colorif (mergeArray[i].a >= 1){// Fully replacebaseArray[i] = mergeArray[i];}else{// Interpolate colorsfloat alpha = mergeArray[i].a;baseArray[i].r += (mergeArray[i].r - baseArray[i].r) * alpha;baseArray[i].g += (mergeArray[i].g - baseArray[i].g) * alpha;baseArray[i].b += (mergeArray[i].b - baseArray[i].b) * alpha;baseArray[i].a += mergeArray[i].a;}}}
}

該方法用于合并兩個顏色數組,根據?mergeArray?中像素的透明度進行不同的處理:如果透明度為 1,則完全替換;否則,進行顏色插值。

當像素為半透明時,需要對顏色進行插值操作,以實現顏色的混合效果,具體計算方法是:

  • 對于紅、綠、藍三個顏色通道,計算?mergeArray?中對應通道值與?baseArray?中對應通道值的差值,然后乘以透明度?alpha,再將結果加到?baseArray?中對應通道的值上。
  • 對于透明度通道,直接將?mergeArray?中當前像素的透明度加到?baseArray?中對應像素的透明度上。

5、處理Player對象

Hierarchy -> Player -> CharacterCustomiser添加ApplyCharacterCustomisation組件,配置參數如下:

6、運行游戲

操作Select Shirt Style / Select Sex兩個range值,可以看到角色不用的顏色效果。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/85700.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/85700.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/85700.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【HarmonyOS 5】游戲開發教程

一、開發環境搭建 ?工具配置? 安裝DevEco Studio 5.1&#xff0c;啟用CodeGenie AI助手&#xff08;Settings → Tools → AI Assistant&#xff09;配置游戲模板&#xff1a;選擇"Game"類型項目&#xff0c;勾選手機/平板/折疊屏多設備支持 二、游戲引擎核心架構…

深度探索:如何用DeepSeek重構你的工作流

前言:AI時代的工作革命 在人工智能浪潮席卷的今天,DeepSeek作為國產大模型的代表之一,正以其強大的自然語言處理能力、代碼生成能力和多模態交互特性,重新定義著人類的工作方式。根據IDC報告顯示,2024年企業級AI應用市場規模已突破800億美元,其中智能辦公場景占比達32%,…

Linux 進程調度與管理:從內核管理到調度機制的深度解析

文章目錄 引言一、進程基礎&#xff1a;概念與核心數據結構1.1 進程的本質&#xff1a;程序的動態化身1.2 進程控制塊&#xff08;PCB&#xff09;&#xff1a;內核管理的靈魂1.2.1 鏈表節點嵌入1.2.2 鏈表操作宏1.2.3 全局鏈表管理 1.3 進程查看與系統調用1.3.1 通過系統調用獲…

信息學奧賽一本通 1570:【例 2】能量項鏈 | 1843:【06NOIP提高組】能量項鏈 | 洛谷 P1063 [NOIP 2006 提高組] 能量項鏈

【題目鏈接】 ybt 1570&#xff1a;【例 2】能量項鏈 ybt 1843&#xff1a;【06NOIP提高組】能量項鏈 洛谷 P1063 [NOIP 2006 提高組] 能量項鏈 【題目考點】 1. 動態規劃&#xff1a;區間動規 2. 環形序列 解決方法&#xff1a;破環為鏈 模板題&#xff1a;洛谷 P1880 [N…

旅游微信小程序制作指南

想創建旅游微信小程序嗎&#xff1f;知道旅游業企業怎么打造自己的小程序嗎&#xff1f;這里有零基礎小白也能學會的教程&#xff0c;教你快速制作旅游類微信小程序&#xff01; 旅游行業能不能開發微信小程序呢&#xff1f;答案是肯定的。微信小程序對旅游企業來說可是個寶&am…

Vue3+Vite中lodash-es安裝與使用指南

在 Vue 3 Vite 項目中安裝和使用 lodash-es 的詳細指南如下&#xff1a; 一、為什么選擇 lodash-es&#xff1f; ES 模塊支持&#xff1a;lodash-es 以原生 ES 模塊格式發布&#xff0c;支持現代構建工具的 Tree Shaking 按需加載&#xff1a;只引入需要的函數&#xff0c;顯…

法律模型選型

當然可以&#xff0c;以下是關于法律法規相關模型的技術選型調研建議&#xff0c;適合算法實習生從0入手&#xff0c;并能交付有深度的調研報告&#xff1a; 一、調研背景與目標 目標&#xff1a;調研用于處理法律法規類任務的大模型與技術方案&#xff0c;明確適合本團隊的模…

軟件工程專業的本科生應該具備哪些技能

軟件工程專業的本科生需要具備扎實的技術基礎、良好的開發流程認知和一定的軟技能&#xff0c;以適應軟件開發行業的需求。以下從技術技能、開發流程與工具、軟技能、實踐能力等維度整理核心技能清單&#xff0c;供參考&#xff1a; 一、核心技術技能 1. 編程語言 - 必學基礎語…

[Java 基礎]類,面向對象的藍圖

首先需要區分類和對象都是啥&#xff1f; 類&#xff1a;類是一個模板&#xff0c;它描述一類對象的行為和狀態&#xff0c;類這個概念更像是下定義&#xff0c;更像是模板&#xff08;橡皮泥膜具&#xff09;。 對象&#xff1a;對象&#xff08;不是女朋友&#xff09;是類…

selenium-自動更新谷歌瀏覽器驅動

1、簡介 selenium最初是一個自動化測試工具&#xff0c;而爬蟲中使用它主要是為了解決requests無法直接執行JavaScript代碼的問題&#xff0c;因為有些網頁數據是通過JavaScript動態加載的。selenium本質是通過驅動瀏覽器&#xff0c;完全模擬瀏覽器的操作&#xff0c;比如輸入…

java從azure中讀取用戶信息

以下是用 Java 從 Azure AD 獲取用戶信息的完整實現方案&#xff0c;使用 Spring Boot 框架和 Microsoft 身份驗證庫 (MSAL)&#xff1a; 1. 添加 Maven 依賴 <dependencies> <!-- Spring Boot Web --> <dependency> <groupId>org.…

C# 數據庫訪問與ORM框架全面指南:從ADO.NET到Entity Framework Core

在現代應用開發中&#xff0c;數據持久化是核心需求之一。作為.NET生態系統中的主力語言&#xff0c;C#提供了豐富多樣的數據庫訪問技術和工具。本文將全面探討C#中的數據庫訪問方式&#xff0c;重點介紹三種主流ORM&#xff08;對象關系映射&#xff09;框架&#xff1a;Entit…

day19 leetcode-hot100-37(二叉樹2)

104. 二叉樹的最大深度 - 力扣&#xff08;LeetCode&#xff09; 1.深度優先遍歷&#xff08;遞歸&#xff09;ps:不好理解&#xff0c;所以我一般不喜歡用遞歸 思路 典型算法&#xff0c;用遞歸求出高度&#xff0c;每次都是深度優先。 具體算法 /*** Definition for a bi…

【LLMs篇】13:LLaDA—大型語言擴散模型

欄目內容論文標題大型語言擴散模型 (Large Language Diffusion Models)核心思想提出LLaDA&#xff0c;一種基于擴散模型的LLM&#xff0c;通過前向掩碼和反向預測過程建模語言分布&#xff0c;挑戰自回歸模型&#xff08;ARM&#xff09;在LLM領域的主導地位&#xff0c;并展示…

Deepfashion2 數據集使用筆記

目錄 數據類別: 篩選類別數據: 驗證篩選前2個類別: Deepfashion2 的解壓碼 數據類別: 類別含義: Class idx類別名稱英文名稱0短上衣short sleeve top1長上衣long sleeve top2短外套short sleeve outwear3長外套long sleeve outwear4裙子skirt5褲子trousers6連衣裙dre…

Java并發編程哲學系列匯總

文章目錄 并發編程基礎并發編程進階并發編程實踐 并發編程基礎 Java并發編程基礎小結 Java線程池知識點小結 詳解JUC包下各種鎖的使用 并發編程利器Java CAS原子類全解 深入理解Java中的final關鍵字 Java并發容器深入解析&#xff1a;HashMap與ArrayList線程安全問題及解…

git 之 stash

一、git stash&#xff1a;臨時保存工作區修改 作用 將當前工作目錄和暫存區的未提交修改保存到棧中&#xff0c;并恢復工作區到上一次提交的干凈狀態。 適用場景&#xff1a; 臨時切換分支修復緊急 Bug拉取遠程代碼前清理工作區保存實驗性代碼避免生成無效提交 常用命令&am…

vxe-grid 雙擊行,打開expand的內容

1、官網api Vxe Table v4.6&#xff08;根據版本&#xff09; 要調用這個事件&#xff0c;雙擊單元格&#xff0c;我們打開type"expand"的內容 2、打開的事件toggleRowExpand 3、事件的說明 這個方法&#xff0c;會自動判斷當前展開的狀態&#xff0c;然后去觸發相…

Java Stream 高級實戰:并行流、自定義收集器與性能優化

一、并行流深度實戰&#xff1a;大規模數據處理的性能突破 1.1 并行流的核心應用場景 在電商用戶行為分析場景中&#xff0c;需要對百萬級用戶日志數據進行實時統計。例如&#xff0c;計算某時段內活躍用戶數&#xff08;訪問次數≥3次的用戶&#xff09;&#xff0c;傳統循環…

計算機系統結構-第5章-監聽式協議

監聽式協議******&#xff1a; 思想: 每個Cache除了包含物理存儲器中塊的數據拷貝之外&#xff0c;也保存著各個塊的共享狀態信息。 Cache通常連在共享存儲器的總線上&#xff0c;當某個Cache需要訪問存儲器時&#xff0c;它會把請求放到總線上廣播出去&#xff0c;其他各個C…