Учебник по программированию.

Первые шаги. Язык программирования PascalABC.

 

Предыдущий параграф Назад в содержание Следующий параграф


§22. Модель мяча.

В предыдущем параграфе мы заставили круг двигаться. Теперь усложним задачу и заставим круг двигаться не просто по определённой траектории, а по такой же траектории, как и реальный мяч, брошенный в замкнутой области. Таким образом, мы создадим виртуальную модель мяча.

При решении такой задачи мы должны учесть силу притяжения, которая притягивает мяч к земле. Что бы задача не была слишком сложной, не будем учитывать силу трения мяча об воздух, и примем, что соударение мяча об границы замкнутой области будет абсолютно упругим, это значит, что с какой скоростью мяч прилетел к границе, с такой же скоростью он и отлетит от неё.

Однако сделаем другие усложнения, которые необходимы нам для профессионального роста. Все подпрограммы, константы и переменные, отвечающие за прорисовку, стирание и расчёт координат поместим в отдельный модуль с названием MMyach. Так же создадим константу r радиус мяча, и переменные X1,Y1,X2,Y2 координаты прямоугольника, являющегося замкнутой областью. Эти переменные нам нужны для того, что бы полученный модуль можно было использовать и в других программах, соответственно все расчёты надо будет производить относительно этих переменных.

Плюсом ко всему сделаем так, что бы мяч не стирал объекты, уже нарисованные на экране. Это означает, что если на экране уже нарисована линия, то мяч, пролетая через неё, оставлял её в том же виде. При этом мы не должны каждый раз перерисовывать весь экран.

Думаю, что задача ясна. Можно приступать непосредственно к программированию. Далее будет рассмотрено создание каждой процедуры в отдельности.

Процедуры рисования и стирания мяча.

Т.к. нам необходимо, что бы мяч не стирал, уже нарисованный рисунок, то мы должны сохранить область этого рисунка, которую закроет мяч, до того, как он будет нарисован в этой области. Затем, при стирания мяча, как раз эту область мы должны нарисовать обратно.

Для сохранения области создадим двухмерный массив типа color. Первый диапазон индексов будет содержать координаты х, а второй диапазон индексов координаты y. Размерность массива будет r на r. Каждый элемент массива будет содержать цвет точки с координатами х и y. Т.е. таким образом, мы сохраним цвета всех точек квадрата со сторонами r, в который вписан наш мяч.

Этот массив (область) будем заполнять с помощью функции GetPixel. Затем при стирании мяча вставлять эту область будем с помощью процедуры SetPixel.

Далее код процедур рисования и стирания мяча:

const r=40;//Радиус мяча


var x,y,//Координаты центра мяча

    obl:array [1..2*r,1..2*r] of Color;//Массив для хранения области под мячом


procedure Draw;//Рисуем мяч

var tmpColor:Color;//Переменная для временного хранения цвета кисти

begin

//Сохраняем область которую закроет мяч

  for var i:=1 to 2*r do

    for var j:=1 to 2*r do

      obl[i,j]:=GetPixel(x-r+i-1,y-r+j-1);

  tmpColor:=PenColor;//Сохранаяем цвет кисти

  SetBrushColor(clYellow);//Рисуем мяч

  FillCircle(x,y,r-1);//Рисуем мяч

  SetBrushColor(tmpColor);//Возвращаем цвет кисти

end;


procedure Clear;//Стираем мяч

begin

  for var i:=1 to 2*r do

    for var j:=1 to 2*r do

      SetPixel(x-r+i-1,y-r+j-1,obl[i,j]);

end;


Обратите внимание, в процедуре Draw прежде чем нарисовать круг мы сохраняем цвет кисти, а затем после прорисовки мяча возвращаем этот цвет. Это для того, что бы тот, кто будет пользоваться нашим модулем, не задумывался о том, что цвет кисти может измениться в результате работы нашей процедуры.

Процедура Koordinati.

Данная процедура предназначена для вычисления координат движущегося мяча. Прежде чем приступить к её написанию рассмотрим некоторые теоретические вопросы.

Скорость движения величина векторная. Как и любой вектор её можно разложить на две составляющие: скорость относительно оси X и скорость относительно оси Y. Следующий рисунок это наглядно демонстрирует:



Соответственно мы можем рассматривать две скорости относительно осей X и Y отдельно друг от друга, не задумываясь о том, какая будет скорость общая.

