Portfolio
In this article, I will list all the works I have participated in or completed, as well as some related work!
Game Projects
2020
2020 Net-Ease High School MiniGame Competition - 前世今生狩猎小队
Your browser does not support the video tag.
Art
Introduction:In 前世今生狩猎小队, players take on the role of hunters hunting a giant monster. Each time a hunter dies, a new clone is created to replicate the player’s previous actions. After multiple deaths, a squad is formed, working together to finally defeat the mons ...
项目作品集汇总
在这篇文章里,我会罗列出所有我参与或独立完成的作品,以及一些相关的工作!
参与制作的正式作品
2020
2020网易游戏高校MINI-GAME挑战赛 - 前世今生狩猎小队
Your browser does not support the video tag.
游戏美术
项目介绍:在《前世今生狩猎小队》中,玩家扮演猎人狩猎一个巨型怪物,猎人每次死亡都会制造出一个新的克隆体来复刻玩家生前的操作,死亡多次以后就形成了一个小队,互相配合,最后击败怪物。
主要工作:这是我接触的第一个游戏项目,在本项目中负责给各位大佬端茶倒水游戏场景、道具等素材的绘制。
西二在线第三轮考核 - 合成大西瓜
全栈
站内链接
项目介绍:该项目是我参加本科计算机相关社团“西二在线工作室(West2Online)”Unity方向第三轮考核时的作品,考核内容为在一个月内完成一个小游戏的复刻,我所选择的是当时的热门小游戏《合成大西瓜》。
主要工作:这是由我作为全栈实现的第一个游戏项目,通过该项目接触并熟悉了Unity游戏引擎的各项基础功能。
西二在线第四轮考核 - 复刻明日方舟
全栈
站内链接
项目介绍:该项目是我参加本 ...
2024年终总结
小时候觉得时间过得很慢,每年经历的事都仿佛历历在目。上了大学之后,时间逐渐变得同质化了,我甚至记不起我的 2020 和 2021 有什么差别,这让我有些苦恼了。如果之后有人问我某某年都干了什么呀,结果一句话说不上来的话,那也太尴尬了,简直像在毫无目的地虚度光阴(也或许确实是这样)。虽然我从 2019 年开始每年都会给下一年的自己写一封信,但信的内容总是太过于简陋(外加一些难以言喻的回旋镖),我决定从今年开始好好写一写年终总结。由于是第一年写年终总结,加上这一年发生了不少大事,所以其实这篇文章不仅是一个“总结”,更像一篇流水账式的回忆录。
2024 对我来说是很 神奇 的一年。为什么说“神奇”呢?因为在这一年我重新见到了一位故人,这在之前的我看来是极其不可思议的。
这位故人是我在 2015 年夏天的时候认识的。2015 年的我特别迷恋天文学(因为看了点介绍太阳系的纪录片),周围又没有能聊这方面的人,所以就在 qq 上加了一些相关的爱好者交流群,还在群里办了两场科普讲座(其实就是对纪录片内容的总结)。在群里风风火火地吹牛的时候也有一些人通过某些渠道知道了我是个 15 岁的小屁孩这件事,于 ...
在终面之后——更好的关卡系统
之前聊过了战斗系统的优化,今天聊聊《终面》中的关卡系统,关于我是如何实现的,以及应该怎么去优化它。
首先,《终面》的关卡结构是下面这样的一颗固定的树状结构:
可以看到,不包括开场CG,整个游戏一共有24个关卡(场景),每个关卡有独立的编号、类型,以及所指向的后续关卡(这是固定的)。
我的做法
由于每个场景的房间布局是确定的,因此我为每种类型的关卡创建了一个场景:
其中 0_Driver 只会在刚进入游戏时加载一次,用于各种系统(包括关卡系统)的初始化。这个场景存在的目的是避免过场景不销毁的一些组件在游戏返回标题时与新建的组件产生冲突。
关卡的数据结构是一个结构体:
1234567891011[System.Serializable]public struct LevelStruct { public string name; // 关卡类型 public int id; // 关卡id(用来确定进度) public int[] childId; // 子关卡id // 构造 public LevelStruct(string n ...
在终面之后——更精确的战斗系统
十一月毕业典礼结束之后我终于算正式毕业了,随着腾讯 2024 游戏创作大赛颁奖典礼的结束,《终面/OneLastInterview》的开发到现在也算是告一段落了。《终面》能在众多参赛作品中突围进入决赛,这已经是大赛方对我们的努力和实力的认可了,很开心。
最近正值秋招时节,经常被问到简历上的项目,《终面》作为我最新的项目自然是被经常提及的,经过面试官们的点拨,我也认为项目中的关卡系统和战斗系统需要结合业内做法更进一步的改进。本文将重点讨论战斗系统方面的优化,探讨如何实现更好的战斗系统。
首先介绍一下《终面》中 Boss 战斗相关逻辑的策划案。对于Boss从巡逻到发起攻击,其中的逻辑是:
pie
graph LR
巡逻 -- 无玩家 --> 巡逻
巡逻 -- 发现玩家 --> 警觉计时
警觉计时 -- 无玩家 --> 巡逻
警觉计时 -- 玩家在视野内 --> 计时结束触发攻击
警觉计时 -- 被弹反 --> 硬直
硬直 -- 硬直计时结束 --> 巡逻
注:硬直时玩家的伤害倍率提升为2倍。
对于boss所使用的具体攻击招式,则分为了两个阶段:
一阶段(血量大于1500 ...
C#内存管理
在C#应用的背后…
C#是微软推出的一种基于.NET框架和后来的.NET的、面向对象的高级编程语言。C#衍伸自C和C++,继承了C和C++的强大功能,同时去掉了一些复杂特性,使其成为C语言家族中高效强大的编程语言。C#以.NET框架类库作为基础,拥有类似Visual Basic的快速开发能力。微软在2000年发布了这种语言,希望借助这种语言来取代Java。
.NET 框架中有一些但看缩写非常类似的概念,例如:CLI/CIL/CLR/CTS/CLS/JIT/GC 等等。
在通用语言基础架构 (Common Language Infrastructure) 中,C# 在构建时会被编译为 CIL (Common Intermediate Language),即通用中间语言(在一些地方也被称为字节码/ByteCode)。在.Net开发平台下,所有语言(C#、VB.NET、J#、C++/CLI)都会被编译为通用中间语言,再由 CLR (Common Language Runtime),即通用语言运行时负责运行。
在 CLR 中,CLR 通过 JIT (Just In - Time Comp ...
热更新其三:Addressables
Addressables(可寻址资源系统)是 Unity 新推出的用于智能管理 AssetBundle 资源和加载的工具包,它为 AssetBundle 提供了更高层次的抽象,旨在取代 AssetBundle Manager。
Addressables 允许开发人员通过一个独一无二的地址字符串请求资产,无需关心底层复杂的资源加载逻辑。
一旦资产(例如预制件)被标记为“Addressable”,它就会生成一个可以从任何地方调用的地址,无论资产位于何处(本地或远程),系统都会找到它及其依赖项,然后将其返回,非常简单易用。
与AssetBundle的关系
Addressables 的底层基于 AssetBundle,相比于通过自己写代码造轮子或第三方插件去管理 AB 包,Addressables 提供了一个直观易用的图形管理界面,同时可以帮助开发者自动处理 AssetBundle 的生成和依赖关系,减轻项目管理的压力和打包遇到问题的几率。
AssetsBundle的依赖关系
有一些 AB 包之间存在依赖关系,这是什么意思呢?比方说某个 AB 包在的 Prefab A 使用了一个材质 M,而 ...
热更新其二:AssetBundle初窥
在项目开发的过程中,我们经常会遇到“版本更新”或者“资源补丁”,例如游戏素材随着活动而发生改变等等:
在上一篇文章中我们也提到过了,如果每次更新都让用户重新下载客户端对产品留存来说是一大挑战,因为大家都很懒,而且卸载重装的流量也是要钱的。在实际项目中,项目组都会使用热更新将基础功能模块和业务逻辑分开,方便做热更新。
在 Unity 中实现资源热更新的方式有 AssetBundle 和 Addressable,一般大家都用前者(因为轮子多),后者是 Unity 推出的比较新的资源管理系统(旨在替换 AB 包)。
AssetBundle
我们常说的“打包”其实指的就是打 AB 包。“bundle”一词就是“a bunch of …”的意思,因此顾名思义,asset bundle是一捆资源的意思。
AssetBundle 是一个存档文件,包含可在运行时由 Unity 加载的特定于平台的非代码资源(比如模型、纹理、预制件、音频甚至整个场景)。
AssetBundle 可以表示彼此之间的依赖关系,例如一个 AssetBundle 中的材质可以引用另一个 AssetBundle 中的纹理。为 ...
热更新其一:基于xLua的Unity代码热更新尝试
最近用 Unity 做了一个移动端纯 UI 应用的尝试,应用的功能挺简单的,涉及到UI界面的响应、适配和切换等等,其中有一个部分是“通知”:
一般情况下“通知”都会显示为暂无通知,但是在实际项目中,开发者需要依照需求对通知的内容进行更改,比如改成“关于十周年庆典的活动说明”:
每次更新通知的时候,替换文本的内容、重新打包成apk发送给用户让他们下载就可以实现版本更新了。但是这样的体验对用户来说显然是糟糕透顶的,为了用这玩意每次更新用户都得重新安装整个应用,太浪费流量了,对用户留存率来说是一个威胁。
在实际的游戏项目中,我们通常采用热更新的方式来实现版本更新。游戏热更新是指在不需要重新编译打包游戏的情况下,在线更新游戏中的一些代码和资源,比如活动运营和打补丁。用户每次只需要在本地获取需要更新的资源就可以了,不用重新下载客户端。
在实际开发中,我们通常会把业务逻辑与整体架构分离,将业务逻辑放在可以热更新的模块中,方便开发者随时修改业务数据(比如数值策划提出需要修改某个伤害计算公式)。
那么该怎么实现热更新呢?热更新分为资源热更新(例如图片素材、音频)和代码热更新,在这篇文章中我们将先 ...
ECS设计模式
什么是ECS?为什么我们需要它?
在传统游戏开发中,开发者习惯于将每个功能写成一个class,例如玩家控制器(PlayerController)。在这个脚本中不但包含了玩家自身的数据(data,例如速度、血量等),还包含了针对这些数据的处理逻辑(behavior)。
有时作为开发者,我们需要在极短时间完成原型发开来验证玩法的可行性(或者我们只是打了个game jam),一些能够被重复利用的“轮子”是相当重要的。我们前面所提到的每个功能一个 class 的做法其实很不适合拓展,因为数据和行为是耦合的,不便于修改。
有没有更好的设计模式呢?接下来让我们隆重推出本文的主角——ECS设计模式!本文将参照轻量级 ECS 解决方案 Entitas 对该设计模式从源码层面进行介绍。首先,什么是ECS ?ECS 即 Entity-Component-System,指“实体-组件-系统”设计模式,是一种有别于传统 OOP(面向对象)类型的设计模式,它是 DOP(面向数据)的。
Entity/实体
实体类似于 GameObject,可以简单理解为“游戏对象”。
Component/组件
组件包含了实体 ...
游戏反作弊的一些总结
游戏中的作弊问题一直是开发者面临的一个严峻的问题,各种类型的外挂层出不穷,在笔者小时候就有许多修改器存在,这些外挂俨然成了影响游戏公平性的罪魁祸首,甚至能在网络上直接搜到某些猖狂的外挂公司。目前大多游戏公司都采用专业的游戏安全公司提供的防外挂服务,例如腾讯的游戏安全ACE等。
虽然安全公司提供了很多服务,但一个不争的事实是:正所谓道高一尺魔高一丈,各种游戏外挂依然层出不穷,射击游戏更是成了外挂的重灾区。防外挂是一项长期工作,需要开发者们与外挂分子斗智斗勇,本文将介绍一些作为开发者的我们可以采取的几种反外挂方案。
防外挂措施
非法输入
服务端防外挂的关键点是——不能信任客户端。因为任何客户端传来的数据都有可能是经过外挂篡改的数据。举个例子:假设一个玩家想要购买某个价值648金币的商品,客户端在玩家点击确认时会向服务端发送一个协议,例如:
1234{ "item_id" : 1, // 购买的商品id "need_money" : 648 // 花费的金币}
这时按正常的逻辑,服务端会检查玩家剩余金币是否足 ...
网络同步-可靠UDP
及时性的通用解决方案-可靠UDP
经过前两篇文章的学习,我们已经对网络同步中实现一致性的两种方式——帧同步和状态同步有了一定了解。现在我们需要考虑一下“及时性”,为同步算法选择一个恰当的网络协议了。传输层有两个协议:TCP 和 UDP。在能保证 UDP 传输可靠性的前提下,我们可以有“可靠UDP(RUDP)”。那么,该选择 TCP 还是 RUDP 呢?
可以看到,在不同的网络环境下,RUDP 的平均延迟都是要好于 TCP 的,这是为什么呢?
TCP 虽然以稳定著称,但因为 TCP 的 Nagle 算法,默认情况下会收集尽量多的小包,然后再一次性发送。这样虽然这样可以减少带宽,但会导致及时性变差(不过我们可以通过 TCP_NODELAY 选项去关闭这个功能)。
TCP 具有超时重传机制。当客户端或服务端有一帧的包没有收到的话,直到这个包被重传到端之前,游戏的逻辑是无法正常执行的。即使使用 TCP 的快重传机制(即收到三个重复ACK时立马重传),产生的时延相比UDP来说还是太久了。
当出现丢包之后,TCP 的拥塞控制机制会触发慢启动和拥塞避免,这会限制发包的速度。
因此在实际开发 ...
网络同步-状态同步
继续探讨同步难题…
上一篇文章介绍了帧同步,帧同步是解决网络同步的一个很好的方案,服务端以固定频率向所有客户端广播和指令。但帧同步非常容易受到网络波动影响,我们需要模拟玩家镜像的行为(甚至可能因为等待指令收到而出现卡顿),这对于对网络要求很高的 fps(第一人称射击游戏)、tps(第三人称射击游戏)、qte 判定来说是不可接受的。为什么呢?拿射击游戏来说,如果稍有网络延迟,玩家就难以瞄准目标,或者可能被莫名其妙打死。
什么是状态同步?
状态同步就类似于我们在网络同步系列的第一篇文章中说的直接同步用户的状态。客户端将指令发送给服务端,服务端计算出状态后广播给其他客户端,客户端收到后进行更新。这样虽然会加重服务端的运算负载,但可以有效避免客户端作弊的发生。
当然,我们还可以结合一下分布式运算的想法,将计算压力分摊到客户端上。在这种模式中,客户端将部分指令即时计算成状态之后,将状态发送给其他客户端(其他客户端同理),其他客户端收到后进行更新。这特别适合需要及时看到游戏反馈的射击类游戏,因为如果采用帧同步或者服务器模式(由服务器计算指令)的状态同步的话,数据经由服务端运算再传回各个客户端,会导 ...
网络同步-帧同步
什么是帧同步?
古典帧同步
古典帧同步又叫Lockstep Synchronization(锁步同步),其一大特色在于当一个人未同步完成时,其他人都必须等待直到其同步完成,然后再执行下一帧的同步。
上图有两个客户端。客户端 B 的网络比较差,A 和 B 都在 T0 时间点向服务器发送了用户输入,A 的请求在 T1 到达服务端,B 的请求在 T2 到达服务端,前面我们提到,服务器需要收集“所有用户”的请求后才开始工作,因此需要到 T2 时间点才开始生成 frame。
因为 Client B 比较慢,我们“惩罚”了所有的玩家。而且为了服务器能正常工作,即使客户端没有产生任何指令也需要往服务器发送心跳包,这造成了流量开销。锁步同步可以用在确实需要玩家等待的回合制游戏或者对延迟不敏感的慢节奏游戏。
帧同步就对应于我们在上文中说的同步用户的输入的操作、或者这些操作所产生的一系列事件,保证所有人在每一帧上都获得相同的输入,执行相同的逻辑,最后得到一致的表现和结果。之所以被称之为“帧”同步,是因为帧同步是以固定频率(比如60Hz)同步玩家的下一帧的操作的。从中我们也能看出,帧同步包含了两个同步, ...
网络同步-基本概念介绍
什么是网络同步
网络同步是指通过网络将数据从一个系统或节点传输到另一个系统或节点,并保持两者之间的数据一致性。换句话说,游戏中的网络同步就是把我的状态同步给远程玩家看到的“我”的镜像,使双方在屏幕上看到的效果是一致(或接近一致)的。
在常见的多人联机游戏(例如下棋、格斗、fps等等)中,网络同步是非常重要的一项需求。游戏中的玩家需要时时刻刻了解其他玩家的状态或者行为,才能辅助自己做出下一步决策(例如是否开枪等)。
如何知道其他玩家的状态,又如何让其他玩家知道我的状态呢?这时候就需要网络同步技术了。
同步设计目标
同步设计主要有两个目标,即 一致性 和 及时性 。一致性指多个客户端看到的效果是一致的,而及时性指能否及时完成网络同步。在实际工程中我们往往很难同时满足一致性和及时性,这是因为网络延迟始终存在,客户端在接收到最新状态前只能对其他玩家镜像的状态进行预测(通常会采用一些客户端障眼法,例如插值)。
在 一致性 和 及时性 这两个核心目标下,我们可以拆解出三个子设计要素(公平、体验和开销),根据实际情况进行优先级权衡。一般来说用户体验是位于首位的。
公平性
确定
操作具有幂等性,即 ...