Перед тем как почесть этот пост я советую посмотреть ролик Всеволода Леонова, посвященный анимации.
Собственно, существует, как минимум два способа реализовать движение объектов в FireMonkey.
Первый из них – банально менять координаты объекта по таймеру.
Второй – использовать компонент TFloatAnimation.
На видео проиллюстрирована реализация движения тела, брошенного под углом к горизонту обеими способами.
И здесь можно увидеть, что траектории полета мячей немного отличаются. Почему? Давайте попробуем разобраться.
Левый мяч “летит” с помощью таймера. Код примерно такой:
procedure TMy3DForm.Button3Click(Sender: TObject); begin ct:= 0; v:= 20; g:= 9.8; x0:=0; angleYG:=30; BallRadius:= Ball.Width/2; Timer1.Enabled:= True; end;
procedure TMy3DForm.Timer1Timer(Sender: TObject); var x, y: real; function CheckGround: boolean; begin Result:= False; if Ball.Position.Y>-BallRadius then begin ct:=0; Timer1.Enabled:= False; Ball.Position.Y:=-BallRadius; x0:= Ball.Position.Z; end; end; begin AngleYR:= DegToRad(AngleYG); ct:= ct+(Timer1.Interval/1000); x := x0+v*ct*Cos(AngleYR); Ball.Position.Z:=x; y:= (BallRadius+ (v*ct*sin(AngleYR))-(ct*ct*g/2)); Ball.Position.Y:= -1*y; CheckGround; end;
Здесь просто изменяется позиция мяча с учетом его радиуса. В конце каждой итерации проверяется, не достиг ли мяч земли (процедура CheckGround). И здесь очень важно, какой задан интервал таймера. Для того, что бы получить более точную имитацию движения объекта интервал должен быть минимальным. С другой стороны, значительное уменьшение интервала может привести к подтормаживанию. С точки зрения визуального восприятия лучше задать значение 40 (25 кадров в секунду), что я и сделал.
Для второго мяча (правого) я использовал два компонента TFloatAnimation. Примерно так, как это сделал Всеволод Леонов. Из кода наглядно видны настройки обоих компонентов.
procedure TMy3DForm.Button4Click(Sender: TObject); begin Ball.Position.Y:= -0.5; Ball2.Position.Y:= -0.5; Ball.Position.Z:= 0; Ball2.Position.Z:= 0; ct:= 0; v:= 20; g:= 9.8; x0:=0; angleYG:=30; BallRadius:= Ball.Width/2; CulcParams; AnimationX.Duration:= t; AnimationX.StopValue:= L; AnimationY.AnimationType:= TAnimationType.atOut; AnimationY.StartFromCurrent:= True; AnimationY.Duration:= t/2; PosYMax:= -(yMax+BallRadius); AnimationY.StopValue:= PosYMax; AnimationY.Interpolation:= TInterpolationType.itQuadratic; AnimationY.AutoReverse:= True; { CodeSite.Send('Z - '+FloatToStr(Ball2.Position.Z)+'Y - '+FloatToStr(Ball2.Position.Y)+ ' L - '+FloatToStr(L)+ ' t - '+ FloatToStr(t)+' yMax - '+FloatToStr(yMax));} AnimationX.Enabled:= True; AnimationY.Enabled:= True; AnimationY.Start; AnimationX.Start; end; procedure TMy3DForm.AnimationYProcess(Sender: TObject); begin if Ball2.Position.y = -BallRadius then begin AnimationX.Stop; AnimationY.Stop; CodeSite.Send(FloatToStr(PosYMax)); v:= v/1.5; CulcParams; AnimationY.AnimationType:= TAnimationType.atOut; AnimationY.StartFromCurrent:= False; AnimationY.StartValue:= -BallRadius; AnimationY.Duration:= t/2; AnimationY.StopValue:= -(yMax+BallRadius); AnimationY.Loop:= True; AnimationX.StartValue:=Ball2.Position.Z; AnimationX.Duration:= t; AnimationX.StopValue:= Ball2.Position.Z+L; CodeSite.Send('Z - '+FloatToStr(Ball2.Position.Z)+' Y - '+FloatToStr(Ball2.Position.Y)+ ' L - '+FloatToStr(L)+ ' t - '+ FloatToStr(t)+ ' V - '+FloatToStr(V)+' yMax - '+FloatToStr(yMax)); AnimationX.Start; AnimationY.Start; end; end;
Расчет дальности и времени полета, а так же максимальной высоты, на которую поднимается мяч производится по формулам в процедуре CulcParams:
procedure TMy3DForm.CulcParams(); begin AngleYR:= DegToRad(AngleYG); yMax:= Sqr(v)*Sqr(Sin(AngleYR))/(2*g); L:= Sqr(v)*Sin(2*AngleYR)/g; t:= L/(v*Cos(AngleYR)); end;
Здесь же я реализовал эффект отскока мяча. Чуть позже я прокомментирую этот код, а пока вернемся к исходному вопросу. Почему первый мяч пролетает чуть дальше второго?
Чуть чуть поправим код процедуры CheckGround. Включим лог и реализуем отскок.
function CheckGround: boolean; begin Result:= False; if Ball.Position.Y>-BallRadius then begin ct:=0; Timer1.Enabled:= False; // Пишем реальное значение в лог CodeSite.Send(FloatToStr(Ball.Position.Z)+' - '+FloatToStr(Ball.Position.Y)); Ball.Position.Y:=-BallRadius; x0:= Ball.Position.Z; v:= v/1.5; // После удара о землю скорость падает if v<1 then begin // check stop Result:=True; Timer1.Enabled:= False; Exit; end; // Отскок. Запускаем таймер заново Timer1.Enabled:= True; end; end;
для логирования я воспользовался инструментом Code Site, входящим в состав RAD Studio. Он действительно очень эффективно позволяет организовать логирование в процессе работы программы. Подробнее прочесть о Code Site вы можете в блоге WebDelphi.ru. Я же просто приведу сам лог.
Info 36.0266571044922 – -0.100639998912811
Info 52.1924629211426 – -0.229333326220512
Info 59.2746276855469 – -0.441528886556625
Info 62.5591087341309 – -0.389256298542023
Info 64.0644989013672 – -0.420495808124542
Info 64.7031478881836 – -0.484564274549484
Info 65.0072631835938 – -0.479582995176315
Info 65.1289138793945 – -0.499673187732697
Мы видим, что из-за дискретности таймера мяч немного “проваливается”, а не останавливается на значении 0,5 (радиус мяча) по оси Y. Соответственно, и по оси X он пролетает чуть дальше. Именно для того, что бы погрешность не накладывалась я вставил в код строчку:
Ball.Position.Y:=-BallRadius;
А вот при анимации с помощью TFloatAnimation, я изначально рассчитал экстремальные точки и компоненты сделали интерполяцию. И как видно из ролика – достаточно точно.
Теперь еще пару комментариев по коду.
По оси Y я поднимаю мяч до рассчитаной заранее точки и возвращаю его обратно по тойже траектории:
AnimationY.AutoReverse:= True;
По оси X осуществляется линейное движение.
TFloatAnimation имеет события OnProcees и OnFinish. Но дело в том, что если движение зациклено (Loop = True), то OnFinish срабатывать не будет. Как и в предыдущем случае, я при ударе мяча об землю гашу скорость в полтора раза и переопределяю значение свойства Duration (длительность анимации) и начальное и конечное положение объекта по обеим осям. Что бы остановить и возобновить движение я использую методы Stop и Start. При этом если мы вызвали Stop, то изменение значения свойства Enabled уже не влияет на поведение объекта.
Траектория движения подбирается значениями свойств AnimationType и Interpolation.
Конечно, сложные траектории будет довольно сложно смоделировать с помощью TFloatAnimation , но для простых – это хороший вариант. Возможно, если уравнение перемещения объекта задается одной формулой, то его можно реализовать с помощью собственного компонента, расширив список типов интерполяции. Я уверен, что разумное решение можно найти для любой, даже такой невероятной траектории:
<span style=’color:#800000; font-weight:bold; ‘>begin</span>
AngleYR<span style=’color:#808030; ‘>:</span><span style=’color:#808030; ‘>=</span> DegToRad<span style=’color:#808030; ‘>(</span>AngleYG<span style=’color:#808030; ‘>)</span><span style=’color:#800080; ‘>;</span>
yMax<span style=’color:#808030; ‘>:</span><span style=’color:#808030; ‘>=</span> Sqr<span style=’color:#808030; ‘>(</span>v<span style=’color:#808030; ‘>)</span><span style=’color:#808030; ‘>*</span>Sqr<span style=’color:#808030; ‘>(</span>Sin<span style=’color:#808030; ‘>(</span>AngleYR<span style=’color:#808030; ‘>)</span><span style=’color:#808030; ‘>)</span><span style=’color:#808030; ‘>/</span><span style=’color:#808030; ‘>(</span><span style=’color:#008c00; ‘>2</span><span style=’color:#808030; ‘>*</span>g<span style=’color:#808030; ‘>)</span><span style=’color:#800080; ‘>;</span>
L<span style=’color:#808030; ‘>:</span><span style=’color:#808030; ‘>=</span> Sqr<span style=’color:#808030; ‘>(</span>v<span style=’color:#808030; ‘>)</span><span style=’color:#808030; ‘>*</span>Sin<span style=’color:#808030; ‘>(</span><span style=’color:#008c00; ‘>2</span><span style=’color:#808030; ‘>*</span>AngleYR<span style=’color:#808030; ‘>)</span><span style=’color:#808030; ‘>/</span>g<span style=’color:#800080; ‘>;</span>
t<span style=’color:#808030; ‘>:</span><span style=’color:#808030; ‘>=</span> L<span style=’color:#808030; ‘>/</span><span style=’color:#808030; ‘>(</span>v<span style=’color:#808030; ‘>*</span>Cos<span style=’color:#808030; ‘>(</span>AngleYR<span style=’color:#808030; ‘>)</span><span style=’color:#808030; ‘>)</span><span style=’color:#800080; ‘>;</span>
<span style=’color:#800000; font-weight:bold; ‘>end</span><span style=’color:#800080; ‘>;</span>
</pre>
Другие статьи серии:
Firemonkey на практике #0
Firemonkey на практике #1
Firemonkey на практике #2. Освещение и материал поверхности 3D объектов
Firemonkey на практике #3. Использование 3D моделей
Firemonkey на практике #4. Ты попал!
Firemonkey на практике #5.
Оставить комментарий