无主之地3感想

玩无主之地的时候想到了一个问题。即无主之地是一个以剧情为推进动力的线性游戏,为什么这个游戏关于剧情和人物的争议不大。比如无主之地的剧情是否精彩,玩家不会就这个问题吵得不可开交。以及热衷于分析剧情中蛛丝马迹的列文虎克不多也不受关注。

而听说同样是剧情为推进动力的古龙风云传最近就出了不少节奏。玩家会关心游戏中的角色性取向是不是正常的,剧情是否合理,人物动机是否合理,以及就算合理是否反映了他是个有道德缺陷的人。好像如果确认主要角色有道德缺陷就有可能构成对游戏差评的理由,也可以用来判断这个游戏是不是值得玩。类似的争论在原神的社区更是每天都在上演,而无主之地中的角色离谱的多,剧情荒谬的多,却没有这种问题。

那么是无主之地的剧情是不好或者不重要,以至于不值得产生争议吗?我认为是否定的。我认为之所以会有这样的区别,原因是语境不同。

制作者通过音乐,美术,以及文字塑造了一种语境。玩家如果能进入到这种语境就可以和作品同频。比如无主之地系列的基调就是荒诞的废土风格。在这种基调下,无论出现什么离奇的转折,有着怪癖的角色,异想天开的物品,玩家都不会觉得是因为剧情推进不下去或者原不上了而新增的设定。

于是就可能会出现这样一个问题。即有的语境对剧情设计宽容,有的严格。比如科幻类,本格推理类就属于严格的。推理作品中出现了低智或者不合逻辑的情节观众就会出戏,且认为这是低质量的作品。反例比如燕双鹰系列,虽然剧中有推理悬疑要素。但是作品本身确立了爽剧的基调,进入了这种语境的观众不会对推理过程太较真。但相应的,爽的地方要爽出感觉才会被认为是好剧。

所以在这里要暂时提出一个问题。在设计游戏作品的时候要不要尽早明确游戏的语境,从而在潜意识里“规定”玩家较真的地方。或者说,如何避免玩家在不该较真的地方较真。

2024读书计划

今年计划先把过去买了没读或没读完的书清一清。清完后再更新这个列表。

1.《如何阅读一本书》 莫提默·J.艾德勒

2.《海底两万里》 儒勒·凡尔纳

3.《心》 夏目漱石

4.《西西弗神话》 加缪

5.《什么是民粹主义》 维纳尔·米勒

6.《自私的基因》 理查德·道金斯

7.《简明逻辑学》 牛津通识读本

8.《故事》 罗伯特·麦基

9.《中国古代文化常识》 王力

10.《金雀花王朝》 丹·琼斯

11.《大问题》 罗伯特·所罗门、凯思林·希金斯

12.《易中天中华史》 易中天

C#变量

命名约定

命名约定示例适用场合
驼峰样式cost、orderDetail、dateOfBirth局部变量、私有字段
标题样式String、Int32、Cost、DateOfBirth、Run类型、非私有字段以及方法

存储文本——理解逐字字符串
在字符串变量中存储文本时,可以包括转义序列,转义序列使用反斜杠表示特殊字符,如制表符和新行,如下所示:

string fullNameWithTabSeparator = "Bob\tSmith";

但是,如果在Windows上要将路径存储到文件中,并且路径中有文件夹的名称以t开头,如下所示:

string fileFath = "C:\televisions\sony\bravia.txt";

那么编译器将把\t转换成制表符,这显然是错误的!
逐字字符串必须加上@符号作为前缀,如下所示:

string fileFath = @"C:\televisions\sony\bravia.txt";

字面字符串:用双引号括起来的一些字符。它们可以使用转义字符\t作为制表符。要表示反斜杠需要是使用\\。
逐字字符串:以@为前缀的字面字符串,以禁用转义字符,因此反斜杠就是反斜杠。它还允许字符串值跨越多行,因为空白字符被视为空白,而不是编译器的指令。
内插字符串:以$为前缀的字面字符串,以支持嵌入的格式化变量,详见本章后面的内容。

存储整数
计算机把所有东西都存储为位。位的值不是0就是1,即二进制。

