# 模块简介

希望了解某个模块具体作用的朋友,可以阅读这部分;希望了解“如何编写新的代码”,请参阅如何编写你的代码。本部分中,我将自底向上构建起整个框架,先从最重要的Archive模块开始,按照 UtilityLayerGlobal LayerGameObject Layer的顺序依次介绍所有主要模块的功能

# Archive

# ConditionTable

# RandomGenerator

# ObjectPool

# PlayerFinder

# Tube&TubeUnit

# PrefabList

# PositonManager

# Relic

Relic模块控制了玩家的藏品相关的所有内容,包括用来储存藏品描述的RelicInfo类,用来管理玩家身上藏品的RelicHandler类,用来管理和分发所有游戏中藏品实例的RelicPool类,以及所有藏品的基类Relic类

# RelicInfo.cs

这个类是一个数据类,存放藏品的所有信息,接下来列出该类存储的数据

Property Type Description
id string 藏品的ID
name string 藏品的名字
description string 藏品的效果描述
story string 藏品的故事和来源等其它文字信息
rank int 藏品的稀有度
permanent bool 藏品是否为永久藏品
totalResource int 藏品在一局游戏中的总数
className string 藏品所对应的代码类的名称
icon Sprite 藏品所对应用来显示的Sprite

# RelicPool.cs

这个类是一个全局的单例,使用RelicPool.GetInstance()便可以访问到单例本身。该类是所有藏品的工厂,能够按照配置文件创造游戏中的所有藏品,除此之外,从游戏功能的角度而言,它存储了本局游戏中所有藏品的数量信息,并可以按照需求创建藏品回收被弃用的藏品,接下来介绍本类的所有函数,以及他们的使用方法和作用

Relic CreateRelicInstanceById(string relicID):应由RelicHandler调用,创建并返回一个ID为relicID的藏品实例

string GetRandomRelicFromPool(string setName, bool takeAway = true):从名称为setName的藏品集合(是全体藏品的一个子集)中获得一个随机的,可获得的relicID并返回,若takeAway为false,则不会将这个relic资源量减少(也就是视为并没有取走这个藏品)

void ReturnRelicToPool(string relicID):将relicID对应的藏品返还回池子,也就是该藏品对应的资源量+1

RelicInfo GetRelicInfo(string relicID):返回relicID对应的藏品的信息

bool IsRelicPermanent(string relicID):返回relicID对应的藏品是否为永久藏品

# RelicHandler.cs

这个类是一个全局的单例,使用RelicHandler.GetInstance()便可以访问到单例本身。该类存储了所有玩家持有的永久非永久藏品,并且可以添加删除藏品。接下来介绍本类的所有函数,以及他们的使用方法和作用

void AddRelic(string relicID):将relicID对应的藏品添加给玩家

void RemoveRelic(string relicID):从玩家身上删除relicID对应的藏品(若有)

void LoadRelic(string relicID, int count):加载存档时使用,向玩家身上一次性添加指定的count数目的relicID对应的藏品

void ClearAllRelic():开始新的一局时使用,清空玩家身上的所有藏品

# Relic.cs

这个类是一个抽象类,当创建具体的Relic的时候,应该继承自此类,首先我们介绍这个类提供给外部调用的通用方法:

void InitFromFile(int initCount):加载存档时使用,将藏品的数量同步为存档中的initCount并通过OnLoad()函数使得藏品状态同步为该层数对应的状态

void Add():添加藏品时使用,藏品数量增加1并通过OnAdd()函数执行添加时的逻辑

void Remove():移除藏品时使用,藏品数量减少1并通过OnRemove()函数执行添加时的逻辑

接下来是子类必须手动实现的函数:

OnAdd()添加藏品时的逻辑,应当实现诸如藏品效果根据层数更新等逻辑。

OnRemove()减少藏品时的逻辑,同样应当实现诸如藏品效果根据层数更新等逻辑。

OnLoad()从存档加载藏品时的逻辑,应当确保在这里的逻辑能够让藏品的状态与藏品在该层数应有的状态一致。

# Mission

在这个游戏中,玩家可以在任何地方接收NPC派发的任务,当完成任务后,可以与NPC对话并交付任务,从中获得任务奖励。为了实现这些功能,需要存储实现任务具体功能的Mission类,存储任务信息的MissionInfo类,全局任务派发器MissionDispatcher类,以及管理全局任务的MissionManager类。

