基于ScriptableObject设计游戏数据表

前言

本篇文章是针对之前对于ScriptableObject概念讲解的实际应用之一,在游戏开发中,我们可以使用该类来设计编辑器时的可读写数据表或者运行时的只读数据表。本文将针对运行时的只读数据表的应用进行探索,并且结合自定义的本地持久化存储方式使得基于ScriptableObject开发的数据表能够在运行时进行读写。

代码

代码目录结构
  • Table
    • Base
    • Editor
    • Interface
    • Unit

Table则为本模块的根目录,存储各个游戏数据表的脚本,Base目录存储数据表和游戏表基类,Editor目录存储数据表的编辑器脚本,Interface目录存储数据表和数据单元接口,Unit目录存储数据单元。

Base目录 

BaseTable.cs

using System;
using UnityEngine;/// <summary>
/// 基础表
/// </summary>
public abstract class BaseTable : ScriptableObject
{/// <summary>/// 表类型/// </summary>public abstract Type mType { get; }
}

GameTable.cs

using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Text;
using UnityEngine;/// <summary>
/// 游戏表
/// </summary>
/// <typeparam name="T0">表类型</typeparam>
/// <typeparam name="T1">表单元类型</typeparam>
public class GameTable<T0, T1> : BaseTable, ITableHandler<T0, T1>
where T0 : GameTable<T0, T1>
where T1 : ITableUnit
{[Tooltip("是否自动控制加载和保存")] public bool isAutoControl = true;[HideInInspector, SerializeField] protected T1[] units;#if UNITY_EDITOR
#pragma warning disable CS0414[HideInInspector, SerializeField] bool isAutoSave = true;
#pragma warning restore CS0414
#endifpublic sealed override Type mType => typeof(T0);public ReadOnlyCollection<T1> mUnits => Array.AsReadOnly(wrapper.value);public int mCount => wrapper.value == null ? 0 : wrapper.value.Length;public event Action<T1> mModifiedCallback;protected string jsonPath;protected TempWrapper<T1[]> wrapper;/// <summary>/// 保存到本地/// </summary>public virtual void SaveLocally(){if (Application.isEditor) return;wrapper.UnWrapByBinary(ref units);string jsonStr = JsonUtility.ToJson(this);if (!string.IsNullOrEmpty(jsonStr)){string dirPath = Path.GetDirectoryName(jsonPath);if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath);if (!File.Exists(jsonPath)) File.Create(jsonPath).Dispose();using (FileStream fs = new FileStream(jsonPath, FileMode.OpenOrCreate, FileAccess.Write)){byte[] bytes = Encoding.UTF8.GetBytes(jsonStr);fs.Write(bytes, 0, bytes.Length);fs.Flush();fs.Close();}}}/// <summary>/// 从本地加载/// </summary>public virtual void LoadFromLoacl(){if (Application.isEditor) return;if (File.Exists(jsonPath)){using (TextReader tr = new StreamReader(jsonPath, Encoding.UTF8)){string jsonStr = tr.ReadToEnd();if (!string.IsNullOrEmpty(jsonStr)){try{JsonUtility.FromJsonOverwrite(jsonStr, this);int len = units.Length;wrapper.value = new T1[len];units.CopyTo(wrapper.value, 0);InvokeModifiedEvents();}catch (Exception e){LogUtility.Log(e.Message, LogType.Error);}}tr.Close();}}else{string dirPath = Path.GetDirectoryName(jsonPath);if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath);if (!File.Exists(jsonPath)) File.Create(jsonPath).Dispose();}}public virtual void ShareUnitsWith(T1[] array){int len = wrapper.value.Length;if (array == null || array.Length != len)array = new T1[len];for (int i = 0; i < len; i++){array[i] = wrapper.value[i];}}public virtual void SetDefault(){T0 table = Resources.Load<T0>(GamePathUtility.GetTableResourcesPath<T0>());if (table != null){int len = table.units.Length;wrapper.value = new T1[len];table.units.CopyTo(wrapper.value, 0);InvokeModifiedEvents();}}public virtual T1 Get(Func<T1, bool> logic){if (logic == null) return default;int len = wrapper.value.Length;for (int i = 0; i < len; i++){ref T1 unit = ref wrapper.value[i];if (logic(unit)) return unit;}return default;}public virtual T1 Get(int index){int len = wrapper.value.Length;if (index < 0 || index >= len) return default;return wrapper.value[index];}public virtual void Set(Func<T1, T1> logic){if (logic == null) return;int len = wrapper.value.Length;for (int i = 0; i < len; i++){wrapper.value[i] = logic(wrapper.value[i]);}InvokeModifiedEvents();}void InvokeModifiedEvents(){if (mModifiedCallback != null){int len = wrapper.value.Length;for (int i = 0; i < len; i++){mModifiedCallback.Invoke(wrapper.value[i]);}}}void Awake(){jsonPath = Path.Combine(Application.dataPath, $"Json/{mType.Name}.json");}void OnEnable(){if (units == null) units = Array.Empty<T1>();if (wrapper == null) wrapper = TempWrapper<T1[]>.WrapByBinary(ref units);if (isAutoControl) LoadFromLoacl();}void OnDisable(){if (isAutoControl) SaveLocally();if (wrapper != null){wrapper.Dispose();wrapper = null;}}
}
Interface目录 

