只有UnityPlayer.dll和启动器,典型的momo打包,直接分析data/managed/Assembly-CSharp.dll

这就是恐怖效果的代码,并且大家都调用的这一个逻辑,可造成画面偏移、拉伸、抖动,使角色发生偏移。

这就是最让这个游戏具有真实感/突破次元壁的数据,老莫会和你聊天的时候说出“其实你是叫xxx”“你的后台还挂着xxx”这类话语,就是这也代码的功劳,当然更出名一点的meta游戏君彼女应该也是有类似于这样的脚本的。

这就是这个游戏精彩所在了,选词写诗,其定义了,第一轮不会出现诡异画面,选了什么词,谁的好感度+3(并非经验),第二轮有四百零一分之一的概率出现诡异画面和“glitchtext”,而第三轮会出现诡异画面,当二十个词语全部选择正确会获得完美诗歌成就,并且还会有个很隐藏的羊叫音效,也是概率极低

没写完……这个诗歌游戏都花了好久去看,另外也看了关于整个游戏的页面渲染,存档,文本等一些基本的,等我解包过后继续,或者也不用解包,

ok将其导出,基本都是c#编写的.cs文件,我们就分析其中的一些文件吧(我感兴趣的)

[Serializable]
	public class Blood : Line, IDisplayable, ITransform, IApply, IPostRenderable
	{
		// Token: 0x060010D5 RID: 4309 RVA: 0x00044398 File Offset: 0x00042598
		private void AddSquirt()
		{
			float f = Random.Range(0f, 1f) * 6.283f;
			RenpyStandardProxyLib.Blood.SquirtParticle item;
			item.alive = true;
			item.xspeed = (float)this.m_SquirtPower * Mathf.Cos(f);
			item.yspeed = (float)this.m_SquirtPower * Mathf.Sin(f);
			this.m_Squirts.Add(item);
		}

		// Token: 0x060010D6 RID: 4310 RVA: 0x000443FC File Offset: 0x000425FC
		private void AddBurst(float startTime)
		{
			RenpyStandardProxyLib.Blood.DropParticle dropParticle;
			dropParticle.alive = true;
			dropParticle.xspeed = (Random.Range(0f, 1f) - 0.5f) * this.m_BurstSpeedX + 20f;
			dropParticle.yspeed = (Random.Range(0f, 1f) - 0.75f) * this.m_BurstSpeedY + 20f;
			dropParticle.time = this.m_ParticleTime;
			dropParticle.startTime = startTime;
			dropParticle.x = 0f;
			dropParticle.y = 0f;
			dropParticle.zoomstart = 0.75f;
			dropParticle.zoomend = 0f;
			dropParticle.zoomtime = Random.Range(0.25f, 0.55f);
			dropParticle.zoom = dropParticle.zoomstart;
			this.m_Drops.Add(dropParticle);
		}

		// Token: 0x060010D7 RID: 4311 RVA: 0x000444D8 File Offset: 0x000426D8
		private void AddDrip(float startTime)
		{
			RenpyStandardProxyLib.Blood.DropParticle dropParticle;
			dropParticle.alive = true;
			dropParticle.xspeed = (Random.Range(0f, 1f) - 0.5f) * this.m_DripSpeedX + 20f;
			dropParticle.yspeed = Random.Range(0.1f, 1f) * this.m_DripSpeedY + 20f;
			dropParticle.time = this.m_ParticleTime;
			dropParticle.startTime = startTime;
			dropParticle.x = 0f;
			dropParticle.y = 0f;
			dropParticle.zoomstart = 0.75f;
			dropParticle.zoomend = 0f;
			dropParticle.zoomtime = Random.Range(0.25f, 0.55f);
			dropParticle.zoom = dropParticle.zoomstart;
			this.m_Drops.Add(dropParticle);
			this.dripCount++;
		}

		// Token: 0x060010D8 RID: 4312 RVA: 0x000445BC File Offset: 0x000427BC
		private void AddSquirtDrip(RenpyStandardProxyLib.Blood.SquirtParticle s, float startTime)
		{
			RenpyStandardProxyLib.Blood.DropParticle dropParticle;
			dropParticle.alive = true;
			dropParticle.xspeed = s.xspeed + (Random.Range(0f, 1f) - 0.5f) * 20f;
			dropParticle.yspeed = s.yspeed + (Random.Range(0f, 1f) - 0.5f) * 20f;
			dropParticle.time = this.m_ParticleTime;
			dropParticle.startTime = startTime;
			dropParticle.x = (Random.Range(0f, 1f) - 0.5f) * 5f;
			dropParticle.y = (Random.Range(0f, 1f) - 0.5f) * 5f;
			dropParticle.zoomstart = 0.75f;
			dropParticle.zoomend = 0f;
			dropParticle.zoomtime = Random.Range(0.25f, 0.55f);
			dropParticle.zoom = dropParticle.zoomstart;
			this.m_Drops.Add(dropParticle);
		}

		// Token: 0x060010D9 RID: 4313 RVA: 0x00005AE8 File Offset: 0x00003CE8
		public void Apply(IContext context)
		{
		}

		// Token: 0x060010DA RID: 4314 RVA: 0x000446C4 File Offset: 0x000428C4
		public bool Immediate(GameObject gameObject, CurrentTransform currentTransform)
		{
			this.m_TotalEffectTime = 0f;
			this.m_Squirts = new List<RenpyStandardProxyLib.Blood.SquirtParticle>(this.m_NumSquirts);
			for (int i = 0; i < this.m_NumSquirts; i++)
			{
				this.AddSquirt();
			}
			this.m_Drops = new List<RenpyStandardProxyLib.Blood.DropParticle>(this.m_BurstSize);
			for (int j = 0; j < this.m_BurstSize; j++)
			{
				this.AddBurst(0f);
			}
			return true;
		}

这是RenpyStandardProxyLib.cs里生成血滴的逻辑,有喷射粒子(squirparticle)和滴落粒子(dropparticle)两种,基于滴落血滴生成爆发型血滴,基于喷射粒子生成喷射分裂血滴。重点看看爆发型血滴:y轴上的速度随即范围更负,即血滴向下运动,贴合现实,缩放逐渐变为0,即血滴消失。yuri自杀靠的就是这个逻辑喵~

if (Renpy.Input.CurrentInputModality == RenpyInputModality.Controller || Renpy.Input.CurrentInputModality == RenpyInputModality.Keyboard)
			{
				if (EventSystem.current.currentSelectedGameObject == null || !EventSystem.current.currentSelectedGameObject.activeInHierarchy)
				{
					Selectable selectable = null;
					RenpyScreenBehaviour renpyScreenBehaviour2 = null;
					int num = int.MinValue;
					foreach (RenpyScreenID key in this.activeScreens)
					{
						RenpyScreenBehaviour renpyScreenBehaviour3 = this.screenLookUpTable[key];
						Selectable defaultSelectable = renpyScreenBehaviour3.GetDefaultSelectable();
						if (defaultSelectable && defaultSelectable.gameObject.activeInHierarchy && defaultSelectable.interactable && renpyScreenBehaviour3.MenuFocusPriority >= num)
						{
							renpyScreenBehaviour2 = renpyScreenBehaviour3;
							selectable = defaultSelectable;
							num = renpyScreenBehaviour3.MenuFocusPriority;
						}
					}
					if (selectable != null)
					{
						if (renpyScreenBehaviour2 != null)
						{
							renpyScreenBehaviour2.UnfocusSelectables();
						}
						RenpyButtonUI component = selectable.GetComponent<RenpyButtonUI>();
						if (component != null)
						{
							component.muteNextSelectedSound = true;
						}
					}
					EventSystem.current.SetSelectedGameObject((selectable != null) ? selectable.gameObject : null);

这是RenpyScreenManager里最核心的键盘鼠标手柄输入适配逻辑,主要是确保玩家玩的时候总有一个交互的UI(UI 是 User Interface 的缩写,中文叫用户页面),设计了优先级机制MenuFocusPriority,值越高越优先,所以当弹窗打开时玩家就只能在弹窗上操作,另外设置了component.muteNextSelectedSound = true,即静音下一次选中音效,可以避免声音刷屏,只有玩家主动交互时才响。

// Token: 0x060004F8 RID: 1272 RVA: 0x00016E14 File Offset: 0x00015014
	public string GetFileName(string poemName, string languageTag, PoemCreationApp.OutputImageData imageData)
	{
		if (imageData.filePostfix == null || string.IsNullOrEmpty(imageData.filePostfix) || string.IsNullOrWhiteSpace(imageData.filePostfix))
		{
			return poemName + "_" + languageTag + ".png";
		}
		return string.Concat(new string[]
		{
			poemName,
			"_",
			imageData.filePostfix,
			"_",
			languageTag,
			".png"
		});
	}

	private string GetFullFileName(string poemName, string languageTag, PoemCreationApp.OutputImageData imageData)
	{
		return this.GetScreenshotDirectory() + this.GetFileName(poemName, languageTag, imageData);
	}

	// Token: 0x06000501 RID: 1281 RVA: 0x00017048 File Offset: 0x00015248
	private string GetScreenshotDirectory()
	{
		char directorySeparatorChar = Path.DirectorySeparatorChar;
		return string.Format("{0}{1}LauncherAssets{2}GalleryImages{3}GeneratedPoems{4}", new object[]
		{
			Application.dataPath,
			directorySeparatorChar,
			directorySeparatorChar,
			directorySeparatorChar,
			directorySeparatorChar
		});
	}

	// Token: 0x06000502 RID: 1282 RVA: 0x00017097 File Offset: 0x00015297
	private Lines GetLines(string languageCode)
	{
		return ActiveAssetBundles.PerformLoadBundle("lang" + languageCode).LoadAsset<Lines>("Assets/DDLC/ScriptableObjects/Lines_" + languageCode + ".asset");
	}

这是PoemCreationApp里负责生成诗歌截图和输出文件的关键代码,截图格式是诗歌名_后缀_语言.png,并生成跨平台的截图存储路径(通过获取当前系统的目录分隔符实现,win是\,mac和Linux是/,生成结果长这样Assets\LauncherAssets\GalleryImages\GeneratedPoems\,而最后的getline就是从Ren’Py 的资源包中加载指定语言的文本配置文件然后返回实例,后续通过该实例获取具体的诗歌

// Token: 0x060001B9 RID: 441 RVA: 0x00007658 File Offset: 0x00005858
	public override void prepareForUse()
	{
		this._target = (this._ownerTween.target as Transform);
		this._endValue = this._originalEndValue;
		if (this._ownerTween.isFrom)
		{
			if (this._useLocalPosition)
			{
				if (this._isRelative)
				{
					this._startValue = this._target.localPosition + this._endValue;
				}
				else
				{
					this._startValue = this._endValue;
				}
				this._endValue = this._target.localPosition;
			}
			else
			{
				if (this._isRelative)
				{
					this._startValue = this._target.position + this._endValue;
				}
				else
				{
					this._startValue = this._endValue;
				}
				this._endValue = this._target.position;
			}
		}
		else if (this._useLocalPosition)
		{
			this._startValue = this._target.localPosition;
		}
		else
		{
			this._startValue = this._target.position;
		}
		base.prepareForUse();
	}

	// Token: 0x060001BA RID: 442 RVA: 0x0000775C File Offset: 0x0000595C
	public override void tick(float totalElapsedTime)
	{
		float value = this._easeFunction(totalElapsedTime, 0f, 1f, this._ownerTween.duration);
		Vector3 vector = GoTweenUtils.unclampedVector3Lerp(this._startValue, this._diffValue, value);
		if (this._useLocalPosition)
		{
			this._target.localPosition = vector;
			return;
		}
		this._target.position = vector;
	}

这是PositionTweenProperty里负责算动画起点终点和每一帧的计算并应用于位置的逻辑,其中prepareForUse在动画开始前只执行一次,通过正向反向动画和相对绝对位移以及本地坐标计算出起点和终点。而tick就是根据动画播放进度,计算当前应该在的位置,并赋值给物体。两者结合完成动画的播放,所有的位置移动包括人物和特效都是这套逻辑。

嗯差不多就分析这么多吧,或许还会更新……

分类: study

0 条评论

发表回复

Avatar placeholder

您的邮箱地址不会被公开。 必填项已用 * 标注