首页 > 代码库 > Endless Sky源码学习笔记-3

Endless Sky源码学习笔记-3

文本解析:

       将txt存入DataFile,DataFile包含DataNode,每个DataNode是txt中的一行,每个DataNode包含若干token(std::vector<string>),token之间用空格隔开,多个单词可用引号括起来组成一个token,每个txt在读取时在末尾加一个换行符(\n),DataNode之间有层次关系,根据txt中的缩进确定(就是数空格)。

       游戏的文本主要在void GameData::LoadFile(const string &path, bool debugMode)里面解析和载入,每一次读入一个txt到DataFile,他的上级是void GameData::BeginLoad(const char * const *argv),内含两级循环调用加载,将游戏主目录和插件目录的data文件夹内的txt依次读入。对txt的解析在DataFile类的构造函数中,他会调用void DataFile::Load(const char *it, const char *end)来实际进行解析,分为两步,先建立DataNode,然后对当前DataNode分token,用一个字符指针来访问每一个字符,数空格并将每一行开头的空格数记入std::vector<int>,注释(‘#’开头)和空白行(‘\n’开头)直接跳过该行(末尾为‘\n’)。添加DataNode需要明确层次关系,首先应该检查上一个DataNode的空格数与当前的哪个大。然后获取上一级的children list地址,再将上一级作为参数传入children的构造函数用来构造当前node,他会初始化上一级为当前对象的parent并预留4个token的空间,再将新对象加入上一级的children list,这样层次关系就建立了,然后再将该child加入std:: vector<DataNode *>,记录对应的空格数,用来决定下一个DataNode的层次关系。实际上root并不含实际值也就是token,只有children list,所有token都在不同层次的children里面,DataFile含root下一级的children list,该级DataNode又有children list,如此就可以继续往下了,用迭代器访问只能访问DataNode的下一级。第一步至此完成,第二步按分隔符来就行了。

 

将data载入系统:

DataNode的token都是std::string,实际使用时如果需要的是数字,调用double DataNode::Value(int index) const将指定token转为double,支持正负号、小数点和科学计数法。载入信息如下:

  1. color,包含rgba四个部分,需转成float,范围为0~1,用来区分不同政府和显示地图、任务以及飞船状态;
  2. conversation,游戏中访问某些星球或者空间站会触发任务,任务一般会跳出对话框,有的可以选择多个分支,当然也有接受和不接受,也可以是必须接受,而且可以触发接受以后马上起飞(有个任务就这样,飞起来就被三架raven围攻,只好退回去前面的存档去攒钱买船再来)。conversation有自己的node来管理显示和跳转,也就是说nodes[0]是conversation的第一个文本,指向下一个node的序号开始为1,conversation包含nodes(std::vector<Node>),Node类包含std::vector<std::pair<std::string, int>>来储存文本和下一个node的序号(因为有可能跳转,所以可能会不再指向创建时的node),相邻同级文本可以合并到同一个node,顺序是后面的合并到前面,判断依据是遇到choice将合并flag设为false,表示这个node不能合并后面的node,关键字不占用node。重要的关键字:
    1. scene用来显示图像;
    2. label用来标记node以便跳转,label不重复,每次添加label(std::map<std::string, int>)会检查是否有未关联的跳转,如果有会试图用新添加的label来关联,同时记录label在nodes中的位置用来指向下一个文本node;
    3. choice用来显示分支选项,choice的下级node为文本,有的choice的下下级node会有goto label,那么就需要建立跳转关系,如果跳转label已经添加,那么直接将label的位置设为跳转语句前一个文本的下一个node序号,如果没添加,那么就将其label和文本位置存进std::multimap<std::string, std::pair<int, int>>,还有的是decline等关键字,则将其对应的数字设为跳转语句前一个文本的下一个node序号(<0);
    4. name用来表示空的choice;
    5. branch用来表示条件分支,条件为branch的下一级,可为多重条件(and、or),存入std::vector<Expression>,expression可为一元或二元操作,一元操作会映射为二元操作,branch关键字后面必须跟条件测试为true的跳转label,此label会被建立关联,如果为false则继续执行下一个node,如果为其他关键字,同choice处理;
    6. apply用来改变condition,比如完成某些任务可以添加一些属性,可以用来触发下一个任务;
  3. effect,用来定义显示效果,比如引擎的尾焰、武器和Jump Drive等。关键字后跟效果名,下一级为sprite、sound、lifetime、velocity、random velocity、random angle、random spin和random frame rate,sprite下一级为sprite的动画参数,包括frame rate(用60归一化)、frame time(倒数就是frame rate)、delay、start frame、random start frame、no repeat和rewind,具体参数作用待查;
  4. event,用来定义特殊事件,需要定义对应的condition用来标记他是不是被触发了。其下一级用来设置事件发生对游戏状态的影响;
  5. fleet,用来定义舰队,关键字后面跟舰队名,同名舰队可以多次载入,载入的ship如果有variant,会将variant替换掉原来的ship。下一级用来设置fleet都有些什么ship,还有其他属性,variant后面跟了个权重用来估算战斗力,ship名后面跟的是数目;
  6. galaxy,用来定义星系,关键字后面跟星系名。相当于给某个区域取名,下一级为位置和显示的sprite,其实就是在指定位置显示sprite;
  7. government,用来定义所属政府,比如Republic、Free World和Hai等,关键字后面跟政府名,参数比较多,主要包括了各种好感度及玩家行为对其影响,有的外星人会说鸟语,有的敌对星球可以打或者贿赂,这些都在这里定义,government有个ID(数字)方便设置好感度;
  8. interface,用来定义显示界面,关键字后面跟界面名和对齐位置,下一级参数可设置显示的sprite、按钮、文字和各种自定义图案等。point关键字可用来画一些自定义图案,主要用来定位和设置尺寸。visible关键字可用来设置下一个node是否可见,后跟if条件。其他关键字可用来设置显示各种element(ImageElement、TextElement和BarElement,均继承自Element),各自的constructor调用void Interface::Element::Load(const DataNode &node, const Point &globalAlignment)来载入参数,整个Interface就是由element和point组成;
  9. minable,用来定义可开采的物体,继承自Body类,游戏中背景会有很多飞来飞去的陨石,有的里面有payload,但是概率不高,每个陨石1000血(有个用陨石撞星球去改变地貌的任务挺犀利,虽然需要大量脑补。。。);
  10. mission,用来定义任务,这货的加载就是一大坨if。日常任务(非剧情)有送货(有的有截止时间,有的会让送非法货物,如果被navy扫描到会罚款 )、送人、护送(一般有截止时间)和打海盗(这个比较赚),会随机生成,可以定义任务出现的地点,有的mission需要用LocationFilter来临时改变星球的某些属性,ConditionSet被用来记录任务的状态,另外会用到std::map<Trigger, MissionAction>来存储任务触发响应,有的任务还包含NPC,不同NPC会有不同的行为模式;
  11. outfit,用来定义ship上的装备(包括武器、货物等除机身以外全部可加载的东西),包括种类、图像、效果、音频和价格等属性,基本就是存到map或vector里,比较麻烦的是加载武器,武器有图像、音频、效果、各种杀伤力和机动性参数等,所有又是一堆if;
  12. outfitter,用来定义装备的买卖信息,基本就是添加删除读取outfitter里的outfit,所以需要传加载的outfit给他,实际是有一个Sale类来管理;
  13. person,用来定义非任务型NPC,游戏中会有飞船跟你说话,地点随机,人物唯一,基本就是加了一些限制的随机;
  14. phrase,用来定义短语,游戏有很多随机产生的名字啊对话啊什么的,就靠这个,需要载入词典;
  15. planet,用来定义星球,参数也多,if搞起。一个system可以有多个planet或者station,planet指可以登陆的星球,有装饰性星球不能登陆,planet不一定有shipyard或outfitter,但是都可以买卖货物,比较有其他功能的是attributes参数,有些任务会用到,还有tribute,带这个参数的可以被征服,然后会给你上贡;
  16. ship,用来定义飞船,参数多,支持飞船变种(同一个model参数不同),基本也是if,武器系统比较复杂且一个ship可装备多个武器,所以另有一个Armament类来管理,这样便于多个武器之间协同工作;
  17. shipyard,用来定义ship的买卖,和outfitter类似,只是传的是ship;
  18. start,用来定义初始化信息,包括时间、地点和玩家账户;
  19. system,用来定义星系信息,包含若干planet,所以要传planet给他。habitable后面跟的是可居住星球的范围,belt后面跟星球的环形带半径,link后面跟通过可通过Hyperdrive直接到达的星系,fleet后面跟舰队名和出现的周期,会用随机概率处理这个周期,随机的相关内容待查,trade定义的是system内商品的基价,实际价格会根据商品的供求量变化,object用来定义比较大物体(planet、star、station等),继承自body,会绕轨道运动,每天更新位置,object后跟星球名表示可以登陆(也就是planet),这个时候会返回对应的Planet对象来提供更多信息,同时记录planet所在的system(如果已经记录过则不添加),object的加载需要明确其类型(star、station、moon),object 有层次关系,注意这里的period不是出现概率而是用来运行周期,会换算成速度(度/天),distance为与parent的距离,offset为角度差(用来区分同速星体),object的运行角度用step表示(0~2^16-1),层次关系由加载object的顺序决定,载入完成后设置非planet的message,这里发现distance还有一个作用,一个星系的顶层object也可以设distance参数,这个distance用来与habitable一起决定object的message,远了就hot,近了就cold,中间就直接不可居住;
  20. trade,用来定义商品信息,每一类商品价格有范围,每一类有具体的商品表,实际交易不可见,有的任务可以用来显示,还有的是garbage,卖不了钱;
  21. tip,用来定义提示信息,下一级为具体提示,与tip后的string合并显示。
  22. 设置星系相邻关系:所有星系都载入到Set<System>里,通过遍历所有星系并比较距离是否大于NEIGHBOR_DISTANCE来建立相邻关系,这里分两类,一类是直接通过Hyperlink连接的星系,他们即便距离大于NEIGHBOR_DISTANCE也相邻,一类是距离小于NEIGHBOR_DISTANCE的任何星系,包括由Hyperlink连接的星系。遍历时只记录了当前星系的相邻星系,所以会重复检查;
  23. 设置飞船:前面从txt中已经分别读取了outfit和ship的信息,现在就是要把outfit装到ship上,获取ship的初始化属性(在加载txt时记录),如果是变种则将未定义属性从原型中复制过来,如果有新增加hardpoint,则添加相应的hardpoint,飞船的类别是装备类的一个属性,设置gun和turret的可装备数(hardpoint),武器的hardpoint和outfit为一一对应,一个ship可有任意个hardpoint,hardpoint可不挂载outfit,有的outfit只会影响attributes,所以只是数值变化,武器会体现在显示效果上,每一个outfit的增减都要计数,ship有最大加载量,同时武器和引擎也有自己的上线,hardpoint的加减是由Armament类来管理,武器由Hardpoint类管理,先计数,后安装,安装时会设置武器的发射方向,和武器的最大发射半径(不是射程,是距离ship中心的最大距离),飞船有主次关系,玩家的旗舰是parent,其他的是escort,只有parent和escort在同一系统时escort才能修复,当ship的hull降到一定值以下或crew没有了(特殊情况除外)就会失控,当ship登陆spaceport时,hull、shield、crew和fuel都会充满,所有负面效果都置零;
  24. 设置NPC飞船,同上。

 

载入存档:

  读取存档和之前的文本分析一样,由PlayerInfo类管理,读取后需要调用void PlayerInfo::ApplyChanges()将内容载入到游戏状态中。changes标签下为对system的改变,在改变system之间的link或neighbor状态时,需要将两个system的link或neighbor状态添加或删除,因为同一状态存储在两个system中,如果两个system距离大于限制,在添加或删除link的同时要添加或删除邻近关系。载入游戏中的日期,根据日期来设置星体的位置,日期为int date=day+(month<<5)+(year<<9),所以读区时要相应位移并用“&”取指定位数,有闰年。hull为负数表示ship挂了,特殊NPC挂了要记录,有上贡的planet要加入记录(std::set<const Planet *>)。

 

载入shader:

  根据不同的着色方式分为多种shader,上层为一个通用的Shader类,用来进行对象管理、编译等通用操作,实际使用的shader的定义和操作在对应的类中。shader对象的创建主要包括编译和联结两步,编译时将OpenGL版本和shader代码存入vector<GLchar>,并在末尾加“\0”(NUL),调用OpenGL函数将其整体编译,编译完成后创建program并将编译的shader与其联结。shader类型有:

  1. FillShader,用来画矩形;
  2. FogShader,用来产生战争迷雾;
  3. LineShader,用来画线;
  4. OutlineShader,用来画sprite的轮廓;
  5. PointShader,用来画“点”;
  6. RingShader,用来画圆或者环;
  7. SpriteShader,用来画sprite模型。

 

Endless Sky源码学习笔记-3