ITableHandler.cs

using System;
using System.Collections.ObjectModel;// 表处理接口
public interface ITableHandler<TTable, TUnit> where TTable : BaseTable where TUnit : ITableUnit
{/// <summary>/// 表单元合集的只读视图/// </summary>ReadOnlyCollection<TUnit> mUnits { get; }/// <summary>/// 表单元合集中元素个数/// </summary>int mCount { get; }/// <summary>/// 表单元合集更改回调/// </summary>event Action<TUnit> mModifiedCallback;/// <summary>/// 分享表单元合集给指定的数组变量/// </summary>/// <param name="array">指定的数组变量</param>void ShareUnitsWith(TUnit[] array);/// <summary>/// 设置为默认值/// </summary>void SetDefault();/// <summary>/// 获取表单元/// </summary>/// <param name="logic">获取逻辑</param>TUnit Get(Func<TUnit, bool> logic);/// <summary>/// 获取表单元/// </summary>/// <param name="index">索引</param>TUnit Get(int index);/// <summary>/// 修改表单元/// </summary>/// <param name="logic">修改逻辑</param>void Set(Func<TUnit, TUnit> logic);
}

ITableUnit.cs

// 表单元接口
public interface ITableUnit { }
Editor目录 

GameTableEditor.cs

using UnityEditor;
using UnityEngine;// 游戏表编辑器
public class GameTableEditor : Editor
{protected SerializedProperty units, isAutoSave;const string tip = "Should be saved after modification. Everything will be saved when we leave the inspector unless you don't check 'Is Auto Save'. In runtime, everything will be loaded from local in 'OnEnable' and saved to local in 'OnDisable' unless you don't check 'Is Auto Control'.";protected void Init(){units = serializedObject.FindProperty("units");isAutoSave = serializedObject.FindProperty("isAutoSave");}protected void SaveGUI(){if (GUILayout.Button("Save")) Save();isAutoSave.boolValue = EditorGUILayout.Toggle(isAutoSave.displayName, isAutoSave.boolValue);}protected void TipGUI(){EditorGUILayout.HelpBox(tip, MessageType.Info);}protected virtual void Save() { }void OnDisable() { if (isAutoSave.boolValue) Save(); }
}
示例(鼠标样式表)

CursorStyleUIUnit.cs

using System;
using UnityEngine;
using UnityEngine.UI;// 鼠标样式UI单元
[Serializable]
public class CursorStyleUIUnit
{[Tooltip("鼠标样式类型")] public CursorStyleType styleType;[Tooltip("Dropdown组件")] public Dropdown dropdown;[Tooltip("当前选项的Image组件")] public Image showImage;[Tooltip("Dropdown组件选项模板下自定义的Image组件")] public Image itemShowImage;
}

CursorStyleUnit.cs

using System;
using UnityEngine;// 鼠标样式单元
[Serializable]
public struct CursorStyleUnit : ITableUnit
{[Tooltip("鼠标样式的属性名称")] public string key;[Tooltip("鼠标样式的属性值")] public string value;public CursorStyleUnit(string key, string value){this.key = key;this.value = value;}
}

CursorStyleTable.cs

