Unity涂鸦【1】——基于Vectrosity的屏幕涂鸦和白板涂鸦的简单实现

〇、涂鸦与画线

  • Vectrosity的介绍
    The ultimate professional line-drawing utility, continuously supported since 2010! Find out why Unity users call Vectrosity “great,” “wonderful,” and why buying it “was the smartest move I’ve made for a long time.”

以上文字用人话来讲:
Vectrosity自2010年横空出世以来——牢记使命不忘初心,迄今已成为最为高端、大气、上档次的画线工具。

特点: Vectrosity是一个非开源的商业包。

本文讨论的内容:基于Vectrosity包进行画线涂鸦

一、简单涂鸦的演示

  • 屏幕涂鸦演示
    请添加图片描述
  • 黑板涂鸦演示
    请添加图片描述

二、用到的包

asset store中的Vectrosity包。

三、涂鸦的实现

1、屏幕涂鸦

  • 屏幕涂鸦的过程
1】按下鼠标左键开始单笔涂鸦,松开鼠标左键则停止本笔涂鸦。
 【2】再次按下则开始画另一笔涂鸦。
 【3】每一笔涂鸦信息用Vectrosity中的一个line对象来保存
 【4】涂鸦的时候,取点的位置是屏幕鼠标位置【2D取xy,3D自动取xy同时自动加一个固定的z】
 【5】用Vectrosity来显示所有的line对象
  • 屏幕涂鸦的代码实现
    代码来自官网DrawLinesMouse.cs,略有改动
// The DrawLinesTouch script adapted to work with mouse input, with the option for 3D or 2D lines
using UnityEngine;
using Vectrosity;
using System.Collections.Generic;

/// <summary>
/// 屏幕涂鸦:
/// 【1】按下鼠标左键开始单笔涂鸦,松开鼠标左键则停止本笔涂鸦。
/// 【2】再次按下则开始画另一笔涂鸦。
/// 【3】每一笔涂鸦信息用Vectrosity中的一个line对象来保存
/// 【4】涂鸦的时候,取点的位置是屏幕鼠标位置【2D取xy,3D自动取xy同时自动加一个固定的z】
/// 【5】用Vectrosity来显示所有的line对象
/// </summary>
public class DrawLinesMouseV2 : MonoBehaviour
{
    
    
    public Texture2D lineTex;
    public int maxPoints = 5000;
    public float lineWidth = 4.0f;
    public int minPixelMove = 5; // Must move at least this many pixels per sample for a new segment to be recorded
    public bool useEndCap = false;
    public Texture2D capLineTex;
    public Texture2D capTex;

    public float capLineWidth = 20.0f;

    // If line3D is true, the line is drawn in the scene rather than as an overlay. Note that in this demo, the line will look the same
    // in the game view either way, but you can see the difference in the scene view.
    public bool line3D = false;
    public float distanceFromCamera = 1.0f;

    /// <summary>
    /// line的对象
    /// </summary>
    private VectorLine line;

    /// <summary>
    /// 所有的线:有很多很多根线
    /// </summary>
    private List<VectorLine> lines = new List<VectorLine>();

    private Vector3 previousPosition;
    private int sqrMinPixelMove;
    private bool canDraw = false;

    /// <summary>
    /// line被创建了?,没被创建的话,按下鼠标左键则会创建一根线。
    /// </summary>
    private bool hasCreated;
    
    private float useLineWidth;
    private Texture2D tex;

    void Start()
    {
    
    
        if (useEndCap)
        {
    
    
            VectorLine.SetEndCap("RoundCap", EndCap.Mirror, capLineTex, capTex);
            tex = capLineTex;
            useLineWidth = capLineWidth;
        }
        else
        {
    
    
            tex = lineTex;
            useLineWidth = lineWidth;
        }

        // Used for .sqrMagnitude, which is faster than .magnitude
        sqrMinPixelMove = minPixelMove * minPixelMove;
    }

