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,支持正负号、小数点和科学计数法。载入信息如下:
- color,包含rgba四个部分,需转成float,范围为0~1,用来区分不同政府和显示地图、任务以及飞船状态;
- 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。重要的关键字:
- scene用来显示图像;
- label用来标记node以便跳转,label不重复,每次添加label(std::map<std::string, int>)会检查是否有未关联的跳转,如果有会试图用新添加的label来关联,同时记录label在nodes中的位置用来指向下一个文本node;
- choice用来显示分支选项,choice的下级node为文本,有的choice的下下级node会有goto label,那么就需要建立跳转关系,如果跳转label已经添加,那么直接将label的位置设为跳转语句前一个文本的下一个node序号,如果没添加,那么就将其label和文本位置存进std::multimap<std::string, std::pair<int, int>>,还有的是decline等关键字,则将其对应的数字设为跳转语句前一个文本的下一个node序号(<0);
- name用来表示空的choice;
- branch用来表示条件分支,条件为branch的下一级,可为多重条件(and、or),存入std::vector<Expression>,expression可为一元或二元操作,一元操作会映射为二元操作,branch关键字后面必须跟条件测试为true的跳转label,此label会被建立关联,如果为false则继续执行下一个node,如果为其他关键字,同choice处理;
- apply用来改变condition,比如完成某些任务可以添加一些属性,可以用来触发下一个任务;
- 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,具体参数作用待查;
- event,用来定义特殊事件,需要定义对应的condition用来标记他是不是被触发了。其下一级用来设置事件发生对游戏状态的影响;
- fleet,用来定义舰队,关键字后面跟舰队名,同名舰队可以多次载入,载入的ship如果有variant,会将variant替换掉原来的ship。下一级用来设置fleet都有些什么ship,还有其他属性,variant后面跟了个权重用来估算战斗力,ship名后面跟的是数目;
- galaxy,用来定义星系,关键字后面跟星系名。相当于给某个区域取名,下一级为位置和显示的sprite,其实就是在指定位置显示sprite;
- government,用来定义所属政府,比如Republic、Free World和Hai等,关键字后面跟政府名,参数比较多,主要包括了各种好感度及玩家行为对其影响,有的外星人会说鸟语,有的敌对星球可以打或者贿赂,这些都在这里定义,government有个ID(数字)方便设置好感度;
- 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组成;
- minable,用来定义可开采的物体,继承自Body类,游戏中背景会有很多飞来飞去的陨石,有的里面有payload,但是概率不高,每个陨石1000血(有个用陨石撞星球去改变地貌的任务挺犀利,虽然需要大量脑补。。。);
- mission,用来定义任务,这货的加载就是一大坨if。日常任务(非剧情)有送货(有的有截止时间,有的会让送非法货物,如果被navy扫描到会罚款 )、送人、护送(一般有截止时间)和打海盗(这个比较赚),会随机生成,可以定义任务出现的地点,有的mission需要用LocationFilter来临时改变星球的某些属性,ConditionSet被用来记录任务的状态,另外会用到std::map<Trigger, MissionAction>来存储任务触发响应,有的任务还包含NPC,不同NPC会有不同的行为模式;
- outfit,用来定义ship上的装备(包括武器、货物等除机身以外全部可加载的东西),包括种类、图像、效果、音频和价格等属性,基本就是存到map或vector里,比较麻烦的是加载武器,武器有图像、音频、效果、各种杀伤力和机动性参数等,所有又是一堆if;
- outfitter,用来定义装备的买卖信息,基本就是添加删除读取outfitter里的outfit,所以需要传加载的outfit给他,实际是有一个Sale类来管理;
- person,用来定义非任务型NPC,游戏中会有飞船跟你说话,地点随机,人物唯一,基本就是加了一些限制的随机;
- phrase,用来定义短语,游戏有很多随机产生的名字啊对话啊什么的,就靠这个,需要载入词典;
- planet,用来定义星球,参数也多,if搞起。一个system可以有多个planet或者station,planet指可以登陆的星球,有装饰性星球不能登陆,planet不一定有shipyard或outfitter,但是都可以买卖货物,比较有其他功能的是attributes参数,有些任务会用到,还有tribute,带这个参数的可以被征服,然后会给你上贡;
- ship,用来定义飞船,参数多,支持飞船变种(同一个model参数不同),基本也是if,武器系统比较复杂且一个ship可装备多个武器,所以另有一个Armament类来管理,这样便于多个武器之间协同工作;
- shipyard,用来定义ship的买卖,和outfitter类似,只是传的是ship;
- start,用来定义初始化信息,包括时间、地点和玩家账户;
- 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,中间就直接不可居住;
- trade,用来定义商品信息,每一类商品价格有范围,每一类有具体的商品表,实际交易不可见,有的任务可以用来显示,还有的是garbage,卖不了钱;
- tip,用来定义提示信息,下一级为具体提示,与tip后的string合并显示。
- 设置星系相邻关系:所有星系都载入到Set<System>里,通过遍历所有星系并比较距离是否大于NEIGHBOR_DISTANCE来建立相邻关系,这里分两类,一类是直接通过Hyperlink连接的星系,他们即便距离大于NEIGHBOR_DISTANCE也相邻,一类是距离小于NEIGHBOR_DISTANCE的任何星系,包括由Hyperlink连接的星系。遍历时只记录了当前星系的相邻星系,所以会重复检查;
- 设置飞船:前面从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都会充满,所有负面效果都置零;
- 设置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类型有:
- FillShader,用来画矩形;
- FogShader,用来产生战争迷雾;
- LineShader,用来画线;
- OutlineShader,用来画sprite的轮廓;
- PointShader,用来画“点”;
- RingShader,用来画圆或者环;
- SpriteShader,用来画sprite模型。
Endless Sky源码学习笔记-3的更多相关文章
- Endless Sky源码学习笔记-1
难得遇到一个比较有趣的开源游戏,又是比较偏爱的太空背景,所以打算学习下源码. Endless Sky的作者是Michael Zahniser,是一个2D太空游戏.整个程序比较简洁明了,数据没有打包,游 ...
- Endless Sky源码学习笔记-5
游戏启动后的UI划分为三个区域,左侧滚动显示credits等信息以及偏好设置和退出按钮,中间显示载入动画,右侧显示玩家信息以及载入存档按钮,调用void MenuPanel::Draw()实现.首先画 ...
- Endless Sky源码学习笔记-4
事件处理: 事件包括:debug模式切换.切换到登陆窗口.退出.窗口大小变化.全屏切换和游戏中的鼠标键盘输入.处理方式分为两类,前几个为简单的if处理,最后一个涉及到游戏中的控制和交互,且事件由每一个 ...
- Endless Sky源码学习笔记-2
数据载入框架: void GameData::BeginLoad(const char * const *argv)为数据载入的最上层method,其主要框架为: void Files::Init(c ...
- Underscore.js 源码学习笔记(下)
上接 Underscore.js 源码学习笔记(上) === 756 行开始 函数部分. var executeBound = function(sourceFunc, boundFunc, cont ...
- Underscore.js 源码学习笔记(上)
版本 Underscore.js 1.9.1 一共 1693 行.注释我就删了,太长了… 整体是一个 (function() {...}()); 这样的东西,我们应该知道这是一个 IIFE(立即执行 ...
- AXI_LITE源码学习笔记
AXI_LITE源码学习笔记 1. axi_awready信号的产生 准备接收写地址信号 // Implement axi_awready generation // axi_awready is a ...
- Hadoop源码学习笔记(6)——从ls命令一路解剖
Hadoop源码学习笔记(6) ——从ls命令一路解剖 Hadoop几个模块的程序我们大致有了点了解,现在我们得细看一下这个程序是如何处理命令的. 我们就从原头开始,然后一步步追查. 我们先选中ls命 ...
- Hadoop源码学习笔记(5) ——回顾DataNode和NameNode的类结构
Hadoop源码学习笔记(5) ——回顾DataNode和NameNode的类结构 之前我们简要的看过了DataNode的main函数以及整个类的大至,现在结合前面我们研究的线程和RPC,则可以进一步 ...
随机推荐
- PSP(11.24~11.30)
27号 类别c 内容c 开始时间s 结束e 中断I 净时间T 作业 构建执法.写博客 14:00 14:40 0m 40m 28号 类别c 内容c 开始时间s 结束e 中断I 净时间T java 编码 ...
- 关于Oracle GoldenGate中Extract的checkpoint的理解 转载
什么是checkpoint? 在Oracle 数据库中checkpoint的意思是将内存中的脏数据强制写入到磁盘的事件,其作用是保持内存中的数据与磁盘上的数据一致.SCN是用来描述该事件发生的准确的时 ...
- Mware vCenter Server 识别固态硬盘为(非SSD)是什么原因?
人工定义一下: 用root登录进ESXi控制台:esxcli storage nmp device list #列出储存清单esxcli storage nmp satp rule add -s VM ...
- Linux下的文件与目录操作 BY 四喜三顺
文件操作权限: chmod 三个八进制数字 文件名 其中:三个八进制数字,第一个代表本用户的权限,第二个代表同组的权限,第三个代表其他用户的权限4代表可读2代表可写1代表可执行例如:chmod 7 ...
- guava – Optional
过多的使用null可能会导致大量的bugs,Google code 底层代码中,95%的集合类默认不接受null值.对null值,使用快速失败拒绝null比默认接受更好. 另外,null本身的含义很模 ...
- 造轮子之数据库对比工具DataBaseComparer
最近同时在维护好几个项目,有些项目是SqlServer的,另一些是MySql的,DBA推荐了一个线上库和线下库的对比工具,用的时候经常会在对比时,半天都没有进度.索性自己这次造个轮子,做了一个纯对比数 ...
- java 将字符串下载为文本文件
通过url访问方法即可进行下载 @RequestMapping("down") public String down(HttpServletRequest request,Http ...
- pthread 学习
1. 创建线程 int pthread_create (pthread_t* thread, pthread_attr_t* attr, void* (*start_routine)(void*), ...
- 环境搭建 Hadoop+Hive(orcfile格式)+Presto实现大数据存储查询一
一.前言 Hadoop简介 Hadoop就是一个实现了Google云计算系统的开源系统,包括并行计算模型Map/Reduce,分布式文件系统HDFS,以及分布式数据库Hbase,同时Hadoop的相关 ...
- 14,SFDC 管理员篇 - 外部数据集成
1,Connect an External Data Source, 添加新的数据源,填写如下 2, 点击Validate and Sync 按钮 3,添加Orders和Orders Details ...