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,而不需要手动进入安装目录。
- 这个选项会将 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 为 int32map<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 和 namepublic 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官网
https://developers.google.com/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,并重写其中的固定方法,自己处理字节数组