当前位置: 首页 > news >正文

《Windows API每日一练》6.2 客户区鼠标消息

第五章已经讲到,Windows只会把键盘消息发送到当前具有输入焦点的窗口。鼠标消息则不同:当鼠标经过窗口或在窗口内被单击,则即使该窗口是非活动窗口或不带输入焦点, 窗口过程还是会收到鼠标消息。Windows定义了 21种鼠标消息。不过,其中11种消息与 客户区无关,称为“非客户区消息”。Windows应用程序经常忽略这类消息。

本节必须掌握的知识点:

        客户区鼠标消息

        第35练:客户区鼠标消息的处理

6.2.1 客户区鼠标消息

客户区鼠标消息

当鼠标移经窗口客户区时,窗口过程接收WM_MOUSEMOVE消息。在窗口客户区内按下或释放鼠标按钮时,窗口过程接收如下表所示的消息:

按钮

按下

释放

第二次按下按钮

左键

WM_LBUTTONDOWN

WM_LBUTTONUP

WM_LBUTTONDBLCLK

中键

WM_MBUTTONDOWN

WM_MBUTTONUP

WM_MBUTTONDBLCLK

右键

WM_RBUTTONDOWN

WM_RBUTTONUP

WM_RBUTTONDBLCLK

窗口过程只对三键鼠标接收MBUTTON消息,只对双键鼠标接收RBUTTON消息。而只有当窗口类被定义成接收鼠标双击时,窗口过程才接收DBLCLK(双击)消息。

对所有这些消息来说,参数IParam包含了鼠标的位置信息,其中低位字表示x坐标, 高位字表示y坐标,它们都是相对于窗口客户区左上角的相对坐标。利用LOWORD宏和 HIWORD宏,可以获取这些坐标值:

X = LOWORD (IParam);

y = HIWORD (IParam);

参数wParam表示鼠标按钮、Shift键和Ctrl键的状态。可以利用WINUSER.H头文件中定义的位掩码来测试参数wParam。前缀MK代表“鼠标键”(mouse key)。

MK_LBUTTON       按下左键

MK_MBUTTON      按下中键

MK_RBUTTON       按下右键

MK_SHIFT             按下 Shift 键

MK_CONTROL      按下 Ctrl 键

例如,当接收到WM_LBUTTONDOWN消息时,若wparam & MK_SHIFT 的值为TRUE(非零),则表示按下鼠标左键的同时按下了 Shift键。

●处理Shift键

处理过程依赖ShiftCtrl键的逻辑处理

单键鼠标模拟双键鼠标

if (wParam & MK_SHIFT)  //按下Shift

{

    if (wParam & MK_CONTROL)

    {

        [按下Shift + Ctrl];

     }

    else{

        [只按下Shift];

    }

}else{                  //未按Shift

    if (wParam & MK_CONTROL)

    {

        [只按下Ctrl];

    }else{

        [ShiftCtrl都没被按下];

    }

}

case WM_LBUTTONDOWN:

//未按Shift时,直接处理左键

    if (!(wParam & MK_SHIFT))

    {

        [这里处理左键];

        return 0;

     }   //注意,这里没有return

    //用户按下了鼠标左键+Shift,执行以下代码,模拟右键。

case WM_RBUTTONDOWN: 

    [这里处理右键];

    return 0;

【注意】双键鼠标也是可以正常处理的。单键鼠标可以通过按住鼠标左键+Shift,来模拟鼠标右键的功能。

【注意】GetKeyState可以通过VK_LBUTTON、VK_RBUTTON、VK_SHIFT、VK_CONTROL等获取鼠标当前状态。但鼠标或键盘未被按下的键不能使用GetKeyState。只有被按下时才会报告其按下状态。(while(GetKeyState(VK_LBUTTON)>=0))是错误的代码。

●鼠标移经窗口的客户区时,Windows系统不会为鼠标经过的每个像素位置都产生 WM_MOUSEMOVE消息。程序收到的WM_MOUSEMOVE消息个数取决于鼠标硬件和窗口过程处理鼠标移动消息的速度。换言之,如果消息队列里还有未处理的 WM_MOUSEMOVE消息,Windows就不会重复向消息队列中添加该消息。试验下面这个 CONNECT程序,可以对WM_MOUSEMOVE消息的产生速度有一个全面的了解。

