游戏引擎详解——图片

news2024/11/25 0:56:41

图片

图片的格式

图片文件格式
png
jpg
纹理压缩格式
ETC1/2
PVRTC
ASTC

图片的属性

图片属性解释
分辨率宽高像素值(pt),如:1024*1024
位深度用来存储像素颜色的值,如RGBA8888,红黄蓝透明度4个维度每个8bit,一共就是32位,一般使用的就是32位,也被称为真彩色
文件大小文件所占用的存储大小

图片的优化

图片的优化分为两种:

    1. 文件大小优化:这种优化会影响到包大小,较小的图片大小对于手机存储容量和网络传输速度和时间会更友好。

一般的优化方式是使用压缩工具如pngquant、tinypng等,直接压缩文件大小。

    1. 图片纹理优化:

图片文件大小压缩,不意味着读到内存中的大小会减少。

一般情况下,以ARPG8888来说,计算一个1024*1024分辨率的图片读到内存中的大小,计算公式为:

1024 * 1024 * 4 * 8 = 33,554,432 bit = 4,194,304 byte ≈ 4 mb

理解起来就是 1024 * 1024 个像素,每个像素有 argb 4个通道,每个通道含有 8 bit数据用来存储颜色值。

png格式不能直接被GPU识别,需要在cpu把图片读进内存中解码后,再传递给gpu使用,这样做会造成一定的cpu消耗和很大的瞬时运行内存(RAM)占用。

因为大部分gpu对于压缩后的纹理有比较好的支持,无需cpu解码,占用内存小。于是我们要寻找一种合适的纹理压缩方式。

  • etc1不支持透明通道。
  • etc2效果很差,容易出现色块。
  • PVRTC仅能在ios上使用.

这时候astc格式就展露在我们眼前,IOS和安卓端都对astc有较好的支持率。

  • iPhone6及iPad mini 4以上iOS设备支持。
  • 大多数支持OpenGL ES 3.1或Vulkan的现代Android GPU也支持ASTC格式,其中包括:自Adreno 4xx / Snapdragon 415(2015年)起的高通GPU,自Mali T624(2012年)起的ARM GPU,自Tegra K1(2014年)起的NVIDIA GPU,以及自GX6250(2014年)起的PowerVR GPU。

在运行内存中来说,astc的内存使用能节省75%左右,提升巨大。

cocoscreator中源码理解

creator引擎在编译时会把选中的纹理压缩类型记录到import文件夹下的json文件中,多种图片格式以下划线‘_’区分开,下图演示里选择了astc6x6格式和webp格式,所以生成的类型就是
在这里插入图片描述

代码中在读取文件格式时,会根据列举的文件类型判断当前机器是否支持。由下面代码图里能够看到,7@34这里的7代表的就是第7个.astc,4代表的就是第4个.webp。若当前设备不支持astc就会去寻找webp格式。

在这里插入图片描述

cocoscreator中源码整个下载流程的理解(建议配合cocos2.4.11源码享用)

  1. 一切的一切都要从bundle.js这个类说起,类中的load函数会调用cc.assetManager.loadAny函数来下载相关资源并获取资源数据返回。
    ,传递的参数是资源名paths,类型type,进度回调函数onProgress,完成回调函数onComplete。
    在这里插入图片描述

  2. cc.assetManager是CCAssetManager类,这个类里有一个loadAny函数,此函数简单封装了一个Task并把task传递到pipeline中。这里的pipeline是定义在shared.js中的new Pipeline(‘normal load’, [])一个名字叫做normal load的管线,异步执行任务,管线填充在CCAssetManager类中pipeline.append(preprocess).append(load);包含一个preprocess和load方法。

task的状态为:
{
	input: 资源名,
	onProgress: onProgress,
	onComplete: onComplete,
	options: {
		preset: 'default',
		__requestType__: 'path',
		type: type,
		bundle: bundle名,
		__outputAsArray__: false,
	}
}

在这里插入图片描述
4. normal load–pipeline先调用preprocess,这个函数里对Task的options属性做遍历,这里也就是preset、requestType、type、bundle和__outputAsArray__五个key。
requestType、type、bundle和preset键值对保存到subOptions对象中,__outputAsArray__和preset保存到leftOptions对象中,并覆盖掉task的options属性。
新建一个subTask,input为task.input,options为subOptions对象,走transformPipeline管线流程同步执行任务,获取的值设置为task.source和task.output。最后调用done()。