using UnityEngine;// 鼠标样式单元存储表
[CreateAssetMenu(fileName = "Assets/Resources/Tables/CursorStyleTable", menuName = "Custom/Create CursorStyle Table", order = 1)]
public sealed class CursorStyleTable : GameTable<CursorStyleTable, CursorStyleUnit>
{[HideInInspector, SerializeField] CursorShape defaultShape;[HideInInspector, SerializeField] CursorColor defaultColor;[HideInInspector, SerializeField] int defaultSize;/// <summary>/// 默认鼠标形状/// </summary>public CursorShape mDefaultShape => defaultShape;/// <summary>/// 默认鼠标颜色/// </summary>public CursorColor mDefaultColor => defaultColor;/// <summary>/// 默认鼠标尺寸/// </summary>public int mDefaultSize => defaultSize;
}

CursorStyleTableEditor.cs

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;[CustomEditor(typeof(CursorStyleTable))]
public sealed class CursorStyleTableEditor : GameTableEditor
{SerializedProperty defaultShape, defaultColor, defaultSize;ReorderableList list;string[] styleTypes; // 样式类型合集Dictionary<int, Style> styles; // key表示该项在整个集合中的索引,value表示样式Style defaultShapeStyle, defaultColorStyle, defaultSizeStyle; // 样式默认值GUIContent defaultShapeContent, defaultColorContent, defaultSizeContent;string[] shapeDisplayNames, colorDisplayNames, sizeDisplayNames; // 样式默认值下拉菜单选项int _shapeIndex, _colorIndex, _sizeIndex; // 样式默认值所选菜单项索引bool isStylesDirty;int shapeIndex{get => _shapeIndex;set{if (_shapeIndex != value){_shapeIndex = value;UpdateDefaultStyles(Array.FindIndex(styleTypes, t => t == CursorStyleConstant.SHAPE));}}}int colorIndex{get => _colorIndex;set{if (_colorIndex != value){_colorIndex = value;UpdateDefaultStyles(Array.FindIndex(styleTypes, t => t == CursorStyleConstant.COLOR));}}}int sizeIndex{get => _sizeIndex;set{if (_sizeIndex != value){_sizeIndex = value;UpdateDefaultStyles(Array.FindIndex(styleTypes, t => t == CursorStyleConstant.SIZE));}}}// 记录每种样式类型和值struct Style{public int styleTypeIndex; // 样式类型索引public string value; // 样式值public Style(int styleTypeIndex, string value){this.styleTypeIndex = styleTypeIndex;this.value = value;}public bool CompareTo(ref Style other){return styleTypeIndex == other.styleTypeIndex && value == other.value;}}void OnEnable(){Init();defaultShape = serializedObject.FindProperty("defaultShape");defaultColor = serializedObject.FindProperty("defaultColor");defaultSize = serializedObject.FindProperty("defaultSize");list = new ReorderableList(serializedObject, units, false, false, true, true){drawElementCallback = DrawUnitCallback,onAddCallback = OnAddElement,onRemoveCallback = OnDelElement};styleTypes = new string[] { CursorStyleConstant.SHAPE, CursorStyleConstant.COLOR, CursorStyleConstant.SIZE };styles = new Dictionary<int, Style>();defaultShapeStyle.styleTypeIndex = Array.FindIndex(styleTypes, t => t == CursorStyleConstant.SHAPE);defaultShapeStyle.value = ((CursorShape)defaultShape.intValue).ToString();defaultColorStyle.styleTypeIndex = Array.FindIndex(styleTypes, t => t == CursorStyleConstant.COLOR);defaultColorStyle.value = ((CursorColor)defaultColor.intValue).ToString();defaultSizeStyle.styleTypeIndex = Array.FindIndex(styleTypes, t => t == CursorStyleConstant.SIZE);defaultSizeStyle.value = defaultSize.intValue.ToString();int len = units.arraySize;SerializedProperty element;for (int i = 0; i < len; i++){element = units.GetArrayElementAtIndex(i);int styleTypeIndex = Array.IndexOf(styleTypes, element.FindPropertyRelative("key").stringValue);AddOrSetElement(i, new Style(styleTypeIndex, element.FindPropertyRelative("value").stringValue));}defaultShapeContent = new GUIContent(defaultShape.displayName, defaultShape.tooltip);defaultColorContent = new GUIContent(defaultColor.displayName, defaultColor.tooltip);defaultSizeContent = new GUIContent(defaultSize.displayName, defaultSize.tooltip);len = styleTypes.Length;for (int i = 0; i < len; i++){UpdateDefaultDisplayNames(i);}string str = defaultShapeStyle.value;_shapeIndex = Array.FindIndex(shapeDisplayNames, s => s == str);str = defaultColorStyle.value;_colorIndex = Array.FindIndex(colorDisplayNames, s => s == str);str = defaultSizeStyle.value;_sizeIndex = Array.FindIndex(sizeDisplayNames, s => s == str);}void DrawUnitCallback(Rect rect, int index, bool isActive, bool isFocused){if (index >= styles.Count) styles[index] = new Style();Style style = styles[index];rect.y += 2;style.styleTypeIndex = EditorGUI.Popup(new Rect(rect.x, rect.y, 80, EditorGUIUtility.singleLineHeight), style.styleTypeIndex, styleTypes);style.value = EditorGUI.TextField(new Rect(rect.x + 100, rect.y, rect.width - 100, EditorGUIUtility.singleLineHeight), style.value);UpdateStyle(ref style, index);}void OnAddElement(ReorderableList list){ReorderableList.defaultBehaviours.DoAddButton(list);AddOrSetElement(list.count - 1, new Style(0, string.Empty));}void OnDelElement(ReorderableList list){DelElement(list.index);ReorderableList.defaultBehaviours.DoRemoveButton(list);}void AddOrSetElement(int index, Style style){if (style.styleTypeIndex < 0 || style.styleTypeIndex >= styleTypes.Length|| string.IsNullOrEmpty(style.value) || index < 0 || index >= list.count) return;styles[index] = style;UpdateDefaultDisplayNames(style.styleTypeIndex);}void DelElement(int index){Style style = styles[index];styles.Remove(index);UpdateDefaultDisplayNames(style.styleTypeIndex);}void UpdateDefaultDisplayNames(params int[] styleTypeIndexes){if (styleTypeIndexes == null || styleTypeIndexes.Length == 0) return;int len = styleTypeIndexes.Length;var group = styles.GroupBy(kv => kv.Value.styleTypeIndex);string CONST_STR;IGrouping<int, KeyValuePair<int, Style>> temp;for (int i = 0; i < len; i++){int index = styleTypeIndexes[i];if (index < 0 || index >= styleTypes.Length) continue;CONST_STR = styleTypes[index];switch (CONST_STR){case CursorStyleConstant.SHAPE:temp = group.Where(g => g.Key == index).FirstOrDefault();if (temp != null) shapeDisplayNames = temp.Select(kv => kv.Value.value).ToArray();else shapeDisplayNames = Array.Empty<string>();break;case CursorStyleConstant.COLOR:temp = group.Where(g => g.Key == index).FirstOrDefault();if (temp != null) colorDisplayNames = temp.Select(kv => kv.Value.value).ToArray();else colorDisplayNames = Array.Empty<string>();break;case CursorStyleConstant.SIZE:temp = group.Where(g => g.Key == index).FirstOrDefault();if (temp != null) sizeDisplayNames = temp.Select(kv => kv.Value.value).ToArray();else sizeDisplayNames = Array.Empty<string>();break;}}}void UpdateDefaultStyles(params int[] styleTypeIndexes){if (styleTypeIndexes == null || styleTypeIndexes.Length == 0) return;int len = styleTypeIndexes.Length;string CONST_STR;for (int i = 0; i < len; i++){int index = styleTypeIndexes[i];if (index < 0 || index >= styleTypes.Length) continue;CONST_STR = styleTypes[index];switch (CONST_STR){case CursorStyleConstant.SHAPE:if (_shapeIndex < 0 || _shapeIndex >= shapeDisplayNames.Length)defaultShapeStyle.value = CursorShape.None.ToString();else defaultShapeStyle.value = shapeDisplayNames[_shapeIndex];break;case CursorStyleConstant.COLOR:if (_colorIndex < 0 || _colorIndex >= colorDisplayNames.Length)defaultColorStyle.value = CursorColor.None.ToString();else defaultColorStyle.value = colorDisplayNames[_colorIndex];break;case CursorStyleConstant.SIZE:if (_sizeIndex < 0 || _sizeIndex >= sizeDisplayNames.Length)defaultSizeStyle.value = "0";else defaultSizeStyle.value = sizeDisplayNames[_sizeIndex];break;}}}void UpdateStyle(ref Style style, int index){if (!styles[index].CompareTo(ref style)){styles[index] = style;isStylesDirty = true;}}public override void OnInspectorGUI(){serializedObject.Update();base.OnInspectorGUI();EditorGUILayout.LabelField("鼠标样式单元合集", EditorStyles.boldLabel);list.DoLayoutList();EditorGUILayout.LabelField("鼠标样式默认值", EditorStyles.boldLabel);if (isStylesDirty){isStylesDirty = false;for (int i = 0; i < styleTypes.Length; i++){UpdateDefaultDisplayNames(i);UpdateDefaultStyles(i);}}EditorGUI.BeginDisabledGroup(shapeDisplayNames.Length == 0);shapeIndex = EditorGUILayout.Popup(defaultShapeContent, shapeIndex, shapeDisplayNames);EditorGUI.EndDisabledGroup();EditorGUI.BeginDisabledGroup(colorDisplayNames.Length == 0);colorIndex = EditorGUILayout.Popup(defaultColorContent, colorIndex, colorDisplayNames);EditorGUI.EndDisabledGroup();EditorGUI.BeginDisabledGroup(sizeDisplayNames.Length == 0);sizeIndex = EditorGUILayout.Popup(defaultSizeContent, sizeIndex, sizeDisplayNames);EditorGUI.EndDisabledGroup();SaveGUI();TipGUI();serializedObject.ApplyModifiedProperties();}protected override void Save(){List<CursorStyleUnit> reserve = new List<CursorStyleUnit>();int len = styles.Count;for (int i = 0; i < len; i++){Style style = styles[i];if (!string.IsNullOrEmpty(style.value)){CursorStyleUnit v_unit = new CursorStyleUnit(styleTypes[style.styleTypeIndex], style.value);if (!reserve.Contains(v_unit)) reserve.Add(v_unit);}}units.ClearArray();styles.Clear();len = reserve.Count;CursorStyleUnit unit;SerializedProperty element;for (int i = 0; i < len; i++){units.InsertArrayElementAtIndex(i);element = units.GetArrayElementAtIndex(i);unit = reserve[i];element.FindPropertyRelative("key").stringValue = unit.key;element.FindPropertyRelative("value").stringValue = unit.value;styles[i] = new Style(Array.FindIndex(styleTypes, t => t == unit.key), unit.value);}for (int i = 0; i < styleTypes.Length; i++){UpdateDefaultDisplayNames(i);UpdateDefaultStyles(i);}if (Enum.TryParse(defaultShapeStyle.value, out CursorShape shape))defaultShape.intValue = (int)shape;if (Enum.TryParse(defaultColorStyle.value, out CursorColor color))defaultColor.intValue = (int)color;defaultSize.intValue = Convert.ToInt32(defaultSizeStyle.value);serializedObject.ApplyModifiedProperties();}
}

