Стрельба на опережение
Автор: Вадим Игоревич Борейко
Скороченая версия статьи.
Создания самообучающейся системы - задача, основной этап решения которой ориентирован на поиск подходящей модели. В данном случае, требуется найти оптимальный угол для стрельбы, основываясь на опыте предыдущих. Входные параметры - координаты, направление движения игрока, скорость, координаты снарядов.
Алгоритм должен стремиться попасть в игрока - оптимальным будет вариант, когда снаряд попадет в центр. Но для этого снаряд должен долететь до цели т. е. расстояния от пушки до игрока и до снаряда должны сравняться. В этом случае можно заключить, насколько удачен был выстрел и, на основе этого опыта, алгоритм должен сделать вывод. А вывод таков - "если бы пушка выстрелила в направлении текущей позиции игрока этим снарядом, то результат был бы оптимален". Соответственно, компьютер учитывает поправку к выстрелу для данного расстояния и, когда игрок будет на таком же расстоянии, как в прошлый раз, нужно будет выстрелить, отклонившись от прямого направления на игрока на сохраненный угол.
Для хранения соответствий "расстояние - поправка" создадим массив и зададим шаг, на который будут различаться соседние расстояния в массиве. Расстояние для записи поправки можно взять как в момент встречи (так в приведенном коде), так и в момент выстрела (придется запоминать и включить переменную в тип снаряда) - разница невелика.
В программе есть доп. алгоритмы стрельбы - в заданном направлении, вращаясь и стрельба веером.
;shooting.bb
Graphics3D 800,600
PositionEntity CreateCamera(),0,0,-20
RotateEntity CreateLight(),45,45,45
;Тип для пушки - координаты, угол, режим стрельбы, указатели на 3D-объекты,
; переменные для AI
Type turret
Field x#,y#,Mode,head,barrel,ang#,dir#,dx#,dy#
End Type
;Тип для выстрела - координаты, приращения, указатель на 3D-объект,
; расстояние от пушки, вспомогательные переменные для AI
Type shot
Field x#,y#,dx#,dy#,h,r#,tx#,ty#,dir#,t.turret
End Type
Const aimax=200,aistp#=.2
Dim aiang#(aimax)
SetFont LoadFont("arial",14)
SetBuffer BackBuffer()
;Константы - период стрельбы, скорость снарядов, скорость игрока
Const frq=200, v0#=.01, vp#=.005
;Const frq=200, v0#=.2, vp#=.1
Const bs#=2
Global x#,y#
;createturret 1:createturret 2:createturret 3:createturret 4
createturret 5:createturret 4:createturret 2
;Создание игрока
p=CreateSphere(16)
h=CreateSphere(8,p)
PositionEntity h,1,0,0
ScaleEntity h,.4,.4,.4
Repeat
tim=MilliSecs()
For t.turret=Each turret
;Вспомогательные переменные + нацеливание на игрока по умолчанию
dx#=x#-t\x
dy#=y#-t\y
cang#=ATan2(dy#,dx)
t\dx#=dx#
t\dy#=dy#
t\dir#=cang#
pr#=Sqr(dx#*dx#+dy#*dy#)
;Режимы прицеливания пушек:
Select t\Mode
Case 0; стрельба в данном направлении
cang=0
Case 1; стрельба с вращением
cang#=t\ang#+.05*dt#
Case 2; точное нацеливание на объект
Case 3; стрельба веером
cang#=cang#+Sin(dcang#)*30
dcang#=dcang#+2
Case 4; стрельба на опережение
;Вычисление коэффициентов квадратного уравнения
a#=v#*v#-v0#*v0#
b#=2.0*v#*(dx#*Cos(ang#)+dy#*Sin(ang#)-bs#)
c#=dx#*dx#+dy#*dy#-bs#*bs#
;Вычисление дискриминанта
d#=b#*b#-4.0*a#*c#
If d#>=0 Then
;Вычисление момента встречи снаряда и игрока
t1#=(-b#+Sqr(d#))/2.0/a#
t2#=(-b#-Sqr(d#))/2.0/a#
If t2#>0 Then t1#=t2#
If t1#>=0 Then
;Вычисление угла через координаты объекта в найденный момент времени
cang#=ATan2(y#+t1#*v#*Sin(ang#)-t\y#,x#+t1#*v#*Cos(ang#)-t\x#)
End If
End If
Case 5; самообучающаяся пушка (поправка к углу берется из массива
cang#=cang#+aiang(Int(pr#*aistp#))
End Select
;Поворот пушки
rotateturret t,cang#
;Возврат дула на прежнее положение
If EntityX(t\barrel)<.75 Then MoveEntity t\barrel,0,-.25/frq*dt#,0
;Выстрел и смещение дула внутрь пушки
If nextshot<=tim Then
fire t
PositionEntity t\barrel,.5,0,0
End If
Next
;Задание следующего момента стрельбы
If nextshot<=tim Then nextshot=tim+frq
For s.shot=Each shot
;Смещение снарядов
s\x#=s\x#+s\dx#*dt#
s\y#=s\y#+s\dy#*dt#
s\r#=s\r#+v0#*dt#
PositionEntity s\h,s\x#,s\y#,0
;Если расстояния от пушки до снаряда и до игрока равны, то
If pr#<=s\r# And s\dir#<999 Then
;Вычисляется корректирующий угол для данного расстояния
Newang#=s\t\dir#-s\dir#
; втискивается в границы [-180,180)
Newang#=Newang#-Floor((Newang#+180.0)/360.0)*360.0
; и заносится в массив,
aiang(Int(pr#*aistp#))=Newang#
; (следующая строка - чтобы не заносить этот снаряд в массив еще раз)
s\dir#=999
End If
If (s\x#-x#)^2+(s\y#-y#)^2<1 Then
FreeEntity s\h:Delete s
red=255
hits#=hits#+1
Else
;Удаление снарядов, вышедших за границы экрана
If Abs(s\x#)+Abs(s\y#)>40 Then FreeEntity s\h:Delete s
End If
Next
;Индикация попадания
If red>0 Then
red=red-Int(dt/3)
Else
red=0
End If
EntityColor p,255,255-red,255-red
;Перемещение и поворот игрока (скорость вычисляется исходя из прошедшего времени)
PositionEntity p,x#,y#,0
RotateEntity p,0,0,ang#
v#=(KeyDown(200)-KeyDown(208))*vp#
ang#=ang#+.3*dt#*(KeyDown(203)-KeyDown(205))
x#=x#+v#*Cos(ang#)*dt#
y#=y#+v#*Sin(ang#)*dt#
RenderWorld
;Счет
Text 0,0,"Hits/sec:"+(hits#/sec#)
Flip
;Время, затраченное этот цикл (дробное, так как умножается на дробные величины)
dt#=MilliSecs()-tim
sec#=sec#+0.001*dt#
Until KeyHit(1)
Function createturret(Mode)
;Создание головной части
t.turret=New turret
t\head=CreateCylinder(6)
ScaleMesh t\head,1,.5,1
RotateMesh t\head,90,0,0
;Дуло создать отдельно, но привязать к головной части
t\barrel=CreateCylinder(16,False)
RotateEntity t\barrel,0,0,90
ScaleEntity t\barrel,.2,1,.2
PositionEntity t\barrel,.75,0,0
h=CreateCylinder(16,True)
ScaleEntity h,.3,.3,.3
RotateEntity h,0,0,90
PositionEntity h,2,0,0
EntityParent t\barrel,t\head
EntityParent h,t\barrel
;Задание координат пушки случайным образом
t\x#=Rnd(-20,20)
t\y#=Rnd(-20,20)
PositionEntity t\head,t\x#,t\y#,0
t\Mode=Mode
End Function
Function rotateturret(t.turret,ang#)
t\ang#=ang#
RotateEntity t\head,0,0,ang#
End Function
Function fire(t.turret)
ang#=t\ang#
s.shot=New shot
s\x#=t\x#+Cos(ang#)*r
s\y#=t\y#+Sin(ang#)*r
s\r#=r
s\dx#=Cos(ang#)*v0#
s\dy#=Sin(ang#)*v0#
s\h=CreateSphere(2)
ScaleEntity s\h,.15,.15,.15
EntityColor s\h,255,255,0
;Вспомогательные переменные (для AI)
s\tx#=t\x#
s\ty#=t\y#
s\dir=t\dir#
s\t=t
End Function
Автор: Матвей Меркулов (MattMerkmail.ru)
7 октября 2006