You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Phaser游戏中如何检测鼠标绘制线条是否穿过曲线两侧?

Level 4 切割检测实现方案

核心思路

将红色宽曲线拆解为两条平行的折线(对应曲线的左右两侧),检测用户绘制的线段是否与这两条折线同时相交,满足条件则判定切割成功。

具体实现步骤

1. 保存关键数据与生成两侧曲线

在场景的create方法中,将原曲线转为类属性,并生成曲线两侧的偏移点集合(偏移距离为红色曲线宽度的一半,即20):

create(){
    const graphics = this.add.graphics();

    // 将path设为类属性,方便后续调用
    this.path = new Phaser.Curves.Path(0, 300);

    for (let i = 0; i < 8; i++)
    {
        if (i % 2 === 0)
        {
            this.path.ellipseTo(50, 80, 180, 360, true, 0);
        }
        else
        {
            this.path.ellipseTo(50, 80, 180, 360, false, 0);
        }
    }

    graphics.lineStyle(40, 0xff0000, 1);
    this.path.draw(graphics);

    // 生成红色曲线的两侧偏移点
    const pathPoints = this.path.getPoints(200); // 密集采样点保证检测精度
    this.leftCurvePoints = [];
    this.rightCurvePoints = [];
    const offsetDistance = 20; // 线宽40的一半

    for (let i = 0; i < pathPoints.length - 1; i++) {
        const p1 = pathPoints[i];
        const p2 = pathPoints[i + 1];
        // 计算线段垂直法向量
        const dx = p2.x - p1.x;
        const dy = p2.y - p1.y;
        const length = Math.sqrt(dx * dx + dy * dy);
        if (length === 0) continue;
        const normalX = -dy / length;
        const normalY = dx / length;

        // 左侧偏移点
        this.leftCurvePoints.push(new Phaser.Geom.Point(p1.x + normalX * offsetDistance, p1.y + normalY * offsetDistance));
        // 右侧偏移点
        this.rightCurvePoints.push(new Phaser.Geom.Point(p1.x - normalX * offsetDistance, p1.y - normalY * offsetDistance));
    }
    // 处理最后一个点的偏移
    const lastPoint = pathPoints[pathPoints.length - 1];
    const prevPoint = pathPoints[pathPoints.length - 2];
    const dx = lastPoint.x - prevPoint.x;
    const dy = lastPoint.y - prevPoint.y;
    const length = Math.sqrt(dx * dx + dy * dy);
    if (length > 0) {
        const normalX = -dy / length;
        const normalY = dx / length;
        this.leftCurvePoints.push(new Phaser.Geom.Point(lastPoint.x + normalX * offsetDistance, lastPoint.y + normalY * offsetDistance));
        this.rightCurvePoints.push(new Phaser.Geom.Point(lastPoint.x - normalX * offsetDistance, lastPoint.y - normalY * offsetDistance));
    }

    this.lineGraphics = this.add.graphics();
    this.input.on("pointerdown", this.startDrawing, this);
    this.input.on("pointerup", this.stopDrawing, this);
    this.input.on("pointermove", this.keepDrawing, this);
    this.isDrawing = false;
    levelText = this.add.text(13, 11, 'Level 4',{fontFamily: 'Georgia, "Goudy Bookletter 1911", Times, serif'});
}

2. 添加相交检测辅助函数

实现检测线段与折线曲线是否相交的工具方法:

// 检测线段是否与折线曲线相交
doesLineIntersectCurve(line, points) {
    for (let i = 0; i < points.length - 1; i++) {
        const curveSegment = new Phaser.Geom.Line(points[i].x, points[i].y, points[i+1].x, points[i+1].y);
        if (Phaser.Geom.Line.Intersects(line, curveSegment)) {
            return true;
        }
    }
    return false;
}

3. 完成stopDrawing中的检测逻辑

修改stopDrawing方法,判断用户线段是否与两侧曲线都相交:

stopDrawing(pointer){
    this.isDrawing = false;
    // 创建用户绘制的线段
    const userLine = new Phaser.Geom.Line(pointer.downX, pointer.downY, pointer.x, pointer.y);
    // 检测是否与左右两侧曲线都相交
    const intersectLeft = this.doesLineIntersectCurve(userLine, this.leftCurvePoints);
    const intersectRight = this.doesLineIntersectCurve(userLine, this.rightCurvePoints);

    if (intersectLeft && intersectRight){
        levelText.setVisible(false);
        this.add.text(190, 300, 'Level 4 complete!',{fontFamily: 'Georgia, "Goudy Bookletter 1911", Times, serif', fontSize: '60px'}).bringToTop();
    }
};

完整修改后代码