# MissionInfo.cs

这个类是一个数据类,存放某个任务的所有信息。接下来列出该类存储的数据

string missionID:这个任务的ID

string name:任务名

List<string> progressTitleList:一个任务由多个任务进度组成,当所有任务进度走满时即代表能够交付任务。这条数据存储了由这些进度条名称构成的列表

List<int> maxProgressList:进度条最大进度构成的列表,这代表所有任务条件都应当能转化为整数进度

int rank:任务的等级,通常等级越高任务就越难完成,奖励也更丰富

Type missionClass:任务对应的代码类

# Mission.cs

这个类是一个抽象类,当创建具体的Mission的时候,应该继承自此类,该类提供了所有任务的通用方法,并存储了非文字信息(这是为了本地化方便所做出的分离措施),首先我们介绍这个类提供给外部调用的通用方法:

string MissionID(),NpcID NpcID(),int ProgressCount(),int MaxProgress(int i),int CurrentProgress(int i):这几个函数用来获得对应的属性,也就是任务的ID,发布任务的NPC的ID,任务进度的数目,最大进度和目前进度。

void SetProgress(int i, int value),void AddProgress(int i, int value):增量式的添加某一进度或者覆盖式的设置某一进度,无论哪种方式,都会更新任务的完成状态。

bool CheckCompleted():查看任务是否可被完成

void Create(string id, NpcID npc, List<int> maxProgressList, List<int> startProgressList):由MissionManager调用,根据给出的参数为任务的非文字信息赋值,将任务的完成状态注册进ConditionTable并调用Init()初始化这一任务。

接下来是子类必须手动实现的函数:

void Init():对于同一类任务,在不同的难度等级下会对应不同的最大进度,在这一函数中,你需要根据难度等级设置任务的最大进度。

# MissionManager.cs

这个类是一个全局的单例,使用MissionManager.GetInstance()便可以访问到单例本身。该类是所有任务的工厂,能够按照配置文件创造游戏中的所有任务,除此之外,从游戏功能的角度而言,它存储了本局游戏中所有任务的所有信息,并提供了接口用以在需要时由MissionDispatcher创造任务实例,接下来介绍本类的所有函数,以及他们的使用方法和作用

Mission CreateMissionInstance(string id, NpcID npcID, List<int> startProgress = null):由MissionDispacher调用,通过Mission的Create函数创建并初始化

MissionInfo GetMissionInfo(string missionID):获取编号为missionID的任务的信息

# MissionDispatcher.cs

这个类是一个全局的单例,使用MissionDispatcher.GetInstance()便可以访问到单例本身。该类的作用主要是按照难度发配任务,并管理奖励的发放。存储了本局游戏中所有任务任务奖励对应关系,以及游戏的根据天数的难度分布,并提供了能够提交某一任务并获得奖励的方法。接下来介绍本类的所有函数,以及他们的使用方法和作用

int SampleADifficulty(InGameTime time):根据难度分布以及当前时间time,抽样并返回一个难度用以创建任务

void UpdateCurrentMissionList(InGameTime time):在时间变更时调用,根据time更新目前的任务列表

bool SubmitMission(NpcID npcID):尝试提交npcID对应的任务,若成功,则移除任务并使玩家获得奖励,若失败则无事发生。返回提交是否成功。应当调用这个函数以完成任务的提交。

Mission GetNpcCurrentMission(NpcID npcID):根据npcID获得该NPC现在派发的任务

# Dialog

Dialog模块管理了游戏中可能出现的对话。在这个游戏中,大部分对话GameDialog由特定物体(NPC,商店等)发出,在对话过程中会出现不同的对话选项DialogSelection,用以完成对话功能并触发分支对话。如果这条选项需要承担某些功能,那么点击这个选项后会触发对话函数DialogFunctions。为了分离模型和视图,能够触发对话的物体需要一个对话请求器DialogRequester,将请求发送给全局的对话管理器DialogManager

# GameDialog.cs

这个类是一个数据类,存放某条对话的所有信息,对话在文件中以类似链表的方式存储。接下来列出该类存储的数据

string id:本条对话的ID

string speakerID:本条对话叙述者的ID

string message:对话的内容

string defaultNextID:这条对话默认的下一条对话的ID

List<DialogSelection> selections:这条对话所有能够触发的选项

# DialogSelection.cs

