Статьи

Играя с упругими столкновениями

В этом уроке мы создадим игру, целью которой является предотвращение столкновения других объектов с вашим курсором. Мы не будем использовать встроенные в hitTestObject() методы hitTestObject() ; вместо этого мы напишем наши собственные процедуры обнаружения столкновений.

Переизданный учебник

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


Давайте посмотрим на конечный результат, к которому мы будем стремиться:


Создайте новый файл Flash (ActionScript 3.0)

Учебник по Flash столкновению

Установите размеры сцены на 500x500px и FPS на 32.

Учебник по Flash столкновению

Этот класс будет содержать все данные, связанные с одним мячом. Мяч имеет _mass , _radius , _xSpeed и _ySpeed . Поэтому мы сделаем недвижимость для каждого. В конструктор мы передаем массу, угол и скорость мяча. Поскольку класс будет связан с экранным объектом, мы можем получить радиус нашего шара, разделив ширину _xSpeed объекта на 2. _xSpeed и _ySpeed можно рассчитать с помощью простых функций синуса и косинуса.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package
{
  import flash.display.Stage
  import flash.display.Sprite
  import flash.events.Event
 
  public class Ball extends Sprite
  {
    private var _radius:Number = 0
    private var _mass:Number = 0
    private var _xSpeed:Number = 0
    private var _ySpeed:Number = 0
 
    public function Ball(mass:Number = 10.0, angle:Number = Math.PI, speed:Number = 10.0):void
    {
      this.mass = mass
      this._radius = this.width/2
      this.xSpeed = speed*Math.sin(angle)
      this.ySpeed = speed*Math.cos(angle)
    }
  }
}

Для получения дополнительной информации об этих тригонометрических функциях Math.sin () и Math.cos () см. Этот Быстрый совет .


В нашем классе Ball мы предоставляем геттеры и сеттеры для наших свойств.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public function get radius():Number
{
  return this._radius
}
 
public function set mass(mass:Number):void
{
  this._mass = mass
}
 
public function get mass():Number
{
  return this._mass
}
 
public function set xSpeed(xSpeed:Number):void
{
  this._xSpeed = xSpeed
}
 
public function get xSpeed():Number
{
  return this._xSpeed
}
 
public function set ySpeed(ySpeed:Number):void
{
  this._ySpeed = ySpeed
}
 
public function get ySpeed():Number
{
  return this._ySpeed
}

Эта функция обновляет свойства x и y нашего шара в соответствии с _xSpeed и _ySpeed . Мы реализуем эту функцию в нашем классе Ball .

1
2
3
4
5
public function update():void
{
  this.x += _xSpeed
  this.y += _ySpeed
}

На этом мы закончим наш урок по Ball .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package
{
  import flash.display.Stage
  import flash.display.Sprite
  import flash.events.Event
 
  public class Ball extends Sprite
  {
    private var _radius:Number = 0
    private var _mass:Number = 0
    private var _xSpeed:Number = 0
    private var _ySpeed:Number = 0
 
    public function Ball(mass:Number = 10.0, angle:Number = Math.PI, speed:Number = 10.0):void
    {
      this.mass = mass
      this._radius = this.width/2
      this.xSpeed = speed*Math.sin(angle)
      this.ySpeed = speed*Math.cos(angle)
    }
 
    public function get radius():Number
    {
      return this._radius
    }
 
    public function set mass(mass:Number):void
    {
      this._mass = mass
    }
 
    public function get mass():Number
    {
      return this._mass
    }
 
    public function set xSpeed(xSpeed:Number):void
    {
      this._xSpeed = xSpeed
    }
 
    public function get xSpeed():Number
    {
      return this._xSpeed
    }
 
    public function set ySpeed(ySpeed:Number):void
    {
      this._ySpeed = ySpeed
    }
 
    public function get ySpeed():Number
    {
      return this._ySpeed
    }
 
    public function update():void
    {
      this.x += _xSpeed
      this.y += _ySpeed
    }
  }
}