task的状态为:
{
	input: 资源名,
	onProgress: onProgress,
	onComplete: onComplete,
	options: {
		preset: 'default',
		__outputAsArray__: false,
	},
	output: ,
	source: ,
}
subTask的状态为:
{
	input: 资源名,
	options: {
		preset: 'default',
		__requestType__: 'path',
		type: type,
		bundle: bundle名,
	}
}

在这里插入图片描述
5. transformPipeline里包含两个函数parse和combine。

  • parse管线任务,先判断input,若input为字符串,新建一个无原型链对象item,把item对象的【options.__requestType__值】,也就是【RequestType.PATH】,也就是【‘path’】设置为input值,把options中的其他键值对都复制到item中,这里包含四个键值对,requestType、type、bundle和path。
    再遍历item,这里只有path这个key有处理,其他key都过滤了,通过bundle的_config找item.path值的info。这里的item.path也就是我们一开始传进来的资源名。info里包含了资源的uuid。
    把config、info和uuid值填入到out对象中,并把out对象push到task.output中。
    管线会把task.output值赋值给task.input。
subTask的状态为:
{
	options: {
		preset: 'default',
		__requestType__: 'path',
		type: type,
		bundle: bundle名,
	},
	input: [{
		config: bundle所有配置,
		uuid: 资源的uuid,
		info: {path: path, uuid: uuid},
		ext: '.json',
	}]
}
源码展示:
function parse (task) {

    var input = task.input, options = task.options;
    input = Array.isArray(input) ? input : [ input ];

    task.output = [];
    for (var i = 0; i < input.length; i ++ ) {
        var item = input[i];
        var out = RequestItem.create();
        if (typeof item === 'string') {
            item = Object.create(null);
            item[options.__requestType__ || RequestType.UUID] = input[i];
        }
        if (typeof item === 'object') {
            // local options will overlap glabal options
            cc.js.addon(item, options);
            if (item.preset) {
                cc.js.addon(item, cc.assetManager.presets[item.preset]);
            }
            for (var key in item) {
                switch (key) {
                    case RequestType.UUID: 
                        var uuid = out.uuid = decodeUuid(item.uuid);
                        if (bundles.has(item.bundle)) {
                            var config = bundles.get(item.bundle)._config;
                            var info = config.getAssetInfo(uuid);
                            if (info && info.redirect) {
                                if (!bundles.has(info.redirect)) throw new Error(`Please load bundle ${info.redirect} first`);
                                config = bundles.get(info.redirect)._config;
                                info = config.getAssetInfo(uuid);
                            }
                            out.config = config;
                            out.info = info;
                        }
                        out.ext = item.ext || '.json';
                        break;
                    case '__requestType__':
                    case 'ext': 
                    case 'bundle':
                    case 'preset':
                    case 'type': break;
                    case RequestType.DIR: 
                        if (bundles.has(item.bundle)) {
                            var infos = [];
                            bundles.get(item.bundle)._config.getDirWithPath(item.dir, item.type, infos);
                            for (let i = 0, l = infos.length; i < l; i++) {
                                var info = infos[i];
                                input.push({uuid: info.uuid, __isNative__: false, ext: '.json', bundle: item.bundle});
                            }
                        }
                        out.recycle();
                        out = null;
                        break;
                    case RequestType.PATH: 
                        if (bundles.has(item.bundle)) {
                            var config = bundles.get(item.bundle)._config;
                            var info = config.getInfoWithPath(item.path, item.type);
                            
                            if (info && info.redirect) {
                                if (!bundles.has(info.redirect)) throw new Error(`you need to load bundle ${info.redirect} first`);
                                config = bundles.get(info.redirect)._config;
                                info = config.getAssetInfo(info.uuid);
                            }

                            if (!info) {
                                out.recycle();
                                throw new Error(`Bundle ${item.bundle} doesn't contain ${item.path}`);
                            }
                            out.config = config; 
                            out.uuid = info.uuid;
                            out.info = info;
                        }
                        out.ext = item.ext || '.json';
                        break;
                    case RequestType.SCENE:
                        if (bundles.has(item.bundle)) {
                            var config = bundles.get(item.bundle)._config;
                            var info = config.getSceneInfo(item.scene);
                            
                            if (info && info.redirect) {
                                if (!bundles.has(info.redirect)) throw new Error(`you need to load bundle ${info.redirect} first`);
                                config = bundles.get(info.redirect)._config;
                                info = config.getAssetInfo(info.uuid);
                            }
                            if (!info) {
                                out.recycle();
                                throw new Error(`Bundle ${config.name} doesn't contain scene ${item.scene}`);
                            }
                            out.config = config; 
                            out.uuid = info.uuid;
                            out.info = info;
                        }
                        break;
                    case '__isNative__': 
                        out.isNative = item.__isNative__;
                        break;
                    case RequestType.URL: 
                        out.url = item.url;
                        out.uuid = item.uuid || item.url;
                        out.ext = item.ext || cc.path.extname(item.url);
                        out.isNative = item.__isNative__ !== undefined ? item.__isNative__ : true;
                        break;
                    default: out.options[key] = item[key];
                }
                if (!out) break;
            }
        }
        if (!out) continue;
        task.output.push(out);
        if (!out.uuid && !out.url) throw new Error('Can not parse this input:' + JSON.stringify(item));
    }
    return null;
}
  • combine管线任务,主要是拼出来完整的资源地址。
