Изучение сил представляет центральный интерес в динамике, изучение причин движения и изменений в движении. Гравитационная сила является одним из примеров; это то, что заставляет спутники вращаться вокруг планет, и мы остаемся на земле.
В этом уроке мы построим симуляцию такого явления и сможем наблюдать, экспериментировать и играть с частицами на сцене.
Среди всех генерируемых частиц одна главная частица привлечет другие. Когда эти частицы движутся к основной части, пользователи могут щелкнуть эту основную частицу, чтобы тащить ее вокруг, заставляя эти частицы перенаправить свой курс. Эти частицы перестанут двигаться, сталкиваясь с краем основного шара, но они не будут перекрывать друг друга.
Структура этого учебного пособия организована таким образом, чтобы вкратце изложить теорию по физике, прежде чем вводить реализацию моделирования. Наслаждайтесь!
Окончательный результат предварительного просмотра
Давайте посмотрим на конечный результат, к которому мы будем стремиться:
Нажмите и перетащите большой зеленый круг, чтобы переместить его, и посмотрите, как реагируют маленькие синие круги.
Шаг 1: Гравитационная сила, Формула
Во-первых, предисловие по физике. Сила притяжения между любыми двумя объектами выражается через следующую формулу:
F : сила притяжения, воздействующая на интересующий объект (p 2 )
произвольная частица (p 1 ).
G : гравитационная постоянная
м 1 : масса р 1
м 2 : масса р 2
r : расстояние между p 1 и p 2
Обратите особое внимание на следующее:
- Соотношение между гравитацией и расстоянием: F обратно пропорционально квадрату расстояния, разделяющего две частицы. Это означает, что чем ближе А и В находятся друг к другу, тем выше сила притяжения между ними и наоборот. Если вы удвоите расстояние, сила уменьшится до четверти от первоначального значения.
- Значение гравитационной постоянной, G, научно составляет 6,67259 х 10 -11 Н * м 2 / кг 2 . Однако 500 заменит это значение в среде Flash.
- Мы можем связать ширину частиц с их массой. Для этого примера я определил массу частицы равной половине ее радиуса.
Шаг 2: 2- й закон Ньютона, уравнение
Чтобы перевести силу в кинематику, нам нужно рассчитать ускорение частицы. Знаменитое уравнение сэра Исаака Ньютона показано ниже:
F : сила гравитации, действующая на интересующий объект (p 2 )
m : масса объекта интереса (p 2 )
a : ускорение объекта интереса (p 2 ) под воздействием F
Здесь подразумевается, что более высокая сила, приложенная к частице A, приводит к более высокому ускорению (при условии, что ее масса остается неизменной). Это ускорение изменит скорость частицы.
Шаг 3: Запуск проекта
Реализация будет осуществляться в FlashDevelop IDE. Создайте свой файл проекта.
- Начать новый проект, ПРОЕКТ> НОВЫЙ ПРОЕКТ…
- Выберите во всплывающем окне AS3 PROJECT
- Назовите свой проект. В моем случае Аттрактор
- Выберите местоположение вашего проекта
См. Это руководство для ознакомления с FlashDevelop.
Шаг 4: Классы, которые вам нужны
В папке \ src \ можно создать 4 класса: Main.as, Vector2D.as, Ball.as & Math2.as. Желательно, чтобы вы загрузили все эти файлы из исходного пакета и попытались сопоставить их с шагами, чтобы прийти к общему пониманию механизма перед их изменением. Их роли выражены следующим образом:
Имя класса | Цель организации |
Main.as | Класс для визуального создания шаров и добавления анимации к событиям. |
Vector2D | Класс, который содержит все функции векторного управления. |
Мяч | Класс, который содержит функции для визуального генерирования мяча, реализует динамику и кинематику мяча. |
Math2 | Статический класс, который содержит функцию для облегчения рандомизации начального местоположения шаров. |
Шаг 5: рандомизация значений
Давайте сначала поговорим о классе Math2. Функция ниже поможет сгенерировать случайное число в указанном диапазоне. Принимает два входа, минимальный предел и максимальный предел в диапазоне.
1
2
3
4
5
6
|
public static function randomiseBetween(range_min:int, range_max:int):int
{
var range:int = range_max — range_min;
var randomised:int = Math.random() * range + range_min;
return randomised;
}
|
Шаг 6: Vector2D, геттеры и сеттеры
Основная часть используемого Math находится в Vector2D. В этом руководстве предполагается, что пользователи знакомы с векторным анализом. Приведенные ниже функции обычно используются для получения и установки векторных компонентов, а также для сброса всех компонентов на ноль. В любом случае, если вас не устраивает «Векторы», посетите большую статью о евклидовых векторах Даниила Сидиона.
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
|
public function Vector2D(valueX:Number, valueY:Number)
{
this._x = valueX;
this._y = valueY;
}
public function set vecX(valueX:Number):void
{
this._x = valueX;
}
public function get vecX():Number
{
return this._x
}
public function set vecY(valueY:Number):void
{
this._y = valueY;
}
public function get vecY():Number
{
return this._y
}
public function setVector(valueX:Number, valueY:Number):void
{
this._x = valueX;
this._y = valueY;
}
public function reset():void
{
this._x = 0;
this._y = 0;
}
|
Шаг 7: Vector2D, Операции
Основное использование Vector2D заключается в следующих функциях:
- получить величину вектора
- получить угол, сделанный вектором относительно начала координат
- получить вектор направления вектора
- выполнять простые векторные операции сложения, вычитания и скаляра
умножение
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
|
public function getMagnitude():Number
{
var lengthX:Number = this._x;
var lengthY:Number = this._y;
return Math.sqrt(lengthX * lengthX +lengthY * lengthY);
}
public function getAngle():Number
{
var lengthX:Number = this._x;
var lengthY:Number = this._y;
return Math.atan2(lengthY, lengthX);
}
public function getVectorDirection():Vector2D
{
var vectorDirection:Vector2D = new Vector2D(this._x / this.getMagnitude(), this._y / this.getMagnitude());
return Vector2D(vectorDirection);
}
public function minusVector(vector2:Vector2D):void
{
this._x -= vector2.vecX;
this._y -= vector2.vecY;
}
public function addVector(vector2:Vector2D):void
{
this._x += vector2.vecX;
this._y += vector2.vecY;
}
public function multiply (scalar:Number):void
{
this._x *= scalar;
this._y *= scalar;
}
|
Шаг 8: Рисунок Ball.as
Класс Ball
— это место, где проходят все интересные операции. Чтобы начать анимацию, нам нужно нарисовать шар и установить несколько переменных, связанных с кинематикой и динамикой. Функция рисования мяча:
1
2
3
4
5
6
|
private function draw(radius:Number, color:uint) :void
{
graphics.beginFill(color, 1);
graphics.drawCircle(0, 0, radius);
graphics.endFill();
}
|
Шаг 9: Ball.as Частные переменные
Упомянутые несколько переменных, связанных с кинематикой и динамикой, указаны ниже:
1
2
3
4
5
|
private var _disp:Vector2D;
private var _velo:Vector2D;
private var _acc:Vector2D;
private var _attractive_coeff:Number = 500;
private var _mass:Number;
|
Шаг 10: Инициирование Ball.as
Как называется конструктор класса Ball, графика рисуется. После розыгрыша мяч будет случайно размещен на сцене. Мы также установим приватные переменные. Все векторные величины также будут инициализированы в 0, за исключением смещения, которое измеряется относительно начала координат.
01
02
03
04
05
06
07
08
09
10
|
public function Ball(radius:Number = 20, color:uint = 0x0000FF)
{
this.draw(radius, color);
this._mass = radius / 2;
this.x = Math2.randomiseBetween(0, 550);
this.y = Math2.randomiseBetween(0, 400);
this._disp = new Vector2D(this.x, this.y);
this._velo = new Vector2D(0, 0);
this._acc = new Vector2D(0, 0);
}
|
Шаг 11: Ball.as рассчитать силу притяжения
Нам нужно рассчитать основную силу, которая вызывает оживление наших частиц. Угадай что, это гравитационная сила. Функция ниже поможет в расчете этой силы. Обратите внимание, что ограничение ускорения на 5
применяется. Горизонтальные и вертикальные компоненты силы получены с использованием тригонометрии; Анимация выше может помочь в понимании математики этого.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public function get mass():Number
{
return _mass;
}
private function getForceAttract (m1:Number, m2:Number, vec2Center:Vector2D):Vector2D
{
/* calculate attractive force based on the following formula:
* F = K * m1 * m2 / r * r
*/
var numerator:Number = this._attractive_coeff * m1 * m2;
var denominator:Number = vec2Center.getMagnitude() * vec2Center.getMagnitude();
var forceMagnitude:Number = numerator / denominator;
var forceDirection:Number = vec2Center.getAngle();
//setting a cap
if (forceMagnitude > 0) forceMagnitude = Math.min(forceMagnitude, 5);
//deriving force component, horizontal, vertical
var forceX:Number = forceMagnitude * Math.cos(forceDirection);
var forceY:Number = forceMagnitude * Math.sin(forceDirection);
var force:Vector2D = new Vector2D(forceX, forceY);
return force;
}
|
Шаг 12: Ball.as рассчитать ускорение
Как только вектор силы получен, мы можем рассчитать результирующее ускорение. Помните, что F = ma
, поэтому a = F/m
.
1
2
3
4
5
6
|
public function getAcc(vecForce:Vector2D):Vector2D
{
//setting acceleration due to force
var vecAcc:Vector2D = vecForce.multiply(1 / this._mass);
return veccAcc;
}
|
Шаг 13: Ball.as Рассчитать смещение
Рассчитав ускорение, мы можем эффективно рассчитать результирующее смещение.
Помните, что рассчитанная сила фактически основана на смещении между центрами шаров.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
private function getDispTo(ball:Ball):Vector2D
{
var currentVector:Vector2D = new Vector2D(ball.x, ball.y);
currentVector.minusVector(this._disp);
return currentVector;
}
public function attractedTo(ball:Ball) :void
{
var toCenter:Vector2D = this.getDispTo(ball);
var currentForceAttract:Vector2D = this.getForceAttract(ball.mass, this._mass, toCenter);
this._acc = this.getAcc(currentForceAttract);
this._velo.addVector(this._acc);
this._disp.addVector(this._velo);
}
|
Шаг 14: Ball.as реализует смещение
Затем мы можем переместить наш мяч на новое место с помощью функции ниже. Обратите внимание, что вычисленное смещение никогда не применяется к текущему местоположению мяча сразу. Такая конструкция позволяет проводить проверку: обнаружение столкновений между шариками.
1
2
3
4
5
|
public function setPosition(vecDisp:Vector2D):void
{
this.x = Math.round(vecDisp.vecX);
this.y = Math.round(vecDisp.vecY);
}
|
Помните, что сила основана на расстоянии между центрами. Следовательно, шары будут проникать и продолжать движение из-за того, что сила притяжения выше, когда они ближе. Нам нужно сбросить ускорение и скорость до 0, когда шары касаются края друг друга. Однако нам нужно получить средство обнаружения столкновения двух шаров.
Шаг 15: Обнаружение столкновения Ball.as
Столкновение можно легко проверить. Разделение между любыми двумя шарами не должно быть меньше суммы их радиусов. Вот функция обнаружения столкновений:
01
02
03
04
05
06
07
08
09
10
11
12
|
public function collisionInto (ball:Ball):Boolean
{
var hit:Boolean = false;
var minDist:Number = (ball.width + this.width) / 2;
if (this.getDispTo(ball).getMagnitude() < minDist)
{
hit = true;
}
return hit;
}
|
Шаг 16: Ball.as рассчитать смещение, чтобы отразить
Обычно, когда обнаруживается столкновение между двумя шарами, их состояние перекрывает друг друга. Нам нужно убедиться, что они будут просто сидеть на краю и не перекрываться. Как? Мы можем сместить один из шариков подальше от другого, но нам нужно сначала рассчитать правильное смещение, чтобы скорректировать. Вот расчет смещения:
01
02
03
04
05
06
07
08
09
10
11
|
public function getRepel (ball:Ball): Vector2D
{
var minDist:Number = (ball.width + this.width) / 2;
//calculate distance to repel
var toBall:Vector2D = this.getDispTo(ball);
var directToBall:Vector2D = toBall.getVectorDirection();
directToBall.multiply(minDist);
directToBall.minusVector(toBall);
directToBall.multiply( -1);
return directToBall;
}
|
Шаг 17: Ball.as реализует смещение, чтобы отразить
После того, как мы рассчитали правильное смещение, нам нужно его реализовать. Действие похоже на отталкивание одного из шаров. Кроме того, нам нужно сделать еще две дополнительные команды. Помните, мы имеем дело с динамичной средой. Даже после того, как мы установили смещение одного из шариков к краю, ускорение из-за силы и результирующей скорости оживит его, вызывая нежелательное движение толчков внутрь и наружу. Нам нужно сбросить эти значения ускорения и скорости на ноль.
1
2
3
4
5
6
7
|
public function repelledBy(ball:Ball):void
{
this._acc.reset();
this._velo.reset();
var repelDisp:Vector2D = getRepel(ball);
this._disp.addVector(repelDisp);
}
|
Шаг 18: Ball.as Animate
Наконец, мы можем анимировать (визуализировать) наш шар, как если бы он был привлечен другим. При обнаружении столкновения смещение будет отрегулировано так, чтобы оно не проникало через край. Это произойдет сначала для шаров, когда они сталкиваются с центром, а затем для шаров, когда они сталкиваются друг с другом.
01
02
03
04
05
06
07
08
09
10
11
|
public function animate(center:Ball, all:Array):void
{
this.attractedTo(center);
if (collisionInto(center)) this.repelledBy(center);
for (var i:int = 0; i < all.length; i++)
{
var current_ball:Ball = all[i] as Ball;
if (collisionInto(current_ball) && current_ball.name != this.name) this.repelledBy(current_ball);
}
this.setPosition(this._disp);
}
|
Шаг 19: Main.as частные переменные
Переходя к нашему последнему занятию, Main
. Основной класс генерируется в начале проекта. Частные переменные будут включать один шар, который привлекает все остальные, и количество шариков в нашей Flash-презентации.
1
2
|
private var mainBall:Ball;
private var totalBalls:int = 10;
|
Шаг 20: Main.as рисовать шары
Прежде всего, мы должны инициализировать шары. Будет один главный мяч, который привлечет всех остальных. Другие названы так, что ссылки могут быть легко сделаны позже.
01
02
03
04
05
06
07
08
09
10
11
|
private function createBalls ():void
{
mainBall = new Ball(50, 0x00FF00);
this.addChild(mainBall);
for (var i:int = 0; i < this.totalBalls; i++)
{
var currentBall:Ball = new Ball();
currentBall.name = «ball» + i;
this.addChild(currentBall);
}
}
|
Шаг 21: Main.as реализуем взаимодействие с мячом
Затем назначьте события главному шару, чтобы сделать его перетаскиваемым при нажатии, и остановите его при отпускании.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
createBalls();
mainBall.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
animateAll();
}
private function onMouseUp(e:MouseEvent):void
{
stopDrag();
}
private function onMouseDown(e:MouseEvent):void
{
e.target.startDrag();
}
|
Шаг 22: Main.as анимационные шары
Анимационные шары, которые притягиваются главным. Событие EnterFrame назначается каждому шару.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
private function animateAll():void
{
for (var i:uint = 0; i < totalBalls; i++)
{
//each ball is pulled by main_ball
var current_ball:Ball = this.getChildByName(«ball» + i) as Ball;
current_ball.addEventListener(Event.ENTER_FRAME, enterFrame);
}
}
private function enterFrame(e:Event):void
{
var allObj:Array = new Array();
for (var i:int = 0; i <= totalBalls; i++)
{
var current_ball:Ball = this.getChildAt(i) as Ball;
allObj.push(current_ball);
}
e.target.animate(mainBall, allObj);
}
|
Шаг 23: Тестируй фильм
Наконец, нажмите Ctrl + Enter, чтобы просмотреть анимацию.
Вывод
Чтобы продвинуть этот учебник еще дальше, читатели могут расширить этот проект, применяя другие линейные силы.
В любом случае, симуляции служат отличным инструментом для предоставления идей, которые трудно объяснить простым текстом и изображением в классной комнате Физики, особенно когда состояние меняется со временем.
Я надеюсь, что это небольшое руководство поможет вам в некотором роде. Terima kasih (это «спасибо» в Малайзии) за то, что нашли время прочитать и с нетерпением ждем комментариев от других читателей.