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

基于PixiJS的Words of Wonder类单词游戏拖拽选字问题求助

修复PixiJS拖拽选中路径字母的问题

我正在用PixiJS开发一款类似Words of Wonder的单词游戏,当前代码只能检测拖拽的起始和结束动作,无法识别拖拽过程中经过的字母,求修复方案。

解决方案

要实现拖拽路径上的字母选中,核心是在拖拽过程中实时检测鼠标位置与字母的碰撞,具体实现如下:

  • 全局监听拖拽移动事件:在PIXI舞台绑定pointermove事件,实时追踪鼠标位置
  • 维护拖拽状态:用变量标记是否处于拖拽中,避免非拖拽状态下的误触发
  • 给字母元素添加标识:为每个字母文本添加letter属性,方便快速获取字母内容
  • 碰撞检测逻辑:在移动事件中遍历所有字母,判断鼠标是否进入字母的交互区域,未选中的字母加入选中数组
  • 拖拽结束重置:清空选中状态,恢复所有字母的可交互性

修改后的完整代码

window.onload = () => {
const container = document.getElementById('canvas-container');
const app = new PIXI.Application({
    width: container.clientWidth * 2,
    height: container.clientHeight * 2,
    backgroundColor: 0x1099bb,
});
container.appendChild(app.view);

const background = PIXI.Sprite.from('https://cdn.glitch.global/b457296f-514e-47e5-82c6-28bdeeda16ca/background.png?v=1716472339788');
background.width = app.screen.width;
background.height = app.screen.height;
app.stage.addChild(background);

const gridContent = [
    [null, null, null, null],
    [null, '', null, ''],
    [null, null, null, ''],
];

const gridSize = app.screen.width / 5.3;
const gridPadding = gridSize / 10;
const letters = ['L', 'G', 'O', 'D'];
const angleIncrement = (Math.PI * 2) / letters.length;
const circleCenterX = app.screen.width / 2;
const circleCenterY = app.screen.height - app.screen.width / 3 - 170;
const circleRadius = app.screen.width / 3.3;
const letterRadius = circleRadius / 4.2;
const letterColors = ['#ff9933', '#ff9933', '#ff9933', '#ff9933'];
const numRows = gridContent.length;
const numCols = gridContent[0].length;
const gridStartX = (app.screen.width - (numCols * (gridSize + gridPadding))) / 2;
const gridGapY = 120;
const gridStartY = gridGapY;

// 维护拖拽状态和选中字母数组
let isDragging = false;
let selectedLettersArray = [];

// 全局拖拽移动事件,检测经过的字母
app.stage.on('pointermove', (event) => {
    if (!isDragging) return;
    
    const globalPos = event.data.global;
    // 获取所有带标识的字母文本元素
    const letterTexts = app.stage.children.filter(child => child instanceof PIXI.Text && child.letter);
    
    letterTexts.forEach(text => {
        // 检测鼠标是否在字母的碰撞范围内(基于文本边界框)
        const bounds = text.getBounds();
        if (
            globalPos.x >= bounds.x &&
            globalPos.x <= bounds.x + bounds.width &&
            globalPos.y >= bounds.y &&
            globalPos.y <= bounds.y + bounds.height &&
            !text.isSelected
        ) {
            // 标记为已选中,加入数组
            text.isSelected = true;
            selectedLettersArray.push(text.letter);
            console.log('选中字母:', text.letter, '当前选中序列:', selectedLettersArray);
            checkForValidWord();
        }
    });
});

function checkForValidWord() {
    const selectedLetters = selectedLettersArray.join('');
    console.log('检查有效单词:', selectedLetters);
    const foundAnswer = correctAnswers.find(answer => selectedLetters.endsWith(answer));
    if (foundAnswer) {
        console.log('找到有效单词:', foundAnswer);
        fillGrid(foundAnswer, selectedLetters.length - foundAnswer.length);
        // 重置选中状态
        resetSelectionState();
    }
}

// 重置选中状态
function resetSelectionState() {
    selectedLettersArray = [];
    const letterTexts = app.stage.children.filter(child => child instanceof PIXI.Text && child.letter);
    letterTexts.forEach(text => {
        text.isSelected = false;
        text.interactive = true;
    });
    isDragging = false;
}

function drawLetterWheel() {
    const shuffleButtonUrl = 'images/shuffle.png';
    const shuffleButtonSize = 65;

    const circle = new PIXI.Graphics();
    circle.beginFill(0xfff7e6, 0.7);
    circle.drawCircle(circleCenterX, circleCenterY, circleRadius);
    circle.endFill();
    app.stage.addChild(circle);

    letters.forEach((letter, index) => {
        const angle = index * angleIncrement;
        const letterShiftFactor = 0.8;
        const x = circleCenterX + Math.cos(angle) * (circleRadius - letterRadius) * letterShiftFactor;
        const y = circleCenterY + Math.sin(angle) * (circleRadius - letterRadius) * letterShiftFactor;

        const letterText = new PIXI.Text(letter, {
            fontSize: letterRadius * 2,
            fill: letterColors[index],
            fontFamily: ['Poetsen One', 'Roboto', 'sans-serif']
        });
        letterText.anchor.set(0.5);
        letterText.position.set(x, y);
        // 给字母添加标识和选中状态
        letterText.letter = letter;
        letterText.isSelected = false;
        app.stage.addChild(letterText);

        letterText.interactive = true;
        letterText.buttonMode = true;

        letterText
            .on('pointerdown', onDragStart)
            .on('pointerup', onDragEnd)
            .on('pointerupoutside', onDragEnd);

        function onDragStart() {
            console.log('拖拽开始:', letter);
            isDragging = true;
            // 标记当前字母为已选中,加入数组
            letterText.isSelected = true;
            selectedLettersArray.push(letter);
            console.log('当前选中序列:', selectedLettersArray);
            checkForValidWord();
            letterText.interactive = false; // 选中后暂时禁用交互
        }

        function onDragEnd() {
            console.log('拖拽结束');
            resetSelectionState();
        }
    });

    const shuffleButton = PIXI.Sprite.from(shuffleButtonUrl);
    shuffleButton.anchor.set(0.5);
    shuffleButton.width = shuffleButton.height = shuffleButtonSize;
    shuffleButton.position.set(circleCenterX, circleCenterY);
    shuffleButton.interactive = true;
    shuffleButton.buttonMode = true;
    shuffleButton.on('pointerdown', () => {
        shuffleLetters();
    });
    app.stage.addChild(shuffleButton);
}

const correctAnswers = ['GOLD', 'DOG', 'GOD', 'LOG'];

function drawGrid() {
    for (let row = 0; row < numRows; row++) {
        for (let col = 0; col < numCols; col++) {
            if (gridContent[row][col] === null) {
                const cell = new PIXI.Graphics();
                cell.beginFill(0xfff7e6);
                cell.drawRoundedRect(0, 0, gridSize, gridSize, 20);
                cell.endFill();
                cell.position.set(gridStartX + col * (gridSize + gridPadding), gridStartY + row * (gridSize + gridPadding));
                app.stage.addChild(cell);
            }
        }
    }
}

function fillGrid(answer, startIndex) {
    for (let i = 0; i < answer.length; i++) {
        const row = Math.floor(startIndex / 4);
        const col = startIndex % 4;
        const cell = app.stage.children.find(child => child instanceof PIXI.Graphics &&
            child.position.x === col * (gridSize + gridPadding) + gridStartX &&
            child.position.y === row * (gridSize + gridPadding) + gridStartY);
        if (cell) {
            const text = new PIXI.Text(answer[i], { fill: 'black' });
            text.anchor.set(0.5);
            text.position.set(cell.x + gridSize / 2, cell.y + gridSize / 2);
            app.stage.addChild(text);
        }
        startIndex++;
    }
}

function shuffleLetters() {
    const shuffleLetters = ['L', 'G', 'O', 'D'];
    for (let i = shuffleLetters.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [shuffleLetters[i], shuffleLetters[j]] = [shuffleLetters[j], shuffleLetters[i]];
    }

    const letterTexts = app.stage.children.filter(child => child instanceof PIXI.Text && child.letter);
    shuffleLetters.forEach((letter, index) => {
        letterTexts[index].text = letter;
        letterTexts[index].letter = letter; // 更新字母标识
        letterTexts[index].isSelected = false;
        letterTexts[index].interactive = true;
    });
}

drawGrid();
drawLetterWheel();
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 13:52:02