subTask的状态为:
{
	options: {
		preset: 'default',
		__requestType__: 'path',
		type: type,
		bundle: bundle名,
	},
	output: [{
		config: bundle所有配置,
		uuid: 资源的uuid,
		info:  {path: path, uuid: uuid},
		ext: '.json',
		url: 具体地址,
	}]
}
task的状态为:
{
	input: 资源名,
	onProgress: onProgress,
	onComplete: onComplete,
	options: {
		preset: 'default',
		__outputAsArray__: false,
	},
	output:  [{
		config: bundle所有配置,
		uuid: 资源的uuid,
		info: 具体信息,
		ext: '.json',
		url: 具体地址,
	}],
	source:  [{
		config: bundle所有配置,
		uuid: 资源的uuid,
		info: 具体信息,
		ext: '.json',
		url: 具体地址,
	}],
}
源码展示:
function combine (task) {
    var input = task.output = task.input;
    for (var i = 0; i < input.length; i++) {
        var item = input[i];
        if (item.url) continue;

        var url = '', base = '';
        var config = item.config;
        if (item.isNative) {
            base = (config && config.nativeBase) ? (config.base + config.nativeBase) : cc.assetManager.generalNativeBase;
        } 
        else {
            base = (config && config.importBase) ? (config.base + config.importBase) : cc.assetManager.generalImportBase;
        }

        let uuid = item.uuid;
            
        var ver = '';
        if (item.info) {
            if (item.isNative) {
                ver = item.info.nativeVer ? ('.' + item.info.nativeVer) : '';
            }
            else {
                ver = item.info.ver ? ('.' + item.info.ver) : '';
            }
        }

        // ugly hack, WeChat does not support loading font likes 'myfont.dw213.ttf'. So append hash to directory
        if (item.ext === '.ttf') {
            url = `${base}/${uuid.slice(0, 2)}/${uuid}${ver}/${item.options.__nativeName__}`;
        }
        else {
            url = `${base}/${uuid.slice(0, 2)}/${uuid}${ver}${item.ext}`;
        }
        
        item.url = url;
    }
    return null;
}
  • 管线运行完毕,返回subTask.output
  1. 回到normal load管线任务中的preprocess任务,transformPipeline返回了对象设置成task的output和source值。把output的值赋值给input,再把output设为null,继续下一个normal load管线任务load。
task的状态为:
{
	onProgress: onProgress,
	onComplete: onComplete,
	options: {
		preset: 'default',
		__outputAsArray__: false,
	},
	input:  [{
		config: bundle所有配置,
		uuid: 资源的uuid,
		info:  {path: path, uuid: uuid},
		ext: '.json',
		url: 具体地址,
	}],
	source:  [{
		config: bundle所有配置,
		uuid: 资源的uuid,
		info: 具体信息,
		ext: '.json',
		url: 具体地址,
	}],
}
  1. load函数里给task.options新增了progress和__exclude__字段,progress用来记录资源下载进度。然后遍历每一个task.input,对每一个需要load的资源新建了一个subTask,input值为每一个task的input值,onProgress为task.onProgress,options为task.options,progress为task.progress,onComplete为新的函数,完成时调用,更新progress中的值。再把这个subTask传递到loadOneAssetPipeline下载管道中。