二进制和十六进制计数法
二进制计数法以2为基数,只使用0和1,数字字面值的开头是0b。
十六进制计数法以16为基数,使用的是0~9和A~F,数字字面值的开头是0x。

使用数字分隔符提高可读性
C# 7.0级更高版本中的两处改进是使用下划线_作为数字分隔符以及支持二进制字面值。可以在数字字面值(包括十进制、二进制和十六进制表示法)中插入下划线,以提高可读性。例如,可以将十进制数字1000000写成1_000_000。

存储任何类型的对象
有一种名为object的特殊类型,这种类型可以存储任何数据,但这种灵活性是以混乱的代码和可能较差的性能为代价的。由于这两个原因,应该尽可能避免使用object类型。

动态存储类型
还有一种特殊类型名为dynamic,可用于存储任何类型的数据,并且灵活性相比object类型更强,代价是性能下降了。dynamic关键字是在C# 4.0中引入的。但是,与object不同的是,存储在dynamic变量中的值可以在没有显式进行强制转换的情况下调用成员。dynamic类型存在的限制是,代码编辑器不能显示智能感知来帮助编写代码。这是因为编译器在构建期间不能检查类型是什么。相反,CLR会在运行时检查成员;如果缺少成员,则抛出异常。

推断局部变量的类型
可以使用var关键字来声明局部变量。编译器将从赋值操作符=之后赋予的值推断类型。
尽管使用var很方便,但一些开发人员避免使用它,以使代码阅读者更容易理解所使用的类型。

使用面向类型的new实例化对象
在C# 9.0中,微软引入了另一种用于实例化对象的语法,称为面向类型的new。当实例化对象时,可以先指定类型,再使用new,而不用重复写出类型。
除非必须使用C# 9前的编译器,否则尽量使用面向类型的new来实例化对象。
C#中实例化对象是指用new关键字创建类的具体对象,这样才能调用类中的非静态方法。例如,如果你有一个名为Person的类,你可以这样实例化一个名为person的对象:

Person person = new Person();

实例化对象的过程就是给对象分配内存空间,并调用类的构造函数。

获取和设置类型的默认值
除了string外,大多数基本类型都是值类型,这意味着它们必须有值。可以通过使用default()操作符并将类型作为参数传递来确定类型的默认值。可以使用default关键字指定类型的默认值。
string类型是引用类型。这意味着string变量包含值的内存地址而不是值本身。引用类型的变量可以有空值;空值是字面量,表示变量尚未引用任何东西。空值是所有引用类型的默认值。

在数组中存储多个值
当需要存储同一类型的多个值时,可以声明数组。例如,当需要在string数组中存储四个名称时,就可以这样做。
下面的代码可用来为存储四个字符串值的数组分配内存。首先再索引位置0~3存储字符串值(数组是从0开始计数的,因此最后一项比数组长度小1)。

string[] names;
names = new string[4];
names[0] = "Kate";
names[1] = "Jack";
names[2] = "Rebecca";
names[3] = "Tom";

还有另一种方法是使用数组初始化器语法,如下面的代码所示:

string[] names = new[] {"Kate""Jack""Rebecca""Tom"};

使用new[]语法为数组分配内存时,花括号中至少要有一个项,以便编译器推断出数据类型。

不要假设所有数组的计数都是从零开始的。.NET中最常见的数组类型是szArray,这是一种一维的零索引数组,它们使用正常的[]语法。但是.NET也有mdArray,一个多维数组,它们不必有一个为零的下界。这很少使用,但需要知道它的存在。

数组对于临时存储多个项很有用,但是再动态添加和删除项时,集合是更灵活的选择。后面会学到。

揭示C#词汇表的范围

using System.Reflection;

Assembly? assembly = Assembly.GetExecutingAssembly();
if (assembly == null) return;

