LD49复盘:游戏中的观察者模式
Ludum Dare 49圆满结束了,我们的《绝地天通(Babel Blocks)》最终取得了1413名,放在三千多个参赛作品中也还是不错的!欢迎大家试玩我们的游戏:https://guinytime.itch.io/babel-blocks
在此我想对我在本次开发中负责的模块做一些复盘。UI部分已经是老生常谈了,我想聊聊我在开发中用到的观察者模式。
什么是观察者模式?观察者模式是一种行为型设计模式,定义了一种一对多的依赖关系,即当一个对象(称为发布者或被观察者)的状态发生变化时,所有依赖于它的对象(称为订阅者或观察者)都会自动收到通知并更新。
在游戏开发中,这种模式通常用于处理事件广播、状态更新等场景。例如,当玩家生命值减少时,游戏中的血条、UI、音效等观察者都会随之更新。
观察者模式的关键结构由以下三部分组成:
发布者(Subject):
管理观察者列表,提供添加、移除订阅者的方法。
当状态发生变化时,通知所有订阅者。
订阅者(Observer):
定义一个接口,所有观察者必须实现该接口中的更新方法。
当主题通知时,观察者根据需要更新自己的状态。
通知机制:
发布 ...
§2-2:基础纹理
基础概念
纹理映射(Texture Mapping)
纹理映射指的是将一张贴图映射在模型表面,逐纹素(texel)的控制模型的颜色。
纹理映射坐标(Texture-mapping Coordinates)
通过模型的纹理展开(展uv),我们可以获得一组纹理映射坐标,储存在每个顶点上。通常纹理映射坐标由(u, v)来表示,所以我们会简称纹理映射坐标为UV坐标。
不管贴图实际上有多大,UV坐标的范围通常被归于[0, 1]之间,并以纹理贴图的左下角为原点
纹理空间(Texture Image Sace)
纹理空间在纹理映射坐标的基础上乘以了纹理分辨率(如分辨率为256*256,新的uv坐标为(256u,256v))而得到的新坐标,用于寻找纹理贴图上对应的像素点的信息
纹理采样(Texture Sampleing)
我们获取纹理映射后对应纹理贴图像素点的过程
纹理管线的完整流程,可参考:https://zhuanlan.zhihu.com/p/393323667
Unity中的纹理属性
在向Unity导入纹理之后,检查器中可以对纹理进行一系列设置:
具体的设置见下:
纹理类型(Textu ...
§2-1:基础光照模型
光照模型分为两种:
一种是基于物理的光照模型(PBR),非常真实的反映了现实世界的光照,但是需要大量复杂的计算
另一种是经验光照模型(如兰伯特),用较为简单的方式模拟了物理光照,也能得到不错的效果
本章节主要介绍经验光照模型
基本概念
光源(Light Source)
光从光源中发射出来,照到物体表面。对于不同类型的光,需要设置不同种类的光源(例如影响全局的场景光、点光源以及投射灯等等)。
在Unity中,当我们选择创建光源时,有以下四种选项(反射球暂时先不考虑):
辐照度(Irradiance)
辐照度是衡量一个光源发出的光的多少使用的单位,用来量化光。
对于平行光,辐照度可以通过计算垂直于光源方向l的单位面积上单位时间内穿过的能量得到。
而存在一定夹角的时候,可以通过求光源方向l和法线之间的夹角的余弦值求得(就是投影到垂直平面的方向的分量)。
当光线间隔相同时,光与平面的夹角越大,单位面积内光的数量越少。
出射度(Exitance)
衡量出射光线的数量和方向的量,利用入射光线的数量和角度计算。和辐照度存在线性关系。
吸收和散射 ...
§1-4:光栅化/Rasterize
光栅(Raster)这个词最初来源于德语的“屏幕”一词,相当于英语中的Screen。那么光栅化的含义就比较明确了,指的是把图形从标准立方体(NDC)绘制到屏幕上的过程。
屏幕坐标系
我的笔记本电脑的主显示屏分辨率是1920*1080,代表长1920个像素,宽1080个像素,一共有1920*1080个像素。假设以屏幕左下角为原点建立坐标系,可以给每个像素一个坐标。但由于像素具有实际大小,每个像素的中心点坐标为(x+0.5,y+0.5)(x + 0.5, y + 0.5)(x+0.5,y+0.5)
从NDC到屏幕坐标系
屏幕映射(Screen Mapping)
NDC是一个长宽高范围均为[-1, 1]的标准立方体。在经过MVP矩阵变换与齐次除法之后,现在要做的就是把具有三维属性的NDC映射到只有二维属性的屏幕空间,怎么实现呢?
如果忽略z(深度信息),其实可以直接把NDC的x和y属性“拉伸”成屏幕空间的形状,使用视口变换矩阵:
M=(width200width20height20height200100001)M =
\begin{pmatrix}
\frac{width}{ ...
§1-3:坐标空间与MVP矩阵变换
从观察者的观察空间投影到屏幕空间
什么是MVP矩阵?MVP矩阵的三个字母指的是模型矩阵(Model)、观察矩阵(View)和投影矩阵(Projection)。回想一个模型从导出到最终呈现在屏幕上的过程,我们发现模型的顶点的坐标经历了这么些事:
在建模软件中,模型顶点的坐标是以模型中心为原点计算的
在把模型拖入场景之后,模型顶点的坐标是以世界中心为原点计算的
从摄像机出发观察模型
将模型投影到摄像机上,进行渲染,最终呈现在玩家眼前
实际上,这个变换就是使用了MVP矩阵。从模型空间的顶点,我们最终得到了投影在相机上的结果。再想想写shader的时候,顶点着色器往往包含以下语句:
12v2f o; // 传输到片元着色器o.pos = UnityObjectToClipPos(v.vertex); // 将顶点坐标从模型空间(Object)转换到裁剪空间(Clip)
我们知道,顶点着色器最基本的功能就是将顶点坐标从模型空间转换到裁剪空间。这里的UnityObjectToClipPos函数的作用即将顶点坐标乘上 ...
§1-2:数学基础
写shader对线性代数的要求还是挺高的w…毕竟基础原理和线代密切相关,主要是矩阵和矩阵变换那块的内容,现在来复习一下!
坐标系
为了给每个点一个确切的表示,我们规定一个坐标系。坐标系基于一个人为规定的原点,以及相互垂直的坐标轴。二维的笛卡尔坐标系属于小学生都会的范畴就不多提了,三维笛卡尔坐标系由于镜面对称(像手性碳一样)分为两种:左手系和右手系。
如何区分左手系和右手系?习惯上,我们用:
拇指表示x轴正方向
食指表示y轴正方向
中指表示z轴正方向
很明显,用左手和右手都可以获得一个三维笛卡尔坐标系,它们镜面对称从而不完全一样,于是我们把左手表示的坐标系称为左手系,右手的称为右手系。Unity和Blender使用的都是左手系。
那么这两种手系可以互相转换吗?答案当然是可以,只需要把左手系的一根坐标轴的方向反过来它就变成了右手系,对于右手系也是同理。这也是为什么从摄像机角度出发的观察空间(View Space)是右手系,因为摄像机出发的前向的z轴是负方向。
不同手系的正向旋转
不同手系的正向旋转完全不同,不过我们可以用手性法则来表示不同手系的正向旋转!
由 ...
§1-1:渲染流水线
我们总在说渲染管线,几乎每一个与TA有关的课程第一课都和渲染管线相关,这是一切图像的基础。就拿最近在用的Unity内置渲染管线为例吧,其中包含了CPU和GPU两个部分,总的流程图如下所示:
应用阶段/CPU处理阶段
应用阶段的处理场所在CPU。这一阶段主要处理以下几件事:
准备好场景数据
剔除不需要渲染的部分
设置渲染顺序
加载数据到显存
调用DrawCall
输出渲染图元(Rendering Primitives)作为几何阶段的输入。通俗来讲,渲染图元可以是点、线、三角面等,这些信息会传递给GPU渲染管线处理。
剔除(Culling)
CPU阶段的剔除是粗粒度的,和GPU阶段的裁剪不同。粗粒度剔除发生在物体层面,剔除是以物体1为单位进行的。这一步包含了三种剔除:
视锥体剔除
视锥体剔除指的是剔除掉相机视锥体之外的元素,视锥体即相机的可视范围,全部在外的物体(比如下图的鸟)不需要渲染,部分在外的物体会在GPU渲染部分被裁剪。
层级剔除
层级剔除指物体所处的层(Layer)是否被相机所渲染。如果相机的渲染层不包含该物体的layer,则该物体不被渲染。譬如在上文的例子中, ...
MVC设计模式
MVC 模式的全称是 Model-View-Controller,即模型-视图-控制器。严格来说,MVC并不是GoF(四人帮)归纳出的一种独立设计模式,而是组合模式(UI界面的构建)、策略模式(VC之间的一对多关系)和观察者模式(MV间的事件触发)的组合。
这种设计模型通常用于用户图形界面的图形/数据/逻辑分离(是不是感觉在某些方面很像 ECS?),因此在游戏领域中常被用于 UI 相关的功能上。那么,什么是 M / V / C 呢?
M(模型)
模型层(M 层)负责存放数据,类似于 ECS 中的 C(组件)。
V(视图)
视图层(V 层)负责展示用户图形界面,类似于 ECS 中的 E(实体),是用户与之直接交互的元素。
C(控制器)
控制层(C 层)负责处理数据逻辑,类似于 ECS 中的 S(系统),包含了所有数据计算的部分,负责更新M层的数据。
使用 MVC 模式的目的是将 M 和 V 的实现代码分离,从而使同一个程序可以使用不同的表现形式。比如一批统计数据可以分别用柱状图、饼图来表示。C 存在的目的则是确保 M 和 V 的同步,一旦 M 改变,V 应该同步更新。
Unity中的 ...
用Unity简单实现明日方舟
前言
这次学校工作室的考核任务(是的还是考核)是复刻一款游戏,给出了三个游戏选择(饥荒/杀戮尖塔/元气骑士)不过我都没有玩过,于是想了想我决定做一个简陋版的明日方舟。之前在b站看到过大佬仅耗时48小时开发出明日方舟的战斗界面,感觉好像也不会很难吧……就去试了!
但是做了之后才发现其实是很有挑战性的一次任务。先上两张游戏界面的图(p1p2)!
制作思路
场景:
最开始我的想法包括了五个场景:两个关卡、选择干员的界面、看板界面和登录起始界面(包括一些本地化的内容,工作量挺大的)。后来由于时间不够了就砍成了登录起始界面和一个关卡。
干员:2D骨骼/Trail Renderer
选择什么干员来演示比较合适呢?方舟并没有公开模型,如果要获取对应的gif会比较麻烦,而自己画角色好像也很麻烦。
纠结了半天我决定画三个自己的原创角色,并使用2D骨骼动画制作了相关的站立、战斗的动画(对2D骨骼感兴趣的同学可以在b站上找Michael老师的系列教程参考学习)
在干员战斗的时候你会注意到一些秘制特效,这是用Unity自带的组件Trail Renderer做的。这个组件很强大,如果加上一点代码能得到非常酷 ...
用Unity简单实现合成大西瓜
最近一款名叫“合成大西瓜”的游戏风靡空间,大家在真香之后都笑称自己为“西瓜人”,随后各种恶搞大西瓜的趣图、大西瓜bot更是接踵而至,甚至诞生了“合成大西瓜护肝版”、“合成小葡萄”、“合成大国家”等一系列衍生体…本文将主要探讨如何用Unity做一个“合成大西瓜”。
素材搜集
素材,或者说贴图展现了水果们的样貌。在这里如果把贴图替换成其他元素,比如说香蕉啦、波兰球啦一类的我们就能得到合成大国家等等的恶搞游戏。
这里为了简单(其实是我不会画)表现一下水果,笔者画了几个球,如图:
从左到右分别表示的是葡萄、小番茄、橘子、柠檬、猕猴桃、西红柿、半个西瓜和西瓜。游戏规则是两个等级相同的水果可以合成更高一级的水果,合成出西瓜就算获得游戏胜利。
这里在合成路线上省略了椰子、土豆等中间部分的水果,因为道理是一样的嘛就不画了(说到底还是因为懒惰
UI
笔者简单设计了一个UI,如图:
可以看到这个UI主要由三个部分组成。第一个部分是顶端的白色图片,这张图片将用于显示下一个出现的水果的图案;第二部分是左上角的分数,用来统计玩家通过合成获得的分数;第三部分则是左部、右部和底端的边界,都加上了碰撞器,防止水果 ...