subTask的状态为:
{
	input: {
		config: bundle所有配置,
		uuid: 资源的uuid,
		info:  {path: path, uuid: uuid},
		ext: '.json',
		url: 具体地址,
	},
	onProgress: onProgress,
	onComplete: newComplete,
	options: {
		preset: 'default',
		__outputAsArray__: false,
		__exclude__: {},
	},
}
task的状态为:
{
	onProgress: onProgress,
	onComplete: onComplete,
	progress : { finish: 0, total: task.input.length, canInvoke: true },
	options: {
		preset: 'default',
		__outputAsArray__: false,
		__exclude__: {},
	},
	input:  [{
		config: bundle所有配置,
		uuid: 资源的uuid,
		info: 具体信息,
		ext: '.json',
		url: 具体地址,
	}],
	source:  [{
		config: bundle所有配置,
		uuid: 资源的uuid,
		info: 具体信息,
		ext: '.json',
		url: 具体地址,
	}],
}
源码展示:
function load (task, done) {

    let firstTask = false;
    if (!task.progress) {
        task.progress = { finish: 0, total: task.input.length, canInvoke: true };
        firstTask = true;
    }
    
    var options = task.options, progress = task.progress;

    options.__exclude__ = options.__exclude__ || Object.create(null);

    task.output = [];
    
    forEach(task.input, function (item, cb) {
        let subTask = Task.create({ 
            input: item, 
            onProgress: task.onProgress, 
            options, 
            progress, 
            onComplete: function (err, item) {
                if (err && !task.isFinish) {
                    if (!cc.assetManager.force || firstTask) {
                        if (!CC_EDITOR) {
                            cc.error(err.message, err.stack);
                        }
                        progress.canInvoke = false;
                        done(err);
                    }
                    else {
                        progress.canInvoke && task.dispatch('progress', ++progress.finish, progress.total, item);
                    }
                }
                task.output.push(item);
                subTask.recycle();
                cb();
            }
        });

        loadOneAssetPipeline.async(subTask);

    }, function () {

        options.__exclude__ = null;

        if (task.isFinish) {
            clear(task, true);
            return task.dispatch('error');
        }

        gatherAsset(task);
        clear(task, true);
        done();
    });
}
  1. loadOneAssetPipeline下载单资源管线包含两个管线任务,fetch和parse。
  2. fetch函数调用packManager.load获取资源数据。packManager.load先判断是否在files缓存中有无此id。由于我们在加载资源前必定已经加载了fgui的bin文件,而我们都是选择合并json类型,而bin文件下载时已经把依赖的json文件下载过了,文件值都缓存到files中,这里就直接拿到json信息了,返回值赋值给task的file变量,也就是包含资源类型的json串。进入下一个管线任务parse。parse函数中对未缓存的uuid资源调用parser.parse函数,传递file值。其中又调用parser.parseImport函数,传递file和options;继续调用deserialize函数,传递file和options;继续调用cc.deserialize也就是deserialize-compiled文件中的deserialize函数,传递file和options;继续调用parseInstances;继续调用deserializeCustomCCObject;继续调用对象的_deserialize函数;这里图片对象调用的就是CCTexture2D的_deserialize函数;继续调用Texture2D._parseExt函数;这个_parseExt函数里就做了判断,对资源类型进行解析,并判断设备是否支持纹理类型,支持就返回该后缀。假设这里返回.png后缀,设置Texture2D的_native属性为.png,再设置其他属性,如过滤方案,并返回一个CCTexture2D对象。
  • 这里会衍生到图片的其他属性值,原串是这样的,eg: “0,9729,9729,33071,33071,0,0,1”
  • 第一个0:表示图片的纹理类型,0就是第0个.png。其他类型如下[‘.png’, ‘.jpg’, ‘.jpeg’, ‘.bmp’, ‘.webp’, ‘.pvr’, ‘.pkm’, ‘.astc’],
  • 第二个9729:表示纹理缩小时设定的纹理过滤方案。const GL_NEAREST = 9728;const GL_LINEAR = 9729; 9728是最近点过滤,采样时选择最近的点,成本小,但易产生锯齿。9729是线性过滤,由4个颜色进行加权,这种方法可以让纹理边缘看起来更平滑,但需要进行更多的计算。
  • 第三个9729:表示纹理放大时设定的纹理过滤方案。同上。
  • 第四个33071:表示横轴纹理环绕方式。指定了当纹理坐标超出0到1的标准范围时该如何处理纹理的采样。10497是重复、33071是边缘拉伸、33648是镜像重复。
  • 第五个33071:表示纵轴纹理环绕方式。同上。
  • 第六个0:表示颜色是否预乘,1表示预乘,0表示非预乘。
  • 第七个0:表示是否是否mipmaps。1表示使用,0表示不使用。
  • 第八个1:表示纹理是否参与合图。1表示参与,0表示不参与。
  1. parser.parse完成后调用loadDepends函数,先为Texture2D添加addRef,然后调用getDepends函数,获取依赖,再新建一个任务,走下载管线完成实际图片加载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2061586.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在Windows Server 2012 R2上安装.NET Framework 3.5

