前言

先感谢亲爱的学长,没有他们,我一个人根本无法完成这篇博客

顿首,顿首,再顿首!!!

本篇博客属于实验记录,由于LUA脚本较为简单,所以本博客不多做深入探讨,基本上是把官方的用法更为详细地记录一下。本博客只涉及官方LUA,由于实验时间会持续好几天,所以本博客会动态更新,内部难免也会出现不少纰漏(毕竟是一边实验一边制作的)

另外,对于完全不懂得LUA代码的调试和使用的同学请看这篇博客(点我)!

以Shoot脚本为例浅谈状态跳转函数

状态跳转函数是LUA机器人编程的基础和精髓

下面我们就来看一看状态跳转函数的官方模板

switch=function()if ... thenreturn ...end
end,

本质上很简单,其它关键字都保持不变,我们要做的基本上就是把省略号的地方填充上我们所需要的东西即可,下面我来解释一下每个省略号的作用。

  • if后方的省略号实际上是跳转条件,只有满足这个条件才可以跳转,往往这个条件可以是机器人是否拿到了球?或者球是否踢出去?往往在判断这些的时候,官方都会提供相应的函数帮助你来完成这些复杂的判断,详见SOM二次开发手册后面的附录
  • return后方的省略号便是跳转的位置,我可以在return后面加上任意函数名,他就会跳转过去。

实际上,想要完成更加复杂的操作,依赖这种简单的模板是远远不够的,更加复杂的操作在后期会介绍

我们先来看一个简单的例子。

第一个实例:控制机器人射门

先上代码:

gPlayTable.CreatePlay{firstState="getball",["getball"]={switch=function()if CIsGetBall("Kicker") thenreturn "shoot"endend,Kicker=task.GetBall("Kicker","Kicker"),
},["shoot"]={switch=function()if CIsBallKick("Kicker") thenreturn "finish"endend,Kicker=task.Shoot("Kicker"),
},name="PassballAndShoot"
}

某种角度上来说,比开始的简单抓球脚本的确复杂了许多,但是我们把它拆开来看。

这个程序的目的在于,先操控机器人Kicker去抓球,如果Kicker抓到了球,那么开始射门。

为了实现先去抓球的目的,所以我们的firstState就被设置为getball,并且给Kicker分配任务为GetBall,此函数的具体用法可以参考开发手册。

一旦CIsGetBall(“Kicker”)成立,也就是Kicker成功抓到了球,那就跳转到Shoot函数,执行射门。如果球被Kicker顺利踢出,那么我们结束这个脚本。返回finish即可。

官方LUA射门脚本的漏洞分析

如果操控机器人踢足球这么简单,那么简直没有比赛的必要。

其实这种常规的进攻手段是完全无法应对比赛的纷繁局势的。

这个LUA脚本存在三个严重的漏洞,下面我一一描述:

球边旋转BUG

这个存在最严重的问题大概就是当足球在机器人侧面较近的位置(几乎贴上去但是又没有在机器人正面),机器人可以探测到但是会在球附近转圈圈而死活不射门。

球在机器人侧面,机器人却不去抓,在绕着球转圈圈。根据我们的LUA脚本,他会先去抓球,然后射门,而现在它连球都不去抓了。为了解决这种问题,我们只能不使用官方的函数,因为bug实在太恶心,我们选择自己写抓球脚本。这就关系到后面的C++知识,这里不再深入探讨。

直线带球BUG

这个BUG也相当恶心,只要你把足球放在机器人的屁股后面,它不会智能地朝向球,而是会屁股对着球跑过去抓球……当然这样能抓到就有鬼了,机器人平整的那一面是头部,有吸球装置,有弧度的位置都是身体,不具备吸球功能。

官方的函数会让机器人一直顶着球,而在这种情况下,**CIsGetBall(“Kicker”)**是永远不会成立的,因为你根本就没抓啊。所以就永远不会射门。机器人会自己把球顶出界,呵呵……

这个也需要自己写dll来解决,只需用C++语言让机器人对准球的方向即可。

战略问题:不懂绕行

这个射门战术简直是最愚蠢的战术了,它只知道敌方球门在哪里,但是不会检测敌人在哪里。所以只要它知道自己抓到了球,就会开始射门,所以……

对,他会直接把球往敌方脸上踢!

然后球碰到了敌方的机器人,就反而弹往我们的球门了,如果还进了球,对手直接得分。

