找回密码
 立即注册
查看: 401|回复: 0

Unity实时绘制线条(附加物理效果)

[复制链接]
发表于 2021-12-29 13:35 | 显示全部楼层 |阅读模式
最近参加了taptap 5th独立营比赛,团队想法是在传统横板平台游戏的基础上,加上绘制物理线条的操作,实现绘制、擦除、填色等功能,进而提供铺设道路、触碰开关、困住移动物体、改变线条样式、受重力浮动/下落等玩法。

0. 思路
一开始想到的有两种实现思路:

  • 绘制线条对象:容易实现,容易交互,但是线条精度低,难以精确擦除。
  • 绘制像素图像:接近传统绘图软件的效果,但是难于实现各种交互功能。
最后考虑玩法侧重(以及自己很菜),还是选择了绘制线条对象,具体使用的是unity自带的LineRenderer


  • 绘制线条
要绘制线条首先要确立坐标,需要将鼠标的屏幕空间转换为摄像机的世界空间坐标,如果是正交投影Orthographic只需要这样就足够了,但是如果想要做立体场景,例如2d项目实现前景后景分层,使用透视投影Perspective则需要额外加上z轴位置的转换。
    /// <summary>
    /// 使用3d视角镜头时,需要指定z值为 画布所处平面 与 摄像机 之间的偏移距离
    /// </summary>
    private Vector3 getMouseWorldPosition()
    {
        var mp = Input.mousePosition;
        var i = new Vector3(mp.x, mp.y, gd.zLevel - Camera.main.transform.position.z);
        return Camera.main.ScreenToWorldPoint(i);
    }
LineRenderer(后面简称LR)是通过一系列坐标点来绘制线条的,在update中连续使用LR的SetPosition记录鼠标坐标转换的世界坐标即可。(注意要同步增加positionCount)
LR的useWorldSpace选项需要关闭(设为false),否则线条会永远固定在绘制的位置,不能实现移动线条的功能。
至此应该就能愉快地在屏幕上绘制线条了(如果看不见线条请检查镜头距离、线条粗细、材质使用2d还是2dlit等问题)

2. 添加物理效果
为了避免在绘制的过程中线条就移动偏移,影响操作和记录数据,我选择在绘制结束抬笔(放开鼠标按键)的时候再添加Rigidbody2D到线条对象上。

  • 使用RigidbodyType2D.Dynamic实现可自由受力移动的线
  • 使用RigidbodyType2D.Kinematic实现固定不动但是可以检测碰撞的线
  • (如果使用RigidbodyType2D.Static则不会有任何交互)
至此绘制出的线条在提笔时开始下落了,然而线条还是幽灵会穿透一切,为此我们需要再添加上碰撞体积,这里使用PolygonCollider2D(后面简称PC)获得精准匹配的碰撞体积。
PC一般会自动生成匹配物体形状的碰撞体积,但是好像只对SpriteRenderer的sprite形状奏效,我们这里使用的LR则需要手动产生匹配的形状。
和绘制线条的过程类似,我们需要在PC中使用SetPath方法指定一组坐标来包围线条,使用之前绘制线条的LR positions坐标,这个坐标是在线条的中心,需要加上一个偏移量,每个坐标产生4个偏移点,形成一个包围盒,整体就近似贴合曲线形状了。


计算偏移量的方法是从网上找的一个实现,这里宽度直接使用了startWidth,如果LR配置过宽度曲线,可能无法完美匹配形状,不过在要求不高的场景已经足够准确了。
    private List<Vector2> CalculateColliderPoints(List<Vector2> positions)
    {
        var lr = GetComponent<LineRenderer>();
        //Get The Width of the Line
        float width = lr.startWidth;

        // m = (y2 - y1) / (x2 - x1)
        float m = (positions[1].y - positions[0].y) / (positions[1].x - positions[0].x);
        float deltaX = (width / 2f) * (m / Mathf.Pow(m * m + 1, 0.5f));
        float deltaY = (width / 2f) * (1 / Mathf.Pow(1 + m * m, 0.5f));

        //Calculate Vertex Offset from Line Point
        Vector2[] offsets = new Vector2[2];
        offsets[0] = new Vector2(-deltaX, deltaY);
        offsets[1] = new Vector2(deltaX, -deltaY);

        List<Vector2> colliderPoints = new List<Vector2> {
            positions[0] + offsets[0],
            positions[1] + offsets[0],
            positions[1] + offsets[1],
            positions[0] + offsets[1]
        };

        return colliderPoints;
    }

3. 擦除/切断线条
这个功能使用了非常原始的笨办法实现,但是实际玩下来感觉并没有产生太大的性能问题(其实已经需要限制线条长度了……)
写一个简单步骤吧:

  • 使用RaycastHit2D检测鼠标位置碰到的线条
  • 跟之前一样获得转换坐标,遍历线条的positions找到最接近的坐标点
  • 将positions根据这个点分成两半
  • 创建两条新线条,把positions存入新线条
  • 删除旧线条

至此我们获得了需要的大部分能力,剩下的功能跟画线的核心逻辑关系不大,就不再赘述了。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-22 23:22 , Processed in 0.079562 second(s), 23 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表