●若在非活动窗口的客户区内按下鼠标左键,Windows会将该窗口变为活动窗口,并向窗口过程发送WM_LBUTTONDOWN消息。当窗口过程接收到WM_LBUTTONDOWN消息时,程序就能够安全地保证该窗口是活动窗口。但是,在事先没有接收 WM_LBUTTONDOWN消息的情况下,窗口过程仍然可以接收WM_LBUTTONUP消息。 比如,当用户在其他窗口内按下鼠标,再移动到用户窗口,然后释此时就会发生这种情况。类似地,当移动鼠标到另一个窗口再释放时,前一个窗口过程在接收 WM_LBUTTONDOWN消息后,就接收不到相应的WM_LBUTTONUP消息。

前面这些规则有两个例外:

●即使鼠标位于窗口的客户区之外,窗口过程也有办法“捕获鼠标”,并且继续接收鼠标消息。本章会在后面讲述如何捕获鼠标。

●若正在显示一个系统模式消息框或系统模式对话框,则其他任何程序都不能接收鼠标消息。当系统模式消息框或对话框处于活动状态时,它们会阻止系统切换到另一个窗口。例如,关闭Windows时弹出的消息框就是一个系统模式消息框。

6.2.2 第35练:客户区鼠标消息的处理

/*---------------------------------------------------------------

035  WIN32 API 每日一练

     第35个例子CONNECT.C:客户区鼠标消息的处理

     SetPixel函数

     SetCursor函数

     ShowCursor函数

     WM_LBUTTONDOWNE消息

     WM_MOUSEMOVE消息

     WM_LBUTTONUP消息

(c) www.bcdaren.com, 2020

----------------------------------------------------------------*/

#include <windows.h>

#define MAXPOINTS 1000

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

 PSTR szCmdLine, int iCmdShow)

{

     static TCHAR szAppName[] = TEXT("Connect");

    (略)

     return msg.wParam;

}

LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM

lParam)

{

     static POINT pt[MAXPOINTS];//鼠标经过窗口区像素点坐标数组

     static int iCount;

     HDC hdc;

     int i,j;

     PAINTSTRUCT ps;

     switch (message)

     {

     /*测试:非客户区消息

     //用于通知应用程序在非客户区(Non-Client Area)

//接收到鼠标消息时进行的命中测试(Hit Test)。

     case WM_NCHITTEST:

         //直接返回位置信息,阻止系统向所有窗口客户区和非窗口客户区发送鼠标消息

         return (LRESULT)HTNOWHERE;

     //测试:按下ALT+F、Ctrl+C等系统消息

     case WM_SYSKEYDOWN:

        //直接返回,使所有系统键盘消息失效

         return 0;*/

     //按下鼠标左键消息

     case WM_LBUTTONDOWN:    

          iCount = 0;

          InvalidateRect(hwnd,NULL,TRUE);//重绘窗口---清除背景

          return 0;

     //鼠标移动消息

     case WM_MOUSEMOVE:  

          //按下鼠标左键并且iCount小于1000

          if (wParam & MK_LBUTTON && iCount < 1000)

          {

               //填充坐标数组

               pt[iCount].x = LOWORD(lParam);

               pt[iCount++].y = HIWORD(lParam);

               hdc = GetDC(hwnd);

               //设置像素点颜色,RGB(0)黑色

               SetPixel(hdc,LOWORD(lParam),HIWORD(lParam),0);

               ReleaseDC(hwnd,hdc);

          }

          return 0;

     //松开鼠标左键消息

     case WM_LBUTTONUP

          //重新绘制窗口---不清除背景,保留WM_MOUSEMOVE里画下的点。

          InvalidateRect(hwnd,NULL,FALSE);

          return 0;

     case WM_PAINT:

          hdc = BeginPaint(hwnd,&ps);

          SetCursor(LoadCursor(NULL,IDC_WAIT));//设置鼠标形状为等待状态

          ShowCursor(TRUE);//显示鼠标

          //像素点之间画线

          for (i = 0;i < iCount - 1;i++)

          {

               for (j = 0;j < iCount - 1;j++)

               {

                    MoveToEx(hdc,pt[i].x,pt[i].y,NULL);

                    LineTo(hdc,pt[j].x,pt[j].y);

               }

          }

          ShowCursor(FALSE);//隐藏鼠标

          SetCursor(LoadCursor(NULL,IDC_ARROW));//设置鼠标位图“箭头形状”

          EndPaint(hwnd,&ps);

          return 0;

     case WM_DESTROY:

          PostQuitMessage(0);

          return 0;

     }

     return DefWindowProc(hwnd, message, wParam, lParam);

}

