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值,可以看到角色不用的顏色效果。