阅读视图

发现新文章,点击刷新页面。
🔲 ☆

别搞“小而美”了!Rust 开发者请愿:求求标准库学学 Go 吧

本文永久链接 – https://tonybai.com/2026/04/09/stop-being-small-and-beautiful-rust-petition-to-learn-from-go

大家好,我是Tony Bai。

如果你之前经常听 Go 社区最火的播客 GoTime(很遗憾,该播客2024年末因平台原因停播了),你一定会熟悉每期节目最后的那个经典环节——“Unpopular Opinion”(非主流观点)。在这个环节,嘉宾们会分享一些看似离经叛道、却往往一针见血的“暴论”。

但就在前几天,这个流行于 Go 社区的“梗”,却被隔壁的 Rust 社区“偷”了过去,并掀起了一场史诗级的“路线之争”。

一位 Rust 开发者,在 r/rust 论坛上发了一篇帖子,标题就叫:《Unpopular opinion: Rust should have a larger standard library》(非主流观点:Rust 应该有一个更大的标准库)。

他在这篇帖子中发出了灵魂拷问:

“我不想为了写一个程序,被迫去拉几百个我根本没时间、也没人去审计的第三方依赖包。看看隔壁的 Go 是怎么做标准库的,你几乎可以不依赖任何三方包就构建出复杂的系统!”

这篇帖子瞬间引爆了 Rust 社区。短短一天,帖子收获了近 700 的高赞和近 300 条激烈辩论。

这看起来像是一场简单的“库多库少”之争,但本质上,它背后是 Rust 这门以“零成本抽象、极致安全”著称的语言,在面对日益猖獗的供应链安全威胁和 Go 语言“开箱即用”的降维打击时,所爆发的一场深刻的身份危机与哲学反思。

“小而美”的代价:悬在每个 Rust 项目头顶的达摩克利斯之剑

长期以来,Rust 社区一直为自己“小核心、强生态”的模式感到自豪。Rust 的标准库(std)极其精简,只提供最基础、最核心的功能。任何稍微高级一点的需求,比如随机数生成、异步运行时、序列化,官方都鼓励你去 crates.io 上找社区“钦定”的“明星库”(Blessed Crates)。

这套模式在早期极大地促进了生态的繁荣。但随着 npm left-pad 事件和各种开源投毒攻击的阴影笼罩全球,这套模式的代价也变得越来越难以承受。

原帖作者一针见血地指出了所有人的噩梦:

“是的,你可以采取各种缓解措施。但等你发现某个藏在你依赖树第三层的、不起眼的包被植入了恶意软件时,你的服务器密钥可能早就被偷光了!”

评论区里的一位开发者用一句话概括了所有人的痛点:

“我完全同意。有时候 std 里就是缺了那么一点至关重要的东西。我能理解这背后的原因,但为了生成一个随机数就要去装一个第三方包,这实在有点小题大做了。”

这正是 Rust 开发者面临的尴尬:当你只是想生成一个 UUID,或者发起一个 HTTP 请求时,你被迫要对 rand、reqwest、tokio 这些由社区个人或小团体维护的库,付出与 Rust 官方核心团队同等级别的“信任”。

而这种信任,正在变得越来越昂贵和危险。

隔壁的诱惑:Go 语言的“大一统”模式

在这场大讨论中,一个名字被反复提及,它就是 Go 语言。

Go 从诞生之初,就选择了与 Rust 截然相反的“自带电池(Batteries Included)”哲学。

  • 你想做 Web 开发?net/http 原生支持,性能强大到可以直接裸奔在生产环境。
  • 你想做 JSON/XML 解析?encoding/json(以及实验性的encoding/json/v2)、encoding/xml 是标配。
  • 你想做并发?goroutine 和 channel 是语言级原生特性。
  • 你想生成随机数?math/rand、crypto/rand 随便用。

评论区里,一位 Rust 开发者的对比极其扎心:

“把恶意代码偷偷塞进一个(流行的)Crate 的第四层依赖里,比把它塞进 Rust 的 std 里要容易得多。”

Go 语言通过一个庞大、稳定、由官方核心团队直接维护的标准库,为开发者提供了一道坚固的“安全护城河”。你可以在不引入任何一个第三方依赖的情况下,构建出一个功能极其完备、性能强大的高并发网络服务。

这种“开箱即用”的安全感和便利性,对于那些深受供应链安全审计折磨的企业开发者来说,是致命的诱惑。

社区的挣扎:当“保守”成为“瓶颈”

面对社区的“呐喊”,Rust 核心团队的成员和社区大佬们也纷纷下场,给出了极其理性和深刻的解释。他们的回复,揭示了 Rust 在标准库扩张上,面临的“三重枷锁”。

枷锁一:向后兼容性的“诅咒”

一位核心成员引用了 Python 社区的一句名言:

“标准库,是模块最终的坟场(The standard library is where modules go to die)。”

一旦一个 API 进入了 std,它就必须背上永不破坏向后兼容的沉重承诺。哪怕 10 年后发现这个设计有缺陷,也只能眼睁睁地看着它腐烂,或者推出一个 urllib2、urllib3 这样极其丑陋的补丁。

Rust 团队宁愿让这些库在社区里自由进化、大浪淘沙,等到它们的设计真正成熟、稳定到可以“永恒”时,再考虑纳入 std。比如 once_cell 和最新的 rand(目前在 nightly 版本中)。

枷锁二:无休止的“维护地狱”

另外一名核心成员指出,将一个库纳入 std,意味着它的维护成本将全部转移到人数本就捉襟见肘的官方维护者身上。而在社区,每个 Crate 都有自己专门的维护者。这是两种完全不同的成本模型。

枷锁三:设计的“过早僵化”

最典型的例子就是异步。原帖作者提议:“Rust 能不能偷一下 Zig 的 IO 思想,这样我们就不需要在 Tokio 和 non-Tokio 生态之间分裂了?”

一位社区大佬立刻反驳:Zig 没有 Rust 的 Send/Sync 标记,两者的异步模型有本质区别。Rust 的异步生态之所以看起来“分裂”,恰恰是语言给了开发者在不同场景下做最优选择的自由。如果过早地在 std 里统一一个官方运行时,反而会扼杀创新。

破局之路:从“大一统”到“邦联制”

在这场激烈的辩论中,一些极具建设性的“折中方案”也开始浮现。这或许预示着 Rust 未来的演进方向。

方案一:官方背书的“准标准库(Semi-official)”

一位开发者提出,Rust 项目组可以借鉴 C++ Boost 库的模式,官方接管 serde、rand、tokio 这些“钦定”的明星库,将它们纳入一个统一的 extd (extended) 命名空间下。

use extd::regex::Regex;
use extd::rand;

这并不会增加 std 的体积,但给了这些库一个“官方认证”的金字招牌,极大地解决了开发者的信任和审计问题。

方案二:引入“孵化期(Incubation Phase)”

一位开发者建议,应该有一个更明确的孵化流程,让那些有潜力进入 std 的库,先在一个类似 Go golang.org/x 的“实验场”里进行检验,而不是直接从某个个人开发者仓库里一步登天。

方案三:强化 Cargo 的安全审计能力

一些核心成员则认为,问题的根源不在于 std 的大小,而在于 crates.io 的分发机制不够安全。与其“因噎废食”地把所有东西都塞进 std,不如去建立更强大的包安全审计机制,比如:

  • 发布隔离期:新发布的包必须经过 72 小时自动化扫描才能被下载。
  • 签名与信任链:通过 cargo 增强包签名和审计者签名,让企业可以选择只使用“可信审计者”批准的依赖列表。

小结:一场关于“灵魂”的拷问

这场由“非主流观点(Unpopular Opinion)”引发的大讨论,表面上是在争论标准库的大小,但其核心,却是一场关于 Rust 与 Go 两种截然不同建国哲学的灵魂拷问。

  • Go 语言,像一个大一统的、中央集权的帝国。它为你提供了从道路、货币到度量衡的一切基础设施。你享受着极高的安全感和便利性,代价是必须忍受它某些时候的“独裁”与“不灵活”。
  • Rust 语言,则更像一个松散的、充满活力的城邦联盟。官方只提供最基础的法律和军队,剩下的一切都交给各个城邦(Crates)自由发展。你拥有无与伦比的自由和选择权,代价是你必须自己承担选择的风险,并时刻提防“外敌入侵”(供应链攻击)。

这两种哲学没有绝对的优劣,只有不同场景下的取舍。

但 Rust 社区的这场“请愿”,无疑为我们所有技术人敲响了警钟:在软件供应链日益脆弱的今天,一个强大、可靠、由顶级专家背书的“官方基础设施”,其价值正在被无限放大。

或许,Rust 的未来,真的需要在“自由”与“安全”之间,找到一个新的平衡点。而隔壁 Go 的作业,他们可能真的需要抄一抄了。

资料链接:https://www.reddit.com/r/rust/comments/1seu7p2/unpopular_opinion_rust_should_have_a_larger/


今日互动探讨:

在你的日常开发中,你是更喜欢 Go 这种“自带电池”的大标准库模式,还是 Rust 这种“小核心+强生态”的自由模式?你是否也曾因为“拉了一堆三方库”而感到安全焦虑?

欢迎在评论区分享你的看法!


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

© 2026, bigwhite. 版权所有.

🔲 ☆

Unity 游戏的 Google Play 16 kb页面对齐处理

16 KB 内存页面大小的支持,是Google Play 新提出的要求。要在2026年5月31日之前,满足这一条件:

https://play.google.com/console/u/0/developers/9060101706093336387/app/4974577679306649072/policy-center/issues/4988764664083295045/details

Android 15 的 16KB page 检测主要看三件事:

  1. PT_LOAD >= 16 KB
  2. RELRO 必须在 segment 末尾(suffix)
  3. RELRO end 必须 16KB 对齐

对于aab 来说,上面的三个要求,一般只用关注PT_LOAD。因为aab 有压缩率,所以关于压缩的两项检验直接计算是不准确的。Google Play 在分发时会自动处理。

官方文档:

https://developer.android.com/guide/practices/page-sizes?utm_source=chatgpt.com&hl=zh-cn

检测方式

关于so 文件是否合规的检测,主要有以下几种方法:

方法一

可以解压出so文件,然后使用ndk 工具检测(一定使用PowerShell,cmd无法运行):

1
llvm-objdump.exe -p E:\Test\so\arm64-v8a\libanogs.so | Select-String -Pattern "LOAD"

运行结果可以看LOAD off 类型行尾是不是带有212、213。
低于2**14的so文件都不符合。

工具的路径在:

1
{YourNDKPath}\toolchains\llvm\prebuilt\windows-x86_64\bin

但是这里只检查了PT_LOAD。

方法二

Google 官方提供了检测工具:

https://cs.android.com/android/platform/superproject/main/+/main:system/extras/tools/check_elf_alignment.sh?hl=zh-cn

可以直接使用这个脚本进行检测。

1
check_elf_alignment.sh APK_NAME.apk

方法三

最后,如果将Android Studio 更到最新版本,即可使用Apk Analyze 功能进行检测。

前者报错为:

1
4 KB LOAD section alignment, but 16 KB is required

后者报错为:

1
RELRO is not a suffix and its end is not 16 KB aligned

适配方法

Unity 相关so

Unity相关的so,需要通过提升Unity 编辑器版本号的方式进行解决。

第三方SDK

第三方SDK 相关的so,需要联系相关提供商进行SDK 升级。

如果无法完成合规,应该考虑取消对该插件的接入。

自己编译的so

如果是自己使用NDK 编译的so,需要升级NDK工具并在编译中指定相关参数。

1
2
3
4
5
6
7
({NDK Path}/ndk-build ^
NDK_PROJECT_PATH=. ^
APP_BUILD_SCRIPT=Android.mk ^
NDK_APPLICATION_MK=Application.mk ^
APP_ABI="armeabi-v7a x86 arm64-v8a x86_64" ^
APP_LDFLAGS="-Wl,-z,max-page-size=16384 -Wl,-z,common-page-size=16384" ^ // 新增
|| pause) && pause

对于Android.mk 文件和Application.mk 文件也有内容需要修改:

如果旧内容涉及cmd-strip,在打包时会报错。这是因为NDK r23+中,strip 工具路径变成 LLVM 版本。最简单的解决方式是删掉这一行,新版本不需要手动调用strip,会自动调用。

如果文件中存在:APP_STL := gnustl_static,也需要进行修改。
修改为:APP_STL := c++_static

