cocoscreator的沿着刚体边缘画线的实现好难啊

先给个效果图

基本原理是使用了物理系统中的射线检测
划线的时候,延长线段,然后做检测,如果没有刚体相交,那就正常走,如果有刚体相交


如图,B是之前的点,A是鼠标移动到的新点,使用射线检测,在c点和刚体相交
考虑线有宽度,故需要计算出D点

之后,鼠标已经在刚体内部,移动,如图


此时做射线检测,可以得到法线向量frag,将其旋转90°(无所谓哪个方向)的frag2,设定frag2以B为起点,计算向量BA在frag2上的投影,计算出D点
这样就可以让线条沿着刚体边走了。

以上是基本想法,实际实现还有很多东西。
另外就gif可知道,在一些角度上可能会卡

前面先写下最开始决定做这功能的初始想法,后面写一些实现,不定时更新。

然而这样的实现做起来效果不是很佳,没有一些其他游戏里顺畅(随你怎么乱画,都不担心卡,甚至还能智能的给你拐边角)

本想说实际上线版本手感(gif里不是),可以去微信体验,但因为坑爹的7.0.3。。。重写了的新版本,还没有提交,因有其他事,有的忙。

5赞

更新:

画线前的准备工作
1.设定线宽多少,我在1280*720场景里设定线宽为10
2.判断触摸点是否在场景预制刚体内或相交(线有宽度),因为如果是从刚体上起画,则不可以画线。
首先把触摸点的坐标,切换为带刚体的节点的坐标系内坐标,然后获取节点的碰撞框
如果是圆形碰撞框,则只需要判断圆心和点坐标距离是否为碰撞框半径r + 线宽的一半
如果是box碰撞框,给点构造个长宽为线宽的rect,使用cc.rect.intersects(rect)判断
如果是多边形,则参考https://blog.csdn.net/kaitiren/article/details/11913453
不过原文是计算点是否在多边形内部,而我这里是画线,线有宽度,故做改动,改为先判断点离多边形的边最短距离是否低于线宽,如果低于,就是在刚体上,否则,继续原判断

/**
* 判断一个点是否在多边形内部的方法
* points 多边形顶点数组 这个是排好顺序了的,只要依次连接就是该多边形的边(在设计碰撞框的时候就已经排好序)
* p 要判断的点的坐标
* 返回1代表在多边形内部
*
* 基本步骤:
* 1,过p点垂直向上作一条射线
* 2,判断此射线与n边形n条边的交点
* 3,把所有交点相加,如果是奇数则说明在多边形内,否则在多边形外
* 思路非常的简单,另外说明一下几种特殊的情况:
* 1,射线与多边形的顶点相交;比如射线过多边形的Pi点,则如果Pi-1和Pi+1在此射线的异侧,此交点可以算一个,如果此两点在射线的同侧,则此交点不计。此结论非常简单,画个图应该就能明白了
* 2,p点在多边形的某一条边上;也认为p在多边形中
* 3,p不在多边形的边上,但p的射线与多边形的某一条边重合;比如与Pi,Pi+1线段重合,则如果Pi-1和Pi+2在射线的两侧,此情况也算一个交点,否则此情况不计交点。跟一种的情况类似,画个图应该明白了!
*
* 来自 https://blog.csdn.net/kaitiren/article/details/11913453
*/

    public static dotIsInPolygon(points: cc.Vec2[], p: cc.Vec2, lineWidth:number):number {
        let counter = 0;
        let i = 0;
        let xinters = 0.0;
        let p1 = points[0];
        let p2 = null;
        let len = points.length;
        for (i = 1; i <= points.length; i++) {
            if (i == len) {
                p2 = points[0];//这是循环到数组最后一个顶点的时候,这个最后一个顶点连第一个顶点就是多边形最后一条边
            } else {
                p2 = points[i];
            }
            //在这直接判断p是否在线段p1->p2上 这样规避了情况1和2
            if (this.dotLeveaveLine(p1, p2, p) < lineWidth / 2 + 0.1) {
                //cc.log("check p leave line less than " + lineWidth / 2);
                return 1;
            }
            //判断情况3
            if (p1.y == p2.y && p.y == p1.y && p.x <= Math.max(p1.x, p2.x)){
                //此时判断p0->p1 p2->p3是否在p1-p2的两侧,也即是 p0的y和p3的y轴坐标,分别在p1-p2的2边,即一个小于p.y,一个大于p.y                
                //p0 he p3
                let p0Idx = i - 2;
                p0Idx = p0Idx < 0 ? len + p0Idx : p0Idx;
                while (points[p0Idx].y == p.y) {
                    p0Idx -= 1;
                    p0Idx = p0Idx < 0 ? len + p0Idx : p0Idx;
                }
                let p3Idx = i + 1 >= len ? i + 1 - len : i + 1;
                while (points[p3Idx].y == p.y) {
                    p3Idx += 1;
                    p3Idx = p0Idx >= len ? p3Idx - len : p3Idx;
                }                
                if ((points[p0Idx].y < p.y && points[p3Idx].y > p.y) || (points[p3Idx].y < p.y && points[p0Idx].y > p.y)) {
                    counter++;
                } else {
                    counter += 2;
                }
                //如果出现满足这个if的线段,那么显然不会不会满足下一个if,不必浪费时间判断
                continue;
            }
            //如下是水平向右边计算交点
            if (p.y > Math.min(p1.y, p2.y)) {
                if (p.y < Math.max(p1.y, p2.y)) {                        
                    if (p.x <= Math.max(p1.x, p2.x)) {
                        if (p1.y != p2.y) {
                            xinters = (p.y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) + p1.x;
                            if (p1.x == p2.x || p.x <= xinters) {
                                counter++;
                            }
                        }
                    }    
                }
            }
            p1 = p2;
        }    
        if (counter % 2 == 0) {
            return 0;
        } else {
            return 1;
        }
    }