PS:上一届的机器人足球的队员的守门脚本很强势,基本攻不破。但是官方的守门脚本可没这么强。

一场完整的PLAY

刚刚的脚本给大家演示了机器人Kicker是怎么实现抓球-射球功能,但是,一场真正意义上的一场比赛,是各个机器人相互配合完成的。

提示:前机器人足球队队长是3V3的模式,然而接下来的比赛可能是4V4的模式,这里先暂时采用3V3的形式

gPlayTable.CreatePlay{firstState="GetBall",["GetBall"]={switch=function()if CBall2RoleDist("Receiver")<30 then
--      if CIsGetBall("Receiver") then    return "PassBall"endend,Kicker=task.GoRecePos("Kicker"),Receiver=task.GetBall("Receiver","Receiver"),Goalie=task.Goalie()
},["PassBall"]={switch=function()if CIsBallKick("Receiver") thenreturn "Shoot"endend,Kicker=task.GoRecePos("Kicker"),Receiver=task.PassBall("Receiver","Kicker"),Goalie=task.Goalie()
},["Shoot"]={switch=function()if CIsBallKick("Kicker") thenreturn "finish"endend,Kicker=task.Shoot("Kicker"),Receiver=task.RefDef("Receiver"),Goalie=task.Goalie()
},name="PassAndShootReal"
}

以上代码来自SOM二次开发手册中的完整PLAY,我们将对其进行分析

上述代码的大意如下:

首先Receiver执行GetBall,然后Receciver拿到球之后,判断自己和球的距离是否小于三十厘米,这里有必要强调一下,根据官方提供的C++的constants.h库中对对场地各项参数的定义,我们可以知道机器人的头部长度为7.5cm,尾部长度为9cm

这样讲大家肯定不明所以,我来介绍一下传说中的constants.h库吧

constants.h 库的作用

以下是constants.h库中包含的所有文件,他们已经被集成在utils工具包中,你只需要在VS2013的环境中对此头文件引用即可

#include "util/constants.h"

constants,h库中拥有几乎全部的场地信息,包括机器人的信息,这些信息在大家日后写脚本的时候十分重要的参数。例如场地的长宽,机器人的大小,禁区的长宽等等……

一下是constants.h中包含的左右内容,比起其他库,这个算是少的。

 * TITLE:        constants.h** PURPOSE:      This is file contains the major system constants*               * WRITTEN BY:   Michael Bowling, James R Bruce, Brett Browning*/
/* LICENSE:=========================================================================CMDragons'02 RoboCup F180 Source Code Release-------------------------------------------------------------------------Copyright (C) 2002 Manuela Veloso, Brett Browning, Mike Bowling,James Bruce; {mmv, brettb, mhb, jbruce}@cs.cmu.eduSchool of Computer Science, Carnegie Mellon University-------------------------------------------------------------------------This software is distributed under the GNU General Public License,version 2.  If you do not have a copy of this licence, visitwww.gnu.org, or write: Free Software Foundation, 59 Temple Place,Suite 330 Boston, MA 02111-1307 USA.  This program is distributedin the hope that it will be useful, but WITHOUT ANY WARRANTY,including MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.------------------------------------------------------------------------- */
#ifndef __CONSTANTS_H__
#define __CONSTANTS_H__
#include "vector.h"
const int MAX_ROBOTS = 12;
const int MAX_TEAM_ROBOTS = 6;
const int MAX_ROBOT_SIZE = 9;
const double BALL_SIZE = 5;
const int SEGMENT_NUM = 4;
//==== Field Dimensions (cm) =========================================//
// diagonal is of 2800 x 2300 is 3623.53
const double FIELD_LENGTH = 605;
const double FIELD_WIDTH = 405;
const double FIELD_LENGTH_H = (FIELD_LENGTH / 2);
const double FIELD_WIDTH_H = (FIELD_WIDTH / 2);
const double GOAL_WIDTH = 70;
const double GOAL_DEPTH = 18;
const double DEFENSE_WIDTH = 205;
const double DEFENSE_DEPTH = 85;
const double WALL_WIDTH = 1;
const double PENALTY_BUFF = 4;
const double PENALTY_AREA_R = 80;
const double PENALTY_AREA_L = 35;
const double PENALTY_KICKER_L = 75;
const double GOAL_WIDTH_H = (GOAL_WIDTH / 2);
const double GOAL_DEPTH_H = (GOAL_DEPTH / 2);
const double DEFENSE_WIDTH_H = (DEFENSE_WIDTH / 2);
const double DEFENSE_DEPTH_H = (DEFENSE_DEPTH / 2);
const double CENTER_CIRCLE_RADIUS = 50;
const double PENALTY_BISECTOR = (GOAL_WIDTH / SEGMENT_NUM);
const double ROBOT_HEAD = 7.5;
namespace RuleParam {const double Stop_Dist = 50;const double Free_Kick_Away_Dist = 50;const double Goalie_Away_Goal_Dist = 15;
}
const double OUTER_PENALTY_AREA_R = PENALTY_AREA_R + MAX_ROBOT_SIZE * 4 + RuleParam::Free_Kick_Away_Dist + PENALTY_BUFF;
#endif