为了兼容RELRO 必须在 segment 末尾(suffix)和 RELRO end 必须 16KB 对齐,需要在Android.mk 中增加:

1
2
LOCAL_LDFLAGS += -Wl,-z,max-page-size=16384
LOCAL_LDFLAGS += -Wl,-z,common-page-size=16384

最后,在旧的代码中,如果使用了PAGE_SIZE / PAGE_MASK 宏,这会报错,在新版本中,NDK 不再提供。
需要增加以下内容:

1
2
3
4
5
6
7
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif

#ifndef PAGE_MASK
#define PAGE_MASK (~(PAGE_SIZE - 1))
#endif

3. il2cpp处理方式

升级到新版Unity之后,如果不修改代码,可能不会让C++代码重新导出。

也可以删除Library目录下的Bee目录和所有的il2cpp_*目录的缓存,强制生成。

🔲 ☆

关于Unity China的后续记录

这是利用海外服务器做出的log记录,我们可以很清楚的看到请求的变化情况

此处记录仅作为存档,置信度请自行判断,不作为任何事件分析的直接依据

以下log记录于2026.3.13

追踪记录

sakura@Debian:~$ wget https://download.unity3d.com/download_unity/4016570cf34f/Windows64EditorInstaller/UnitySetup64-2021.3.16f1.exe
--2026-03-12 23:57:43--  https://download.unity3d.com/download_unity/4016570cf34f/Windows64EditorInstaller/UnitySetup64-2021.3.16f1.exe
Resolving download.unity3d.com (download.unity3d.com)... 23.60.96.233, 23.43.50.13, 2600:1417:4400:10::1731:6837, ...
Connecting to download.unity3d.com (download.unity3d.com)|23.60.96.233|:443... connected.
HTTP request sent, awaiting response... 302 Moved Temporarily
Location: https://download.unitychina.cn/download_unity/4016570cf34f/Windows64EditorInstaller/UnitySetup64-2021.3.16f1.exe [following]
--2026-03-12 23:57:43--  https://download.unitychina.cn/download_unity/4016570cf34f/Windows64EditorInstaller/UnitySetup64-2021.3.16f1.exe
Resolving download.unitychina.cn (download.unitychina.cn)... 116.153.76.58
Connecting to download.unitychina.cn (download.unitychina.cn)|116.153.76.58|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2441942336 (2.3G) [application/x-ms-dos-executable]
Saving to: ‘UnitySetup64-2021.3.16f1.exe’

UnitySetup64-2021.3.16f1.exe                              0%[                                                                                                                              ]   3.48M  3.38MB/s

Azure, Microsoft Data Center, Hong Kong SAR, China

此处我们可以看到Unity将请求从download.unity3d.com使用302重定向回到了download.unitychina.cn


root@instance:~# wget https://download.unity3d.com/download_unity/4016570cf34f/Windows64EditorInstaller/UnitySetup64-2021.3.16f1.exe
--2026-03-13 00:07:08--  https://download.unity3d.com/download_unity/4016570cf34f/Windows64EditorInstaller/UnitySetup64-2021.3.16f1.exe
Resolving download.unity3d.com (download.unity3d.com)... 23.220.73.210, 23.220.73.163, 2600:1405:8400:e::173e:e2ed, ...
Connecting to download.unity3d.com (download.unity3d.com)|23.220.73.210|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2424305752 (2.3G) [application/x-msdos-program]
Saving to: ‘UnitySetup64-2021.3.16f1.exe’

UnitySetup64-2021.3.16f1.exe              100%[====================================================================================>]   2.26G  28.7MB/s    in 93s

2026-03-13 00:08:42 (24.9 MB/s) - ‘UnitySetup64-2021.3.16f1.exe’ saved [2424305752/2424305752]

Oracle Data Center, Phoenix, AZ, USA

而对于美国的这台服务器来说,请求从始至终都是在download.unity3d.com,没有经过任何的302重定向

附言

考虑到Unity在国内毕竟已经单独分了个团结引擎出来,搞点小动作还是正常的

只不过对于咱们这些必须用Unity的开发的群体来说,这就有点难受了,有些坑也必须要小心一些

unity china看起来也不是替换了所有的安装包,但今后出于稳妥考虑还是应该优先使用别的渠道分发的离线包比较好

🔲 ☆

使用贝塞尔曲线实现道具随机飞动效果

工作中,遇到了一个需求是要实现获得道具和货币的飞动效果:

  1. 根据道具的多少生成不同数目的道具
  2. 货币首先要有一个炸开的效果,然后向一个点汇集;道具从原位置沿直线飞过去
  3. balabala

这里只讨论货币飞行路线的问题。

贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。PS中的钢笔工具就是使用贝塞尔曲线来绘制矢量曲线的。

这里使用简单的,四个点确定的贝塞尔曲线来实现飞行轨迹。首先p0和p3分别是移动的起点和终点。为了模拟爆炸效果,可以通过随机选取以p0为圆心,长度为R的圆上的点,调整曲线的第一次弯折位置实现。这一点定为P1。最后,根据P1点和P0点之间的关系,进行x轴或者y轴的修正,实现曲线平滑延申到p3,这一点是p2点。

p1点(爆炸点)的获取方式:

1
2
3
4
5
private static Vector3 GetRandomPosition(Vector3 vec)
{
float sita = UnityEngine.Random.Range(-Mathf.PI, Mathf.PI);
return new Vector3(vec.x + Mathf.Cos(sita) * Screen.width / 8, vec.y + Mathf.Sin(sita) * Screen.width / 8);
}

p2点(修正点)的获取方式:

1
2
3
4
private static Vector3 GetFixedPoint(Vector3 start, Vector3 end)
{
return new Vector3(start.x + 3*(end.x - start.x)/4, start.y + 3 * (end.y - start.y) / 4 + p1.y > start.y ? Screen.height / 15 : - Screen.height / 15);
}