В исходные файлы я включил стартовый FLA, который содержит все необходимые элементы библиотеки. Вы можете нарисовать их сами, если хотите, конечно. Убедитесь, что ваш FLA имеет следующие экранные объекты:

Учебник по Flash столкновению

(Примечание редактора: это опечатка: «ennemyball» должен сказать «вражеский шар».)


Класс Ball мы только что создали, должен быть связан со Sprite enemyball в библиотеке.

Учебник по Flash столкновению

Спрайт PlayerBall должен иметь Ball качестве базового класса и PlayerBall качестве класса.

Учебник по Flash столкновению

Клип с score должен иметь класс Score .

Учебник по Flash столкновению

Класс Application будет содержать всю игровую логику. Мы импортируем все классы, которые нам нужны. Как вы можете видеть, мы будем использовать TweenMax .

Далее мы определяем наши переменные поля. Первая переменная поля — это ballPlayer .

Поскольку базовым классом нашего спрайта ballPlayer является Ball мы можем сохранить этот класс в переменной ballPlayer . Это облегчает проверку позже столкновения между ballPlayer и вражескими шарами.

Вторая переменная поля — это массив, который будет содержать все наши вражеские шары. Третья переменная — это таймер, который будет использоваться для выполнения основного игрового цикла. Четвертое и последнее поле — это экземпляр объекта библиотеки Score который будет использоваться для отображения прошедшего игрового времени. В конструкторе мы вызываем функцию init() которую я объясню на следующем шаге.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package
{
  import flash.display.Sprite
  import flash.display.Graphics
  import flash.events.Event
  import flash.events.TimerEvent
  import flash.events.MouseEvent
  import flash.geom.Matrix
  import flash.utils.Timer
  import flash.ui.Mouse
  import com.greensock.TweenMax
  import com.greensock.easing.*
 
  public class Application extends Sprite
  {
 
    private var ballPlayer:Ball
    private var eballs:Array
    private var tmr:Timer
    private var score:Score
 
    public function Application():void
    {
      init()
    }
  }
}

Не забудьте связать класс документа !

Учебник по Flash столкновению

Посмотрите на этот код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private function init():void
{
  ballPlayer = new PlayerBall()
  eballs = new Array()
  tmr = new Timer(10)
  score = new Score()
 
  stage.align = «TL»
  stage.scaleMode = «noScale»
  Mouse.hide()
 
  setBackground()
 
  score.x = stage.stageWidth/2
  score.y = stage.stageHeight/2
  stage.addChild(score)
 
  stage.addEventListener(MouseEvent.MOUSE_MOVE, updatePlayerBall)
  stage.addChild(ballPlayer)
 
        tmr.addEventListener(TimerEvent.TIMER, updateTime)
 
  stage.addEventListener(MouseEvent.CLICK, startGame)
}

В первых четырех строках мы инициализируем наши переменные поля.

Затем мы убедимся, что наша сцена выровнена по верхнему левому углу и не масштабируется.

Мы прячем курсор мыши. Наш курсор будет заменен на playerball Sprite. Далее мы вызываем функцию setBackground (объяснено на следующем шаге).

Мы центрируем наш score на экране и добавляем его в список отображения. Чтобы обновить позицию ballPlayer мы присоединяем событие MouseEvent.MOUSE_MOVE к сцене.

Функция updatePlayerBall (объясненная на шаге 11) будет обрабатывать этот MouseEvent. Затем мы добавляем ballPlayer в список отображения.

Таймер будет использоваться для отображения игрового времени. Мы присоединяем слушатель TimerEvent.TIMER к нашему таймеру, который будет запускать updateTime() (объяснено в шаге 12) каждые 10 миллисекунд.