这里面的数据全部都是常量,我们需要的机器人数据位于28行的MAX_ROBOT_SIZE以及52行的ROBOT_HEAD,在这里我且暂时叫MAX_ROBOT_SIZE为机器人的尾部,ROBOT_HEAD为机器人头部。

温馨提示:在作者的IDE中,这些位置的确位于52和28行,大家那边视情况而定!

给一张漂亮的图:

以后大家要学会熟练地使用此库中的常量,常量的具体内容,有很多连作者自己也弄不清楚,所以一定要多做实验,才能得出结论!

回到我们的PLAY脚本分析

到了这里,你就会感慨官方脚本的沙雕之处

我们先来看GetBall函数中的那个判断:

这是CBall2RoleDist函数的定义,求当前球员(Receiver)到球的距离,实际上,距离小于30cm,不能认为Receiver已经抓到了球。因为30cm仍然远大于机器人的尺寸,所以当LUA脚本以60fps的速度执行的时候,Receiver就会朝着球爱的转圈圈,就是明明有过去,但是就是不抓球。


此时前锋一号机器人仍然在原地候球。

一个小问题——谁是谁?

我们在测试脚本的时候,由于机器人数量较少,所以往往就选择那么一个机器人,但是大家有没有想过,每一个机器人所承担的角色对应LUA脚本中的哪个角色呢?

Kicker=task.GoRecePos("Kicker"),
Receiver=task.GetBall("Receiver","Receiver"),
Goalie=task.Goalie()

这里一号是前锋(Kicker),二号是中场(Receiver),三号是守门员(Goalie)

如果机器人英文名和中文名不匹配,指令就不会发出!!!

我们可以在这个界面来给机器人分配序号和角色:

如果你LUA脚本里面有给receiver分配任务但是在这里没有选中场,而选择了别的职业,这个机器人将会一动不动!!!所以这里一定要选对的机器人!!!

先说一句,官方的译名也比较沙雕,我也一直在想为什么中场不是Middle

再一次回到我们的PLAY脚本分析

不过为什么一号机器人会等待接球?

是的,很笨的函数,去接球点接球……

守门员的函数最简单,直接执行守门。这里不提供用法,反正没有参数。

虽然像他这种CBall2RoleDist(“Receiver”)<30的神奇写法根本抓不到球,但是我们为了看接下去的代码,我们先当他抓到了球。

如果抓到了球,就执行PassBall函数,那么就Receiver执行传球给Kicker

Receiver=task.PassBall(“Receiver”,“Kicker”)

我们不妨也看一看PassBall函数的用法


在我们写的PassBall函数内部(注意区分是官方提供的PassBall还是我们写的PassBall状态函数!!!)得到分析到此也告一段落了。到此为止,实现了传球,所以完成传球后Kicker就要开始执行射门的任务了。

于是我们的状态跳转函数再次发挥了用场:

switch=function()if CIsBallKick("Receiver") thenreturn "Shoot"end
end,

这句话的意思就是如果Receiver把球踢出去了,那么Kicker就可以执行守门了。

不妨也看看CIsBallKick函数的用法:

实际上,无论是Shoot还是PassBall都是把球踢出去,都可以用这个函数来判断

如果真的把球踢出去了,那么万事大吉,我们可以进入射门阶段了

于是我们进入Shoot函数:

["Shoot"]={switch=function()if CIsBallKick("Kicker") thenreturn "finish"endend,Kicker=task.Shoot("Kicker"),Receiver=task.RefDef("Receiver"),Goalie=task.Goalie()
},