在Windows Server 2012 R2上安装.NET Framework 3.5&#xff0c;可以按照以下步骤进行&#xff1a; 打开服务器管理器&#xff1a; 首先&#xff0c;登录到Windows Server 2012 R2的服务器。然后&#xff0c;打开“服务器管理器”。添加角色和功能&#xff1a; 在“服务器管理…

文件传送协议:FTP 的工作原理与应用

在网络环境中&#xff0c;将文件从一台计算机复制到另一台计算机是一项基本应用。文件传送协议&#xff08;File Transfer Protocol, FTP&#xff09; &#xff08;RFC959&#xff09;是互联网上使用最广泛的文件传输协议之一。FTP不仅提供交互式访问&#xff0c;允许用户指定文…

自然语言处理(NLP)大模型

自然语言处理&#xff08;NLP&#xff09;大模型 自然语言处理&#xff08;NLP&#xff09;领域中的一种重要技术&#xff0c;具有强大的语言理解和生成能力。以下是对NLP大模型的详细介绍&#xff1a; 一、定义与背景 NLP大模型是指通过大规模预训练和自监督学习技术构建的…

【STM32】I2C接口(一主多从)

本篇博客重点在于标准库函数的理解与使用&#xff0c;搭建一个框架便于快速开发 目录 前言 I2C外设简介 IO口初始化 I2C接口配置 I2C时钟配置 I2C初始化 I2C接口使能 I2C外设配置框架 主机的发送与接收 主机发送 主机接收 I2C例程 前言 I2C协议介绍&#xff1a;…

进程间的通信3——IPC对象通信->共享内存、网络通信

一、共享内存 1、原理 直接对实际物理内存进行操作&#xff0c;不用先拷贝到用户空间再到内核空间&#xff08;物理内存&#xff09;。 2、特点 &#xff08;1&#xff09;共享内存是一块内核预留的空间&#xff1b; &#xff08;2&#xff09;最高效的通信方式。 3、操作 产…

进阶SpringBoot之 Mybatis 框架

Maven 仓库 导入 mybatis-spring-boot-starter 的 jar 包 <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version> </dependency>新建 Web…

跨平台快递追踪系统共享

物流追踪一站式平台推荐&#xff1a;固乔快递查询助手 在快速发展的电商时代&#xff0c;物流追踪已成为商家和消费者日常不可或缺的一部分。无论是商家需要监控订单状态&#xff0c;还是消费者期待及时了解包裹动向&#xff0c;一个高效、便捷的物流追踪平台都显得尤为重要。…

多模态大模型视觉特征嵌入语言模型特征流程-(以MMLM的forward源码解读)

文章目录 前言一、多模态大模型的forward方法(llava)1、位置2、源码3、模型输入3、图像编码与文本格式输入4、大语言模型5、计算loss6、重点内容提示二、prepare_inputs_labels_for_multimodal关键函数解读1、视觉编码2、input_ids循环遍历处理3、寻找图像位置token-batch循环4…

公开课观后感:密歇根大学python for everyone

从2024年1月17日到2024年8月20日&#xff0c;终于将密歇根大学的python for everyone的python公开课跟完。站在一月份规划的时刻来看&#xff0c;比我想象中花费的时间更多&#xff0c;我当时肯定没有想到要花上整整七个月的时间才能将这个公开课的内容看完&#xff0c;毕竟这个…

