谈谈模拟足球游戏中人工智能
[转]
当球队进攻时,对于有球队员来说,它(暂且用它咯)在每一个瞬间都会有一个行为指导,也就是下一步行为:是向某方向带球?还是以某种方式传球给队友中的某一人?或者即是立即射门。这个指导行为从何迩来?又如何根据状况得以改变?不管情况怎样变化,使球员能在任何时刻都有一种较为合理的下一步行为为备,这样大概便可以模拟出踢足球的AI了。
其实足球AI模拟的关键就在于会让球员能较为聪明合理地分析球场上瞬息万变的赛况,并根据这个判断得出一个更为聪明合理的下一步行为。能力好的球员会不断的分析,从而迅速地调整它的行为而得以使比赛向更为有利的趋向发展,这个过程不断地持续,一场计算机AI模拟的高水平足球赛便可得以实现了。
第一个难度便是如何教会让AI球员判断场上的情况,如何产生AI分析,如何得出结论并生成下一步行为。
第一步是视觉判断:
带球球员能看到队友及对方的行为,从离它近的到离它远,无论在它前面或是在它侧面甚至是在它身后的,优秀的球员都能够感知。也就是模拟使AI球员“视野开阔”。这些信息可以很方便地从SIM比赛中球员的坐标,速度,动作数据得来,模拟起来并不算太难。
第二步是赛况分析:
也就是说使球员在视觉信息采集后产生一个意图,使比赛能更为向我方有利地发展。(比如:队友位置都不太好,无法穿球,AI使它得出了继续运球的意图;当它离球门距离不太远,有一定的射门空挡,AI便使它得出带球射门的意图;当有队友的跑位出现空挡时,AI便使它产生了向其分球射门的意图;当两侧队友跟进到位时,AI便使它产生向其穿球打下底穿中的意图)
这些都是第一步行为意图,是战术的雏形。
从这些极其离散的视觉采集信息(就是那些敌我双方球员坐标,速度,行为甚至能力)而产生一个战术意图看起来是非常玄妙而神秘的。殊不知,聪明的人类却能够从这些离散而无规的数据中能提炼出非常多隐藏在内的有用的比赛信息!而AI模拟的任务就要让COM球员学会找出这些隐藏的信息!
因此可以将导向式思考(利用经验规则导向)与推论式思考(用极大极小法推导出最佳行为)两种方式合理巧妙地结合起来。
另一个重要的因素便是将大量隐藏信息进行“过滤”,也就是说,AI需要找出最有价值(或者讲是合适)的隐藏信息,这里有随机因素,也有权排序。
最后一步便是根据所产生战术意图而形成一个下一步行为:
这很像是一个细节处理,因为下一步行为行为是随时在频繁地改变,而战术意图相对来说是较为稳定的。(毕竟近处看得多,远处观的少)实现下一步行为便是这个SIM足球的“动作引擎元素”,合理的动作元素可以使模拟比赛的可视性更为真实,更重要的是它能够与AI相互配合,这两者可以说是相辅相成缺一不可的(呵呵~哲学课考试我用的最多就是这句话:)FIFA的缺陷便在于它的动作引擎元素没有WE系列合理真实,这便直接导致了它的真实性不如WE,AI做的再棒也无济于事。
总结:AI球员通过观察赛况,找出离散数据的隐藏信息,得出了一个战术意图,做出了一个动作行为,这便就似完成了一个TURN。剩下的,SidMeier有一个很有用的经验思路:“我先教它如何玩一个回合开始。然后教它如何玩两个回合,再后来就该考虑教它如何玩10个回合了。”当然,SIM足球的AI也许会更复杂,因为我们只考虑了有球球员AI模拟的一个例子,但是我相信这3个部件是适用与足球场上每一个情况的,要做的只是更深入的分析,体会,感悟。
[以下为原创]
OK.我就以一个例子来说吧,这是一个模拟足球的全过程。包括loading,选队,
选难度,暂停等功能。
不过也由此看出flash在一个场景搞多个AI的CPU占用率会很高。当然这只是一个小小的尝试,
也是我看到一个德国的足球游戏受到的启发。
首先搞定一个球场,一个球员,和一个门。其它的球员可以经过这个球员复制得到,用setRGB可以使不同的队有不同的颜色;门一边一个。当然还有一个球、难度选择菜单
和一个选择队的菜单。
在游戏第一帧加上:
//这是初始化一些东东
//载入进度
OnePercent
=
this.getBytesTotal()
/
100;
//难度
difficulty
=
0;
//选择难度的菜单
diftext
=
new
Array
("Novice",
"Normal",
"Expert");
//左右两队
teamLeft
=
7;
teamRight
=
14;
//有十五个队供你选择
teamNumber
=
15;
Teams
=
new
Array
(teamNumber);
//球衣颜色
torsColors
=
new
Array
(teamNumber);
//球裤颜色
legColors
=
new
Array
(teamNumber);
//日本
Teams[0]
=
"Japan";
torsColors[0]
=
"-255,-255,90";
legColors[0]
=
"255,255,255";
//韩国
Teams[1]
=
"Korea
Republic";
torsColors[1]
=
"255,255,255";
legColors[1]
=
"255,-150,-120";
//英格兰
Teams[2]
=
"England";
torsColors[2]
=
"180,180,180";
legColors[2]
=
"180,180,180";
//挪威
Teams[3]
=
"Netherlands";
torsColors[3]
=
"-255,-75,90";
legColors[3]
=
"
255,
-30,-120";
//西班牙
Teams[4]
=
"Spain";
torsColors[4]
=
"-15,-255,-255";
legColors[4]
=
"-255,-255,-45";
//葡萄牙
Teams[5]
=
"Portugal";
torsColors[5]
=
"-90,-255,-255";
legColors[5]
=
"-255,-75,-75";
//意大利
Teams[6]
=
"Italy";
torsColors[6]
=
"255,255,255";
legColors[6]
=
"-255,-30,255";
//俄国
Teams[7]
=
"Russia";
torsColors[7]
=
"-255,-60,30";
legColors[7]
=
"-255,-60,30";
//乌克兰
Teams[8]
=
"Ukraine";
torsColors[8]
=
"-255,-75,90";
legColors[8]
=
"120,160,-255";
//巴西
Teams[9]
=
"Brasil";
torsColors[9]
=
"255,255,-255";
legColors[9]
=
"-255,-255,135";
//
土耳其
Teams[10]
=
"Turkey";
torsColors[10]
=
"210,-225,-225";
legColors[10]
=
"210,-225,-225";
//沙特阿拉伯
Teams[11]
=
"Saudi-Arabia";
torsColors[11]
=
"-165,-15,-30";
legColors[11]
=
"-165,-15,-30";
//美国
Teams[12]
=
"USA";
torsColors[12]
=
"255,45,45";
legColors[12]
=
"-255,-255,-60";
//塞内加尔
Teams[13]
=
"Senegal";
torsColors[13]
=
"-150,-15,-15";
legColors[13]
=
"255,255,255";
//德国
Teams[14]
=
"Germany";
torsColors[14]
=
"255,255,255";
legColors[14]
=
"-255,-255,-255";
//链接
borderbannerURL
=
"
http://www.flash8.net";;;;
endgameURL
=
"
http://www.flash8.net";;;;
coryrightURL
=
"
http://www.flash8.net";;;;
先帖一些代码,
具体的还在写。
//重新定义一些东西
function
ResetObjects
()
{
//
ball.x
=
0;
ball.y
=
0;
//
ball.dx
=
0;
ball.dy
=
0;
//
ball.goalStarted
=
false;
ball.SetScreenPos();
ball.gotoAndPlay("place");
i
=
1;
while
(i
<=
3)
{
var
obj
=
eval
("friend"
+
i);
//策略
obj.strategy
=
-1;
//有球吗?
obj.hasBall
=
false;
//位置
obj.x
=
friends[(i
-
1)
*
2];
obj.y
=
friends[((i
-
1)
*
2)
+
1];
obj.SetScreenPos();
//
obj.dir
=
4;
obj.hasBall
=
false;
//
obj.strength
=
1;
obj.run
=
false;
//假如是3号的话,
守门去。
(i
==
3)
?
((obj.step
=
KEEPER))
:
((obj.step
=
STEP));
//
obj.body.gotoAndStop("stay4");
obj.RestoreColors();
//对方
obj
=
eval
("enemy"
+
i);
obj.strategy
=
-1;
obj.hasBall
=
false;
obj.x
=
enemies[(i
-
1)
*
2];
obj.y
=
enemies[((i
-
1)
*
2)
+
1];
obj.SetScreenPos();
obj.dir
=
0;
obj.hasBall
=
false;
obj.strength
=
1;
obj.run
=
false;
(i
==
3)
?
((obj.step
=
KEEPER))
:
((obj.step
=
STEP));
obj.body.gotoAndStop("stay0");
obj.RestoreColors();
i++;
}
enabled
=
true;
}
//暂停
function
StopFootballers
()
{
i
=
1;
while
(i
<=
3)
{
var
obj
=
eval
("friend"
+
i);
obj.body.gotoAndStop("stay"
+
obj.dir);
obj.RestoreColors();
var
obj
=
eval
("enemy"
+
i);
obj.body.gotoAndStop("stay"
+
obj.dir);
obj.RestoreColors();
i++;
}
}
//开始的时候初始化
function
OneTimeInit
()
{
DIFFICULTY
=
_root.difficulty;
ResetObjects();
if
(DIFFICULTY
==
0)
{
STEP
=
2;
USERSTEP
=
4;
KEEPER
=
3;
}
else
if
(DIFFICULTY
==
1)
{
STEP
=
3;
USERSTEP
=
5;
KEEPER
=
3;
}
else
if
(DIFFICULTY
==
2)
{
STEP
=
3;
USERSTEP
=
3;
KEEPER
=
4;
}
enabled
=
true;
paused
=
false;
table.ResetTable;
table.StopTimer;
i
=
1;
while
(i
<=
3)
{
var
obj
=
eval
("friend"
+
i);
obj.en1
=
enemy1;
obj.en2
=
enemy2;
obj.gates
=
0;
obj.engates
=
2;
obj.keeper
=
friend3;
obj.control
=
false;
if
(i
==
1)
{
obj.partner
=
friend2;
obj.control
=
true;
obj.gotoAndStop("selected");
obj.RestoreColors();
}
if
(i
==
2)
{
obj.partner
=
friend1;
}
if
(i
==
3)
{
obj.isKeeper
=
true;
}
obj.SetColors(_root.teamRight);
obj
=
eval
("enemy"
+
i);
obj.en1
=
friend1;
obj.en2
=
friend2;
obj.gates
=
2;
obj.engates
=
0;
obj.keeper
=
enemy3;
obj.control
=
false;
if
(i
==
1)
{
obj.partner
=
enemy2;
}
if
(i
==
2)
{
obj.partner
=
enemy1;
}
if
(i
==
3)
{
obj.isKeeper
=
true;
}
obj.SetColors(_root.teamLeft);
trace
(obj._name);
i++;
}
_root.red
=
0;
_root.yellow
=
0;
}
function
UpdateScene
()
{
i
=
1;
while
(i
<=
3)
{
var
obj
=
eval
("friend"
+
i);
obj.Update();
obj
=
eval
("enemy"
+
i);
obj.Update();
ball.Update();
i++;
}
}
function
MakeStrategy
()
{
i
=
1;
while
(i
<=
3)
{
var
obj
=
eval
("friend"
+
i);
if
(obj.control
==
false)
{
obj.MakeStrategy();
}
obj
=
eval
("enemy"
+
i);
obj.MakeStrategy();
i++;
}
}
//长宽
WIDTH
=
530;
HEIGHT
=
300;
//
MAXHIT
=
400;
//
CLOSEDIST
=
20;
//
DX
=
1;
DY
=
1;
//
ASTEP
=
45;
GATE
=
150;
//处罚
PENALTY
=
100;
PENALTYH
=
240;
//球速
BALLSPEED
=
3;
//多少分钟
MINUTE
=
3000;
//自己这边的
friends
=
new
Array
(50,
50,
70,
-40,
(WIDTH
/
2)
-
20,
0);
//对方的
enemies
=
new
Array
(-50,
-50,
-70,
40,
((-WIDTH)
/
2)
+
20,
0);
//门
gates
=
new
Array
(WIDTH
/
2,
0,
(-WIDTH)
/
2,
0);
//选了什么
selected
=
1;
//初始化MC在屏幕的位置
MovieClip.prototype.SetScreenPos
=
function
()
{
this._x
=
this.x;
this._y
=
-this.y;
};
//距离
Math.distance
=
function
(x1,
y1,
x2,
y2)
{
var
deltaX
=
(x2
-
x1);
var
deltaY
=
(y2
-
y1);
return
(Math.sqrt((deltaX
*
deltaX)
+
(deltaY
*
deltaY)));
};
//
Math.sign
=
function
(n)
{
var
result;
(n
>=
0)
?
((result
=
1))
:
((result
=
-1));
return
(result);
};
//在不在场咯
Movieclip.prototype.inArea
=
function
(x1,
y1,
x2,
y2)
{
if
((this.x
<
x1)
||
(x2
<
this.x))
{
return
(false);
}
if
((this.y
<
y1)
||
(y2
<
this.y))
{
return
(false);
}
return
(true);
};
//两个MC之间的角度
Movieclip.prototype.calcAngle
=
function
(x1,
y1)
{
var
deltax
=
(this.x
-
x1);
var
deltay
=
(this.y
-
y1);
angle
=
Math.atan2(deltay,
deltax)
-
(Math.pi
/
2);
angle
=
angle
/
(Math.pi
/
180);
angle
=
angle
-
90;
(angle
<
0)
?
((angle
=
angle
+
360))
:
((angle
=
angle));
return
(angle);
};
//
离目标MC的角度
Movieclip.prototype.distance
=
function
(targetclip)
{
var
deltaX
=
(this.x
-
targetclip.x);
var
deltaY
=
(this.y
-
targetclip.y);
return
(Math.sqrt((deltaX
*
deltaX)
+
(deltaY
*
deltaY)));
};
//离球门的角度
Movieclip.prototype.goalAngle
=
function
(isRight)
{
var
gateY
=
random
(_parent.GATE
-
(_parent.GATE
/
2));
var
gateX;
(isRight
==
true)
?
((gateX
=
WIDTH
/
2))
:
((gateX
=
(-WIDTH)
/
2));
var
deltax
=
(this.x
-
gateX);
var
deltay
=
(this.y
-
gateY);
angle
=
Math.atan2(deltay,
deltax)
-
(Math.pi
/
2);
angle
=
angle
/
(Math.pi
/
180);
angle
=
angle
-
90;
(angle
<
0)
?
((angle
=
angle
+
360))
:
((angle
=
angle));
return
(angle);
};
//声音
sound.gotoAndStop("derby");
加一个空的MC,为control
//判断是否进门
function
inGates
()
{
if
((x
>=
(((-_parent.WIDTH)
/
2)
+
5))
&&
(((_parent.WIDTH
/
2)
-
5)
>=
x))
{
return
(false);
}
if
((((-_parent.GATE)
/
2)
>=
y)
||
(y
>=
(_parent.GATE
/
2)))
{
return
(false);
}
return
(true);
}
//踢一下,,dir为方向,strength为力量
function
Kick
(dir,
strength)
{
_parent.sound.gotoAndPlay("kick");
if
((dir
>
2)
&&
(dir
<
6))
{
dx
=
(-_parent.BALLSPEED)
*
strength;
}
else
if
((dir
==
2)
||
(dir
==
6))
{
dx
=
0;
}
else
{
dx
=
_parent.STEP
*
strength;
}
if
((dir
>
0)
&&
(dir
<
4))
{
dy
=
(-_parent.STEP)
*
strength;
}
else
if
((dir
==
4)
||
(dir
==
0))
{
dy
=
0;
}
else
{
dy
=
_parent.STEP
*
strength;
}
frame
=
1;
}
//
function
Update
()
{
if
(_parent.paused
==
true)
{
return;
}
x
=
x
+
dx;
y
=
y
-
dy;
//进球了
if
((inGates()
==
true)
&&
(goalStarted
==
false))
{
_parent.enabled
=
false;
_parent.StopFootballers();
_parent.panel.gotoAndPlay("goal");
goalStarted
=
true;
x
=
x
-
dx;
y
=
y
+
dy;
}
frame++;
if
((frame
%
20)
==
0)
{
frame
=
1;
if
(dx
!=
0)
{
dx
=
dx
-
(Math.abs(dx)
/
dx);
}
if
(dy
!=
0)
{
dy
=
dy
-
(Math.abs(dy)
/
dy);
}
<