界面展示

分析

BaseTable作为所有表格的抽象基类并继承自ScriptableObject,用于后续扩展。ITableHandler声明表格的公开属性和行为。ITableUnit声明数据单元的公开属性和行为,作为暂留接口用于后续扩展,所有数据单元需要实现该接口。GameTable继承自BaseTable,并实现了ITableHandler接口,作为游戏数据表的基类,实现通用属性和方法,向具体游戏表类开放重写方法。GameTableEditor作为游戏数据表编辑器脚本的基类,实现通用逻辑。


示例中CursorStyleUIUnit作为鼠标样式的UI单元,负责定义UI界面上与表格数据相对应的UI组件。CursorStyleUnit作为鼠标样式的数据单元,负责定义每一项表格数据。CursorStyleTable则是定义鼠标样式表的具体逻辑。CursorStyleTableEditor用于定义鼠标样式表在编辑器Inspector面板中的GUI界面。


GameTable中isAutoControl字段用于启用运行时自动进行本地持久化管理的服务,在OnEnable方法中从本地持久化文件中加载内容,在OnDisable方法中将缓存内容保存至本地持久化文件中。isAutoSave字段用于启用编辑器时表格自动保存修改到资产文件的服务,若不勾选,每次在Inspector面板中进行修改后需要手动点击Save按钮进行保存,勾选后会自动保存。提供了指示表类型、表单元只读视图、表单元个数和修改回调等属性,以及本地持久化管理、表单元共享、表单元获取和设置以及重置为默认值等方法。