/***************************************************************************

SetPixel函数:指定坐标到指定的颜色设置像素

COLORREF SetPixel(

  HDC      hdc,

  int      x,  //坐标

  int      y,

  COLORREF color    //RGB颜色

);

***************************************************************************

SetCursor函数:设置鼠标形状

HCURSOR SetCursor(

  HCURSOR hCursor   //IDC_ARROW,IDC_WAIT

);

ShowCursor函数:显示/隐藏鼠标

int ShowCursor(

  BOOL bShow   //TRUE显示,FALSE隐藏

);

***************************************************************************

WM_LBUTTONDOWNE消息:当光标在窗口的客户区域中时用户按下鼠标左键时发布。

如果未捕获鼠标,则消息将发布到光标下方的窗口。否则,该消息将发布到捕获鼠标的窗口中。

参数wParam:指示各种虚拟键是否按下。此参数可以是以下一个或多个值。

MK_CONTROL  0x0008  CTRL键按下。

MK_LBUTTON  0x0001  鼠标左键按下。

MK_MBUTTON  0x0010  鼠标中键按下。

MK_RBUTTON  0x0002  鼠标右键按下。

MK_SHIFT    0x0004  SHIFT键按下。

MK_XBUTTON1 0x0020  第一个X按钮按下。

MK_XBUTTON2 0x0040  第二个X按钮按下。

lParam

低位字指定光标的x坐标。坐标相对于客户区域的左上角。

高阶字指定光标的y坐标。坐标相对于客户区域的左上角。

返回值

如果应用程序处理此消息,则应返回零。

***************************************************************************

WM_MOUSEMOVE消息:光标移动时张贴到窗口。如果未捕获鼠标,则消息将发布到包含光标的窗口中。否则,该消息将发布到捕获鼠标的窗口中。

参数与WM_LBUTTONDOWNE消息相同

***************************************************************************

WM_LBUTTONUP消息:当光标在窗口的客户区域中时,用户释放鼠标左键时发布。

如果未捕获鼠标,则消息将发布到光标下方的窗口。否则,该消息将发布到捕获鼠标的窗口中。

参数与WM_LBUTTONDOWNE消息相同

*/

       运行结果:

图6-1 客户区鼠标消息

 

总结

●实例操作方法:

1.第一种——在客户区按下左键,略微移动,再松开左键。

2.第二种——在客户区按下左键,快速移动鼠标。

●己知的问题:在客户区外释放左键,Connnect不会连接这些点,因为没收到WM_LBUTTONUP消息。

●该程序较耗时,绘制时,鼠标变沙漏形,处理WM_PAINT完后回原来的状态。用SetCursor来切换鼠标。ShowCursor隐藏或显示鼠标指针。

●窗口过程:

1.实例CONNECT.C处理了三个鼠标消息。

WM_LBUTTONDOWNE消息:按下鼠标左键时,调用InvalidateRect函数清除背景,重绘窗口。

WM_MOUSEMOVE消息:移动鼠标时,采集不超过1000个鼠标移动坐标点,保存在pt数组中,然后使用SetPixel函数绘制坐标点(系统默认黑色画笔)。

WM_LBUTTONUP消息:松开鼠标左键时,重绘窗口,但是并不清除背景。

2.处理WM_PAINT消息时,CONNECT程序需要耗费一定的时间来绘制直线,因此鼠标指针会变成等待位图。调用SetCursor函数,加载并设置鼠标位图为等待位图,显示鼠标位图。接着使用双循环将所有坐标点连接起来。然后再恢复原鼠标位图。

3.在用户释放左键时,如果鼠标指针已经移出客户区,CONNECT程序就不会连接这些点, 因为程序没有接收到WM_LBUTTONUP消息。此时如果再将鼠标移入客户区,并按下左键,CONNECT程序就会清空客户区。如果想在客户区外释放鼠标,并继续设计图形,就可以在客户区外按下鼠标的左键,再将鼠标移入客户区。

4.动手实验:处理WM_NCHITTEST消息时可以直接返回鼠标位置信息,阻止系统向所有窗口客户区和非窗口客户区发送鼠标消息。

处理WM_SYSKEYDOWN消息时,可以让所有系统键盘消息失效。

下一节我们讲述如何在非窗口客户区捕捉鼠标消息。

相关文章:

《Windows API每日一练》6.2 客户区鼠标消息