这个类是一个数据类,存放对话选项的所有信息,接下来列出DialogSelection类存储的数据

string selectionText:选项的文字内容

string nextID:选择选项后对应的下一条对话ID

string dialogFunction:若选项要触发某个DialogFunctions,则为对应的函数名,否则为空

List<DialogCondition> conditions:该选项出现的全部条件

DialogCondition类同样是数据类,因其所存储的数据与ConditionTable类中存储的一致,故请参考ConditionTable模块中的讲解

# DialogFunctions.cs

这个文件中定义了变量集DialogFuncParam以及静态类DialogFunctions,当需要使用新的函数时,请仿照静态类中已有的函数的写法,将可能使用到的变量声明在变量集内,并在静态类中编写具体函数。

# DialogRequester.cs

这个类是一个抽象类,根据对话类别创建不同的子类,首先我们介绍这个类提供给外部调用的通用方法:

void RequestPlayingDialog():将Requester的具体信息包装为一个DialogRequestHeader,并传递给DialogManager开始新的对话。DialogRequestHeader是一个数据类,包含如下数据

string firstID:对话应当从哪个ID开始

DialogRequester requester:发起对话的DialogRequester的引用

DialogShowMethod showMethod:对话的显示方式,是一个枚举。游戏中的对话除了以传统对话框的方式显示。也可能以字幕,全屏显示等方式呈现。

作为本游戏中出现频率最高的对话类型,NPCDialogRequester已经完成实现并可以用于所有NPC上,使用时在NPC的Prefab上挂载即可。

# DialogManager.cs

这个类是一个全局的单例,使用DialogManager.GetInstance()便可以访问到单例本身。该类是所有对话的工厂,能够按照配置文件创造游戏中的所有对话,除此之外,从游戏功能的角度而言,它存储了游戏中所有的对话,能够按照本局游戏的进度以及全局进度的推进情况控制对话和选项是否出现,按照要求以正确的方式显示对话,以及以既定的跳转顺序逐条播放对话。接下来介绍本类的所有函数,以及他们的使用方法和作用

void StartNewConversation(DialogRequestHeader requestHeader):由DialogRequester调用,根据requestHeader的信息选择第一条对话并开始播放。

void PlayDialog():根据默认的顺序播放下一条对话

void ExecuteSelection(DialogSelection selection):执行selection选项对应的DialogFunction并跳转到selection中指定的下一条对话

# NPC

本游戏的NPC是及其重要的功能,每一个NPC都是独一无二的,他们可以根据熟悉等级向玩家发布任务,与玩家对话讲述故事,结算玩家的任务,同时给予玩家奖励。某一个NPC可能在指定的某些时段出现在星港的一部分指定位置。关于任务和奖励,以及对话的部分可以参见Mission以及Dialog。其余功能为本模块所着重解决的内容

为了完成剩下的功能,需要NpcInfo类用以记录NPC信息,NpcPositionManager类用以控制NPC在何时出现在何地,以及一个全局的NpcManager类来对NPC进行统一管理

NpcTinyData类只记录了NPC的ID,挂载在Prefab上作为NPC最小化的唯一标识符

# NpcInfo.cs

这个类是一个数据类,存放NPC的所有信息。接下来列出该类存储的数据

NpcID id:NPC的唯一ID,注册在一个枚举(NpcID.cs)中,每当添加一个新NPC就应当向该枚举中注册一个新项

string name:NPC的名字

string description:NPC的描述

string story:NPC的介绍小故事

Sprite npcAvatar:NPC的头像

Sprite npcImage:NPC的立绘

List<int> subFamiliarPerLevel:游戏中每个NPC都有熟悉等级,代表了玩家对他们的了解程度。每个熟悉等级之下有几个子等级,可以通过任务提升子等级,进而提升熟悉等级。这个变量是每个熟悉等级下子等级的数量

int maxFamiliarLevel:人物的最高熟悉等级

int familiarLevel:人物的当前熟悉等级

int subFamiliarLevel:人物的当前子熟悉等级

# NpcPosionManager.cs

这个类是一个全局的单例,使用NpcPositionManager.GetInstance()便可以访问到单例本身。该类能够根据配置文件中写明的NPC出现的可能性和可能位置,随机挑选一个位置生成作为NPC的生成地点。该类仅有一个能够外部调用的函数void ResampleNpcPositions(InGameTime time),功能是在时间发生变化的时候根据time重新指派NPC的位置。