Наконец, мы добавляем MouseEvent.CLICK на нашу сцену. Функция startGame (объясненная на шаге 13) запустит нашу игру.


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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
private function setBackground():void
{
  var type:String = «radial»
  var colors:Array = [0xffffff,0xcccccc]
  var alphas:Array = [ 1, 1 ]
  var ratios:Array = [ 0, 255 ]
  var matr:Matrix = new Matrix()
  matr.createGradientBox(stage.stageWidth, stage.stageHeight, Math.PI / 2, 0, 0 )
  //SpreadMethod will define how the gradient is spread.
  var sprMethod:String = «pad»
  //Start the Gradietn and pass our variables to it
  var sprite:Sprite = new Sprite()
  //Save typing + increase performance through local reference to a Graphics object
  var g:Graphics = sprite.graphics
  g.beginGradientFill( type, colors, alphas, ratios, matr, sprMethod )
  g.drawRect(0,0,stage.stageWidth,stage.stageHeight)
 
  stage.addChild(sprite)
}

Эта функция обновляет положение ballPlayer соответствии с положением вашей мыши.

1
2
3
4
5
private function updatePlayerBall(e:MouseEvent):void
{
  ballPlayer.x = mouseX
  ballPlayer.y = mouseY
}

Мы рассчитываем время в секундах и помещаем его в текстовое поле нашего score Sprite. Каждые 5000 мс (пять секунд) мы добавляем новый мяч в игру.

1
2
3
4
5
6
7
8
private function updateTime(e:TimerEvent):void
{
  score.txtScore.text = String(((tmr.currentCount*tmr.delay)/1000).toFixed(2));
  if((tmr.currentCount*tmr.delay) % 5000 == 0)
  {
    addBall();
  }
}

Игра запускается нажатием на сцену. Сначала мы удаляем слушателя для щелчка по сцене, чтобы мы не могли начать игру несколько раз. Мы добавляем в игру три мяча, трижды вызывая addBall() (объясняется в следующем шаге). Мы запускаем наш таймер, который обновит наше игровое время.

Наконец, мы добавляем событие ENTER_FRAME на нашу сцену. Функция gameLoop() (объясненная в шаге 15) будет обновлять положение наших вражеских шаров.

1
2
3
4
5
6
7
8
9
private function startGame(e:MouseEvent):void
{
  stage.removeEventListener(MouseEvent.CLICK, startGame)
  addBall()
  addBall()
  addBall()
  tmr.start()
  stage.addEventListener(Event.ENTER_FRAME, gameLoop)
}

Сначала мы создаем новый экземпляр нашего класса Ball . Мы размещаем ball случайным образом на сцене с альфа 0 и добавляем его в список отображения.

Затем мы вернем альфу обратно в 1. (Я использую TweenMax, он включен в исходные файлы. Вы также можете использовать встроенный движок анимации Flash.) Вторая анимация на самом деле не анимация. Он просто ждет секунду, и функция onComplete eballs ball в наш массив eballs . Таким образом, gameLoop() (объясненная на следующем шаге) может обрабатывать все остальное.

01
02
03
04
05
06
07
08
09
10
private function addBall():void
{
  var ball:Ball = new Ball(10, Math.random()*Math.PI*2, 5)
  ball.x = Math.random()*stage.stageWidth
  ball.y = Math.random()*stage.stageHeight
  ball.alpha = 0
  stage.addChild(ball)
  TweenMax.to(ball, 0.5, {alpha:1})
  TweenMax.to(ball, 0, {delay: 1, onComplete:function():void{eballs.push(ball)}})
}

Каждый кадр пройдет через эту функцию.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
private function gameLoop(e:Event):void
{
  for (var i:uint = 0; i < eballs.length; i++)
  {
    for (var j:uint = i + 1; j < eballs.length; j++)
    {
      if (collision(eballs[i], eballs[j]))
      {
        doCollision(eballs[i], eballs[j])
      }
    }
 
    if(collision(eballs[i], ballPlayer))
    {
      endOfGame()
      break
    }
 
    eballs[i].update()
    checkBounds(eballs[i])
  }
}