这里截下来的代码和上面是一致的,只是为了方便读者观看,不用跳上跳下

在这里的代码中,Kicker终于执行了Shoot任务,而不是在GoRecePos了。

然后你 似乎 就可以这么坐着等他射门了

依旧漏洞满满的官方脚本

我早就说过,这一切看似天衣无缝的代码,实际上充满着漏洞

且不说前面的“爱的转圈圈”,哪怕你运气好,或者你把官方的

if CBall2RoleDist("Receiver")<30 then

改成

if CIsGetBall("Receiver") then

看似稳如老狗,实则毫无卵用

哪怕他真的抓到了球,然后踢球给Kicker,此时就会出现一系列诡异现象:

Receiver把球踢出去之后,Receiver还会在再过去抢球!而Kicker会不断等待Receiver踢出球,才会执行射门,在此之前,它都一直守候在接球点。官方的GoRecePos说什么根据场上的逻辑获得,额,实际上它的逻辑的根据就是球的位置,所以球动了它也就动了。

比如我们测试这样一个脚本:

gPlayTable.CreatePlay{firstState="GoRecePosTest",["GoRecePosTest"]={Kicker=task.GoRecePos("Kicker"),
},name="GoRecePosTest"
}

然后当我们把球放在场地中央:

然后我们再换个位置:


啊,这就是所谓逻辑……

而你知道,球在传过来的时候,怎么可能不动嘛

所以在Receiver传球的时候,Kicker除了接球什么动作都会做,死活不射门

直到把球弄出界……

对官方脚本的改良

其实保证Kicker可以正确接到球的方法也不难,我就这个问题咨询过前队长,他直接告诉我去写一个 dll 搞定……额,他的意思就是不用官方的函数

但是我太菜了,还是想用官方的函数解决一些问题,就算我不菜,这里本来就是讲LUA,不讲C++,所以我也不提怎么用dll解决。

因此我们来认识一个新函数:

此函数没有返回值,就是一个动作,然后也是根据官方那个神仙逻辑……

Kicker=task.ReceiveBall("Kicker"),

我们只需要在传球的时候,球在跑的时候,别让Kicker四处乱动,就安安心心地接球,所以我们在PassBall函数中吧原本的去接球点的函数替换掉就好了。

为了保证传球成功率,我们再让Receiver拿球的时候对着Kicker,只需要在GetBall函数中用这个语句替换即可:

Receiver=task.GetBall("Receiver","Kicker"),

到此为止,官方脚本的射门终于可以基本正常进行了。

这里可以看见Kicker成功的射门了,但是这个魔性的Kicker又去追球,真是醉了。

测试模式调试LUA的局限性

这是我从来没有说到的话题,但是我很有必要说一下。

不知道大家有没有注意到这个函数,我一直都没有提到:

Kicker射门之后,我们的Receiver就在执行这个,这是一种防守函数,但是在测试模式里面的效果看起来跟没有一样,被执行者一动不动。我一直以为这是一个废的函数。

其实它有一定的作用,这个在官方源码里面有给予详细的说明。

if (role == "Kicker"){task.target_pos = opp_receive_player + Maths::vector2polar(RuleParam::Stop_Dist*2, opp_receive_goal);task.orientate = anglemod(opp_receive_goal + PI);}

但是在测试模式没有敌人的情况下,上面这段代码几乎是无效的。

在测试模式中,很多情况下和实际比赛有出入,所以我们会需要进行一场模拟比赛。

而且,经过阅读本篇博文,你会发现C++写的dll几乎渗透了LUA的每一个角落,实际上dll才是比赛的精髓所在,有关C++,本文不再继续阐述。

下一篇博文不再公开。会在适当时间后再公开。

有疑问者请加作者QQ:3351769279