Скорость относительно оси X.  Скорость это изменение координаты за промежуток времени. Поэтому каждый раз после паузы будем увеличивать или уменьшать координату x в зависимости от направления движения. Например, при движении вправо код будет следующим: x:=x+Vx, где x координата мяча, Vx изменение координаты за промежуток времени равный паузе, или другими словами скорость движения мяча относительно оси X.

Направление движения будет меняться в двух случаях: при соударении мяча с левой стенкой и при соударении с правой стенкой. При этом необходимо учитывать некоторые тонкости. Расстояние до стенки может оказаться меньше, чем скорость изменения координаты. Поэтому мяч должен за этот же промежуток времени долететь до стенки и ещё вернуться обратно.

Например: мяч движется влево, и в данный момент координата мяча равна 10 пикселей, при этом скорость изменения координаты равна 25 пикселей за паузу, тогда мяч должен в этот же проход отскочить от стенки на 15 пикселей. Получаем следующий код для определения координаты:

x:=x-Vx;

If x<X1 then x:=X1+(X1-x);

Здесь X1  координата левой стенки замкнутой области. (X1-x) «перелёт мяча», т.е. то расстояние, на которое должен отскочить мяч от стенки. Далее приведу рисунок, который наглядно демонстрирует всё вышесказанное:


В случае если мяч двигался вправо, код будет немножко отличаться:

x:=x+Vx;

If x>X2 then x:=X2-(x-X2);

Здесь (x-X2) «перелёт мяча», т.е. то расстояние, на которое должен отскочить мяч.

Так же стоит сказать, что относительно оси X никакие силы действовать на мяч не будут, соответственно скорость относительно оси X меняться не будет.

Скорость относительно оси Y. Т.к. мы должны смоделировать поведения реального мяча, то на него должна действовать сила тяжести , где  масса мяча, ускорение свободного падения. Как раз и есть то ускорение, которое будет влиять на скорость мяча. Соответственно можем написать следующее соотношение: , где начальная и конечная скорости соответственно, промежуток времени, за который изменилась скорость, или в нашем случае та пауза, которую мы отправляем в качестве параметра, при вызове процедуры Sleep(). Из этого выражения получаем . Здесь не изменится на протяжении работы всей программы, если мы не будем менять паузу между обновлениями изображения. Поэтому это значение можем обозначить как , и присвоить ему какое либо значение в разделе описания констант. Не будем усложнять себе жизнь, и вычислять значение данной константы, которое будет соответствовать реальному поведению мяча, а примем его равным единице, при этом обновлять изображение будем через 1 миллисекунду. После написания программы можно будет поэкспериментировать и выбрать наиболее подходящее значение для . В  коде программы данную константу обозначим как dVy.

Из выше сказанного следует, что скорость движения мяча относительно оси Y будет меняться с каждым проходом программы. Соответственно получим следующий код для нахождения координаты y при движении мяча вниз:

Vy:=Vy+dVy;

y:=y+Vy;

Обратите внимание, что координата y увеличивается, а не уменьшается, как мы привыкли. Это потому, что ось Y на экране направлена сверху вниз.

При соударении мяча с верхней и нижней границами мяч будет отскакивать по тому же принципу, как и от левой и правой границ в случае с координатами по оси X. Однако есть один нюанс, мяч может не долететь до верхней границы в том случае, если он отскочил от нижней границы с недостаточной скоростью.

Если мяч летит вверх, то его скорость уменьшается, соответственно в какой-то момент она станет равной нулю и мяч начинает падать вниз. Так как в компьютере все параметры изменяются дискретно (не плавно, а на какое-то значение), то скорость может стать отрицательной, это как раз и есть сигнал того, что мяч должен сменить направление движения. Тогда координату будем находить следующим образом:

If Vy<0 then y:=y-dVy+2*Abs(Vy);

Далее рисунок для наглядного понимания происходящего:


Здесь необходимо сказать, что мы рассмотрели случай, когда мяч можно считать материальной точкой. В нашем случае мяч будет иметь радиус, и отталкиваться от стенок он будет своими краями, а не центром. Т.к. координаты мяча задают его центр, то при расчёте это обстоятельство необходимо учитывать.

Теперь можем написать процедуру:


const dVy=1;//Изменение скорости относительно оси Y

      r=40;//Радиус мяча

      X1=10;X2=630;Y1=10;Y2=470;//Координаты замкнутой области


//Константы для хранения направления движения

      L=1;//Движение влево

      P=2;//Движение вправо

      V=3;//Движение вверх

      N=4;//Движение вниз