第五章已经讲到&#xff0c;Windows只会把键盘消息发送到当前具有输入焦点的窗口。鼠标消息则不同&#xff1a;当鼠标经过窗口或在窗口内被单击&#xff0c;则即使该窗口是非活动窗口或不带输入焦点&#xff0c; 窗口过程还是会收到鼠标消息。Windows定义了 21种鼠标消息。不过…...

体验升级:扫描全能王智能高清滤镜2.0全面测评

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…...

【JVM系列】JVM调优

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

Linux基础 - Postfix 与 Dovecot 部署邮件系统

目录 零. 简介 一. 部署 二. 设置用户别名信箱 三. Linux 邮件客户端 零. 简介 Postfix 和 Dovecot 是在 Linux 系统中常用于部署邮件系统的两个重要组件。 Postfix 是一种邮件传输代理&#xff08;MTA&#xff09;&#xff0c;主要负责接收、转发和发送邮件。它具有高性能…...

Qt的安装

一、Qt安装 下载地址&#xff1a;https://download.qt.io/archive/qt/ opencv下载安装 下载地址&#xff1a;https://opencv.org/releases/ 陈年旧文&#xff0c;没有下文&#xff0c;以此纪念。。。。。...

ThreeJS-3D教学十二:ShaderMaterial

一、首先 Shader 是做什么的 Shader 可以自定义每个顶点、每个片元/像素如何显示&#xff0c;而控制顶点和片元显示是通过设置 vertexShader 顶点着色器和 fragmentShader 片元着色器&#xff0c;这两个着色器用在 ShaderMaterial 和 RawShaderMaterial 材质上。 我们先看一个例…...

计算机网络面试TCP篇之TCP三次握手与四次挥手

TCP 三次握手与四次挥手面试题 任 TCP 虐我千百遍&#xff0c;我仍待 TCP 如初恋。 巨巨巨巨长的提纲&#xff0c;发车&#xff01;发车&#xff01; PS&#xff1a;本次文章不涉及 TCP 流量控制、拥塞控制、可靠性传输等方面知识&#xff0c;这些知识在这篇&#xff1a; TCP …...

Python-数据分析组合可视化实例图【附完整源码】

数据分析组合可视化实例图 开篇&#xff1a;应女朋友的要求&#xff0c;于是写下了这篇详细的数据可视化代码及完整注释 一&#xff1a;柱状图、折线图横向组合网格布局 本段代码使用了pyecharts库来创建一个包含多个图表&#xff08;柱状图、折线图&#xff09;和网格布局的…...

【JavaEE】Spring Web MVC详解

一.基本概念. 1.什么是Spring Web MVC? 官方链接: https://docs.spring.io/spring-framework/reference/web/webmvc.html Spring Web MVC is the original web framework built on the Servlet API and has been included in the Spring Framework from the very beginning…...

docker安装rocketMq5x以上的版本

1.背景 安装RocketMQ 5.x以上的版本主要是因为新版本引入了许多性能优化、新功能以及对已有特性的增强&#xff0c;这些改进可以帮助提升消息队列系统的稳定性和效率。 1.性能提升&#xff1a;RocketMQ 5.x版本通常包括了对消息处理速度、吞吐量和延迟的优化&#xff0c;使得系…...

【Spring】DAO 和 Repository 的区别

DAO 和 Repository 的区别 1.概述2.DAO 模式2.1 User2.2 UserDao2.3 UserDaoImpl 3.Repository 模式3.1 UserRepository3.2 UserRepositoryImpl 4.具有多个 DAO 的 Repository 模式4.1 Tweet4.2 TweetDao 和 TweetDaoImpl4.3 增强 User 域4.4 UserRepositoryImpl 5.比较两种模式…...

高阶面试-秒杀系统的设计

场景 特价商品如茅台&#xff0c;在8月1日22点10分0秒开始秒杀 平台用户量&#xff1a;几千万&#xff0c;预计几十万用户感兴趣 需求 临时性的活动&#xff0c;不要太大技术改动 原则 商品不能超卖下单成功的订单不能丢失服务器和数据库不能崩溃尽量不让机器人抢走商品 …...

四十五、 证券基金业数据出境有无特别规范需要注意?

证券基金业数据合规除应遵守本《实务问答》前述的各项通用规定外&#xff0c;还应注意中国证券监督管理委员会等其他机构发布的相关规范。其中&#xff0c;与数据出境相关的主要包括《证券期货业数据分类分级指引》&#xff08;JR/T 0158—2018&#xff0c;2018年 9月 27日实施…...

02.Linux下安装FFmpeg