    void Update()
    {
    
    
        //获取鼠标位置
        var newPoint = GetMousePos();

        // 按下鼠标左键,则开始一笔新的涂鸦(对应为一根新的线)。Mouse button clicked, so start a new line
        if (Input.GetMouseButtonDown(0))
        {
    
    
            if (!hasCreated)
            {
    
    
                if (line3D)
                {
    
    
                    line = new VectorLine("DrawnLine3D", new List<Vector3>(), tex, useLineWidth, LineType.Continuous,
                        Joins.Weld);
                }
                else
                {
    
    
                    line = new VectorLine("DrawnLine", new List<Vector2>(), tex, useLineWidth, LineType.Continuous, Joins.Weld);
                }

                line.endPointsUpdate =
                    2; // Optimization for updating only the last couple points of the line, and the rest is not re-computed
                if (useEndCap)
                {
    
    
                    line.endCap = "RoundCap";
                }

                hasCreated = true;
            }

            if (line3D)
            {
    
    
                line.points3.Clear();
                line.Draw3D();
            }
            else
            {
    
    
                line.points2.Clear();
                //line.Draw();

                //显示多笔涂鸦信息
                if(! lines.Contains(line)) lines.Add(line);
                lines.ForEach(line=>line.Draw());
            }

            previousPosition = Input.mousePosition;
            if (line3D)
            {
    
    
                line.points3.Add(newPoint);
            }
            else
            {
    
    
                line.points2.Add(newPoint);
            }

            canDraw = true;
        }
        // Mouse button held down and mouse has moved far enough to make a new point
        //鼠标左键一直按着,并且鼠标在移动,新点与上一个点距离达到一定程度时,该点才能被取样
        else if (Input.GetMouseButton(0) && (Input.mousePosition - previousPosition).sqrMagnitude > sqrMinPixelMove &&
                 canDraw)
        {
    
    
            previousPosition = Input.mousePosition;
            int pointCount;
            if (line3D)
            {
    
    
                line.points3.Add(newPoint);
                pointCount = line.points3.Count;
                line.Draw3D();
            }
            else
            {
    
    
                line.points2.Add(newPoint);
                pointCount = line.points2.Count;
                line.Draw();
                if (pointCount >= maxPoints)
                {
    
    
                    canDraw = false;
                }
            }
        }

        //松开鼠标左键,本次画线结束,hasCreated置空,后面再次按下的时候,可以创建新的线
        if (Input.GetMouseButtonUp(0))
        {
    
    
            hasCreated = false;
        }

        Vector3 GetMousePos()
        {
    
    
            var p = Input.mousePosition;
            if (line3D)
            {
    
    
                p.z = distanceFromCamera;
                return Camera.main.ScreenToWorldPoint(p);
            }

            return p;
        }
    }
}

2、白板涂鸦的实现

  • 白板涂鸦的实现原理
 /***********************************************************
  *实现原理:
  * 【0】使用的包asset store 中的Vectrosity
  * 【1】从摄像机发射一条射线,方向指向屏幕的鼠标位置
  * 【2】白板Plane上设置一个BoxCollider,把BoxCollider扩大一点,
  * 特别是靠近相机的一边,要覆盖白板,让字显示在白板前面,而不是白板上【不然模型重叠会闪烁】
  * 【3】每帧刷新射线,捕捉在白板上的碰撞点
  * 【4】鼠标按下时开始涂鸦,弹起时完成一笔涂鸦,调用Vectrosity把该笔涂鸦的点显示出来
  * 【5】调用Vectrosity显示每一笔涂鸦的点
  ***********************************************************/
  • 白板涂鸦的代码脚本
using UnityEngine;
using Vectrosity; //asset store search [Vectrosity]
using System.Collections.Generic;

/// <summary>
/// 白板涂鸦
/// 白板——就是一个3D Plane,增加一个BOX碰撞体包围框
/// </summary>
public class DrawOnWhiteBoard : MonoBehaviour
{
    
    
    /***********************************************************
     *实现原理:
     * 【0】使用的包asset store 中的Vectrosity
     * 【1】从摄像机发射一条射线,方向指向屏幕的鼠标位置
     * 【2】白板Plane上设置一个BoxCollider,把BoxCollider扩大一点,
     * 特别是靠近相机的一边,要覆盖白板,让字显示在白板前面,而不是白板上【不然模型重叠会闪烁】
     * 【3】每帧刷新射线,捕捉在白板上的碰撞点
     * 【4】鼠标按下时开始涂鸦,弹起时完成一笔涂鸦,调用Vectrosity把该笔涂鸦的点显示出来
     * 【5】调用Vectrosity显示每一笔涂鸦的点
     ***********************************************************/

    /// <summary>
    /// 主相机
    /// </summary>
    public Camera camera;

    /// <summary>
    /// 白板的名字
    /// </summary>
    public string whiteBoardName;

    public Texture2D lineTex;
    public int maxPoints = 5000;
    public float lineWidth = 4.0f;
    public int minPixelMove = 5; // Must move at least this many pixels per sample for a new segment to be recorded
    public bool useEndCap = false;
    public Texture2D capLineTex;
    public Texture2D capTex;

    public float capLineWidth = 20.0f;

    // If line3D is true, the line is drawn in the scene rather than as an overlay. Note that in this demo, the line will look the same
    // in the game view either way, but you can see the difference in the scene view.
    public bool line3D = false;
    public float distanceFromCamera = 1.0f;

    /// <summary>
    /// line的对象:单笔涂鸦的信息
    /// </summary>
    private VectorLine line;

