unity save load系统 快速搭建
我的最终目标是快读建立一个关卡数据自动读入储存功能:
1. 每个关卡有自己的编号,如果没有自定义该关卡,则读取默认编号的初始布局,如果有自定义该关卡,则读取新定义的关卡。
2.在游戏中如果对布局做出了更改,随时储存新的修改。
3.save和load系统与玩法系统耦合度低,无需管理。
小试牛刀-soundmanager
先从一个简单的soundmanager开始学习。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;namespace BBG
{public class SaveManager : SingletonComponent<SaveManager>{#region Member Variablesprivate List<ISaveable> saveables;private JSONNode loadedSave;#endregion#region Properties/// <summary>/// Path to the save file on the device/// </summary>public string SaveFilePath { get { return Application.persistentDataPath + "/save.json"; } }/// <summary>/// List of registered saveables/// </summary>private List<ISaveable> Saveables{get{if (saveables == null){saveables = new List<ISaveable>();}return saveables;}}#endregion#if UNITY_EDITOR[UnityEditor.MenuItem("Tools/Bizzy Bee Games/Delete Save Data")]public static void DeleteSaveData(){if (!System.IO.File.Exists(SaveManager.Instance.SaveFilePath)){UnityEditor.EditorUtility.DisplayDialog("Delete Save File", "There is no save file.", "Ok");return;}bool delete = UnityEditor.EditorUtility.DisplayDialog("Delete Save File", "Delete the save file located at " + SaveManager.Instance.SaveFilePath, "Yes", "No");if (delete){System.IO.File.Delete(SaveManager.Instance.SaveFilePath);#if BBG_MT_IAP || BBG_MT_ADSSystem.IO.Directory.Delete(BBG.MobileTools.Utils.SaveFolderPath, true);#endifUnityEditor.EditorUtility.DisplayDialog("Delete Save File", "Save file has been deleted.", "Ok");}}#endif#region Unity Methodsprivate void Start(){Debug.Log("Save file path: " + SaveFilePath);}private void OnDestroy(){Save();}private void OnApplicationPause(bool pause){if (pause){Save();}}#endregion#region Public Methods/// <summary>/// Registers a saveable to be saved/// </summary>public void Register(ISaveable saveable){Saveables.Add(saveable);}/// <summary>/// Loads the save data for the given saveable/// </summary>public JSONNode LoadSave(ISaveable saveable){return LoadSave(saveable.SaveId);}/// <summary>/// Loads the save data for the given save id/// </summary>public JSONNode LoadSave(string saveId){// Check if the save file has been loaded and if not try and load itif (loadedSave == null && !LoadSave(out loadedSave)){return null;}// Check if the loaded save file has the given save idif (!loadedSave.AsObject.HasKey(saveId)){return null;}// Return the JSONNode for the save idreturn loadedSave[saveId];}#endregion#region Private Methods/// <summary>/// Saves all registered saveables to the save file/// </summary>private void Save(){Dictionary<string, object> saveJson = new Dictionary<string, object>();for (int i = 0; i < saveables.Count; i++){saveJson.Add(saveables[i].SaveId, saveables[i].Save());}System.IO.File.WriteAllText(SaveFilePath, Utilities.ConvertToJsonString(saveJson));}/// <summary>/// Tries to load the save file/// </summary>private bool LoadSave(out JSONNode json){json = null;if (!System.IO.File.Exists(SaveFilePath)){return false;}json = JSON.Parse(System.IO.File.ReadAllText(SaveFilePath));return json != null;}#endregion}
}
以上代码中的Register函数很重要,其他的需要储存数据的模块,比如soundmanager,就需要继承Isavable,并且在初始化时register自己给savemanager:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;namespace BBG
{public class SoundManager : SingletonComponent<SoundManager>, ISaveable{#region Classes[System.Serializable]private class SoundInfo{public string id = "";public AudioClip audioClip = null;public SoundType type = SoundType.SoundEffect;public bool playAndLoopOnStart = false;[Range(0, 1)] public float clipVolume = 1;}private class PlayingSound{public SoundInfo soundInfo = null;public AudioSource audioSource = null;}#endregion#region Enumspublic enum SoundType{SoundEffect,Music}#endregion#region Inspector Variables[SerializeField] private List<SoundInfo> soundInfos = null;#endregion#region Member Variablesprivate List<PlayingSound> playingAudioSources;private List<PlayingSound> loopingAudioSources;public string SaveId { get { return "sound_manager"; } }#endregion#region Propertiespublic bool IsMusicOn { get; private set; }public bool IsSoundEffectsOn { get; private set; }#endregion#region Unity Methodsprotected override void Awake(){base.Awake();SaveManager.Instance.Register(this);playingAudioSources = new List<PlayingSound>();loopingAudioSources = new List<PlayingSound>();if (!LoadSave()){IsMusicOn = true;IsSoundEffectsOn = true;}}private void Start(){for (int i = 0; i < soundInfos.Count; i++){SoundInfo soundInfo = soundInfos[i];if (soundInfo.playAndLoopOnStart){Play(soundInfo.id, true, 0);}}}private void Update(){for (int i = 0; i < playingAudioSources.Count; i++){AudioSource audioSource = playingAudioSources[i].audioSource;// If the Audio Source is no longer playing then return it to the pool so it can be re-usedif (!audioSource.isPlaying){Destroy(audioSource.gameObject);playingAudioSources.RemoveAt(i);i--;}}}#endregion#region Public Methods/// <summary>/// Plays the sound with the give id/// </summary>public void Play(string id){Play(id, false, 0);}/// <summary>/// Plays the sound with the give id, if loop is set to true then the sound will only stop if the Stop method is called/// </summary>public void Play(string id, bool loop, float playDelay){SoundInfo soundInfo = GetSoundInfo(id);if (soundInfo == null){Debug.LogError("[SoundManager] There is no Sound Info with the given id: " + id);return;}if ((soundInfo.type == SoundType.Music && !IsMusicOn) ||(soundInfo.type == SoundType.SoundEffect && !IsSoundEffectsOn)){return;}AudioSource audioSource = CreateAudioSource(id);audioSource.clip = soundInfo.audioClip;audioSource.loop = loop;audioSource.time = 0;audioSource.volume = soundInfo.clipVolume;if (playDelay > 0){audioSource.PlayDelayed(playDelay);}else{audioSource.Play();}PlayingSound playingSound = new PlayingSound();playingSound.soundInfo = soundInfo;playingSound.audioSource = audioSource;if (loop){loopingAudioSources.Add(playingSound);}else{playingAudioSources.Add(playingSound);}}/// <summary>/// Stops all playing sounds with the given id/// </summary>public void Stop(string id){StopAllSounds(id, playingAudioSources);StopAllSounds(id, loopingAudioSources);}/// <summary>/// Stops all playing sounds with the given type/// </summary>public void Stop(SoundType type){StopAllSounds(type, playingAudioSources);StopAllSounds(type, loopingAudioSources);}/// <summary>/// Sets the SoundType on/off/// </summary>public void SetSoundTypeOnOff(SoundType type, bool isOn){switch (type){case SoundType.SoundEffect:if (isOn == IsSoundEffectsOn){return;}IsSoundEffectsOn = isOn;break;case SoundType.Music:if (isOn == IsMusicOn){return;}IsMusicOn = isOn;break;}// If it was turned off then stop all sounds that are currently playingif (!isOn){Stop(type);}// Else it was turned on so play any sounds that have playAndLoopOnStart set to trueelse{PlayAtStart(type);}}#endregion#region Private Methods/// <summary>/// Plays all sounds that are set to play on start and loop and are of the given type/// </summary>private void PlayAtStart(SoundType type){for (int i = 0; i < soundInfos.Count; i++){SoundInfo soundInfo = soundInfos[i];if (soundInfo.type == type && soundInfo.playAndLoopOnStart){Play(soundInfo.id, true, 0);}}}/// <summary>/// Stops all sounds with the given id/// </summary>private void StopAllSounds(string id, List<PlayingSound> playingSounds){for (int i = 0; i < playingSounds.Count; i++){PlayingSound playingSound = playingSounds[i];if (id == playingSound.soundInfo.id){playingSound.audioSource.Stop();Destroy(playingSound.audioSource.gameObject);playingSounds.RemoveAt(i);i--;}}}/// <summary>/// Stops all sounds with the given type/// </summary>private void StopAllSounds(SoundType type, List<PlayingSound> playingSounds){for (int i = 0; i < playingSounds.Count; i++){PlayingSound playingSound = playingSounds[i];if (type == playingSound.soundInfo.type){playingSound.audioSource.Stop();Destroy(playingSound.audioSource.gameObject);playingSounds.RemoveAt(i);i--;}}}private SoundInfo GetSoundInfo(string id){for (int i = 0; i < soundInfos.Count; i++){if (id == soundInfos[i].id){return soundInfos[i];}}return null;}private AudioSource CreateAudioSource(string id){GameObject obj = new GameObject("sound_" + id);obj.transform.SetParent(transform);return obj.AddComponent<AudioSource>();;}#endregion#region Save Methodspublic Dictionary<string, object> Save(){Dictionary<string, object> json = new Dictionary<string, object>();json["is_music_on"] = IsMusicOn;json["is_sound_effects_on"] = IsSoundEffectsOn;return json;}public bool LoadSave(){JSONNode json = SaveManager.Instance.LoadSave(this);if (json == null){return false;}IsMusicOn = json["is_music_on"].AsBool;IsSoundEffectsOn = json["is_sound_effects_on"].AsBool;return true;}#endregion}
}
如上所述的soundmanager,里面有两个内容是告知savemanager如何自动储存信息的
public string SaveId { get { return "sound_manager"; } } public Dictionary<string, object> Save(){Dictionary<string, object> json = new Dictionary<string, object>();json["is_music_on"] = IsMusicOn;json["is_sound_effects_on"] = IsSoundEffectsOn;return json;}
另外,观察soundmanager可知,它在初始化时,去做了一次loadsave函数,也就是去找savemanager要数据,如果要到了,怎样设置,如果没有要到,怎样设置。
public bool LoadSave(){JSONNode json = SaveManager.Instance.LoadSave(this);if (json == null){return false;}IsMusicOn = json["is_music_on"].AsBool;IsSoundEffectsOn = json["is_sound_effects_on"].AsBool;return true;}
实战—关卡储存管理
先看一下这个gamemanager中与储存相关的代码
public Dictionary<string, object> Save(){Dictionary<string, object> json = new Dictionary<string, object>();json["num_stars_earned"] = SaveNumStarsEarned();json["last_completed"] = SaveLastCompleteLevels();json["level_statuses"] = SaveLevelStatuses();json["level_save_datas"] = SaveLevelDatas();json["star_amount"] = StarAmount;json["hint_amount"] = HintAmount;json["num_levels_till_ad"] = NumLevelsTillAd;return json;}private List<object> SaveNumStarsEarned(){List<object> json = new List<object>();foreach (KeyValuePair<string, int> pair in packNumStarsEarned){Dictionary<string, object> packJson = new Dictionary<string, object>();packJson["pack_id"] = pair.Key;packJson["num_stars_earned"] = pair.Value;json.Add(packJson);}return json;}private List<object> SaveLastCompleteLevels(){List<object> json = new List<object>();foreach (KeyValuePair<string, int> pair in packLastCompletedLevel){Dictionary<string, object> packJson = new Dictionary<string, object>();packJson["pack_id"] = pair.Key;packJson["last_completed_level"] = pair.Value;json.Add(packJson);}return json;}private List<object> SaveLevelStatuses(){List<object> json = new List<object>();foreach (KeyValuePair<string, Dictionary<int, int>> pair in packLevelStatuses){Dictionary<string, object> packJson = new Dictionary<string, object>();packJson["pack_id"] = pair.Key;string levelStr = "";foreach (KeyValuePair<int, int> levelPair in pair.Value){if (!string.IsNullOrEmpty(levelStr)) levelStr += "_";levelStr += levelPair.Key + "_" + levelPair.Value;}packJson["level_statuses"] = levelStr;json.Add(packJson);}return json;}private List<object> SaveLevelDatas(){List<object> savedLevelDatas = new List<object>();foreach (KeyValuePair<string, LevelSaveData> pair in levelSaveDatas){Dictionary<string, object> levelSaveDataJson = pair.Value.Save();levelSaveDataJson["id"] = pair.Key;savedLevelDatas.Add(levelSaveDataJson);}return savedLevelDatas;}private bool LoadSave(){JSONNode json = SaveManager.Instance.LoadSave(this);if (json == null){return false;}LoadNumStarsEarned(json["num_stars_earned"].AsArray);LoadLastCompleteLevels(json["last_completed"].AsArray);LoadLevelStatuses(json["level_statuses"].AsArray);LoadLevelSaveDatas(json["level_save_datas"].AsArray);StarAmount = json["star_amount"].AsInt;HintAmount = json["hint_amount"].AsInt;NumLevelsTillAd = json["num_levels_till_ad"].AsInt;return true;}private void LoadNumStarsEarned(JSONArray json){for (int i = 0; i < json.Count; i++){JSONNode childJson = json[i];string packId = childJson["pack_id"].Value;int numStarsEarned = childJson["num_stars_earned"].AsInt;packNumStarsEarned.Add(packId, numStarsEarned);}}private void LoadLastCompleteLevels(JSONArray json){for (int i = 0; i < json.Count; i++){JSONNode childJson = json[i];string packId = childJson["pack_id"].Value;int lastCompletedLevel = childJson["last_completed_level"].AsInt;packLastCompletedLevel.Add(packId, lastCompletedLevel);}}private void LoadLevelStatuses(JSONArray json){for (int i = 0; i < json.Count; i++){JSONNode childJson = json[i];string packId = childJson["pack_id"].Value;string[] levelStatusStrs = childJson["level_statuses"].Value.Split('_');Dictionary<int, int> levelStatuses = new Dictionary<int, int>();for (int j = 0; j < levelStatusStrs.Length; j += 2){int levelIndex = System.Convert.ToInt32(levelStatusStrs[j]);int status = System.Convert.ToInt32(levelStatusStrs[j + 1]);levelStatuses.Add(levelIndex, status);}packLevelStatuses.Add(packId, levelStatuses);}}/// <summary>/// Loads the game from the saved json file/// </summary>private void LoadLevelSaveDatas(JSONArray savedLevelDatasJson){// Load all the placed line segments for levels that have progressfor (int i = 0; i < savedLevelDatasJson.Count; i++){JSONNode savedLevelDataJson = savedLevelDatasJson[i];JSONArray savedPlacedLineSegments = savedLevelDataJson["placed_line_segments"].AsArray;JSONArray savedHints = savedLevelDataJson["hints"].AsArray;List<List<CellPos>> placedLineSegments = new List<List<CellPos>>();for (int j = 0; j < savedPlacedLineSegments.Count; j++){placedLineSegments.Add(new List<CellPos>());for (int k = 0; k < savedPlacedLineSegments[j].Count; k += 2){placedLineSegments[j].Add(new CellPos(savedPlacedLineSegments[j][k].AsInt, savedPlacedLineSegments[j][k + 1].AsInt));}}List<int> hintLineIndices = new List<int>();for (int j = 0; j < savedHints.Count; j++){hintLineIndices.Add(savedHints[j].AsInt);}string levelId = savedLevelDataJson["id"].Value;int numMoves = savedLevelDataJson["num_moves"].AsInt;LevelSaveData levelSaveData = new LevelSaveData();levelSaveData.placedLineSegments = placedLineSegments;levelSaveData.numMoves = numMoves;levelSaveData.hintLineIndices = hintLineIndices;levelSaveDatas.Add(levelId, levelSaveData);}}#endregion
我们发现,因为数据较为复杂,无论是load还是save,都针对不同数据有自己的辅助函数。
gamemanager中,有一个startlevel,它需要一个packinfo(总关卡信息,可暂时忽略),以及一个leveldata.
这里拿到的leveldata,是制作者本身就默认写好的值,
如果这个leveldata的id,已经存在于levelsavedata的字典中,就说明这个leveldata经过了修改,因此要读取的是新的levelsavedata中的配置数据。
如果这个leveldata的id没有存在于levelsavedata的字典中,就说明这次是第一次打开这个level,那么需要新建一个savedata:
下面的代码记录了这个功能。
/// <summary>/// Starts the level./// </summary>public void StartLevel(PackInfo packInfo, LevelData levelData){ActivePackInfo = packInfo;ActiveLevelData = levelData;// Check if the lvel has not been started and if there is loaded save data for itif (!levelSaveDatas.ContainsKey(levelData.Id)){levelSaveDatas[levelData.Id] = new LevelSaveData();}gameGrid.SetupLevel(levelData, levelSaveDatas[levelData.Id]);UpdateHintAmountText();UpdateLevelButtons();GameEventManager.Instance.SendEvent(GameEventManager.EventId_LevelStarted);ScreenManager.Instance.Show("game");// Check if it's time to show an interstitial adif (NumLevelsTillAd <= 0){NumLevelsTillAd = numLevelsBetweenAds;#if BBG_MT_ADSBBG.MobileTools.MobileAdsManager.Instance.ShowInterstitialAd();#endif}}
其他的功能基本上和soundmanager一样:
比如,在初始化时,注册自己,并试图loadsave.
比如,在游戏中断时,进行保存
protected override void Awake(){base.Awake();GameEventManager.Instance.RegisterEventHandler(GameEventManager.EventId_ActiveLevelCompleted, OnActiveLevelComplete);SaveManager.Instance.Register(this);packNumStarsEarned = new Dictionary<string, int>();packLastCompletedLevel = new Dictionary<string, int>();packLevelStatuses = new Dictionary<string, Dictionary<int, int>>();levelSaveDatas = new Dictionary<string, LevelSaveData>();if (!LoadSave()){HintAmount = startingHints;NumLevelsTillAd = numLevelsBetweenAds;}gameGrid.Initialize();if (startingStars > 0){StarAmount = startingStars;}}private void OnDestroy(){Save();}private void OnApplicationPause(bool pause){if (pause){Save();}}
至此,重点结束。
然后,其他功能的脚本,可以通过获得currentlevelsavedata的方式去修改其数据,方便在关闭界面时进行数据更新。
/// <summary>/// Sets the numMoves and updates the Text UI/// </summary>private void SetNumMoves(int amount){currentLevelSaveData.numMoves = amount;moveAmountText.text = currentLevelSaveData.numMoves.ToString();}
围绕这个功能,还可以方便设计undo/redo功能
相关文章:
unity save load系统 快速搭建
我的最终目标是快读建立一个关卡数据自动读入储存功能: 1. 每个关卡有自己的编号,如果没有自定义该关卡,则读取默认编号的初始布局,如果有自定义该关卡,则读取新定义的关卡。 2.在游戏中如果对布局做出了更改&#x…...
前端 TS 快速入门之四:函数
1. 为函数定义类型 一般使用中,我们可以不必完整写出函数类型,因为 TypeScript 会为我们自动推断出类型,需要注意的是:类型中的参数名称可以不必和值中的参数名称匹配,只要它们类型是兼容的便可。 // 书写完成函数类型…...

Linux 救援模式
Linux突然坏了 第三次坏了 第一次是找不到盘,修复好了 第二次是找不到卷,但是能启动,启动界面选择救援模式,可以正常使用 第三次,尝试修复卷,启动后,找不到文件系统了,只能从光盘…...
HOT100自查题集
前言 遗忘是人类的本能,经常自查是必不可少的。现在面试手撕已经是必备项目,手撕题目大部分来自于力扣 HOT100 以及对应方向的一些编码题。我对 HOT 100 的题目以及Java相关的一些编码题目进行了分类整理,并提供解题思路,用作复习…...

Post-Process1-水下
一、新建第三人称游戏项目,我这里选择C,你也可以选择Blueprint。 新建一个Level,命名为DemoUnderWater 保存一下,命名为DownUnderWater 添加水插件 选择Yes 勾选Show Engine Content和Show Plugin Content,在左侧可以看…...

通过pipeline配置sonar自动化实现过程解析
这篇文章主要介绍了通过pipeline配置sonar自动化实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.sonar配置webhooks, 2.url填写jenkins的地址:http://jenkinsurl/sonarqu…...

Spring framework Day 23:容器事件
前言 容器事件是 Spring Framework 中的一个重要概念,它提供了一种机制,使我们能够更好地了解和响应 Spring 容器中发生的各种事件。通过容器事件,我们可以在特定的时间点监听和处理容器中的各种状态变化、操作和事件触发,以实现…...
【Dockerfile】基于openjdk镜像添加常用命令工具
背景 官方openjdk镜像是Red Hat操作系统,可以使用microdnf命令安装 bash-4.4# cat /etc/redhat-release Red Hat Enterprise Linux release 8.5 (Ootpa)拉取openjdk镜像 docker pull openjdk:17.0.2 编写Dockerfile touch Dockerfile-openjdk-utilsFROM openjd…...
目录内图片转PDF(多图片打印助手)
最近同事经常找我帮她打印试卷,很奇葩的是,她的试卷都是*红书上下载的图片,一张张打印不好看,而且可能打印不完全,大小也不协调,所以有了这个脚本。 【需要给小孩打印图片格式的试卷时也比较实用】 -----…...

React TypeScript安装npm第三方包时,些包并不是 TypeScript 编写的
npm install types/包名称 例如:npm install types/jquery 学习链接...

Java 基础 面试 多线程
1.多线程 1.1 线程(Thread) 线程时一个程序内部的一条执行流程,java的main方法就是由一条默认的主线程执行 1.2 多线程 多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行) 许多平台都离不开多…...
软考 系统架构设计师系列知识点之软件构件(1)
所属章节: 第2章. 计算机系统基础知识 第3节. 计算机软件 2.3.7 软件构件 1. 概述 构件又称为组件,是一个自包容、可复用的程序集。构建是一个程序集、或者说是一组程序的集合。这个集合可能会以各种方式体现出来,如源程序或二进制代码。这…...

RHCE8 资料整理(三)
RHCE8 资料整理 第三篇 网络相关配置第11章 网络配置11.1 网络基础知识11.2 查看网络信息11.3 图形化界面修改11.4 通过配置文件修改11.5 命令行管理11.6 主机名的设置 第12章 ssh12.1 ssh基本用法12.2 打开远程图形化界面12.3 ssh无密码登录12.4 ssh安全设置12.5 ssh限制用户1…...

C++ 使用httplib库,发送HTTP请求
简介 C 使用httplib库,发送HTTP请求 接口信息 ip地址 192.168.16.166 端口 8899 接口地址/abc/tk 请求方式GET 响应内容: { “result”: true, “message”: “”, “tk”: “yueguangsaxialexiangshuitan0ihai”, “datetimeout”: “2023-10-22 21…...

OceanBase自动安装部署演示环境demo
OceanBase自动安装部署 前提条件 官方给出硬件条件需要满足以下要求 本文操作系统为:Red Hat Enterprise Linux 8 64 位 下载链接:https://pan.baidu.com/s/1rZ39xJFhk0HdmC4wEJcxvg 提取码:c01x 下载并安装 all-in-one 安装包 执行如下…...

windows系统mysql服务启动失败
原因 电脑重启navicat连接mysql失败,在电脑-管理-服务没有mysql服务 解决方案 找到mysql的安装目录进入bin目录 执行mysqld --install 进行重新安装 提示服务安装成功 net start mysql mysql 启动成功 ...

B-tree(PostgreSQL 14 Internals翻译版)
概览 B树(作为B树访问方法实现)是一种数据结构,它使您能够通过从树的根向下查找树的叶节点中所需的元素。为了明确地标识搜索路径,必须对所有树元素进行排序。B树是为有序数据类型设计的,这些数据类型的值可以进行比较和排序。 下面的机场代…...

React环境初始化
环境初始化 学习目标: 能够独立使用React脚手架创建一个React项目 1.使用脚手架创建项目 官方文档:(https://create-react-app.bootcss.com/) - 打开命令行窗口 - 执行命令 npx create-react-app projectName 说明:…...

Hadoop3教程(二十八):(生产调优篇)NN、DN的多目录配置及磁盘间数据均衡
文章目录 (148)NN多目录配置(149)DataNode多目录配置及磁盘间数据平衡磁盘间数据均衡 参考文献 (148)NN多目录配置 NN多目录的意思是,本地目录可以配置成多个,且每个目录存放内容相…...

博客续更(五)
十一、后台模块-菜单列表 菜单指的是权限菜单,也就是一堆权限字符串 1. 查询菜单 1.1 接口分析 需要展示菜单列表,不需要分页。可以针对菜单名进行模糊查询。也可以针对菜单的状态进行查询。菜单要按照父菜单id和orderNum进行排序 请求方式 请求路径…...

测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...

Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...