//循环访问此应用程序引用的程序集
foreach (AssemblyName name in assembly.GetReferencedAssemblies())
{
    //加载程序集,以便我们可以读取其详细信息
    Assembly a = Assembly.Load(name);

    //声明一个变量来计算方法的数量
    int MethodCount = 0;

    //循环遍历程序集中的所有类型
    foreach (TypeInfo t in a.DefinedTypes)
    {
        //将方法的数量相加
        MethodCount += t.GetMethods().Count();
    }

    //输出类型及其方法的计数
    Console.WriteLine
        (
        "{0:N0} type with {1:N0} methods in {2} assembly.",
        arg0: a.DefinedTypes.Count(),
        arg1: MethodCount, arg2: name.Name
        );
}

这里显示的类型和方法的数量会根据使用的操作系统而有所不同。

隐式和全局导入名称空间

传统上,每个需要导入名称空间的.cs文件都必须首先使用using语句来导入这些名称空间。对于System和System.Linq这样的名称空间,几乎所有的.cs文件都需要,所以每个.cs文件的前几行通常至少有几个using语句,如下面的代码所示:

using System;
using System.Linq;
using System.Collections.Generic;


当使用ASP.NET Core创建网站和服务时,每个文件都需要导入几十个名称空间。
C# 10引入了一些简化名称空间导入的新特性。
首先,global using语句意味着指需要在一个.cs文件中导入一个名称空间,它将在所有.cs文件中都可用。可以把global using语句放到Program.cs文件中,但建议为这些语句创建一个单独的文件,命名为GlobalUsings.cs或GlobalNamespaces.cs,代码如下所示:

global using System;
global using System.Linq;
global using System.Collections.Generic;


其次,任何以.NET 6.0为目标并因此使用C# 10编译器的项目都会在.obj文件夹中生成一个.cs文件,以隐式的全局导入一些公共名称空间,比如System,隐式导入的名称空间的具体列表取决于面向的SDK。自动生成的隐式导入文件如下图所示。

选择项目,然后向项目文件中添加其他条目,以控制隐式导入哪些名称空间,如下图高亮显示的代码所示。

保存后,此时Vocabulary.GlobalUsings.g.cs文件现在导入System.Numerics,而非System.Threading,代码如下图所示。

另外,我们可以删除项目文件中的一个条目来禁用所有SDK的隐式导入名称空间特性,如下面的标记所示。

C#语法

C#语法包括语句和块。

语句
C#用分号标识语句结束。C#语句可以由多个变量和表达式组成。例如,下面的C#语句中,totalPrice是变量,而subtotal + salesTax是表达式。
var totalPrice = subtotal + salesTax;
以上表达式由一个名为subtotal的操作数、运算符+,和一个名为salesTax的操作数组成。操作数和运算符的顺序很重要。


C#用花括号{}表示代码块。
块以声明开始,以指示要定义的内容。例如,块可以定义许多语言解构的开始和结束,包括名称空间、类、方法或foreach这样的语句。
· 名称空间(namespace)包含类型(如类),将它们组在一起。
· 类(class)包含对象的成员(包括方法)。
· 方法(method)包含的语句,实现对象可以执行的操作。

导入名称空间
System是名称空间,类似于类型的地址。
System.Console.WriteLine告诉编译器在System名称空间的Console类型中查找WriteLine方法。为了简化代码,.NET 6.0之前的每个版本的控制台应用程序项目模板都在代码文件的顶部添加了一条语句,告诉编译器始终在System名称空间中查找没有加上名称空间前缀的类型,如下所示:

using System; //import the System namespace


我们称这种操作为导入名称空间,导入名称空间的效果是,名称空间中的所有可用类型都对程序可用,而不需要输入名称空间前缀,在编写代码时名称空间将以只能感知的方式显示。

动词表示方法
在英语中,动词是动作或行动。在C#中,动作或行动被成为方法。C#有成千上万个方法可用。
在英语中,动词的写法取决于动作发生的时间。在C#中,像WriteLine这样的方法会根据操作的细节改变调用或执行的方式。这成为重载(后面再详细讨论这个问题)。

初步了解.NET

了解现代.NET、.NET Core、.NET Framework、Mono、Xamarin和.NET Standard之间的异同。
现代.NET:指代.NET 6和前身.NET 5(来自.NET Core)。
旧.NET:指代.NET Framework、Mono、Xamarin和.NET标准。