var x,y,//Координаты мяча

//Скорости движения относительно оси Х и Y соответственно

    Vx,Vy:integer;

//направление движения по оси X и по оси Y соответственно

    naprX,naprY:byte;


procedure Koordinati;//Вычисляем координаты x,y

begin

//Координаты по оси X

  case naprX of

    P:begin

        x:=x+Vx;

        if x>=(X2-r) then //Если достигли правой стенки 

          begin

            naprX:=L;

            x:=(X2-r)-(x-(X2-r));

          end

      end;

    L:begin

        x:=x-Vx;

        if x<=(X1+r) then //Если достигли левой стенки

          begin

            naprX:=P;

            x:=(X1+r)+(X1+r-x);

          end

      end;

  end;

//Координаты по оси Y

  case naprY of

    V:begin

       Vy:=Vy-dVy;

       if Vy<=0 then //Мяч достиг мёртвой точки и начал падать вниз

         begin

          naprY:=N;

          if Vy<0 then //Если скорость получилась меньше нуля

            begin

            //Если мяч не долетел до потолка  

              if (y-(dVy+Vy))>=(Y1+r) then

                 y:=y-(dVy+Vy)+Abs(Vy)

            //Мяч всё-таки долетел до потолка и от него оттолкнулись

                else

                 y:=(Y1+r+1)+((dVy+Vy)+Abs(Vy))-(y-(Y1+r+1));

            end;

         end

    //Мяч либо летит вверх, либо долетел до потолка и оттолкнулся от него

        else

         begin

           y:=y-Vy;

           if y<=(Y1+r+1) then//Мяч долетел до потолка и оттолкнулся

             begin

               naprY:=N;

               if y>=0 then//Если мяч не вылетел за пределы экрана

                   y:=(Y1+r+1)-y+(Y1+r+1)

                 else//Мяч вылетел за пределы экрана

                   y:=Abs(y) + 2*(Y1+r+1);

             end;

         end;

      end;

    N:begin

       Vy:=Vy+dVy;

       y:=y+Vy;

       if y>=(Y2-r-1) then//Если достигли пола

         begin

          naprY:=V;

          if y>(Y2-r-1) then//Если упали ниже пола

             y:=y-2*(y-(Y2-r-1));

         end;

      end;

  end;

end;


В принципе все сложности написания модуля на этом закончены. Далее приведу весь модуль целиком за исключением тех строк, которые были уже приведены и которые заменены многоточиями:


unit MMyach;


uses GraphABC;


const ..............


procedure Draw;//Рисуем мяч

.............................


procedure Clear;//Стираем мяч

...............................


procedure Koordinati;//Вычисляем координаты x,y

................................................


end.


Теперь данный модуль можно испытать. Для этого напишем небольшую программку:


program ModelMyacha;

uses MMyach, GraphABC;


begin

//Устанавливаем координаты замкнутой области

  X1:=10;X2:=630;Y1:=10;Y2:=470;

//Устанавливаем начальные параметры движения

  y:=Random(Y1+R,Y2-R); x:=Random(X1+R,X2-r);

  naprY:=Random(3,4); naprX:=Random(1,2);

  Vy:=Random(4,20); Vx:=Random(2,10);

//Рисуем замкнутую область

  Rectangle(X1,Y1,X2,Y2);

//Рисуем произвольный рисунок

  Line(X1,Y1,X2,Y2);

  Line(X2,Y1,X1,Y2);

//Рисуем мяч

  LockDrawing;

  while true do

    begin

      Draw;

      Redraw;

      Sleep(1);

      clear;

      koordinati;

    end;

end.


Далее несколько слов о том для чего данный параграф, а в частности данный модуль и весь процесс его написания были помещены в книгу.

На примере данного модуля мы, во-первых рассмотрели, как реальный физический процесс можно перенести на экран компьютера; во-вторых научились перемещать предметы по экрану, не изменяя при этом его содержимое; в третьих потренировались рисовать объекты в относительных координатах. А так же на данном примере можно проследить весь процесс разработки программы.

Так же стоит сказать, что на основе данного модуля надо будет написать свой в задачах для самостоятельного решения. Этот модуль будет использован в дальнейших параграфах.