let level4;
window.onload = function() {
    let gameConfig = {
        transparent: true,
        type: Phaser.AUTO,
        scale: {
            mode: Phaser.Scale.FIT,
            autoCenter: Phaser.Scale.CENTER_BOTH,
            parent: "thegame",
            width: 600,
            height: 700
        },
        scene: scene4,
        physics: {
            default: "matter",
            matter: {
                gravity: {
                    y: 0
                },
                debug: true,
            }
        }
    }
    level4 = new Phaser.Game(gameConfig);
    window.focus();
}
let levelText;
class scene4 extends Phaser.Scene{
    constructor(){
        super();
    }

    create(){
        const graphics = this.add.graphics();

        this.path = new Phaser.Curves.Path(0, 300);

        for (let i = 0; i < 8; i++)
        {
            if (i % 2 === 0)
            {
                this.path.ellipseTo(50, 80, 180, 360, true, 0);
            }
            else
            {
                this.path.ellipseTo(50, 80, 180, 360, false, 0);
            }
        }

        graphics.lineStyle(40, 0xff0000, 1);
        this.path.draw(graphics);

        // 生成两侧曲线点
        const pathPoints = this.path.getPoints(200);
        this.leftCurvePoints = [];
        this.rightCurvePoints = [];
        const offsetDistance = 20;

        for (let i = 0; i < pathPoints.length - 1; i++) {
            const p1 = pathPoints[i];
            const p2 = pathPoints[i + 1];
            const dx = p2.x - p1.x;
            const dy = p2.y - p1.y;
            const length = Math.sqrt(dx * dx + dy * dy);
            if (length === 0) continue;
            const normalX = -dy / length;
            const normalY = dx / length;

            this.leftCurvePoints.push(new Phaser.Geom.Point(p1.x + normalX * offsetDistance, p1.y + normalY * offsetDistance));
            this.rightCurvePoints.push(new Phaser.Geom.Point(p1.x - normalX * offsetDistance, p1.y - normalY * offsetDistance));
        }
        const lastPoint = pathPoints[pathPoints.length - 1];
        const prevPoint = pathPoints[pathPoints.length - 2];
        const dx = lastPoint.x - prevPoint.x;
        const dy = lastPoint.y - prevPoint.y;
        const length = Math.sqrt(dx * dx + dy * dy);
        if (length > 0) {
            const normalX = -dy / length;
            const normalY = dx / length;
            this.leftCurvePoints.push(new Phaser.Geom.Point(lastPoint.x + normalX * offsetDistance, lastPoint.y + normalY * offsetDistance));
            this.rightCurvePoints.push(new Phaser.Geom.Point(lastPoint.x - normalX * offsetDistance, lastPoint.y - normalY * offsetDistance));
        }

        this.lineGraphics = this.add.graphics();
        this.input.on("pointerdown", this.startDrawing, this);
        this.input.on("pointerup", this.stopDrawing, this);
        this.input.on("pointermove", this.keepDrawing, this);
        this.isDrawing = false;
        levelText = this.add.text(13, 11, 'Level 4',{fontFamily: 'Georgia, "Goudy Bookletter 1911", Times, serif'});
    }
           
    startDrawing(){
        this.isDrawing = true;
    }

    keepDrawing(pointer){
        if(this.isDrawing){
            this.lineGraphics.clear();
            this.lineGraphics.lineStyle(5, 'black');
            this.lineGraphics.moveTo(pointer.downX, pointer.downY);
            this.lineGraphics.lineTo(pointer.x, pointer.y);
            this.lineGraphics.strokePath();
        }
    }

    updateLine(){
        this.lineGraphics.lineStyle(15, 'black');
        this.lineGraphics.moveTo(linePoints[0].x, linePoints[0].y);
        for(var i = 1; i < linePoints.length; i++) {
            this.lineGraphics.lineTo(linePoints[i].x, linePoints[i].y);
        }
        this.lineGraphics.strokePath();
    }

    doesLineIntersectCurve(line, points) {
        for (let i = 0; i < points.length - 1; i++) {
            const curveSegment = new Phaser.Geom.Line(points[i].x, points[i].y, points[i+1].x, points[i+1].y);
            if (Phaser.Geom.Line.Intersects(line, curveSegment)) {
                return true;
            }
        }
        return false;
    }

    stopDrawing(pointer){
        this.isDrawing = false;
        const userLine = new Phaser.Geom.Line(pointer.downX, pointer.downY, pointer.x, pointer.y);
        const intersectLeft = this.doesLineIntersectCurve(userLine, this.leftCurvePoints);
        const intersectRight = this.doesLineIntersectCurve(userLine, this.rightCurvePoints);

        if (intersectLeft && intersectRight){
            levelText.setVisible(false);
            this.add.text(190, 300, 'Level 4 complete!',{fontFamily: 'Georgia, "Goudy Bookletter 1911", Times, serif', fontSize: '60px'}).bringToTop();
        }
    };
};

内容的提问来源于stack exchange,提问作者engineeringstudent1999

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 14:45:09