Мы начинаем с перебора всех наших вражеских шаров.

Второй цикл for проверяет наличие столкновений между вражескими шарами. Цикл начинается с «i + 1». Таким образом, мы не проверяем дважды столкновения.

Затем мы проверяем, поражает ли ballPlayer вражеский шар. Если так, игра закончена. Затем мы обновляем позицию нашего вражеского мяча.

Мы уверены, что шары остаются на игровом экране, вызывая функцию checkBounds() (объяснено позже).


Эта функция проверяет, сталкивается ли любая данная пара шаров.

Сначала мы вычисляем расстояние x и расстояние y между двумя шарами. Используя теорему Пифагора (см. Следующую диаграмму), мы вычисляем абсолютное расстояние между ними. Если расстояние меньше или равно сумме радиусов шаров, мы сталкиваемся.

Учебник по Flash столкновению
1
2
3
4
5
6
7
8
private function collision(ball1:Ball, ball2:Ball):Boolean
{
  var xDist:Number = ball1.x — ball2.x
  var yDist:Number = ball1.y — ball2.y
  var Dist:Number = Math.sqrt(xDist * xDist + yDist * yDist)
 
  return Dist <= ball1.radius + ball2.radius
}

Эта функция будет вычислять новые x- и y-скорости шариков в соответствии со скоростью и углом столкновения. Предупреждение: математика;)

Сначала мы рассчитываем горизонтальное расстояние между двумя шарами, а затем вертикальное расстояние между шарами. С этими расстояниями (и немного большей тригонометрией ) мы можем вычислить угол между шарами (см. Диаграмму).

Учебник по Flash столкновению

Далее мы вычисляем то, что я называю величиной каждого шара. (У нас есть вектор xspeed и вектор yspeed; величина является векторной суммой этих значений.) Затем мы вычисляем угол каждого шара (аналогично предыдущему вычислению угла).

Затем мы вращаем новые x- и y-скорости каждого шара. На самом деле мы вращаем систему координат. Вращая наши оси, мы сталкиваемся с 1D. (См. Следующую диаграмму).

Учебник по Flash столкновению

Ньютон говорит, что общее количество кинетической энергии в замкнутой системе постоянно. Теперь мы используем эти формулы:

  • v1 = (u1*(m1-m2) + 2*m2*u2)/(m1+m2)
  • v2 = (u2*(m2-m1) + 2*m1*u1)/(m1+m2)

где:

v1 = окончательный xSpeedBall 1

v2 = окончательный xSpeedBall 2

m1 = масса мяча 1

м2 = масса шара 2

u1 = начальная скорость мяча 1

u2 = начальная скорость мяча 2

Y-скорости не меняются, так как это одномерное столкновение.

С помощью этих формул мы можем рассчитать xSpeed и ySpeed каждого шара.