【C/C++】菱形继承问题

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

编译 onigmo 库

onigmo github: https://github.com/k-takata/Onigmo 测试环境&#xff1a;Windows 我编译库时习惯于在 vs code 下 git clone 文件后&#xff0c;再执行相应的编译操作 而 vs code 提供的终端一般是 git bash 和 powershell 在编译 windows 下运行的库》.lib 和 .dll 不能直接…

Linux设置内网时间同步

背景&#xff1a;公司有三台服务器检测到同步外网的时间&#xff0c;现需要将其修改为同步公司内网自己搭建的ntp服务器 1、登录服务器检查 同步外网无疑 2、修改配置文件&#xff0c;同步内网ntp服务器时间 配置文件源内容如下&#xff1a; 修改后如下&#xff1a; [rootl…

Redis—持久化机制

Redis持久化机制 1. RDB1.1 实现方式1.2 实现原理 2. AOF2.1 实现方式2.2 AOF文件载入2.3 AOF重写2.4 重写触发 3. RDB vs AOF3.1 RDB3.2 AOF3.3 如何选择&#xff1f; 4. Redis 4.0 混合持久化 Redis的持久化机制有两种持久化机制&#xff0c;分别是 RDB 和 AOF 1. RDB Redi…

Python和MATLAB谐波生成导图

&#x1f3af;要点 绘制三次谐波生成透射功率谱、对数对数图表示半导体曲面二次谐波生成&#xff0c;分析判断材料特性谐波均值估计计算边际似然&#xff08;贝叶斯统计&#xff09;二次谐波散射分析胶体染料分子结构交流电谐波波形傅立叶分析分析旋转各向异性谐波高次谐波非线…

TMGM:7月日本贸易收支可能受到显著走强的日元影响

经济学家和市场参与者预计今年将再次加息美联储可能助推美元/日元的看跌延续 7月日本贸易平衡可能受到显著走强的日元影响7月日本的贸易平衡比预期更差&#xff0c;但赤字大约是5月的一半&#xff0c;约为1月的三分之一。7月进口量增长超出预期&#xff0c;而较强的日元可能影…

模型 闭环原理

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。反馈驱动&#xff0c;持续循环&#xff0c;缺陷亦被放大。 1 闭环原理的应用 1.1 闭环原理解读 AI自我训练&#xff0c;从人工智能变成人工智障 这里主要使用闭环原理来解释 AI 自我训练导致的问题。…

基于STM32F103的FreeRTOS系列(十一)·信号量·二值信号量与计数信号量详细使用以及移植教程

目录 1. 信号量简介 1.1 同步和互斥 1.1.1 同步 1.1.2 互斥 1.1.3 总结 1.2 分类 1.2.1 二值信号量 1.2.2 计数信号量 1.2.3 互斥信号量 1.2.4 递归信号量 2. 信号量控制块 3. 常用信号量API函数 3.1 创建信号量函数 3.1.1 创建二值信号量 xSemap…

实验七:独立按键实验

硬件电路图和题目; LED1-LD8是 P2口8个管脚 mian.c #include<reg52.h>sbit But1=P3^1 ; sbit But2=P3^0 ; sbit But3=P3^2 ; sbit But4=P3^3 ;sbit LED1 =P2^0 ; sbit LED2 =P2^1 ; sbit LED3 =P2^2 ; sbit LED4 =P2^3 ;#define PRESS_1 1 #define PRESS_…

数据库多表设计:深入理解一对多、一对一、多对多关系 【后端 12】

数据库多表设计&#xff1a;深入理解一对多、一对一、多对多关系 在数据库设计中&#xff0c;表之间的关系决定了如何组织和存储数据。常见的表关系包括一对多、一对一和多对多。在不同的业务场景下&#xff0c;我们会选择不同的关系模式进行数据库设计。本文将通过具体案例介绍…

linux Qt QkeyEvent及驱动键盘按键捕获

基于正点原子 QT中有专门的类处理键盘事件的类QKeyEvent 1.include “QKeyEvent” 查看它的说明中的描述 也就是说接受按键事件在keyPressEvent和keyReleaseEvent这两个函数&#xff0c;继续查看 重构这个函数 查看输入的QKeyEvent类&#xff0c;发现有一个方法key返回哪一个按…