判断点pos是否在刚体B内部:
let point = b.convertToNodeSpaceAR(pos);
let colliders = b.getComponents(cc.PhysicsPolygonCollider); if (colliders != null && colliders.length > 0) { for(let i = 0; i < colliders.length; i++) { if(VerticalUtil.dotIsInPolygon(colliders[i].points, point.sub(colliders[i].offset), lineW) == 1) { return b; } } }

其他的box类型,circle类型就简单了。

chain的话感觉像个单像素的绳子。。。游戏中不使用。

然后还有一些辅助,都是数学问题,可以网上搜,cc应该也有自带(不过庆幸手写了,使得从193版本用208重写时省了点事):
1.计算投影长度
/**
* 计算向量v1在另一个向量v2上的投影(长度)
*/
public static vectorShadow (v1:cc.Vec2, v2:cc.Vec2): number {
return (v1.x * v2.x + v1.y * v2.y) / Math.sqrt(Math.pow(v2.x, 2) + Math.pow(v2.y, 2));
}
2.计算向量之间夹角

    /**
     * 计算向量夹角的cos值,为-负数表示钝角,为正数表示锐角
     * 
     */
    public static chekDirection(p1: cc.Vec2, p2: cc.Vec2, len1: number, b1: cc.Vec2, b2: cc.Vec2, len2: number):number {
        if(VerticalUtil.checkEqual(p1, p2) || VerticalUtil.checkEqual(b1, b2)) {
            return -1;
        }
        let v1 = {x: (p2.x - p1.x) , y: (p2.y - p1.y)};//线段p1->p2向量
        let v2 = {x: (b2.x - b1.x), y: (b2.y - b1.y)};//线段b1->b2向量
        //let v3 = {x: v1.x + v2.x, y:v1.y + v2.y};//v3 = v2 + v1
        let cosval = (v1.x * v2.x + v1.y * v2.y) / (len1 * len2);

        return cosval;
        //0° 1 60° 0.5 75° 0.2588  90° 可算出为0 
        //100° -0.173  120°为-0.5 140°为0.766 150°为-0.866 
        //160°为-0.9397 165°为-0.9659 170°为-0.9848 172 -0.99 175为-0.9962 178为-0.9993 179为-0.99985
        //大于等于90°当作同方向,否则当作反方向
    }

    public static chekDirection2(p1: cc.Vec2, p2: cc.Vec2, b1: cc.Vec2, b2: cc.Vec2):number {
        return VerticalUtil.chekDirection(p1, p2, p1.sub(p2).mag(), b1, b2, b1.sub(b2).mag());
    }

计算点离线段最近距离(线段,不是直线,注意这点)
/**
* 计算点离线段的最短距离的平方
* p1 p2 线段的两端端点
* p 点
/
public static dotLeveaveLineSQ (p1: cc.Vec2, p2: cc.Vec2, p: cc.Vec2):number {
let len = 0;
let cross = (p2.x - p1.x) * (p.x - p1.x) + (p2.y - p1.y) * (p.y - p1.y);
if (cross <= 0) {
len = (p.x - p1.x) * (p.x - p1.x) + (p.y - p1.y) * (p.y - p1.y);
} else {
let d = (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y);
if (cross >= d) {
len = (p2.x - p.x) * (p2.x - p.x) + (p2.y - p.y) * (p2.y - p.y);
} else {
let px = p1.x + (p2.x - p1.x) * cross / d;
let py = p1.y + (p2.y - p1.y) * cross / d;
len = (p.x - px) * (p.x - px) + (p.y - py) * (p.y - py);
}
}
return len;
}
/
*
* 计算点离线段的最短距离
* p1 p2 线段的两端端点
* p 点
*/
public static dotLeveaveLine (p1: cc.Vec2, p2: cc.Vec2, p: cc.Vec2):number {
//首先p的x和y要在p1 p2的范围之内
let len = VerticalUtil.dotLeveaveLineSQ(p1, p2, p);
return Math.sqrt(len);
}

1赞

顶个贴!!!

实现沿边画线的基础支持是物理引擎的射线检测,我目前的实现是以此为基础。
如果无这个则大概很难了

这个方法写的有点蠢。。。
/**
* 给点线段p1->p2,判断这个线段是否和已有刚体交叉,
* 如果无,返回空,否则返回和刚体的交叉点以及法线向量,
* 返回的交叉点为离p1最近的点
*
*/

findIntersectionWithRigidbody (p1: cc.Vec2, p2: cc.Vec2): [cc.Vec2, cc.Vec2, number, cc.PhysicsCollider, number] {
    try {
        let results = cc.director.getPhysicsManager().rayCast(p1, p2, cc.RayCastType.Closest);
        if(results.length > 0) {
            //cc.log("raycast type closest,len:" + results.length);
            //寻找和s1最接近的点
            //cc.log(["ray cast on", p1, p2, results]);
            return [results[0].point, results[0].normal, results[0].fraction, results[0].collider, 0];
        }
    } catch (error) {
        cc.log([p1, p2, "raycasterr1", error])
    }
    return null;
}

顶顶顶

mark

mark

mark 刚体边缘画线