对表格进行设计后,我们可以使用表格管理器来统一管理所有表格,基于ScriptableObject的特性,我们可以为每个表格创建资产文件,通过加载资产文件即可获取表格实例。


TempWrapper称为字段临时缓存包装器,具体请看系列文章中与此相关的内容。

版本改进

......

系列文章

字段临时缓存包装器

如果这篇文章对你有帮助,请给作者点个赞吧!

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

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

相关文章

cheese安卓版纯本地离线文字识别插件

目的 cheese自动化平台是一款可以模拟鼠标和键盘操作的自动化工具。它可以帮助用户自动完成一些重复的、繁琐的任务&#xff0c;节省大量人工操作的时间。可以采用Vscode、IDEA编写&#xff0c;支持Java、Python、nodejs、GO、Rust、Lua。cheese也包含图色功能&#xff0c;识别…

SpringBoot——基础配置

但是还需要删除pom.xml中的标签——模板的文件也同样操作 banner的选项——关闭 控制台 日志 banner图片的位置——还会分辨颜色 在 Java 的日志框架&#xff08;如 Logback、Log4j2 等&#xff09;中&#xff0c;logging.level.root主要用于设置根日志记录器的日志级别…

location指令

无前缀,必须以”/“开头 前缀""精准匹配。 前缀”^~“ 普通url匹配。 前缀”~“基于正则表达式的匹配&#xff0c; 区分大小写 前缀”~*“ 匹配优先级 locationlocation^~和无前缀/location ~或~* 1.无任何前缀 不加任何规则&#xff0c;默认大小写敏感&#x…

