精华内容
下载资源
问答
  • 摘要:逐次逼近寄存器(SAR)模数转换器(ADC)占据着大部分的中等至高分辨率ADC市场。SAR ADC的采样速率最高可达5Msps,分辨率为8位至18位。SAR架构允许高性能、低功耗ADC采用小尺寸封装,适合对尺寸要求严格的系统。...
    © Jul 02, 2009, Maxim Integrated Products, Inc.

    摘要:逐次逼近寄存器型(SAR)模数转换器(ADC)占据着大部分的中等至高分辨率ADC市场。SAR ADC的采样速率最高可达5Msps,分辨率为8位至18位。SAR架构允许高性能、低功耗ADC采用小尺寸封装,适合对尺寸要求严格的系统。

    本文说明了SAR ADC的工作原理,采用二进制搜索算法,对输入信号进行转换。本文还给出了SAR ADC的核心架构,即电容式DAC和高速比较器。最后,对SAR架构与流水线、闪速型以及Σ-Δ ADC进行了对比。


    引言

    逐次逼近寄存器型(SAR)模拟数字转换器(ADC)是采样速率低于5Msps (每秒百万次采样)的中等至高分辨率应用的常见结构。SAR ADC的分辨率一般为8位至16位,具有低功耗、小尺寸等特点。这些特点使该类型ADC具有很宽的应用范围,例如便携/电池供电仪表、笔输入量化器、工业控制和数据/信号采集等。

    顾名思义,SAR ADC实质上是实现一种二进制搜索算法。所以,当内部电路运行在数兆赫兹(MHz)时,由于逐次逼近算法的缘故,ADC采样速率仅是该数值的几分之一。

    SAR ADC的架构

    尽管实现SAR ADC的方式千差万别,但其基本结构非常简单(见图1)。模拟输入电压(VIN)由采样/保持电路保持。为实现二进制搜索算法,N位寄存器首先设置在中间刻度(即:100... .00,MSB设置为1)。这样,DAC输出(VDAC)被设为VREF/2,VREF是提供给ADC的基准电压。然后,比较判断VIN是小于还是大于VDAC。如果VIN大于VDAC,则比较器输出逻辑高电平或1,N位寄存器的MSB保持为1。相反,如果VIN小于VDAC,则比较器输出逻辑低电平,N位寄存器的MSB清0。随后,SAR控制逻辑移至下一位,并将该位设置为高电平,进行下一次比较。这个过程一直持续到LSB。上述操作结束后,也就完成了转换,N位转换结果储存在寄存器内。

    图2给出了一个4位转换示例,y轴(和图中的粗线)表示DAC的输出电压。本例中,第一次比较表明VIN < VDAC。所以,位3置为0。然后DAC被置为01002,并执行第二次比较。由于VIN > VDAC,位2保持为1。DAC置为01102,执行第三次比较。根据比较结果,位1置0,DAC又设置为01012,执行最后一次比较。最后,由于VIN > VDAC,位0确定为1。


    注意,对于4位ADC需要四个比较周期。通常,N位SAR ADC需要N个比较周期,在前一位转换完成之前不得进入下一次转换。由此可以看出,该类ADC能够有效降低功耗和空间,当然,也正是由于这个原因,分辨率在14位至16位,速率高于几Msps (每秒百万次采样)的逐次逼近ADC极其少见。一些基于SAR结构的微型ADC已经推向市场。MAX1115/MAX1116MAX1117/MAX1118 8位ADC以及分辨率更高的可互换产品MAX1086MAX1286 (分别为10位和12位),采用微小的SOT23封装,尺寸只有3mm x 3mm。12位MAX11102采用3mm x 3mm TDFN封装或3mm x 5mm µMAX®封装。

    SAR ADC的另一个显著的特点是:功耗随采样速率而改变。这一点与闪速ADC或流水线ADC不同,后者在不同的采样速率下具有固定的功耗。这种可变功耗特性对于低功耗应用或者不需要连续采集数据的应用非常有利(例如,用于PDA 数字转换器)。

    SAR的深入分析

    SAR ADC的两个重要部件是比较器和DAC,稍后我们可以看到,图1中采样/保持电路可以嵌入到DAC内,不作为一个独立的电路。

    SAR ADC的速度受限于:

    • DAC的建立时间,在这段时间内必须稳定在整个转换器的分辨率以内(如:½ LSB)
    • 比较器,必须在规定的时间内能够分辨VIN与VDAC的微小差异
    • 逻辑开销

    DAC

    DAC的最大建立时间通常取决于其MSB的建立时间,原因很简单,MSB的变化代表了DAC输出的最大偏移。另外,ADC的线性也受DAC线性指标的限制。因此,由于元件固有匹配度的限制,分辨率高于12位的SAR ADC常常需要调理或校准,以改善其线性指标。虽然这在某种程度上取决于处理工艺和设计,但在实际的DAC设计中,元件的匹配度将线性指标限制在12位左右。

    许多SAR ADC采用具有固有采样/保持功能的电容式DAC。电容式DAC根据电荷再分配的原理产生模拟输出电压,由于这种类型的DAC在SAR ADC中很常用,所以,我们最好讨论一下它们的工作原理。

    电容式DAC包括一个由N个按照二进制加权排列的电容和一个“空LSB”电容组成的阵列。图3是一个16位电容式DAC与比较器相连接的范例。采样阶段,阵列的公共端(所有电容连接的公共点,见图3)接地,所有自由端连接到输入信号(模拟输入或VIN)。采样后,公共端与地断开,自由端与VIN断开,在电容阵列上有效地获得了与输入电压成比例的电荷量。然后,将所有电容的自由端接地,驱动公共端至一个负压-VIN

    作为二进制搜索算法的第一步,MSB电容的底端与地断开并连接到VREF,驱动公共端电压向正端移动½VREF

    因此,VCOMMON = -VIN + ½ × VREF

    如果VCOMMON < 0 (即VIN > ½ × VREF),比较器输出为逻辑1。如果VIN < ½ × VREF,比较器输出为逻辑0。

    如果比较器输出为逻辑1,MSB电容的底端保持连接至VREF。否则,MSB电容的底端连接至地。

    接下来,下一个较小电容的底端连接至VREF,将新的VCOMMON电压与地电位进行比较。

    继续上述过程,直至所有位的值均确定下来。

    简言之,VCOMMON = -VIN + BN-1 × VREF/2 + BN-2 × VREF/4 + BN-1 × VREF/8 + ... + B0 × VREF/2N-1 (B_为比较器输出/ADC输出位)。

    DAC校准

    对于一个理想的DAC来讲,每个与数据位相对应的电容应该精确到下一个较小电容的两倍。在高分辨率ADC (如16位)中,这会导致过宽的数值范围,以致无法用经济、可行的尺寸实现。16位的SAR ADC (如MAX195)实际由两列电容组成,利用电容耦合减小LSB阵列的等效容值。MSB阵列中的电容经过微调以降低误差。LSB电容的微小变化都将对16位转换结果产生明显的误差。不幸的是,仅仅依靠微调并不能达到16位的精度,或者补偿由于温度、电源电压或其它参数的变化所造成的性能指标的改变。考虑到上述原因,MAX195内部为每个MSB电容配置了一个校准DAC,这些DAC通过电容耦合到主DAC输出,根据它们的数字输入调节主DAC的输出。

    校准时,首先要确定用于补偿每个MSB电容误差的修正代码,并存储该代码。此后,当主DAC对应的数据位为高电平时就把存储的代码提供给适当的校准DAC,补偿相关电容的误差。一般由用户发起校准过程,也可以在上电时进行自动校准。为降低噪声效应,每个校准过程都执行许多次(MAX195大约持续14,000个时钟周期),结果取平均值。当供电电压稳定后最好进行一次校准。高分辨率ADC应该在电源电压、温度、基准电压或时钟等任何一个参数发生显著变化后进行再校准,因为这些参数对直流偏移有影响。如果只考虑线性指标,可以容许这些参数有较大改变。因为校准数据是以数字方式存储的,无需频繁转换即可保持足够的精度。

    比较器

    比较器需要具有足够的速度和精度,尽管比较器的失调电压不影响整体的线性度,它将给系统传输特性曲线带来一个偏差,为减小比较器的失调电压引入了失调消除技术。然而,还必须考虑噪声,比较器的等效输入噪声通常要设计在1 LSB以内。比较器必须能够分辨出整个系统精度以内的电压,也就是说比较器需要保证与系统相当的精度。

    SAR ADC与其它ADC结构的比较

    与流水线ADC相比

    流水线ADC采用一种并行结构,并行结构中的每一级同时进行一位或几位的逐次采样。这种固有的并行结构提高了数据的吞吐率,但要以功耗和延迟为代价。所谓延迟,在此情况下定义为ADC采样到模拟输入的时间与输出端得到量化数据的时间差。例如,一个5级流水线ADC至少存在5个时钟周期的延迟,而SAR只有1个时钟周期的延迟。需要注意的是,延迟的定义只是相对于ADC的吞吐率而言,并非指SAR的内部时钟,该时钟是吞吐率的许多倍。流水线ADC需要频繁地进行数字误差校准,以降低对流水线上每一级闪速ADC (即比较器)的精度要求。而SAR ADC的比较器精度只需与整体系统的精度相当即可。流水线ADC一般比同等级别的SAR需要更多的硅片面积。与SAR一样,精度高于12位的流水线ADC通常需要一些某种形式的微调或校准。

    与闪速ADC相比

    闪速ADC由大量的比较器构成,每个比较器包括一个宽带、低增益预放大器和锁存器。预放大器必须仅用于提供增益,不需要高线性度和高精度,这意味着只有比较器的门限值才需具有较高的精度。所以,闪速ADC是目前转换速率最快的一种架构。

    通常需要折衷考虑闪速ADC的速度以及SAR DAC的低功耗和小尺寸特性。尽管极高速的8位闪速ADC (以及它们的折叠/内插变种)具有高达1.5Gsps的采样速率(例如MAX104MAX106MAX108),但很难找到10位的闪速ADC,而12位(及更高位)闪速ADC还没有商用化的产品。这是由于分辨率每提高1位,闪速ADC中比较器的个数将成倍增长,同时还要保证比较器的精度是系统精度的两倍。而在SAR ADC中,提高分辨率需要更精确的元件,但复杂度并非按指数率增长。当然,SAR ADC的速度是无法与闪速ADC相比较的。

    与Σ-Δ转换器相比

    传统的过采样/Σ-Δ转换器被普遍用于带宽限制在大约22kHz的数字音频应用。近来,一些宽带Σ-Δ转换器能够达到1MHz至2MHz的带宽,分辨率在12位至16位。这通常由高阶Σ-Δ调制器(例如,4阶或更高)配合一个多位ADC和多位反馈DAC构成。Σ-Δ转换器具有一个优于SAR ADC的先天优势:即不需要特别的微调或校准,即使分辨率达到16位至18位。由于该类型ADC的采样速率要比有效带宽高得多,因此也不需要在模拟输入端增加快速滚降的抗混叠滤波器。由后端数字滤波器进行处理。Σ-Δ转换器的过采样特性还可用来“平滑”模拟输入中的任何系统噪声。

    Σ-Δ转换器要以速率换取分辨率。由于产生一个最终采样需要采样很多次(至少是16倍,一般会更多),这就要求Σ-Δ调制器的内部模拟电路的工作速率要比最终的数据速率快很多。数字抽取滤波器的设计也是一个挑战,并要消耗相当大的硅片面积。在不远的将来,速度最高的高分辨率Σ-Δ转换器的带宽将不大可能高出几兆赫兹很多。

    总结

    综上所述,SAR ADC的主要优点是低功耗、高分辨率、高精度、以及小尺寸。由于这些优势,SAR ADC常常与其它更大的功能集成在一起。SAR结构的主要局限是采样速率较低,并且其中的各个单元(如DAC和比较器),需要达到与整体系统相当的精度。



    展开全文
  • 游戏中成功的定义非常 – 使玩家具有独特功能,每种功能都可用来满足一类基本的个性化需求,并有助于满足其他玩家的需求; 功能提供模式: 预先定义 – “角色分类”(游戏加入很特性使每种职业有各自的特色,并在...

    原作名:《Massively Multiplayer Game Development》 – Thor Alexander

    #一、MMP设计技术
    ##【卡通城OL:面向大众的MMO】

    1. 游戏设计问题:
    • ①孩子家长也必须是销售对象 – a.儿童成人都喜欢; b.借助信赖的品牌; c.角色共通; d.可与家人分享;
    • ②允许有冲突但是须禁止暴力 – a.取消玩家对战; b.工作和娱乐的冲突; c.卡通式战斗; d.机器敌人; e.笑槽;
    • ③太多选择会让玩家无所适从 – a.指南(单人,互动,多人); b.任务(明确目标,提供故事线,多种升级系统提高趣味);
    • ④玩家希望立即享受游戏乐趣 – a.下载时插播游戏要素/背景故事; b.创建角色(定制); c.瞬间移动;
    • ⑤众人面前普通玩家惧怕失败 – a.确保成功; b.观看战斗; c.强调团队合作; d.新手老手一起游戏;
    • ⑥复杂的系统令玩家不知所措 – a.避免用数字描述状态(优先图); b.适当的冗余信息; c.背囊有限; d.合并并统一界面; e.避免不合理的按键; f.使用熟悉的界面; g.隐藏不必要细节; h.让探索充满乐趣; i.不要过于简单;
    • ⑦玩家通常只会玩一小段时间- - 明确的目标可以节约时间;
    1. 社会性问题:
    • ①受众对"捣乱"容忍度很低 – 捣乱(指以降低游戏完整性,在游戏中反复骚扰其他玩家的行为);
    • ②玩家无法容忍那些在现实生活中也会发生的社会问题–比如贵重物品被非法转移等;
    • ③家长不放心孩子上网 – a.提供安全的交流形式(基于菜单的聊天系统); b.现实朋友的私密聊天(48小时秘钥聊天频道);
    • ④与社会特性不一致 – a.永远不要阻止朋友; b.社交(群组友好,聊天泡泡); c.合作性迷你游戏;
    • ⑤刚开始游戏会害羞 – a.轻量级群组(任务临时队伍); b.打破沉默; c.共同的目标; d.朋友列表;
    1. 轻量级RPG经验:
    • ①让玩家可立即享受游戏带来的快乐 – 比如游戏最初,游戏指南带入一场战斗;
    • ②避免强迫玩家对不了解的事物进行选择 – 比如最初角色选择与游戏职业/能力分离;
    • ③地理布局应该完全由游戏模式决定 – 比如矿洞挖矿, 码头池塘钓鱼, 空地地标玩家聚集;
    • ④每次进行游戏的时间应该较短 – 比如3小时大任务分解成子任务,增量进行;
    • ⑤允许并鼓励玩家成为观众 – 比如观战;
    • ⑥相比独立做任务,团队合作更有意义 – 尽可能鼓励合作,加强玩家联系;

    ##【每个人都需要某个人:怎样让玩家合作】

    1. 让玩家合作的缘由:
      • 降低系统开销 – 假设可承受3000并发玩家,不合作需处理3000个AI,反之,3人/队则只需提供1000个并发AI;
      • 使游戏更富于变化 – 使玩家能自行创造快乐会延长游戏生命周期,并带动朋友家人加入分享快乐;
      • 使玩家建立起牢固的社会关系从而不愿停止游戏;
    2. 玩家非必须下不会自主合作:
      • 合作游戏多数情况回报更高,但别错误地去掉所有单独游戏的机会,OL中有很多内向的人,占比超过现实比例;
      • 创建一系列不同的任务,它们中一部分适合独立游戏,另一部分适合玩家合作,这些任务最终提供了一个合作性的社会环境;
      • 示例: Ultima Online(矿工,炼金等职业任务是搜集并提供冒险物品,可独立完成;同时,通过市场/NPC代卖与其他玩家进行合作);
    3. 角色扮演是主流:在玩家可以提供独特功能时合作最为有效;
      • 人们参与MMP游戏是因为这些游戏可使他们获得成功,而所花时间比真实世界短很多[从心理学角度来说,这极具吸引力];
      • 游戏中成功的定义非常多 – 使玩家具有独特功能,每种功能都可用来满足一类基本的个性化需求,并有助于满足其他玩家的需求;
    4. 功能提供模式:
      • 预先定义 – “角色分类”(游戏加入很多特性使每种职业有各自的特色,并在合作中提供关键技能);
      • 功能定义 – “基于技能”(游戏角色可学习掌握任何技能,但只擅长练习最多的技能,应提供技能指导和搭配建议);
    5. 为游戏角色提供挑战:
      • 设计游戏内容不断地支持并加强各类型功能,只有组合各类型功能,才能发现和征服游戏中的每一个挑战;
      • 挑战,应让不同角色可按照不同策略来组合征服,避免唯一方法导致缺少必备技能玩家而无法征服;
      • 不要把角色设计得只能少数情况下使用,而应在不同情况下都有用,避免功能太窄而缺少意义;
    6. 保持功能的完整性:
      • 拓宽功能时,小心保持每个角色在游戏世界黑暗能地位的完整性,避免削弱某类职业的价值;
      • 不应该让某一类角色担当完成游戏中唯一类型的关键先生;比如死亡复活,牧师复活比跑尸复活装备掉更少耐久.
    7. 帮助玩家彼此找到对方:
      • 视觉区分 – 用差异化的原型/装备区分职业,便于玩家更易寻找;比如在餐馆,我们会方便的找到穿着制服的服务员;
      • 节点区分 – 创建可满足某种玩家共同需要的场所,比如拍卖行/教堂/广场,用于交易治疗或聊天;
      • 功能界面–例如冒险小组可通过组队UI,搜索特定职业设置为空闲的玩家;
    8. 帮助玩家进行交流:
      • 在实现玩家可便捷找到对方后,提供一些工具使玩家可管理彼此间的交互;
      • 简化交流方式 – 比如战略游戏提供某种界面便于发出简短命令来协调行动;
      • 交流不止于交谈 – 比如贸易系统; 比如玩家门口放置商人NPC售卖物品; 比如无主之地中的频死状态闪烁提醒;

    ##【游戏平衡】

    1. 为游戏中的数值建立基线:从项目中的核心系统开始,比如战斗系统;

    2. 为数值编写模拟程序:简单地获取用户输入的数据,使用一些原型系统算法对他们进行处理并显示结果; [程序示例见P25];

    3. 建立游戏中的度量:对所关心的游戏数值进行长期(按天、月、年或特定时间段进行维护)跟踪和记录; 项目中期;
      需跟踪的因素:

      • 玩家升级 – 统计玩家升级情况,确定游戏进展是否符合预期速度;
      • 物品使用 – 统计物品使用频率如武器挥砍次数/装甲穿着时间,一般使用频率高的物品更强反之较弱;
      • 动作行为 --了解玩家在游戏中的时间究竟花在了哪里,是打怪还是制造?比如治疗是战斗时间的两倍,则需提高治疗效果;
    4. 内部和外部测试:仅当测试玩家数量接近正式运营期望值时,才可能对游戏进行正确的平衡调整; 封测是无痛测试平衡性的最后机会;
      观察玩家动作:

      • 玩家对物品/技能/动作的偏爱,升级速度等;
      • 注意力放在异常数据,比如过快的升级速度/大量财富; 游戏数据/行为异常通常可指出平衡中的错误;
      • 用心观察那些玩家花费大量时间的地方; 增加难度或缩减难度;
    5. 发布后对游戏进行平衡:

      • 如果某个修改对某些玩家不利,很可能导致他们离开游戏;
      • “相对于修复,保留这个失衡的地方是否会导致更多的问题” – “是”,那就应该修改;
      • 在修改前将问题告知玩家,并解释该失衡会对游戏的造成的危害;并尽可能补偿受影响玩家;
      • 提供论坛,便于玩家提供建议或看法;
    6. 对新功能进行平衡:

      • 即使加入非常小的功能,在和其他功能相互作用后都可能带来严重后果;须广泛测试避免失衡;
      • 建立测试服,鼓励玩家访问并使用它,这样,设计人员可安全地调节数值,降低发布后不良影响的可能;
        ##【用支付矩阵来设计游戏平衡和AI】
    7. 支付矩阵:博弈论概念,表示博弈中两个或多个参与者在做出不同决定时的收益和损失;

    8. 囚徒困境:
      图1.8.囚徒困境支付矩阵

      简单的格斗游戏及其纳什均衡:
      图1.10.怎样达到纳什均衡状态的有向支付矩阵

    9. 延迟和停止:定义每个状态所需最少和最多时间,然后为每个状态添加一个转换表,显示该状态完成后可进入哪些状态;

    10. 自动化:

      • 自动化的矩阵测试; 记录模拟/游戏中的战斗日志;
      • 选定敌对者后识别他们可选的动作列表建立矩阵,对当前合法的目标状态进行估价后选则新状态,从而构造复杂的AI系统;
      • 估价函数是该AI系统复杂的部分,它必须考虑AI的当前状态及每个合法行为的支付;
      • 要在选项中做出选择,加权随机数可用来创建一个简单的[模糊逻辑系统];

    ##【使用用例来描述游戏行为】

    1. 用于设计人员和程序员之间进行关于游戏需求的交流,把游戏设计中的创造性思想系统地映射到一个技术性的设计模型上;
    2. 每个用例代表了一个离散的行为单元,它具有定义清晰的作用域、清楚的步骤以及明确的前提条件和后置条件;
    3. 识别用例:
      • 角色:展示行为的实体; 角色通常位于系统的外部; 比如玩家角色(PC)、NPC、怪物;
      • 事件:需识别出产生事件的对象,并为每个事件提供一句话的描述;
      • 规则:一个事件是一件单独的事情;一个事件可以用一句话简单地描述;
    4. 用例中的元素 – 一个标准模板:
      图1.14.用例标准模板
    5. 用例模型图:
      • 为系统的作用域提供可视化表示;
      • 识别用例之间的关系;
      • 有助于重构和组织相关的用例来进行子系统设计;
    6. 开始实现:不建议从用例直接编码,而是在用例基础上建立更充实的设计模型(特别是序列图);
      • ①序列图 – 使开发人员有机会能更详细地考察类接口和对象互动,随着对对象和方法进行详细说明,可逐渐建模一个类结构;
      • ②候选抽象概念 – 确定宿主结构; 第一步,识别用例步骤中的名词,将候选的抽象概念具体化为类、数据结构或重要属性;
      • ③对抽象概念的分析 – 例如武器抽象:
        • 武器等装备与其他类物品有何不同?
        • 是否不能装备某些其他物品?
        • 是否每个装备了的物品都可以作为武器使用?
        • 为了让模型更简明,排除那些不会成为类、数据结构或属性的候选抽象;
      • ④开发一个类结构 – 通过研究候选对象,帮助建立游戏的结构视图;
        随着编写更多的用例,游戏的行为和结构建模将更为充实;
    7. 用例的指导方针:
      • 描写待解决的问题,而不是解决方案;
      • 迭代–用例是探索工具; 从一个与其他未实现的子系统依赖很少的子系统开始,尽可能实现,接着编写相关子系统用例;
      • 面对面的合作–设计文档很重要,但程序与设计的沟通与合作更重要;
      • 不要指望用例可以捕获所有需求 – 例如安全、性能、延迟、运行期支持(行动报告/客户支持调停)等无法构建用例;
      • 交流 – 在向团队领导、制作人、计划经理及其他团队成员描述正在开发的游戏时,作用巨大;
      • 避免线性思考 – 刻板坚持第一印象会遗漏重要的游戏需求或识别出无效需求;
      • 不要过于强调工具 – 超大型项目可能从某些工具受益,但多数情况会成为负担,最后造成放弃用例;
      • 把重点放在清晰的交流上而不是格式上;
      • 避免把注意力集中在客户端服务器细节上–比如哪个操作服务端执行,哪个在客户端执行等;
      • 把游戏和UI分离开 – 用例不应关心UI交互,比如按哪个键/菜单会导致某某行为; UI是UI,游戏是游戏,要分离,各自进行用例描述;
      • 不要强求完全覆盖 – 比如网络、数据库、进程管理等技术早已被实现者良好理解的,不需强求建立用例;
        ##【使用生命值来设计转换因子】
    8. 根据物品造成/治疗伤害的能力为其赋予适当的价值,并以此为基础,确定游戏经济系统中大多数物品的价值;
    9. 武器的价值:
    • 根据武器损坏/可使用前所能造成的所有伤害来决定其价格,而不只是把价格建立在其造成伤害的速度上;
    • 假设一枚货币等价于一点生命值,该基础值可在以后调整;
    • 价值 = 攻击次数 * (损坏后的平均攻击力 + (全新时的平均攻击力 - 损坏后的平均攻击力) / 2)
    • 修复价格 = 基本成本 * 损坏百分比的平方 (鼓励玩家尽早修复武器,越晚越贵)
    1. 治疗、防具和减轻伤害:直接把生命值大小映射到货币就可确定它们的价格;
    2. 从NPC获得的战利品:
    • 战利品价值 = (玩家武器的损坏 + 玩家防具的损坏) ; [统计角度]战利品价值 = 2 * 玩家武器的损坏;
    • 总损坏量决定战利品参考价值,即每个怪物平均会带多少战利品; 或者,知道怪物战利品和减伤效果,即可确定其生命值是多少;
    • 玩家武器的损坏 = NPC受到的伤害 + NPC防具的损坏; 战利品价值 = 2 * (NPC受到的伤害 + NPC防具的损坏);
    1. 制造业:
    • 游戏中若有贸易功能并且允许玩家制造武器,就应鼓励玩家使用制造技能;
    • 部件价格之和不能超过武器价值,否则玩家不会去制造它们;部件价格也不可远低于武器价值,否则成了印钞机,破坏经济系统;
    • 必须确定玩家通过制造物品并且用低于NPC商人的价格出售可获得的最大利润,得到这个值后,就可确定部件的组合价值;
    • 如果发现部件成本太低,可通过增加制造过程中的人力成本来进行调整;
    1. 无关物品:
    • 有些物品与治疗/伤害毫无关系,可借助已知物品来得到与其他数据一致的价格;
    • 一旦对某个新物品类型中的某些物品初步定价后,就可通过配方把这些物品关联起来以保持它们彼此间的一致性;
    1. 校验:
    • 交易日志 – 将玩家间的交易价格和预设价格比较,如果实际交易价格有差异或交易量突然上下起伏,可得知价格制定有错误;
    1. 总结:切记一味遵循这些转换因子和定价方式; 这些只是用来帮助平衡的方法; 掌握基本思想,即可对物品价格进行各种变化;
      ##【在MMP游戏设计中加入故事情节】
    2. 为了一个更有意义的MMP:
    • 单机游戏 – 设计需要更多的考虑故事情节、拟人化的角色交互以及为玩家的行动创建一个更广阔更有意义的环境;
    • MMP游戏 – 为一个典型RPG游戏创建40~80小时详细线性游戏经历,需要数百个月,传统单机方式不适合,需要更具伸缩性的方法;
    1. 游戏与故事情节–尴尬的结合:游戏的开放性与玩家对故事结构的期望是冲突的;
    • 游戏剧情里,玩家处于一个预定的故事结构之中,又想真正地拥有对结果的决定权,他们往往处于两者的折中中;
    1. 故事情节在游戏中的功能:
    • 故事情节为游戏提供了结构;
      1. 创造一个熟悉而有结构的切入点(日常生活中人们接触到的多数娱乐都是基于讲故事的);
      2. 创建了一个隐含的线性发展过程,利用玩家对故事情节发展的基本期望,推动故事,满足需求;
      3. 故事情节的存在保证了结局的存在 – 人们都希望在看故事的时候能够看到结局;
    • 故事情节使玩家的行动更有意义;
      • 通过把行动和“戏剧性场景”结合 – 比如为营救恋人杀6个怪物与为战斗乐趣杀死6个怪物是截然不同的;
      • 使玩家更易在游戏中投入感情 – 使玩家去关心场景,去鄙视坏人,去和英雄一起庆祝等等,把游戏中有意义的方面联系在一起;
    1. 挑战更多的认知能力:
    • 游戏能带来满足是因为玩家可以用交互方式来主动地使用各种认知能力;
    • “纯粹玩家” – 涉及认知能力有 反应、观察能力以及空间和逻辑智能;
    • “剧情玩家” – 涉及不同认知能力如 社交、叙事和情感智能;
    • 玩家更倾向于测试反应和空间能力的游戏,如《LOL》; 但《模拟人生》的成功展示了游戏也可使用和挑战玩家的社交和情感能力;
    • 当游戏中行动具有特定意义时,玩家会主动使用范围更广的认知能力;
    1. 使用有意义行动的最佳场所:
    • MMP游戏没有保存和重新开始,始终存在并持续发展,可以提供独一无二的“真实”感觉;
    • MMP游戏特别之处就是让玩家在(以身体、情感、社交等方式)做出行动时能被别人看见;
    • 如果游戏中唯一有意义的行动是机械的角色升级,那么玩家无法成为英雄或坏人,也就无法归属于这个世界;
    1. 为MMP游戏模式加入故事情节:
    • 在那些具有进展的游戏元素中加入故事情节 – 比如角色升级;
    • 通过创建背景来建立一些游戏世界中的戏剧性场景 – 为游戏行动建立上下文从而使行动自身成为一个逐渐展开的故事;
    • 创造机会让玩家卷入那些具有故事形式的公众事件或场景中去 – 使他们具有某个共同的目标就是一个很好的例子;
    • 1.把角色升级作为故事 – 可用奖励系统来确保并加强游戏行动的故事性,回报不一定是物质的,可以是荣誉、声望、社会地位等可衡量的;
    • 2.发掘戏剧性场景 – 背景故事和游戏真正目的必须协调;
      戏剧性场景可用于单个角色、某个区域、整个游戏世界;
      为了更令人信服,戏剧性场景需"写"入游戏世界中,简单或毫无新意并不重要,重要的是行动; 比如区域对抗,立雕像;
    1. 公共目标:创造具有独特生命力的、动态而一致的游戏元素;
    • 目标 – 能根据玩家行动而变化,且未来不确定; 例如《率土之滨》历史进程与州对抗; 《魔兽世界》安其拉开门等;
    • 危机 – 需玩家长时间持续行动才能解决的动态危机; 譬如某区域产生很多敌人对一条重要贸易路径带来危险;
      ##【客户服务和玩家声望:一切都和信任有关】
    1. 捣乱:指在游戏规则允许的范围内,故意通过一些方法来激怒、威胁或骚扰其他玩家;经常在没明显真实动机的情况下;
    2. 规则只是工具:如果不适应游戏设计的意图,再好的规则都可能会被破坏或滥用;
    3. 意图:不能单凭玩家自身的行动去猜测他的意图;
    4. 客服部门:为了解决那些开发团队无法通过游戏机制来发现或解决的问题、观察有问题的玩家、以及贯彻执行服务条款协定;
    5. 声望:1. a.一般人所能看到和判断的总体质量或特性;
      b.别人对某些特性或者能力的承认;
      2. 受到公众尊重或尊敬的地方:好名声; 比如淘宝的差评或好评;
    6. 正当行为:声望系统用来阻止尽可能多的不良行为,还可用来鼓励/奖赏游戏社区中的良好行为;
      仔细平衡良好声望的奖励是很重要的;
      1.当人们对于和别人的交互感到高兴时,应该让他们能够表达出来;
      2.以一种平衡的方式来奖励良好行为;
    7. 恶意行为:声望系统还应该能阻止恶意行为;
      1.必须给那些公认的"坏"人一些惩罚以促使他们改变或把他们从游戏社会中赶走;
      2.让玩家指控其他玩家的不良行为时,他必须小心的斟酌他的决定;
    8. 不能以封面来判断一本书的好坏,同样,对人也不能以偏概全:
      • 要判断一个玩家的好坏,不能只根据几个人的说法,而要根据他们对社会的实际影响;
      • 要根据玩家的行为模式来判断他们是不是在长期地捣乱;
    9. 行为模式跟踪方法:阿佛加多信任度量; 贝叶斯分析;
    10. 使用声望:
    • 避免把评价过程变得很麻烦,和其他功能一样,玩家需要尽快确定他们在和谁交互以及他们的感觉;
    • 要让客服部门把声望系统作为一个让游戏社区处于正规的主动工具来使用;
      1. 当玩家与另一玩家进行了一次良好交互后,这个玩家想给他的不仅是一句"谢谢你";
      2. 当玩家与另一玩家进行了一次不好的交互后,他需要某种立即的方式来让对方及其他玩家知道他的感觉;
      3. 它允许玩家用很长时间来树立一个他们是怎样对待其他玩家或与其他玩家进行交互的真实形象;
      4. 有良好行为的玩家应该受到奖励;
      5. 玩家应该对他们的社区有真正的控制权;
        #【MMP体系结构】
        ##【制作仿真框架,第一部分:结构建模】
    1. 体系结构纵览:
      • 1.客户/服务器组件 – 网络抽象层 → 分发消息数据包 → 游戏仿真层接受消息并处理,保持客户端和服务端游戏状态空间的一致性;
      • 2.通过代理仿真 – 服务端游戏仿真层保留对游戏状态空间的唯一精确表示,通过请求交互; 永远不要信任客户端对于状态空间的表示;
    2. 支持类:
      • 词典类/哈希表 – 词典类是抽象数据类型,存储按值访问的元素;
      • 仿真事件 – 系统中的事件对象,包含了事件类型、源标识、目标标识、参数以及频道(让接受者过滤只获得感兴趣的事件);
      • 仿真状态:该类是系统对状态模式实现的一种描述; 状态的基类 – CanTransition(能否转换)和Transition(进行转换);
      • 动作状态:该类标识那些典型的游戏仿真操作,譬如击打对手或打开一扇门;
      • 控制状态:定义了与它关联的行动者可以从哪里接受命令; 还可用在基于观察的训练中;
      • 用户控制状态:维护了一个等待执行的动作请求列表,这些动作由客户端发出并为服务端所接受;
      • AI控制状态:负责确定用于执行的合适动作; AI技术(有限状态机、行为树、神经网络或者模糊逻辑);
    3. 核心类:
      • 1.仿真对象类SOB:所有由仿真层处理的核心类的基类,包括Actor、Area、Item、Obstacle(障碍物);
        • 可包含其他SOB – 会维护一个内容词典(contents) 及 一个所有者标识(ownerID);
        • 联系词典links – 维护与其他对象之间的联系,提供SOB之间的聚合或"整体/局部"的关系;
        • 属性词典Property – 以数据驱动的方式存储每个对象特定于游戏系统的属性; 比如对象的位置/移动速度/生命值等;
        • 持久化机制 – 用一个通用的方式和存储系统合作; 维护dirty标识,数据变化时设置,Store定期检查进行保存或Restore;
      • 2.执行者Performer:为Actor和ActorProxy类提供了在客户和服务端共享的功能及接口;
        • 调度优先级 – 实现对调度的仿真;
        • 当前动作状态 – 标识该对象当前所执行的动作,由PerformAction(执行动作)方法确定并进行设置;
      • 3.行动者Actor:能与仿真环境进行交互的服务端SOB;
        • 控制状态 – 使其能被不同的空置房(agent)控制,包括玩家/AI或脚本驱动的过场动画;
        • eventQueue – 批量存储事件,延迟到下一次PerformAction被调度执行时一并处理;
        • ReceiveEvent – 可把需要立即处理的事件过滤出来,而不是放到事件队列eventQueue中;
      • 4.行动代理ActionProxy – 行动者在客户端的副本;
      • 5.非执行者类 – 那些不需要和仿真环境直接交互的核心类. 物品; 障碍物; 代理对象; 区域;
    4. 管理器类和工厂类:以单例模式实现; 初始化时创建直到仿真层终止;
      • 1.仿真对象工厂 – 负责创建SOB并确保他们具有独一无二的标识;
      • 2.仿真对象管理器 – 负责维护一个仿真对象的词典; 提供ID查找对象引用、保存、载入;
      • 3.调度管理器 – 调度活动的执行者仿真对象,以安排时间调用它们的PerformAction回调方法;
      • 4.查询管理器 – 为在运行时访问静态游戏数据提供了一个快速高效的机制;
    5. 仿真类:把支持类、核心类和管理类包装成一个良好的顶层接口方便管理;

    ##【制作仿真框架,第二部分:行为建模】

    1. 把用户和行动者关联起来:
      • 服务端和客户端SOB都提供AttachUser(连接用户)和DetachUser(断开用户)的方法实现; 可实现接管玩家角色、变换玩家视角;
      • User(玩家)可被映射到服务端某个行动者实例以及与之对应的客户端行动代理对象上,SOB根据状态判断是否接受连接;
    2. 动作请求:ClientSOB → SendActionRequest → Server → ActorSOB eventQueue;
    3. 动作调度:物理层:Tick() → 服务端仿真层:ProcessTasks() → 调度管理器:PerformAction() → Actor:ProcessEventQueue;
    4. 事件广播和处理:
      • 当行动者试图进行转换时,获取控制状态对象的目标动作状态,随后检验是否可转换,进行切换或忽略;
      • 活动的SOB维护着一个订阅者词典(对其动作感兴趣的其他对象可进行登记),通过调用订阅者的ReceiveEvent方法进行通知;
    5. 服务端事件处理:当事件订阅者收到事件后,会传递给这个类型的事件所对应的事件处理程序,若该对象有用户连接,则发送给客户端;
    6. 客户端事件处理:事件消息 → 客户端 → 行动者管理器 → (创建/查找)对应的行动代理对象;
    7. 客户端代理:行动代理对象:收到消息 → 查找合适的处理程序 → 处理 → 状态转换 → 通知;
    8. 仿真与表示分离:观察者设计模式; 游戏的代理动作状态类作为被订阅对象,当状态改变时,通过自身的Notify方法,通知观察者Update;
      ##【为游戏脚本创建一个"安全沙盘"】
    9. 脚本语言与MMP开发:
      • 网游不仅需要持续地保持可靠性,还需经常更新内容;
      • 使用脚本语言实现经常变动的内容(如任务,UI等),可在不断改进和修正的同时,保证代码的可维护和稳定性;
    10. 使用沙盘的理由:
      • 上层系统 – 游戏规则/有趣的部分,会频繁的修改(进行游戏平衡、加入特殊事件、加入新物品/技能时);
      • 底层系统 – 网络、数据库接口、物理、移动、骇客检测等,极少需要修改,且每次修改都必须非常小心;
      • 游戏编码和内容维护应当避免的做法:
        • 在正常的框架以外任意地创建和销毁游戏对象;
        • 直接控制游戏中对象的移动、位置和方向;
        • 为了适合特殊的关卡而对AI或寻路算法进行有条件的调整;
        • 绕开游戏数据库并且把游戏数据写到本地文件系统中;
        • 直接打开一个连接到其他服务端/客户端的套接字,而不是使用现有的消息系统;
        • 直接嵌入SQL来查询/更新数据库,而不是通过现有的数据更新和读取接口;
        • 向游戏客户端发送任意的文本消息;
        • 对某个现存子系统的使用与其设计目的不一致;
    11. 安全沙盘的设计:
      • 1.需限制脚本语言提供的服务,控制底层系统中哪些可用;
      • 2.安全沙盘的关键元素:
        • a.定制缺省的受限执行环境以满足游戏的需要;(比如禁用Python某些模块,用某些函数替代原始函数等);
        • b.支持对某些特定对象/属性的访问,同时禁止未经授权的访问;
        • c.包装对象和对象的属性,对其进行保护;
    12. 在安全沙盘中编写游戏代码: 无需处理权限和数据保护,沙盘框架已进行处理;
      ##【大型多人游戏中的单元测试】
    13. 为什么需要单元测试:
      • 可在开发过程中保证代码的完整性,尤其是在整合过程中;
      • 在游戏发展过程中,它可以在成品代码中使引入错误的风险最小化;
      • 单元测试、持续代码整合、小型发布、结对编程、集体拥有代码等组成敏捷开发;
    14. 单元测试的定义:
      • 1.单元的定义 –
        • 须具有良好定义的接口且对其他单元的依赖较少;
        • 定义单元最有效的方式 – 从最底层或最基本的类开始,向系统上方进行直到高层类被定义;
      • 2.数据集的定义 –
        • 内部数据集:可直接硬编码在单元测试内以提供一/多组数据用于测试;
        • 外部数据集:从文件、数据库、套接字等单元测试代码以外的其他资源中读取数据;
        • 确保这些数据在测试时总是存在,并且在多次测试后不会改变; 保证其完整和一致!
      • 3.测试的定义 –
        • 应对所有衍生数据(根据单元状态计算出的数据)或改变单元状态的方法进行测试; 无需测试那些简单获取/改变数据的方法;
    15. 单元测试框架:
      • 使用语言对应的xUnit等测试框架;
      • 尽可能使用简单的测试方法;
    16. 测试先行的设计:
      • 在编写代码前编写测试代码 – 测试真正需要测试/可能会出错的代码;
      • 编码的步骤 –
        • 1.为一个还不存在的函数或方法编写测试代码;
        • 2.必要的话,编译测试代码; 编译将会失败;
        • 3.通过编写测试的函数或方法的存根版本(无实际功能,仅用来通过编译)来修正编译错误;
        • 4.运行测试:因功能未实现,测试将会失败;
        • 5.通过编写被测函数或方法的函数体来修正测试错误;
        • 6.运行测试:如果失败,修正代码; 如果成功,编写另一个测试;
        • 7.重复1-6的步骤;
      • 让自己成为最初的受害者 – 从使用者角度编写,是功能的作者也是设计决策的第一个受害者; 更快的理解设计决策所带来的影响;
      • 单元测试和重构 –
        • 1.辨认出那些需要重构的代码;
        • 2.编写单元测试来证明现存的代码能够工作;
        • 3.开始重构,对代码进行微笑的、慎重的修改;
        • 4.重新编译并且对每个修改运行单元测试; 单元测试将失败;
        • 5.重写测试使得它符合新的代码;
        • 6.重新运行测试,如果代码能工作,继续重构; 如果测试失败,修正新代码;
        • 7.重复1~6步骤;
    17. 使用因素:
      • 测试过程自动化 – 从代码库获取新代码 → 编译链接 → 运行所有单元测试 → 向团队报告结果; 每天测试,失败时优先修复;
      • 独立的测试程序 – 使用独立的可执行程序以批处理方式调用所有测试,每当代码有新测试加入,自动加入,并可生成报告发出邮件/网页;
      • 集成的运行时测试程序 –
        • 在运行环境中执行测试,单元测试可对宿主进程提供的资源进行访问;
        • 不要让对测试框架的调用破坏产品代码;
        • 调用点应只有一行代码,用来调用一个函数(测试识别、初始化、执行和报告等)以启动测试过程,这样就可通过条件编译选项去除该函数;
      • 测试异步执行的代码 – 关键点就是要在被测试代码执行的同时强制单元测试等待异步代码执行完; 系统函数或测试包含主循环;
        ##【使用Twisted框架进行MMP服务整合】
        ##【Beyond 2:构建虚拟世界的开源平台】
        ##【使用并行状态机来创建可信的角色】
    18. 状态模式:利用多态为同一个对象的不同状态定义不同的行为;
    19. 并行状态层:若某个功能与某一层中其他功能只能以互斥的方式执行,就放入该层中;而可独立或并发执行的功能则放入不同层次;
      • 移动层:处理角色在游戏世界中移动相关的问题; 停止 - 前 - 后 - 左 - 右等移动状态;
      • 姿势层:(下半身行为)处理角色在移动时的外观; 站立 - 坐下 - 步行 - 跑步 - 坠落 - 游泳 - 骑马 - 平躺; 用途:
        • a.通知动画子系统什么时候该改变这个角色正在播放的动画;
        • b.接受或拒绝用户界面提出的状态改变请求;
      • 行动层:(上半身行为)处理角色可以进行的各种行动; 空闲 - 制造 - 施法 - 战斗 - 做手势 - 出生;
    20. 状态管理器:维护了每个状态层当前状态的引用 – 当前行动状态、当前移动状态、当前姿势状态;
    21. 跨层阻止:多数情况每层的状态可独立于其他层次执行,但总有例外,比如设计玩家处于空中时无法做出任何行动;
      • 实现 – 在移动抽象层增加blocked布尔变量,通过Block和Unlock方法设置或清除; 状态转换时判断blocked,为真表示不可转换;
        ##【使用观察者/可观察者设计模式】
    22. 观察者和被观察者:
      • 一个被观察者对象具有其观察者的引用集合;
      • 当被观察者对象被修改时,调用所有观察者的Touch()/Update()方法通知它们;
      • 当观察者要获得被观察者对象的状态时,首先检查该对象是否已被修改,然后查询被观察者内部状态并更新自己的内部状态;
    23. 基本结构:
      • 该设计模式可使客户端和服务端使用完全相同的类;
      • 在服务端,对仿真观察并把检测到的改变传到客户端;然后客户端使用同样的仿真对象并使用一个图形观察者来观察它们;
    24. 服务端架构:
      • 由仿真对象、相应的通讯观察者以及通讯管理器组成;
      • 更新消息 – 服务端循环处理所有的仿真观察者,如果某个观察者被标记为脏,就查询其内部状态以获得一个将要发送给客户的更新缓存;
        服务端保留该缓存,并生成一个包含了观察者标识的修改消息,让通讯管理器发送,客户端接受消息后,进行同步处理;
      • 创建消息 – 服务端创建新的仿真对象 → 创建对应的通讯观察者并挂接到通讯管理器 → 发送类标识和对象标识 → 客户端接收后创建 → GUI;
      • 删除消息 – 根据观察者标识 → 销毁对应的所有观察者并从通讯管理器中移除 → 向所有正在监听的客户发送删除消息 → 销毁;
      • 新客户消息 – 当新客户订阅仿真时,必须先接收仿真中所有对象的创建消息;
        传输时基于优先级,最高的最先被更新,其次下个循环再更新,防止占用大量带宽引起性能问题;
    25. 客户端架构:
      • 由仿真对象、相对应的图形观察者和一个通讯控制器组成(处理方针对象的创建、修改、删除);
      • 每个仿真对象实现了可控制接口,用来解包服务端发来的消息并且更新它们的内部状态;
      • 创建消息 – 获取类标识 → 控制器获取相应类型 → 创建具体控制器类 → 加入映射 → 后续消息即可被发送给正确的实例了;
      • 更新消息 – 通讯控制器 → 获取标识映射到的对象实例引用 → 更新缓存发送给对象 → 对象的Unpack(解包)方法处理;
      • 删除消息 – 销毁相应的可控制对象(析构函数应销毁所有的图形观察者),并删除所有对它的引用;
    26. 增强:
      • 差别观察者 – 当某个属性改变时,必须用属性索引/标识调用Update(),而后只打包被标记的已修改内容,达到节省带宽的作用;
      • 每个连接一个观察者 – 低速接收者积累一段时间的数据后调用SendChanges(),高速接收者每帧发送改变;
        为所有观察者建立一个在独立线程中运行的公共队列来避免通知延迟;
      • 优先级和视野 – 只发送客户视野范围内对象的更新;
        游戏系统需实现一个方法来检查某个对象是否在给定客户的视野(九宫格/十字链表等AOI);

    #【服务端开发】
    ##【无缝服务器:优点和缺点】

    1. 杀死怪物不止一个方法:
      • 1.分割物理和游戏计算 – 把检验角色移动、碰撞等物理空间计算与游戏部分分离,并使用独立服务器处理;
      • 2.让AI独立运行 – 把AI分离到独立服务器中,缺陷是AI通常依赖玩家角色等游戏对象属性,需要对数据进行复制,带来明显的同步问题;
      • 3.“暴力”方法 – 使用更强大的服务器; 使用多线程;
      • 把游戏世界分割为更小的部分伸缩性更好,同时便于在不影响当前服务器负载的情况下添加更多的游戏内容(比如新大陆);
    2. 无缝世界模式的原型:
      • 分割游戏世界 – 二维或部分三维游戏可按照网格划分; 对于那些真正的"6度空间"仿真游戏来说,八叉树等方式细分空间更适合;
      • 平均分配 – 相邻区域的共享边界,最小应与客户端的游戏世界或玩家的可感知范围一样大,再小会造成视野差别;
      • 详细的代理对象还是精确的代理对象(两者不可兼得) – 边界区域中的对象可跨越服务器边界进行交互;
        • 当对象进入边界区域时,所在服通知相邻服,使其创建代理对象来表示远程对象;当其从边界移至邻服"内"时,该代理对象将被销毁;
        • 代理对象需提供多少信息 – 必须包含位置、方向及对象类型等基本属性; 对象属性赋予优先级,尽快或延迟更新;
      • 定义边界线 – 当对象越过"刚性"服务器边界时,它的所有权会立即转移给邻服,没任何延迟; “柔性”在转移前只允许"略微跨越"边界;
      • 静态或动态边界 – "静态"不可变; "动态"根据负载(玩家数/AI数)调整边界,必须通过一个原子事务低延迟的把一组对象整体转移;
    3. 无缝世界模式的有点:
      • 拥有更大的游戏空间 – 而分区游戏区域大小受限于单个服务器所能处理的玩家数量; 一个更大的游戏世界与更好更有趣的游戏无必然联系;
      • 可伸缩性和可靠性 – 动态平衡调整以适应不断增加的负载; 静态亦可停机时根据玩家数量和分布调整;
      • 不确定的边界线 – 错误常与服务器间来回移动数据有关,动态不确定的边界可降低玩家对这些错误的滥用行为,但错误必须找到并修正;
      • 不需要载入地图 – 载入操作会被平摊到游戏世界每一个分块,仅在需要时才会被载入;
    4. 无缝世界的缺点:
      • 不确定法则:不仅仅是客户端 – 每个玩家有自己的视野,多人游戏必然引入不确定性,而客户端必须对其进行处理(航位推测法);
        服务端代理对象不可能和其对应的真实对象完全同步,交互需通过异步处理,而缺乏同步是很多错误的根源;
      • 对设计的影响 – 系统复杂会出现大量竞争和失败问题; 这会直接导致开发周期延长/使游戏更不稳定;
      • 示例:给一个物品 – 详细见P182事件序列; 复杂度是单服操作的好几倍;
      • 构筑游戏世界/美工 – 地形建模、贴图绘制、对象放置等,分块操作,整合等需特别小心处理; 尺寸需小于边界大小;
      • 运营和扩展 – 常见问题为"物品复制",根本原因是竞争条件,代理和相应的对象不能保持完全同步会导致大量"失效状态"错误;
      • 对额外的复杂度处理失当 – 比如只支持同服玩家交易,会暴露边界,为更多的游戏侵入行为打开大门(比如反复跨服休整后打BOSS);
        ##【服务端对象的更新频率】
    5. 视觉连贯性与精确度:目标是视觉和感觉上都很好,即使牺牲一小部分视觉精确度; 指导方针是"如果玩家发现不了,那就不要紧!"
    6. 需要发送哪些数据:
      • 环境变化 – 极高的优先级; 不一致会造成大量碰撞和同步问题,环境数据应包含打开/关闭的门及旋转风车等循环移动等;
      • 玩家之间/玩家与NPC的交互 – 战斗信息至少需要和环境变化一样及时; 交易聊天等可略微延迟;
      • PC和NPC的移动 – 服务端必须以足够高的频率发送其移动信息,保证玩家视觉上感觉是连贯的;
    7. 带宽限制:
      • 更新频率 – UDP包头28字节,TCP包头72字节; 频率高会使包头部分消耗额外带宽; 频率低会加重视觉上的跳跃和偏差感,丢包影响大;
      • 分区的地形和连续的地形 – 分区地形会自动为最远范围赋予上限,连续地形中需使用额外代码确定界限;
    8. 每个用户在服务端需要的数据:每个玩家在服务器上都应有一个发送队列和位置列表(包含最近几次发送更新时玩家的位置),且长度相同;
    9. 管理数据的大小:应建立某种机制将游戏环境划分成区域,在这些区域中可方便知道某个特定玩家的视野中有哪些玩家(比如九宫格AOI);
    10. 更新队列:玩家发送队列由一系列时间槽组成循环队列; 循环更新(比如位置)发送后移动到特定槽(控制频率),一次性更新(比如聊天)发送后丢弃;
    11. 缺省的更新频率:近距离物体频繁更新,远距离放缓更新;
    12. 计算范围:
      • 精确的范围计算需要大量CPU时间 – 等式3-1(P190);
      • 可用曼哈顿距离等方法近似处理 – 等式3-2; 可计算出从一点走到另一点时所需经过的正方形街区数;
      • 得到近似距离并知道最大的可视范围,即可通过线性函数计算出更新应置入哪个时间槽中;
    13. 调整优先级:
      • 长时间保持静止的PC和NPC,服务端可以一开始就发送位置,之后偶尔更新以防数据包的丢失;
      • 处于同一群组的玩家 或 被选为目标的PC/NPC,应提高更新频率; 这表示玩家想要了解目标更多信息;
      • 根据玩家过去的位置列表调整 – 比如停留超过几秒则调低频率, 直线奔跑可预测中等频率, 不规则转向或移动使用更高频率;
    14. 调整队列:若玩家优先级调整突然发生很大变化,应在队列中加入一个新的更新(一次性的);
      ##【MMP服务器开发和维护】
    15. 正在使用什么服务器:让系统的每个组件都向与它连接的其他组件报告版本;
    16. 应该和谁建立连接:将可连接的服务器等信息集中放在一个随时可访问的地方比如一个网页上;
    17. 怎样才能知道角色出了什么问题:
      • 服务端应尽可能向客户端提供精确有用的出错反馈,比如错误代码;
      • 让用户可看到服务器日志或日志的某些合理部分,比如使用服务器进程日志网页;
      • 不断监视所有服务器 – 用脚本周期性检查可能问题,比如网络、进程、内存等问题,并转发给开发团队;
    18. 对复杂度进行管理:
      • 分支:
        • a.坚持使用命名规范(包含"Dev"),所有测试服都必须进行相应的命名;
        • b.为开发服务器和运营服务器建立独立的环境,不要交叉;
        • c.应使用一个自动构建系统来进行每日构建和用于检查的构建;两台独立电脑来构建运营和开发版本;
      • 共享代码 – 服务端和客户端怎样构建?是否相同的OS构建?是否相同OS和硬件环境下运行?
        客户端和服务端代码共享还应考虑是否隐藏了服务端内部实现;
        ##【使用Python进行精确的游戏事件广播】
    19. 事件驱动编程:和线程相比,事件是非抢占式的,只在调用时才执行,且持续执行直到任务完成;
      • 同步和异步调用 – 同步(阻塞)调用在执行完前不会把控制权返回给调用者; 异步(非阻塞)调用立即返回,独立于调用者的执行而继续执行;
      • 并发模拟 – 通过把玩家的请求分解成原子操作,在每个游戏循环里依次处理,因完成时间短,会制造出并发处理的假象;
      • 高级游戏事件 – 比如游戏世界中的物品、其他玩家或是关键的游戏系统进行交互; 底层(网络/物理/碰撞)则可提供钩子;
      • 游戏服务端主循环 – (WaitForRequests() → ProcessRequests()); 请求包含客户端玩家请求/游戏某次循环的异步调用;
      • 事件和线程并不是互斥的 – 主线程游戏循环,其他线程对网络消息等进行处理,通过一个队列放入取出请求;
    20. 延迟调用:使用请求的方式来对函数或者方法进行调用,从而使它们可以在未来的某个时间被处理;
      • 6个主要元素 – a.调用的目标对象; b.调用本身; c.调用的参数,必须符合形参列表; d.调用的执行时间,这必须是将来的某个时间; e.当延迟调用完成后所调用的回调函数; f.回调函数的参数;
      • 延迟调用接口 – Call(target, call, args, delay, cb, cbArgs);
      • 延迟调用的实现 – a.延迟调用必须能被缓存在某种类型的容器里,并可在以后取出; b.延迟调用不能在期望的执行时间之前执行,若安排在同一时间,则依据FIFO(先进先出)方式被处理;
      • 调用 – 与普通函数调用区别在于怎样指定所调用的对象和方法;决定是否提供回调函数接收通知;
      • 延迟调用缺点 – 调用方必须知道被调用的目标对象的标识; 游戏中的事件往往导致某些动作的执行,状态的改变,调试和维护会变得困难;
    21. 事件广播:对事件驱动系统的第一个改进 – 在它之上加入一个层次来为游戏事件实现广播机制;
      • 游戏事件 – 一个整形常量标识,用于表示游戏中发生的某件事情;
      • 事件处理器 – 任何可调用的对象/函数/方法,它们在特定事件发生后被调用;
      • 事件广播器 – 把事件派发给对其感兴趣的对象,这些对象会为某个特定事件向事件广播器注册(register)一个事件处理器;
      • 事件参数 – 事件必须是函数式的,可接受参数以包含更多意义;
      • 使用事件广播 – 首先为关心的事件声明一/多个处理器;接着把处理器注册到事件广播,并传入实现这些处理器的函数或方法的引用;
      • 优势与劣势 – 成功解耦了事件发送者和接收者; 事件分发只有一段代码方便添加日志和调试; 但会造成很多低效的无效调用;
    22. 精确的事件广播 – 对事件驱动系统的最后改进 – 精确注册,不仅可指定关心的事件还可指定在什么情况调用处理器;
      • 游戏事件索引 – 索引可根据事件、事件源和目标组合;
      • 事件索引派生类 – 若要使用更多标准来注册事件处理器,可从游戏事件索引基类派生;
      • 使用游戏事件索引来注册事件处理器或发送游戏事件;
        ##【实现移动和物理模块的注意事项】
    23. 可以发布游戏吗:保证足够的真实性从而让玩家持续游戏,并加以足够限制使游戏可及时而经济的被实现; 团队达成共识;
    24. 这是一场战争:对设计和实现评估时,必须考虑恶意玩家会怎样利用这些信息以欺骗服务器;
    25. 服务端永远是对的:比如防止客户端侵入房屋获取屋内物品 –
      • 先确认是否合法进入,而后才把房屋内容发送给这个客户端;
      • 要求把房屋中的物品保存在一个固定的保险箱里,并有一把单独的钥匙;
      • 把进入房屋实现成将玩家传输到另一区域,这样就不能仅仅通过移动进入房屋;
    26. 移动的代价:
      • 假设每秒15个移动数据包(20byte) – 每玩家300byte/s,若同屏20人则需6Kbyte/s,这还仅仅是雕像般的移动; 占用太高!
      • "暂时频道"法 – 客户端仍然产生数据包,但只发送一个更新周期中最新的移动数据; 这对于瞬时信息非常有用;
      • 采样点:
        • a.服务端收到移动数据包时会根据该前一个包做判断(前后位置以及采样点合法),若无效发送修正数据包(实际移动),并广播给附近玩家;
        • b.关键要确保两个位置之间的采样点距离应小于游戏中最小的碰撞/触发体; 避免穿过门等障碍物;
        • c.另一关键要对两次更新的移动距离设置上限; 比如最大速6m/s,每秒更新4次,上限设为2m,超速修正,防止手工数据包;
        • d.增加欺骗检测机制,比如记录玩家最大移动速度历史,统计非法移动,使GM可针对性细致监视;
    27. 移动速度:
      • 应了解在游戏中允许提高移动速度会加大系统对移动数据包的检验错误;
      • 保守做法是只允许两种速度(快速和慢速); 或缩小地图尺寸比例;
    28. 玩家可从这里到那里吗?
      • 游戏世界中的移动 – 开始设计时假定玩家哪都不能去,随后开辟道路来指定可合法移动到的位置,会相比障碍物阻挡少很多漏洞;
      • 瞬间移动 – 确定有效目的地,无碰撞会使传输更稳健,即时偶尔角色重叠,玩家亦会很快移动走;
      • 平移动画 – 跳跃轻功等,比如瞬移5米,可在服务器确认后开始动画; 也可立即显示动画,失败时在障碍处被迫停止; 都比无法移动感觉好;
    29. 碰撞检测:基本方针是尽量把在任何时刻必须进行碰撞检测的物体数量最小化;
      • 碰撞体 – 先用一个简单的几何形状来构建碰撞体并用于粗略判断是否碰撞,若碰撞再使用更细致的几何体进行精确判断;
      • 角色碰撞体 – 使用躯干圆柱体(不低于膝盖上方,不高于肩膀),这样角色不会被简单物体(如楼梯)阻挡;
        障碍物碰撞体的长宽需大于角色合法移动的最大距离,且高度要高于角色碰撞体的最低点;
      • 角色间碰撞:
        • 优势:a.使游戏更真实; b.避免角色重合的视觉问题; c.实现简单,碰撞通用处理; d.更有趣的机制,比如攻城阻挡战略的实现;
        • 劣势:a.CPU负荷增加; b.瞬间移动实现难度加大; c.碰撞骚扰(比如阻挡出口等); d.多玩家抢点代码复杂; e.AI碰撞;
      • 缩放 – 应考虑角色模型大小的多样性和独特性; 把握针对不同尺寸变化实施独特游戏机制的机会;
    30. 物品放置:允许"放置",使玩家可在游戏世界留下自己的"标志"; 比如获取房屋并装饰,是非常诱人的;
      • 可能被利用的地方:应尽可能避免在公共区域任意的放置物品;
        • 在对战区域放置大量物品,使其他进入玩家暂时失控(因物品数据包会占用大量带宽);
        • 通过富有创造性的方式堆叠物品形成通路,进入通常无法进入的区域; 或是站在物品上,收取物品放置到其他地方;
      • 可行方案:
        • 归属权在一定时间内属于原主人,之后可被其他玩家捡取,若一段时间未被认领则自动销毁;
        • 将放置物抽象为某种通用的容器,类似袋子,并且不能被堆叠也不能放置在其他容器中;
        • 指定一些预定义/可接受的位置用于放置,比如公共基座允许团体放置装饰;
        • 不允许公共区域放置,只允许放置在安全区域,比如玩家的房屋中;
        • 实现一些四处走动并会拾取放置物品的NPC;
        • 物品只能在预定义的放置处有限叠加,比如画挂墙上,花瓶放桌上;
        • 发送数据时,与物品相关的信息设置一个上限,赋予那些会影响游戏的对象更高的优先级,比如有威胁的怪物;
    31. 侵入检测:
      • 加密只是为了赢得更长时间,来持续加固系统以检测和防止恶意玩家的侵入;
      • 对设计进行评估时,优先选择那些很难活不可能被侵入的游戏机制;
      • 记录玩家行为,但不要过量也不能影响性能,构建于商业数据库系统上的异步日志机制是强有力的工具;
      • 用更多的侵入检测代码对有恶意嫌疑的玩家进行更细致的评估; 比如不停试图达到允许的移动距离上限玩家,进行更多检测或通知GM跟踪;

    #【客户端开发】
    ##【客户端移动预测】

    1. 游戏需良好的移动预测:原因 – 玩家需要获得即时反馈; 网络数据包延迟的不确定性;
    2. 命令时间同步:
      • 即时战略游戏如《星际争霸》必须基于寻路请求来把对象移动到特定的位置;
      • 仅发送"移动X到达位置P"不如"移动X使其在未来的某个时间T到达P",若客户端与服务端时间同步,可通过调整X客户端速度达到精确同步;
      • 服务端通过X的速度V以及最低和最高延迟验证命令是否正确后,通知所有客户端X必须在T时刻移动到P;
      • 在调整速度时,注意使用平均速度;为了平缓移动速度的骤然变化(比如10m/s到7m/s),可在到达P前等量减慢X的最终速度;
    3. 合并路点:
      • 路点 – 物体移动路径上的一系列关键点; 例如怪物,无路点不可移动;若有则始终朝第一个路点移动,达到后删除首路点;
      • 方法 – 服务端只需要在移动开始的时候发送一个初始的路点列表,在这个怪物停止移动前就不用再去更新它了;
      • 客户端发现它到终点的直线上无障碍物则可自行移动,若一个对象撞上另一对象时服务端发出停止命令; 两端有差异时,合并路点;
    4. 插值和推导:
      • 推导 – 使用最后得到的位置和对象的速度来预测其未来的位置和速度; 延迟越大,差异越大,需插值平滑防止跳跃;
      • 将对象的移动路径切割成100ms-200ms的时间片进行推导,当服务端和客户端路径点不同时,客户端插值调整到服务端的路径点;
      • 线性插值; 曲线插值(比如FPS游戏中,不是在跳跃就是在地上,需要拖回地面的曲线平滑,否则会造成滑翔的视觉错误);
    5. 为瞄准延迟使用反向仿真:
      • 上述方法不足以解决FPS中的问题 – 当玩家延迟较大时,可能已经瞄准目标并在合适的时刻按下了扳机,但已有方法会使击中无效;
      • 如果服务端可以知道客户端进行射击时的情景,它就能正确地判断目标是否被击中了;
      • 得到确认的消息序列沿着历史记录回溯,并计算出发送命令时客户端对象的位置,完成击中/未击中计算后,疑问对象被放到它当前的时间和位置中去;
      • 示例 – 客户端A在400ms前收到B最后一个移动数据包,因此,根据此时B的速度和方向,推断A按下扳机时B的位置,如果击中,则判定击中;
        ##【保持流畅:异步客户和时空穿梭】
    6. 共享状态的基本问题:
      • MMP游戏里必然有一些对玩家都必须相同的共享数据,有些数据还会以不可预见的方式改变,怎样发送这些共享状态的改变?
      • 重要成果 – 分布式交互仿真(DIS)、仿真网络(SIMNET)、高级架构(HLA);
    7. 航位推测法:客户端预测方法,广泛应用于需要发布位置数据的游戏中比如FPS; 减少发送数据; 补偿网络延迟;
      • 使用带有时间戳的信息进行预测可使移动更流畅;
      • 很难对会突然变向的对象预测,对其流畅的显示意味着不精确;若该对象并不需要在每个玩家视野中都具有相同表现,亦可使用航位推测;
    8. 仿真时间:通常并非真实时间(wall-clock time);
      • 服务端需要对分发时间值所花费的时间进行补偿 – 简单方法是用一个集中的时间源发出一堆访问,而后把平均回复时间的一半加在接收到的值上;
      • 仿真时间必须完全独立于帧频率,对客户端时间的操作应独立于画面更新的节奏;
    9. 直接操纵时间:
      • 常用方法 – 让所有客户端的仿真事件略微落后于实际时间,服务端需要使用某个网络消息时,可提供一个到达客户端的时间;
      • 上述方法会引起以个固定的延迟,但可通过一些声音和动画效果立即对玩家的请求进行确认,并马上开始玩家的行动;
      • 控制时间 – a.最小更新时间(一个对象的状态发生改变到它的状态可以再次发生改变所要经过的最短时间);
        b.时间戳下限(一个参与者可能获得下一个更新的最早时间);
    10. 总结:
      • 让客户端保持流畅比精确更重要; 让每个玩家都觉得他才是游戏中唯一的玩家; 不惜一切代价避免停滞和跳跃;
      • 让玩家能够知道游戏中发生的事并能参与其中; 让玩家可以掌控自己的游戏世界比让他们为其他玩家的游戏世界做贡献更为重要;
        ##【使用程序生成游戏世界:避免数据激增】
    11. 运行时生成的优点:
      • 有效减少数据量 – 用一个随机数种子和一个良好的随机数发生器就可生成整个游戏世界,只要玩家使用相同的种子和生成函数,所见便相同;
      • 细节生成 – 只需通过发布新的生成程序和生成DLL进行更新,地形/对象/新贴图等改变都可在各个客户端自动生成;
    12. 运行时生成的缺点:
      • 性能影响 – 须谨慎决定哪些数据在运行时生成?哪些在载入游戏关卡时预先生成?哪些在更新/发行时预先生成?
      • 增加复杂度延长开发时间 – 生成函数本身不复杂,但动态的细节层次函数可能非常复杂;
      • 多数生成函数很难控制 – 简单解决方案是预先生成到某个特定的细节层次,然后动态生成来补充其余的细节;
    13. 地形生成算法的分类:
      • 静态算法 – 先确定网格大小而后对整个网格操作,因控制方便,可用来把地形预先生成到一定的细节层次,而后动态算法补充;
        断层线算法(迭代次数越多,生成的地形越美观);
      • 细分算法 – 等离子算法(静态算法); 可建立一个树状结构来模拟自适应网格算法;
      • 动态算法 – Perlin噪声算法; 分型布朗运动(fBm); 多重分型算法(MojoWorld虚拟世界创建程序的主要驱动力);
    14. 修改程序生成的地形:手工修改; 静态算法修改; 伪动态算法修改;
    15. 高效地渲染程序生成的地形:ROAM算法;
    16. 生成贴图:
    17. 在程序生成的地形进行碰撞检测:避免在碰撞检测时进行三角形判断;
    18. 大型游戏中世界中的比例问题:
      • float在1000公里左右不够精确,double在一万亿公里时不够精确;
      • 尽量少用双精度,仅用于表示物体位置或关键位置,其他使用单精度;
      • 替身(Impostor)渲染;

    ##【为固定大小的对象编写一个高速有效的分配器】

    1. 游戏系统会频繁地分配很多对象,像矩阵、向量等,这些对象更适合于分配在栈上,不仅可减少内存/高速缓存不一致带来的问题,也可避免使用指针;
    2. 一个简单的向量分配器:分配一块连续的内存,大小是类T大小的整数倍,用指针数组保存空余内存块;最初每个分块指针都在数组中,用索引指示下一个空余块(初始为0),分配时返回索引位置的内存块,将索引递增1指向下一可用空闲内存,释放分块时索引减1,并将内存块指针放回指针数组中;
    3. 重载操作符,使调用者不需做额外工作即可使用分配器功能,比如重载C++中的new和delete运算符;
    4. 字节对齐:所有分配都应该至少和8个字节边界对齐,以获得最佳性能; 针对缓存线和处理器中的向量单元,分配16位或32位对齐可能更好;
    5. 降低分配器内存开销:把管理分配的数据结构放在未分配的内存块中;
      • 不再需要内存指针数组,而只需在每个空闲块开始的几个字节里,保存指向下一空闲块的指针; 循环链表;

    ##【使用贴图定制三维角色】

    1. 角色定制的类型:
      • 网格定制 – 可改变角色轮廓; 分段网格交换; 避免引起光照或碰撞问题;
      • 贴图定制 – 网格已固定,所需资源较少,对引擎功能依赖低,可提供大量变化;
    2. 贴图合成简介:定义和构造一个可用于贴图合成的区域,最好支持调色(hueing)和Alpha混合;
    3. 分层:例如 – 0皮肤 → 1身体艺术<纹身/伤痕> → 2毛发 → 3衣服 → 4外衣 → 5徽章;
    4. 贴图定制模板和样本:贴图到网格的正确映射是贴图定制成败的关键; 定义贴图区域的结构称为贴图模板;
    5. 样本集合:提供一个抽象层来简化用户与贴图定制系统之间的交互;隔离细节并确保在对模板修改或细分时不会影响用户已作出的关联;

    ##【游戏机平台上MMP游戏的独特挑战】

    1. 环境:游戏机更多地处于一个共享的环境中–人们聚集和社交的地方;
      • 分屏模式 – 让2-4人在同一台游戏机上共同游戏可降低很多问题的复杂度,包括交流、分组和交易;
      • 减少单次游戏持续时间 – 加快连接寻找朋友、决定做什么、到达目的地这一过程,使玩家半小时内足以进行一次有效游戏;
    2. 登录:简化登录过程,记住用户名,密码可通过一系列按键动作来输入,或同用户名一样使用虚拟键盘;
    3. 分辨率:小心决定低分辨率下的文本显示方式; 分屏模式进行组合显示(如小组名单/雷达地图);
    4. 聊天频道:一对一聊天; 和少量玩家分组式聊天; 和大量朋友或在行会中聊天; 大型事件中关于战略/战术的聊天;
      • 语音 – 通道切分; 无法过滤内容; 带宽需求高(可用P2P分布式聊天);
      • 表情 – 自定义表情需要额外传输时间,可能导致某些玩家不愿使用,一组不需额外传输时间的缺省表情非常重要;
      • 简短的固定表达 – 形象的手势、简短而固定的文本消息、热键触发的声音事件等;
    5. 选择目标:就近/最弱/团队目标/小组成员; 隐式目标选取 – 系统自动把处于攻击范围的对手作为目标(例如FPS中对准什么就攻击什么);
    6. 菜单:a.功能分组置于顶层菜单,从而平铺开使玩家可用手柄中不同的按键来触发; b.通常建议大部分菜单功能组合起来,置于一个独立的顶层菜单上;
    7. 背囊管理和交易:可使用虚拟指针拖拽; 装备、保存、交易、物品使用,需特别注意;
    8. 补充界面:若可通过行会网站、玩家组织、关于游戏诀窍的站点或其他Web拓展来建立游戏以外的存在,会加强玩家集体感;
      • 鼓励外部集体的一个方法是给与他们访问任何游戏数据的权限,只要不带来安全/隐私的风险、带宽问题、不涉及专有知识;

    #【游戏系统】
    ##【从原料到成品:社会经济中的职业生涯】

    1. 原料获取和加工:
      • 原料采集系统 – 玩家能力/技能/属性可影响采集结果(数量、质量、耗时); 原料核心亦可控制采集结果;
      • 超越期望的乐趣 – 引入制造系统的每一阶段,概率结果不应进行惩罚,而是用一些超出预期的结果来奖励玩家;
      • 时间投入和原料产出以及风险和回报 – 比如缩短时间降低质量获取更多低级原料; 危险地点采集稀有物品;
      • 实例 – 制造专家委托 → 采集专家 → 危险区域护送(任务公告栏)/朋友 → 复杂技巧获取高质量原料 → 采集附产品 → 活着回来丰厚回报;
    2. 社会经济中的合作制造:通过对原料采集、物品需求度、合理的技能多样化及丰富的制造配方进行考虑;
      • 制造物品 – 制造技能 → 配方 → 详细描述所需工具、原料或其他物品;出问题时所需原料 → 原料/工具质量决定成品质量 → 制造时间;
      • 时间投入与产品价值 – 稀有度决定价值,通过对原料/工具的技能等级或产出控制; 玩家合作制造;
      • 原料、工具和物品的作用 – 工具可由玩家制造,独特限制,成品质量影响; 成品可作为其他物品部件,比如零件与引擎;
      • 对新制造的工具进行改进和修理 – 使用和制造物品相同的方式进行修理和改进,简单把所需修理或改进的物品组合起来;
    3. 物品制造在社会经济中起到的作用:
      • 社会作用 – a.设计为需要很多不同的技能集合; b.调整风险和回报间的关系使从事制造业的玩家依赖于冒险玩家;
      • 制造业、物品和声望 –
        • a.把信息提供给感兴趣的玩家 – 广告栏、任务发布栏;
        • b.外部网页建立公告栏镜像使玩家游戏外可寻找合作伙伴;
        • c.可获取制造物品的特定信息非常有用(总数、质量、杀怪数、使用者平均声望技能等级等); 大型战役致命一击信息可获取/传播等;
      • 保持需求(重要) – a.让物品会损坏; b.让玩家的物品保持较高的周转率;

    ##【玩家房屋供给:我的房屋就是你的房屋】

    1. 成长之路:价格高供应量少 → 不断存钱 → 选址(地产有限) → 内部装饰 → 升级更好的房屋。<成就感、展示、商业、收藏>
    2. 商业方法:雇佣商人NPC销售货物 → 增加房屋需求。
    3. 地段、地段、地段:a.直接在房屋边通过的人流更易带来顾客(拍卖将降低房屋重要性); b.战利品展示等可炫耀的事情;
    4. 应该把戟放在哪里:个性摆放来展示创造力和设计能力; 珍惜品市场; 垃圾储藏室(人们不喜欢放弃) – 建议开始就进行限制;
    5. 让玩家管理一家商店、开家酒馆或拥有一座壮丽的城堡可让玩家以一种难以置信的方式对身边的世界产生影响;

    ##【社会游戏系统:促进玩家社会化并提供游戏回报的另一途径】

    1. 社会游戏系统:就是那些给予不同的社会行为支持、鼓励、回报和惩罚的系统;
      • 吸收新成员融入虚拟世界 – 良好的设计可帮助玩家学习和应用游戏世界中的社会期望(譬如进入图书馆就知道那是安静的地方不应打扰他人);
      • 促进玩家的交流和联系 – 公开的、私人的、近距离的、群体中的聊天系统; 但社交不止于交谈;
      • 支持和促进合作游戏 – 积极回报正确的社会行为并阻止反社会行为;
      • 培养归属感 – 马斯洛需要层次第三步,归属、联系、被他人接受、付出与获得爱和友谊;
      • 小型世界网络 – 六度分离; 通过提供更强的社会系统,降低所需度数,进一步增强玩家间联系;
    2. 为什么要让玩家社会化?
      • 创建自洽社会 – 尽量创建一个不需开发人员参与即可独立运转和发展的自洽世界,缩减开支,增强虚拟世界玩家的联系;
      • 减少"反社会"玩家 – "玩家捣蛋投诉"占客服时间40%,而将其花费在帮助技术困难或遇到程序错误的玩家身上,会更有建设性;
      • 增加玩家的股份 – 玩家对虚拟世界投入(朋友/虚拟活动/金钱等)越多,忠诚度越高;
      • 创建更丰富的游戏世界 – "广度"指实现更多游戏系统尤其是社会系统; "深度"指提供更丰富的游戏体验;
        • 更好的AI、更复杂的任务以及更广阔的地理区域; 更复杂的社交系统;
        • 它不仅仅是一个游戏 – 有些玩家在游戏中发现了他所未开发的技能或天赋; 领袖体验、克服游泳恐惧、虚拟婚姻到真实婚姻等;
    3. 目前使用的社会系统:
      • 聊天 – 频道聊天; 广播; 图形表情; 语音; 肢体动作如"挥手";
      • 层次结构 – 行会系统; 效忠君主系统; 金字塔效应;
      • 声望 – “根据某人的人格或其他品质对其作出一般评价;某个人或某种事物所受到的相对评价或尊重;” 玩家可评价他人;
      • Bartle类型 – 根据玩家动机分为四种类型:探险、获得成就、杀人和社交;
        • 探险者希望了解游戏世界,会绘制地图,测试游戏机制/数据等;
        • 获得成就的人为自己创建游戏相关目标并完成(积攒战利品或寻求地位);
        • 杀手会缠着其他玩家,寻求展示比其他玩家更为优越的机会; PK或寻求各种方法来和其他玩家捣蛋;
        • 社交玩家喜欢与其他玩家进行交互,即进行社会化;
      • 匹配服务 – 帮助玩家寻找或加入其他玩家; 譬如梦幻西游的剧情/任务队伍查询/加入(等级职业限制等);
    4. 回报社会性游戏的创新方法:
      • 对目前的社会游戏系统进行改进 – 更易用的界面; 根据玩家反馈和建议完善; 借助问卷帮助系统选择组合玩家队伍; 重要信息手机通知;
      • 增加社会联系 – 马尔科姆.格拉德威尔《引爆流行》少数法则,"链接者"能认识大量其他人,找到他们并给予回报,即可创建和加强社交网络;
      • 开发与真实世界平行的社交系统 – 玩家聚集点往往都都是那些建立起真正友谊的玩家;
        • 应该使用人们在真实生活中使用的社交系统并且把它们运用在游戏世界中,譬如虚拟饮食/服装经营,增强投入感和满足感;
      • 为新的社会行为定义规范和礼节 – 命名规范; 鼓励符合游戏文化的社会规范;
      • 通过仪式 – 设计通过仪式(成就里程碑)增强参与感;譬如5级才可选一个专业领域,10级才可选择一个姓;
      • 新的经济体系 – 货币、战利品或商业组成; 货币可使用不同类型等价物,譬如有用的物品、时间、空间、声望等;
      • 导师 – NPC管理的学徒/导师系统;
      • 社会化 – 要想方设法报答社会性玩家 – 譬如长期在公共频道分享故事,帮助其他玩家,向不熟悉游戏的玩家介绍游戏系统的玩家;

    ##【为创建和管理行会设计灵活的命令集】

    1. 创建:对初始成员的最小数量做出要求避免滥用行会创建; 允许行会某些成员可改变行会名(投票制?);
    2. 领导:数字表示等级,比如领袖为0,普通成员为100; 可对每个等级进行定义和命名,并指定职责和能力;
    3. 标识:提供除名称外的其他标识; 譬如纹章/饰章,使用源自游戏的完全虚构的图记,避免带来“真实世界”问题; “纹章学”; “可预览”;
    4. 行会维护:增删成员; 等级设置; 管理权限等;
    5. 财产:行会银行/仓库; 共享装备; 金钱捐赠/提取; 查询财产提存记录;
    6. 专用区域:特定区域供行会成员使用; 特定建筑供特定等级的成员进入;
    7. 交流:行会内部交流; 限定可加入行会的临时频道; 指定多个所有者; 成员移除/禁言;

    ##【创建声望系统】

    1. 友谊和仇恨的实现:每个NPC都属于某个派系,定义不同关系(友好→中立→仇恨)时的可能反应; 声望调整; 目击者传递; 派系声望;
    2. 宽恕:个人声望; 标记"全局"或"非全局",比如兽人士兵攻击精灵士兵,目击者为全局的,则会引发种族对抗; 仇恨衰减;
    3. 投降:提供不会消逝的永久个人声望调整,避免投降场景短期可用,长期则出现问题;
    4. 玩家对战的设置:玩家追随者/召唤兽使用主人的声望; 可通过"玩家关系"面板设置是否仇视其他玩家,确保宠物行为符合期望;

    ##【城邦政府在在线社区中的作用】

    1. 公民生涯:赋予玩家初始归属感,并提供一些导引; 无论参与度公民都可享受归属和导引带来的社会优越性; 避免城邦间流动性过大;
      • 国籍 – 国籍获得、更改所需步骤、对国籍滥用带来的问题及对策;
        • 登录时授予国籍,提供城邦简单数据,比如总资产、总人口、占领区域、完成的任务、平均声望; 默认玩家无选择时平均分配并给予奖励;
        • 更改申请 → 缓冲时间 → 流浪状态 → 找到担保人 → 缴纳统治者规定的物资/费用 → 加入新国籍;
        • 限制每个账户只能拥有一个国籍; 避免"傀儡型"赏金猎人、滥用选举系统等;
      • 国籍可能带来的权益 – 包括可从玩家及周围环境获得保护; 根据角色能力获得奖励; 参与城邦相关行动和任务;
        • 国籍权益与城邦核心特征以及所占区域的核心特征挂钩; 譬如倾向魔法/战斗/生活职业;
        • 权益与城邦扩张相关,占据特定区域获得相连的技能奖励,亦可区域类采集、冒险或资源占领,鼓励玩家在该区域聚集,加强公民联系;
        • 权益与参与政府任务相关,消灭采集区惹麻烦的NPC、获得建造资源、守卫/侦查特定区域;玩家为其社会权益工作,冒险将更有意义;
    2. 参与城邦工作:强健的在线政府系统可为玩家提供大量的参与机会;
      • 领袖选举 – 投票权限制公民在线时长/等级/活跃度; 提供候选者简单数据(属性、成就、声望)及宣言; 胜利者广播并发布到网页上;
      • 政府职业如"赏金猎人" – 玩家满足基本要求(时长/战力)后可申请,统治者通过查看成就/玩家评价决定是否授予;
        • 玩家需要时即可呼叫赏金猎人,之后可瞬移至玩家身边,为避免滥用,需支付一定费用;
        • 鼓励赏金猎人扩大巡逻范围,比如设置动态路点,到达时刻获得一定报酬,促使巡逻到不常去的地点;
        • 提供工具识别玩家杀手并跟踪,基于声望、行会关系、所杀玩家数等,在一定时间内驱逐出城邦;
        • 使负责的赏金猎人付出的时间和努力的回报要远高于正常冒险; 记录行为(应答数,忽略数,杀敌,路点),用于清除滥用职位能力的玩家;
      • 让玩家能自行参与城邦工作可让他们感到自己是社会的一份子; 选举,影响发展方向,职位参与等各种形式包容并引导,建立更强的社会联系;
    3. 定义政治过程:
      • 成为统治者:
        • 满足基本的等级在线时长/行会领袖,一定数量(总人口特定百分比)玩家宣誓(付出一定代价如钱或资源)支持后,成为候选人;
        • 候选者接受相应条款(要求捐献大量金钱,与总人口通胀等挂钩;公开账户角色的行为等信息)后,提出宣言(治理计划等)后,开始参与选举;
        • 选举持续一定时间,避免服务器压力过大以及控制权力转移是的改变和混乱; 限制连任次数或一定时期内担任次数(譬如每年不超过三次);
        • 罢黜 – 考验公民的团结度,超过一定百分比即自动废除,在下次选举前,系统NPC代管(根据历任平均数据开展工作);
        • 辞职 – 可选择立即辞职; 登入次数/行动数不达标,将自动失去职位;
      • 统治者权力与义务:确定法律,指定税率,负责职位及城市发展支出间的财政分配,任命赏金猎人等不需选举的政府职位;
        • 法律包括a.是否允许攻击公民;b.安全区设置;c.不同声望可享受的权益;d.物品制造/销售限制;e.技能限制等; 限制法律变更次数及时限;
        • 在预先指定的范围内调整税率(对内/对外),税收存入国库,不可直接提取,但能分配职位收入/发展城市/建造银行等;
    4. 城邦为不同政体间的大规模战争及领土扩展提供了有意义的背景;一个强有力的在线政府系统为解决在线社会中的很多问题提供了强大的框架;
    展开全文
  • ROS专题----导航功能包navigation基础汇总

    万次阅读 多人点赞 2017-03-02 09:59:01
    资料来源ROS官网:...但是,想要在任意机器人上使用导航功能包集可能有点复杂。使用导航功能包集的先决条件是,机器人必须运行ROS,有一个tf变换树,使用正确的ROS Message types发布传感器数据。而且

    资料来源ROS官网:http://wiki.ros.org/cn/navigation

    nav_comic.png

    概述

    • 概念层面上讲,导航功能包集是相当简单的。 它从里程计和传感器数据流获取信息,并将速度命令发送给移动基站(比如你的机器人)。但是,想要在任意机器人上使用导航功能包集可能有点复杂。使用导航功能包集的先决条件是,机器人必须运行ROS,有一个tf变换树,使用正确的ROS Message types发布传感器数据。而且,我们需要在高层为一个具有一定形状和动力学特点的机器人配置导航功能包集。本手册指导配置一个典型的导航功能包集。

    硬件需求

    • 虽然导航功能包集被设计成尽可能的通用,在使用时仍然有三个主要的硬件限制:
    1. 它是为差分驱动的完全约束的轮式机器人设计的。它假设移动基站受到理想的运动命令的控制并可实现预期的结果,命令的格式为:x速度分量,y速度分量,角速度(theta)分量。
    2. 它需要在移动基站上安装一个平面二维激光。这个激光用于构建地图和定位。
    3. 导航功能包集是为正方形的机器人开发的,所以方形或圆形的机器人将是性能最好的。 它也可以工作在任意形状和大小的机器人上,但是较大的矩形机器人将很难通过狭窄的空间,例如门道。

    ----配置机器人的TF----

    Transform Configuration(变换配置)

    许多ROS功能包,都要求利用tf软件库,以机器人识别的变换树的形式进行发布。抽象层面上,变换树其实就是一种“偏移”,代表了不同坐标系之间的变换和旋转。更具体点来说,设想一个简单的机器人,只有一个基本的移动机体和挂在机体上方的扫描仪。基于此,我们定义了两个坐标系:一个对应于机体中心点的坐标系,一个对应于扫描仪中心的坐标系。分别取名为“base_link”和“baser_laser”。关于坐标系的命名习惯,参考REP 105.

    此时,可以假设,我们已经从传感器获取了一些数据,以一种代表了物体到扫描仪中心点的距离的形式给出。换句话说,我们已经有了一些“base_laser”坐标系的数据。现在,我们期望通过这些数据,来帮助机体避开物理世界的障碍物。成功的关键是,我们需要一种方式,把传感器扫描的数据,从“base_laser”坐标系转换到“base_link”坐标系中去。本质上,就是定义一种两个坐标系的“关系”。

    http://wiki.ros.org/navigation/Tutorials/RobotSetup/TF?action=AttachFile&do=get&target=simple_robot.png

    为了定义这种关系,假设我们知道,传感器是挂在机体中心的前方10cm,高度20cm处。这就等于给了我们一种转换的偏移关系。具体来说,就是,从传感器到机体的坐标转换关系应该为(x:0.1m,y:0.0m, z:0.2m),相反的转换即是(x:-0.1m,y:0.0m,z:0.2m)。

    我们可以选择去自己管理这种变换关系,意味着需要自己去保存,以及在需要的时候调用。但是,这种做法的缺陷即是随着坐标转换关系数量的增加,而愈加麻烦。幸运的是,我们也没有必要这么干。相反,我们利用tf定义了这么一种转换关系,那么就让它来帮我们管理这种转换关系吧。

    利用tf来管理这种关系,我们需要把他们添加到转换树(transform tree)中。一方面来说,转换树中的每一个节点都对应着一类坐标系,节点之间的连线即是两个坐标相互转换的一种表示,一种从当前节点到子节点的转换表示。Tf利用树结构的方式,保证了两个坐标系之间的只存在单一的转换,同时假设节点之间的连线指向是从parent到child。

    http://wiki.ros.org/navigation/Tutorials/RobotSetup/TF?action=AttachFile&do=get&target=tf_robot.png

    基于我们简单的例子,我们需要创建两个节点,一个“base_link”,一个是“base_laser”。为了定义两者的关系,首先,我们需要决定谁是parent,谁是child。时刻记得,由于tf假设所有的转换都是从parent到child的,因此谁是parent是有差别的。我们选择“base_link”坐标系作为parent,其他的传感器等,都是作为“器件”被添加进robot的,对于“base_link”和“base_laser”他们来说,是最适合的。这就意味着转换关系的表达式应该是(x:0.1m,y0.0m,z:0.2m)。关系的建立,在收到“base_laser”的数据到“base_link”的转换过程,就可以是简单的调用tf库即可完成。我们的机器人,就可以利用这些信息,在“base_link”坐标系中,就可以推理出传感器扫描出的数据,并可安全的规划路径和避障等工作。

    Writing Code(代码编写)

    希望上面的例子,一定程度上可以帮助大家理解tf。现在,我们可以建立通过代码来实现转换树。对于这个例子来说,前提是熟悉ROS,所以如果不熟悉,先预习下ROS Documentation吧。

    假定,我们以上层来描述“base_laser”坐标系的点,来转换到"base_link"坐标系。首先,我们需要创建节点,来发布转换关系到ROS系统中。下一步,我们必须创建一个节点,来监听需要转换的数据,同时获取并转换。在某个目录创建一个源码包,同时命名“robot_setup_tf”。添加依赖包roscpp,tf,geometry_msgs

    $ cd %TOP_DIR_YOUR_CATKIN_WS%/src
    $ catkin_create_pkg robot_setup_tf roscpp tf geometry_msgs

    至此,你必须运行上面的命令,当然你必须有必要的权限(例如,~/ros目录下,你可能在之前的文档中操作过这个目录)

    Alternative in fuerte, groovy and hydro: there is a standard robot_setup_tf_tutorial package in the navigation_tutorialsstack. You may want to install by following (%YOUR_ROS_DISTRO% can be { fuertegroovy } etc.):

    $ sudo apt-get install ros-%YOUR_ROS_DISTRO%-navigation-tutorials

    Broadcasting a Transform(广播变换)

    至此,我们已经创建了package。我们需要创建对于的节点,来实现广播任务base_laser->base_link。在robot_setup_tf包中,用你最喜欢的编辑器打开,然后将下面的代码粘贴到src/tf_broadcaster.cpp文件中去。

    切换行号显示
       1 #include <ros/ros.h>
       2 #include <tf/transform_broadcaster.h>
       3 
       4 int main(int argc, char** argv){
       5   ros::init(argc, argv, "robot_tf_publisher");
       6   ros::NodeHandle n;
       7 
       8   ros::Rate r(100);
       9 
      10   tf::TransformBroadcaster broadcaster;
      11 
      12   while(n.ok()){
      13     broadcaster.sendTransform(
      14       tf::StampedTransform(
      15         tf::Transform(tf::Quaternion(0, 0, 0, 1), tf::Vector3(0.1, 0.0, 0.2)),
      16         ros::Time::now(),"base_link", "base_laser"));
      17     r.sleep();
      18   }
      19 }
    

    现在,让我们来针对上面的代码,作更细节的解释。

    Error: No code_block found Tf功能包提供了一种实现tf::TransformBroadcaster ,使任务发布变换更容易。为了调用TransformBroadcaster, 我们需要包含 tf/transform_broadcaster.h 头文件.

    Error: No code_block found 我们创建一个TransformBroadcaster对象,之后我们可以利用他来发送变换关系,即base_link→ base_laser。

    Error: No code_block found 这部分是关键部分。通过TransformBroadcaster来发送转换关系,需要附带5个参数。第1个参数,我们传递了旋转变换,在两个坐标系的发送的任意旋转,都必须通过调用btQuaternion.现在情况下,我们不想旋转,所以我们在调用btQauternion的时候,将pitch,roll,yaw的参数都置0.第2个参数,btVector3,任何变换过程都需要调用它。无论怎样,我们确实需要做一个变换,所以我们调用了btVector3,相应的传感器的x方向距离机体基准偏移10cm,z方向20cm。第3个参数,我们需要给定转换关系携带一个时间戳,我们标记为ros::Time::now()。第4个参数,我们需要传递parent节点的名字。第5个参数,传递的是child节点的名字。

    Using a Transform(调用变换)

    上面的代码,我们创建了一个节点来发布转换关系,baser_laser->base_link。现在,我们需要利用转换关系,将从传感器获取的数据转换到机体对应的数据,即是“base_laser”->到“base_link”坐标系的转换。下面的代码,后边会紧根更详细的解析。在robot_setup_if功能包中,在src目录下创建tf_listener.cpp,并将下面的代码粘贴到里面:

    切换行号显示
       1 #include <ros/ros.h>
       2 #include <geometry_msgs/PointStamped.h>
       3 #include <tf/transform_listener.h>
       4 
       5 void transformPoint(const tf::TransformListener& listener){
       6   //we'll create a point in the base_laser frame that we'd like to transform to the base_link frame
       7   geometry_msgs::PointStamped laser_point;
       8   laser_point.header.frame_id = "base_laser";
       9 
      10   //we'll just use the most recent transform available for our simple example
      11   laser_point.header.stamp = ros::Time();
      12 
      13   //just an arbitrary point in space
      14   laser_point.point.x = 1.0;
      15   laser_point.point.y = 0.2;
      16   laser_point.point.z = 0.0;
      17 
      18   try{
      19     geometry_msgs::PointStamped base_point;
      20     listener.transformPoint("base_link", laser_point, base_point);
      21 
      22     ROS_INFO("base_laser: (%.2f, %.2f. %.2f) -----> base_link: (%.2f, %.2f, %.2f) at time %.2f",
      23         laser_point.point.x, laser_point.point.y, laser_point.point.z,
      24         base_point.point.x, base_point.point.y, base_point.point.z, base_point.header.stamp.toSec());
      25   }
      26   catch(tf::TransformException& ex){
      27     ROS_ERROR("Received an exception trying to transform a point from \"base_laser\" to \"base_link\": %s", ex.what());
      28   }
      29 }
      30 
      31 int main(int argc, char** argv){
      32   ros::init(argc, argv, "robot_tf_listener");
      33   ros::NodeHandle n;
      34 
      35   tf::TransformListener listener(ros::Duration(10));
      36 
      37   //we'll transform a point once every second
      38   ros::Timer timer = n.createTimer(ros::Duration(1.0), boost::bind(&transformPoint, boost::ref(listener)));
      39 
      40   ros::spin();
      41 
      42 }
    

    Ok... now we'll walk through the important bits step by step.

    好的,现在让我们将上面代码的重点步步分解:

    Error: No code_block found 这里,我们包含tf/transform_listener.h头文件,是为了后边创建tf::TransformListener。一个TransformListener目标会自动的订阅ROS系统中的变换消息主题,同时管理所有的该通道上的变换数据。

    Error: No code_block found创建一个函数,参数为TransformListener,作用为将“base_laser”坐标系的点,变换到“base_link”坐标系中。这个函数将会以ros::Timer定义的周期,作为一个回调函数周期调用。目前周期是1s。

    Error: No code_block found 此处,我们创建一个虚拟点,作为geometry_msgs::PointStamped。消息名字最后的“Stamped”的意义是,它包含了一个头部,允许我们去把时间戳和消息的frame_id相关关联起来。我们将会设置laser_point的时间戳为ros::time(),即是允许我们请求TransformListener取得最新的变换数据。对于header里的frame_id,我们设置为“base_laser”,因为我们是创建的是扫描仪坐标系里的虚拟点。最后,我们将会设置具体的虚拟点,比如x:1.0,y:0.2,z:0.0

    Error: No code_block found 至此,我们已经有了从“base_laser”到“base_link”变换的点数据。进一步,我们通过TransformListener对象,调用transformPoint(),填充三个参数来进行数据变换。第1个参数,代表我们想要变换的目标坐标系的名字。第2个参数填充需要变换的原始坐标系的点对象,第3个参数填充,目标坐标系的点对象。所以,在函数调用后,base_point里存储的信息就是变换后的点坐标。

    Error: No code_block found 如果某些其他的原因,变换不可得(可能是tf_broadcaster 挂了),调用transformPoint()时,TransformListener调用可能会返回异常。为了体现代码的优雅性,我们将会截获异常并把异常信息呈现给用户。

    Building the Code代码构建

    至此,根据我们写的例子,接下来我们需要构建编译。打开CMakeList.txt,在文件末尾添加下面的几行:

    add_executable(tf_broadcaster src/tf_broadcaster.cpp)
    add_executable(tf_listener src/tf_listener.cpp)
    target_link_libraries(tf_broadcaster ${catkin_LIBRARIES})
    target_link_libraries(tf_listener ${catkin_LIBRARIES})

    Next, make sure to save the file and build the package.

    $ cd %TOP_DIR_YOUR_CATKIN_WS%
    $ catkin_make

    Running the Code(代码运行)

    好的,万事俱备只欠东风!让我恩试着实际运行下吧。这部分,你需要开三个终端窗口

    第一个窗口,运行core

    roscore

    第二个,运行 tf_broadcaster

    rosrun robot_setup_tf tf_broadcaster

    好的。现在,我们会在第三个窗口运行tf_listener,将从传感器坐标系获取的虚拟点,变换到机体坐标系。

    rosrun robot_setup_tf tf_listener

    如果一切顺利,应该会看到类似的结果。每次打印间隔1s。

    [ INFO] 1248138528.200823000: base_laser: (1.00, 0.20. 0.00) -----> base_link: (1.10, 0.20, 0.20) at time 1248138528.19
    [ INFO] 1248138529.200820000: base_laser: (1.00, 0.20. 0.00) -----> base_link: (1.10, 0.20, 0.20) at time 1248138529.19
    [ INFO] 1248138530.200821000: base_laser: (1.00, 0.20. 0.00) -----> base_link: (1.10, 0.20, 0.20) at time 1248138530.19
    [ INFO] 1248138531.200835000: base_laser: (1.00, 0.20. 0.00) -----> base_link: (1.10, 0.20, 0.20) at time 1248138531.19
    [ INFO] 1248138532.200849000: base_laser: (1.00, 0.20. 0.00) -----> base_link: (1.10, 0.20, 0.20) at time 1248138532.19

    祝贺你,你已经成功的编写了一个针对平面传感器的坐标变换。下一步就是替换PointStamped,来使用真正的传感器进行操作。幸运的是,已经有相关的指导文档了here

    ----在机器人上配置并使用导航综合功能包----

    机器人配置

    attachment:overview_tf.png假定我们已经以特定方式配置机器人,导航功能包集将使其可以运动。上图概述了这种配置方式。白色的部分是必须且已实现的组件,灰色的部分是可选且已实现的组件,蓝色的部分是必须为每一个机器人平台创建的组件。以下章节将介绍使用导航功能包集的先决条件以及如何满足不同平台。

    ROS

    导航功能包集假定机器人使用ROS系统。请查阅ROS documentation以了解如何在你的机器人上安装ROS。

    TF变换配置(其他变换)

    导航功能包集需要机器人不断使用tf发布有关坐标系之间的关系的信息。详细的配置教程请查阅:Tf配置.

    传感器信息(sensor source)

    导航功能包集使用来自传感器的信息避开现实环境中的障碍物,它假定这些传感器在ROS上不断发布sensor_msgs/LaserScan消息或者sensor_msgs/PointCloud消息。有关在ROS上发布这些消息的教程,请查阅在ROS上发布传感器数据流。此外,一些已经有了ROS上的驱动的传感器亦满足这一教程。一下是部分ROS支持的传感器以及相关驱动链接:

    里程信息(odometry source)

    导航功能包集需要使用tfnav_msgs/Odometry消息发布的里程信息。这里有一篇发布里程信息的教程:在ROS上发布里程信息. 以下列出部分支持里程计的平台以及可用的驱动:

    基座控制器(base controller)

    导航功能包集假定它可以通过话题"cmd_vel"发布geometry_msgs/Twist类型的消息,这个消息基于机器人的基座坐标系,它传递的是运动命令。这意味着必须有一个节点订阅"cmd_vel"话题, 将该话题上的速度命令(vx, vy, vtheta转换为电机命令(cmd_vel.linear.x, cmd_vel.linear.y, cmd_vel.angular.z)发送给移动基座。以下列出部分支持基座控制器的平台以及可用的驱动:

    地图 (map_server)

    导航功能包集的配置并不需要有一张地图以供操作,但基于本教程的目的,我们假定你有一张地图。请查阅教程创建一张地图了解在你的系统环境下创建一张地图的细节。

    导航功能包集配置

    本节介绍如何配置导航功能包集。假设上述所有需要的环境都已满足。特别的,这意味着,机器人必须使用tf发布坐标帧,并从所有的传感器接收 sensor_msgs/LaserScan 或者 sensor_msgs/PointCloud 消息用于导航,同时需要使用 tf 和nav_msgs/Odometry 消息发布导航消息,消息会以命令的形式下发给机器人底座。如果所需要的配置你都没有,请参见机器人配置

    创建一个软件包

    首先,我们创建一个软件包,用来保存我们所有的配置文件和启动文件。这个软件包需要包含所有用于实现 机器人配置小节所述以来,就如其以依赖导航功能包集高级接口 move_base 软件包一样。因此, 为你的软件包选好位置,执行以下命令:

    catkin_create_pkg my_robot_name_2dnav move_base my_tf_configuration_dep my_odom_configuration_dep my_sensor_configuration_dep

    这个指令会创建一个包含运行导航功能包集所需依赖的软件包。

    创建机器人启动配置文件

    现在,我们有了一个存放所有配置文件和启动文件的工作空间,我们会创建一个roslaunch文件来启动所有的硬件以及发布机器人所需的tf。打开你喜欢的编辑器,粘贴一下内容到my_robot_configuration.launch。你可以自由的将 "my_robot" 改成你的机器人的名字。以后,我们会对launch文件做相似的更改,确保你阅读了本节其余内容。

    <launch>
      <node pkg="sensor_node_pkg" type="sensor_node_type" name="sensor_node_name" output="screen">
        <param name="sensor_param" value="param_value" />
      </node>
    
      <node pkg="odom_node_pkg" type="odom_node_type" name="odom_node" output="screen">
        <param name="odom_param" value="param_value" />
      </node>
    
      <node pkg="transform_configuration_pkg" type="transform_configuration_type" name="transform_configuration_name" output="screen">
        <param name="transform_configuration_param" value="param_value" />
      </node>
    </launch>

    好了,现在我们有了一个launch文件模板,但是,我们需要根据自己的机器人去完善它。以下章节,我们会逐步的改变它。

      <node pkg="sensor_node_pkg" type="sensor_node_type" name="sensor_node_name" output="screen">
        <param name="sensor_param" value="param_value" />
      </node>

    这里,我们会启动机器人运行导航功能包所需的所有传感器。用你的传感器对应的ROS驱动包替换"sensor_node_pkg",用你的传感器类型替换"sensor_node_type"(通常与节点名一致),用你的传感器节点名替换"sensor_node_name","sensor_param"包含所有必需的参数。注意,如果你有多个传感器,在这里一起启动它们。

      <node pkg="odom_node_pkg" type="odom_node_type" name="odom_node" output="screen">
        <param name="odom_param" value="param_value" />
      </node>

    这里,我们启动基座(底盘)的里程计。同样,替换相应的pkg, type, name,并根据实际指定相关参数。

      <node pkg="transform_configuration_pkg" type="transform_configuration_type" name="transform_configuration_name" output="screen">
        <param name="transform_configuration_param" value="param_value" />
      </node>

    这里,我们启动相应的tf变换。同样,替换相应的pkg, type, name,并根据实际指定相关参数。

    配置代价地图 (local_costmap) & (global_costmap)

    导航功能包集需要两个代价地图来保存世界中的障碍物信息。一张代价地图用于 规划,在整个环境中创建长期的规划,另一个用于局部规划与避障。有一些参数两个地图都需要,而有一些则各不相同。因此,对于代价地图,有三个配置项: common配置项, global配置项和local配置项。

    注意: 接下来的内容只是代价地图的基本配置项。想要查看完整的配置,参看costmap_2d文档.

    共同配置(local_costmap) & (global_costmap)

    导航功能包集使用代价地图存储障碍物信息。为了使这个过程更合理,我们需要指出要监听的传感器的话题,以更新数据。我们创建一个名为costmap_common_params.yaml的文件,内容如下:

    obstacle_range: 2.5
    raytrace_range: 3.0
    footprint: [[x0, y0], [x1, y1], ... [xn, yn]]
    #robot_radius: ir_of_robot
    inflation_radius: 0.55
    
    observation_sources: laser_scan_sensor point_cloud_sensor
    
    laser_scan_sensor: {sensor_frame: frame_name, data_type: LaserScan, topic: topic_name, marking: true, clearing: true}
    
    point_cloud_sensor: {sensor_frame: frame_name, data_type: PointCloud, topic: topic_name, marking: true, clearing: true}

    好,现在我们分解以上代码。

    obstacle_range: 2.5
    raytrace_range: 3.0

    这些参数设置放入代价地图的障碍信息的阈值。 “obstacle_range”参数决定了引入障碍物到代价地图的传感器读书的最大范围。 在这里,我们把它设定为2.5米,这意味着机器人只会更新以其底盘为中心半径2.5米内的障碍信息。 “raytrace_range”参数确定的空白区域内光线追踪的范围。 设置为3.0米意味着机器人将试图根据传感器读数清除其前面3.0米远的空间。

    footprint: [[x0, y0], [x1, y1], ... [xn, yn]]
    #robot_radius: ir_of_robot
    inflation_radius: 0.55

    这里我们设置机器人的footprint或机器人半径(如果是圆形的)。 指定的footprint时,机器人的中心被认为是在(0.0,0.0),顺时针和逆时针规范都支持。 我们还将设置代价地图膨胀半径。膨胀半径应该设置为障碍物产生代价的最大距离。 例如,膨胀半径设定在0.55米意味着机器人所有路径与障碍物保持0.55米或更的远离(具有同样的成本)。

    observation_sources: laser_scan_sensor point_cloud_sensor

    “observation_sources”参数定义了一系列传递空间信息给代价地图的传感器。每个传感器定义在下一行。

    laser_scan_sensor: {sensor_frame: frame_name, data_type: LaserScan, topic: topic_name, marking: true, clearing: true}

    这一行设置“observation_sources”中提到的传感器。这个例子定义了 laser_scan_sensor。 “frame_name”参数应设置为传感器坐标帧的名称,“data_type”参数应设置为LaserScan或PointCloud,这取决于主题使用的消息,“topic_name”应该设置为发布传感器数据的主题的名称。 “marking”和“clearing”参数确定传感器是否用于向代价地图添加障碍物信息,或从代价地图清除障碍信息,或两者都有。

    全局配置(global_costmap)

    下面我们将创建一个存储特定的全局代价地图配置选项的文件。新建一个文件:global_costmap_params.yaml并粘贴以下内容:

    global_costmap:
      global_frame: /map
      robot_base_frame: base_link
      update_frequency: 5.0
      static_map: true

    “global_frame”参数定义了代价地图运行所在的坐标帧。在这种情况下,我们会选择/map frame。 “robot_base_frame”参数定义了代价地图参考的的机器地毯的坐标帧。“update_frequency”参数决定了代价地图更新的频率。 “static_map”参数决定代价地图是否根据map_server提供的地图初始化。如果你不使用现有的地图,设为false。

    本地配置(local_costmap)

    下面我们将创建一个存储特定的本地代价地图配置选项的文件。新建一个文件:localal_costmap_params.yaml并粘贴以下内容:

    local_costmap:
      global_frame: odom
      robot_base_frame: base_link
      update_frequency: 5.0
      publish_frequency: 2.0
      static_map: false
      rolling_window: true
      width: 6.0
      height: 6.0
      resolution: 0.05

    “global_frame”,“robot_base_frame”,“update_frequency”,“static_map”参数与全局配置意义相同。“publish_frequency”参数决定了代价地图发布可视化信息的频率。将“rolling_window”参数设置为true,意味着随着机器人在限时世界里移动,代价地图会保持以机器人为中心。“width”、“height”,“resolution”参数分别设置代价地图的宽度(米、)高度(米)和分辨率(米/单元)。 注意,这里的分辨率和你的静态地图的分辨率可能不同,但我们通常把他们设成一样的。

    完整的配置选项

    这里是用于启动和运行的最简单的配置,更多的细节请参阅costmap_2d 文档.

    === Base Local Planner 配置=== Base_local_planner负责根据高层规划计算速度命令并发送给机器人基座。 我们需要根据我们的机器人规格配置一些选项使其正常启动与运行。新建一个名为base_local_planner_params.yaml的文件,内容如下:

    注意: 本节只涵盖TrajectoryPlanner的基本配置选项。 文档的全部选项,请参阅base_local_planner 文档.

    TrajectoryPlannerROS:
      max_vel_x: 0.45
      min_vel_x: 0.1
      max_vel_theta: 1.0
      min_in_place_vel_theta: 0.4
    
      acc_lim_theta: 3.2
      acc_lim_x: 2.5
      acc_lim_y: 2.5
    
      holonomic_robot: true

    上面的第一部分参数定义机器人的速度限制。 第二部分定义了机器人的加速度的限制。

    为导航功能包创建一个Launch启动文件

    现在我们已经有了所有的配置文件,我么需要在一个启动文件中一起启动他们,创建一个名为move_base.launch的文件,内容如下:

    <launch>
      <master auto="start"/>
    
      <!-- Run the map server -->
      <node name="map_server" pkg="map_server" type="map_server" args="$(find my_map_package)/my_map.pgm my_map_resolution"/>
    
      <!--- Run AMCL -->
      <include file="$(find amcl)/examples/amcl_omni.launch" />
    
      <node pkg="move_base" type="move_base" respawn="false" name="move_base" output="screen">
        <rosparam file="$(find my_robot_name_2dnav)/costmap_common_params.yaml" command="load" ns="global_costmap" />
        <rosparam file="$(find my_robot_name_2dnav)/costmap_common_params.yaml" command="load" ns="local_costmap" />
        <rosparam file="$(find my_robot_name_2dnav)/local_costmap_params.yaml" command="load" />
        <rosparam file="$(find my_robot_name_2dnav)/global_costmap_params.yaml" command="load" />
        <rosparam file="$(find my_robot_name_2dnav)/base_local_planner_params.yaml" command="load" />
      </node>
    </launch>

    唯一需要修改的地方是更改地图服务器使指向你的已有的地图,并且,如果你有一台差分驱动的机器人,将"amcl_omni.launch"改为"amcl_diff.launch"。对于如何创建一张地图,请查阅 创建一张地图.

    AMCL 配置(amcl)

    AMCL有许多配置选项将影响定位的性能。 有关AMCL的更多信息请参阅amcl文档.

    运行导航功能包集

    现在我们配置结束,我们可以运行导航功能包了。为此我们需要在机器人上启动两个终端人。 在一个终端上,我们将启动 my_robot_configuration.launch 文件,在另一个终端上我们将启动我们刚刚创建的move_base.launch

    终端1:

    roslaunch my_robot_configuration.launch

    终端2:

    roslaunch move_base.launch

    祝贺你,导航功能包集现在应该运行了。关于如何通过图形化界面给导航功能包集发送一个目标信息,请参阅rviz和导航教程。 如果你想使用代码给导航功能包集发送导航目标,请参阅发送简单导航目标教程。

    故障排除

    关于运行导航功能包集时遇到的常见问题,请参阅导航功能包集故障排除页面。

    #keywords 移动平台配置,机器人配置,设置机器人,getting started with mobile robot

    ----在ROS上发布传感器数据----

    在ROS上发布传感器数据流

    在ROS上正确地发布从传感器获取的数据对导航功能包集的安全运行很重要。 如果导航功能包集无法从机器人的传感器接收到任何信息,那么它就会盲目行事,最有可能的是发生碰撞。 有许多传感器可用于为导航功能包集提供信息:激光、摄像头、声纳、红外线、碰撞传感器等等。然而,目前导航功能包集只接受使用sensor_msgs/LaserScansensor_msgs/PointCloud消息类型发布的传感器数据。下面的教程将提供典型的设置和使用这两种类型的消息的例子。 相关: TF配置

    ROS消息头

    消息类型 sensor_msgs/LaserScan和 sensor_msgs/PointCloud跟其他的消息一样,包括tf帧和与时间相关的信息。为了标准化发送这些信息,消息类型Header被用于所有此类消息的一个字段。

    类型Header包括是哪个字段。字段seq对应一个标识符,随着消息被发布,它会自动增加。字段stamp存储与数据相关联的时间信息。以激光扫描为例,stamp可能对应每次扫描开始的时间。字段frame_id存储与数据相关联的tf帧信息。以激光扫描为例,它将是激光数据所在帧。

    #Standard metadata for higher-level flow data types
    #sequence ID: consecutively increasing ID 
    uint32 seq
    #Two-integer timestamp that is expressed as:
    # * stamp.secs: seconds (stamp_secs) since epoch
    # * stamp.nsecs: nanoseconds since stamp_secs
    # time-handling sugar is provided by the client library
    time stamp
    #Frame this data is associated with
    # 0: no frame
    # 1: global frame
    string frame_id

    在ROS上发布LaserScans

    LaserScan消息

    对于机器人的激光扫描仪,ROS提供了一个特殊的消息类型LaserScan来存储激光信息,它位于包sensor_msgsLaserScan消息方便代码来处理任何激光,只要从扫描仪获取的数据可以格式化为这种类型的消息。我们谈论如何生成和发布这些消息之前,让我们来看看消息本身的规范:

    #
    # Laser scans angles are measured counter clockwise, with 0 facing forward
    # (along the x-axis) of the device frame
    #
    
    Header header
    float32 angle_min        # start angle of the scan [rad]
    float32 angle_max        # end angle of the scan [rad]
    float32 angle_increment  # angular distance between measurements [rad]
    float32 time_increment   # time between measurements [seconds]
    float32 scan_time        # time between scans [seconds]
    float32 range_min        # minimum range value [m]
    float32 range_max        # maximum range value [m]
    float32[] ranges         # range data [m] (Note: values < range_min or > range_max should be discarded)
    float32[] intensities    # intensity data [device-specific units]

    正如所期望的,上面的名字/注释明确表述了消息里的各个字段。为了更具体的说明,我们来写一个简单的激光数据发布器来展示他们是如何工作的。

    编写代码发布一个LaserScan消息

    在ROS上发布一个LaserScan消息是相当简单的。我们先提供下面的示例代码,然后将代码分解逐行。

    切换行号显示
       1 #include <ros/ros.h>
       2 #include <sensor_msgs/LaserScan.h>
       3 
       4 int main(int argc, char** argv){
       5   ros::init(argc, argv, "laser_scan_publisher");
       6 
       7   ros::NodeHandle n;
       8   ros::Publisher scan_pub = n.advertise<sensor_msgs::LaserScan>("scan", 50);
       9 
      10   unsigned int num_readings = 100;
      11   double laser_frequency = 40;
      12   double ranges[num_readings];
      13   double intensities[num_readings];
      14 
      15   int count = 0;
      16   ros::Rate r(1.0);
      17   while(n.ok()){
      18     //generate some fake data for our laser scan
      19     for(unsigned int i = 0; i < num_readings; ++i){
      20       ranges[i] = count;
      21       intensities[i] = 100 + count;
      22     }
      23     ros::Time scan_time = ros::Time::now();
      24 
      25     //populate the LaserScan message
      26     sensor_msgs::LaserScan scan;
      27     scan.header.stamp = scan_time;
      28     scan.header.frame_id = "laser_frame";
      29     scan.angle_min = -1.57;
      30     scan.angle_max = 1.57;
      31     scan.angle_increment = 3.14 / num_readings;
      32     scan.time_increment = (1 / laser_frequency) / (num_readings);
      33     scan.range_min = 0.0;
      34     scan.range_max = 100.0;
      35 
      36     scan.ranges.resize(num_readings);
      37     scan.intensities.resize(num_readings);
      38     for(unsigned int i = 0; i < num_readings; ++i){
      39       scan.ranges[i] = ranges[i];
      40       scan.intensities[i] = intensities[i];
      41     }
      42 
      43     scan_pub.publish(scan);
      44     ++count;
      45     r.sleep();
      46   }
      47 }
    

    现在我们将上面的代码一步一步分解。

    切换行号显示
       2 #include <sensor_msgs/LaserScan.h>
       3 
    

    这里,我们include我们想要发布的sensor_msgs/LaserScan消息。

    切换行号显示
       8   ros::Publisher scan_pub = n.advertise<sensor_msgs::LaserScan>("scan", 50);
    

    段代码创建了一个ros::Publisher用于使用ROS发送 LaserScan 消息。

    切换行号显示
      10   unsigned int num_readings = 100;
      11   double laser_frequency = 40;
      12   double ranges[num_readings];
      13   double intensities[num_readings];
    

    里我们创建一组存储虚拟数据的变量,用来模拟激光扫描(其中填充所扫描到的障碍物信息)。实际的程序应从他们的激光驱动程序获取这些数据。

    切换行号显示
      18     //generate some fake data for our laser scan
      19     for(unsigned int i = 0; i < num_readings; ++i){
      20       ranges[i] = count;
      21       intensities[i] = 100 + count;
      22     }
      23     ros::Time scan_time = ros::Time::now();
    

    填充激光数据,填充值每秒加1.

    切换行号显示
      25     //populate the LaserScan message
      26     sensor_msgs::LaserScan scan;
      27     scan.header.stamp = scan_time;
      28     scan.header.frame_id = "laser_frame";
      29     scan.angle_min = -1.57;
      30     scan.angle_max = 1.57;
      31     scan.angle_increment = 3.14 / num_readings;
      32     scan.time_increment = (1 / laser_frequency) / (num_readings);
      33     scan.range_min = 0.0;
      34     scan.range_max = 100.0;
      35 
      36     scan.ranges.resize(num_readings);
      37     scan.intensities.resize(num_readings);
      38     for(unsigned int i = 0; i < num_readings; ++i){
      39       scan.ranges[i] = ranges[i];
      40       scan.intensities[i] = intensities[i];
      41     }
    

    创建一个 scan_msgs::LaserScan 消息,并使用我们预先生成的数据填充,准备发布它。

    切换行号显示
      43     scan_pub.publish(scan);
    

    在ROS上发布这个消息。

    在ROS上发布点云 PointClouds

    点云消息

    为了存储与分享世界中的一系列点, ROS 提供了 sensor_msgs/PointCloud 消息。正如前文所述,这个消息支持将三维空间中的点的数组以及任何保存在一个信道中的相关数据。 例如,一条带有"intensity"信道的 PointCloud 可以保持点云数据中每一个点的强度。接下来我们使用ROS发布一个 PointCloud 来探索这个过程。

    #This message holds a collection of 3d points, plus optional additional information about each point.
    #Each Point32 should be interpreted as a 3d point in the frame given in the header
    
    Header header
    geometry_msgs/Point32[] points  #Array of 3d points
    ChannelFloat32[] channels       #Each channel should have the same number of elements as points array, and the data in each channel should correspond 1:1 with each point

    编写代码发布 PointCloud 消息

    使用ROS发布 PointCloud 相当简单.接下来,我们给出一个完整的例子,并详细的讨论他的每一个细节.

    切换行号显示
       1 #include <ros/ros.h>
       2 #include <sensor_msgs/PointCloud.h>
       3 
       4 int main(int argc, char** argv){
       5   ros::init(argc, argv, "point_cloud_publisher");
       6 
       7   ros::NodeHandle n;
       8   ros::Publisher cloud_pub = n.advertise<sensor_msgs::PointCloud>("cloud", 50);
       9 
      10   unsigned int num_points = 100;
      11 
      12   int count = 0;
      13   ros::Rate r(1.0);
      14   while(n.ok()){
      15     sensor_msgs::PointCloud cloud;
      16     cloud.header.stamp = ros::Time::now();
      17     cloud.header.frame_id = "sensor_frame";
      18 
      19     cloud.points.resize(num_points);
      20 
      21     //we'll also add an intensity channel to the cloud
      22     cloud.channels.resize(1);
      23     cloud.channels[0].name = "intensities";
      24     cloud.channels[0].values.resize(num_points);
      25 
      26     //generate some fake data for our point cloud
      27     for(unsigned int i = 0; i < num_points; ++i){
      28       cloud.points[i].x = 1 + count;
      29       cloud.points[i].y = 2 + count;
      30       cloud.points[i].z = 3 + count;
      31       cloud.channels[0].values[i] = 100 + count;
      32     }
      33 
      34     cloud_pub.publish(cloud);
      35     ++count;
      36     r.sleep();
      37   }
      38 }
    

    下来,我们一句一句来看.

    切换行号显示
       2 #include <sensor_msgs/PointCloud.h>
       3 
    

    包含 sensor_msgs/PointCloud 消息头文件.

    切换行号显示
       8   ros::Publisher cloud_pub = n.advertise<sensor_msgs::PointCloud>("cloud", 50);
    

    创建我们用来发布Creat PointCloud 消息的 ros::Publisher .

    切换行号显示
      15     sensor_msgs::PointCloud cloud;
      16     cloud.header.stamp = ros::Time::now();
      17     cloud.header.frame_id = "sensor_frame";
    

    填充 PointCloud 消息的头:frame 和 timestamp.

    切换行号显示
      19     cloud.points.resize(num_points);
    

    设置点云的数量.

    切换行号显示
      21     //we'll also add an intensity channel to the cloud
      22     cloud.channels.resize(1);
      23     cloud.channels[0].name = "intensities";
      24     cloud.channels[0].values.resize(num_points);
    

    增加信道 "intensity" 并设置其大小,使与点云数量相匹配.

    切换行号显示
      26     //generate some fake data for our point cloud
      27     for(unsigned int i = 0; i < num_points; ++i){
      28       cloud.points[i].x = 1 + count;
      29       cloud.points[i].y = 2 + count;
      30       cloud.points[i].z = 3 + count;
      31       cloud.channels[0].values[i] = 100 + count;
      32     }
    

    使用虚拟数据填充 PointCloud 消息.同时,使用虚拟数据填充 intensity 信道.

    切换行号显示
      34     cloud_pub.publish(cloud);
    

    使用 ROS 发布 PointCloud 消息.

    ----附----

    概述

    导航堆栈在概念级别上是相当简单的。它接收来自里程计和传感器流的信息,并输出速度命令以发送到移动基站。但是,在任意机器人上使用导航堆栈有点复杂。作为导航堆栈使用的先决条件,机器人必须运行ROS,具有tf变换树,并使用正确的ROS消息类型发布传感器数据此外,导航堆栈需要被配置为机器人的形状和动力学在高水平执行。为了帮助这个过程,本手册旨在作为典型的导航堆栈设置和配置的指南。


    硬件要求

    虽然导航堆栈设计为尽可能通用,但有三个主要硬件要求限制其使用:

    1. 它仅适用于差速驱动和完整轮式机器人。它假定移动基站通过发送期望的速度命令来控制,以便以下列形式实现:x速度,y速度,θ速度。
    2. 它需要安装在移动基座上某处的平面激光器。该激光器用于地图构建和本地化。
    3. 导航堆栈是在方形机器人上开发的,因此其性能将是最接近正方形或圆形的机器人。它在任意形状和尺寸的机器人上工作,但是在狭窄空间(如门口)中的大型矩形机器人可能有困难。

    文档

    以下文档假定熟悉机器人操作系统。关于ROS的文档可以在这里找到:ROS文档

    报告错误

    例子

    使用导航堆栈的机器人列表

    教程

    1. 在模拟中导航

      这个pagge描述真棒模拟

    2. 用真正的机器人导航

      本页描述了真实机器人的导航

    3. 为TurtleBot设置导航堆栈

      提供了机器人导航配置的第一瞥,参考其他更全面的教程。

    4. 为TurtleBot设置导航堆栈

      提供了机器人导航配置的第一瞥,参考其他更全面的教程。

    5. 赫斯基移动基地演示

      使用基本move_base设置运行Husky ,没有映射或本地化。

    6. 探索周边地区并制作地图

      从机器人的视觉探索真实的环境,并保存地图。

    7. 使用已知地图导航

      在已知区域中使用先前保存的地图来淹没

    8. 赫斯基AMCL演示

      使用move_base设置运行Husky ,使用amcl进行本地化。

    9. 赫斯基Gmapping演示

      使用move_base设置运行Husky ,使用gmapping映射和本地化(SLAM)。

    10. 赫斯基边境勘探演示

      使用move_base设置运行Husky ,使用frontier_exploration进行勘探规划,gmapping用于映射和本地化(SLAM)。

    11. 探索周边地区并制作地图

      从机器人的视野探索环境,并保存地图。

    12. 使用已知地图导航

      在已知区域中使用先前保存的地图来淹没。

    13. SLAM地图大厦与TurtleBot

      如何使用gmapping生成地图

    14. 使用TurtleBot自动导航已知地图

      本教程介绍如何使用TurtleBot与以前已知的地图。

    15. SLAM地图大厦与TurtleBot

      如何使用gmapping生成地图

    16. 使用TurtleBot自动导航已知地图

      本教程介绍如何使用TurtleBot与以前已知的地图。

    17. 使用tf设置您的机器人

      本教程提供了设置您的机器人开始使用tf的指南。

    18. Evarobot的导航在凉亭的

      如何使用以前已知的地图导航在凉亭的evarobot。

    19. 基本导航调整指南

      本指南旨在给出一些关于如何调整机器人上ROS导航堆栈的标准建议本指南并不全面,但应该给出一些洞察过程。我也鼓励人们,确保他们已经阅读ROS导航教程之前,这篇文章,因为它提供了一个很好的概述设置导航堆栈在机器人wheras本指南只是提供建议的过程。

    20. 编写全局路径规划器作为ROS中的插件

      在本教程中,我将介绍在ROS中编写和使用全局路径规划器的步骤。首先要知道的是,为ROS添加一个新的全局路径规划器,新的路径规划器必须遵守nav_core包中定义nav_core :: BaseGlobalPlanner C ++接口一旦编写了全局路径规划器,它必须作为插件添加到ROS中,以便它可以被move_base包使用。在本教程中,我将提供从编写路径规划器类开始直到将其部署为插件的所有步骤。我将使用Turtlebot作为机器人的一个例子来部署新的路径规划器。有关如何将真实GA计划程序集成为ROS插件的教程,请参阅在ROS中添加遗传算法全局路径规划器作为插件

    21. 无题

      没有说明

    22. 无题

      没有说明

    23. 机器人上的导航堆栈的设置和配置

      本教程提供了如何获取在机器人上运行的导航堆栈的分步说明。涵盖的主题包括:使用tf发送转换,发布里程计信息,通过ROS从激光器发布传感器数据,以及基本导航堆栈配置。

    24. Gazebo'da Evarobot Navigasyonu

      ÇıkartılmışharitaüzerindenGazebo'da otonom Evarobot navigasyonu。

    25. Bilinen Bir Haritada Otonom Evarobot Navigasyonu

      Dahaöncedençıkartılmışharitada otonom robot navigasyonu。

    26. 在导航堆栈中使用rviz

      本教程提供了使用rviz与导航堆栈初始化本地化系统,发送目标到机器人,以及查看导航堆栈通过ROS发布的许多可视化的指南。

    27. 通过ROS发布Odometry信息

      本教程提供了一个为导航堆栈发布里程测量信息的示例。它包括通过ROS发布nav_msgs / Odometry消息,以及通过tf从“odom”坐标框架到“base_link”坐标框架的转换。

    28. 在ROS上发布传感器流

      本教程提供了通过ROS 发送两种类型的传感器流(sensor_msgs / LaserScan消息和sensor_msgs / PointCloud消息)的示例

    29. 将目标发送到导航堆栈

      导航堆栈用于将移动基座从一个位置驱动到另一个位置,同时安全地避开障碍物。通常,机器人被赋予使用诸如rviz的预先存在的工具结合地图移动到目标位置的任务。例如,为了告诉机器人去特定的办公室,用户可以在地图中点击办公室的位置,并且机器人将试图去那里。然而,能够发送机器人目标以使用代码移动到特定位置也是重要的,很像rviz在引擎盖下。例如,用于插入机器人的代码可以首先检测插座,然后告诉机器人驱动到距离墙壁一英尺的位置,然后尝试使用臂将插头插入插座。本教程的目标是提供一个从用户代码发送导航堆栈简单目标的示例。

    30. 安装

      安装和编译此软件包的说明

    31. Evarobot探索

      如何使用frontier_exploration自动生成SLAM映射与Evarobot

    32. 使用Evarobot自动导航已知地图

      如何自主导航Evarobot与已知地图。



    机器人使用ROS

    下面机器人的硬件平台使用,或者可以与ROS软件一起使用。为了你的机器人添加到该页面,您可以按照该指令

    门户

    内嵌图片:0x-Alpha-Tracked_288.jpg
    0X阿尔法NEX由机器人
    内嵌图片:0x-Delta-4WD_340.jpg
    0X三角洲NEX机器人
    210士丹利创新V3赛格威
    220士丹利创新V3赛格威
    内嵌图片:innok-heros-223-thumb.jpg
    223 Innok英雄
    内嵌图片:innok-heros-224-thumb.jpg
    224 Innok英雄
    420全士丹利创新V3赛格威
    440LE士丹利创新V3赛格威
    440SE士丹利创新V3赛格威
    内嵌图片:innok-heros-444-thumb.jpg
    444 Innok英雄
    ABB机器人(ROS-实业)
    擅长MobileRobots公司先锋系列(P3DX,P3AT,...)
    擅长MobileRobots公司先锋LX
    擅长MobileRobots公司的Seekur家庭(的Seekur,小的Seekur)
    淖艾尔帕兰
    快板手SIMLAB
    朋友
    内嵌图片:AR10.jpg
    AR10机器人手
    AscTec旋翼
    内嵌图片:x-terrabot.jpg
    阿西莫夫机器人的X Terrabot
    内嵌图片:aubo_robot/aubo_robotics.jpg
    AUBO机器人
    巴雷特手
    内嵌图片:BIG-i.jpg
    BIG-I
    BipedRobin
    内嵌图片:crazyflize20_small.png
    Bitcraze Crazyflie
    内嵌图片:bluerov-small.jpg
    蓝机器人BlueROV
    的ClearPath机器人灰熊
    的ClearPath机器人赫斯基
    的ClearPath机器人豺狼
    的ClearPath机器人翠鸟
    内嵌图片:Ridgeback+Icon+-+Small.png
    的ClearPath机器人背脊犬
    的ClearPath机器人疣猪
    内嵌图片:hamster_img.ong
    Cogniteam仓鼠
    司空见惯的机器人操作平台
    司空见惯的机器人移动器
    司空见惯的机器人SRA服务机器人手臂
    CoroWare Corobot
    Cyton伽马
    Dataspeed ADAS发展汽车
    Dataspeed移动基地
    电装VS060
    机器人捷豹博士
    Eddiebot
    易诺华机器人彩扩
    厄尔 - 脑
    尔勒脑2
    厄尔 - 直升机
    厄尔 - 直升机Ubuntu的核心特别版
    厄尔 - HexaCopter
    厄尔平面
    厄尔 - 罗孚
    厄尔 - 蜘蛛
    内嵌图片:evarobot50.png
    evarobot
    发那科机器人(ROS-实业)
    费斯托说教Robotino
    取机器人:取
    取机器人:货运
    弗劳恩霍夫IPA护理-O-BOT 3
    弗劳恩霍夫IPA护理-O-BOT 4
    吕(开放源码的类人型机器人)
    Gosti爵士
    内嵌图片:GoThere.png
    去那里!机器人
    内嵌图片:hansagv.png
    韩寒的机器人
    内嵌图片:icart_mini.png
    I-车迷你
    Ingeniarius ForteRC
    内嵌图片:innok-heros-thumb.jpg
    Innok英雄
    英特尔爱迪生
    iRobot公司的Roomba
    川田NEXTAGE /浩
    JACI版本
    版本图标
    Kobuki
    乐高NXT
    劣质煤
    内嵌图片:mecanumbot-small.jpg
    Mecanumbot
    梅林miabotPro
    内嵌图片:milvus_atr.png
    鸢属ATR机器人
    内嵌图片:milvus_mrp2.png
    鸢属机器人MRP2
    内嵌图片:milvus_robin.png
    鸢属机器人罗宾
    内嵌图片:motoman/robots_icon.jpg
    莫托曼,安川(ROS-实业)
    NAV2
    内嵌图片:navio2.png
    Navio2
    Neobotix MP-500
    Neobotix和珍贵-500
    Neobotix和珍贵-700
    开放式机组机器人
    奥托博克速度感应手
    PAL机器人PMB-2
    PAL机器人REEM-C
    PAL机器人蒂亚戈
    RazBot
    REEM
    Robonaut 2
    RoboSavvy自平衡平台
    内嵌图片:Armadillo.jpg
    RoboTiCan犰狳
    RoboTiCan科莫多
    RoboTiCan Lizi
    内嵌图片:robotis_manipulator_h.png
    ROBOTIS手-H
    内嵌图片:robotis_thormang3.png
    ROBOTIS Thormang3
    内嵌图片:robotnik_agvs_small.png
    Robotnik AGVS
    内嵌图片:CROM_WIKI_ROS.png
    Robotnik CROM
    内嵌图片:robotnik_guardian_small.png
    Robotnik GUARDIAN
    内嵌图片:robotnik_rb1_small.png
    Robotnik RB-1
    内嵌图片:RB-1_BASE_WIKI_ROS.png
    Robotnik RB-1 BASE
    内嵌图片:robotnik_rbcar_small.png
    工人RBCAR
    内嵌图片:robotnik_summit_xl_small.png
    Robotnik SUMMIT XL
    内嵌图片:robotnik_summit_x_small.png
    Robotnik SUMMIT-X
    内嵌图片:Roch.png
    罗奇
    ROS-工业
    内嵌图片:ros2bot.jpg
    Ros2Bot
    影手
    内嵌图片:tally.jpg
    Simbe机器人理货
    软银辣椒
    内嵌图片:Spark.png
    火花
    郁金香
    TurtleBot
    内嵌图片:universal_robot/robots_icon.jpg
    通用机器人(ROS-实业)
    而且不稳定
    WheeledRobin
    Willow Garage的PR2
    Xaxxon魔环总理
    内嵌图片:xbot.png
    Xbot


    src="http://player.vimeo.com/video/146183080" width="480" height="270" frameborder="0" allowfullscreen="" style="box-sizing: border-box;">

    还有的ROS.org博客了一系列利用ROS机器人

    完整列表

    移动机械臂

    自定义移动机械臂

    移动机器人

    定制的移动机器人

    机械手

    自动驾驶汽车

    社交机器人

    人形

    无人机

    水下机器人

    UWVs

    其他


    ----End----

    展开全文
  • CSS 获取浏览器尺寸

    千次阅读 2012-07-23 11:58:49
    现在用宽屏显示器的越来越,需要将ubbshow.js中的图像重绘代码作些修改,使图像大小随屏幕大小及不同的css风格自动调整。 该代码运行的前提是先为headerbg样式指定个ID名,也就是把header.asp中的class='...
    在以前的代码中, 对贴子中超过746像素宽的图像自动调整为746大小。现在用宽屏显示器的越来越多,需要将ubbshow.js中的图像重绘代码作些修改,使图像大小随屏幕大小及不同的css风格自动调整。

    该代码运行的前提是先为headerbg样式指定个ID名,也就是把header.asp中的class='headerbg'之前加上一句id='headerbg', 这样后面的代码就能访问这个headerbg对象了.

    实现该功能需考虑两个问题, 一个是获取当前屏幕大小,另一个问题是确认当前采用的什么风格文件, 因为即使屏幕为1024px宽,而风格采用的是极速, 图片也不能放大到1024宽,那样就会撑破极速的窗口。

    实现的思路如下,为header.asp文件中的headerbg样式指定个ID,然后在js中读取该ID样式中的width值,如width值为具体数字,则直接采用该宽度,如为100%,则取当前屏幕宽度。需要注意的是,读取的值是字符串,使用前应用parseInt函数将其变为整数。

    需要用到的有关函数或方法:
    parseInt(); 将括号中的内容转换为整数型.
    getElementById() 获取ID号对象
    .currentStyle.width 对象属性值
    getElementById("对象ID名").currentStyle.width即为对象的当前样式中的宽度值.
    关于屏幕控制的有关属性:网页可见区域宽:document.body.clientWidth
    网页可见区域高:document.body.clientHeight
    网页可见区域宽:document.body.offsetWidth (包括边线的宽)
    网页可见区域高:document.body.offsetHeight (包括边线的宽)
    网页正文全文宽:document.body.scrollWidth
    网页正文全文高:document.body.scrollHeight
    网页被卷去的高:document.body.scrollTop
    网页被卷去的左:document.body.scrollLeft
    网页正文部分上:window.screenTop
    网页正文部分左:window.screenLeft
    屏幕分辨率的高:window.screen.height
    屏幕分辨率的宽:window.screen.width
    屏幕可用工作区高度:window.screen.availHeight
    屏幕可用工作区宽度:window.screen.availWidth




    ubbshow.js中DrawImage过程代码如下:

    程序代码: [ 复制代码 ][ 运行代码 ]function DrawImage(ImgD){
    var swidth=document.getElementById("headerbg").currentStyle.width; //获取headerbg对象的宽度,此宽度就是lbbs头背景宽度,也就是lbbs的显示宽度
    swidth=parseInt(swidth);  //值转换为整型数字
    if(swidth==100) {swidth=document.body.clientWidth;} //对于定义宽度为100%的情况,如w_950.css, 直接取当前屏幕宽度
    swidth=swidth-40; //为图像留白,所以宽度减去一些. 可自行修改
    var image=new Image();
    image.src=ImgD.src;
    if(image.width>0 && image.height>0){
      flag=true;
      if(image.width>=swidth){
       ImgD.width=swidth;
       ImgD.height=(image.height*swidth)/image.width;
      }else{
       ImgD.width=image.width;
       ImgD.height=image.height;
      } 
    }

    img   { 


            height:expression(document.body.clientHeight-50);

    }

    展开全文
  • node-red教程 7dashboard简介与输入仪表板控件的使用

    万次阅读 多人点赞 2018-06-21 10:33:48
    前端技术主要负责界面呈现,与用户交互等等,很炫酷的特效都是前端呈现的。Node-red技术有这样的“基因”,界面当然不会差劲。它只需要一个控件就可以实现一个页面。由于本书讲述的应用与页面关系不大,就不讲HTTP...
  • 单火线电原理

    万次阅读 2019-06-10 14:10:25
    但是在实际的安装出现了问题,老式的开关方便父母,但是装上之后,我们的智能模块的供电问题是一个大问题,查阅了很资料 一些朋友不理解什么是单火线,也很奇怪一根火线怎么取得电压和电流。下面简要介绍。   ...
  • 逐次逼近ADC

    千次阅读 2016-06-07 16:53:00
    理解逐次逼近寄存器ADC:与其它类型ADC的架构对比 摘要:逐次逼近寄存器(SAR)模数转换器(ADC)占据着大部分的中等至高分辨率ADC市场。SAR ADC的采样速率最高可达5Msps,分辨率为8位至18位。SAR架构...
  • Android 8.0 功能和 API

    千次阅读 2017-09-25 12:50:10
    Android 8.0 为用户和开发者引入多种新功能。本文重点介绍面向开发者的新功能
  • Oracle数据库ASM功能详解

    千次阅读 2012-09-06 11:21:19
    1什么是ASM ASM全称为Automated Storage Management,即自动存储管理,它是自Oracle10g这个版本Oracle推出的新功能。这是Oracle提供的一个卷管理器,用于替代操作操作系统所提供的LVM,它不仅支持单实例配置,也...
  • 大型场景裁剪渲染

    千次阅读 2017-11-23 16:55:49
    此外,同一像素也可能采集来自个纹理的值,如通道纹理映射和单通道多重纹理映射。所有这些颜色值将根据各自的不透明度融合出最终结果。颜色融合不仅能加强场景真实感,还能产生半透明绘制、景深、基于alpha缓冲...
  • 易语言 自定义数据类型的大小

    千次阅读 2016-09-10 01:37:45
    逻辑值他们都是储存在栈上的 大小都是固定的 用不着复合类型一般储存在堆上 然后在栈上储存堆上数据的引用其中有几种比较特殊:字符串 字节集 数组这两种类型的大小虽然储存在堆上的某个地方但都有专门的大小...
  • 浅谈大比例数字地形图的缩编方法来自:http://www.digitalgx.com/article/zh/zh67.htm陈世梅 王海英 (南宁市勘测院 广西南宁 530021)【摘 要】 为了更好的满足城市规划和建设的需要,利用现有的大比例数字...
  • (注一寸相:如护照,签证申请等,以级学位证书采用的是大一寸,48毫米×33毫米。而身份证,体检表,等采用小一寸32毫米×22毫米,第二代身份 证 (26mm×32mm),普通一寸相则25mm×35mm。护照旅行证件的相片标准...
  • Java 11中的新功能和API详解系列1

    千次阅读 2018-09-27 17:33:08
    Java 11中的新功能和API详解系列1 2018.9.27 版权声明:本文为博主chszs的原创文章,未经博主允许不得转载。 JDK 11在语言语法方面有一个小改动,增加了相当数量的新API,以及运行单文件应用程序而无需使用编译器...
  • 但是在实际系统开发中,经常会有上传图片或者文件的功能,这些文件可能尺寸会很大,我们可以借用Gridfs来辅助实现这些文件的管理.GridFS会将大文件对象分割成个小的chunk(文件片段),一般为256k/个,每个chunk将作为...
  • 然而,一旦客户端需要执行DNS lookup时,等待时间将会决于域名服务器的有效响应的速度。 虽然所有的ISP的DNS服务器都能缓存域名和IP地址映射表,但如果缓存的DNS记录过期了而需要更新,则可能需要通过遍历个...
  • android 8.0新功能

    千次阅读 2017-08-24 17:08:23
    功能可减小 APK 大小,提高应用安装成功率,使个应用可以共享同一种字体。 如需了解有关下载字体的详细信息,请参阅  可下载字体 。 XML 中的字体 Android 8.0 推出一项新功能,即 XML 中的字体,允许...
  • 18.2 增强图元文件

    千次阅读 2015-12-27 20:40:32
     “增强图元文件”格式是在 32 位的 Windows 中才引入的。它涉及一系列新的函数、几个新的数据结构、新的数据结构、新的剪贴板格式和新的文件扩展名 .EMF。  最重要的改进是新的图元文件格式包含了可以通过函数...
  • 智慧停车场系统采用纯车牌自动识别技术、视频停车诱导技术及移动互联网APP停车应用技术,同时支持多种缴费场景和支付方式的全新智能化停车场系统。 系统能准确识别进出车辆的车牌号码,并以车辆的车牌号码作为...
  • mySQL (关系数据库管理系统)

    千次阅读 2017-03-13 08:32:00
    MySQL是一个关系数据库管理系统,由瑞典MySQL AB 公司开发,目前属于Oracle旗下产品。MySQL 是最流行的关系数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System...
  • mySQL(关系数据库管理系统)编辑

    万次阅读 2014-11-05 18:35:24
    收藏 2906 1034 ...mySQL(关系数据库管理系统)编辑 ...MySQL是最流行的关系数据库管理系统,在WEB应用方面MySQL是最好的RDBMS(Relational Database Management System:关系数据库管理系统)应用
  • 菜鸟学习bootstrap常用功能

    万次阅读 2014-10-24 17:01:08
    甚至引用“消费资本化”之类令人模糊的概念,令你迷失在投资与消费之间,这样给你在法律上的维权带来很困难(注:消费行为与投资经营行为是两个不同的法律概念, 适用法律分别是:《消费者权益保护法》和《合同法...
  • 大型web系统数据缓存设计

    千次阅读 2016-09-18 09:21:52
    每个系统在初期规划的时候,都会大致计算一下所要消耗的缓存空间,这主要决于你要缓存的对象数量和单个对象的大小。一般来说,你可以采用对象属性在内存中的存储长度简单加和的方法来计算单个对象的体积,再乘以...
  • ZM-ⅢC智能脉象仪

    千次阅读 2010-07-05 19:22:00
    (现在的新电脑基本都没有以前的串口)ZM-ⅢC智能脉象仪由单头脉象换能器、脉象放大器、A/D转换卡、计算机和脉象辩证分析软件等部分组成。能自动采集脉象信号,并将中医脉象的位、数、形、势和脉图的各项特征参数...
  • 一个完整直播app功能分析

    万次阅读 2016-09-28 15:04:29
    1.一个完整直播app功能 1、聊天 私聊、聊天室、点亮、推送、黑名单等; 2、礼物 普通礼物、豪华礼物、红包、排行榜、第三方充值、内购、礼物动态更新、提现等; 3、直播列表 关注、热门、最新、...
  • ROS 学习篇(八)机器人的导航功能--navigation

    万次阅读 多人点赞 2018-11-04 21:32:04
    ROS 学习篇(八)机器人人的导航功能--navigation 1. 概述 ROS的二维导航功能包,简单来说,就是根据输入的里程计等传感器的信息流和机器人的全局位置,通过导航算法,计算得出安全可靠的机器人速度控制指令。...
  • IOS面试题目(稍微深度)

    千次阅读 2017-09-06 10:34:01
    线程的环境中,注意使用@synchronized关键字或GCD,确保静态实例被正确的创建和初始化(防止线程抢夺资源) 代码: // 用来保存唯一的单例对象 static id _instace; (id)allocWithZone:(struct _...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 32,601
精华内容 13,040
关键字:

多功能取型尺