Unity实时绘制线条(附加物理效果)
最近参加了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.y - positions.y) / (positions.x - positions.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;
offsets = new Vector2(-deltaX, deltaY);
offsets = new Vector2(deltaX, -deltaY);
List<Vector2> colliderPoints = new List<Vector2> {
positions + offsets,
positions + offsets,
positions + offsets,
positions + offsets
};
return colliderPoints;
}
3. 擦除/切断线条
这个功能使用了非常原始的笨办法实现,但是实际玩下来感觉并没有产生太大的性能问题(其实已经需要限制线条长度了……)
写一个简单步骤吧:
[*]使用RaycastHit2D检测鼠标位置碰到的线条
[*]跟之前一样获得转换坐标,遍历线条的positions找到最接近的坐标点
[*]将positions根据这个点分成两半
[*]创建两条新线条,把positions存入新线条
[*]删除旧线条
至此我们获得了需要的大部分能力,剩下的功能跟画线的核心逻辑关系不大,就不再赘述了。
页:
[1]