问题描述:

  今天实现的一个功能是在敌人实例化之前,先让出生点的地砖闪烁一秒,再生成敌人。使用协程实现。先获取目标地砖的材质属性,确定初始色和高亮色,然后使用Mathf.PingPong()产生振荡效果,进行初始色到高亮色的插值变化。结果发现所有的地砖同时闪烁。

 

解决:

  经过调试发现确实获取到了出生点处的地砖及其材质,但是获取材质的方法用的是GetComponent<Renderer>().sharedMaterial,应该是GetComponent<Renderer>().material。之前在渲染障碍物颜色时使用过这个sharedMaterial,当时在网上查了一下两者的区别:

  1. sharedMaterial 是共用的 Material,称为共享材质。修改共享材质会改变所用使用该材质的物体,并且编辑器中的材质设置也会改变。
  2. material 是独立的 Material,返回分配给渲染器的第一个材质。修改材质仅会改变该物体的材质。如果该材质被其他的渲染器使用,将克隆该材质并用于当前的渲染器。

  所以当我改变sharedMaterial的颜色时,所有地砖都使用的改材质,所以全部都会变色。那么问题又来了,之前生成的那么多障碍物使用的就是sharedMaterial,为什么还能显示那么多种颜色呢?以下是源码:

// 渲染障碍物颜色
Renderer obstacleRenderer = newObstacle.GetComponent<Renderer>();
Material obstacleMaterial = new Material(obstacleRenderer.sharedMaterial); // 使用material属性可能会造成内存泄漏
float colourPercent = randomCoord.y / (float)currentMap.mapSize.y; // 提前转化为float防止int整除
obstacleMaterial.color = Color.Lerp(currentMap.foregroundColour, currentMap.backgroundColour, colourPercent);
obstacleRenderer.sharedMaterial = obstacleMaterial;

虽然中间new了新的材质,但是最后也是直接将sharedMaterial换成新的obstacleMaterial,按道理来说所有的障碍物也应该会变成同一种颜色?尝试将代码改成如下,发现所有障碍物都变成同一种颜色了。

Renderer obstacleRenderer = newObstacle.GetComponent<Renderer>();
Material obstacleMaterial = new Material(obstacleRenderer.sharedMaterial); // 使用material属性可能会造成内存泄漏
float colourPercent = randomCoord.y / (float)currentMap.mapSize.y; // 提前转化为float防止int整除
Color color = Color.Lerp(currentMap.foregroundColour, currentMap.backgroundColour, colourPercent);
obstacleRenderer.sharedMaterial.color = color;

两者区别在于,第一种直接将物体的sharedMaterial换成才new出来的新的Matrial,而第二种是将sharedMaterial的color属性换成新的Color。

  我首相尝试输出sharedMaterial变化前后的内存地址,但是发现两种代码输出的地址都发生了变化,那么从此角度寻求答案无果。C#获取引用类型地址:

// 需要引入System.Runtime.InteropServices;
GCHandle  h = GCHandle.Alloc(o, GCHandleType.Pinned);
IntPtr addr = h.AddrOfPinnedObject();
Debug.Log("0x" + addr.ToString("x");

  后来直接模拟了一下。首先创建三个Cube:cube1、cube2、cube3,两个Material:mat1、mat2。将mat1(绿色)附加给cube1,mat2(黄色)附加给cube2和cube3,如图所示。

unity 材质 maskable_内存泄漏

首先尝试代码:

Material newMat = new Material(cube1.GetComponent<Renderer>().sharedMaterial);
cube2.GetComponent<Renderer>().sharedMaterial.color = newMat.color;

运行后cube2和cube3都变成了绿色,其挂载的Material仍然是mat2,且停止运行后mat2仍然是绿色。这符合之前所述的sharedMaterial介绍。

unity 材质 maskable_高亮_02

unity 材质 maskable_unity 材质 maskable_03

unity 材质 maskable_渲染器_04

手动恢复mat2的颜色,然后改变代码:

Material newMat = new Material(cube1.GetComponent<Renderer>().sharedMaterial);
  //cube2.GetComponent<Renderer>().sharedMaterial.color = newMat.color;
  cube2.GetComponent<Renderer>().sharedMaterial = newMat;

此时运行后发现,cube2的颜色变成了绿色,mat2未改变,cube3颜色没变。观察Inspector面板进一步发现,cube2的Material变成了mat1,cube3的Material仍然是mat2。结束游戏运行之后,cube2又变回了黄色,Material为mat2。

unity 材质 maskable_渲染器_05

unity 材质 maskable_高亮_06

unity 材质 maskable_unity 材质 maskable_07

 

  到此为止就很明显了,文章开头两种代码,第一种使用xxx.shareMaterial == yyy直接改变了该组件对原Material的指引,指向了新的Material,此时改变其Color属性并不会影响原来的Material,所以挂载原来Material的物体仍然是原来那个色。第二种方法仍然指向原来的Material,但是改变了其Color属性,这样一来所有挂载这个Material的物体也就全部变色了。