Теперь у нас есть новые x- и y-скорости в нашей повернутой системе координат. Последний шаг — преобразовать все обратно в нормальную систему координат. Мы используем Math.PI/2 потому что угол между xSpeed и ySpeed всегда должен быть 90 градусов (пи / 2 радиана ).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private function doCollision(ball1:Ball, ball2:Ball):void
{
  var xDist:Number = ball1.x — ball2.x
  var yDist:Number = ball1.y — ball2.y
  var collisionAngle:Number = Math.atan2(yDist, xDist)
 
  var magBall1:Number = Math.sqrt(ball1.xSpeed*ball1.xSpeed+ball1.ySpeed*ball1.ySpeed)
  var magBall2:Number = Math.sqrt(ball2.xSpeed*ball2.xSpeed+ball2.ySpeed*ball2.ySpeed)
 
  var angleBall1:Number = Math.atan2(ball1.ySpeed, ball1.xSpeed)
  var angleBall2:Number = Math.atan2(ball2.ySpeed, ball2.xSpeed)
 
  var xSpeedBall1:Number = magBall1 * Math.cos(angleBall1-collisionAngle)
  var ySpeedBall1:Number = magBall1 * Math.sin(angleBall1-collisionAngle)
  var xSpeedBall2:Number = magBall2 * Math.cos(angleBall2-collisionAngle)
  var ySpeedBall2:Number = magBall2 * Math.sin(angleBall2-collisionAngle)
 
  var finalxSpeedBall1:Number = ((ball1.mass-ball2.mass)*xSpeedBall1+(ball2.mass+ball2.mass)*xSpeedBall2)/(ball1.mass+ball2.mass)
  var finalxSpeedBall2:Number = ((ball1.mass+ball1.mass)*xSpeedBall1+(ball2.mass-ball1.mass)*xSpeedBall2)/(ball1.mass+ball2.mass)
  var finalySpeedBall1:Number = ySpeedBall1
  var finalySpeedBall2:Number = ySpeedBall2
 
  ball1.xSpeed = Math.cos(collisionAngle)*finalxSpeedBall1+Math.cos(collisionAngle+Math.PI/2)*finalySpeedBall1
  ball1.ySpeed = Math.sin(collisionAngle)*finalxSpeedBall1+Math.sin(collisionAngle+Math.PI/2)*finalySpeedBall1
  ball2.xSpeed = Math.cos(collisionAngle)*finalxSpeedBall2+Math.cos(collisionAngle+Math.PI/2)*finalySpeedBall2
  ball2.ySpeed = Math.sin(collisionAngle)*finalxSpeedBall2+Math.sin(collisionAngle+Math.PI/2)*finalySpeedBall2
}

Чтобы найти больше информации об упругих столкновениях, взгляните на hoomanr.com .


Это запускается, когда игра заканчивается.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
private function endOfGame():void
{
  tmr.stop()
  Mouse.show()
  stage.removeEventListener(MouseEvent.MOUSE_MOVE, updatePlayerBall)
  stage.removeEventListener(Event.ENTER_FRAME, gameLoop)
 
  while(eballs.length > 0)
  {
    TweenMax.to(eballs[0], 0.5, {scaleX:0, scaleY:0, ease:Bounce.easeOut})
    eballs.splice(0,1)
  }
 
  TweenMax.to(ballPlayer, 0.5, {scaleX:0, scaleY:0, ease:Bounce.easeOut})
}

Прежде всего остановим таймер. Мы снова показываем мышь. Затем мы удаляем прослушиватели событий MOUSE_MOVE и ENTER_FRAME. Наконец мы делаем все шары на сцене невидимыми.


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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private function checkBounds(ball:Ball):void
{
  if((ball.x + ball.radius) > stage.stageWidth)
  {
    ball.x = stage.stageWidth — ball.radius
    ball.xSpeed *= -1
  }
  if((ball.x — ball.radius) < 0)
  {
    ball.x = 0 + ball.radius
    ball.xSpeed *= -1
  }
  if((ball.y + ball.radius) > stage.stageHeight)
  {
    ball.y = stage.stageHeight — ball.radius
    ball.ySpeed *= — 1
  }
  if((ball.y — ball.radius) < 0)
  {
    ball.y = 0 + ball.radius
    ball.ySpeed *= — 1
  }
}

