自由学习记录(36)
- 游戏开发
- 2025-08-29 17:18:01

Linux
Linux 是一个开源的操作系统,其内核及大部分组件都遵循自由软件许可证(如 GPL),允许用户查看、修改和分发代码。这种开放性使得开发者和企业可以根据自己的需求定制系统。
“Linux”严格来说只是指由Linus Torvalds最初开发的那个内核,也就是系统的“心脏”。而我们平时说的“Linux操作系统”或“Linux系统”,通常是指基于这个内核构建的各种发行版(Distribution),例如Ubuntu、Debian、CentOS、Fedora等。这些发行版除了包含Linux内核之外,还集成了GNU工具、图形界面、软件包管理器和其他应用程序,从而为用户提供了完整的操作系统环境。
双启动(Dual Boot)系统允许你在同一台电脑上安装多个操作系统,但在开机时只能运行其中一个。如果你想切换到另一个操作系统,就需要重启电脑,并在启动菜单中选择另一个系统。
如果你希望在Windows系统中同时体验和使用Linux,而不必重启电脑切换系统,那就使用虚拟机
这里使用VMware
虚拟机本身只是一个容器,并不自带操作系统。所以你需要从Linux发行版官方网站下载一个ISO镜像文件,然后在虚拟机里用这个镜像来安装Linux系统。这就像在一台真实电脑上安装系统一样,只不过虚拟机里的安装过程与实际硬件隔离开了。
“Linux系统”通常指的是由Linux内核加上一系列软件(如GNU工具、桌面环境、应用程序等)构成的完整操作系统。而“镜像文件”是一个包含了操作系统安装所需全部文件的单个文件(通常是ISO格式)
Python的学习 环境的搭建一下子还没调整过来,,...先找B站资源吧
--------
python的下载和PyCharm的使用
✔ “将 bin 文件夹添加到 PATH”
作用: 这个选项会将 PyCharm 的 bin 目录添加到 系统的环境变量 PATH 中。这样,你可以在 命令行(cmd / PowerShell / 终端) 直接输入 pycharm 或 charm 来启动 PyCharm,而不需要手动进入安装目录。创建关联
✔ “.py”
作用: 这个选项会将 .py 文件默认关联到 PyCharm。这样,双击 .py 文件时,会自动用 PyCharm 打开,而不是其他编辑器(如 Notepad++、VS Code 或默认的 IDLE)。 影响: 如果勾选,以后双击 .py 文件时,会直接在 PyCharm 里打开。如果不勾选,可能还是默认用 Windows 自带的 Python IDLE 或其他软件打开。python的venv文件夹是虚拟环境文件夹,创建新的脚本不能下载到这个文件夹
一给代码就运行了,做不到多行执行
所以这个时候就诞生了PyCharm这样的
直接python解释器解释 这个文件夹的文件内容了
一些print相关单行注释
而多行注释为三个引号括起来""" """
print(内部可以用逗号多个隔开,直接连接)
我们通过type(变量)可以输出类型,这是查看变量的类型还是数据的类型? 查看的是:变量存储的数据的类型。因为,变量无类型,但是它存储的数据有。
Unity的学习在 Protocol Buffers(protobuf)中,并不能直接定义 C# 中所有的泛型容器,比如 Dictionary、Queue 或其他泛型集合,但 protobuf 提供了两种常用的集合类型来满足大部分需求:
repeated 字段
用于表示数组、列表或队列等顺序数据集合。在生成的 C# 代码中,这类字段会被映射为 RepeatedField<T> 类型。示例:定义一个字符串数组或一个浮点数队列,都可以用 repeated 实现。map 字段
用于表示字典数据结构(键值对)。生成的 C# 代码中,map 字段会被映射为 MapField<TKey, TValue> 类型。注意:map 的键必须是标量类型(例如:int32、string 等),值可以是任意允许的类型。下面给出一个示例 proto 文件,说明如何定义 int/string 的字典、string 数组和 float 队列:
syntax = "proto3"; message MyData { // 定义一个字典,key 为 string,value 为 int32 map<string, int32> myDictionary = 1; // 定义一个字符串数组(或列表) repeated string myStringArray = 2; // 定义一个浮点数队列,这里用 repeated 表示 // 注意:protobuf 不区分队列和数组,具体的队列操作需要在 C# 代码中实现 repeated float myFloatQueue = 3; }对应到 C# 的生成代码
map<string, int32> 会生成一个 MapField<string, int> 类型的属性,例如 public MapField<string, int> MyDictionary { get; }repeated string 会生成一个 RepeatedField<string> 类型的属性,例如 public RepeatedField<string> MyStringArray { get; }repeated float 会生成一个 RepeatedField<float> 类型的属性,例如 public RepeatedField<float> MyFloatQueue { get; }关于其他泛型容器
如果你在 C# 中使用其他泛型容器(例如 List<T>、Queue<T>),需要注意的是:
在 proto 文件中,你只能用 repeated 来表达集合;生成的代码中的 RepeatedField<T> 实际上类似于一个只读列表,你可以将其转换成你需要的类型,但 protobuf 本身不提供额外的泛型容器支持。 定义一个简单的 proto 文件假设我们有如下的 person.proto 文件:
syntax = "proto3"; option csharp_namespace = "MyApp.Protos"; message Person { int32 id = 1; string name = 2; }解释:
syntax:指定使用 proto3 语法版本。option csharp_namespace:指定生成的 C# 代码所属的命名空间。message Person:定义了一个消息类型 Person。字段: id:一个整型字段,对应字段编号为 1。name:一个字符串字段,对应字段编号为 2。 protoc --csharp_out=./Generated person.proto这条cmd命令会在 ./Generated 目录下生成一个 Person.cs 文件,文件中包含了 Person 类的定义
你可以像使用普通的 C# 类那样使用生成的 Protobuf 类。下面给出一个简单的示例,展示如何实例化、赋值、序列化以及反序列化 Person 类:
using System; using System.IO; using MyApp.Protos; // 使用在 .proto 文件中指定的命名空间 using Google.Protobuf; // 引用 Google.Protobuf 库 class Program { static void Main(string[] args) { // 1. 实例化 Person 对象并设置属性 Person person = new Person { Id = 1, Name = "Alice" }; // 2. 序列化:将对象转换为二进制数据 // 方法一:使用 WriteTo 写入到流中 using (MemoryStream stream = new MemoryStream()) { // 将 person 对象写入流中 person.WriteTo(stream); // 获取二进制数据 byte[] data = stream.ToArray(); Console.WriteLine("序列化后的二进制数据长度: " + data.Length); // 3. 反序列化:从二进制数据还原对象 // 使用 Person.Parser.ParseFrom 方法从字节数组解析数据 Person deserializedPerson = Person.Parser.ParseFrom(data); Console.WriteLine($"反序列化后: Id = {deserializedPerson.Id}, Name = {deserializedPerson.Name}"); } // 方法二:直接使用 ToByteArray 方法 byte[] bytes = person.ToByteArray(); Person person2 = Person.Parser.ParseFrom(bytes); Console.WriteLine($"使用 ToByteArray 反序列化后: Id = {person2.Id}, Name = {person2.Name}"); } }sealed 关键字表示这个类不能被继承,也就是说,不能再创建继承自 Person 的子类。
是为了保证序列化逻辑的一致性与安全性,同时也有助于提升性能(因为编译器可以进行更多优化)
partial 关键字允许一个类的定义可以分布在多个文件中
如果没有 partial,你将无法在不修改生成文件的情况下扩展该类。这就意味着每次重新生成代码时,你都需要手动整合你的自定义修改,容易出错且不利于维护。
person.WriteTo(stream) 和 person.ToByteArray() 都是 Protobuf 生成代码中预先定义好的方法,用于实现序列化和反序列化操作。这两个方法的作用分别是:
WriteTo(stream) 将 person 对象按照 Protobuf 协议格式序列化,并写入到指定的流中(例如 MemoryStream 或文件流)。
ToByteArray() 将 person 对象序列化为一个字节数组,这样你可以直接获得序列化后的二进制数据。
其他常用的方法除了上述两个方法,Protobuf 生成的类还包含其他一些常用的方法,主要包括:
CalculateSize() 计算序列化后消息占用的字节数。这对于提前分配足够大小的缓冲区非常有用。
MergeFrom(...) 将传入的二进制数据或流中的数据合并到当前对象中。常见的重载版本有:
MergeFrom(CodedInputStream input)MergeFrom(byte[] data)这使得你可以在已有对象的基础上更新数据,而不是每次都创建新的对象。
Clone() 创建当前消息对象的深拷贝。
Parser.ParseFrom(...) 这是一个静态方法,通过 Person.Parser 访问。它提供了从二进制数据或流中直接解析出 Person 对象的功能。例如:
Person person2 = Person.Parser.ParseFrom(bytes);
ToString() 重写了 ToString() 方法,通常用于调试时获取消息的文本表示。
public class Person { public int Id { get; set; } public string Name { get; set; } // 构造函数,必须传入 id 和 name public Person(int id, string name) { Id = id; Name = name; } } // 使用构造函数创建对象: Person person1 = new Person(1, "Alice");如果有无参构造函数则可以new Person{ }
public class Person { public int Id { get; set; } public string Name { get; set; } // 无参构造函数 public Person() { } } // 使用对象初始化器: Person person2 = new Person { Id = 1, Name = "Alice" };当然,也可以两种混着用
public class Student { public string Name { get; set; } public int Age { get; set; } public string Grade { get; set; } // 带参数的构造函数 public Student(string name) { Name = name; } } // 使用带参数构造函数,再通过对象初始化器设置其他属性: Student student = new Student("Bob") { Age = 20, Grade = "A" };protobuf 的协议生成,
----
什么是Protobuf Protobuf全称是protocol-buffers(协议缓冲区),是谷歌提供给开发者的一个开源的协议生成工具 //它的主要工作原理和我们之前做的自定义协议工具类似 //只不过它更加的完善,可以基于协议配置文件生成 //C++、Java、C#、Objective-C、PHP、Python、Ruby、Go等等语言的代码文件
//它是商业游戏开发中常常会选择的协议生成工具 //有很多游戏公司选择它作为协议工具来进行网络游戏开发 //因为它通用性强,稳定性高,可以节约出开发自定义协议工具的时间 //protocol-buffers官网 developers.google /protocol-buffers #endregion
协议本质上是一套双方都认可并遵循的通信规则,规定了数据如何格式化、传输和解释。这些规则是语言无关的,它们只描述“做什么”而不是“如何实现”。
如果你在客户端的 C# 代码中编写了所有用于解析、响应服务端请求的逻辑,这些代码实际上是对协议的实现。
协议本身仍然是那个双方共同理解的数据交换规则,而你用 C# 写的代码只是用来满足这些规则的手段。
需要注意的是,写完的成员变量默认就是已经存在里面了,已经可以当正常的类来写了
在这种生成式的代码里面,对于自身类的函数方法,要根据自身的成员变量有哪些类型去确认自己要转换成byte数组,需要多少位时,
在自己写进去的脚本里,包含了别的自己写进去的类和枚举
又开了一个类 专门管理 生成脚本的逻辑,传入的参数只需要是XmlNodeList nodes
就是纯正的xml格式被c#的相关类吸收成NodeList
#region知识点一协议(消息)生成主要做什么?
//协议生成主要是使用配置文件中读取出来的信息 //动态的生成对应语言的代码文件 1/每次添加消息或者数据结构类时,我们不需要再手写代码了 //我们不仅可以生成c#脚本文件,还可以根据需求生成别的语言的文件 #endregion
#region知识点二制作功能前的准备工作
//协议生成是不会在发布后使用的功能,主要是在开发时使用 //所以我们在Unity当中可以把它作为一个编辑器功能来做 //因此我们可以专门新建一个Editor文件夹(专门放编辑器相关内容,不会发布) //在具中放置配置文件、自动生成相关脚本文件 #endregion
-----------
这都是在协议的自定义里的
(//field)[last()-2]
last() 代表最后一个节点。last()-2 代表倒数第三个 field。----------------
//enum[@name='E_MONSTER_TYPE']/field[2]
//enum[@name='E_MONSTER_TYPE'] → 选中 name="E_MONSTER_TYPE" 的 <enum>。/field[2] → 在 E_MONSTER_TYPE 里面,选择 第二个 field(即 BOSS)。-----------------------
(//field)[3] //field → 选择所有 <field> 节点(不管在哪)。(//field)[3] → 括号表示把它当作一个集合,取第 3 个元素(索引从 1 开始)。 SelectSingleNode() 只返回第一个匹配的 field,这里确保获取的就是 第三个。不用字段< />
要字段则<message > </message>
xml的数据格式去定义对应的规则(节点式的规则)
也是自定义的管理模版
---
enumNode.Attributes["name"].Value 获取 name 属性值。
可以使用 XmlDocument 或 XDocument (Linq to XML) 来解析 XML 数据并转换成 C# 对象结构
---------------
✅ SelectSingleNode("//field") → 获取第一个匹配的 field ✅ SelectNodes("//field") → 获取所有 field 节点
如果你只想找出 第一个 <field> 节点的值,而不是所有的 <field>,你可以使用 SelectSingleNode() 方法,而不是 SelectNodes()
写法是否完整是否有文本内容适用场景<field name="MAIN">1</field>✅完整写法✅有文本内容 1存储具体数据<field name="OTHER"/>✅完整(但简写)❌无文本内容仅存储属性信息如果你的 XML 需要存储复杂的数组结构,可以直接把 JSON 作为字符串存储
--------
结构体的存储方法(那也可以塞在属性里)
<players> <player> <name>Alice</name> <level>20</level> </player> <player> <name>Bob</name> <level>15</level> </player> </players> XmlDocument doc = new XmlDocument(); doc.Load("data.xml"); XmlNodeList playerNodes = doc.SelectNodes("//players/player"); foreach (XmlNode node in playerNodes) { Console.WriteLine(node.InnerText); // 输出 Alice, Bob, Charlie }在c#中的对这些xml节点的解析
XML 中,**数组(或列表)**的存储方式并没有一种固定的标准,而是取决于你如何组织数据结构。
第一种方式适合简单数据,适用于轻量级 XML。第二种方式更适合复杂结构,便于扩展和查询。属性可以赋值也可以不赋值
可以只使用属性而不提供文本内容
<field name="MAIN">1</field>
里面的 1 是 元素的文本内容,也就是 field 的值
field 是 XML 的元素(节点)。name="MAIN" 是 field 的属性: name 是属性的名称。"MAIN" 是该属性的值,用引号包裹(可以是双引号 "" 或单引号 '')属性 是 标签内的键值对,它用于存储附加信息
协议生成工具的主要优势:
提升开发效率:自动化生成消息类,减少了手动编写的工作量,加快了开发进程。
降低沟通成本:前后端统一使用协议生成工具,确保消息格式一致,避免了因手动声明导致的格式不一致问题。
减少错误率:自动化生成的代码减少了人为错误的可能性,提高了代码的可靠性。
易于维护和扩展:当需要添加新消息时,只需在协议文件中定义,工具会自动生成相应的类,方便后续维护和扩展。
后面发现File作为泛型没有object好用,没改泛型的type判断,在传入参数的时候改成传入object了
到了最后的管理区域,只需要管要下什么,和要怎么用就可以了,也确实是方便,也是因为里面的泛型什么的省了很多事
有自己的类型可以自己去加
异步
异步处理:async/await 更注重于异步执行任务,通常用于 I/O 操作(例如文件读取、网络请求等),它能够避免阻塞主线程。使用 async 和 await,代码看起来是同步的,但实际上会在等待某些操作完成时暂停执行,允许其他任务继续进行。
不能一帧一帧控制:虽然 async 方法能够处理异步任务,但它不能像协程那样按帧执行。await 会让方法暂停,直到操作完成为止。在这一点上,async 方法更像是线程间的切换,控制力度较弱,只能基于任务的完成情况来决定下一步执行。
协程
按帧控制:协程允许你在执行过程中暂停和恢复,这种暂停可以基于时间(例如 WaitForSeconds)或者每一帧的控制(例如 yield return null)。这样你就可以控制协程每一帧的行为,逐步执行任务。
更高的控制力:由于协程是基于 IEnumerator 的,你可以在每次暂停时根据具体的条件决定是否继续执行,也可以在每一帧的执行过程中进行复杂的判断和操作。这使得协程非常适合逐步执行的任务,比如动画、物理模拟、加载过程等。
协程的控制力更强,适合精细的帧间控制,但在使用上稍微复杂一些。
async/await 使得异步任务的使用更加方便,代码更简洁,但它不能像协程那样逐帧控制,适合处理 I/O 密集型任务。
如果协程有多个 yield return 表达式,yield break 会导致协程提前终止,跳过后续的操作
yield break:在协程中,yield break 会结束整个协程的执行
请在一个NetWWWMgr的管理类当中,封装一个基于 UnityWebRequest下载远程数据或加载本地数据的方法 加载完成后,通过委托的形式让外部使用
----------
#region知识点一回顾高级操作中的获取数据 //主要做法:将2进制字节数组处理,独立到下载处理对象中进行处理 ---------------主要就是设置unityWebRequest对象中 downloadHandler 变量 //Unity写好的类有 //1.DownloadHandlerBuffer用于简单的数据存储,得到对应的2进制数据。 //2.DownloadHandlerFile用于下载文件并将文件保存到磁盘(内存占用少)。 //3.DownloadHandlerTexture用于下载图像。 //4.DownloadHandlerAssetBundle 用于提取 AssetBundle。 //5.DownloadHandlerAudioClip用于下载音频文件。
自己要拓展处理方式,继承DownloadHandlerScript,并重写其中的固定方法,自己处理字节数组
自由学习记录(36)由讯客互联游戏开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“自由学习记录(36)”