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

前言

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

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

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

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

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

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

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

switch=function()
    if ... then
        return ...
    end
end,

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

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

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

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

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

先上代码:

gPlayTable.CreatePlay{

firstState="getball",

["getball"]={
	switch=function()
		if CIsGetBall("Kicker") then
			return "shoot"
		end
	end,

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

["shoot"]={
	switch=function()
		if CIsBallKick("Kicker") then
			return "finish"
		end
	end,

	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"
		end
	end,
	Kicker=task.GoRecePos("Kicker"),
	Receiver=task.GetBall("Receiver","Receiver"),
	Goalie=task.Goalie()
},

["PassBall"]={
	switch=function()
		if CIsBallKick("Receiver") then
			return "Shoot"
		end
	end,
	Kicker=task.GoRecePos("Kicker"),
	Receiver=task.PassBall("Receiver","Kicker"),
	Goalie=task.Goalie()
},

["Shoot"]={
	switch=function()
		if CIsBallKick("Kicker") then
			return "finish"
		end
	end,
	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.edu
    School 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, visit
    www.gnu.org, or write: Free Software Foundation, 59 Temple Place,
    Suite 330 Boston, MA 02111-1307 USA.  This program is distributed
    in 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") then
		return "Shoot"
	end
end,

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

不妨也看看CIsBallKick函数的用法:
在这里插入图片描述
实际上,无论是Shoot还是PassBall都是把球踢出去,都可以用这个函数来判断

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

于是我们进入Shoot函数:

["Shoot"]={
	switch=function()
		if CIsBallKick("Kicker") then
			return "finish"
		end
	end,
	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

  • 26
    点赞
  • 120
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值