// 设置贴图
Texture mTexture = Resources.Load("myTexture", typeof(Texture )) as Texture;
material.SetTexture("_MainTex", mTexture );
// 设置整数
material.SetInt("_Int", 1);
// 设置浮点
material.SetFloat("_Float", 0.1f);
// 设置颜色 rgba
material.SetColor("_Color", Color.white);
// 设置向量 xyzw
material.SetVector("_Vector", new Vector4());
// shader的正常接口是没有bool类型的。但通过#pragma multi_compile __ UNITY_name可以实现
// 设置shader的UNITY_name为true
material.EnableKeyword("UNITY_name");
// 设置shader的UNITY_name为true
material.DisableKeyword("UNITY_name");
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/lengyoumo/article/details/105167565
一、协程的概念
什么是协程? - 知乎 (zhihu.com)
我们都知道多线程,当需要同时执行多项任务的时候,就会采用多线程并发执行。拿手机支付举例子,当收到付款信息的时候,需要查询数据库来判断余额是否充足,然后再进行付款。
假设最开始我们只有可怜的10个用户,收到10条付款消息之后,我们开启启动10个线程去查询数据库,由于用户量很少,结果马上就返回了。
然而当用户开始爆炸增长,这时候有10000人同时在线付款,你打算启动10000个线程来处理任务。等等,问题来了,因为每个线程至少会占用4M的内存空间,10000个线程会消耗39G的内存,而服务器的内存配置只有区区8G,这时候你有2种选择,一是选择增加服务器,二是选择提高代码效率。那么是否有方法能够提高效率呢?
我们知道操作系统在线程等待IO的时候,会阻塞当前线程,切换到其它线程,这样在当前线程等待IO的过程中,其它线程可以继续执行。当系统线程较少的时候没有什么问题,但是当线程数量非常多的时候,却产生了问题。一是系统线程会占用非常多的内存空间,二是过多的线程切换会占用大量的系统时间。
协程刚好可以解决上述2个问题。协程运行在线程之上,当一个协程执行完成后,可以选择主动让出,让另一个协程运行在当前线程之上。协程并没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程,而且协程的切换在用户态完成,切换的代价比线程从用户态到内核态的代价小很多。
回到上面的问题,我们只需要启动100个线程,每个线程上运行100个协程,这样不仅减少了线程切换开销,而且还能够同时处理10000个读取数据库的任务,很好的解决了上述任务。
什么是协程?
协程,从字面意义上理解就是协助程序的意思,我们在主任务进行的同时,需要一些分支任务配合工作来达到最终的效果
稍微形象的解释一下,想象一下,在进行主任务的过程中我们需要一个对资源消耗极大的操作时候,如果在一帧中实现这样的操作,游戏就会变得十分卡顿,这个时候,我们就可以通过协程,在一定帧内完成该工作的处理,同时不影响主任务的进行
首先需要了解协程不是线程,协程依旧是在主线程中进行
然后要知道协程是通过迭代器来实现功能的,通过关键字IEnumerator来定义一个迭代方法,注意使用的是IEnumerator
二、协程的使用
void Start ( )
{
//第一种,无参可以直接使用方法名“字符串”来启用协程
StartCoroutine("wjy");
//第二种,无参可以调用方法来启用协程
StartCoroutine(wjy());
//有参
StartCoroutine(jwy(1));
//第三种,有参
StartCoroutine("yjw",1,2);
}
IEnumerator wjy()
{
yield return null;
}
IEnumerator jwy(int b)
{
yield return null;
}
IEnumerator yjw(int i,int r)
{
yield return null;
}
IEnumerator需要和yield配合使用
-
yield return null
; 暂停协程等待下一帧继续执行 -
yield return 0或其他数字
; 暂停协程等待下一帧继续执行 -
yield return new WairForSeconds(时间)
; 等待规定时间后继续执行 -
yield break
; 跳出协程对应方法,其后面的代码不会被执行 yield return new WaitForEndOfFrame()
:等到所有相机画面被渲染完毕后更新
yield 位于 Update() 和 LateUpdate() 之间
三、使用 yield return new WaitForSeconds() 来控制材质球的变化
通过脚本和材质Shader的配合,给角色添加一个被击的效果
当点击被击Hit按钮时,角色的材质有原来的颜色变为红色,等待数秒后变回原来的颜色
可以使用协程yield return new WaitForSeconds()来实现
首先先创建一个空物体,命名为Scripts,用来挂载脚本。
然后创建脚本并命名为material02,在脚本中创建一个变量,用来存放角色。
public GameObject Monster;
将用于变化材质的对象赋予该变量Monster。
随后在场景中创建一个按钮Button
在脚本中添加一个方法OnHit()(角色被击的方法),将之前挂载了脚本的空对象拖到按钮的On Click()中
然后在方法Function中选择该脚本中的相关方法
创建好被击的方法后
再创建协程,然后在被击的方法中调用该协程,完成角色被击的效果
public void OnHit()
{
StopAllCoroutines();
StartCououtine(WaitBeHit());
}
IEnumerator WaitBeHit()
{
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.SetColor("_Color",Color.red);
// 挂起,等待0.2s后继续执行下面的语句
yield return new WaitForSeconds(0.2f);
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.SetColor("_Color",Color.balck);
}
四、利用协程 yield return null 将循环中的内容分帧执行,然后实现角色被毒的效果
首先也是需要创建被毒的按钮,将脚本中的被毒的方法OnDie()拖动到该按钮上
然后在协程中利用循环和Lerp()函数、yield return null 实现角色从绿色到黑色的渐变
yield return null ; //表示暂停协程等待到下一帧后继续执行
该协程中_Time初始值为0,在循环中_Time每增加一个deltaTime,使用_Time/time的值来采样Lerp()函数中的值(time为自设值),当遇到yield return null时挂起循环,然后等待下一帧继续执行循环中的下一个,直到不满足循环的条件后跳出循环
所以这样就会实现角色在time时间段内,颜色由绿色渐变为源颜色
public void OnDU()
{
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial = material01;
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.DisableKeyword("_ClipEnable");
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.SetFloat("_Clip", -0.1f);
//在一个协程开始前,停止所有协程
StopAllCoroutines();
//怪物中毒的协程(在多帧下完成一个函数,常用于变化动态效果)
StartCoroutine(WaitDu(2f));
}
//被毒协程
IEnumerator WaitDu(float time)
{
float _Time = 0;
while(_Time<time)
{
_Time += Time.deltaTime;
//yield return new WaitForEndOfFrame();
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.SetColor("_Color", Color.Lerp(Color.green, Color.black, _Time / time));
//yield return new WaitForEndOfFrame();
yield return null;
}
yield break;
}
五、利用循环和协程 yield return null 以及 SetFloat实现角色逐渐熔解的效果
public void OnDie()
{
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial = material01;
StopAllCoroutines();
StartCoroutine(WaitDie(2f));
}
IEnumerator WaitDie(float time)
{
float _Time=0;
while(_Time<time)
{
_Time += Time.deltaTime;
//设置颜色参数
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.SetColor("_Color", Color.black);
//SetFloat用于设置Float类型的参数 例如_Clip
//使变体有效,引号中需要写宏的名称
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.EnableKeyword("_CLIPENABLE_ON");
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.SetFloat("_Clip",_Time/time);
//挂起等待这一帧执行完后在继续执行该循环
yield return null;
}
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.SetColor("_Color", Color.black);
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.SetFloat("_Clip", -0.1f);
yield break;
}
}
当shader中有开关效果时,需要使用EnableKeyWord来打开变体开关
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMateral.EnableKeyword("_CLIPENABLE_ON")
// _CLIPENABLE_ON为变体宏的名称
六、切换材质球
七、Material和sharedMaterial的区别
Material:
sharedMaterial:
【太妃糖耶】视频加载中,速速查收惊喜!
脚本:
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class material02 : MonoBehaviour
{
public GameObject Monster;
// Start is called before the first frame update
public Material material;
public Material material01;
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void Onclick()
{
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial = material01;
//修改材质球的颜色属性
//Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.SetColor("_Color",Color.red);
//在一个协程开始前,停止所有协程
StopAllCoroutines();
//怪物被击协程(等待)
StartCoroutine(WaitBehit());
}
public void OnDU()
{
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial = material01;
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.DisableKeyword("_ClipEnable");
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.SetFloat("_Clip", -0.1f);
//在一个协程开始前,停止所有协程
StopAllCoroutines();
//怪物中毒的协程(在多帧下完成一个函数,常用于变化动态效果)
StartCoroutine(WaitDu(2f));
}
public void OnDie()
{
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial = material01;
StopAllCoroutines();
StartCoroutine(WaitDie(2f));
}
public void SwitchMaterial()
{
StopAllCoroutines();
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial = material;
}
//定义关于被击协程
IEnumerator WaitBehit()
{
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.EnableKeyword("_ClipEnable");
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.SetFloat("_Clip", -0.1f);
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.SetColor("_Color", Color.red);
yield return new WaitForSeconds(0.2f);
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.SetColor("_Color", Color.black);
}
//被毒协程
IEnumerator WaitDu(float time)
{
float _Time = 0;
while(_Time<time)
{
_Time += Time.deltaTime;
//yield return new WaitForEndOfFrame();
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.SetColor("_Color", Color.Lerp(Color.green, Color.black, _Time / time));
//yield return new WaitForEndOfFrame();
yield return null;
}
yield break;
}
IEnumerator WaitDie(float time)
{
float _Time=0;
while(_Time<time)
{
_Time += Time.deltaTime;
//设置颜色参数
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.SetColor("_Color", Color.black);
//SetFloat用于设置Float类型的参数 例如_Clip
//使变体有效,引号中需要写宏的名称
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.EnableKeyword("_CLIPENABLE_ON");
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.SetFloat("_Clip",_Time/time);
//挂起等待这一帧执行完后在继续执行该循环
yield return null;
}
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.SetColor("_Color", Color.black);
Monster.GetComponentInChildren<SkinnedMeshRenderer>().sharedMaterial.SetFloat("_Clip", -0.1f);
yield break;
}
}
角色Shader:
Shader"unity/practice04"
{
Properties
{
_Color("Color",Color)=(0,0,0,0)
_MainTex("MainTex",2D)="white"{}
_DissolveTex("DissolveTex",2D)="white"{}
_RampTex("RampTex",2D)="black"{}
[Toggle]_ClipEnable("ClipEnable",int)=0
_Clip("Clip",Range(-0.1,1))=0
}
SubShader
{
Offset 1,1
// UsePass"unity/X_Ray02/XRayPass"
Pass
{
CGPROGRAM
#pragma vertex vert;
#pragma fragment frag;
//在使用multi_compile变体时,前面须添加一个空变体才有效
#pragma multi_compile _ _CLIPENABLE_ON
#include "UnityCG.cginc"
//声明变量
sampler2D _MainTex;
sampler2D _DissolveTex;
float4 _DissolveTex_ST;
sampler _RampTex; //优化 RampTex贴图只用到了一个方向的数值
fixed _Clip;
float4 _Color;
struct appdate
{
float4 vertex:POSITION;
float4 uv:TEXCOORD;
};
struct v2f
{
float4 pos:SV_POSITION;
float4 uv:TEXCOORD;
};
v2f vert(appdate v)
{
v2f o;
o.pos=UnityObjectToClipPos(v.vertex); //转换为齐次裁剪空间坐标
//使第二张纹理贴图uv与主贴图的uv分开,方便调节DissolveTex贴图的平铺与偏移而不影响主贴图
o.uv.xy=v.uv.xy;
//o.uv.zw=v.uv*_DissolveTex_ST.xy+_DissolveTex_ST.zw;
o.uv.zw=TRANSFORM_TEX(v.uv,_DissolveTex);
return o;
}
float4 frag(v2f i):SV_TARGET
{
float4 c;
float4 mainTex=tex2D(_MainTex,i.uv.xy);
c=mainTex;
c+=_Color;
float4 dissolveTex=tex2D(_DissolveTex,i.uv.zw);
//smoothstep 需要优化
//float4 rampTex=tex1D(_RampTex,smoothstep(_Clip,_Clip+0.1,dissolveTex.r));
#if _CLIPENABLE_ON
fixed ramp=saturate((dissolveTex.r-_Clip)/(_Clip+0.1-_Clip));
clip(dissolveTex.r-_Clip);
float4 rampTex =tex2D(_RampTex,ramp);
c+=rampTex;
#endif
return c;
}
ENDCG
}
}
}