准备材料:
一块平面白板
给白板新建一个白色的材质并替换掉默认材质
最后白板上应该是这样的
一支笔
给笔尖和笔尖稍前处各创建一个空物体点 ,过会会用到。
代码部分:
白板上的代码:
首先创建好存放白板材质和纹理的变量,另外我们需要一个整型二维向量来自定义白板的分辨率
、
public Material material;
public Texture2D texture;
public Vector2Int size;//具体数值在编辑器里面调整
为了存储白板上的笔迹变化,我们可以创建一个新的Texture2D变量来赋值给白板,白板上笔迹的变化就通过改变这个Texture2D的值来实现,于是我们写一个用于初始化(或重置)白板的Reset方法
public void Reset()
{
texture=new Texture2D(size.x,size.y);
material.mainTexture = texture;
}
在游戏开始时,对白板进行初始化
void Start()
{
material=GetComponent<Renderer>().material;
Reset();
}
笔上的代码:
在Windows平台上实现画画时,通常时鼠标所指位置即为笔的位置,当笔尖接近或触碰到画板并通过某种方式确认落笔时(本文章的例子为按住左键为落笔),画板上出现笔迹。
首先我们实现笔跟随鼠标移动
public GameObject Brush;//笔
public Camera Camera;//摄像机
Ray BrushPosRay;
RaycastHit BrushPosRayHit;
//创建一条由摄像机发出的射线,射线方向为鼠标所指方向
BrushPosRay=Camera.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast(BrushPosRay,out BrushPosRayHit))
{
Brush.transform.position = BrushPosRayHit.point;//笔的位置即为射线射到的点
}
然后实现画板能正常判断笔尖位置并在落笔时正确画出笔迹
笔迹的组成时一个Color数组,在落笔时把落笔点附近的像素替换成这个Color数组
Color[] brushColor;
public int size = 4;//笔迹大小
void Start()
{
brushColor =Enumerable.Repeat(Color.black,size*size).ToArray();//填充Color数组
}
先判断笔相对于画板的位置,这里通过从笔尖处发出射线,然后当射线命中画板时获取命中点坐标来实现
RaycastHit WrittingRayHit;
Ray WrittingRay;
public GameObject StartPos;//笔尖处的空物体
public GameObject EndPos;//笔尖稍前的空物体
WrittingRay.origin=Brush.transform.position;//从笔下方发射一条射线
WrittingRay.direction=Brush.transform.forward;
if (Physics.Linecast(StartPos.transform.position, EndPos.transform.position,out WrittingRayHit)&&Input.GetMouseButton(0))
{
if(WrittingRayHit.transform.TryGetComponent(out BoardScript board))
{
//笔尖射线落点转换到画板上
int x = (int)(WrittingRayHit.textureCoord.x * board.size.x - size / 2);
int y = (int)(WrittingRayHit.textureCoord.y * board.size.y - size / 2);
//当笔的位置在画板边缘时,防止超出数组错误的情况发生
if (x < size || x > board.size.x - size || y < size || y > board.size.y - size) return;
board.texture.SetPixels(x, y, size, size, brushColor);//把落笔点附近的像素替换成颜色数组
board.texture.apply();//保存更改
}
}
但是这样会有一些问题:当笔移动太快时笔迹会出现断连,我们通过获取两帧内落笔点位置并用一个Lerp循环来把两帧落笔点中间的空隙填上。
bool isCheck;//判断是否仍在落笔
Vector2 lastPos;//保存上一个落笔点位置
WrittingRay.origin=Brush.transform.position;//从笔下方发射一条射线
WrittingRay.direction=Brush.transform.forward;
if (Physics.Linecast(StartPos.transform.position, EndPos.transform.position,out WrittingRayHit)&&Input.GetMouseButton(0))
{
if(WrittingRayHit.transform.TryGetComponent(out BoardScript board))
{
int x = (int)(WrittingRayHit.textureCoord.x * board.size.x - size / 2);
int y = (int)(WrittingRayHit.textureCoord.y * board.size.y - size / 2);
if (x < size || x > board.size.x - size || y < size || y > board.size.y - size) return;
if (isCheck)
{
for(float f = 0.01f; f < 1; f += 0.03f)
{
int xLerp = (int)Mathf.Lerp(lastPos.x, x, f);
int yLerp = (int)Mathf.Lerp(lastPos.y, y, f);
board.texture.SetPixels(xLerp, yLerp, size, size, brushColor);
}
board.texture.Apply();
}
lastPos=new Vector2(x, y);
isCheck = true;
return;
}
}
//保持落笔状态时,持续保存上一个落笔点用于Lerp循环,抬手后再落笔才会重新开始保存落笔点继续循环
isCheck = false;
完整代码:
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class WrittingScript : MonoBehaviour
{
public GameObject Brush;
public Camera Camera;
RaycastHit BrushPosRayHit,WrittingRayHit;
Ray BrushPosRay,WrittingRay;
public GameObject StartPos;
public GameObject EndPos;
public int size = 4;
Color[] brushColor;
bool isCheck;
Vector2 lastPos;
// Start is called before the first frame update
void Start()
{
brushColor =Enumerable.Repeat(Color.black,size*size).ToArray();//初始化颜色
}
// Update is called once per frame
void Update()
{
BrushPosRay=Camera.ScreenPointToRay(Input.mousePosition);//创建一条射线
if(Physics.Raycast(BrushPosRay,out BrushPosRayHit))
{
Brush.transform.position = BrushPosRayHit.point;//笔位置跟鼠标同步
}
WrittingRay.origin=Brush.transform.position;//从笔下方发射一条射线
WrittingRay.direction=Brush.transform.forward;
if (Physics.Linecast(StartPos.transform.position, EndPos.transform.position,out WrittingRayHit)&&Input.GetMouseButton(0))
{
if(WrittingRayHit.transform.TryGetComponent(out BoardScript board))
{
int x = (int)(WrittingRayHit.textureCoord.x * board.size.x - size / 2);
int y = (int)(WrittingRayHit.textureCoord.y * board.size.y - size / 2);
if (x < size || x > board.size.x - size || y < size || y > board.size.y - size) return;
if (isCheck)
{
for(float f = 0.01f; f < 1; f += 0.03f)
{
int xLerp = (int)Mathf.Lerp(lastPos.x, x, f);
int yLerp = (int)Mathf.Lerp(lastPos.y, y, f);
board.texture.SetPixels(xLerp, yLerp, size, size, brushColor);
}
board.texture.Apply();
}
lastPos=new Vector2(x, y);
isCheck = true;
return;
}
}
isCheck = false;
}
}
其他:
如果是要在VR设备上直接用手柄射线来画出字迹,可以使用XRRayInteractor脚本
//以左手为例
XRRayInteractor leftInteractor;
RaycastHit rayInfo;
leftInteractor.GetCurrentRaycastHit(out rayInfo);
//后接获取到射线射中点之后处理
按理来说还可以通过在笔尖处放一个碰撞体和刚体,然后通过获取板和笔尖的碰撞点来获得笔在画板上的相对位置来实现画画,但实际尝试下来发现这种方法精度不高而且难以调试,不推荐使用