为了保证模型和视图分离,该类在生成位置时只负责生成需要使用的位置的名称,具体将位置名称对应到游戏Transform的方法见 PostionManager

# NpcManager.cs

这个类是一个全局的单例,使用NpcManager.GetInstance()便可以访问到单例本身。该类存储了所有Npc的Prefab以及所有Npc的信息,以及所有游戏场景中出现的Npc的引用。可以生成(若未生成)或获取(若已生成)Npc的引用。并获取Npc的基本信息和出现状况等数据。实际上,为了确保控制器经由模型操纵视图,任何对NPC的操作,都应当通过NpcManager代为发出。接下来介绍本类的所有函数,以及他们的使用方法和作用

GameObject InstantiateNpcPrefab(NpcID npcID):若编号为npcID的NPC不在场,直接生成该NPC,返回生成的NPC

GameObject GetNpcReference(NpcID id):若编号为id的NPC在场,返回其引用

bool IsNpcAppearance(NpcID id):返回编号为id的NPC是否在场

GameObject InstantiateOrGetNpc(NpcID id):若编号为id的NPC在场,返回其引用,若不在场,生成并返回该NPC

bool RecycleNpcInstance(NpcID npcID):若编号为npcID的NPC在场,将npc的游戏物体回收,返回true,否则返回false

bool HasNpcInfo(NpcID id):返回编号为id的NPC人物信息是否存在

NpcInfo GetNpcInfo(NpcID id):返回编号为id的NPC人物信息

void AddNpcFamiliar(NpcID id, int level):向编号为id的NPC添加大小为level的子熟悉等级

为了方便开发流程,上述方法也可以将NpcID换成string id,NpcID枚举中存在同名项,就会达成完全一样的效果。

# Enemy

在战斗场景太空城中,存在大量的战斗房间,这些房间里可能会分波次刷出各种各样的敌人,敌人除了有自己的数值以外,也可能会被这个房间的深度影响,变得更加强大,为此需要一个EnemySpawner类控制敌人的生成,以及一个EnemyManager类进行敌人的全局管理。除此之外,每一个敌人都拥有一个EnemyInfo类用来记录信息,以及一个EnemyID枚举记录它的专属ID。

需要注意的是,因为战斗和敌人高度相关,因此为了逻辑的连贯性,本应该属于Room模块的RoomBattleManager也会放在这里一并讲解。

# EnemyInfo.cs

这个类是一个数据类,存放敌人的所有信息。接下来列出该类存储的数据

EnemyID id:敌人的唯一ID,注册在一个枚举(EnemyID.cs)中,每当添加一钟新敌人就应当向该枚举中注册一个新项

string name:敌人的名称

string description:敌人的描述

Sprite avatar:敌人的头像Sprite

# EnemyManager.cs

这个类是一个全局的单例,使用EnemyManager.GetInstance()便可以访问到单例本身。该类存储了所有敌人的Prefab,Basic Status以及信息

# EnemySpawner.cs

# Room/BattleRoom/RoomBattleManager.cs

# Map

# Resource

# BattleCharacter

# InteractableObject

在这个游戏中有很多可以交互的物体,玩家可以接近他们后按下特定按键,就可以开启和这些物体的交互流程。为此我们需要一个交互功能的接口IInteractable,以及检测交互并触发交互逻辑的InteractableRayCaster,此外已经实现了多种可交互组件的预设,可以直接挂载到对应物体上使用。

# IInteractable.cs

这是一个接口,所有可交互的物体都要继承这个接口。接下来列举这个接口中需要实现的函数

void InteractThis():定义交互对象交互后的逻辑

string GetInteractableName():获取交互对象的名字

void DebugThis():交互对象在开发中的Debug逻辑

# InteractableRayCaster.cs

将这个类挂在到玩家身上,就可以让玩家与一切可交互物体交互。简单来说,实现方法方法为:玩家向面朝方向发出射线,判断是否碰撞到物体,并获取物体的IInteractable接口,并在检测到对应按键时调用接口的交互或Debug逻辑。接下来讲解这个类可调整的参数的含义

Transform rayCastOrigin:交互射线检测发出射线的起点,射线的发出方向永远为玩家方向,也就是鼠标指向的方向

float rayCastDistance:射线检测的距离

LayerMask layerMask:层级遮罩,可以指定层级剔除不想交互的物体

