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