10.2 Linux_并发_进程相关函数

创建子进程 函数声明如下&#xff1a; pid_t fork(void); 返回值&#xff1a;失败返回-1&#xff0c;成功返回两次&#xff0c;子进程获得0(系统分配)&#xff0c;父进程获得子进程的pid 注意&#xff1a;fork创建子进程&#xff0c;实际上就是将父进程复制一遍作为子进程&…

【Linux操作系统】进程的创建与替换

目录 前言&#xff1a;一、进程创建1.fork();1.1 fork基本介绍1.2 fork的原理1.3 写时拷贝1.4 fork的使用场景1.5 fork调用失败的原因 2.clone() 二、进程替换(exec)1.替换原理2.替换函数3.函数解释4.函数理解 前言&#xff1a; 学习了Linux操作系统我们可以知道&#xff0c;进…

基于php摄影门户网站

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目…

Oracle架构之数据库备份和RAC介绍

文章目录 1 数据库备份1.1 数据库备份分类1.1.1 逻辑备份与物理备份1.1.2 完全备份/差异备份/增量备份 1.2 Oracle 逻辑备份1.2.1 EXP/IMP1.2.1.1 EXP导出1.2.1.2 EXP关键字说明1.2.1.3 导入1.2.1.4 IMP关键字说明 1.2.2 EXPDP/IMPDP1.2.2.1 数据泵介绍1.2.2.2 数据泵的使用 1.…

【STM32单片机_(HAL库)】4-3-2【定时器TIM】测量按键按下时间1——编程实现捕获功能

测量按键按下时长思路 测量按键按下时间实验目的 使用定时器 2 通道 2 来捕获按键 &#xff08;按键接PA0&#xff09;按下时间&#xff0c;并通过串口打印。 计一个数的时间&#xff1a;1us&#xff0c;PSC71&#xff0c;ARR65535 下降沿捕获、输入通道 2 映射在 TI2 上、不分…

华为-单臂路由

1、什么是单臂路由 单臂路由&#xff08;Single-Arm Routing&#xff09;是一种网络架构和配置技术&#xff0c;它允许路由器通过一个物理接口来管理多个虚拟局域网&#xff08;VLAN&#xff09;之间的通信。 这个物理接口被配置为Trunk模式&#xff0c;以便能够传输来自不同VL…