Небольшое примечание по программе: если вы набрали текст программы и запустили её на выполнение, то вы увидели, как мяч двигается в замкнутой области, так как будто он настоящий. На первый взгляд всё хорошо, как и должно быть. Однако  вы должны были заметить, что ресурсы компьютера полностью задействованы, хотя программа достаточно простая. Дело в том, что стирание предмета именно подобным образом отнимает довольно много ресурсов компьютера. Такой способ стирания мы рассмотрели в учебных целях. На практике вы будете пользоваться другими возможностями, о которых узнаете в 32 параграфе.

Так же стоит сказать, что паузу между «кадрами» анимации нужно выдерживать по-другому. Об этом так же узнаете в 32 параграфе.

Эти возможности не приведены сейчас потому, что для их понимания ещё не достаточно знаний.


В данном параграфе мы научились перемещать предметы по экрану, не стирая его содержимое. А так же создали виртуальную модель мяча.

Задачи.

1. В предыдущем параграфе в одном из заданий необходимо было нарисовать машинку. Используя процедуру её рисования, написать программу в которой машинка будет двигаться по дороге с разметкой.

2. В данном параграфе была написана программа, моделирующая поведение мяча в замкнутой области. Необходимо написать подобную программу, но с тремя мячами разного диаметра, с разной начальной скоростью и различными начальными направлениями движения. Для этого необходимо создать три одинаковых модуля M1, М2 и М3.

Примечание: Для того, что бы не создавать программу, требующую больших ресурсов компьютера, и для упрощения задачи стирание мяча производить рисованием белого мяча. Задний рисунок создавать при этом не надо.


Решение.

1. Данную задачу решите самостоятельно.

2.

unit M1;


uses GraphABC;


const dVy=1;//Изменение скорости относительно оси Y

      r=60;//Радиус мяча


//Константы для хранения направления движения

      L=1;//Движение влево

      P=2;//Движение вправо

      V=3;//Движение вверх

      N=4;//Движение вниз


var x,y,//Координаты мяча

  //Скорости движения относительно оси Х и Y соответственно  

    Vx,Vy:integer;

  //направление движения по оси X и по оси Y соответственно

    naprX,naprY:byte;

    X1,X2,Y1,Y2:integer;//Координаты замкнутой области


procedure Draw;//Рисуем мяч

begin

  SetBrushColor(clGreen);//Рисуем мяч

  FillCircle(x,y,r-1);//Рисуем мяч

end;


procedure Clear;//Стираем мяч

begin

  SetBrushColor(clWhite);//Рисуем мяч

  FillCircle(x,y,r);//Рисуем мяч

end;


procedure Koordinati;//Вычисляем координаты x,y

................................................

end.

=========================================================================

program TriMyacha;

uses M1,M2,M3, GraphABC;


begin

//Устанавливаем координаты замкнутой области

  M1.X1:=10;M1.X2:=630;M1.Y1:=10;M1.Y2:=470;

  M2.X1:=10;M2.X2:=630;M2.Y1:=10;M2.Y2:=470;

  M3.X1:=10;M3.X2:=630;M3.Y1:=10;M3.Y2:=470;

//Устанавливаем начальные параметры движения

  M1.y:=Random(Y1+R,Y2-R);  M1.x:=Random(X1+R,X2-r);

  M1.naprY:=Random(3,4);    M1.naprX:=Random(1,2);

  M1.Vy:=Random(4,30);      M1.Vx:=Random(2,10);

  M2.y:=Random(Y1+R,Y2-R);  M2.x:=Random(X1+R,X2-r);

  M2.naprY:=Random(3,4);    M2.naprX:=Random(1,2);

  M2.Vy:=Random(4,30);      M2.Vx:=Random(2,10);

  M3.y:=Random(Y1+R,Y2-R);  M3.x:=Random(X1+R,X2-r);

  M3.naprY:=Random(3,4);    M3.naprX:=Random(1,2);

  M3.Vy:=Random(4,30);      M3.Vx:=Random(2,10);

//Рисуем замкнутую область

  Rectangle(9,9,631,471);

//Рисуем мячи

  LockDrawing;

  while true do

    begin

      M1.Draw;        M2.Draw;        M3.Draw;

      Redraw;

      Sleep(30);

      M1.clear;       M2.Clear;       M3.Clear;

      M1.koordinati;  M2.koordinati;  M3.koordinati;

    end;

end.


Примечание: процедура koordinati осталась без изменения, поэтому она заменена на многоточие. Модули M2 и M3 такие же как и M1. В них только разные радиусы и цвет рисования мяча.



Предыдущий параграф Назад в содержание Следующий параграф