# 已经实现的交互组件

这里列出已经实现的交互组件并指明他们的适用范围

DungeonEntrance.cs:用于传送到战斗场景太空城的传送门

NpcDialogTrigger.cs:用于NPC触发对话

RoomBeacon.cs:用于信标房的信标

RoomDropItem.cs:用于房间掉落物,目前用于测试,后续应针对不同掉落物做针对开发

RoomExitPortal.cs:用于到下一房间的传送门

# Room

Room模块控制了战斗场景中的房间,让房间可以按照逻辑生成并运行。包括房间类型枚举RoomType,房间ID枚举RoomID,用来存储房间实例的RoomPrefabList类,房间核心RoomCore类和房间管理系统RoomManager类

# RoomType.cs & RoomID.cs

这两个文件中,每个文件仅保存了一个枚举,接下来介绍枚举的含义

enum RoomType:代表房间的类型,分为EliteBattleRoom(精英战斗房间),NormalBattleRoom(普通战斗房间),BeaconRoom(信标房间),ShopRoom(黑市房间),MagicRoom(随机事件房间)五种,原则上不再添加新项

enum RoomID:代表房间的唯一ID,每当添加一个新房间,就应该在枚举里新建一项作为它的ID

# RoomPrefabList.cs

这个类是一个挂载在游戏全局物体上单例,以不同的方式为索引存储了所有房间的Prefab,接下来首先介绍房间的存储和索引模式:

List<GameObject> prefabs:存储了所有可能出现在游戏中的房间Prefab,请注意:虽然这个变量是私有变量,但通过[SerializeField]暴露在了Unity中,这意味着你需要手动在Unity编辑器里将可能出现的房间拖入这个列表之中

Dictionary<RoomID, GameObject> roomList:对每个房间以RoomID建立索引,会在程序开始时自动初始化

Dictionary<RoomType, List<RoomID>>:房间类型和某一类型下所有RoomID的对应关系,会在程序开始时自动初始化

接下来介绍本类的所有函数,以及他们的使用方法和作用

RoomID GetRandomRoomIdOfType(RoomType type):由RoomManager调用,根据Type获取随机的房间ID,原则上不由其它组件调用

GameObject GetPrefab(RoomID id):实例化房间时调用,根据id获取对应的房间Prefab

# RoomCore.cs

这个类是一个抽象类,应当对于每个RoomType创建对应的子类,并将其挂于每个房间的Prefab上。首先我们介绍这个类提供给外部调用的通用方法:

void SendPlayerToSpawnPoint():将玩家传送到房间内所设定的玩家出生点

接下来是子类需要手动覆写的函数(请在覆写的函数内调用基函数):

virtual void StartRoom():房屋被创建时的初始逻辑,基函数已经实现了初始化传送门,禁止存档和传送玩家到出生点的逻辑,应手动实现其它逻辑。

virtual void FinishRoom():房屋结束流程时的逻辑,基函数已经实现了激活传送门和开放存档的逻辑,应手动实现其它逻辑。

virtual void LoadRoomFinishState():在房屋中读档的逻辑,应把房屋重置为完成后的状态。基函数已经完成了将房间初始化,以及将传送门置为正确状态的逻辑,应手动实现其它逻辑。

# RoomManager.cs

这个类是一个全局的单例,使用RoomManager.GetInstance()便可以访问到单例本身。该类能够得出接下来应当生成的房间为何种类型,并通过该类型生成随机的房间。也能够通过这个类方便的完成诸如深度,当前房间,难度房间生成相关变量的改查工作。接下来介绍本类的所有函数,以及他们的使用方法和作用

int GetCurrentDepth():获得当前深度

int AddDepth(int depth):将深度增加depth,返回增加后的深度

int GetCurrentDifficulty():根据当前深度,获取当前的难度等级

List<RoomType> GenerateNextRoomTypes():某个房间初始化时使用,生成一系列的房间类型,对应每个传送门能到达何种房间。

List<RoomType> GetCurrentNextRoomTypes():读取房间存档时使用,获取先前生成的房间类型

RoomID GetRandomRoomOfType(RoomType type):根据房间类型type随机取某个该类型房间的RoomID

GameObject InstantiateRoom(RoomID id):根据id在地图的指定位置生成该Room实例

void SendPlayerToStarBase():把玩家传送到星港的指定位置

# UI