Мы завершили наш класс приложений. Теперь у нас есть рабочая игра !!!

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
package
{
  import flash.display.Sprite;
  import flash.display.Graphics;
  import flash.events.Event;
  import flash.events.TimerEvent;
  import flash.events.MouseEvent;
  import flash.geom.Matrix;
  import flash.utils.Timer;
  import flash.ui.Mouse;
  import com.greensock.TweenMax;
  import com.greensock.easing.*;
  public class Application extends Sprite
  {
 
    private var ballPlayer:Ball;
    private var eballs:Array;
    private var tmr:Timer;
    private var score:Score;
    public function Application():void
    {
      init();
    }
 
    private function init():void
    {
      ballPlayer = new PlayerBall();
      eballs = new Array();
      tmr = new Timer(10);
      score = new Score();
 
      stage.align = «TL»;
      stage.scaleMode = «noScale»;
      Mouse.hide();
 
      setBackground();
 
      score.x = stage.stageWidth / 2;
      score.y = stage.stageHeight / 2;
      stage.addChild(score);
 
      stage.addEventListener(MouseEvent.MOUSE_MOVE, updatePlayerBall);
      stage.addChild(ballPlayer);
 
      tmr.addEventListener(TimerEvent.TIMER, updateTime);
 
      stage.addEventListener(MouseEvent.CLICK, startGame);
    }
 
    private function setBackground():void
    {
      var type:String = «radial»;
      var colors:Array = [0xffffff,0xcccccc];
      var alphas:Array = [1,1];
      var ratios:Array = [0,255];
      var matr:Matrix = new Matrix();
      matr.createGradientBox(stage.stageWidth, stage.stageHeight, Math.PI / 2, 0, 0 );
      //SpreadMethod will define how the gradient is spread.
      var sprMethod:String = «pad»;
      //Start the Gradietn and pass our variables to it
      var sprite:Sprite = new Sprite();
      //Save typing + increase performance through local reference to a Graphics object
      var g:Graphics = sprite.graphics;
      g.beginGradientFill( type, colors, alphas, ratios, matr, sprMethod );
      g.drawRect(0,0,stage.stageWidth,stage.stageHeight);
      stage.addChild(sprite);
    }
 
    private function updatePlayerBall(e:MouseEvent):void
    {
      ballPlayer.x = mouseX;
      ballPlayer.y = mouseY;
    }
 
    private function updateTime(e:TimerEvent):void
    {
      score.txtScore.text = String(((tmr.currentCount*tmr.delay)/1000).toFixed(2));
      if ((tmr.currentCount*tmr.delay) % 5000 == 0)
      {
        addBall();
      }
    }
 
    private function startGame(e:MouseEvent):void
    {
      stage.removeEventListener(MouseEvent.CLICK, startGame);
      addBall();
      addBall();
      addBall();
      tmr.start();
      stage.addEventListener(Event.ENTER_FRAME, gameLoop);
    }
 
    private function addBall():void
    {
      var ball:Ball = new Ball(10,Math.random() * Math.PI * 2,5);
      ball.x = Math.random() * stage.stageWidth;
      ball.y = Math.random() * stage.stageHeight;
      ball.alpha = 0;
      stage.addChild(ball);
      TweenMax.to(ball, 0.5, {alpha:1});
      TweenMax.to(ball, 0, {delay: 1, onComplete:function():void{eballs.push(ball)}});
    }
 
    private function gameLoop(e:Event):void
    {
      for (var i:uint = 0; i < eballs.length; i++)
      {
        for (var j:uint = i + 1; j < eballs.length; j++)
        {
          if (collision(eballs[i],eballs[j]))
          {
            doCollision(eballs[i], eballs[j]);
          }
        }
 
        if (collision(eballs[i],ballPlayer))
        {
          endOfGame();
          break;
        }
 
        eballs[i].update();
        checkBounds(eballs[i]);
      }
    }
 
    private function collision(ball1:Ball, ball2:Ball):Boolean
    {
      var xDist:Number = ball1.x — ball2.x;
      var yDist:Number = ball1.y — ball2.y;
      var Dist:Number = Math.sqrt(xDist * xDist + yDist * yDist);
 
      if (Dist <= ball1.radius + ball2.radius)
      {
        if (ball1.x < ball2.x)
        {
          ball1.x -= 2;
          ball2.x += 2;
        }
        else
        {
          ball1.x += 2;
          ball2.x -= 2;
        }
 
        if (ball1.y < ball2.y)
        {
          ball1.y -= 2;
          ball2.y += 2;
        }
        else
        {
          ball1.y += 2;
          ball2.y -= 2;
        }
      }
 
 
      return Dist <= ball1.radius + ball2.radius;
    }
 
    private function doCollision(ball1:Ball, ball2:Ball):void
    {
      var xDist:Number = ball1.x — ball2.x;
      var yDist:Number = ball1.y — ball2.y;
      var collisionAngle:Number = Math.atan2(yDist,xDist);
      var magBall1:Number = Math.sqrt(ball1.xSpeed * ball1.xSpeed + ball1.ySpeed * ball1.ySpeed);
      var magBall2:Number = Math.sqrt(ball2.xSpeed * ball2.xSpeed + ball2.ySpeed * ball2.ySpeed);
      var angleBall1:Number = Math.atan2(ball1.ySpeed,ball1.xSpeed);
      var angleBall2:Number = Math.atan2(ball2.ySpeed,ball2.xSpeed);
      var xSpeedBall1:Number = magBall1 * Math.cos(angleBall1 — collisionAngle);
      var ySpeedBall1:Number = magBall1 * Math.sin(angleBall1 — collisionAngle);
      var xSpeedBall2:Number = magBall2 * Math.cos(angleBall2 — collisionAngle);
      var ySpeedBall2:Number = magBall2 * Math.sin(angleBall2 — collisionAngle);
      var finalxSpeedBall1:Number = ((ball1.mass-ball2.mass)*xSpeedBall1+(ball2.mass+ball2.mass)*xSpeedBall2)/(ball1.mass+ball2.mass);
      var finalxSpeedBall2:Number = ((ball1.mass+ball1.mass)*xSpeedBall1+(ball2.mass-ball1.mass)*xSpeedBall2)/(ball1.mass+ball2.mass);
      var finalySpeedBall1:Number = ySpeedBall1;
      var finalySpeedBall2:Number = ySpeedBall2;
      ball1.xSpeed = Math.cos(collisionAngle) * finalxSpeedBall1 + Math.cos(collisionAngle + Math.PI / 2) * finalySpeedBall1;
      ball1.ySpeed = Math.sin(collisionAngle) * finalxSpeedBall1 + Math.sin(collisionAngle + Math.PI / 2) * finalySpeedBall1;
      ball2.xSpeed = Math.cos(collisionAngle) * finalxSpeedBall2 + Math.cos(collisionAngle + Math.PI / 2) * finalySpeedBall2;
      ball2.ySpeed = Math.sin(collisionAngle) * finalxSpeedBall2 + Math.sin(collisionAngle + Math.PI / 2) * finalySpeedBall2;
    }
 
    private function endOfGame():void
    {
      tmr.stop();
      Mouse.show();
      stage.removeEventListener(MouseEvent.MOUSE_MOVE, updatePlayerBall);
      stage.removeEventListener(Event.ENTER_FRAME, gameLoop);
 
      while (eballs.length > 0)
      {
        TweenMax.to(eballs[0], 0.5, {scaleX:0, scaleY:0, ease:Bounce.easeOut});
        eballs.splice(0,1);
      }
 
      TweenMax.to(ballPlayer, 0.5, {scaleX:0, scaleY:0, ease:Bounce.easeOut});
    }
 
    private function checkBounds(ball:Ball):void
    {
      if ((ball.x + ball.radius) > stage.stageWidth)
      {
        ball.x = stage.stageWidth — ball.radius;
        ball.xSpeed *= -1;
      }
      if ((ball.x — ball.radius) < 0)
      {
        ball.x = 0 + ball.radius;
        ball.xSpeed *= -1;
      }
      if ((ball.y + ball.radius) > stage.stageHeight)
      {
        ball.y = stage.stageHeight — ball.radius;
        ball.ySpeed *= -1;
      }
      if ((ball.y — ball.radius) < 0)
      {
        ball.y = 0 + ball.radius;
        ball.ySpeed *= -1;
      }
    }
  }
}

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

Надеюсь, вам понравился этот урок, спасибо за чтение!