通关蔚蓝
最近把《蔚蓝(Celeste)》通关了(指“登上山顶”,并且路上的草莓我基本上都忽略了)。虽说总共只体验了6小时,但是这款游戏的魅力我想我感受到了。
本篇博客想先谈一下在游戏制作上我想要赞美的地方;随后尝试在Unity上复现一下蔚蓝里的“移动与跳跃”动作节奏。
赞美的地方
0. 手感
我想把“手感”放到第一位,因为我觉得:
作为【游戏】,“机制”是所有东西的基石,如果说一个“出色的游戏”在“机制”上很差,那我更倾向于它是一个“出色的互动视觉小说”或“出色的数字画册”,而不是一个“出色的游戏”。
对于平台跳跃类机制的游戏而言,“手感”是最重要的,因为玩家的绝大部分时间都是在“跳跃”等方式移动,因此“手感”直接决定了玩家的体验。
《蔚蓝》的手感就很棒,可以达到“让玩家觉得萤幕中的角色就是自己”这样的境界。此部分在随后有更详细的讨论与实践。
另外,碰撞检测也很“令人信服”:当角色受到伤害时,画面上看起来确实是“接触到了一些致命的东西”;而当画面上看起来角色是安全时,角色也确实不会受到伤害。
1. 剧情的插入
我发现,对于某些游戏的剧情,我很有耐心;但对于某些游戏的剧情,我极其缺乏耐心(比如很多日式RPG游戏,开篇什么都还没有玩就交待各种宏大的世界观,并给出一些反派阴谋诡计以及正派如临大敌的镜头)。我想,这大概和具体剧情的优秀程度关系不大,重点是剧情与游戏结合的方式。
《女神异闻录5》是在此方面我高度赞赏的游戏。而《蔚蓝》在这方面也很令我满意——我不仅不厌烦剧情,甚至还想多看些故事。我发现《蔚蓝》是这样安排的:
- 在最开始的时候,根本没有剧情,玩家只是向前跟着提示做一些操作,通过教学关之后,也仅有只言片语。
- 随后,通过一个稍有难度的章节后,开始有了一些耐人寻味的剧情。
- 随着通关更多更难的章节,剧情变得更长,并含有更多信息。
- 通关一个超难的关卡后,剧情变得很长,甚至有一个章节是“互动叙事”的,即允许玩家选择进行哪些对话。
我觉得这样安排很棒,因为当我刚开始玩的时候,对游戏根本没有“感情”,根本不关心其中的人物有什么样的故事。而随着推进,我对游戏有了更深的感情,也开始关心其中的人物背后的故事与现在的目的,而正好通关一个章节后我有些“身心俱疲”,此时看一点剧情也能起到休息的作用。随后通过一个超难的章节后,我的“劳累值”,“成就感”,“好奇心”都到达了峰值,此时坐下好好聊一下人物的故事不仅不让人厌烦,甚至让人期待。
2. 循序渐进的难度
在看《蔚蓝》的直播时,我将其定位是一个“IWanna”型的自虐型游戏,当买完《蔚蓝》之后我其实不认为自己能通关。然而实际是,它没有那种虐心的感觉,反而吸引着我玩下去直到最后登上山顶。
如果从《蔚蓝》中抽出一关让我玩,那我大概还是会惊叹操作的难度并怀疑自己是否真的能过去,试了几次后觉得自己不行,然后选择放弃。但我是按顺序去玩的,过程中一丝怀疑都没有,而我的自信来源于“上一关”的通过。
也就是说:《蔚蓝》在难度控制上很好,它给了玩家一种信心:假如你能通过“这一关”,那么你就拥有通过“下一关”的能力。
3. 不追求极限难度,有自由发挥的空间
想要增加我通过的那些关卡的难度,是十分容易的:仅仅需要在某个可通关的路线的周围放上更多的障碍物就好了。但是设计者并没有这么做,反而让很多通道有更宽敞的空间来增加了玩家的容错率。
我想,这可能体现了设计师的一个理念是:不想让玩法变成“找到那个唯一的通关路线并严格遵守”,而是“发现一个可行的通关路线”。我认为后者确实更有趣,而且更多的自由度也让玩家觉得自己有更多的主动性。
(以上讨论仅指的是“主线”,如果想要B面通关,或者新的章节,难度上是比较极限的。。。)
4. 艺术风格
我觉得,2D像素风格这种较为抽象的表现方式,很适合用来制作这种对“跳跃”,“碰撞检测”,比较敏感的游戏。
另外,一些像素风格的特效与音效,也为角色的移动增加了动感。
值得注意的是,角色的长发看起来也会根据风向进行移动。而且画面中的一些绳子类物体看起来也很符合物理规律。这些细节效果都很不错。
移动-跳跃 节奏
在这一个视频中:
【游戏制作工具箱】《蔚蓝》的手感为何迷人?Why Does Celeste Feel So Good to Play? | GMTK_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili
作者讨论了《蔚蓝》迷人的手感,并分析了关于移动和跳跃的一些曲线:
总体来讲,《蔚蓝》中的人物加速,减速都很快,而跳跃的置空时间也较低较短,这些都使其更容易掌控。
另外,视频中谈到的一个令人印象深刻的一点是:制作者做了很多其他“非自然”的设计,比如允许玩家在离开平台一段时间内仍旧可以跳跃。他们说你应该围绕玩家的意图设计,而不是做一个要在正确时间按键的精确仿真器。
如果想获得最详尽的移动控制,可以研究作者公开的源代码:https://github.com/NoelFB/Celeste。
不过这个代码很长,除了基本的控制之外还有很多更高级的移动,因此如果只想研究“移动-跳跃”节奏的话它过于复杂了。我选择自己根据视频的曲线与自己的实验,简单在Unity中尝试一下。
Unity中的尝试
单位尺寸
观察后发现,一个Tile会有8*8
分辨率的色块组成,而在1920*1080
像素的屏幕上,每个色块是6*6
像素。也就是说一个Tile此时有48*48
像素,我以一个Tile为1个单位,那么能算出在1920*1080
上是40*22.5
个单位,和实际观察是相符的。
人物的身高是2
个单位。
最大移动速度
我测试从地图左端跑到最右端的时间是3.5秒(有较大的误差),考虑到有加速度,则实际以匀速跑动的话时间比它稍短,我取一个能被40
整除的值3.333
那么速度约为40/3.333 = 12(单位/秒)
加速度
由于视频中已经测量了,主角达到最大速度是6
帧,那么以60帧/秒
来算,就是0.1秒
,那么加速度是:
12/0.1 = 120(单位/秒^2)
而视频测量了刹车用了3
帧,所以刹车加速度应该是它的2倍。
跳跃速度与重力加速
视频中测量跳跃高度是3个身高,不过我自己观察发现双脚离地的最大高度是3.5
单位。
视频中测量跳跃的置空时间是36
帧,以60帧/秒
来算,就是0.6秒
。
也就是说,跳跃的初始速度j
花费了0.3
秒均匀降速为0
,假设没有减速他应该能在这个时间内移动两倍,即:
0.3
j
=
3.5
(
单
位
)
×
2
0.3j = 3.5(单位) \times 2
0.3j=3.5(单位)×2
那么j = 23单位/秒
而重力加速度g
让它从0.3
秒内均匀降速为0
,所以:
g
=
j
/
0.3
=
78
(
单
位
/
秒
2
)
g = j/0.3 = 78(单位/秒^2)
g=j/0.3=78(单位/秒2)
代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Main : MonoBehaviour
{
public GameObject TileGroundTemplate; //地砖Tile
public GameObject Hero; //主角
//地面高度
const float GroundHeight = -6.0f;
//参数:
const float MaxSpeed = 12.0f; //人物的移动最大速度
const float Aaccelerate = 120.0f; //人物的加速度
const float JumpSpeed = 23.3f; //跳跃速度
const float Gravity = 78.0f; //重力加速度
//运行时变量:
Vector3 Velocity = new Vector3(); //当前速度
bool InAir = false; //当前是否在空中
// Start is called before the first frame update
void Start()
{
for(int i=0;i<40;i++)
{
var tile = Instantiate(TileGroundTemplate.gameObject);
tile.transform.position = new Vector3(-19.5f + i, GroundHeight - 0.5f, 0);
tile.SetActive(true);
}
Hero.transform.position = new Vector3(0, GroundHeight, 0);
}
// Update is called once per frame
void Update()
{
}
private void FixedUpdate()
{
if (Input.GetKey(KeyCode.RightArrow))//向右加速
{
Velocity.x += Aaccelerate * Time.fixedDeltaTime;
if (Velocity.x > MaxSpeed)
Velocity.x = MaxSpeed;
}
else if (Input.GetKey(KeyCode.LeftArrow))//向左加速
{
Velocity.x -= Aaccelerate * Time.fixedDeltaTime;
if (Velocity.x < -MaxSpeed)
Velocity.x = -MaxSpeed;
}
else//刹车
{
if(Velocity.x>0)
{
Velocity.x -= Aaccelerate *2 * Time.fixedDeltaTime;
if (Velocity.x < 0)
Velocity.x = 0;
}
else if(Velocity.x<0)
{
Velocity.x += Aaccelerate *2 * Time.fixedDeltaTime;
if (Velocity.x > 0)
Velocity.x = 0;
}
}
if(InAir) //在空中
{
Velocity.y -= Gravity * Time.fixedDeltaTime;
if(Hero.transform.position.y<= GroundHeight)//落地
{
Hero.transform.position =new Vector2(Hero.transform.position.x, GroundHeight);
Velocity.y = 0;
InAir = false;
}
}
else //在地面
{
if(Input.GetKey(KeyCode.Space))//开始跳跃
{
InAir = true;
Velocity.y = JumpSpeed;
}
}
//实时移动:
Hero.transform.position+= Velocity* Time.fixedDeltaTime;
}
}
评价
整体的“移动-跳跃”节奏看起来很相似,但是也可以体会到除了“曲线”以外,原作在增强“移动-跳跃”这个动作上花费的功夫:
- 人物的动作
- 移动与跳跃飞扬的尘土
- 人物一些较为卡通风格的“拉伸”
- 头发的动感
- 。。。