拯救狗狗系列之物理画线

这几年出了一系列益智类物理画线玩法的爆款产品,比如拯救狗狗,救救小鸡等,本文基于Cocos Creator 3.8.1来实现物理画线。

原理

  • 通过Graphics绘画组件画线
  • 给绘制的线添加RigidBody2D组件,用于物理计算
  • 将绘制的线分成多个线段,每个线段添加一个PolygonCollider2D组件,用于碰撞检测

每个线段包含四个点p1, p2, p3, p4,通过start点、end点和线的宽度lineWidth算出p1, p2, p3, p4的坐标,这个四个点就是每个PolygonCollider2D的多边形顶点数组points

求四个点的坐标

  • 求出start点到end点的方向向量: d = (end - start).normalize();
  • 求出垂直于方向向量d,并且指向p1 - p2线段的方向向量d1
  • 求出垂直于方向向量d,并且指向p3 - p4线段的方向向量d2

d1,d2可以通过2D的旋转矩阵 乘以 方向向量d得出。

2D中的旋转的矩阵是:

将旋转矩阵 R 与向量 d 相乘来得到旋转后的向量 ′d′:

进行矩阵乘法运算,我们得到:

d1d的夹角是-90度,因此 d1 = (dy, -dx)

d2d的夹角是90度,因此 d2 = (-dy, dx)

求出d1d2方向向量后,可以得出4点的坐标

js 复制代码
 halfWidth = lineWidth / 2;
 p1 = start + d1 * halfWidth;
 p2 = end + d1 * halfWidth;
 p3 = end + d2 * halfWidth;
 p4 = start + d2 * halfWidth;

执行

1, 在Cocos Creator创建一个空的2D项目

2,创建一个场景,将角色(这里不是狗头哦, 没有狗头资源)添加到场景中,给角色添加上RigidBody2DBoxCollider2D组件,并设置碰撞范围,将RigidBody2D的类型设置成Dynamic,这样就可以收到重力的影响而下落

3,创建Game.ts,代码如下

Typescript 复制代码
import { _decorator, Component, EventTouch, find, Node, macro, Graphics, v2, Vec2, UITransform, v3, Color, RigidBody2D, PolygonCollider2D, PhysicsSystem2D } from 'cc';
const { ccclass, property } = _decorator;

const __tempV2 = v2()
const __tempV3 = v3()

@ccclass('Game')
export class Game extends Component {

    /** 角色 */
    @property(Node)
    role: Node = null;

    /** 结算节点 */
    @property(Node)
    settle: Node = null;

    private _curGraphics: Graphics;

    start() {
        // 禁用多点触摸
        macro.ENABLE_MULTI_TOUCH = false;
        this.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this)
        this.node.on(Node.EventType.TOUCH_MOVE, this.onTouchMove, this)
        this.node.on(Node.EventType.TOUCH_END, this.onTouchEnd, this)
        this.node.on(Node.EventType.TOUCH_CANCEL, this.onTouchEnd, this)