目录 一、下载FFmpeg的编译源码 二、编译源码 三、ffmpeg工具结构解析 1、bin目录 2、include库 3、lib库 四、注意事项 五、可能出现的一些问题 1、某些工具未安装/版本过久 2、缺少pkg-config工具 3、缺少ffmplay FFmpeg 是一个开源的跨平台音视频处理工具集&…...

华为RH2288H V2服务器,远程端口安装Linux操作系统

1、管理口 每台服务器的管理口不一样的&#xff0c;假如我的管理IP地址为&#xff1a;192.168.111.201 使用网线&#xff0c;将管理口和自己电脑连接起来&#xff0c;自己ip地址设置成和管理ip同一网段。 使用 ie 浏览器&#xff0c;如果是Edge&#xff0c;必须在Internet Exp…...

JS在线加密简述

JS在线加密&#xff0c;是指&#xff1a;在线进行JS代码混淆加密。通过混淆、压缩、加密等手段&#xff0c;使得JS源代码难以阅读和理解。从而可以有效防止代码被盗用或抄袭&#xff0c;保护开发者的知识产权和劳动成果。常用的JS在线加密网站有&#xff1a;JShaman、JS-Obfusc…...

理想汽车提出3DRealCar:首个大规模3D真实汽车数据集

理想提出3DRealCar&#xff0c;这是第一个大规模 3D 实车数据集&#xff0c;包含 2500 辆在真实场景中拍摄的汽车。我们希望 3DRealCar 可以成为促进汽车相关任务的宝贵资源。 理想汽车提出3DRealCar&#xff1a;首个大规模3D真实汽车数据集! 我们精心策划的高质量3DRealCar数…...

HTML5文旅文化旅游网站模板源码

文章目录 1.设计来源文旅宣传1.1 登录界面演示1.2 注册界面演示1.3 首页界面演示1.4 文旅之行界面演示1.5 文旅之行文章内容界面演示1.6 关于我们界面演示1.7 文旅博客界面演示1.8 文旅博客文章内容界面演示1.9 联系我们界面演示 2.效果和源码2.1 动态效果2.2 源代码2.3 源码目…...

山东大学多核并行2024年回忆版

2024.6.13回忆版 矩阵向量乘不可整除代码 集合通信与点对点通信的区别 块划分、循环划分、循环块划分&#xff08;14个向量&#xff0c;4个进程&#xff09; 按行访问还是按列访问快 SISD系统问题 循环依赖问题 问题&#xff1a;为什么不能对这个循环并行化&#xff0…...

CentOS 7 上搭建 JavaEE 环境

CentOS 7 上搭建 JavaEE 环境 安装 Java 环境 1&#xff09;检查系统中是否已安装 Java java -version如果未安装&#xff0c;将返回提示信息。 2&#xff09;安装 Java 8 sudo yum install java-1.8.0-openjdk3&#xff09;配置 Java 环境变量&#xff0c;编辑 /etc/prof…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

LeetCode - 394. 字符串解码

题目 394. 字符串解码 - 力扣&#xff08;LeetCode&#xff09; 思路 使用两个栈&#xff1a;一个存储重复次数&#xff0c;一个存储字符串 遍历输入字符串&#xff1a; 数字处理&#xff1a;遇到数字时&#xff0c;累积计算重复次数左括号处理&#xff1a;保存当前状态&a…...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...

iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈

在日常iOS开发过程中&#xff0c;性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期&#xff0c;开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发&#xff0c;但背后往往隐藏着系统资源调度不当…...

R语言速释制剂QBD解决方案之三

本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...

GruntJS-前端自动化任务运行器从入门到实战

Grunt 完全指南&#xff1a;从入门到实战 一、Grunt 是什么&#xff1f; Grunt是一个基于 Node.js 的前端自动化任务运行器&#xff0c;主要用于自动化执行项目开发中重复性高的任务&#xff0c;例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)

考察一般的三次多项式&#xff0c;以r为参数&#xff1a; p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]&#xff1b; 此多项式的根为&#xff1a; 尽管看起来这个多项式是特殊的&#xff0c;其实一般的三次多项式都是可以通过线性变换化为这个形式…...

基于Java+VUE+MariaDB实现(Web)仿小米商城

仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意&#xff1a;运行前…...

Unity VR/MR开发-VR开发与传统3D开发的差异

视频讲解链接&#xff1a;【XR马斯维】VR/MR开发与传统3D开发的差异【UnityVR/MR开发教程--入门】_哔哩哔哩_bilibili...