如何从huggingface下载

我尝试了一下若干步骤&#xff0c;莫名奇妙就成功了 命令行代理 如果有使用魔法上网&#xff0c;可以使用命令行代码&#xff0c;解决所有命令行连不上外网的问题&#xff1a; #配置http git config --global http.proxy 127.0.0.1:xxxx git config --global https.proxy 127…

移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——15.红黑树

1.红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路 径会比其他路径长出俩倍&#xff0c;…

k8s搭建双主的mysql8集群---无坑

《k8s搭建一主三从的mysql8集群---无坑-CSDN博客》通过搭建一主三从&#xff0c;我们能理解到主节点只有1个&#xff0c;那么承担增删改主要还是主节点&#xff0c;如果你在从节点上去操作增删改操作&#xff0c;数据不会同步到其他节点。本章我们将实现多主&#xff08;双主&a…

YOLO11关键改进与网络结构图

目录 前言&#xff1a;一、YOLO11的优势二、YOLO11网络结构图三、C3k2作用分析四、总结 前言&#xff1a; 对于一个科研人来说&#xff0c;发表论文水平的高低和你所掌握的信息差有着极大的关系&#xff0c;所以趁着YOLO11刚刚发布&#xff0c;趁热了解&#xff0c;先人一步对…

Linux-基础实操篇-组管理和权限管理(上)

Linux 组基本介绍 在 linux 中的每个用户必须属于一个组&#xff0c;不能独立于组外。在 linux 中每个文件 有所有者、所在组、其它组的概念。 用户和组的基本概念&#xff1a; 用户名&#xff1a;用来识别用户的名称&#xff0c;可以是字母、数字组成的字符串&#xff0…

(Kafka源码五)Kafka服务端处理消息

Kafka 服务端&#xff08;Broker&#xff09;采用 Reactor 的架构思想&#xff0c;通过1 个 Acceptor&#xff0c;N 个 Processor(N默认为3)&#xff0c;M 个 KafkaRequestHandler&#xff08;M默认为8&#xff09;&#xff0c;来处理客户端请求&#xff0c;这种模式结合了多线…

kubeadm部署k8s集群,版本1.23.6;并设置calico网络BGP模式通信,版本v3.25--未完待续

1.集群环境创建 三台虚拟机&#xff0c;一台master节点&#xff0c;两台node节点 (根据官网我们知道k8s 1.24版本之后就需要额外地安装cri-dockerd作为桥接才能使用Docker Egine。经过尝试1.24后的版本麻烦事很多&#xff0c;所以此处我们选择1.23.6版本) 虚拟机环境创建参考…

YOLOv11改进策略【损失函数篇】| Shape-IoU:考虑边界框形状和尺度的更精确度量

一、本文介绍 本文记录的是改进YOLOv11的损失函数&#xff0c;将其替换成Shape-IoU。现有边界框回归方法通常考虑真实GT&#xff08;Ground Truth&#xff09;框与预测框之间的几何关系&#xff0c;通过边界框的相对位置和形状计算损失&#xff0c;但忽略了边界框本身的形状和…

关于malloc,calloc,realloc

1.引用的头文件介绍&#xff1a; 这三个函数需要调用<stdlib.h>这个头文件 2.malloc 2.1 函数简单介绍&#xff1a; 首先这个函数是用于动态开辟一个空间&#xff0c;例如数组在c99标准之前是无法arr[N]的&#xff0c;这个时候就需要使用malloc去进行处理&#xff0c…

互斥量mutex、锁、条件变量和信号量相关原语(函数)----很全

线程相关知识可以看这里: 线程控制原语(函数)的介绍-CSDN博客 进程组、会话、守护进程和线程的概念-CSDN博客 1.同步概念 所谓同步&#xff0c;即同时起步&#xff0c;协调一致。不同的对象&#xff0c;对“同步”的理解方式略有不同。如&#xff0c;设备同步&#xff0c;是…

【C语言指南】数据类型详解(上)——内置类型

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《C语言指南》 期待您的关注 目录 引言 1. 整型&#xff08;Integer Types&#xff09; 2. 浮点型&#xff08;Floating-Point …