Login
芋圆社区 > 编程 > 【项目】桌宠之旅 > 10-实现桌宠拖拽

10-实现桌宠拖拽

791
0
2023-07-08
2023-07-18
Hey、小怪兽

  • • 这里实现的桌宠拖拽并不是按住鼠标左键拖拽,而是鼠标右键点击进入拖拽状态,再次点击鼠标右键结束拖拽
  • • 首先要先获取桌宠拖拽的每一帧图片,第五节有介绍如何获取了,放在/modules/kkr下的drap文件夹里:
  • • 修改一下kkr.js的初始配置(/src/modules/kkr/kkr.js):
  • export const init_config = [
        {
            index: 0,
            name: "normal",
            frames: 60,
            texture: [],
            object: {
                loop: true,
            },
        },
        {
            index: 1,
            name: "drap",
            frames: 54,
            texture: [],
            object: {
                loop: true,
            },
        },
    ];
  • • 回到渲染进程/src/_renderer/renderer.js,初始化拖拽的变量:
  • // 当前的动画
    let anim_current = null;
    // 拖拽动画
    let anim_drap = null;
    // 拖拽状态
    let drag_static = false;
  • • 在页面加载方法里写初始化方法,再normal初始化完毕后初始化拖拽的动画:
  • // 页面加载完成执行
    window.addEventListener("load", async (event) => {
        ...其他代码
    
        // 赋值给当前动画
        anim_current = anim_normal;
    
        // 开始初始化其他的动画
        anim_drap = await create_anim_func(init_config.find((obj) => obj.name === "drap"));
    });
  • • 接着之前写的鼠标移动方法需要修改,如果是在拖拽状态的话,就不监听鼠标移动了:
  • // 鼠标移动事件
    anim.on("mousemove", (event) => {
        const global_position = event.data.global;
        const local_position = anim.toLocal(global_position);
        // 当前这一帧的动画贴图
        const anim_img = anim.texture.baseTexture.resource.source;
    
        if (drag_static) {
            // 这个时候在拖拽,什么都不做
        } else {
            body_area_func(get_pixel_color_func(local_position.x, local_position.y, anim_img), local_position)
        }
    });
  • • 再写个进入动画状态的方法change_anim_func,因为桌宠它是normal的动画,要换成拖拽drap的动画:
  • - 传入之前的动画from_anim和要进入的动画to_anim,还有time,time就是多久进入这个动画
  • - 方法里面先把之前的动画from_anim暂停了
  • - 接着移除removeChild之前的动画
  • - 然后把要进入的动画to_anim移动到第一帧播放(不然会中途播放,比方说上次播放到了30帧,这样还是会从30帧播放)
  • // 进入动画,可以用来切换动画(切换回normal或进入drap)
    const change_anim_func = (from_anim, to_anim, time) => {
        from_anim.stop();
        setTimeout(() => {
            app.stage.removeChild(from_anim);
            to_anim.gotoAndPlay(0);
            app.stage.addChild(to_anim);
            // 替换当前动画
            anim_current = to_anim;
        }, time);
    };
  • • 接着就可以写鼠标右键方法了:
  • - 首先判断是否在拖拽状态,如果没有在拖拽状态的话就可以拖拽
  • - 跟之前一样调用mouseAPI的mouseDrapStart,告诉预加载脚本
  • - 开始拖拽后,drag_static变量要改为true,接着从普通动画改变为拖拽动画
  • - 如果再次点击的话,已经在拖拽状态了,就走else里,告诉预加载脚本拖拽完毕了
  • - 将变量drag_static修改为false,并从拖拽动画回到普通的动画
  • // 鼠标点击右键拖拽
    anim.on("rightclick", (event) => {
        const global_position = event.data.global;
        const local_position = anim.toLocal(global_position);
        if (drag_static === false) {
            // 如果没在拖拽状态,右键后进入推拽状态,传给主进程点击的位置
            window.mouseAPI.mouseDrapStart({
                x: local_position.x,
                y: local_position.y,
                drap: true,
            });
            // 开启拖拽状态进入拖拽动画
            drag_static = true;
            change_anim_func(anim_current, anim_drap, 0);
        } else {
            // 再次点击脱离拖拽状态
            window.mouseAPI.mouseDrapEnd({
                drap: false,
            });
            // 取消拖拽状态进入普通动画
            drag_static = false;
            change_anim_func(anim_drap, anim_normal, 0);
        }
    });
  • • 修改预加载脚本/src/_preload/preload.js:
  • const { contextBridge, ipcRenderer } = require("electron");
    
    contextBridge.exposeInMainWorld("mouseAPI", {
        mouseMove: (obj) => ipcRenderer.send("mouse-move", obj),
        mouseDrapStart: (obj) => ipcRenderer.send("mouse-drap-start", obj),
        mouseDrapEnd: (obj) => ipcRenderer.send("mouse-drap-end", obj),
    });
    
  • • 修改主进程/src/_main/main.js,加入一些变量:
  • // 拖拽的初始位置
    let drapPosition = { x: 0, y: 0 };
    // 拖拽定时器
    let drapTimer = null;
  • • 监听mouse-drap-start和mouse-drap-end:
  • • 这里改变位置用了一个定时器,当结束拖拽后清空这个定时器
  • • mainWindow.setPosition(x - drapPosition.x, y - drapPosition.y)可以理解为,当点击右键开始拖拽的时候,记录点击的位置,接着不管往哪个方向移动,当前屏幕的位置减去移动的位置,就是拖拽后的位置
  • // 桌宠拖动开始,记录点击位置,让窗口粘着鼠标
    ipcMain.on("mouse-drap-start", (event, obj) => {
        drapPosition = {
            x: obj.x,
            y: obj.y,
        };
        drapTimer = setInterval(() => {
            const { x, y } = screen.getCursorScreenPoint();
            mainWindow.setPosition(x - drapPosition.x, y - drapPosition.y);
        }, 10);
    });
    
    // 桌宠拖动结束,也就是再按一下右键,定时器清空
    ipcMain.on("mouse-drap-end", (event, obj) => {
        clearInterval(drapTimer);
    });
    
  • • 在终端输入pnpm start,初始的桌宠是这样的normal动画:
  • • 在桌宠本体右键点击:
  • • 再次右键点击,结束拖拽,完美!已经实现了拖拽功能了!
  • • /src/modules/kkr/kkr.js完整代码:
  • export const init_config = [
        {
            index: 0,
            name: "normal",
            frames: 60,
            texture: [],
            object: {
                loop: true,
            },
        },
        {
            index: 1,
            name: "drap",
            frames: 54,
            texture: [],
            object: {
                loop: true,
            },
        },
    ];
  • • 渲染进程/src/_renderer/renderer.js完整代码:
  • // 导入方法
    import { get_pixel_color_func, body_area_func } from "../utils/anim.js";
    // 当前使用的模型
    let current_module = "kkr";
    // 初始配置和动作配置
    let init_config = null;
    // 当前的动画
    let anim_current = null;
    // 初始动画
    let anim_normal = null;
    // 拖拽动画
    let anim_drap = null;
    // 拖拽状态
    let drag_static = false;
    // 动画的舞台
    let app = null;
    // 动画的canvas
    let anim_canvas = null;
    // 虚拟的canvas
    let shadow_canvas = null;
    // 获取窗口的大小
    const bower_width = window.innerWidth;
    const bower_height = window.innerHeight;
    
    // 页面加载完成执行
    window.addEventListener("load", async (event) => {
        // 获取配置
        const module = await import(`../modules/${current_module}/${current_module}.js`);
        init_config = module.init_config;
    
        // 赋值canvas
        anim_canvas = document.getElementById("taro-canvas");
        shadow_canvas = document.getElementById("shadow-canvas");
        shadow_canvas.width = bower_width;
        shadow_canvas.height = bower_height;
    
        // 动画舞台配置
        app = new PIXI.Application({
            view: anim_canvas,
            width: bower_width,
            height: bower_height,
            backgroundAlpha: 0,
            resolution: 1,
        });
        // 添加给div-taropet元素
        document.getElementById("taro-pet").appendChild(app.view);
    
    
        // 先把初始的动画加载完成
        anim_normal = await create_anim_func(init_config[0]);
        anim_normal.play();
        app.stage.addChild(anim_normal);
        // 赋值给当前动画
        anim_current = anim_normal;
    
        // 开始初始化其他的动画
        anim_drap = await create_anim_func(init_config.find((obj) => obj.name === "drap"));
    });
    
    // 创建动画的方法
    const create_anim_func = async (obj) => {
        // 存放文件前缀, 文件格式(png,jpg)
        const file_prefix = "./modules";
        const file_format = ".png";
        const { name, frames, object } = obj;
        const texture_array = [];
        // 通过帧数循环获取贴图
        for (let i = 0; i < frames; i++) {
            const num = `000${i}`.slice(-3);
            // texture_name ./modules/kkr/normal/001.png
            const texture_name = `${file_prefix}/${current_module}/${name}/${num}${file_format}`;
            const texture = await PIXI.Texture.from(texture_name);
            texture_array.push(texture);
        }
        // 生成动画,配置动画属性
        const anim = new PIXI.AnimatedSprite(texture_array);
        anim.name = name;
        anim.animationSpeed = 0.5;
        anim.loop = object.loop;
    
        // 设置交互模式
        anim.eventMode = "dynamic";
    
        // 鼠标移动事件
        anim.on("mousemove", (event) => {
            const global_position = event.data.global;
            const local_position = anim.toLocal(global_position);
            // 当前这一帧的动画贴图
            const anim_img = anim.texture.baseTexture.resource.source;
    
            if (drag_static) {
                // 这个时候在拖拽,什么都不做
            } else {
                body_area_func(get_pixel_color_func(local_position.x, local_position.y, anim_img), local_position)
            }
        });
        // 鼠标左键点击事件
        anim.on("click", (event) => {
            const global_position = event.data.global;
            const local_position = anim.toLocal(global_position);
            console.log('鼠标左键点击:', local_position)
        });
    
        // 鼠标点击右键拖拽
        anim.on("rightclick", (event) => {
            const global_position = event.data.global;
            const local_position = anim.toLocal(global_position);
            if (drag_static === false) {
                // 如果没在拖拽状态,右键后进入推拽状态,传给主进程点击的位置
                window.mouseAPI.mouseDrapStart({
                    x: local_position.x,
                    y: local_position.y,
                    drap: true,
                });
                // 开启拖拽状态进入拖拽动画
                drag_static = true;
                change_anim_func(anim_current, anim_drap, 0);
            } else {
                // 再次点击脱离拖拽状态
                window.mouseAPI.mouseDrapEnd({
                    drap: false,
                });
                // 取消拖拽状态进入普通动画
                drag_static = false;
                change_anim_func(anim_drap, anim_normal, 0);
            }
        });
    
        return anim;
    };
    
    // 进入动画,可以用来切换动画(切换回normal或进入drap)
    const change_anim_func = (from_anim, to_anim, time) => {
        from_anim.stop();
        setTimeout(() => {
            app.stage.removeChild(from_anim);
            to_anim.gotoAndPlay(0);
            app.stage.addChild(to_anim);
            // 替换当前动画
            anim_current = to_anim;
        }, time);
    };
    
  • • 预加载脚本/src/_preload/preload.js完整代码:
  • const { contextBridge, ipcRenderer } = require("electron");
    
    contextBridge.exposeInMainWorld("mouseAPI", {
        mouseMove: (obj) => ipcRenderer.send("mouse-move", obj),
        mouseDrapStart: (obj) => ipcRenderer.send("mouse-drap-start", obj),
        mouseDrapEnd: (obj) => ipcRenderer.send("mouse-drap-end", obj),
    });
    
  • • 主进程/src/_main/main.js完整代码:
  • const { app, screen, ipcMain, BrowserWindow } = require("electron");
    const path = require("path");
    // 主窗口
    let mainWindow = null;
    // 拖拽的初始位置
    let drapPosition = { x: 0, y: 0 };
    // 拖拽定时器
    let drapTimer = null;
    
    const createMainWindow = () => {
        // 获取当前桌面的宽度和高度
        const size = screen.getPrimaryDisplay().workAreaSize;
        const { width, height } = size;
    
        mainWindow = new BrowserWindow({
            width: 390,
            height: 390,
            // 起始位置是屏幕宽度减去窗口宽度,再减去10个像素
            x: width - 390 - 10,
            y: height - 390 - 10,
            // 隐藏菜单栏
            autoHideMenuBar: true,
            // 设置为透明窗口
            transparent: true,
            // 隐藏窗口边框
            frame: false,
            // 窗口置顶
            alwaysOnTop: true,
            // 隐藏任务栏图标
            skipTaskbar: true,
            // 禁止改变窗口大小
            resizable: false,
            // 先隐藏窗口
            show: false,
            // Preload 脚本
            webPreferences: {
                preload: path.resolve(__dirname, "../_preload/preload.js"),
            },
        });
    
        // 允许鼠标穿透
        mainWindow.setIgnoreMouseEvents(true, { forward: true });
    
        // 开启调试工具
        mainWindow.webContents.openDevTools();
    
        mainWindow.loadFile(path.resolve(__dirname, "../index.html"));
    
        mainWindow.on("ready-to-show", () => {
            mainWindow.show();
        });
    };
    
    app.whenReady().then(() => {
        createMainWindow();
    });
    
    // 鼠标移动监听,用于判断是否需要穿透
    ipcMain.on("mouse-move", (event, obj) => {
        if (obj.ignore) {
            mainWindow.setIgnoreMouseEvents(true, { forward: true });
        } else {
            mainWindow.setIgnoreMouseEvents(false);
        }
    });
    
    // 桌宠拖动开始,记录点击位置,让窗口粘着鼠标
    ipcMain.on("mouse-drap-start", (event, obj) => {
        drapPosition = {
            x: obj.x,
            y: obj.y,
        };
        drapTimer = setInterval(() => {
            const { x, y } = screen.getCursorScreenPoint();
            mainWindow.setPosition(x - drapPosition.x, y - drapPosition.y);
        }, 10);
    });
    
    // 桌宠拖动结束,也就是再按一下右键,定时器清空
    ipcMain.on("mouse-drap-end", (event, obj) => {
        clearInterval(drapTimer);
    });
    

上一篇:9-设置进程通信和鼠标穿透

下一篇:11-实现桌宠随机动作

Message Board
回复
回复内容不允许为空
留言字数要大于2,小于200!
提交成功,5s后刷新页面!
编程导航

0-桌宠开发前言

1-搭建环境和创建简单的应用程序

2-修改项目结构

3-创建一个透明的窗口

4-nodemon实现热加载

5-获取桌宠每一帧图片

6-Pixi.js加载桌宠初始动画

7-添加鼠标事件

8-获取区域颜色和判断桌宠本体

9-设置进程通信和鼠标穿透

10-实现桌宠拖拽

11-实现桌宠随机动作

12-实现桌宠播放语音

13-实现桌宠词板功能

14-设置系统托盘

15-实现桌宠模组切换

16-桌宠数据和状态拓展

17-生成ICO图标和准备打包

18-完成开发并打包桌宠应用

Copyright © 2020 芋圆社区

Powered by 浙ICP备2020039309号-1

此页面不支持夜间模式!

已进入夜间模式!

已进入普通模式!

搜索框不允许为空

签到成功!经验+5!芋圆币+2!

签到失败!今日已签到!

需要登录社区账号才可以进入!

复制成功
寄,页面未加载完成或页面无锚点