i've been studying this code example for Rung-Kutta physics but i don't understand what is happening with the acceleration(p:Point, v:Point):Point function. the function accepts 2 point objects as required arguments but doesn't use them in the function while simply returning a new point.
i'm unfamiliar with this style of argument passing. can someone explain the significance of this function to me?
the source is from Keith Peters' book Advanced ActionScript 3.0 Animation, Chapter 6 - Advanced Physics, page 246.
package {
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.geom.Point;
import flash.utils.getTimer;
public class RK2 extends Sprite
{
private var _ball:Sprite;
private var _position:Point;
private var _velocity:Point;
private var _gravity:Number = 32;
private var _bounce:Number = -0.6;
private var _oldTime:int;
private var _pixelsPerFoot:Number = 10;
public function RK2()
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
_ball = new Sprite();
_ball.graphics.beginFill(0xff0000);
_ball.graphics.drawCircle(0, 0, 20);
_ball.graphics.endFill();
_ball.x = 50;
_ball.y = 50;
addChild(_ball);
_velocity = new Point(10, 0);
_position = new Point(_ball.x / _pixelsPerFoot, _ball.y / _pixelsPerFoot);
_oldTime = getTimer();
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void
{
var time:int = getTimer();
var elapsed:Number = (time - _oldTime) / 1000;
_oldTime = time;
var accel1:Point = acceleration(_position, _velocity);
var position2:Point = new Point();
position2.x = _position.x + _velocity.x * elapsed;
position2.y = _position.y + _velocity.y * elapsed;
var velocity2:Point = new Point();
velocity2.x = _velocity.x + accel1.x * elapsed;
velocity2.y = _velocity.y + accel1.x * elapsed;
var accel2:Point = acceleration(position2, velocity2);
_position.x += (_velocity.x + velocity2.x) / 2 * elapsed;
_position.y += (_velocity.y + velocity2.y) / 2 * elapsed;
_velocity.x += (accel1.x + accel2.x) / 2 * elapsed;
_velocity.y += (accel1.y + accel2.y) / 2 * elapsed;
if(_position.y > (stage.stageHeight - 20) / _pixelsPerFoot)
{
_position.y = (stage.stageHeight - 20) / _pixelsPerFoot;
_velocity.y *= _bounce;
}
if(_position.x > (stage.stageWidth - 20) / _pixelsPerFoot)
{
_position.x = (stage.stageWidth - 20) / _pixelsPerFoot;
_velocity.x *= _bounce
}
else if(_position.x < 20 / _pixelsPerFoot)
{
_position.x = 20 / _pixelsPerFoot;
_velocity.x *= _bounce;
}
_ball.x = _position.x * _pixelsPerFoot;
_ball.y = _position.y * _pixelsPerFoot;
}
private function acceleration(p:Point, v:Point):Point
{
return new Point(0, _gravity);
}
}
}
I think the author may be using the method acceleration as a place holder, perhaps for updates on a subsequent chapter.
Of course as it is right now, the acceleration method could be rewritten as
private function acceleration(...rest):Point {
return new Point(0, _gravity);
}
Or the arguments could be removed completely (though that would require the places where the method is called to be updated to not contain any arguments.)
This isn't a style of programming per se, but, I have seen this type of placeholder code put into books before.
You also could set the arguments with a null as default, so they are optional.
private function acceleration(p:Point = null, v:Point = null):Point
{
return new Point(0, _gravity);
}
Related
[edit]
I was really stupid, all is working fine now.
So forget about this dummy question!
The rotation of the main clip was a big mistake.
I've changed this by adding a var called _rota with getter and setters.
I had not to rotate the clip but just to place another Sprite in it, so I can place the sub-Sprite in the right direction by using a simple function.
So I avoid all those loops...
My mistake SRY.
I just added a Sprite which have the rotation of the Main Sprite.
Changing the rotation of the main Sprite was the reason of this issue...
So, thank you and forget about this unclear question!!! :)
private function drawLine():void{
if(!clip){
clip = new Sprite();
addChild(clip);
}
var g:Graphics = clip.graphics;
g.clear();
g.lineStyle(1,0xffffff,1);
g.beginFill(0xffffff,1);
g.drawCircle(Math.sin(rota)*this.width/4,Math.cos(rota)*this.height/4,3);
g.endFill();
}
I was changing the rotation property of the clip, so it was usefulness
Now I have a pretty good result.
Solved...
Sorry again...
As you can see the particles are now set in the right direction an I have no more hitTest issues...
Particles are now moving on the direction showed by the white points.
[/edit]
The first thing that pops out at me is you're potentially modifying the position of both x and y properties twice.
If you run the logic once, and store your directionality, then you should be able to update the position of your ball in one go.
Replace your moveBall function with the following...
private var h:int = 1;
private var v:int = 1;
public function moveBall(e:Event):void {
speedx = Math.sin(deg2rad(rotation+90))*speed;
speedy = Math.cos(deg2rad(rotation+90))*speed;
if (x + radius + (speedx * h) > this.loaderInfo.width || (x + (speedx * h) - radius < 0)) {
h *= -1;
}
if (y + radius + (speedy * v) > this.loaderInfo.height || (y + (speedx * v) - radius < 0)) {
v *= -1;
}
this.x += (speedx * h);
this.y += (speedy * v);
}
As I need to set a Sprite in the right direction when the Ball instance change it's "pseudo rotation" (I avoid here the hitTest features)...
I've now two classes...
Do you thing I'm searching in the bright side of code or is it totally unclear? ;)
This is just a test, and I didn't spent time to code since a few years.
So this test is just to revise some basics about trigonometry...
Don't hesitate, to be rude if I'm wrong!
My new class Main :
package com
{
import com.display.Ball;
import flash.display.Graphics;
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
[SWF(width = "400", height = "300", frameRate = "60", backgroundColor = "#dddddd")]
public class Main extends MovieClip
{
private var b1:Ball;
private var b2:Ball;
private var b3:Ball;
private var b4:Ball;
private var b5:Ball;
private var testClip:Sprite;
private const ANGLE_TOP_LEFT:int=135;
private const ANGLE_BOTTOM_LEFT:int=-135;
private const ANGLE_TOP_RIGHT:int=45;
private const ANGLE_BOTTOM_RIGHT:int=-45;
public function Main()
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
trace("stageSize = " + getStageSize() + ", fps = " + stage.frameRate);
drawlineGuides();
addBalls();
stage.addEventListener(Event.RESIZE,onStageResize);
}
private function addBalls():void{
b1 = new Ball(500/2,250/2,10);
addChild(b1);
b1.color = 0x6666cc;
b1.rota = 135;
b1.drawBall();
b1.move(5);
b2 = new Ball(100,100,10);
addChild(b2);
b2.color = 0xff9900;
b2.rota = -110;
b2.drawBall();
b2.move(4);
b3 = new Ball(50,80,10);
addChild(b3);
b3.color = 0xff0000;
b3.rota = 60;
b3.drawBall();
b3.move(3);
b4 = new Ball(75,20,10);
addChild(b4);
b4.color = 0x00aa00;
b4.rota = 10;
b4.drawBall();
b4.move(4);
b5 = new Ball(125,130,10);
addChild(b5);
b5.color = 0x8457a2;
b5.rota = -45;
b5.drawBall();
b5.move(4);
stage.addEventListener(MouseEvent.MOUSE_DOWN,b1.pauseResume);
stage.addEventListener(MouseEvent.MOUSE_DOWN,b2.pauseResume);
stage.addEventListener(MouseEvent.MOUSE_DOWN,b3.pauseResume);
stage.addEventListener(MouseEvent.MOUSE_DOWN,b4.pauseResume);
stage.addEventListener(MouseEvent.MOUSE_DOWN,b5.pauseResume);
}
private function rotate(e:Event):void{
testClip.rotation = b2.rotation-45;
}
private function getStageSize():Point{
var p:Point= new Point(stage.stageWidth,stage.stageHeight);
return p;
}
private function drawlineGuides():void{
var g:Graphics = this.graphics;
g.clear();
g.lineStyle(1,0x000000,1);
g.moveTo(0,stage.stageHeight/2);
g.lineTo(stage.stageWidth,stage.stageHeight/2);
g.moveTo(stage.stageWidth/2,0);
g.lineTo(stage.stageWidth/2,stage.stageHeight);
}
private function onStageResize(e:Event):void{
drawlineGuides();
}
}
}
And here is my new class Ball :
package com.display
{
/* this import is optionnal
if you want to run this class without the BongSound instance
comment all lines where the var bSound is called
*/
//import com.media.sound.BongSound;
import flash.display.Graphics;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.media.Sound;
public class Ball extends Sprite
{
private var _radius:int;
private var _rotation:Number;
private var _color:int;
private var _g:Graphics;
private var _g2:Graphics;
private var _speed:Number;
private var speedx:Number;
private var speedy:Number;
public var rota:Number;
private var smallCircle:Sprite;
private var rendered:Boolean = false;
public var paused:Boolean = false;
private const ZERO:uint = 0;
//private var bSound:BongSound;
/**
* Ball(posx:Number,posy:Number,radius:uint)<br/>
* this constructor create an instance of a bouncing ball<br/>
* the posx and posy must be included in the range of the defined stageWidth and stageHeight!<br/>
* Otherwise, the ball will be placed in the stage range.
*/
public function Ball(posx:Number,posy:Number,radius:uint)
{
//bSound = new BongSound();
smallCircle = new Sprite();
this.addChild(smallCircle);
this._radius = radius;
this.x = posx;
this.y = posy;
_g = this.graphics;
_g2 = smallCircle.graphics;
}
private function checkStageSize():void{
if(this.x + radius + speedx >= this.stage.stageWidth){
this.x = this.stage.stageWidth - this.width;
}
if(this.y + radius + speedy >= this.stage.stageHeight){
this.y = this.stage.stageHeight - this.height;
}
if(this.x - radius + speedx <= ZERO){
this.x = this.width;
}
if(this.y - radius + speedy <= ZERO){
this.y = this.height;
}
}
public function get speed():Number
{
return _speed;
}
public function set speed(value:Number):void
{
_speed = value;
}
public function get color():int
{
return _color;
}
public function set color(value:int):void
{
_color = value;
}
public function get radius():int
{
return _radius;
}
public function set radius(value:int):void
{
_radius = value;
}
/**
* drawBall()<br/>
* this function draws the main Ball Object
*/
public function drawBall():void
{
_g.clear();
_g.lineStyle(1,0x666666,1);
_g.beginFill(_color,1);
_g.drawCircle(0,0,this._radius);
_g.endFill();
_g.lineStyle(1,0x666666,1);
_g.beginFill(0xffffff,1);
_g.endFill();
}
/**
* drawPoint()<br/>
* this function draws the Point Object wich is placed in the direction/rotation of the main Ball instance.
*/
public function drawPoint():void{
_g2.clear();
_g2.lineStyle(1,0x666666,1);
_g2.beginFill(0xffffff,1);
_g2.drawCircle(ZERO, ZERO, this._radius/2);
smallCircle.x = Math.sin(deg2rad(rota+90))*this.radius/2;
smallCircle.y = Math.cos(deg2rad(rota+90))*this.radius/2;
_g2.endFill();
}
/**
* move(speed:Number):void<br/>
* this function set the speed and makes the Ball move.<br/>
* The displace function is called when an ENTER_FRAME event is triggered.
*/
public function move(speed:Number):void{
this.speed = speed;
this.addEventListener(Event.ENTER_FRAME,displace);
}
/**
* getRota():Number<br/>
* this function returns the rotation of the Ball instance.<br/>
* the rotation is returned in degrees.
*/
public function getRota():Number{
return rad2deg(Math.atan2(speedy,speedx));
}
/**
* pauseResume(e:MouseEvent):void
* Pause or resume movement.
*/
public function pauseResume(e:MouseEvent):void{
switch(paused){
case false:
this.removeEventListener(Event.ENTER_FRAME,displace);
paused = true;
break;
case true:
this.addEventListener(Event.ENTER_FRAME,displace);
paused = false;
break;
}
}
/**
* checkBounds():void<br/>
* <p>
* this function plays a Sound when the Ball instance hit the bounds.<br/>
* the rota variable is updated (even if the rotation of the Ball instance don't change).<br/>
* If the stage is resized, a call to checkStageSize() set the positions x & y in the bounds of the Stage.
* </p>
* #see checkStageSize()
*/
private function checkBounds():void{
if(this.x + radius + speedx >= this.stage.stageWidth){
//bSound.play();
rota = rad2deg(Math.atan2(-speedy,-speedx));
}
if(this.y + radius + speedy >= this.stage.stageHeight){
//bSound.play();
rota = rad2deg(Math.atan2(speedy,speedx));
}
if(this.x - radius + speedx <= ZERO){
//bSound.play();
rota = rad2deg(Math.atan2(-speedy,-speedx));
}
if(this.y - radius + speedy <= ZERO){
//bSound.play();
rota = rad2deg(Math.atan2(speedy,speedx));
}
checkStageSize();
}
/**
* <p>
* displace(e:Event):void
* displace the ball and calls drawPoint to place the sub-Sprite depending of the "rotation" of the Ball instance.</p>
* #see #drawPoint()
* #see #checkBounds()
*/
private function displace(e:Event):void{
checkBounds();
speedx = Math.sin(deg2rad(rota+90))*speed;
speedy = Math.cos(deg2rad(rota+90))*speed;
this.x += speedx;
this.y += speedy;
drawPoint();
}
public function deg2rad(value:Number):Number{
return value/180*Math.PI;
}
public function rad2deg(value:Number):Number{
return value*180/Math.PI;
}
}
}
PrintScreens :
It is now possible to continue the moves even when the Stage is Resized avoiding the issues I had in the past...
In my flash-made game, if my character jump on top of an enemy movieclip it spawns 3 minions from another class by MovieClip(root).addChild(spawn1);. In my minions class I've put the code for them to fall and stop upon hitting the ground and also follow my character.
I have a VCam(virtual camera) movieClip to follow my character(who moves on the stage, not the stage around him) with this code build-in:
import flash.events.Event;
import flash.geom.Matrix;
import flash.geom.Rectangle;
import flash.geom.Point;
import flash.display.MovieClip;
//VCam
addEventListener(Event.ENTER_FRAME, handleEnterFrame);
function handleEnterFrame(event:Event):void {
if (parent) {
parent.scaleX = 1 / scaleX;
parent.scaleY = 1 / scaleY;
if (rotation == 0) {
parent.x = (width / 2 - x) / scaleX;
parent.y = (height / 2 - y) / scaleY;
parent.rotation = 0;
} else {
var bounds:Rectangle = getBounds(this);
var angle:Number = rotation * Math.PI / 180;
var midX:Number = -x / scaleX;
var midY:Number = -y / scaleY;
var rx:Number = -bounds.width / 2;
var ry:Number = -bounds.height / 2;
var cos:Number = Math.cos(angle);
var sin:Number = Math.sin(angle);
var rotatedX:Number = rx * cos - ry * sin;
var rotatedY:Number = ry * cos + rx * sin;
var cornerX:Number = midX - rotatedX;
var cornerY:Number = midY - rotatedY;
cos = Math.cos(-angle);
sin = Math.sin(-angle);
parent.x = cornerX * cos - cornerY * sin;
parent.y = cornerY * cos + cornerX * sin;
parent.rotation = -rotation;
}
}
}
addEventListener(Event.REMOVED, handleRemoved, false, 0, true);
function handleRemoved(event:Event):void
{
removeEventListener(Event.ENTER_FRAME, handleEnterFrame);
removeEventListener(Event.REMOVED, handleRemoved);
}
When I jump with my character it seems that the minions follow the movement of my vcam and not behaving normally, jumping with the camera and falling throu'the ground when the character falls.
If I add a child normally from the main timeline by addChild(m_clip); it does not behave like that.
Is there an easy fix? Thanks!
This is the minions class code:
package {
import flash.display.*;
import flash.events.*;
public class EnemySpawned extends MovieClip {
protected var gravitysp: Number = 1;
protected var ySpeedsp: Number = 0;
protected var Speedsp: Number = 6.5;
var charMTL:MovieClip;
public function EnemySpawned()
{
this.addEventListener(Event.ENTER_FRAME, movement);
trace('exist');
}
function movement(event:Event):void
{
var MTL:MovieClip = MovieClip(root);
charMTL = MTL.char1;
ySpeedsp += gravitysp;
if(! MTL.ground_1.hitTestPoint(this.x, this.y, true))
{
this.y += ySpeedsp;
}
if(ySpeedsp > 40)
{
ySpeedsp = 40;
}
for(var j:int = 0; j<20; j++)
{
if(MTL.ground_1.hitTestPoint(this.x, this.y, true))
{
this.y--
ySpeedsp = 0;
}
}
var Distance:Number = charMTL.x - this.x;
if(Distance < -charMTL.width/2 - this.width/2)
{
this.x -= Speedsp;
}
if(Distance > charMTL.width/2 + this.width/2)
{
this.x += Speedsp;
}
}
}
}
There is no moving of ground_1 movieClip via code.
I'm making sinewave sound with AS3 using SampleDataEvent. I can make a pure sinewave easily enough, but if i try to sweep through frequencies i get horrible popping. Here's the code i'm using - any help would be great.
package
{
import flash.display.*;
import flash.events.*;
import flash.media.*;
import flash.utils.Timer;
public class DynamicSound extends Sprite
{
private var sound:Sound;
private var noise:Number = 0;
private var f:Number = 1000;
private var v:Number = 1;
private var sweepDown:Boolean = true;
// make the sound
public function DynamicSound():void
{
sound = new Sound();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onCallback);
sound.play();
sweep();
}
// create the sinewave
private function onCallback(e:SampleDataEvent):void
{
for (var i:int = 0; i < 8192; i++)
{
noise += 1;
var sampleNumber = noise;
e.data.writeFloat(v * Math.sin(sampleNumber * f / 44100));
e.data.writeFloat(v * Math.sin(sampleNumber * f / 44100));
}
}
// sweep up and down frequency
private function sweep() {
var timer:Timer = new Timer(100);
timer.addEventListener(TimerEvent.TIMER, onTimer);
timer.start();
function onTimer(event:TimerEvent):void
{
if(sweepDown){
f--;
} else {
f++;
}
if (f <=600 ){
sweepDown = false;
}
if (f >= 1000) {
sweepDown = true;
}
trace(f);
}
}
}
}
I believe the fault is with the approach, the sweep needs to be gradual, when you step it like that you create an abrupt change in the sound wave, which is interpreted as a short high-frequency signal - a pop or click. The way I'd recommend you do this modulation would be inside the callback loop.
set a destination freq (dF) and a current frequency(cF), and instead of doing an abrupt change set cF = cF*0.8 + dF*0.2 inside the loop, this should remove the abrupt change and have it happen over several samples.
//When the frequency changes, the phase will also change.
//By adjusting the phase, there will no longer be horrible popping.
//This will solve the problem:
var f_old:Number = f;
noise=noise*f_old/f; f_old=f;
//Copy/paste in the first frame of the main timeline:
import flash.display.*;
import flash.events.*;
import flash.media.*;
import flash.utils.Timer;
var sound:Sound;
var noise:Number = 0;
var f:Number = 1000;
var v:Number = 1;
var sweepDown:Boolean = true;
var f_old:Number = f;
// make the sound
sound = new Sound();
sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onCallback);
sound.play();
sweep();
// create the sinewave
function onCallback(e:SampleDataEvent):void
{
for (var i:int = 0; i < 8192; i++)
{
noise += 1;
var sampleNumber = noise;
e.data.writeFloat(v * Math.sin(sampleNumber * f / 44100));
e.data.writeFloat(v * Math.sin(sampleNumber * f / 44100));
}
}
// sweep up and down frequency
function sweep() {
var timer:Timer = new Timer(100);
timer.addEventListener(TimerEvent.TIMER, onTimer);
timer.start();
function onTimer(event:TimerEvent):void
{
if(sweepDown){
f--;
} else {
f++;
}
if (f <=600 ){
sweepDown = false;
}
if (f >= 1000) {
sweepDown = true;
}
trace(f);
//Keep the instantaneous value at the same level:
noise=noise*f_old/f;
f_old=f;
}
}
//Explanation:
//The instantaneous value of the wave must not change at the moment of frequency change.
//In this script the instantaneous value is determined by:
//v * Math.sin(sampleNumber * f / 44100)
//noise = sampleNumber → v * Math.sin(sampleNumber * f / 44100) = v * Math.sin(noise * f / 44100)
//Keep the instantaneous value at the same level:
//v * Math.sin(noise_new * f / 44100) = v * Math.sin(noise* f_old / 44100)
//Cancel out v*Math.sin and /44100 →
//noise_new * f = noise* f_old → noise_new = noise*f_old/f
//Because noise gets a new value, there is no need to give noise the name noise_new. →
//noise = noise*f_old/f
Hello guys i have a problem and no idea how to fix it :( Can someone tell me how to do it?
Constructor functions must be instance methods.
So here is my code:
package
{
import com.coreyoneil.collision.CollisionList;
import flash.events.Event;
import flash.display.Sprite;
public class terrain extends Sprite
{
private var wheel:Ball;
private var collisionList:CollisionList;
private var speed:Number;
private const GRAVITY:Number = .75;
private const FRICTION:Number = .98;
private const IMMOVABLE:Number = 10000;
public function terrain():void
{
if(stage == null)
{
addEventListener(Event.ADDED_TO_STAGE, init, false, 0, true);
addEventListener(Event.REMOVED_FROM_STAGE, clean, false, 0, true);
}
else
{
init();
}
}
private function init(e:Event = null):void
{
collisionList = new CollisionList(terrain);
wheel = new wheel(10);
wheel.mass = IMMOVABLE * 2;
addChild(wheel);
collisionList.addItem(wheel);
wheel.x = 30;
wheel.y = 10;
speed = 0;
terrain.graphics.lineStyle(15);
addEventListener(Event.ENTER_FRAME, updateScene);
}
private function updateScene(e:Event):void
{
var collisions:Array = collisionList.checkCollisions();
if(collisions.length)
{
var collision:Object = collisions[0];
var angle:Number = collision.angle;
var overlap:int = collision.overlapping.length;
var sin:Number = Math.sin(angle);
var cos:Number = Math.cos(angle);
var vx0:Number = wheel.vx * cos + wheel.vy * sin;
var vy0:Number = wheel.vy * cos - wheel.vx * sin;
// Unlike the other examples, here I'm choosing to calculate the amount
// of bounce based on the objects' masses, with a default mass of 10000 (IMMOVABLE)
// being used for the drawing the wheel is colliding with. As such, the only
// real variable in play here is the current vector of the wheel.
vx0 = ((wheel.mass - IMMOVABLE) * vx0) / (wheel.mass + IMMOVABLE);
wheel.vx = vx0 * cos - vy0 * sin;
wheel.vy = vy0 * cos + vx0 * sin;
wheel.vx -= cos * overlap /wheel.radius;
wheel.vy -= sin * overlap / wheel.radius;
wheel.vx += speed;
}
trace("down");
wheel.vy += GRAVITY;
wheel.vy *= FRICTION;
wheel.vx *= FRICTION;
wheel.x += wheel.vx;
wheel.y += wheel.vy;
if(wheel.x > stage.stageWidth) wheel.x = stage.stageWidth;
if(wheel.x < 0) wheel.x = 0;
if(wheel.y > stage.stageHeight - (wheel.height >> 1))
{
wheel.y = 10;
wheel.x = 30;
wheel.vx = wheel.vy = 0;
}
}
private function clean(e:Event):void
{
removeEventListener(Event.ENTER_FRAME, updateScene);
}
}
}
There are some comment in the code.. Just ignore it i have used example.
collisionList = new CollisionList(terrain);
terrain.graphics.lineStyle(15);
This is error 1026, also thrown if the constructor is static, private or in your case used as an identifier. Either use this.graphics instead of terrain.graphics or just graphics.etc (remove terrain) and also pass 'this' as parameter for 'CollisionList'.
(Unrelated: Also it's better to name classes starting with a capital "Terrain")
I've recently started putting together a little physics simulation from scratch, something I've never tried before, and I've run into an issue regarding the interaction between the collision of the objects I have on stage, and what I think is my constant gravity. I'm not sure if 200 lines of code is too large for here, but this is what I have.
package {
import flash.events.*;
import flash.display.*;
import flash.geom.Rectangle;
[SWF (width="1500", height="1000", frameRate="24")]
public class ElasticityV2 extends MovieClip{
/* Gravity is 9.8 m/s2, which for flash, since it's being applied every frame, needs to be
divided out by the frame rate as to not have super fast acceleration. GravMulti is to balance
out gravity's speed, as it seemed a little slow after the framerate division. Resistance is acting
like friction for now, and slows down the objects in the air and on the ground at the same rate.
Elasticity is how bouncy each object is and how the force it recieves is applied*/
public var gravMulti:Number = 5;
public var gravity:Number = gravMulti *(9.8/stage.frameRate);
public var resistance:Number = 0.98;
public var elasticity:Number = 0.8;
public var floor:Number = stage.stageHeight - 100;
public var objectList:Array = new Array();
public var shadowList:Array = new Array();
public var yVelocityList:Array = new Array();
public var xVelocityList:Array = new Array();
public var massList:Array = new Array();
public var frictionList:Array = new Array();
public var lastXList:Array = new Array();
public var lastYList:Array = new Array();
public var elasticityList:Array = new Array();
public var dragList:Array = new Array();
public var spawnNum:int = 20;
public var bounding:Rectangle = new Rectangle(0,0,stage.stageWidth - 100,stage.stageHeight);
public var distantBackground:Background = new Background();
public var starLight:Light = new Light();
public function ElasticityV2() {
addChild(starLight);
starLight.x = stage.stageWidth/2;
starLight.y = -400;
starLight.addEventListener(MouseEvent.MOUSE_DOWN, onLightDrag);
starLight.addEventListener(MouseEvent.MOUSE_UP, onLightDrag);
starLight.addEventListener(MouseEvent.MOUSE_OUT, onLightDrag);
for(var s:int=0;s<spawnNum;s++){
var ballShadow:Shadow = new Shadow();
addChild(ballShadow);
setChildIndex(ballShadow,0);
ballShadow.y = floor - (ballShadow.height/2);
ballShadow.x = 100;
shadowList.push(ballShadow);
var ball:ElasticBall = new ElasticBall();
var dragging:Boolean = false;
addChild(ball);
ball.y = 100;
ball.x = s * 200;
objectList.push(ball);
yVelocityList.push(randomMe(20,-20));
xVelocityList.push(randomMe(40,-40));
massList.push(randomMe(20,5));
frictionList.push(randomMe(0.6,0.01));
objectList[s].width = objectList[s].height = massList[s] * 10;
elasticityList.push(elasticity);
dragList.push(dragging);
ball.addEventListener(MouseEvent.MOUSE_DOWN, onDrag);
ball.addEventListener(MouseEvent.MOUSE_UP, onDrag);
ball.addEventListener(MouseEvent.MOUSE_OUT, onDrag);
}
addChild(distantBackground);
distantBackground.y = stage.stageHeight - distantBackground.height;
distantBackground.width = stage.stageWidth;
setChildIndex(distantBackground,0);
addEventListener(Event.ENTER_FRAME, onGameLoop);
}
public function onGameLoop(e:Event):void{
//checkCollision();
for(var i:int=0;i<objectList.length;i++){
updatePhysics(i);
updateShadows(i,starLight);
}
}
public function updatePhysics(objRef:int):void{
if(lastXList[objRef] != undefined){
if(lastXList[objRef] != objectList[objRef].x){
xVelocityList[objRef] = objectList[objRef].x - lastXList[objRef];
}
}
if(lastYList[objRef]!= undefined){
if(lastYList[objRef] != objectList[objRef].y){
yVelocityList[objRef] = 4*(objectList[objRef].y - lastYList[objRef])/stage.frameRate;
}
}
if(objectList[objRef].y>= floor - objectList[objRef].height){
yVelocityList[objRef] = -(yVelocityList[objRef] * elasticityList[objRef]);
objectList[objRef].y = floor - objectList[objRef].height;
}
if(objectList[objRef].y<= 0){
yVelocityList[objRef] = -(yVelocityList[objRef] * elasticityList[objRef]);
objectList[objRef].y = 0;
}
if(objectList[objRef].x > (stage.stageWidth - objectList[objRef].width)){
xVelocityList[objRef]=-xVelocityList[objRef];
objectList[objRef].x = stage.stageWidth - objectList[objRef].width;
}
if (objectList[objRef].x <0){
xVelocityList[objRef]=-xVelocityList[objRef];
objectList[objRef].x = 0;
}
if(!dragList[objRef]){
yVelocityList[objRef]+=gravity;
objectList[objRef].y += yVelocityList[objRef];
xVelocityList[objRef]= (xVelocityList[objRef] * resistance);
if(-0.5<xVelocityList[objRef] && xVelocityList[objRef]<0.5){
xVelocityList[objRef] = 0;
}
objectList[objRef].x += xVelocityList[objRef];
}
lastXList[objRef] = objectList[objRef].x;
lastYList[objRef] = objectList[objRef].y;
if(xVelocityList[objRef] == 0){
xVelocityList[objRef]=randomMe(90,-90);
yVelocityList[objRef]=randomMe(90,-90);
}
}
public function onDrag(e:Event):void{
if(e.type == "mouseDown"){
setChildIndex(DisplayObjectContainer(e.target),numChildren - 1)
e.target.startDrag(false,bounding);
//xVelocityList[objRef] = yVelocityList[objRef] = 0;
//dragging = true;
}else{
e.target.stopDrag();
//dragging = false;
}
}
public function onLightDrag(e:Event):void{
if(e.type == "mouseDown"){
e.target.startDrag(false,bounding);
}else{
e.target.stopDrag();
}
}
public function updateShadows(objRef:int, lightSource:MovieClip):void{
//-----Cut for convenience------
}
public function checkCollision():void{
for(var v:int=0;v<objectList.length;v++){
var ball1 = objectList[v];
for(var w:int=v+1;w<objectList.length;w++){
var ball2 = objectList[w];
if((ball1.x + getRadius(ball1) + getRadius(ball2) > ball2.x) && (ball1.x < ball2.x + getRadius(ball1) + getRadius(ball2)) && (ball1.y + getRadius(ball1) + getRadius(ball2) > ball2.y) && (ball1.y < ball2.y + getRadius(ball1) + getRadius(ball2))){
var dx:Number = ball2.x - ball1.x;
var dy:Number = ball2.y - ball1.y;
var dist:Number = Math.sqrt((dx * dx) + (dy * dy));
if(dist < getRadius(ball1)+getRadius(ball2)){
var newX1:Number;
var newY1:Number;
var newX2:Number;
var newY2:Number;
trace("Magnitude 1 is : " + (Math.sqrt((xVelocityList[v] * xVelocityList[v]) + (yVelocityList[v] * yVelocityList[v]))));
trace("Magnitude 2 is : " + (Math.sqrt((xVelocityList[w] * xVelocityList[w]) + (yVelocityList[w] * yVelocityList[w]))));
newX1 = ((massList[v] * xVelocityList[v])+(massList[w] * xVelocityList[w]))/(massList[v] + massList[w]) * 2 - xVelocityList[v];
newY1 = ((massList[v] * yVelocityList[v])+(massList[w] * yVelocityList[w]))/(massList[v] + massList[w]) * 2 - yVelocityList[v];
newX2 = ((massList[v] * xVelocityList[v])+(massList[w] * xVelocityList[w]))/(massList[v] + massList[w]) * 2 - xVelocityList[w];
newY2 = ((massList[v] * yVelocityList[v])+(massList[w] * yVelocityList[w]))/(massList[v] + massList[w]) * 2 - yVelocityList[w];
xVelocityList[v] = newX1;
yVelocityList[v] = newY1;
xVelocityList[w] = newX2;
yVelocityList[w] = newY2;
ball1.x += newX1;
ball1.y += newY1;
ball2.x += newX2;
ball2.y += newY2;
}
}
}
}
}
public function randomMe(high:Number, low:Number = 0):Number{
return Math.random() * (high - low) + low;
}
public function getRadius(obj:MovieClip):Number{
return obj.width/2;
}
public function centerX(obj:MovieClip):Number{
return obj.x + getRadius(obj);
}
public function centerY(obj:MovieClip):Number{
return obj.y + getRadius(obj);
}
}
}
It's a very simple system to check for collision, just comparing the radius of the objects, and midair collisions seem fine, but if one ball lands on top of another that has no x or y velocity, it just sinks into it. Any ideas as to why?
I expect your balls behave like this: When one ball lands on top of the other, that's actually lying on the ground, the other ball gets pushed down into the ground, then you react at it within updatePhysics() by placing it into the position where it originated, thus, those balls become one within another. One of the suggestions that could remedy this will be to hold last collided object for each of the balls for one cycle of physics, say like this:
if(dist < getRadius(ball1)+getRadius(ball2)){
// process collision
ball1.lastCollided=ball2;
ball2.lastCollided=ball1;
}
Then, when you update your coordinates in updatePhysics(), check if lastCollided is null, if not, update that ball's coordinates and speed the same way, essentially simulating another collision. After checking for all the events within update physics cycle, assign null to lastCollided of all balls.