        // 先禁用角色的物理组件
        this.role.getComponent(RigidBody2D).enabled = false;
    }

    /** 获取目标节点的相对位置 */
    private getRelativePos(pos: Vec2) {
        __tempV3.set(pos.x, pos.y, 0)
        this._curGraphics.node.getComponent(UITransform).convertToNodeSpaceAR(__tempV3, __tempV3)
        pos.set(__tempV3.x, __tempV3.y)
        return pos;
    }
    
    private onTouchStart(event: EventTouch) {
        event.getUILocation(__tempV2)
        // 重置花线节点列表
        this._pathPointList.length = 0;

        // 创建Graphics节点
        const graphicsNode = new Node()
        graphicsNode.layer = this.node.layer;
        this.node.addChild(graphicsNode);

        // 添加Graphics绘图组件
        this._curGraphics = graphicsNode.addComponent(Graphics)
        this._curGraphics.strokeColor = Color.BLACK;
        this._curGraphics.lineWidth = 10;   

        // 设置路径起点
        const { x, y } = this.getRelativePos(__tempV2)
        this._curGraphics.moveTo(x, y)

        // 添加到路径点列表
        this._pathPointList.push(v2( x, y ))
    }

    /** 上一个点的斜率 */
    private _preSlope: number = 0
    /** 路径点列表 */
    private _pathPointList: Vec2[] = []

    private onTouchMove(event: EventTouch) {
        event.getUILocation(__tempV2)

        // 获取当前触摸点的相对位置
        const { x, y } = this.getRelativePos(__tempV2)
        // 获取上一个点的位置
        const { x: preX, y: preY } = this._pathPointList[this._pathPointList.length - 1];
        // 计算两点之间的距离
        const diffX = x - preX;
        const diffY = y - preY;
        // 两点之间的距离的平方
        const dis = diffX * diffX + diffY * diffY;
        const lineWidth = this._curGraphics.lineWidth;
        // 两点之间的距离大于线宽的平方,才添加到路径点列表
        if (dis >= lineWidth * lineWidth) {
            const d = 0.001
            const curSlope = Math.abs(diffX) < d ? (Number.MAX_SAFE_INTEGER * Math.sign(diffX) * Math.sign(diffY)) : (diffY / diffX)
            if (this._pathPointList.length > 1) {
                const diffK = curSlope - this._preSlope;
                // 斜率相同去掉前一个点
                if (Math.abs(diffK) < d) {
                    this._pathPointList.pop()
                }
            }
            // 添加到路径点列表
            this._pathPointList.push(v2( x, y ))
            // 绘制路径
            this._curGraphics.lineTo(x, y)
            this._curGraphics.stroke();

            // 保存上一个点的斜率
            this._preSlope = curSlope;
        }
    }

    private onTouchEnd(evt: EventTouch) {
        // 绘制结束,添加刚体组件
        // 两个点以上才添加刚体组件
        if (this._pathPointList.length > 1) {
            this._curGraphics.addComponent(RigidBody2D);
            // 添加多边形碰撞组件
            for (let index = 0; index < this._pathPointList.length - 1; index++) {
                const start = this._pathPointList[index];
                const end = this._pathPointList[index + 1];
                const polyCollider = this._curGraphics.addComponent(PolygonCollider2D);
                // 计算两点的方向向量
                const directVector = v2(end.x - start.x, end.y - start.y).normalize();
                const widhtHalf = this._curGraphics.lineWidth / 2;
                // 计算多边形的四个点
                const p1 = v2(directVector.y, -directVector.x).multiplyScalar(widhtHalf).add2f(start.x, start.y)
                const p2 = v2(-directVector.y, directVector.x).multiplyScalar(widhtHalf).add2f(start.x, start.y)
                const p3 = v2(directVector.y, -directVector.x).multiplyScalar(widhtHalf).add2f(end.x, end.y)
                const p4 = v2(-directVector.y, directVector.x).multiplyScalar(widhtHalf).add2f(end.x, end.y)
                // 设置多边形碰撞组件的定点列表
                polyCollider.points = [p1, p2, p4, p3];
                // 让修改生效
                polyCollider.apply();
            }
        } else {
            this._curGraphics.node.destroy();
        }
        // 重置
        this._curGraphics = null;
        // 启用角色的物理组件
        this.role.getComponent(RigidBody2D).enabled = true;
    }

    protected update(dt: number): void {
        // role的位置掉到屏幕外,显示结算节点
        if (this.role.position.y < -this.node.getComponent(UITransform).height / 2) {
            this.settle.active = true;
        }
    }
}

直接将Game.ts挂在Canvas节点上,预览游戏:

方块间画一个夹角线段:

角色上方添加一个方块,给方块添加RigidBody2DBoxCollider2D组件:

画一个不规则图形:

底部添加长方块:

相关推荐
布鲁克零三四四22 天前
Cocos Creator导出obj文件用于后端寻路
cocos creator
烧仙草奶茶1 个月前
【cocos creator】输入框滑动条联动小组建
cocos creator·cocos-creator
烧仙草奶茶3 个月前
【cocos creator】2.x里,使用3D射线碰撞检测
3d·cocos creator·cocos-creator·2.x
仅此而已7293 个月前
Cocos Creator倒计时
游戏引擎·cocos creator
仅此而已7293 个月前
cocosUI多分辨率适配
游戏引擎·cocos creator·多分辩率适配
SilenceJude4 个月前
cocos creator 3学习记录01——如何替换图片
前端·cocos creator
GrimRaider4 个月前
[Cocos Creator] v3.8开发知识点记录(持续更新)
开发·cocos creator
S_clifftop5 个月前
cocos creator如何使用cryptojs加解密(及引入方法)
cocos creator·加密解密·cryptojs
平淡风云5 个月前
cocosCreator获取手机剪切板内容
java·智能手机·typescript·android studio·cocos creator
zhenmu5 个月前
【cocos creator 3.x】 修改builtin-unlit 加了一个类似流光显示的mask参数
cocos creator·shader·effect