    /// <summary>
    /// 所有的线:很多根线,多笔涂鸦信息
    /// </summary>
    private List<VectorLine> lines = new List<VectorLine>();

    private Vector3 previousPosition;
    private int sqrMinPixelMove;
    private bool canDraw = false;

    /// <summary>
    /// line被创建了?,没被创建的话,按下鼠标左键则会创建一根线。
    /// </summary>
    private bool hasCreated;

    private float useLineWidth;
    private Texture2D tex;

    // Start is called before the first frame update
    void Start()
    {
    
    
        if (useEndCap)
        {
    
    
            VectorLine.SetEndCap("RoundCap", EndCap.Mirror, capLineTex, capTex);
            tex = capLineTex;
            useLineWidth = capLineWidth;
        }
        else
        {
    
    
            tex = lineTex;
            useLineWidth = lineWidth;
        }

        // Used for .sqrMagnitude, which is faster than .magnitude
        sqrMinPixelMove = minPixelMove * minPixelMove;
    }

    // Update is called once per frame
    void Update()
    {
    
    
        Vector3 newPoint = new Vector3();
        bool canWrite = false;  //鼠标按下在白板上涂鸦时,为true

        //相机激发射线穿过屏幕鼠标
        RaycastHit hit;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        if (Physics.Raycast(ray, out hit, Mathf.Infinity))
        {
    
    
            //鼠标【碰到】白板
            if (hit.transform.name == whiteBoardName)
            {
    
    
                newPoint = hit.point;
                canWrite = true;
            }
        }

        //
        if (canWrite)
        {
    
    
            // 按下鼠标左键,则开始一根新的线。
            if (Input.GetMouseButtonDown(0))
            {
    
    
                if (!hasCreated) //本笔涂鸦的线存在,则不创建,不然要创建新的line
                {
    
    
                    if (line3D)
                    {
    
    
                        line = new VectorLine("DrawnLine3D", new List<Vector3>(), tex, useLineWidth, LineType.Continuous,
                            Joins.Weld);
                    }
                    else
                    {
    
    
                        line = new VectorLine("DrawnLine", new List<Vector2>(), tex, useLineWidth, LineType.Continuous, Joins.Weld);
                    }

                    line.endPointsUpdate =
                        2; // Optimization for updating only the last couple points of the line, and the rest is not re-computed
                    if (useEndCap)
                    {
    
    
                        line.endCap = "RoundCap";
                    }

                    hasCreated = true;
                }

                if (line3D)
                {
    
    
                    line.points3.Clear();

                    if (!lines.Contains(line)) lines.Add(line);
                    lines.ForEach(line => line.Draw3D());
                }
                else
                {
    
    
                    line.points2.Clear();

                    if (!lines.Contains(line)) lines.Add(line);
                    lines.ForEach(line => line.Draw());
                }

                previousPosition = Input.mousePosition;
                if (line3D)
                {
    
    
                    line.points3.Add(newPoint);
                }
                else
                {
    
    
                    line.points2.Add(newPoint);
                }

                canDraw = true;
            }
            // Mouse button held down and mouse has moved far enough to make a new point
            //鼠标左键一直按着,并且鼠标在移动,新点与上一个点距离达到一定程度时,该点才能被取样
            else if (Input.GetMouseButton(0) && (Input.mousePosition - previousPosition).sqrMagnitude > sqrMinPixelMove &&
                     canDraw)
            {
    
    
                previousPosition = Input.mousePosition;
                int pointCount;
                if (line3D)
                {
    
    
                    line.points3.Add(newPoint);
                    pointCount = line.points3.Count;
                    line.Draw3D();
                }
                else
                {
    
    
                    line.points2.Add(newPoint);
                    pointCount = line.points2.Count;
                    line.Draw();
                    if (pointCount >= maxPoints)
                    {
    
    
                        canDraw = false;
                    }
                }
            }

            //松开鼠标左键,本笔涂鸦的画线结束,hasCreated置空,后面再次按下的时候,可以创建新一笔的涂鸦
            if (Input.GetMouseButtonUp(0))
            {
    
    
                hasCreated = false;
            }
        }
        Vector3 GetMousePos()
        {
    
    
            var p = Input.mousePosition;
            if (line3D)
            {
    
    
                p.z = distanceFromCamera;
                return Camera.main.ScreenToWorldPoint(p);
            }

            return p;
        }
    }
}

五、其它

实际上,要能够愉快的涂鸦,还得增加许多功能,比如全部清屏,部分擦除,颜色设置等等。

如果单纯只是涂鸦的话,用texture的setpixels可能更“勤俭节约”。

备注:本案例只在Win10 + Unity Editor 环境中测试过

猜你喜欢

转载自blog.csdn.net/dzj2021/article/details/129400010