今日学习: Unity画符
在实体中,我们可以在纸上进行涂鸦;而在一些软件中,我们可以使用手指进行涂鸦,或者用手绘板、鼠标进行画图;游戏《阴阳师》中也有画符。我今天学习的内容,就是在unity中进行画符(实际上就是模仿阴阳师的画符)。
想要在unity中进行画符,首先要知道,怎么才能画出来。鼠标(或者手指)在屏幕上移动,然后走过的地方,就留下了痕迹,这就成了画符。提取关键部分:鼠标移动,走过的地方,痕迹。再简化些,就是鼠标经过的位置,以及屏幕上画图。
鼠标移动
我们先来看第一个部分:鼠标经过的位置。我们需要知道鼠标经过的位置,才能画出来图像。
有没有一种方法,将鼠标经过的位置记录下来呢?当然,我学到了一种方法:将鼠标经过的位置都记录下来,存在一个List中,当鼠标移动时,对链表的内容实时更新,这样,鼠标经过的位置就全部被记录下来了。
//创建一个List链表
private List<Vector3> allPoints;
private void Start()
{
allPoints = allPoints = new List<Vector3>();
}
private void Update()
{
if(Input.GetMouseButton(0))
{
//创建一个Vector3类型的临时变量,用于存放视图坐标。
Vector3 tmpView = Camera.main.ScreenToViewportPoint(Input.mousePosition);
allPoints.Add(tmpView);
}
}
复习一下unity中的几种坐标系:
- 世界坐标系:(0,0,0)为坐标原点;
- 局部坐标系:以物体本身的位置为原点;
- 相机坐标系:以相机位置为原点;
- 屏幕坐标系:以屏幕左下角为原点(0,0),右上角是屏幕的宽和高。例如,如果屏幕为1280*720,那么,屏幕右上角就是(1280,720);
- 视口坐标系:左下角为(0,0)点,右上角为(1,1)点;
- 绘制UI的坐标系:与屏幕坐标系不同,是以左上角为原点,右下角是屏幕的宽高。
当然还有其他坐标系,例如我刚刚查到的惯性坐标系,嵌套坐标系等,这些坐标系在今天的复习回顾中用不到,暂且搁置。
着重介绍一下视口坐标系。跟前几天绘制小地图一样,绘制画符也会用到比例,视口坐标系有一个好处,可以当比例使用。当一个物体位于屏幕正中心时,它的视口坐标系下的坐标就是就(0.5, 0.5),当把这个物体映射到一张300*270的图片(图片左下角为原点)上时,它在图片上的坐标就是(150,135)。
Camera.main.ScreenToViewportPoint()这个方法就是将屏幕坐标系转换为视口坐标系。
记录鼠标位置部分已完成,接下来就是在屏幕上画图。
屏幕上画图
这部分内容在之前的学习中没有接触过,用到了一个对我来说是一个全新知识点的东西:GL。
GL在之前我是完全不了解的,而且我对OpenGL也不了解。因此关于GL的代码,我没有理解透彻,只能看着代码然后将自己的理解写成注释。跟着课程学的时候,这段的代码也是跟着课程一样,直接从API中复制粘贴过来的。
具体的东西,直接在代码里面写吧,这样也方便以后查看。尽管这不符合我心中对于代码注释的规范。
这部分代码改动过,具体原本的代码,请查看API:GL
//定义一个材质,这个材质是画出的线的材质。
static Material lineMaterial;
/// <summary>
/// 创建线的材质。
/// </summary>
static void CreateLineMaterial()
{
if (!lineMaterial)
{
// Unity has a built-in shader that is useful for drawing
// simple colored things.
Shader shader = Shader.Find("Hidden/Internal-Colored");
lineMaterial = new Material(shader);
#region 以下代码我看不懂
lineMaterial.hideFlags = HideFlags.HideAndDontSave;
// Turn on alpha blending
lineMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
lineMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
// Turn backface culling off
lineMaterial.SetInt("_Cull", (int)UnityEngine.Rendering.CullMode.Off);
// Turn off depth writes
lineMaterial.SetInt("_ZWrite", 0);
#endregion
}
}
// Will be called after all regular rendering is done
public void OnRenderObject()
{
CreateLineMaterial();
// Apply the line material
lineMaterial.SetPass(0);
//变成正交投影
GL.LoadOrtho();
GL.Begin(GL.LINES);
//换颜色
GL.Color(Color.red);
for (int i = 1; i < allPoints.Count; i++)
{
//创建两个临时变量,用于存放前一个点的位置和当下这一个点的位置。
Vector3 tmpFront = allPoints[i - 1];
Vector3 tmpBack = allPoints[i];
//将这两个点提交上去。这关于GL.Vertex()这个方法,我还没有理解。
GL.Vertex3(tmpFront.x, tmpFront.y, tmpFront.z);
GL.Vertex3(tmpBack.x, tmpBack.y, tmpBack.z);
}
GL.End();
}
这段代码,我基本属于不理解的状态。Unity的GL这一块儿同样暂且搁置。
将画出来的画符映射到一个画板上:
public void GenerateTexture()
{
paintTexture = new Texture2D(300, 400);
for (int i = 1; i < allPoints.Count; i++)
{
Vector3 tmpFront = allPoints[i - 1];
Vector3 tmpBack = allPoints[i];
//将图片的宽和高乘上一个比例,得到相对于图片(0,0)坐标的位置。
//上一个鼠标位置点的X,Y的值
float paintFrontX = paintTexture.width * tmpFront.x;
float paintFrontY = paintTexture.height * tmpFront.y;
//当前鼠标位置点的X,Y值
float paintBackX = paintTexture.width * tmpBack.x;
float paintBackY = paintTexture.height * tmpBack.y;
//用于插值运算
int tmpCount = 50;
for (int j = 0; j < tmpCount; j++)
{
//插值:a*t+1-tb
//参数:需要渐进变换的参数,峰顶值,变化速率
int tmpX = (int)Mathf.Lerp(paintFrontX, paintBackX, j / (float)tmpCount);
//像素点需要一个整型参数
int tmpY = (int)Mathf.Lerp(paintFrontY, paintBackY, j / (float)tmpCount);
//设置图片像素点的颜色。
paintTexture.SetPixel(tmpX, tmpY, Color.red);
}
}
//paintTexture.SetPixel(paintX, paintY, Color.red);
paintTexture.Apply();
//将物体材质更换为当前代码创建的这个纹理。
transform.GetComponent<Renderer>().material.mainTexture = paintTexture;
}
接收画符的纹理创建好之后,在Update里面引用它。当我们松开鼠标的时候,屏幕上的画符就消失,然后画符呈现在画板上。
private void Update()
{
if (Input.GetMouseButton(0))
{
//得到点
Vector3 tmpView = Camera.main.ScreenToViewportPoint(Input.mousePosition);
allPoints.Add(tmpView);
}
if (Input.GetMouseButtonUp(0))
{
GenerateTexture();
allPoints.Clear();
}
}
完整代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PointerUI : MonoBehaviour
{
List<Vector3> allPoints;
Texture2D paintTexture;
private void Start()
{
allPoints = new List<Vector3>();
}
private void Update()
{
if (Input.GetMouseButton(0))
{
//得到点
Vector3 tmpView = Camera.main.ScreenToViewportPoint(Input.mousePosition);
allPoints.Add(tmpView);
}
if (Input.GetMouseButtonUp(0))
{
GenerateTexture();
allPoints.Clear();
}
}
public void GenerateTexture()
{
paintTexture = new Texture2D(300, 400);
for (int i = 1; i < allPoints.Count; i++)
{
Vector3 tmpFront = allPoints[i - 1];
Vector3 tmpBack = allPoints[i];
//将图片的宽和高乘上一个比例,得到相对于图片(0,0)坐标的位置。
//上一个鼠标位置点的X,Y的值
float paintFrontX = paintTexture.width * tmpFront.x;
float paintFrontY = paintTexture.height * tmpFront.y;
//当前鼠标位置点的X,Y值
float paintBackX = paintTexture.width * tmpBack.x;
float paintBackY = paintTexture.height * tmpBack.y;
//用于插值运算
int tmpCount = 50;
for (int j = 0; j < tmpCount; j++)
{
//插值:a*t+1-tb
//参数:需要渐进变换的参数,峰顶值,变化速率
int tmpX = (int)Mathf.Lerp(paintFrontX, paintBackX, j / (float)tmpCount);
//像素点需要一个整型参数
int tmpY = (int)Mathf.Lerp(paintFrontY, paintBackY, j / (float)tmpCount);
//设置图片像素点的颜色。
paintTexture.SetPixel(tmpX, tmpY, Color.red);
}
}
//paintTexture.SetPixel(paintX, paintY, Color.red);
paintTexture.Apply();
//将物体材质更换为当前代码创建的这个纹理。
transform.GetComponent<Renderer>().material.mainTexture = paintTexture;
}
//定义一个材质,这个材质是画出的线的材质。
static Material lineMaterial;
/// <summary>
/// 创建线的材质。
/// </summary>
static void CreateLineMaterial()
{
if (!lineMaterial)
{
// Unity has a built-in shader that is useful for drawing
// simple colored things.
Shader shader = Shader.Find("Hidden/Internal-Colored");
lineMaterial = new Material(shader);
#region 以下代码我看不懂
lineMaterial.hideFlags = HideFlags.HideAndDontSave;
// Turn on alpha blending
lineMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
lineMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
// Turn backface culling off
lineMaterial.SetInt("_Cull", (int)UnityEngine.Rendering.CullMode.Off);
// Turn off depth writes
lineMaterial.SetInt("_ZWrite", 0);
#endregion
}
}
// Will be called after all regular rendering is done
public void OnRenderObject()
{
CreateLineMaterial();
// Apply the line material
lineMaterial.SetPass(0);
//变成正交投影
GL.LoadOrtho();
GL.Begin(GL.LINES);
//换颜色
GL.Color(Color.red);
for (int i = 1; i < allPoints.Count; i++)
{
//创建两个临时变量,用于存放前一个点的位置和当下这一个点的位置。
Vector3 tmpFront = allPoints[i - 1];
Vector3 tmpBack = allPoints[i];
//将这两个点提交上去。这关于GL.Vertex()这个方法,我还没有理解。
GL.Vertex3(tmpFront.x, tmpFront.y, tmpFront.z);
GL.Vertex3(tmpBack.x, tmpBack.y, tmpBack.z);
}
GL.End();
}
}
在Unity场景中创建一个Quad物体,将脚本挂载到它身上。然后运行。下面是效果:
这部分内容,学得不够透彻。理解得也不够深入。
关于unity中GL这部分,暂时不打算学。过段时间,可能是一两年,两三年,再去学习,学习GL,OpenGL,Shader,等到那时候再去整理思路。
目前我能保持每天花最多一小时学习Unity,这已经是最好的状态了,因为我有比这更重要的事情去做,而Unity被我当做了业余时间一件能放松的事情。毕竟经常看手机刷短视频也不好,浪费时间不说,更不能提升自己。这就作为一个爱好发展下去吧。希望这个爱好能坚持一个月。先一个月吧,我也不是个能有长久爱好的人。
至于昨天和前天为什么没有写日记,请你——未来的我——听我解(jie)释(kou)。
是这样的,前天学的东西,GL这块没跟上,又不想返回去再看一遍,网又差,API加载不出来,我就放弃了,所以前天没写。昨天为啥没写呢,因为昨天我又把前天学的课返了一遍,又学了昨天应该学的新课。学完之后,已经十一点多快十二点了,我就去睡觉了。
其实吧,就是懒,拖延症犯了。
请你放心,我……我不一定会改啊。太难了。比跑步还难。