设置开发环境Visual Studio 2022 for Windows
在Workloads选项卡上,选择以下内容:
· ASP.NET and web development
· Azure development
· .NET desktop development with C++
· Universal Windows Platform development
· Mobile development with .NET
在Individual components选项卡的Code tools部分,选择以下内容:
· Class Designer
· Git for Windows
· PreEmptive Protection – Dotfuscator

了解.NET Standard
2019年,.NET的情况是,微软控制着三个.NET平台分支,如下所示。
· .NET Core:用于跨平台和新应用程序。
· .NET Framework:用于旧应用程序。
· Xamarin:用于移动应用程序。
以上每种.NET平台都有优点和缺点,它们都是针对不同的场景设计的。这导致如下问题:开发人员必须学习三个.NET平台,每个.NET平台都有绫人讨厌的怪癖和限制。
因此,微软定义了.NET Standard,这是所有.NET平台都可以实现的一套API规范,来指示兼容性级别。例如,与.NET Standard 1.4兼容的平台表明提供基本的支持。
在.NET Standard 2.0及后续版本中,微软已将着三个.NET平台融合到现代的最低标准,这使开发人员可以更容易的在任何类型的.NET之间共享代码。
在.NET Core 2.0及后续版本中,微软郑家了许多确实的API,开发人员需要将你为.NET Framework编写的旧代码移植到跨平台的.NET Core中。有些API已经实现了,的那会抛出异常来提示开发人员,不应该实际使用它们!这通常是由于运行.NET的操作系统不同。
理解.NET Standard只是一种标准是很重要的。你不能安装.NET Standard,就像不能安装HTML5一样。要使用HTML5,就不许安装实现了HTML5标准的Web浏览器。
要使用.NET Standard,就必须安装实现了.NET Standard规范的.NET平台。.NET Standard 2.1是由.NET Core 3.0、Mono和Xamarin实现的。C# 8.0的一些特性需要.NET Standard 2.1,.NET Framework 4.8没有实现.NET Standard 2.1,所以应该把.NET Framework当作旧技术。

理解中间语言(Intermediate Language,IL)
dotnet CLI工具使用的C#编译器(名为Roslyn)会将C#源代码转换成中间语言(IL)代码,并将IL存储在程序集(DLL或EXE文件)中。IL代码语句就像汇编语言指令,由.NET的虚拟机CoreCLR执行。
在运行时,CoreCLR从程序集中加载IL代码,再由JIT编译器将IL代码编译成本机CPU指令,最后由机器上的CPU执行。
以上的两步编译过程带来的好处是,微软能为Linux、macOS以及Windows创建CLR。再编译过程中,相同的IL代码会导出运行,这将为本地操作系统和CPU指令集生成代码。
不管源代码是哪种语言编写的(如C#、Visual Basic或F#),所有的.NET应用程序都会为存储在程序集中的指令使用IL代码。使用微软和其他公司提供的反汇编工具(如.NET反编译工具ILSpy)可以打开程序集并显示IL代码。

UV展开,贴图和材质的概念

UV展开:
把三维模型展开到一张平面上。

贴图(texture):
像素组成的图片。
如果是写实的贴图可以通过相机拍摄获取,或者在网上进行素材的下载。
写实风格的素材网站Textures for 3D, graphic design and Photoshop!
如果是风格化或者卡通一些的贴图,通常需要自己进行手动的绘制。
贴图的尺寸需要是2n
(一般手游贴图质量为512 * 512;顶级3A能用到4096 * 4096)

常见的贴图:
S代表Specular Map(高光贴图),
用来控制物体的高光部分的反射程度的贴图。
D代表Diffuse Map(漫反射贴图),
用来控制物体的基本颜色的贴图。(一般的漫反射贴图适不适合配置在Unity的基础贴图有待验证)
N代表Normal Map(法线贴图),
用来控制物体表面法线方向的贴图。
法线贴图的使用场景是,让低模具有类似高模的效果。
在建模时保留两个模型,一个低模一个高模。在高模上丰富细节,生成它的法线贴图,再贴在低模上。

材质(material):
材质是一种根据用户输入的贴图和数值,通过算法渲染出相应视觉效果的程序。