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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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…