根据已知的四个点,实现物体延贝塞尔曲线移动的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/// <summary>
/// 延贝塞尔曲线移动
/// </summary>
private static IEnumerator MoveBezier(GComponent gcom, float time, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
{
for (float t = 0; t < time; t += Time.deltaTime)
{
gcom.position = CalculateCubicBezierPoint(t / time, p0, p1, p2, p3);
yield return 0;
}
gcom.position = p3;
gcom.Dispose();
}

/// <summary>
/// 计算贝塞尔曲线点的坐标
/// </summary>
private static Vector3 CalculateCubicBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
{
float u = 1 - t;
float tt = t * t;
float uu = u * u;
float uuu = uu * u;
float ttt = tt * t;

Vector3 p = uuu * p0;
p += 3 * uu * t * p1;
p += 3 * u * tt * p2;
p += ttt * p3;

return p;
}
🔲 ☆

U3D问题总结(六) 优化

请简述GC(垃圾回收)产生的原因,并描述如何避免(?

GC回收堆上的内存
避免:1.减少new产生对象的次数
2.使用公用的对象(静态成员)
3.将String换为StringBuilder

如何优化内存?

1.压缩自带类库;
2.将暂时不用的以后还需要使用的物体隐藏起来而不是直接Destroy掉;
3.释放AssetBundle占用的资源;
4.降低模型的片面数,降低模型的骨骼数量,降低贴图的大小;
5.使用光照贴图,使用多层次细节(LOD),使用着色器(Shader),使用预设(Prefab)。
6.代码中少产生临时变量

UNITY3d在移动设备上的一些优化资源的方法

1.使用assetbundle,实现资源分离和共享,将内存控制到200m之内,同时也可以实现资源的在线更新
2.顶点数对渲染无论是cpu还是gpu都是压力最大的贡献者,降低顶点数到8万以下,fps稳定到了30帧左右
3.只使用一盏动态光,不是用阴影,不使用光照探头
粒子系统是cpu上的大头
4.剪裁粒子系统
5.合并同时出现的粒子系统
6.自己实现轻量级的粒子系统
animator也是一个效率奇差的地方
7.把不需要跟骨骼动画和动作过渡的地方全部使用animation,控制骨骼数量在30根以下
8.animator出视野不更新
9.删除无意义的animator
10.animator的初始化很耗时(粒子上能不能尽量不用animator)
11.除主角外都不要跟骨骼运动apply root motion
12.绝对禁止掉那些不带刚体带包围盒的物体(static collider )运动
NUGI的代码效率很差,基本上runtime的时候对cpu的贡献和render不相上下
13每帧递归的计算finalalpha改为只有初始化和变动时计算
14去掉法线计算
15不要每帧计算viewsize 和windowsize
16filldrawcall时构建顶点缓存使用array.copy
17.代码剪裁:使用strip level ,使用.net2.0 subset
18.尽量减少smooth group
19.给美术定一个严格的经过科学验证的美术标准,并在U3D里面配以相应的检查工具

场景优化

1.遮挡剔除(Occlusion Culling) 不显示被遮挡住的物体
2.LOD 根据相机距离远近显示不同精细程度的模型
3.大场景可以调节相机可视距离
4.小物体可以适当隐藏掉
5.使用光照贴图 避免动态实时的进行光照计算,提高效率

UI优化

1.将同一画面图片放到同一图集中
2.图片和文字尽量不要交叉,会产生多余drawcall(相同材质和纹理的UI元素是可以合并的)
3.UI层级尽量不要重叠太多
4.取消勾选不必要的射线检测RaycastTarget
5.将动态的UI元素和静态的UI元素放在不同的Canvas中,减少canvas网格重构频率

GC优化

1.字符串使用StringBuilder而不是string,stringBuilder在创建时会自动获取一个容量存储并逐渐扩充,string每一次改变都会创建一个新的对象。
2.访问物体tag的时候尽量使用Gameobject.CompareTag(),因为访问物体的tag属性会在堆上额外的分配空间
3.使用对象池缓存大量创建的物体
4.用for代替foreach,foreach每次迭代产生24字节垃圾内存

🔲 ☆

U3D问题总结(五) 渲染与光照

什么是渲染管道(?

是指在显示器上为了显示出图像而经过的一系列必要操作。 渲染管道中的很多步骤,都要将几何物体从一个坐标系中变换到另一个坐标系中去。主要步骤有:
本地坐标->视图坐标->背面裁剪->光照->裁剪->投影->视图变换->光栅化

对渲染管线的理解

渲染流水线流程:
1.应用阶段(由CPU负责,输出是渲染所需要的几何信息,即渲染图元。然后发起Draw Call,进入GPU流水线)
2.几何阶段(由GPU负责,处理渲染图元,这阶段中最重要的就是把顶点坐标变换屏幕空间中,交给光栅器处理
这阶段输出的是屏幕空间中二维顶点坐标、每个顶点对应的深度值、着色等相关信息)
3.光栅化阶段(由GPU,负责这一阶段会使用上个阶段传递的数据来产生屏幕上的像素,并渲染出最终的图像)
细节过程https://zhuanlan.zhihu.com/p/97498781

什么是DrawCall?DrawCall高了又什么影响?如何降低DrawCall?

Unity中,每次引擎准备数据并通知GPU的过程称为一次Draw Call。DrawCall越高对显卡的消耗就越大。降低DrawCall的方法:

  1. Dynamic Batching
  2. Static Batching
  3. 高级特性Shader降级为统一的低级特性的Shader。

什么是DrawCall,有什么方法可以减少DrawCall

CPU通过调用绘制命令来告诉GPU开始进行一个渲染过程(一次DrawCall)。
CPU方面减少DrawCall:
1、使用Draw Call Batching 、Dynamic Batching动态批处理
2、纹理打包成图集减少材质使用
3、少用反光、阴影
4、设置一个合适的Fixed Timestep
5、不要使用网格碰撞器(Mesh Collider)
6、大量或频繁的字符串连接操作一定要用StringBuilder
7、某些可能情况,使用结构体代替类
8、使用对象池重复利用空间
9、尽量不要用foreach,用for
10、不要直接访问GameObjcet的tag属性
11、不要频繁使用GetComponent,访问一次后保留其引用
12、使用OnBecameVisible()和OnBecameInVisible(),控制物体update()函数的执行减少开销
13、使用内建数组,如Vector3.zero而不是new Vector3(0,0,0)
14、使用ref关键字对方法的参数进行优化
15、关闭所有update中的log操作
16、不在update中调用GetComponent、SendMessage、FindWithTag等方法
17、不在update中使用临时变量

GPU方面减少DrawCall

1、使用纹理图集代替一系列单独小贴图
2、保持材质数目尽可能少
3、如果使用纹理图集和共享材质,用Renderer.sharedMaterial代替Renderer.material
4、使用光照纹理(lightmap)而非实时灯光
5、使用LOD
6、使用mobile版的shader
7、尽可能减少顶点数、背面删减
8、压缩图片,减少显存带宽压力

什么是material,什么是shader,二者有什么关系

材质系统定义了如何渲染物件表面信息。shader里面使用材质信息加自身操作,最终呈现物体渲染。shader是material一部分,是根据计算即时演算生成贴图的程序,叫着色器。常用处理无法用固定贴图表现的模型。material是模型的材质,包含贴图、shader、顶点、凹凸等信息。

如何在Unity3D中查看场景的面数,顶点数和Draw Call数?如何降低Draw Call数

在Game视图右上角点击Stats。降低Draw Call 的技术是Draw Call Batching
这个在5.0以后在window-》Profiler下面,快捷键是cmd + 7(ctl + 7

DrawCall和SetPass Call

DrawCall:meshes网格绘制应用批处理后的总数。请注意,在多次呈现对象(例如,由像素灯照明的对象),每个在一个单独的渲染结果绘制调用。

SetPass Call:渲染改变( passes)次数。每个改变 需要Unity运行时绑定一个新的渲染器(shader),它可能会引入 CPU 开销。

Unity3D Shader分哪几种,有什么区别

表面着色器的抽象层次比较高,它可以轻松地以简洁方式实现复杂着色。表面着色器可同时在前向渲染及延迟渲染模式下正常工作。
顶点片段着色器可以非常灵活地实现需要的效果,但是需要编写更多的代码,并且很难与Unity的渲染管线完美集成。
固定功能管线着色器可以作为前两种着色器的备用选择,当硬件无法运行那些酷炫Shader的时,还可以通过固定功能管线着色器来绘制出一些基本的内容。

有A和B两组物体,有什么办法能够保证A组物体永远比B组物体先渲染?

把A组物体的渲染队列大于B物体的渲染队列,通过shader里面的渲染队列来渲染

问一个Terrain,分别贴3张,4张,5张地表贴图,渲染速度有什么区别?为什么?

没有区别,因为不管几张贴图只渲染一次。

LOD是什么,优缺点(?

LOD(Level of detail)多层次细节,是最常用的游戏优化技术。它按照模型的位置和重要程度决定物体渲染的资源分配,降低非重要物体的面数和细节度,从而获得高效率的渲染运算。缺点是增加了内存。

MipMap是什么,作用(?

MipMapping:在三维计算机图形的贴图渲染中有常用的技术,为加快渲染进度和减少图像锯齿,贴图被处理成由一系列被预先计算和优化过的图片组成的文件,这样的贴图被称为MipMap。

什么是LightMap

LightMap:就是指在三维软件里实现打好光,然后渲染把场景各表面的光照输出到贴图上,最后又通过引擎贴到场景上,这样就使物体有了光照的感觉。

alpha blend工作原理

Alpha Blend 实现透明效果,不过只能针对某块区域进行alpha操作,透明度可设。

alpha blend 用于做半透明效果。Color = (源颜色 源系数) OP ( 目标颜色 目标系数);其中OP(混合方式)有加,减,反减,取最小,取最大

Unity的Shader中,Blend SrcAlpha OneMinusSrcAlpha这句话是什么意思

作用就是Alpha混合。公式:最终颜色 = 源颜色 x 源透明值 + 目标颜色 x(1 - 源透明值)

alpha test在何时使用?能达到什么效果

Alpha Test ,中文就是透明度测试。简而言之就是V&F shader中最后fragment函数输出的该点颜色值(即上一讲frag的输出half4)的alpha值与固定值进行比较。AlphaTest语句通常于Pass{}中的起始位置。Alpha Test产生的效果也很极端,要么完全透明,即看不到,要么完全不透明。

Vertex Shader是什么,怎么计算

顶点着色器是一段执行在GPU上的程序,用来取代fixed pipeline中的transformation和lighting,Vertex Shader主要操作顶点。
Vertex Shader对输入顶点完成了从local space到homogeneous space(齐次空间)的变换过程,homogeneous space即projection space的下一个space。在这其间共有world transformation, view transformation和projection transformation及lighting几个过程。

写出光照计算中的diffuse(漫反射)的计算公式

diffuse = Kd x colorLight x max(N*L,0);
Kd 漫反射系数、colorLight 光的颜色、N 单位法线向量、L 由点指向光源的单位向量、其中N与L点乘,如果结果小于等于0,则漫反射为0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
答:漫反射光(diffuse)计算公式为:Idiffuse = Dintensity*Dcolor*N.L ; (Dintensity表示漫反射强度,Dcolor表示漫反射光颜色,N为该点的法向量,L为光源向量)

其他,3D渲染中,物体表面的光照计算公式为:

I = 环境光(Iambient) + 漫反射光(Idiffuse) + 镜面高光(Ispecular);

其中,环境光(ambient)计算公式为:

Iambient= Aintensity* Acolor; (Aintensity表示环境光强度,Acolor表示环境光颜色)

漫反射光(diffuse)计算公式为:

Idiffuse = Dintensity*Dcolor*N.L ; (Dintensity表示漫反射强度,Dcolor表示漫反射光颜色,N为该点的法向量,L为光源向量)

镜面光照(specular)计算公式为:

Ispecular = Sintensity*Scolor*(R.V)n; (Sintensity表示镜面光照强度,Scolor表示镜面光颜色,R为光的反射向量,V为观察者向量)

综上所得:整个光照公式为:

I = Aintensity* Acolor+ Dintensity*Dcolor*N.L + Sintensity*Scolor*(R.V)n ;

将一些值合并,并使用白色作为光照颜色,则上述公式可简化为:

I = A + D*N.L + (R.V)n

MeshRender中material和sharedmaterial的区别(?

修改sharedMaterial将改变所有物体使用这个材质的外观,并且也改变储存在工程里的材质设置。不推荐修改由sharedMaterial返回的材质。如果你想修改渲染器的材质,使用material替代。

简述水面倒影的渲染原理

原理就是对水面的贴图纹理进行扰动,以产生波光玲玲的效果。用shader可以通过GPU在像素级别作扰动,效果细腻,需要的顶点少,速度快

什么叫动态合批?跟静态合批有什么区别

如果动态物体共用着相同的材质,那么Unity会自动对这些物体进行批处理。动态批处理操作是自动完成的,并不需要你进行额外的操作。
区别:动态批处理一切都是自动的,不需要做任何操作,而且物体是可以移动的,但是限制很多。静态批处理:自由度很高,限制很少,缺点可能会占用更多的内存,而且经过静态批处理后的所有物体都不可以再移动了。

两种阴影判断的方法、工作原理。

本影:景物表面上那些没有被光源直接照射的区域(全黑的轮廓分明的区域)。
半影:景物表面上那些被某些特定光源直接照射但并非被所有特定光源直接照射的区域(半明半暗区域)
工作原理:从光源处向物体的所有可见面投射光线,将这些面投影到场景中得到投影面,再将这些投影面与场景中的其他平面求交得出阴影多边形,保存这些阴影多边形信息,然后再按视点位置对场景进行相应处理得到所要求的视图(利用空间换时间,每次只需依据视点位置进行一次阴影计算即可,省去了一次消隐过程)

Unity提供了几种光源,分别是什么

四种。
平行光:Directional Light
点光源:Point Light
聚光灯:Spot Light
区域光源:Area Light

实时点光源的优缺点是什么

可以有cookies – 带有 alpha通道的立方图(Cubemap )纹理。点光源是最耗费资源的。

GPU的工作原理

简而言之,GPU的图形(处理)流水线完成如下的工作:(并不一定是按照如下顺序) 顶点处理:这阶段GPU读取描述3D图形外观的顶点数据并根据顶点数据确定3D图形的形状及位置关系,建立起3D图形的骨架。在支持DX8和DX9规格的GPU中,这些工作由硬件实现的Vertex Shader(定点着色器)完成。 光栅化计算:显示器实际显示的图像是由像素组成的,我们需要将上面生成的图形上的点和线通过一定的算法转换到相应的像素点。把一个矢量图形转换为一系列像素点的过程就称为光栅化。例如,一条数学表示的斜线段,最终被转化成阶梯状的连续像素点。 纹理帖图:顶点单元生成的多边形只构成了3D物体的轮廓,而纹理映射(texture mapping)工作完成对多变形表面的帖图,通俗的说,就是将多边形的表面贴上相应的图片,从而生成“真实”的图形。TMU(Texture mapping unit)即是用来完成此项工作。 像素处理:这阶段(在对每个像素进行光栅化处理期间)GPU完成对像素的计算和处理,从而确定每个像素的最终属性。在支持DX8和DX9规格的GPU中,这些工作由硬件实现的Pixel Shader(像素着色器)完成。 最终输出:由ROP(光栅化引擎)最终完成像素的输出,1帧渲染完毕后,被送到显存帧缓冲区。
总结:GPU的工作通俗的来说就是完成3D图形的生成,将图形映射到相应的像素点上,对每个像素进行计算确定最终颜色并完成输出。

图形学

光照模型有哪些

3维模型组成

Mesh

Mesh下面有哪些字段

如向将文理贴在模型上

图片向格式有那些

🔲 ☆

U3D问题总结(四) 物理相关

射线检测碰撞物的原理是

射线是3D世界中一个点向一个方向发射的一条无终点的线,在发射轨迹中与其他物体发生碰撞时,它将停止发射 。

Unity3d中的碰撞器和触发器的区别?

碰撞器是触发器的载体,触发器是碰撞器的属性
Is Trigger=false,碰撞器根据物理引擎引发碰撞,产生碰撞的效果
此时调用OnCollisionEnter/Stay/Exit函数
Is Trigger=true,碰撞器被物理引擎所忽略,没有碰撞效果
此时调用OnTriggerEnter/Stay/Exit函数

发生碰撞的必要条件

两个物体都必须带有碰撞器(Collider)
其中至少有一个物体带有刚体(Rigidbody)或者角色控制器(CharacController)
必须是运动的物体带有Rigidbody脚本才能检测到碰撞

物体发生碰撞的过程有几个阶段

1.OnCollisionEnter
2.OnCollisionStay
3.OnCollisionExit

当一个细小的高速物体撞向另一个较大的物体时,会出现什么情况?如何避免?

穿透(碰撞检测失败)
1)增大细小物体的碰撞体(不建议这样做)
(2)使用射线检测,检测他们之间的距离
(3)FixedUpdate频率修改,可以physics time减小(同样不建议)
(4)改变物体的速度(废话)
(5)将检测方式改为连续检测,rigifdbody.collisionDetectionMode =CollisionDetectionMode.Continuous;
或者是动态连续检测(CollisionDetectionMode.ContinuousDynamic)
(6)代码限制,加大计算量 提前计算好下一个位置

Unity3d物理引擎中,有几种施加力的方式(?

rigidbody.AddForce/AddForceAtPosition,都在rigidbody系列函数中

CharacterController和Rigdibody的区别

Rigidbody:刚体组件、用于模拟真实的物理效果、可以受到重力和其他力的作用、这个力可以直接施加、也可以来自其他刚体的碰撞
CharacterController:角色控制组件,它自带一个胶囊控制器,能够受到重力的影响。移动时使用自身的Move()、SimpleMove()方法

Rigidbody具有完全真实物理的特性,Unity中物理系统最基本的一个组件,包含了常用的物理特性,而CharacterController可以说是受限的的Rigidbody,具有一定的物理效果但不是完全真实的,是Unity为了使开发者能方便的开发第一人称视角的游戏而封装的一个组件

什么叫做链条关节(?

Hinge Joint,可以模拟两个物体间用一根链条连接在一起的情况,能保持两个物体在一个固定距离内部相互移动而不产生作用力,但是达到固定距离后就会产生拉力。

🔲 ☆

U3D问题总结(三) Unity基础

Unity基础

Unity和Android与iOS如何交互

Unity可以导出安卓和IOS的工程。导出的工程有Unity封装的方法,可以通过发消息的方式跟Unity进行交互。其中,C#不能与OC直接进行交互,需要用C++去写。

Unity3D支持的作为脚本的语言

C#、JS、Boo

Unity中用过哪些插件?具体功能

NGUI,制作2D界面

Helpshift,帮助与提示

KTPlay,游戏论坛

UniClipboard,粘贴板

Unity中常用的插件(Δ)

Unity引擎使用的是左手坐标系还是右手坐标系(A)

A.左手坐标系 B.右手坐标系
C.可以通过ProjectSetting切换右手坐标系 D.可以通过Reference切换左手坐标系

什么是导航网格(NavMesh)(B)

A.一种用于描述相机轨迹的网格 B.一种用于实现自动寻址的网格
C.一种被优化过的物体网格 D.一种用于物理碰撞的网格

生命周期

OnEnable、Awake、Start运行时发生的顺序,哪些可以在同一周期中重复发生

Awake->OnEnable->Start
OnEnable可以在同一周期中重复发生

生命周期顺序

Awake——>OnEnable–>Start——>Update——>FixedUpdate——>LateUpdate——>OnGUI——>OnDisable——>OnDestroy

Awake() 脚本唤醒,系统执行的第一个方法,用于脚本初始化,只执行一次。

Start()在Awake之后、Update之前执行,只执行一次。

Update()用于逻辑正常更新,每帧由系统自动调用一次。

FixedUpdate()固定更新。

LateUpdate()推迟更新,每帧调用,在Update之后调用。

OnGUI() 每帧可能会被绘制多次,每次对应于一个 GUI event

OnDestroy()当前脚本销毁时调用。

12.以下关于 MonoBehaviour.OnGUI()的描述错误的是(D)

A.如果 MonoBehaviour 没有被启用,则OnGUI函数不会被调用
B.用于绘制和处理 GUI events
C.每帧可能会被绘制多次,每次对应于一个 GUI event
D.每帧被调用一次

Addcomponent后哪个生命周期函数会被调用?

对于AddComponent添加的脚本,其Awake,Start,OnEnable是在Add的当前帧被调用的,其中Awake,OnEnable与AddComponent处于同一调用链上,Start会在当前帧稍晚一些的时候被调用。Update则是根据Add调用时机决定何时调用:如果Add是在当前帧的Update前调用,那么新脚本的Update也会在当前帧被调用,否则会被延迟到下一帧调用。
https://blog.csdn.net/qq_32821435/article/details/94760815

物理更新一般放在哪个系统函数里

FixedUpdate。update跟当前平台的帧数有关,而FixedUpdate是真实时间

Update和FixedUpdate的区别?

Update是在每次渲染新的一帧的时候会调用,FixedUpdate,是在固定的时间间隔执行,不受游戏帧率的影响。FixedUpdate的时间间隔可以在项目设置中更改,Edit->ProjectSetting->time 找到Fixedtimestep。

移动相机动作在哪个函数里,为什么在这个函数里

LateUpdate,是在所有的update结束后才调用,比较适合用于命令脚本的执行。官网上例子是摄像机的跟随,都是所有的update操作完才进行摄像机的跟进,不然就有可能出现摄像机已经推进了,但是视角里还未有角色的空帧出现。

关于 MonoBehaviour.LateUpdate 函数描述错误的是:(B)

A.当 MonoBehaviour 类被启用后,每帧调用一次
B.常被用于处理 Rigidbody 的更新
C.在所有 Update 函数执行后才能被调用
D.常被用于实现跟随相机效果,且目标物体的位置已经在 Update 函数中被更新

应该放在FixedUpdate

如何让已经存在的GameObject在LoadLevel后不被卸载掉

1
2
3
4
void Awake()
{
DontDestroyOnLoad(transform.gameObject);
}

脚本及编辑器

命名空间

unityEngne

9.在哪个面板中可以修改物体的空间属性,如位置,朝向,大小等(B)

A.Project B.Inspector C.Hierarchy D.Toolbar

如何为一个Asset 资源设定一个Label,从而能够方便准确的搜索到?(D)

A.在Project窗口中选中一个Asset,右键->Create->Label
B.在Project窗口中选中一个Asset,右键->Add Label
C.在Project窗口中选中一个Asset,在Inspector窗口中点击添加Label的图标
D.在Project窗口中选中一个Asset,在Inspector窗口中点击按钮“Add Label”

5.Application.loadLevel命令为(A)

A.加载关卡 B.异步加载关卡 C.加载动作 D.加载动画

将图片的TextureType选项分别选为“Texture”和“Sprite”有什么区别

Sprite作为UI精灵使用,Texture作用模型贴图使用。Sprite需要2的整次幂,打包图片省资源

为什么dynamic font在unicode环境下优于static font(?

Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。
使用动态字体时,Unity将不会预先生成一个与所有字体的字符纹理。当需要支持亚洲语言或者较大的字体的时候,若使用正常纹理,则字体的纹理将非常大。

OnBecameVisible及OnBecameInvisible的发生时机,以及这一对回调函数的意义

当物体是否可见切换之时。可以用于只需要在物体可见时才进行的计算。

采用Input.mousePosition 来获取鼠标在屏幕上的位置,以下表达正确的是(C)

A.左上角为原点(0,0),右下角为(Screen.Width, Screen.Height)
B.左下角为原点(0,0),右下角为(Screen.Height, Screen.Width)
C.左下角为原点(0,0),右上角为(Screen.Width, Screen.Height)
D.左上角为原点(0,0),右下角为(Screen.Height, Screen.Width)

物体自身旋转使用的函数?

Transform.Rotate()

物体绕某点旋转使用函数叫什么?

transform.RotateAround()

以下选项中,将游戏对象绕Z轴逆时针旋转90度(C)

A.transform.rotation = Quaternion.Euler(0,0,90)
B.transform.rotation = Quaternion.Angle(0,0,90)
C.transform.Rotate(new Vector3(0,0,90))
D.transform.Rotate(new Vector3(90,0,0))

游戏对象B是游戏对象A的子物体,游戏对象A经过了旋转,请写出游戏B围绕自身的Y轴进行旋转的脚本语句,以及游戏对象B围绕世界坐标的Y轴旋转的脚本语句

绕世界坐标旋转:transform.Rotate (transform.upspeedTime.deltatime);
绕自身Y轴旋转:transform.Rotate (Vector.upspeedTime.deltatime);

U3D中用于记录节点空间几何信息的组件名称,及其父类名称

Transform 父类是 Component

关于Vector3 的API,以下说法正确的是(BC)

A.Vector3.normalize 可以获取一个三维向量的法线向量
B.Vector3.magnitude 可以获取一个三维向量的长度
C.Vector3.forward 与 Vector3(0,0,1)是一样的意思
D.Vector3.Dot(向量A,向量B)是用来计算向量A与向量B的叉乘

以下哪个函数在游戏进入新场景后会被马上调用(B)

A.MonoBehaviour.OnSceneWastLoaded()
B.MonoBehaviour.OnSceneEnter()
C.MonoBehaviour.OnLevelEnter()
D.MonoBehaviour.OnLevelWastLoaded()

14.在Unity引擎中,Collider所指的是什么(D)

A.collider 是Unity引擎中所支持的一种资源,可用作存储网格信息
B.Collider 是Unity引擎中内置的一种组件,可用对网格进行渲染
C.Collider 是Unity引擎中所支持的一种资源,可用作游戏对象的坐标转换
D.Collider 是Unity引擎中内置的一种组件,可用作游戏对象之间的碰撞检测

下列选项中,关于Transform组件的Scale参数描述正确的是(A)

A.Transform组件的Scale参数不会影响ParticleSystem产生粒子的大小
B.Transform组件的Scale参数不会影响GUITexture的大小
C.添加Collider组件后的GameoObject,其 Collider 组件的尺寸不受Transform组件的Scale参数影响
D.添加Rigidbody组件后的物体,大小将不再受Transform组件中 Scale 参数的影响

如何销毁一个UnityEngine.Object及其子类

Destroy()方法

DestroyImmediate和Destroy的区别

DestroyImmeditate 销毁对象的时候,会立即释放资源。Destroy只是从该场景销毁,但是还在内存当中。

在编辑场景时将GameObject设置为Static有何作用

设置游戏对象为Static时,这些部分被静态物体挡住而不可见时,将会剔除(或禁用)网格对象。因此,在你的场景中的所有不会动的物体都应该标记为Static。

如何通过脚本来删除其自身对应的Gameobject(A)

A.Destroy(gameObject) B.this.Destroy()
C.Destroy(this) D.其他三项都可以

某个GameObject有一个名为MyScript的脚本,该脚本中有一个名为DoSomething 的函数,则如何在该Gameobject的另外一个脚本中调用该函数?(A)

A.GetComponent().DoSomething()
B.GetComponent
C.GetComponent().Call(“DoSomething”)
D.GetComponent

CompareTag比直接用gameObject.tag要好

简述一下对象池

对象池就存放需要被反复调用资源的一个空间,当一个对象回大量生成的时候如果每次都销毁创建会很费时间,通过对象池把暂时不用的对象放到一个池中(也就是一个集合),当下次要重新生成这个对象的时候先去池中查找一下是否有可用的对象,如果有的话就直接拿出来使用,不需要再创建,如果池中没有可用的对象,才需要重新创建,利用空间换时间来达到游戏的高速运行效果,在FPS游戏中要常被大量复制的对象包括子弹,敌人,粒子等

对象池使用什么数据结构构建

频繁创建GameObject会降低程序性能为什么?怎么解决?

频繁创建游戏对象,会增加游戏的Drawcall数,降低帧率,GPU会一直在渲染绘制。可以通过对象池来管理对象:当需要创建一个游戏对象时,先去对象池中查找一下对象池中是否存在没有被正在使用的对象,如果有的话直接使用这个对象,并把它标记为正在使用,没有话就创建一个,并把它添加到池中,然后标记为使用中。一个游戏对象使用完毕的时候,不要销毁掉,把它放在池中,标记为未使用。

如何在Unity中创建地形系统?(D)

A.Terrain->Create Terrain B.Component->Create Terrain
C.Asset->Create Terrain D.Windows->Create Terrain

资源相关

当删除Unity工程Assets目录下地meta文件时会导致什么?为什么?

会导致在场景中游戏对象看不到,或者报错,材质找不到资源。多人协作的时候会导致资源的重复产生。因为每个资源文件都对应一个.meta文件,这个.meta文件中的guid就是唯一标识这个资源的。材质就是通过这个guid来记录自己使用了那些资源,而且同一个资源的guid会因为不同的电脑而不同,所以当你上传了丢失了.meta文件的资源的时候,到了别人的机器上就会重新产生guid,那个这个资源就相当于垃圾了。

meta文件的作用

prefab的作用

  1. 在游戏运行时实例化。Prefab相当于一个模板,对已有的素材、脚本和参数做一个基础的配置,便于以后的修改
  2. Prefab打包的内容简化了导出操作,便于团队协同

下列叙述中有关 Prefab 说法错误的是哪一项(B)

A.Prefab 是一种资源类型 B.Prefab 是一种可以反复使用的游戏对象
C.Prefab 可以多次在场景进行实例 D.当一个 Prefab 添加到场景中时,也就是创建了它的一个实例

资源加载方式

1.Resources
2.AssetBundle
3.AssetDatabase

资源数据库 (AssetDatabase)

资源数据库 (AssetDatabase) 是允许您访问工程中的资源的 API。此外,其提供方法供您查找和加载资源,还可创建、删除和修改资源。Unity 编辑器 (Editor) 在内部使用资源数据库 (AssetDatabase) 追踪资源文件,并维护资源和引用资源的对象之间的关联。Unity 需要追踪工程文件夹发生的所有变化,如需访问或修改资源数据,您应始终使用资源数据库 (AssetDatabase) API,而非文件系统。 资源数据库 (AssetDatabase) 接口仅适用于编辑器,不可用于内置播放器。和所有其他编辑器类一样,其只适用于置于编辑器 (Editor) 文件夹中的脚本(只在主要的资源 (Assets) 文件夹中创建名为“编辑器”的文件夹(不存在该文件夹的情况下))。

什么是AssetBundle?谈谈对AssetBundle内存分配情况的理解

可以把多个游戏对象或资源二进制文件封装到AssetBundle中,提供封装与解包的方法使用很方便。

加载资源三个步骤:

  1. www/LoadFromFile/LoadFromMemory等接口加载AssetBundle本身
  2. AssetBundle.LoadAsset()等接口从AssetBundle中加载资源
  3. 对于GameObject类资源,需要通过GameObject.Instantiate()创建Clone

黑色区域:www类本身占用内存,还保留了一份对WebStream数据的引用。使用www = null或www.dispose()释放。前者等待GC,后者立即释放。释放后WebStream引用计数会减一。

橙色区域:WebStream数据,数据真正的存储区域。AssetBundle被加载进来后,这部分内存就被分配了。包含三个内容:1、压缩后的AssetBundle本身。2、解压后的资源。3、一个解压缓冲区。www或AssetBundle对象都只是有一个结构指向了WebStream数据,从而对外部提供操作真正资源数据的方法。当WebStream数据引用为0时,系统会自动释放。为了不频繁的开辟和销毁解压Buffer,绿色Decompression解压缓冲区Unity会至少保留一份。

粉色区域:AssetBundle对象,引用WebStream数据部分,提供从WebStream数据中加载资源的接口。AssetBundle.Unload(bool unloadAllLoadedObjects)释放资源。AssetBundle.Unload(false)释放AssetBundle对象本身,可能引起WebStream释放,导致无法通过接口或依赖关系从该AssetBundle加载资源,但已加载资源可以正常使用。AssetBundle(true)不仅释放WebStream部分,所有被加载出来的资源将被释放。

红色部分:通过Instantiate()创建的GameObject所包含的资源。这些资源根据类型与AssetBundle原始资源(WebStream资源部分)有不同关系。如Texture、shader资源,通常只是使用,不会做出改动,所以仅仅是引用关系;每个GameObject是特殊的,所以是完全复制一份;Mesh和Material,则是引用+复制的关系。

动态加载资源的方式 区别

1.Resources.Load();
2.AssetBundle

1.通过Resources模块,调用它的load函数:可以直接load并返回某个类型的Object,前提是要把这个资源放在Resource命名的文件夹下,Unity不关有没有场景引用,都会将其全部打入到安装包中。
2.通过bundle的形式:即将资源打成 asset bundle 放在服务器或本地磁盘,然后使用WWW模块get 下来,然后从这个bundle中load某个object。

以下关于WWW.LoadFromCacheOrDownload描述正确的是(C)

A.可被用于将 Text Assets 自动缓存到本地磁盘
B.可被用于将 Resource 自动缓存到本地磁盘
C.可被用于将 Asset Bundles 自动缓存到本地磁盘
D.可被用于将任意格式的Unity资源文件自动缓存到本地磁盘

如何安全地在不同工程间安全地迁移asset数据(?

  1. 将Assets目录和Library目录一起迁移
  2. 导出包,export Package
  3. 用unity自带的assets Server功能 或者meta功能

AssetBundle包加载流程

图集打包怎么分类

1.按业务功能的预制,寻找依赖,收集所有预制引用的图片,
2.如果有多个预制使用了同一张图片,我们就把它扔到common文件夹
3.让图集尽量紧凑,没有太多空白,尽量让图集处于2的n次方大小

为什么Unity3d中会发生在组件上出现数据丢失的情况(?

组件上绑定的物体对象被删除了

UI与Camera

UGUI的Canvas的作用

Canvas画布是承载所有UI元素的区域。所有的UI元素都必须是Canvas的子对象。如果场景中没有画布,那么我们创建任何一个UI元素,都会自动创建画布,并且将新元素置于其下。

创建Canvas:GameObject->UI->Canvas

如何实现UI界面的层级

Unity3d实现2d游戏,有几种方式

  1. 使用自身的GUI
  2. 把摄像机的Projection(投影)值调整为Orthographic(正交投影),不考虑z轴
  3. 使用2d的ui插件:2DToolKit、NGUI等

为何大家都在移动设备上寻求U3D原生GUI的替代方案

不美观,OnGUI很耗费时间,效率不高,使用不方便

如何在不同分辨率下保持UI的一致性(?

NGUI很好的解决了这一点,屏幕分辨率的自适应性,原理就是计算出屏幕的宽高比跟原来的预设的屏幕分辨率求出一个对比值,然后修改摄像机的size。UGUI通过锚点和中心点和分辨率也解决这个问题

ngui和ugui的区别

简述NGUI中Grid和Table的作用

对Grid和Table下的子物体进行排序和定位

请简述NGUI中Panel和Anchor的作用

  1. 只要提供一个half-pixel偏移量,它可以让一个控件的位置在Windows系统上精确的显示出来(只有这个Anchor的子控件会受到影响)
  2. 如果挂载到一个对象上,那么他可以将这个对象依附到屏幕的角落或者边缘
    3.UIPanel用来收集和管理它下面所有widget的组件。通过widget的geometry创建实际的draw call。没有panel所有东西都不能够被渲染出来,你可以把UIPanel当做Renderer

UGUI中Image和RawImage的区别

Imgae比RawImage更消耗性能
Image只能使用Sprite属性的图片,但是RawImage什么样的都可以使用
Image适合放一些有操作的图片,裁剪平铺旋转什么的,针对Image Type属性
RawImage就放单独展示的图片就可以,性能会比Image好很多

在场景中放置多个Camera并同时处于活动状态会发生什么(?

游戏界面可以看到很多摄像机的混合。可以用depth(深度),Layer(层)+ Culling Mask,enable = false/true来控制

照相机的Clipping Planes的作用是什么?调整Near、Fare两个值时,应该注意什么

剪裁平面 。从相机到开始渲染和停止渲染之间的距离。

将Camera组件的ClearFlags选项选成Depth only是什么意思?有何用处

如果把摄像机的ClearFlags勾选为Deapth Only,那么摄像机就会只渲染看得见的对象,把背景会完全透明,这种情况一般用在两个摄像机以上的场景中

在 Unity 中的场景中创建 Camera 时,默认情况下除了带有Transform、Camera、GUILayer、Flare Layer 组件之外,还带有以下哪种组件(C)

A.Mouse Look B.FPS Input Controller C.Audio Listener D.Character Motor

以下哪组摄像机中 Normalized View Port Rect 的数值设置可以使摄像机显示的画面位于1280*720分辨率的屏幕画面右上角(D)

A.X=640,Y=360,W=640,H=360 B.X=640,Y=0,W=640,H=360
C.X=0,Y=0,W=0.5,H=0.5 D.X=0.5,Y=0.5,W=0.5,H=0.5

多媒体

如果将一个声音剪辑文件从Project 视图拖动到 Inspector 视图或者 Scene 视图中的游戏对象上,该游戏对象会自动添加以下哪种组件(C)

A.Audio Listener B.Audio Clip C.Audio Source D.Audio Reverb Zone

以下哪一个选项不属于Unity引擎所支持的视频格式文件(D)

A.后缀为mov的文件 B.后缀为mpg的文件
C.后缀为avi的文件 D.后缀为swf的文件

请描述游戏动画有哪几种

主要有关节动画、骨骼动画、单一网格模型动画(关键帧动画)。
关节动画:把角色分成若干独立部分,一个部分对应一个网格模型,部分的动画连接成一个整体的动画,角色比较灵活,Quake2中使用这种动画;
骨骼动画,广泛应用的动画方式,集成了以上两个方式的优点,骨骼按角色特点组成一定的层次结构,有关节相连,可做相对运动,皮肤作为单一网格蒙在骨骼之外,决定角色的外观;
单一网格模型动画由一个完整的网格模型构成,在动画序列的关键帧里记录各个顶点的原位置及其改变量,然后插值运算实现动画效果,角色动画较真实。

下列选项中有关Animator的说法错误的是?(D)

A.Animator是Unity引擎中内置的组件
B.任何一个具有动画状态机功能的GameObject都需要一个Anim组件
C.它主要用于角色行为的设置,包括StateMachine、混合树BlendTrees以及同通过脚本控制的事件
D.Animator同Animation组件的用法是相同的

Animator.CrossFade 命令作用是:(B)

A.动画放大 B.动画转换 C.Update() D.OnMouseButton()

Animation和Animator的区别

Animation需要通过代码手动控制动画的播放和迁移。而Animator拥有有动画状态机,可以通过动画状态机来设置动画之间的状态,并且可以为单个动画设置脚本代码来控制事件。

其他

Unity3d提供了一个用于保存和读取数据的类(PlayerPrefs),请列出保存和读取整形数据的函数

PlayerPrefs.SetInt(string, int) PlayerPrefs.GetInt(string)

Unity3D是否支持写成多线程程序?如果支持的话需要注意什么

仅能从主线程中访问Unity3D的组件,对象和Unity3D系统调用
支持:如果同时你要处理很多事情或者与Unity的对象互动小可以用thread,否则使用coroutine。
注意:C#中有lock这个关键字,以确保只有一个线程可以在特定时间内访问特定的对象

Unity3D的协程和C#线程之间的区别是什么?

多线程程序同时运行多个线程 ,而在任一指定时刻只有一个协程在运行,并且这个正在运行的协同程序只在必要时才被挂起。除主线程之外的线程无法访问Unity3D的对象、组件、方法。
Unity3d没有多线程的概念,不过unity也给我们提供了StartCoroutine(协同程序)和LoadLevelAsync(异步加载关卡)后台加载场景的方法。 StartCoroutine为什么叫协同程序呢,所谓协同,就是当你在StartCoroutine的函数体里处理一段代码时,利用yield语句等待执行结果,这期间不影响主程序的继续执行,可以协同工作。

🔲 ☆

U3D问题总结(二) 线性代数与算法

线性代数

向量的点乘、叉乘以及归一化的意义

1.点乘描述了两个向量的相似程度,结果越大两向量越相似,还可表示投影
2.叉乘得到的向量垂直于原来的两个向量
3.标准化向量:用在只关系方向,不关心大小的时候

叉乘:
几何意义:得到一个与这两个向量都垂直的向量,这个向量的模是以两个向量为边的平行四边形的面积
在同一平面内, 结果 > 0 表示 B在A的逆时针方向, 结果 <0 表示B在A的顺式针方向, 结果 = 0表示B与A同向
应用:计算两个向量方向的
点乘:
几何意义:可以用来表征或计算两个向量之间的夹角,以及在b向量在a向量方向上的投影
两个向量的点乘所得到的是两个向量的余弦值,也就是-1 到1之间,0表示垂直,-1表示相反,1表示相同方向。
应用:计算两个向量方向的夹角

矩阵相乘的意义及注意点

用于表示线性变换:旋转、缩放、投影、平移、仿射
注意矩阵的蠕变:误差的积累

简述四元数的作用,四元数对欧拉角的优点

四元数用于表示旋转
相对欧拉角的优点:
1.能进行增量旋转
2.避免万向锁
3.给定方位的表达方式有两种,互为负(欧拉角有无数种表达方式)

算法

🔲 ☆

U3D问题总结(一) 计算机基础与C#

计算机

概述序列化

定义:将对象的状态信息转换为可以存储或传输的形式的过程。与序列化相对的是反序列化,它将流转换为对象。

目的:当我们需要把对象的状态信息通过网络进行传输,或者需要将对象的状态信息持久化,以便将来使用时都需要把对象进行序列化

对象序列化:1.把对象转换为字节序列的过程称为对象的序列化
2.把字节序列恢复为对象的过程称为对象的反序列化

比如,可以序列化一个对象,然后使用HTTP通过Internet在客户端和服务器端之间传输该对象

什么是协同程序(?

A coroutine is a function that is executed partially and, presuming suitable conditions are met, will be resumed at some point in the future until its work is done.

协程是一个分部执行,遇到条件(yield return 语句)会挂起,直到条件满足才会被唤醒继续执行后面的代码。
Unity在每一帧(Frame)都会去处理对象上的协程。Unity主要是在Update后去处理协程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using UnityEngine;  
using System.Collections;

public class CoroutineCountdown : MonoBehaviour
{
void Start()
{
StartCoroutine(Countdown());
}

IEnumerator Countdown()
{
for(floattimer = 3; timer >= 0; timer -= Time.deltaTime)
Yield return 0;

Debug.Log("This message appears after 3 seconds!");
}
}

yield return的常见返回值及其作用:

1
2
3
4
5
6
7
yield return new WaitForSeconds(3.0f); // 等待3秒,然后继续从此处开始,常用于做定时器
yield return null; // 这一帧到此暂停,下一帧再从暂停处继续,常用于循环中
yield return new WaitForEndOfFrame(); // 等到这一帧的cameras和GUI渲染结束后再从此处继续,即等到这帧的末尾再往下运行。这行之后的代码还是在当前帧运行,是在下一帧开始前执行,跟return null很相似
yield return new WaitForFixedUpdate(); // 在下一次执行FixedUpdate的时候继续执行这段代码,即等一次物理引擎的更新
yield return www; // 等待直至异步下载完成
yield break; // 直接跳出协程,对某些判定失败必须跳出的时候,比如加载AssetBundle的时候,WWW失败了,后边加载bundle没有必要了,这时候可以yield break跳出。
yield return StartCoroutine(methodName); // 等待另一个协程执行完。这是把协程串联起来的关键,常用于让多个协程按顺序逐个运行

协程的开启关闭

开启:

  1. StartCoroutine(string methodName)
  2. StartCoroutine(IEnumerator method)

终止:

  1. StopCoroutine (string methodName) // 只能终止指定的协程
    在程序中调用StopCoroutine() 方法只能终止以字符串形式启动的协程
  2. StopAllCoroutine() // 终止所有协程

协程的用途

1.用来延时
2.用来异步加载等待
3.加载WWW
4.制代码在特定的时机执行。

协同程序的执行代码是什么?有何用处,有何缺点

1
2
3
4
5
6
7
8
9
10
11
function Start() { 
// 协同程序WaitAndPrint在Start函数内执行,可以视同于它与Start函数同步执行.
StartCoroutine(WaitAndPrint(2.0));
print ("Before WaitAndPrint Finishes " + Time.time );
}

function WaitAndPrint (waitTime : float) {
// 暂停执行waitTime秒
yield WaitForSeconds (waitTime);
print ("WaitAndPrint "+ Time.time );
}

作用:一个协同程序在执行过程中,可以在任意位置使用yield语句。yield的返回值控制何时恢复协同程序向下执行。协同程序在对象自有帧执行过程中堪称优秀。协同程序在性能上没有更多的开销。
缺点:协同程序并非真线程,可能会发生堵塞。

协程的执行原理

协程函数的返回值时IEnumerator,它是一个迭代器,可以把它当成执行一个序列的某个节点的指针,它提供了两个重要的接口,分别是Current(返回当前指向的元素)和MoveNext()(将指针向后移动一个单位,如果移动成功,则返回true)

yield关键词用来声明序列中的下一个值或者是一个无意义的值,如果使用yield return x(x是指一个具体的对象或者数值)的话,那么MoveNext返回为true并且Current被赋值为x,如果使用yield break使得MoveNext()返回为false

如果MoveNext函数返回为true意味着协程的执行条件被满足,则能够从当前的位置继续往下执行。否则不能从当前位置继续往下执行。

网络

客户端与服务器交互方式有几种

socket通常也称作”套接字”,实现服务器和客户端之间的物理连接,并进行数据传输。主要有UDP和TCP两个协议,处于网络协议的传输层。
http协议传输的主要有http协议和基于http协议的Soap协议(web service)(基于XML)。常见的方式是 http 的post 和get 请求、web service。

TCP和UDP的区别

1、连接方面区别
TCP面向连接(如打电话要先拨号建立连接)。
UDP是无连接回的,即发送数据之答前不需要建立连接。

2、安全方面的区别
TCP提供可靠的服务,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达。
UDP尽最大努力交付,即不保证可靠交付。

3、传输效率的区别
TCP传输效率相对较低。
UDP传输效率高,适用于对高速传输和实时性有较高的通信或广播通信。

4、连接对象数量的区别
TCP连接只能是点到点、一对一的。
UDP支持一对一,一对多,多对一和多对多的交互通信。

Http和Https的区别

一、传输bai信息安全性不同
1.http协议:是超文本传输协议,信息是明文传输。如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息。
2.https协议:是具有安全性的ssl加密传输协议,为浏览器和服务器之间的通信加密,确保数据传输的安全。

二、连接方式不同
1.http协议:http的连接很简单,是无状态的。
2.https协议:是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议。

三、默认端口不同
1.http协议:默认端口是80
2.https协议:默认的端口是443

四、证书申请方式不同
1.http协议:免费申请。
2.https协议:需要到ca申请证书,一般免费证书很少,需要交费。

面向对象

面向对象的优点

  1. 易维护
    采用面向对象思想设计的结构,可读性高,由于继承的存在,即使改变需求,那么维护也只是在局部模块,所以维护起来是非常方便和较低成本的。
  2. 质量高
    在设计时,可重用现有的,在以前的项目的领域中已被测试过的类使系统满足业务需求并具有较高的质量。
  3. 效率高
    在软件开发时,根据设计的需要对现实世界的事物进行抽象,产生类。使用这样的方法解决问题,接近于日常生活和自然的思考方式,势必提高软件开发的效率和质量。
  4. 易扩展
    由于继承、封装、多态的特性,自然设计出高内聚、低耦合的系统结构,使得系统更灵活、更容易扩展,而且成本较低。

什么是里氏代换原则

里氏替换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。任何基类可以出现的地方,子类一定可以出现。(就是子类对象可以赋值给基类对象,基类对象不能赋值给子类对象)

继承和组合的区别

继承:可以使用现有类的功能,并且在无需重复编写原有类的情况下对原有类进行功能上的扩展。(is-a关系)

组合:在新类里面创建原有类的对象,重复利用已有类的功能。(has-a关系)

组 合 关 系继 承 关 系
优点:不破坏封装,整体类与局部类之间松耦合,彼此相对独立缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性
优点:具有较好的可扩展性缺点:支持扩展,但是往往以增加系统结构的复杂度为代价
优点:支持动态组合。在运行时,整体对象可以选择不同类型的局部对象缺点:不支持动态继承。在运行时,子类无法选择不同的父类
优点:整体类可以对局部类进行包装,封装局部类的接口,提供新的接口缺点:子类不能改变父类的接口
缺点:整体类不能自动获得和局部类同样的接口优点:子类能自动继承父类的接口
缺点:创建整体类的对象时,需要创建所有局部类的对象优点:创建子类的对象时,无须创建父类的对象

虚方法virtual抽象方法abstract

  1. 虚方法必须有实现部分,抽象方法没有提供实现部分。抽象方法是一种强制派生类覆盖的方法,否则派生类将不能被实例化
  2. 抽象方法只能在抽象类中声明,虚方法不是。如果类包含抽象方法,那么该类也是抽象的,也必须声明为抽象的
  3. 派生类必须重写抽象类中的抽象方法,虚方法则不必要
  4. 虚方法可以实现多态,而抽象方法不行

类和结构体的区别?使用环境?

结构体是值类型,类是引用类型。结构体存储在栈中,类存储在堆中,栈的空间小但是访问快,堆的空间大但是访问速度较慢。

结构体不能继承,不能创建默认构造函数和析构函数。结构成员不能指定为 abstract、virtual 或 protected。结构体的构造函数必须为所有值赋初值。

结构体一般存储较为轻量的数据,类一般存储具有较为复杂逻辑结构的数据。

使用环境:

  1. 当堆栈的空间很有限,且有大量的逻辑对象时,创建类要比创建结构好一些;
  2. 对于点、矩形和颜色这样的轻量对象,假如要声明一个含有许多个颜色对象的数组,则CLR需要为每个对象分配内存,在这种情况下,使用结构的成本较低;
  3. 在表现抽象和多级别的对象层次时,类是最好的选择,因为结构不支持继承。

数据结构

Heap与Stack有何区别(?

1.heap是堆,stack是栈。
2.stack的空间由操作系统自动分配和释放,heap的空间是手动申请和释放的,heap常用new关键字来分配。
3.stack空间有限,heap的空间是很大的自由区。

栈和堆谁比较快?为什么?

栈,原因:

  1. 栈有专门的寄存器,堆是随机内存。
  2. 栈是在一级缓存上运行的,而堆是在二级缓存上运行的。
  3. 访问栈上的数据只需一次,而访问堆上的数据需要两次,先访问栈,再访问堆。

c/c++程序运行时有堆内存与栈内存之分,请写一个语句在堆中分配一个整数:(int a = new int(4)),在栈内存中分配一个整数:(int a = 5)。

值类型和引用类型有何区别

1.值类型根据声明位置不同堆和栈中都有可能存储,引用类型存储在堆中
2.值类型存取速度快,引用类型存取速度慢。
3.值类型表示实际数据,引用类型表示指向存储在内存堆中的数据的指针或引用
4.值类型继承自System.ValueType,引用类型继承自System.Object

结构体和类有何区别

结构体是一种值类型,而类是引用类型。(值类型、引用类型是根据数据存储的角度来分的)就是值类型用于存储数据的值,引用类型用于存储对实际数据的引用。那么结构体就是当成值来使用的,类则通过引用来对实际数据操作

排序方式有哪些

选择排序,冒泡排序,快速排序,插入排序,希尔排序,归并排序

k层二叉树最多有 2^k - 1 个结点。

##

请简述ArrayList和List的主要区别

ArrayList存在不安全类型(ArrayList会把所有插入其中的数据都当做Object来处理)装箱拆箱的操作(费时)List是接口,ArrayList是一个实现了该接口的类,可以被实例化

数组和List两者效率之间哪个好

数组: 它在内存中是连续的存储的,所以索引速度很快,而且赋值与修改元素也很简单。可以利用偏移地址访问元素,时间复杂度为O(1);删除时间复杂度为O(n),数组没有添加数据选项。

List:基于数组,时间复杂度相同,插入为O(n);不过在数据少量的时候跟数组差不多,数据庞大的时候效率会低于数组。

哈希表与字典

字典:内部用了Hashtable作为存储结构
如果我们试图找到一个不存在的键,它将返回 / 抛出异常。
它比哈希表更快,因为没有装箱和拆箱,尤其是值类型。
仅公共静态成员是线程安全的。
字典是一种通用类型,这意味着我们可以将其与任何数据类型一起使用(创建时,必须同时指定键和值的数据类型)。
Dictionay 是 Hashtable 的类型安全实现, Keys和Values是强类型的。
Dictionary遍历输出的顺序,就是加入的顺序

哈希表:
如果我们尝试查找不存在的键,则返回 null。
它比字典慢,因为它需要装箱和拆箱。
哈希表中的所有成员都是线程安全的,
哈希表不是通用类型,
Hashtable 是松散类型的数据结构,我们可以添加任何类型的键和值。
HashTable是经过优化的,访问下标的对象先散列过,所以内部是无序散列的

StringBuilder和String的区别

String是字符串常量。
StringBuffer是字符串变量 ,线程安全。
StringBuilder是字符串变量,线程不安全。
String类型是个不可变的对象,当每次对String进行改变时都需要生成一个新的String对象,然后将指针指向一个新的对象,如果在一个循环里面,不断的改变一个对象,就要不断的生成新的对象,所以效率很低,建议在不断更改String对象的地方不要使用String类型。
StringBuilder对象在做字符串连接操作时是在原来的字符串上进行修改,改善了性能。这一点我们平时使用中也许都知道,连接操作频繁的时候,使用StringBuilder对象。

如果是处理字符串的话,用string中的方法每次都需要创建一个新的字符串对象并且分配新的内存地址,而stringBuilder是在原来的内存里对字符串进行修改,所以在字符串处理方面还是建议用stringBuilder这样比较节约内存。但是string 类的方法和功能仍然还是比stringBuilder类要强。

有一本牛津词典,现在输入一串字母组成一个单词,怎么样快速查询词典中是否有这个单词

使用树结构来存储词典的单词,以字母为顺序分别放在相应的子树中。然后根据输入将字母从左到右分级并根据树的结构依次查询。

在一段文本中,有许多”{}”和”[]”和”()”,判断这段文本中的括号是否使用正确?

使用栈的结构进判断,将所有括号依次入栈,当一次入栈是右括号时判断之前的栈顶是否是对应的左括号,如果是说明合法,将之前的左括号和现在入栈的右括号都出栈。然后继续将新的括号依次入栈,当有一次入栈非法即可判定非法,或者知道最后全部判定合法则判定该文本合法。

设计模式

https://blog.csdn.net/weixin_43122090/article/details/105462226

C

在类的构造函数前加上static会报什么错?为什么?

构造函数格式为 public+类名,如果加上static会报错(静态构造函数不能有访问修饰符)
原因:静态构造函数不允许访问修饰符,也不接受任何参数;
无论创建多少类型的对象,静态构造函数只执行一次;
运行库创建类实例或者首次访问静态成员之前,运行库调用静态构造函数;
静态构造函数执行先于任何实例级别的构造函数;
显然也就无法使用this和base来调用构造函数。

以下选项中,正确的是(D)

A.Mathf.Round方法作用是限制 B.Mathf.Clamp方法作用是插值
C.Mathf.Lerp方法作用是四舍五入 D.Mathf.Abs方法作用是取得绝对值

C#、.Net与Mono的关系?

mono是.net的一个开源跨平台工具,就类似java虚拟机,java本身不是跨平台语言,但运行在虚拟机上就能够实现了跨平台,由Xamarin提出,它是.NET框架的一个开源版本。
.net是微软的一个开发平台,只能在windows下运行,而mono可以实现跨平台跑,可以运行于linux,Unix,Mac OS等。
C#是微软的编程语言,开发包是.NET,就像Java之于JDK

C#和C++的区别(?

C# 与C++ 比较的话,最重要的特性就是C# 是一种完全面向对象的语言,而C++ 不是,另外C# 是基于IL 中间语言和.NET Framework CLR 的,在可移植性,可维护性和强壮性都比C++ 有很大的改进。C# 的设计目标是用来开发快速稳定可扩展的应用程序,当然也可以通过Interop 和Pinvoke 完成一些底层操作

C# 是一种完全面向对象的语言。另外C# 是基于IL 中间语言和.NET Framework CLR 的,在可移植性,可维护性和强壮性都比C++ 有很大的改进。

C#与C++结构体的区别

## 实现计时器的方法

Time eltatine:协程

“”与null的区别

ref参数和out参数是什么?有什么区别(?

ref和out参数的效果一样,都是通过关键字找到定义在主函数里面的变量的内存地址,并通过方法体内的语法改变它的大小。不同点就是输出参数必须对参数进行初始化。ref必须初始化,out 参数必须在函数里赋值。ref参数是引用,out参数为输出参数。

C#的委托是什么?有何用处

委托类似于一种安全的指针引用,在使用它时是当做类来看待而不是一个方法,相当于对一组方法的列表的引用。用处:使用委托使程序员可以将方法引用封装在委托对象内。然后可以将该委托对象传递给可调用所引用方法的代码,而不必在编译时知道将调用哪个方法。与C或C++中的函数指针不同,委托是面向对象,而且是类型安全的。

三种泛型委托

委托delegate是什么,event关键字有什么用

delegate 委托,是C#的一种类型,持有对某个方法的引用的类,能够拥有一个签名,引用只能与签名方法相匹配。实现:1、声明一个委托对象,与传递方法具有相同参数列表和返回值类型。2、创建委托对象,将要传递的函数作为参数传入。3、在实现异步调用地方,通过上一步创建对象调用方法。

event 事件,在类中声明且生成,通过使用同一个类或其他类的委托与事件处理程序关联。包含事件的类用于发布事件,称为发布器(publisher)类;接受该事件的类称为订阅器(subscriber)类。事件使用发布-订阅模型。两者的区别:1、委托允许直接访问相应处理函数,事件只能通过公布的回调函数去调用。2、事件只能通过“+=”、“-=”方式注册和取消处理函数,委托除此之外还可以“=”直接赋值处理函数。

概述c#中代理和事件

代理就是用来定义指向方法的引用。
C#事件本质就是对消息的封装,用作对象之间的通信;发送方叫事件发送器,接收方叫事件接收器

sealed关键字用在类声明时与函数声明时的作用

sealed修饰的类为密封类,类声明时可防止其他类继承此类,在方法中声明则可防止派生类重写此方法。

请简述private,public,protected,internal的区别

public:对任何类和成员都公开,无限制访问
private:仅对该类公开
protected:对该类和其派生类公开
internal:只能在包含该类的程序集中访问该类

请描述接口Interface与抽象类之间的不同(?

抽象类和接口都不能实例化。

抽象类可以有抽象的的方法和未抽象的的方法,可以通过子类来重写。抽象类主要是子类的通用结构。

常量、字段、运算符、实例构造函数、析构函数或类型、不能包含静态成员。接口不能有实现的方法。接口主要是作为规范来使用。

static和const关键字的作用

static 关键字至少有下列几个作用:
(1)函数体内static 变量的作用范围为该函数体,不同于auto 变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
(2)在模块内的static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
(3)在模块内的static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;
(4)在类中的static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(5)在类中的static 成员函数属于整个类所拥有,这个函数不接收this 指针,因而只能访问类的static 成员变量。
const 关键字至少有下列几个作用:
(1)欲阻止一个变量被改变,可以使用const 关键字。在定义该const 变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
(2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;
(3)在一个函数声明中,const 可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
(4)对于类的成员函数,若指定其为const 类型,则表明其是一个常函数,不能修改类的成员变量
(5)对于类的成员函数,有时候必须指定其返回值为const 类型,以使得其返回值不为“左值”。

C#中四种访问修饰符是哪些?各有什么区别?

1.属性修饰符 2.存取修饰符 3.类修饰符 4.成员修饰符。
属性修饰符:
Serializable:按值将对象封送到远程服务器。
STATread:是单线程套间的意思,是一种线程模型。
MATAThread:是多线程套间的意思,也是一种线程模型。
存取修饰符:
public:存取不受限制。
private:只有包含该成员的类可以存取。
internal:只有当前工程可以存取。
protected:只有包含该成员的类以及派生类可以存取。
类修饰符:
abstract:抽象类。指示一个类只能作为其它类的基类。
sealed:密封类。指示一个类不能被继承。理所当然,密封类不能同时又是抽象类,因为抽象总是希望被继承的。
成员修饰符:
abstract:指示该方法或属性没有实现。
sealed:密封方法。可以防止在派生类中对该方法的override(重载)。不是类的每个成员方法都可以作为密封方法密封方法,必须对基类的虚方法进行重载,提供具体的实现方法。所以,在方法的声明中,sealed修饰符总是和override修饰符同时使用。
delegate:委托。用来定义一个函数指针。C#中的事件驱动是基于delegate + event的。
const:指定该成员的值只读不允许修改。
event:声明一个事件。
extern:指示方法在外部实现。
override:重写。对由基类继承成员的新实现。
readonly:指示一个域只能在声明时以及相同类的内部被赋值。
static:指示一个成员属于类型本身,而不是属于特定的对象。即在定义后可不经实例化,就可使用。
virtual:指示一个方法或存取器的实现可以在继承类中被覆盖。
new:在派生类中隐藏指定的基类成员,从而实现重写的功能。 若要隐藏继承类的成员,请使用相同名称在派生类中声明该成员,并用 new 修饰符修饰它。

已知strcpy函数的原型是:char strcpy(char strDest,const char strSrc); 1.不调用库函数,实现strcpy函数。2.解释为什么要返回char

1
2
3
4
5
6
7
8
char * strcpy(char * strDest,const char * strSrc)
{
if ((strDest==NULL)||(strSrc==NULL))
throw "Invalid argument(s)";
char * strDestCopy=strDest;
while ((*strDest++=*strSrc++)!='\0');
return strDestCopy;
}
🔲 ☆

关于Unity China偷偷更换了LTS的安装包的这档事

论抽象还是你Unity在行

https://unity.com/releases/editor/archive 中国大陆打开直接跳团结引擎官网去了

就算成功打开了unity archive,拉起unity hub(非c1)下载2021.3.16f1,unity hub自动重定向到unity china,然后这个版本作为还在维护的LTS版本,非常贴心的给你把2021.3.16f1安装包偷偷换成了2021.3.16f1c1

如果你通过hub下载的时候一起选择了其他组件,你会发现Editor安装完毕后其他任务直接卡在等待队列,下载队列死翘翘走不动了

重启unity hub后你会发现你的2021.3.16f1变成了2021.3.16f1c1(这能装上2021.3.16f1的组件才有鬼了)

惊喜不?

顺带一提,unity hub这边看起来是不知道自己下的变成了2021.3.16f1c1,因为安装路径之类的还是指向2021.3.16f1(没有c1),估计要是留着2021.3.16f1c1这货,你就算真的下到了2021.3.16f1的离线包也安装不上去

只不过我今天花了10个G的流量下Editor等个老半天就为了早点能开始干活去改模型,结果你给我来这出?这像话么

🔲 ☆

Unity重磅调整:3月31日起,中国开发者将无法继续访问海外资源商店

今天(3月3日),全球知名游戏引擎开发商Unity通过官方邮件通知,因区域许可、分发及合规政策更新,自2026年3月31日起,中国大陆及港澳地区用户将无法继续使用海外 Unity资源商店,同时无法访问…

The post Unity重磅调整:3月31日起,中国开发者将无法继续访问海外资源商店 appeared first on Jake blog.

🔲 ☆

为什么 Go 社区强调避免不必要的抽象?—— 借用海德格尔哲学寻找“正确”的答案

本文永久链接 – https://tonybai.com/2026/01/16/go-community-the-right-kind-of-abstraction

大家好,我是Tony Bai。

“Go 的哲学强调避免不必要的抽象。”

这句话我们听过无数次。当你试图引入 ORM、泛型 Map/Reduce 、接口或者复杂的设计模式时,往往会收到这样的反馈。这句话本身没有错,但难点在于:到底什么是“不必要”的?

函数是抽象吗?汇编是抽象吗?如果不加定义地“避免抽象”,我们最终只能对着硅片大喊大叫。

在 GopherCon UK 2025 上,John Cinnamond 做了一场与众不同的演讲。他没有展示任何炫酷的并发模式,而是搬出了马丁·海德格尔(Martin Heidegger)和伊曼努尔·康德(Immanuel Kant),试图用哲学的视角,为我们解开关于 Go 抽象的终极困惑。

注:海德格尔与《存在与时间》

马丁·海德格尔(Martin Heidegger)是 20 世纪最重要的哲学家之一。他在 1927 年的巨著《存在与时间》(Being and Time) 中,深入探讨了人(此在)如何与世界互动。John Cinnamond 在演讲中引用的核心概念——“上手状态” (Ready-to-hand)“在手状态” (Present-at-hand),正是海德格尔用来描述我们与工具(如锤子)之间关系的术语。这套理论极好地解释了为什么优秀的工具(或代码抽象)应该是“透明”的,而糟糕的工具则会强行占据我们的注意力。

img{512x368}

我们都在使用的“必要”抽象

首先,让我们承认一个事实:编程本身就是建立在无数层抽象之上的。

  • 泛型:这是对类型的抽象。虽然 Go 曾长期拒绝它,但在技术上它是必要的,否则我们将充斥着重复代码。
  • 接口:这是对行为的抽象。io.Reader 让我们不必关心数据来自文件还是网络。
  • 函数:这是对指令序列的抽象。没有它,我们只能写长长的 main 函数。
  • 汇编语言:这是对机器码的抽象。

所以,当我们说“避免不必要的抽象”时,我们真正想表达的其实是——避免“不恰当” (Inappropriate) 的抽象

那么,如何判断一个抽象是否“恰当”?

何为抽象?—— 一场有目的的“细节隐藏”

在深入探讨“正确”的抽象之前,我们必须先回到最基本的定义。John Cinnamond 在演讲中给出了一个精炼而深刻的定义:

“抽象是一种表示 (Representation),但它是一种刻意移除被表示事物某些细节的表示。”

让我们拆解这个定义:

  1. 抽象是一种“表示”,而非事物本身
    它不是代码的实体,而是代码的地图或模型。例如,一辆模型汽车是真实汽车的表示,但 Gopher 吉祥物是地鼠的抽象——它刻意省略了真实地鼠的所有细节,只保留了核心特征。

  1. 抽象是“有目的的”细节移除
    这与仅仅是“不精确”或“粗糙”不同。抽象是有意为之的,它不试图精确描绘所有方面,而是只关注某个特定维度

  1. 抽象在编程中具有动态性
    • 不确定引用 (Indefinite Reference):一个抽象(如 io.Reader)通常可以指代许多不同的具体实现。
    • 开放引用 (Open Reference):抽象的内容或它所指代的事物可以随着时间而改变。

为什么要刻意移除细节?John 总结了几个核心动机:

  • 避免重复代码:将重复的逻辑提取到抽象中。
  • 统一不同的实现:允许以统一的方式处理本质上不同的数据结构(如所有实现了 Read 方法的类型)。
  • 推迟细节:隐藏那些当下不重要、或开发者不关心的细节(例如,你坐火车参会,不需要知道每节车厢的编号)。
  • 揭示领域概念:用抽象来更好地表达业务领域中的核心概念。
  • 驾驭复杂性:这是最核心的理由——没有抽象,我们无法在大脑中一次性处理所有细节,也就无法解决复杂的问题。

但请记住,并非所有抽象都是一样的。John 将它们分为三类:

  1. 基于“它是如何工作的” (How it works)
    这是为了代码复用而提取的抽象。例如,你发现两处代码都在做“检查用户是否是管理员”的逻辑,于是将其提取为一个函数。这种抽象关注的是内部机制。 (这类抽象通常比较脆弱,一旦实现细节变化,抽象可能就会失效。)

  2. 基于“它做了什么” (What it does)
    这是 Go 语言中接口(Interface)最典型的用法。例如 io.Reader,我们不关心它是文件还是网络连接,我们只关心它能“读取字节”。这是一种行为抽象。

  3. 基于“它是什么” (What it is)
    这是基于领域模型的抽象。例如一个 User 结构体,它代表了系统中的一个实体。这种抽象关注的是本质属性。

在现实中,好的抽象往往是这三者的混合体,但在设计时,明确你是在抽象“行为”还是“实现”,对于判断抽象的质量至关重要。

理解了抽象的本质,我们可能会觉得:既然抽象能驾驭复杂性,那是不是越多越好?

且慢。在急于评判一个抽象是否“恰当”之前,我们必须先意识到一个常被技术人员忽略的现实:抽象不仅存在于代码中,更存在于人与人的互动里。 这将我们引向了一个更现实的考量维度。

抽象的代价 —— 代码是写给人看的

John 提醒我们,软件开发本质上是一项社会活动 (Social Activity)

“除非你是为了自己写着玩,否则你的代码总是写给别人看的。团队是一个微型社会,它有自己的习俗、信仰和‘传说’(Lore)。”

引入一个新的抽象,本质上是在向这个微型社会引入一种新的文化或规则。这意味着:

  1. 你需要支付“社会成本”:如果这个抽象与团队现有的习惯(Lore)相悖——比如在一个从未用过函数式编程的 Go 团队里强推 Monad——你将遭遇巨大的阻力。
  2. 团队的保守性:成熟的团队往往趋于保守,改变既定习惯需要巨大的能量。你不能仅仅因为一个抽象在理论上很美就引入它,你必须证明它的收益足以覆盖它带来的社会摩擦成本
  3. 认知负担是共享的:一个抽象对你来说可能很清晰,但如果它让队友感到困惑,那就是在消耗团队的整体智力资源。

因此,当我们评判一个抽象是否“恰当”时,不能只看代码本身,还必须看它是否“合群”。这正是我们接下来要引入海德格尔哲学的现实基础。

锤子哲学 —— “上手状态” vs. “在手状态”

John 引用了海德格尔在《存在与时间》中的一个著名概念:Ready-to-hand (上手状态)Present-at-hand (在手状态)

  • 上手状态 (Ready-to-hand):当你熟练使用一把锤子钉钉子时,你的注意力完全在钉钉子这件事上,锤子本身在你意识中是“透明”的。你感觉不到它的存在,它只是你身体的延伸。
  • 在手状态 (Present-at-hand):当锤子突然坏了(比如锤头掉了),或者你拿到一把设计奇特的陌生工具时,你的注意力被迫从“钉钉子”转移到了“锤子”本身。你开始审视它的构造、重量和用法。

这对代码意味着什么?

  • 好的抽象是“上手状态”的:比如 for 循环。作为经验丰富的开发者,你使用它时是在思考“我要遍历数据”,而不是“这个循环语法是怎么编译的”。它透明、顺手,让你专注于解决问题。

  • 坏的抽象是“在手状态”的:比如一个复杂的、过度设计的 ORM 或者一个晦涩的 Monad 库。当你使用它时,你的思维被迫中断,你需要停下来思考:“这个函数到底在干什么?这个参数是什么意思?”

如果一个抽象让你频繁地从“解决业务问题”中抽离出来去思考“工具本身”,那么它很可能是一个坏的抽象

注:通过学习和实践,在手状态 (Present-at-hand)的抽象可以转换为 上手状态 (Ready-to-hand)的抽象。

真理的检验 —— “本质真理” vs. “巧合真理”

接着,John 又搬出了康德关于真理的分类,引导我们思考抽象的持久性

  • 分析真理 (Analytic Truth):由定义决定的真理。比如“所有单身汉都没结婚”。在代码中,这就像 unnecessary abstractions are unnecessary,虽然正确但没啥用。
  • 综合真理 (Synthetic Truth):由外部事实决定的真理。比如“外面在下雨”。它的真假取决于环境,随时可能变。
  • 本质真理 (Essential Truth):虽然不是由定义决定,但反映了世界的本质规律。比如“物质由原子构成”。

这对抽象意味着什么?

当你提取一个抽象时,问问自己:它代表的是代码的“本质真理”,还是仅仅是一个“巧合”?

举个例子:你有一段过滤商品的代码,可以按“价格”过滤,也可以按“库存”过滤。你提取了一个 Filter(Product) bool 的抽象。

  • 如果未来所有的过滤需求(如颜色、大小)都能用这个签名解决,那么你发现了一个本质真理。这个抽象是稳固的。
  • 但如果突然来了一个需求:“过滤掉重复的商品”,这个需求需要知道所有商品的状态,而不仅仅是单个商品。原本的 Filter(Product) bool 签名瞬间失效。

如果你提取的抽象仅仅是因为几段代码“长得像”(巧合),而不是因为它们“本质上是一回事”,那么当需求变更时,这个抽象就会崩塌,变成一种负担。

由此可见,好的抽象不是被创造出来的,而是被发现(Recognized)出来的。它们是对代码中某种本质结构的捕捉。

实战指南 —— 如何引入抽象?

最后,John 给出了一个评估抽象是否“恰当”的五步清单:

  1. 明确收益 (Benefit):你到底是为了解决重复、隐藏细节,还是仅仅因为觉得它“很酷”?
  2. 考虑社会成本 (Social Cost):编程是社会活动。这个抽象符合团队的习惯吗?引入它是否需要消耗大量的团队认知成本?(比如在 Go 里强推 Monad等函数式编程的范式)。
  3. 是否处于“上手状态” (Ready-to-hand):它能融入开发者的直觉吗?还是会成为注意力的绊脚石?
  4. 是否本质 (Essential):它是否捕捉到了问题的核心结构,能经得起未来的变化?
  5. 是否涌现 (Emergent):它是你从现有代码中“识别”出来的模式,还是你强加给代码的枷锁?

小结:保持怀疑,但别放弃好奇

Go 社区的“避免不必要的抽象”文化,本质上是对认知负担的防御。我们见过太多为了抽象而抽象的烂代码。但 John 提醒我们,不要因此走向另一个极端——恐惧抽象

正确且必要的抽象是强大的武器,它能让我们驾驭巨大的复杂性。只要我们能像海德格尔审视锤子那样审视我们的代码,区分“上手”与“在手”,区分“本质”与“巧合”,我们就能在 Go 的简约哲学中,找到属于自己的那条“正确”道路。

资料链接:https://www.youtube.com/watch?v=oP_-eHZSaqc


你的“锤子”顺手吗?

用海德格尔的视角审视代码,确实别有一番风味。在你现在的项目中,有哪些抽象是让你感觉“如臂使指”的(上手状态)?又有哪些抽象经常让你
“出戏”,迫使你不得不去研究它内部的构造(在手状态)?

欢迎在评论区分享你的“哲学思考”! 让我们一起寻找那个最本质的代码真理。

如果这篇文章带给你一次思维的“脑暴”,别忘了点个【赞】和【在看】,并转发给那些喜欢深究技术的伙伴!


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


你的Go技能,是否也卡在了“熟练”到“精通”的瓶颈期?

  • 想写出更地道、更健壮的Go代码,却总在细节上踩坑?
  • 渴望提升软件设计能力,驾驭复杂Go项目却缺乏章法?
  • 想打造生产级的Go服务,却在工程化实践中屡屡受挫?

继《Go语言第一课》后,我的《Go语言进阶课》终于在极客时间与大家见面了!

我的全新极客时间专栏 《Tony Bai·Go语言进阶课》就是为这样的你量身打造!30+讲硬核内容,带你夯实语法认知,提升设计思维,锻造工程实践能力,更有实战项目串讲。

目标只有一个:助你完成从“Go熟练工”到“Go专家”的蜕变! 现在就加入,让你的Go技能再上一个新台阶!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

© 2026, bigwhite. 版权所有.

🔲 ☆

使用贝塞尔曲线实现道具随机飞动效果

工作中,遇到了一个需求是要实现获得道具和货币的飞动效果:

  1. 根据道具的多少生成不同数目的道具
  2. 货币首先要有一个炸开的效果,然后向一个点汇集;道具从原位置沿直线飞过去
  3. balabala

这里只讨论货币飞行路线的问题。

贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线。PS中的钢笔工具就是使用贝塞尔曲线来绘制矢量曲线的。

这里使用简单的,四个点确定的贝塞尔曲线来实现飞行轨迹。首先p0和p3分别是移动的起点和终点。为了模拟爆炸效果,可以通过随机选取以p0为圆心,长度为R的圆上的点,调整曲线的第一次弯折位置实现。这一点定为P1。最后,根据P1点和P0点之间的关系,进行x轴或者y轴的修正,实现曲线平滑延申到p3,这一点是p2点。

p1点(爆炸点)的获取方式:

1
2
3
4
5
private static Vector3 GetRandomPosition(Vector3 vec)
{
float sita = UnityEngine.Random.Range(-Mathf.PI, Mathf.PI);
return new Vector3(vec.x + Mathf.Cos(sita) * Screen.width / 8, vec.y + Mathf.Sin(sita) * Screen.width / 8);
}

p2点(修正点)的获取方式:

1
2
3
4
private static Vector3 GetFixedPoint(Vector3 start, Vector3 end)
{
return new Vector3(start.x + 3*(end.x - start.x)/4, start.y + 3 * (end.y - start.y) / 4 + p1.y > start.y ? Screen.height / 15 : - Screen.height / 15);
}

根据已知的四个点,实现物体延贝塞尔曲线移动的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/// <summary>
/// 延贝塞尔曲线移动
/// </summary>
private static IEnumerator MoveBezier(GComponent gcom, float time, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
{
for (float t = 0; t < time; t += Time.deltaTime)
{
gcom.position = CalculateCubicBezierPoint(t / time, p0, p1, p2, p3);
yield return 0;
}
gcom.position = p3;
gcom.Dispose();
}

/// <summary>
/// 计算贝塞尔曲线点的坐标
/// </summary>
private static Vector3 CalculateCubicBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
{
float u = 1 - t;
float tt = t * t;
float uu = u * u;
float uuu = uu * u;
float ttt = tt * t;

Vector3 p = uuu * p0;
p += 3 * uu * t * p1;
p += 3 * u * tt * p2;
p += ttt * p3;

return p;
}
🔲 ☆

UGF 源码阅读笔记:(1)安装

UnityGameFramework(UGF) 是由 Ellan Jiang 开发的一个 Unity 游戏开发框架。我决定采用它作为我最近为公司开发的一款 3D 扫雷游戏的开发框架,为此,我觉得有必要仔细阅读它的源码,并做好笔记。另外,我还建了一个仓库去写一些测试代码。

官网已经有教程教用户如何安装框架了。它推荐的安装方式是安装 Unity 插件包,其中核心部分的代码都打包成 DLL 形式了。虽然这种方法方便了用户使用,但我的目的是阅读和调试代码,我得拿到所有的代码。

下面是我的安装方法:

  • 下载某一个版本(如我当前使用的是 v2019.11.09)的 UnityGameFramework,并将它拷贝到新建的 Unity 工程的 Assets 目录之中,如:

  • 删除 UnityGameFramework/Libraries 文件夹下的 GameFramework.dllGameFramework.xml 文件。
  • 下载某一个版本的 GameFramework,并将它的源码拷贝到 Unity 工程的 Assets 目录之中。其存放位置任意,如我就将它放进了 UnityGameFramework 目录之中:

    然后在 GameFramework 文件夹下新建一个 GameFramework.asmdef 文件:

      {
          "name": "GameFramework",
          "references": [],
          "includePlatforms": [],
          "excludePlatforms": [],
          "allowUnsafeCode" : true
      }
    

    然后让 UnityGameFramework.Runtime.asmdef 依赖 GameFramework.asmdef,让 UnityGameFramework.Editor.asmdef 同时依赖 GameFramework.asmdefUnityGameFramework.Runtime.asmdef 即可。

      {
          "name": "UnityGameFramework.Runtime",
          "references": [
              "GameFramework"
          ],
          "includePlatforms": [],
          "excludePlatforms": []
      }
    
      {
          "name": "UnityGameFramework.Editor",
          "references": [
              "UnityGameFramework.Runtime",
              "GameFramework"
          ],
          "includePlatforms": [
              "Editor"
          ],
          "excludePlatforms": []
      }
    

如此,整个框架就安装好了。接下来我们可以新建一个空场景 LaunchScene.unity 作为我们的游戏的启动场景,然后把框架提供的 GameFramework.prefab 拖入到场景中:

现在点击运行按钮就可以让框架代码跑起来了。

❌