通常我在进行不同平台的设置时会基于以下几点:
1:创建、开发、打包时我们通常针对Player和Quality设置进行质量的设定
2:在不同平台上运行时,有不同的平台包体大小,加载方式的限定,测试、打包上的区别,帧率稳定60
3:代码封装上的区别,特别针对单一项目转为不同不同平台的问题
4:输入系统的不同
一、PC
PC硬件相对比较强大,所以对于内存大小,贴图质量,模型面数的限制没有其他平台那么大,PC和其他平台的Input系统也有所差别,PC端更加注重画面质量,细节,包括灯光、渲染等问题,所以我通常在开发PC版本的项目时,第一考虑在开发目的,开发需求都能满足的前提下,如何在帧率稳定的前提下,将画面的质量提升到最好,当然美术给的资源是确定的,只能使用Profier对占用资源比较多的部分进行优化
1:针对Player Setting、Quality设置
PlayerSetting如下,我会针对如下的所有选项进行详解
1:PlayerSetting
Resolution
FullscreenMode:针对目标软件是哪种全屏模式
Fullscreen Window:通常的选项,默认的全屏模式,始终以桌面分辨率运行,其他请求的分辨率将进行缩放以适合此窗口
Exlusive FullScreen:此模式会更改显示器的操作系统分辨率来匹配应用程序选择的分辨率,设置应用程序以保持对显示器的单独全屏使用
Maximized Window(Mac):Mac Only
Windowed:窗口模式
Defult is Native Resolution:是否自动分配分辨率
Run In BackGround:后台运行,如果为False,则比如OnApplicationPause则不起作用
Standalone Player Options
Capture Single Screen:Unity定义是全屏游戏是否应使辅助显示变暗。
Use Player Log:向日志文件写入调试信息,一般路径是C:\user\用户\AppData\LocalLow\CompanyName\ProjectName找到日志文件
Resizable Window:在独立平台播放器构建中使用可调节大小的窗口
Visible In Background:与全屏窗口模式结合使用,如果使用全屏窗口模式,可在后台显示应用程序
Allow Fullscreen Switch:允许用户使用特定于操作系统的键盘快捷键在全屏模式和窗口模式之间切换
Force Single Instance:防止应用程序被运行两次
Use DXHI flip model swapchain for D3D11:翻转模型可确保最佳性能。禁用它可回退到 Windows 7 样式的 BltBlt 模型
D3D11是Direct的图形API,DXHI可以共享一份swap chain的代码,管理屏幕输出的部分
Suported Aspect Rations:宽高比适配器
*Shared setting between multiple platforms.针对上述设置,切换平台后仍然生效
Other Setting
ColorSpace:一般PC首选Linear的色彩空间,它的优点是随着光强增加,着色器的颜色会线性变亮,且Linear的显示效果更好,一些移动设备不支持Linear的色彩空间
Auto Graphics API for Windows/Mac/Linux:图形渲染API,如果对渲染的流程或者有特殊的渲染需求,效果,可以进行自己选择,一般情况下自动选择
Static Batching:静态批处理,通过把物体设置为Static或者Bathing Static,将静态物体合并为一个大网格,从而以更快的速度渲染它们,这里面会牵扯到一个DrawCall的概念,简单来说,DrawCall就是CPU先准备好模型的数据(包括模型各个顶点的坐标、法线方向、纹理等),然后CPU告诉GPU,如果我们需要渲染1000个三角形,那么把它们按1000个单独的网格进行渲染(1000次DrawCall)所花费的时间要远大于>>直接渲染1个包含了1000个三角形的网格(1次DrawCall)。使用批处理,可以减少DrawCall,这就是静态批处理的概念, 当然还有动态批处理
GPU Skinning:简单的来说就是GPU蒙皮,蒙皮是指将Mesh中的顶点附着(绑定)在骨骼之上,而且每个顶点可以被多个骨骼所控制,这样在关节处的顶点由于同时受到父子骨骼的拉扯而改变位置就消除了裂缝。
说明:此操作只支持VR应用,仅在项目中选择支持虚拟现实时才起作用
这里讲的很详细:Unity的GPUSkinning进一步介绍_gpu skin-CSDN博客
Graphics Jobs:用来决定Unity是否使用工作线程来承担渲染任务。如果不勾选此项,这些工作将由主线程或渲染线程承担。如果平台支持这个选项,它将会带来较大的性能提升。如果我们希望开启这个特性,我们应该对比开启和不开启的情况的性能表现。
Texture compression format:压缩方式是有特定的压缩算法的,DXT是PC端常用的压缩算法,质量比较低,一般的压缩算法如下:
ETC是Android平台通用的压缩格式,质量较低
ASTC是Android和IOS平台下的一种高质量压缩方式,支持Android5.0和iPhone6以上机型
具体的可以参照各平台的推荐、默认和支持的纹理压缩格式 - Unity 手册
Normal Map Encoding:法线贴图的编码方式
Lightmap Encoding:用于控制光照贴图的编码/压缩,选择 High Quality 将启用 HDR 光照贴图支持,而 Normal Quality 将切换为使用 RGBM 编码。 在移动平台上,__Low Quality__ 将切换为 dLDR 编码,在其他平台上相当于 Normal Quality。
在桌面平台和游戏主机平台上,Lighting 窗口中启用了光照贴图 Compression 时,将使用 BC6H 压缩格式来压缩光照贴图。对于移动平台,Unity 根据下表选择 HDR 格式,具体的参数可以参照官方手册
Lightmap Streaming:启用光照贴图流,在启用的情况下,可仅在需要时加载光照贴图mipmap。要渲染当前的游戏摄影机,Unity会在生成纹理时将该值应用于光照贴图纹理。
Frame Timing Stats:启用帧计时统计,启用后,播放器会收集 CPU 和 GPU 帧计时统计信息。可以使用 FrameTimingManager 类来访问这些统计信息。
OpenGL:Profiler GPU Recorders:GPU Usage Profiler 模块显示应用程序的 GPU 时间使用情况。只能在运行模式 (Playmode) 下使用 GPU Profiler 或者用于应用程序的构建。不能用于对 Editor 进行性能分析。
注意:如果在 Player Settings 中启用了 __Graphics Jobs__,则不支持 GPU 性能分析。
Vulkan Setting/MacApp Store Options/Mac Configuration:暂时一直用GL的API和PC开发,这些设置没有用到
Scripting Backend:脚本后处理(脚本编译模式),可以选择Mono和IL2CPP,要彻底了解这两个选项,就得从跨平台Mono说起,它的工作流程是把C#编译成IL中间语言,然后通过Mono运行时中的编译器将IL编译成对应平台的原生代码
转译方式有三种,即时编译(Just In Time,JIT),程序运行过程中,将CIL的byte code转译为目标平台的原生码。提前编译(Ahead of time,AOT),程序运行之前,将.exe或.dll文件中的CIL的byte code部分转译为目标平台的原生码并且存储。
从官方文档可以看出两者的区别:
- Mono uses just-in-time (JIT) compilation and compiles code on demand at runtime.
- IL2CPP uses ahead-of-time (AOT) compilation and compiles your entire application before it is run.
(图片来源:【Unity游戏开发】Mono和IL2CPP的区别 - 知乎 (zhihu.com))
从上面两个图,我们可以看出使用Mono和IL2CPP的区别,Mono在游戏运行的时候,IL和项目里其他第三方兼容的DLL一起,放入Mono VM虚拟机,由虚拟机解析成机器码,而IL2CPP将他们重新变回C++代码,然后再由各个平台的C++编译器直接编译成能执行的原生汇编代码。
所以大致的区别也如下:
- 相比Mono, 代码生成有很大的提高
- 可以调试生成的C++代码
- 可以启用引擎代码剥离(Engine code stripping)来减少代码的大小
- 程序的运行效率比Mono高,运行速度快
- 多平台移植非常方便
- 相比Mono构建应用慢
- 只支持AOT(Ahead of Time)编译
IL2CPP比较适合开发和发布项目 ,但是为了提高版本迭代速度,可以在开发期间切换到Mono模式(构建应用快)
Api Compatiblity Level:参见微软官方的介绍在 Unity 中使用 .NET 4.x | Microsoft Learn
-
.NET Standard 2.1。 此配置文件与 .NET Foundation 发布的 .NET Standard 2.1 配置文件匹配。 Unity 建议新项目使用 .NET Standard 2.1。 它比 .NET 4.x 小,有利于尺寸受限的平台。 此外,Unity 承诺在 Unity 支持的所有平台上支持此配置文件。
-
.NET Framework。 此配置文件提供对最新 .NET 4 API 的访问权限。 它包括 .NET Framework 类库中提供的所有代码,并且支持 .NET Standard 2.1 配置文件。 如果 .NET Standard 2.0 配置文件中未包含项目所需的部分 API,请使用 .NET 4.x 配置文件。 但此 API 的某些部分并非在所有 Unity 平台上均受支持。
Use Incremental GC:增量式垃圾回收,有关这个选项,这篇博文讲的很清楚
unity 增量式GC_unity 增量gc_红黑色的圣西罗的博客-CSDN博客
Assembly Version Validation:when Mono Resolve types from a assembly that is strong Named,Versions have to match with the one already loaded.当 Mono Resolve 从强命名的程序集键入时,版本必须与已加载的版本匹配。
Active Input Handling:选择新的输入系统还是旧的输入系统,还是两者都允许
Shader precision model:Shader精度模型,官方有两种,一种默认的,一种全精度
PlatformDefault Use the target platform defaults for sampler precision. This results in lower precision on mobile targets and full precision elsewhere. Unified Use full sampler precision by default and make it so you have to explicitly declare when you want to use lower precision. This sets BuiltinShaderDefine.UNITY_UNIFIED_SHADER_PRECISION_MODEL when Unity compiles shaders. Keep Loaded Shaders Alive:一般是启用的,因为Shader的加载和解析很耗时,所以不希望Shader被卸载
Defuult chunk size:可以使用 PlayerSettings.SetDefaultShaderChunkSizeInMB 来限制压缩块的大小。
Default chunk count:同上,用来压缩Shader精度的,一般默认
suppress Coommon Warnings:Disable this setting to display the C# warnings CS0169 and CS0649.
Allow unsafe Code:允许在预定义的程序集(例如,Assembly-CSharp.dll)中编译“不安全”的 C# 代码。
Use Deterministic Compilation:启用此设置后,每次编译程序集时,它们都是逐字节相同的。
Enable Roslyn Analyzers:
Prebake Collision Meshes:启用此选项可在构建时将碰撞数据添加到网格中。
Managed Stripping Level:这个功能的主要目的则是通过删除一些未使用的代码来减小应用程序的大小, the Unity Linker process can strip unused code from the managed DLLs your Project uses. Stripping code can make the resulting executable significantly smaller, but can sometimes accidentally remove code that is in use,就是说有可能会把你正在运行的代码也移除
,所以默认情况是Disable的
Vertex Compression:顶点压缩,可以自定义选择
Optimize Mesh Data:选择此选项将从构建中使用的网格中剥离未使用的顶点属性。
这减少了网格中的数据量,这可能有助于减少构建大小、加载时间和运行时内存使用量。但是,如果启用了此设置,则必须记住不要在运行时更改材质或着色器设置,但是这个和减少代码量可能存在一样的问题,导致的 Mesh 顶点数据的丢失,所以一般禁用
Texture MipMap Stripping:MipMap是分级细化纹理,也可以理解为LOD(Level of Detail),
拿这两张图片做比较,左边是开启了MipMap,右边是没有开启MipMap,当我们开启MipMap时,摄像机远处的纹理变模糊了。而右边关闭MipMap的纹理,远处没有模糊,这里这个功能也是移除没有使用的贴图Level以减少包体的大小,一般也是不开启的
Stack Trace:输出堆栈信息,包括如下选项
- None: Unity doesn’t output stack trace information.
- ScriptOnly: Unity outputs stack trace information only for managed code. This is the default option.
- Full: Unity outputs stack trace information for both managed and unmanaged code.
2:Quality Setting
Quality - Unity 手册
这里可以选择配置文件,渲染管线,贴图质量等
Anisotropic Textures:各向异性纹理,选择 Unity 是否以及如何使用各向异性纹理。选项包括 Disabled、Per Texture 和 _Forced On_(即始终启用)。
Particle Raycast Budget:设置用于模拟粒子系统碰撞的最大射线投射数
Billboards Face Camera Position:公告牌将面向摄像机位置而不是摄像机方向
Shadowmask Mode:ShadowMask和Distance ShadowMask,前者是默认的阴影遮罩,后者Distance Shadowmask__是 Shadowmask(阴影遮罩)__光照模式的一个版本。该模式由场景中的所有混合光源共用,可提供从静态游戏对象投射到动态游戏对象的高质量阴影,提供更高性能的平台才能够使用此选项
Asyne Asset Upload:
LOD(细节级别):
Lod Bias:设置细节级别 (LOD) 偏差。根据对象在屏幕上的大小选择 LOD 级别。当大小在两个 LOD 级别之间时,可以偏向于两个可用模型中细节级别更低或更高者。此属性设置为 0 到 +无穷大之间的值。设置为 0 到 1 之间时,表示倾向于更少细节。超过 1 的设置表示倾向于更多细节。例如,将 LOD Bias 设置为 2 并使其在 50% 距离处变化,LOD 实际上仅基于 25% 变化。
Maximum LOD Level:设置游戏使用的最高 LOD
Skin Weights:选择在动画期间可以影响给定顶点的骨骼数量。可用选项包括 1 Bone、2 Bones、4 Bones 和 Unlimited。
2:包体大小限定,加载方式
PC打包的包体大小一般没有特定的限制,除非对于特定的安装包有大小的限定或者发布平台的限制,一般是追求最高画质,但是需要提前预留好不同画质下的贴图质量以保证不同PC设备的流畅运行
PC的加载方式和Web和安卓有些,一般都是提前打包好所有资源,这样可以保证流畅加载
3:代码的封装问题
针对PC没有特定的规范,只要耦合性好,扩展性好,有逻辑层次,都没有太大问题,但是当你的PC项目需要转化到Android的时候,就需要提前把适配不同平台的代码准备和封装好
4:PC测试、打包
一般情况PC的调试比较方便,可以使用Development Build,或者查看Log日志即可
二、Android
1:针对PlayerSetting、Quality
Android Player 设置 - Unity 手册
这里面的设置基本和我上面介绍的没有什么差异,基本差别在于
ColorSpace、LightmapEncoding(硬性要求)、Texture Compression
安卓多了Minimum API Level、Target API Level,需要适配不同机型,基本上最低我会选择到Android5.1,最高是Autimatic
2:包体大小限定,加载方式,测试
安卓项目对于包体大小有严格的限定,我一般选择ASTC压缩方式,包体大小会小很多
测试使用Development Build,Build and Run,Android Studio这三种进行测试
3:输入方式
我采用了EasyTouch插件,一般移动端有比如单击,双击,滑动屏幕等操作,使用的代码也不是很复杂
双击/长按
if (Input.GetMouseButton(0)) { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { #region 双击 if (Input.touchCount == 1 && Input.GetTouch(0).phase == TouchPhase.Began)//判断几个点击位置而且是最开始点击的屏幕,而不是滑动屏幕 { if (Input.GetTouch(0).tapCount == 2)//tapcount是点击次数 { Destroy(hit.collider.gameObject); } } #endregion #region 长按 if (Input.touchCount == 1) { Touch touch = Input.GetTouch(0); if (touch.phase == TouchPhase.Began) { newTouch = true; touchTime = Time.time; } else if (touch.phase == TouchPhase.Stationary)//点击没有滑动的时候会触发Stationary { if (newTouch == true && Time.time - touchTime > 1f) { newTouch = false; Destroy(hit.collider.gameObject); } } else { newTouch = false; } } #endregion }
滑动屏幕
if (Input.GetMouseButton(0)) { if (Input.touchCount==1) { if (Input.GetTouch(0).phase==TouchPhase.Moved)//滑动状态 { transform.Rotate(Vector3.up * Input.GetAxis("Mouse X") * -xSpeed * Time.deltaTime,Space.World); } } }
双指缩放等
///
/// 判断手势 /// /// 开始的第一个点 /// 开始的第二个点 /// 结束的第一个点 /// 结束的第二个点 ///bool isEnlarge(Vector2 op1, Vector2 op2, Vector2 np1, Vector2 np2) { float startLength = Mathf.Sqrt((op1.x - op2.x) * (op1.x - op2.x)+ (op1.y - op2.y) * (op1.y - op2.y)); float endLength = Mathf.Sqrt((np1.x - np2.x) * (np1.x - np2.x) + (np1.y - np2.y) * (np1.y - np2.y)); if (startLength 三、VR
VR也是基于Android平台,不同的是,它需要使用基于不同公司开发的SDK来接入项目
对于如何配置一个VR项目,我之前的文章有介绍到
2023—Unity打包Pico4(3)全流程(Pico插件)-CSDN博客
1:PlayerSetting、Quality
VR对美术需求更高,比如如何制作很好的贴图,能适应性能的同时有很好的展现形式,灯光尽量也全部使用烘焙,需要多调试
2:包体大小,加载方式,测试
前面两项基本相同,但是测试上,对于比较新的Pico SDK(2.1.3以上),可以使用Development Center+PICO Unity Live Preview Plugin进行串流测试,但是实测效果并不理想
如果使用2.1.2/2.1.3可以两端安装Preview Tool进行串流测试,效果比较稳定
3:输入方式
VR可以用如下脚本进行监听,在其他脚本的OnEnable进行监听即可
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.XR; ///
/// 提供各种输入事件 /// public class InputEvent { private static InputEvent _instance; private static object lockObj = new object(); //提供一个锁对象,以确保同时只有一个线程能够执行相关代码块 public static InputEvent Instance { get { lock (lockObj) { if (_instance == null) { _instance = new InputEvent(); } } return _instance; } } private InputEvent() { Init(); } //*************输入设别************************** InputDevice leftHandController; InputDevice rightHandController; InputDevice headController; //**************对外提供公开事件****************** #region public event public event Action onLeftTriggerEnter; public event Action onLeftTriggerDown; public event Action onLeftTriggerUp; public event Action onRightTriggerEnter; public event Action onRightTriggerDown; public event Action onRightTriggerUp; public event Action onLeftGripEnter; public event Action onLeftGripDown; public event Action onLeftGripUp; public event Action onRightGripEnter; public event Action onRightGripDown; public event Action onRightGripUp; public event Action onLeftAppButtonEnter; public event Action onLeftAppButtonDown; public event Action onLeftAppButtonUp; public event Action onRightAppButtonEnter; public event Action onRightAppButtonDown; public event Action onRightAppButtonUp; public event Action onLeftJoyStickEnter; public event Action onLeftJoyStickDown; public event Action onLeftJoyStickUp; public event Action onRightJoyStickEnter; public event Action onRightJoyStickDown; public event Action onRightJoyStickUp; public event ActiononLeftJoyStickMove; public event Action onRightJoyStickMove; public event Action onLeftAXButtonEnter; public event Action onLeftAXButtonDown; public event Action onLeftAXButtonUp; public event Action onLeftBYButtonEnter; public event Action onLeftBYButtonDown; public event Action onLeftBYButonUp; public event Action onRightAXButtonEnter; public event Action onRightAXButtonDown; public event Action onRightAXButtonUp; public event Action onRightBYButtonEnter; public event Action onRightBYButtonDown; public event Action onRightBYButtonUp; #endregion //提供状态字典独立记录各个feature的状态 Dictionary stateDic = new Dictionary (); //单例模式提供的初始化函数 // // 每个场景启动调用一次,不然保存的可能还是上一个场景的Controller public void Init() { leftHandController = InputDevices.GetDeviceAtXRNode(XRNode.LeftHand); rightHandController = InputDevices.GetDeviceAtXRNode(XRNode.RightHand); headController = InputDevices.GetDeviceAtXRNode(XRNode.Head); //stateDic = new Dictionary (); } //*******************事件源的触发************************** /// /// 按钮事件源触发模板 /// /// 设备 /// 功能特征 /// 开始按下按钮事件 /// 按下按钮事件 /// 抬起按钮事件 private void ButtonDispatchModel(InputDevice device, InputFeatureUsageusage, Action btnEnter, Action btnDown, Action btnUp) { //Debug.Log("usage:" + usage.name); //为首次执行的feature添加bool状态 -- 用以判断Enter和Up状态 string featureKey = device.name + usage.name; if (!stateDic.ContainsKey(featureKey)) { stateDic.Add(featureKey, false); } bool isDown; if (device.TryGetFeatureValue(usage, out isDown) && isDown) { if (!stateDic[featureKey]) { stateDic[featureKey] = true; if (btnEnter != null) btnEnter(); } if (btnDown != null) btnDown(); } else { if (stateDic[featureKey]) { if (btnUp != null) btnUp(); stateDic[featureKey] = false; } } } /// /// 摇杆事件源触发模板 /// /// 设备 /// 功能特征 /// 移动摇杆事件 private void JoyStickDispatchModel(InputDevice device, InputFeatureUsageusage, Action joyStickMove) { Vector2 axis; if (device.TryGetFeatureValue(usage, out axis) && !axis.Equals(Vector2.zero)) { //Debug.Log("move " + axis); if (joyStickMove != null) joyStickMove(axis); } } //******************每帧轮询监听事件*********************** public void onUpdate() { if (!rightHandController.isValid && !leftHandController.isValid) { Init(); } ButtonDispatchModel(leftHandController, CommonUsages.triggerButton, onLeftTriggerEnter, onLeftTriggerDown, onLeftTriggerUp); ButtonDispatchModel(rightHandController, CommonUsages.triggerButton, onRightTriggerEnter, onRightTriggerDown, onRightTriggerUp); ButtonDispatchModel(leftHandController, CommonUsages.gripButton, onLeftGripEnter, onLeftGripDown, onLeftGripUp); ButtonDispatchModel(rightHandController, CommonUsages.gripButton, onRightGripEnter, onRightGripDown, onRightGripUp); ButtonDispatchModel(leftHandController, CommonUsages.primaryButton, onLeftAXButtonEnter, onLeftAXButtonDown, onLeftAXButtonUp); ButtonDispatchModel(rightHandController, CommonUsages.primaryButton, onRightAXButtonEnter, onRightAXButtonDown, onRightAXButtonUp); ButtonDispatchModel(leftHandController, CommonUsages.secondaryButton, onLeftBYButtonEnter, onLeftBYButtonDown, onLeftBYButonUp); ButtonDispatchModel(rightHandController, CommonUsages.secondaryButton, onRightBYButtonEnter, onRightBYButtonDown, onRightBYButtonUp); ButtonDispatchModel(leftHandController, CommonUsages.primary2DAxisClick, onLeftJoyStickEnter, onLeftJoyStickDown, onLeftJoyStickUp); ButtonDispatchModel(rightHandController, CommonUsages.primary2DAxisClick, onRightJoyStickEnter, onRightJoyStickDown, onRightJoyStickUp); ButtonDispatchModel(leftHandController, CommonUsages.menuButton, onLeftAppButtonEnter, onLeftAppButtonDown, onLeftAppButtonUp); ButtonDispatchModel(rightHandController, CommonUsages.menuButton, onRightAppButtonEnter, onRightAppButtonDown, onRightAppButtonUp); JoyStickDispatchModel(leftHandController, CommonUsages.primary2DAxis, onLeftJoyStickMove); JoyStickDispatchModel(rightHandController, CommonUsages.primary2DAxis, onRightJoyStickMove); } } 四、WebGL
1:PlayerSetting、Quality
因为WebGL的特性,所以贴图质量和模型精度不能太高,不然包体会比较大,所以必须舍弃其中之一,要么是展现,要么是速度
2:大小限定,加载方式,测试
WebGL的包体最好控制在200M以内,这样上传到服务器后,加载的压力比较小
所以对于WebGL项目最好采用远程加载的方式,可以使用AB包或者Addressable加载方式,关于Addressable加载,可以参照林新发的文章【精选】【游戏开发探究】Unity Addressables资源管理方式用起来太爽了,资源打包、加载、热更变得如此轻松(Addressable Asset System | 简称AA)_player content must be built before entering play -CSDN博客
WebGL的测试也比较繁琐,必须网站运行出来才可以看到,所以采用Development的打包方式
3:Inputsystem系统和PC基本一样
参考:
WebGL Player 设置 - Unity 手册
Quality - Unity 手册
Player - Unity 手册(PC)
Android Player 设置 - Unity 手册
猜你喜欢
- 6天前Unity-Timeline制作动画(快来制作属于你的动画吧)
- 6天前C# .Net Framework webapi 当配置模型验证
- 6天前【鸿蒙应用ArkTS开发系列】- http网络库使用讲解和封装
- 6天前.NET 8.0 AOT 教程 和使用 和 .NET ORM 操作
- 6天前.Net7 环境安装配置
- 6天前QT 打开项目时显示 “No valid settings file could be found” 错误信息
- 6天前回归预测 | Matlab实现CPO-LSTM【24年新算法】冠豪猪优化长短期记忆神经网络多变量回归预测
- 6天前erlang (erlang 操作模块)学习笔记(一)
- 6天前提高后进生的方法和措施
- 6天前Unity 编辑器篇|(十三)自定义属性绘制器(PropertyDrawer ,PropertyAttribute) (全面总结 | 建议收藏)
网友评论
- 搜索
- 最新文章
- 热门文章