SOMv3.3.3二次开发中LUA脚本对机基础操作指南相关推荐

  1. 关于objectArx /CAD二次开发中“属性块”操作

    关于objectArx /CAD二次开发中"属性块"操作 属性块就是在图块上附加一些文字属性(Attribute),这些文字可以非常方便地修改.属性块被广泛应用在工程设计和机械设计 ...

  2. python在abaqus二次开发_Python在ABAQUS二次开发中的应用实例2ppt

    PPT内容 这是Python在ABAQUS二次开发中的应用实例2ppt,包括了ABAQUS 脚本概述,Python 语言简介,ABAQUS脚本编写等内容,欢迎点击下载. 主要内容 一.ABAQUS 脚 ...

  3. 织梦php开发tags功能开发,织梦dedecms二次开发中几个标签的应用

    在织梦dedecms 里面对于数组进行循环的标签有好几个,在前台模板即使用静态模板引擎有,{dede:foreach array='数组名称'}[field:key/] [field:value/]{ ...

  4. lisp陡坎程序_(终稿)毕业论文设计_Autolisp在CAD二次开发中的应用.doc(最终版)最新版...

    <毕业论文:Autolisp在CAD二次开发中的应用.doc>由会员分享,可免费在线阅读全文,更多与<(终稿)毕业论文设计_Autolisp在CAD二次开发中的应用.doc(最终版) ...

  5. 「FastAdmin」fastadmin二次开发中如何自定义查询数据

    fastadmin二次开发中如何自定义查询数据 问题背景:最近做一个网站的过程中遇到了一个需求:对于不同用户组的用户,显示的数据要根据权限来筛选.问题看起来不是很难,文档和社区中已经给了足够的提示,我 ...

  6. Creo/Proe 二次开发中使用 QT 编程流程

    本文过期, 可以查看 http://blog.csdn.net/STPrinceT/article/details/71535694 或者 http://blog.csdn.net/stprincet ...

  7. 【2G模组Air202开发】Lua脚本编程实现MQTT协议连接Tlink平台(五)

    [2G模组Air202开发]Lua脚本编程实现MQTT协议连接Tlink平台(五) 整体思路: 在TLINK平台上创建一个MQTT协议的设备 对Air202模组进行lua编程并烧录 使用串口向Air2 ...

  8. Redis 中 Lua 脚本的应用和实践

    引言 前段时间组内有个投票的产品,上线前考虑欠缺,导致被刷票严重.后来,通过研究,发现可以通过 redis lua 脚本实现限流,这里将 redis lua 脚本相关的知识分享出来,讲的不到位的地方还 ...

  9. Bentley ORD(openroads designer) 二次开发(BIM)第二节 基础接口分享

    Bentley ORD(openroads designer) 二次开发(BIM)第二节 基础接口分享 CoderLPF 2021-01-25 07:37:54  89  已收藏 1 分类专栏: Be ...

最新文章

  1. ie9怎么开兼容模式
  2. C#多线程之旅(2)——详解线程的开始和创建
  3. 如何在修改了默认值之后跟新
  4. java计算加速减速_java – 使用JOCL / OPENCL计算强度的加速总和
  5. poj 2240 Arbitrage(bellman-ford spfa 判断正环)
  6. [vb]格式输出Format函数
  7. modal verbs(一)
  8. 190817每日一句
  9. logback 打印日志参考,包含异步打印日志及历史日志压缩
  10. 3D打印机改装雕刻机经验分享
  11. ‘’vr‘’全景抓鸡游戏总结
  12. html js手册chm,W3C Javascript CHM参考手册离线版
  13. msql--基础使用
  14. python 处理 图像和视频
  15. 测速工具使用心得体会
  16. 微服务化小团队:让 GitLab、Jenkins 与 Sonar 碰撞出火花
  17. excel表格怎么调整行高和列宽_Excel 表格技巧—一键调整行高列宽的方法
  18. 关于面试总结2-SQL学生表
  19. 硬件设计【1】——光耦的基本原理及TLP521使用
  20. 聊斋志异 - 善恶篇,诡异录,苍穹斗

热门文章

  1. java BigDecimal比较大小
  2. 庄子:当你一事无成,感到茫然无助时,读懂这几句话,会让你重新看待人生
  3. [UE5蓝图基础一]13.类似”人类一败涂地”掉落一定距离会回到空中 最终着落点还是设定地形上
  4. 恒生与中国信通院联合发布《证券行业分布式核心系统SRE运维白皮书》
  5. java 移动目录_java 移动文件夹内的文件,从一个目录移动到另外一个目录
  6. 自己计算机设置盘密码怎么操作,怎么给电脑盘设置密码
  7. 螣龙安科入侵感知:防火墙有哪些缺陷?
  8. 最好的生活方式:存钱,运动,读书,早起
  9. 基于DEM的GIS水文分析——河网与集水区域的提取
  10. [UVM]UVM TLM1.0 Interface归纳总结 --- 图解UVM TLM1.0 Interface