I have placed one large ball, "centerBall", in the center of the stage. Then I added in a
bunch of smaller ones, giving them random sizes and velocities. These will move with basic motion code and bounce off the walls. On each frame, did a distance-based collision check between each moving ball and the center ball. If I got a collision, I've calculated an offset spring target based on the angle between the two balls and the minimum distance.
There is still one problem: some of the smaller balls bypass "centerBall" boundaries and then bounce off. You can see that in the attached image. Why is happening this?
Here is the code:
import flash.display.Sprite;
import flash.events.Event;
public class Bubbles extends Sprite
{
private var balls:Array;
private var numBalls:Number = 10;
private var centerBall:Ball;
private var bounce:Number = -1;
private var spring:Number = 0.2;
public function Bubbles()
{
init();
}
private function init():void
{
balls = new Array();
centerBall = new Ball(100, 0xcccccc);
addChild(centerBall);
centerBall.x = stage.stageWidth / 2;
centerBall.y = stage.stageHeight / 2;
for(var i:uint = 0; i < numBalls; i++)
{
var ball:Ball = new Ball(Math.random() * 40 + 5, Math.random() * 0xffffff);
ball.x = Math.random() * stage.stageWidth;
ball.y = Math.random() * stage.stageHeight;
ball.vx = Math.random() * 6 - 3;
ball.vy = Math.random() * 6 - 3;
addChild(ball);
balls.push(ball);
}
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void
{
for(var i:uint = 0; i < numBalls; i++)
{
var ball:Ball = balls[i];
move(ball);
var dx:Number = ball.x - centerBall.x;
var dy:Number = ball.y - centerBall.y;
var dist:Number = Math.sqrt(dx * dx + dy * dy);
var minDist:Number = ball.radius + centerBall.radius;
if(dist < minDist)
{
var angle:Number = Math.atan2(dy, dx);
var targetX:Number = centerBall.x + Math.cos(angle) * minDist;
var targetY:Number = centerBall.y + Math.sin(angle) * minDist;
ball.vx += (targetX - ball.x) * spring;
ball.vy += (targetY - ball.y) * spring;
}
}
}
private function move(ball:Ball):void
{
ball.x += ball.vx;
ball.y += ball.vy;
if(ball.x + ball.radius > stage.stageWidth)
{
ball.x = stage.stageWidth - ball.radius;
ball.vx *= bounce;
}
else if(ball.x - ball.radius < 0)
{
ball.x = ball.radius;
ball.vx *= bounce;
}
if(ball.y + ball.radius > stage.stageHeight)
{
ball.y = stage.stageHeight - ball.radius;
ball.vy *= bounce;
}
else if(ball.y - ball.radius < 0)
{
ball.y = ball.radius;
ball.vy *= bounce;
}
}
}
Click here to see the pic
The problem you have is that you are doing collision detection based on them frames and not the positions.
You need to check where it is now and where it was last frame so you can keep track of its movements. This is why it goes through your center ball because you check in the current frame for a collision.
Here is a link to a time based collision detection of circles.
Timed based collision
Hope this helps ; )
Related
I have some code which loads a movie clip from the library, reproduces it and spreads it around the stage in different sizes, positions and rotations. What I can't figure out is how to then animate each one with an ENTER_FRAME event listener - So maybe I can also animate the scale, position and rotations? Any help greatly appreciated. Thanks.
for (var i = 0; i < 20; i++ )
{
//Generate Item from library
var MovieClip_mc:mcMovieClip = new mcMovieClip();
addChild(MovieClip_mc);
//Size
var RandomSize = (Math.random() * 0.5) + 0.5;
MovieClip_mc.scaleX = RandomSize;
MovieClip_mc.scaleY = RandomSize;
//Rotation
var RandomRotation:Number = Math.floor(Math.random()*360);
MovieClip_mc.rotation = RandomRotation;
//Position
MovieClip_mc.x = Math.floor(Math.random() * CanvasWidth);
MovieClip_mc.y = Math.floor(Math.random() * CanvasHeight);
}
For performance benefits, it is better to do it using one ENTER_FRAME handler. The handler can be located in your main class and update each mcMovieClip's properties by calling certain methods declared in the mcMovieClip class. Below is a simple example.
Main class
var mcs:Array = [];
for(var i:int = 0; i < 1; i++)
{
mcs.push(new mcMovieClip());
addChild(mcs[i]);
}
addEventListener(Event.ENTER_FRAME, updateTime);
function updateTime(e:Event):void
{
for(var j:int = 0; j < mcs.length; j++)
{
mcs[j].updatePosition(Math.random() * stage.stageWidth,
Math.random() * stage.stageHeight);
mcs[j].updateRotation(Math.random() * 360);
mcs[j].updateSize(Math.random());
}
}
mcMovieClip class
function updateSize(size:Number):void
{
this.scaleX = this.scaleY = size;
}
function updateRotation(rot:Number):void
{
this.rotation = rot;
}
function updatePosition(newX:Number, newY:Number):void
{
this.x = newX;
this.y = newY;
}
You don't actually need to do that from the outside. You can animate it with its own script in the first frame, so each instance will be animated:
addEventListener(Event.ENTER_FRAME, onFrame);
function onFrame(e:Event):void
{
// Math.random() - 0.5 will produce a random value from -0.5 to 0.5
x += Math.random() - 0.5;
y += Math.random() - 0.5;
scaleX += (Math.random() - 0.5) / 10;
scaleY = scaleX;
rotation += (Math.random() - 0.5) * 5;
}
Having multiple movieclips being able to move around on a grid, once dragging it, it seems to duplicate and the 'original' remains in place.
What causes it to duplicate in my code?
import flash.display.MovieClip
[SWF(width = 1300, height = 1000)]
var tileSize: int = 100;
var cols: int = stage.stageWidth / tileSize;
var rows: int = stage.stageHeight / tileSize;
var grid: Sprite = Sprite(addChild(new Sprite()));
grid.graphics.lineStyle(0, 0x000000, 0);
var i: int = 0;
for (i = 1; i < cols; i++) {
var posX: Number = i * tileSize
grid.graphics.moveTo(posX, 0);
grid.graphics.lineTo(posX, stage.stageHeight);
}
for (i = 1; i < rows; i++) {
var posY: Number = i * tileSize
grid.graphics.moveTo(0, posY);
grid.graphics.lineTo(stage.stageWidth, posY);
var ball: the_ball = new the_ball();
addChild(ball);
ball.x = tileSize * 5;
ball.y = tileSize * 5;
ball.buttonMode = true;
ball.addEventListener(MouseEvent.MOUSE_DOWN, onDown);
function onDown(evt: MouseEvent): void {
addEventListener(Event.ENTER_FRAME, onRunSnapping);
}
function onRunSnapping(evt: Event): void {
ball.x = Math.round(mouseX / tileSize) * tileSize;
ball.y = Math.round(mouseY / tileSize) * tileSize;
}
stage.addEventListener(MouseEvent.MOUSE_UP, onUp);
function onUp(evt: MouseEvent): void {
removeEventListener(Event.ENTER_FRAME, onRunSnapping);
}
}
It's not duplicating, there are just many movieclips stacked on top of each other.
You add a new ball for each row you add on the exact same position.
I dont know how you want the balls to be distributed, but try to replace
ball.y = tileSize * 5; with ball.y = posY; to see what im talking about.
can't figure out why the ball object falls right through line[2] while it bounces off all the others. I have two classes below; a main class that creates the lines along with some collision and motion code for the ball, and a class for the ball. I included a comment above the line in the main class which pertains to the line that doesn't interact. When the program runs, it bounces off of all the lines with some distance based collision code - but passes right through one of the lines- the line created in the array at line[2]
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Rectangle;
public class MultiAngleBounce extends Sprite
{
private var ball:Ball;
private var lines:Array;
private var numLines:uint = 6;
private var gravity:Number = 0.3;
private var bounce:Number = -0.6;
public function MultiAngleBounce()
{
init();
}
private function init():void
{
ball = new Ball(20);
addChild(ball);
ball.x = 100;
ball.y = 100;
//create five lines
lines = new Array();
for(var i:uint = 0; i < numLines; i++)
{
var line:Sprite = new Sprite();
line.graphics.lineStyle(1);
line.graphics.moveTo(-50, 0);
line.graphics.lineTo(50, 0);
addChild(line);
lines.push(line);
}
lines[0].x = 100;
lines[0].y = 100;
lines[0].rotation = 30;
lines[1].x = 100;
lines[1].y = 230;
lines[1].rotation = 50;
//why does this one get ignored
lines[2].x = 250;
lines[2].y = 180;
lines[2].rotation = -30;
lines[3].x = 150;
lines[3].y = 330;
lines[3].rotation = 10;
lines[4].x = 230;
lines[4].y = 250;
lines[4].rotation = -30;
lines[5].x = 300;
lines[5].y = 350;
lines[5].rotation = -20;
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void
{
//normal motion code
ball.vy += gravity;
ball.x += ball.vx;
ball.y += ball.vy;
//bounce off ceiling, floor, walls
if(ball.x + ball.radius > stage.stageWidth)
{
ball.x = stage.stageWidth - ball.radius;
ball.vx *= bounce;
}
else if(ball.x - ball.radius < 0)
{
ball.x = ball.radius;
ball.vx *= bounce;
}
if(ball.y + ball.radius > stage.stageHeight)
{
ball.y = stage.stageHeight - ball.radius
ball.vy *= bounce;
}
else if(ball.y - ball.radius < 0)
{
ball.y = ball.radius;
ball.vy *= bounce;
}
//check each line
for(var i:uint = 0; i < numLines; i++)
{
checkLine(lines[i]);
}
}
private function checkLine(line:Sprite):void
{
//get the bounding box of the line
var bounds:Rectangle = line.getBounds(this);
if(ball.x > bounds.left && ball.x < bounds.right)
{
//get angle, sine and cosine
var angle:Number = line.rotation * Math.PI / 180;
var cos:Number = Math.cos(angle);
var sin:Number = Math.sin(angle);
//get position of ball, relative to line
var x1:Number = ball.x - line.x;
var y1:Number = ball.y - line.y;
//rotate coordinates
var y2:Number = cos * y1 - sin * x1;
//rotate velocity
var vy1:Number = cos * ball.vy - sin * ball.vx;
//perform bounce with rotated values
if(y2 > -ball.height / 2 && y2 < vy1)
{
//rotate coordinates
var x2:Number = cos * x1 + sin * y1;
//rotate velocity
var vx1:Number = cos * ball.vx + sin * ball.vy;
y2 = -ball.height / 2;
vy1 *= bounce;
//rotate everything back;
x1 = cos * x2 - sin * y2;
y1 = cos * y2 + sin * x2;
ball.vx = cos * vx1 - sin * vy1;
ball.vy = cos * vy1 + sin * vx1;
ball.x = line.x + x1;
ball.y = line.y + y1;
}
}
}
}
}
package
{
import flash.display.Sprite;
public class Ball extends Sprite
{
public var radius:Number;
private var color:uint;
public var internalAngle:Number;
public var internalRadius:Number;
internal var vx:Number = 0;
internal var vy:Number = 0;
public function Ball(radius:Number = 40, color:uint = 0xff0000)
{
this.radius = radius;
this.color = color;
init();
}
public function init():void {
graphics.beginFill(color);
graphics.drawCircle(0, 0, radius);
graphics.endFill();
}
}
}
You bounds check
if(ball.x > bounds.left && ball.x < bounds.right)
is failing for the second line. The ball hasn't attained enough speed by the time it reaches lines[2] that it can get completely into the bounds rect. You can use
if(line.hitTestObject(ball))
to do collision detection instead.
I am making a top down shooter in flash using AS3 and I'm having some trouble firing a bullet from a ship moved by the WASD keys towards the cursor. The bullets are created fine enough but they go off in random directions rather than towards where the cursor is when you press space.
This is the code for the bullet class:
package{
import flash.display.MovieClip;
import flash.events.Event;
import flash.ui.Mouse;
import flash.events.MouseEvent;
import flash.geom.Point;
public class Bullet extends MovieClip
{
var speed:Number;
var moveX:Number;
var moveY:Number;
var shipX:Number;
var shipY:Number;
function Bullet(target:Ship)
{
speed = 20;
shipX = target.x;
shipY = target.y;
graphics.lineStyle(7,0x000000);
graphics.moveTo(-2,0);
graphics.lineTo(2,0);
graphics.lineStyle(3,0xffffff);
graphics.moveTo(-2,0);
graphics.lineTo(2,0);
addEventListener(Event.ADDED_TO_STAGE, onStage);
}
function onStage (e:Event)
{
/*addEventListener("enterFrame", enterFrame);
/*var mc =
var angleRadian = Math.atan2(mouseY - mc.y, mouseX - mc.x);
var angleDegree = angleRadian * 180 / Math.PI;
mc.rotation = angleDegree;*/
/*Ship.y += speed * Math.sin((Ship.rotation - 90) * (Math.PI / 180));
Ship.x += speed * Math.cos((Ship.rotation - 90) * (Math.PI / 180));*/
trace(stage.mouseY);
var cy:Number = stage.mouseY - shipY;
var cx:Number = stage.mouseX - shipX;
moveX = cx / cy;
moveY = (cy < 0) ? -1 : 1;
var Radians:Number = Math.atan2(cy,cx);
var Degrees:Number = Radians * 180 / Math.PI;*/
}
function enterFrame(e:Event)
{
/*this.x += speed;*/
this.x += moveX * speed;
this.y += moveY * speed;
if (this.x > 900)
{
removeEventListener("enterFrame",enterFrame);
stage.removeChild(this);
}
if (this.x < 0)
{
removeEventListener("enterFrame",enterFrame);
stage.removeChild(this);
}
if (this.y > 800)
{
removeEventListener("enterFrame",enterFrame);
stage.removeChild(this);
}
if (this.y < 0)
{
removeEventListener("enterFrame",enterFrame);
stage.removeChild(this);
}
for(var i in Enemy.list)
{
if(this.hitTestObject(Enemy.list[i]))
{
removeEventListener("enterFrame", enterFrame);
stage.removeChild(this);
Enemy.list[i].kill();
break;
}
}
}
}
}
Any help getting the bullets to fire properly would be much appreciated!
The x and y velocities should be the result of cos and sin of the angle (in radians) respectively.
i.e.
var angle:Number = Math.atan2(cy, cx);
moveX = Math.cos(angle);
moveY = Math.sin(angle);
You then multiply both of these by the speed (which you're already doing).
Also, you don't need to calculate the degrees unless you want to rotate the graphics:
rotation = angle * 180 / Math.PI;
UPDATE
I now have this but only one ball bounces when the flash movie is run
var array:Array = new Array;
var dx:Number = Math.random() * 20;
var dy:Number = Math.random() * 10;
var velX:Number = 5;
var velY:Number = 5;
addEventListener(Event.ENTER_FRAME, movearray);
for (var i:Number = 0; i < 20; i++) {
var ball:Ball = new Ball();
ball.x = Math.random() * 550;
ball.y = Math.random() * 400;
addChild(ball);
array.push(ball);
}
function movearray(evt:Event):void {
ball.x += velX;
ball.y += velY;
if (ball.x > stage.stageWidth - ball.width / 2 || ball.x < 0 + ball.width /2){
velX *= -1;
}
else if (ball.y > stage.stageHeight - ball.height / 2 || ball.y < 0 + ball.height /2){
velY *= -1;
}
}
Thanks any help appreciated.
Of course only one ball moves, that's because in your movearray function you are only referring to one ball instance. Since you are storing are your ball instances in an array, you have to loop through that array to check for every ball. Also, since each ball moves independently, is mandatory to store an array of ball velocities as well.
For example:
import flash.geom.Point;
var numBalls:uint = 20;
var arrayBalls:Array = new Array();
var arrayVels:Array = new Array();
//I don't know what is this for
//var dx:Number = Math.random() * 20;
//var dy:Number = Math.random() * 10;
var initialVelX:Number = 5;
var initialVelY:Number = 5;
addEventListener(Event.ENTER_FRAME, movearray);
for (var i:uint = 0; i < numBalls; i++)
{
var ball:Ball = new Ball();
ball.x = Math.random() * 550;
ball.y = Math.random() * 400;
addChild(ball);
arrayBalls.push(ball);
//use a Point to store velocities in two axis
//you could also set random starting velocities
//so each ball would move differently initially
var vel:Point = new Point(initialVelX,initialVelY);
arrayVels.push(vel);
}
function movearray(evt:Event):void
{
var ball:Ball;
var vel:Point;
for (var i:uint = 0; i < numBalls; i++)
{
ball = arrayBalls[i];
vel = arrayVels[i];
ball.x += vel.x;
ball.y += vel.y;
if (ball.x > stage.stageWidth - ball.width / 2 || ball.x < 0 + ball.width /2)
{
vel.x *= -1;
}
else if (ball.y > stage.stageHeight - ball.height / 2 || ball.y < 0 + ball.height /2)
{
vel.y *= -1;
}
}
}
To move a ball you need to specify velocity for it. Suppose a ball has velocity vx, vy. And the time between successive call of movearray is dt. So the new position of the ball is:
dx = vx * dt;
dy = vy * dt;
xNew = xOld + dx;
yNew = nOld + dy;
When a ball hits top or bottom part of the screen you need to negate vy. When ball hits left or right end you need to negate vx. For example, vx is positive and thus the ball is moving right. When it hits the right end you need to make vx = -vx, so vx is now negative and the ball will start moving left.
So you have to store velocity (may be random, just like the initial positions) for every ball and inside movearray calculate dt and new positions. And if any ball hits the wall then negate the velocity accordingly. To test the hit on wall you need to compare their x, y coordinates with screen width, height etc. To calculate dt you can keep track of calling time of movearray.
dt = currentTime - lastTimeCalled;
lastTimeCalled = currentTime;
Note that, this method will only collide on screen boundary, it won't detect ball-ball collision.