Problem with collision detection in AS3?
I'm trying to have a rotatable line, controlled by the arrow keys. When you click the mouse, a ball drops from the cursor, and stops when it hits the line.
However, the balls always stop at the highest point of the line, across a line parallel to the x-axis.
My document class is as follows:
package
{
import flash.display.MovieClip;
import flash.events.*
import flash.display.Stage
import ball
public class Engine extends MovieClip
{
public static var stageRef:Stage
private static var leftKey:Boolean = false
private static var rightKey:Boolean = false
public static var pi = Math.PI
public static var lineRotate:Number = 0
public static var spinRate:Number = 60
public static var ground:line = new line()
public function Engine()
{
// constructor code
stage.addEventListener(Event.ENTER_FRAME, loop)
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler)
stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler)
stage.addEventListener(MouseEvent.CLICK, addBall)
stageRef = stage
ground.x = 300
ground.y = 200
stage.addChild(ground)
}
private static function keyDownHandler(e:KeyboardEvent)
{
if (e.keyCode == 37) //left
{
leftKey = true
}
if (e.keyCode == 39)
{
rightKey = true
}
}
private static function keyUpHandler(e:KeyboardEvent)
{
if (e.keyCode == 37) //left
{
leftKey = false
}
if (e.keyCode == 39) //right
{
rightKey = false
}
}
public function loop(e:Event)
{
spin()
}
public static function addBall(e:MouseEvent) //adds ball
{
var tempBall:ball = new ball()
tempBall.x = e.stageX
tempBall.y = e.stageY
stageRef.addChild(tempBall)
}
private static function spin() //spins the "ground" line
{
if (leftKey) // minus
{
lineRotate -= spinRate
}
if (rightKey) // plus
{
lineRotate += spinRate
}
ground.rotation = lineRotate * (pi / 180) //convert to radians
}
}
}
The class for the ball is as follows:
package
{
import flash.display.MovieClip;
import flash.events.*
public class ball extends MovieClip
{
public var vX:Number = 0
public var vY:Number = 2
private var gravity:Number = 0
public function ball()
{
// constructor code
addEventListener(Ev开发者_C百科ent.ENTER_FRAME, loop)
}
public function loop(e:Event)
{
this.x += vX
this.y += vY
this.vY += gravity
if (this.x > stage.stageWidth || this.x < 0 || this.y < 0 || this.y > stage.stageHeight)
{
removeSelf()
}
if (Engine.ground.hitTestObject(this))
{
trace('yep')
stopBall()
}
else
{
trace('nope')
}
}
public function removeSelf()
{
removeEventListener(Event.ENTER_FRAME, loop)
this.parent.removeChild(this)
}
public function stopBall()
{
gravity = 0
vY = 0
vX = 0
}
}
}
I've uploaded my .swf to here.
The easiest thing you can do, for balls is a hitTestPoint with the third argument as true, that turns on shape detection.
ground.hitTestPoint(x,y,true);
http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/display/DisplayObject.html#hitTestPoint()
Ok, so, hm, you can check a single point on the ball, like it's bottom point, or you can check an array of points along the bottom of the ball for easier precision... this is the quickest way to do this if you're not planing anything more complex then this. If, however, you want to create a full fleshed game, leave it to a 2d physics library like http://www.box2dflash.org/.
Forget skinners collision detection for a bigger game(something small like this could survive with it though), as it is bitmap based and would kill performance much more then box2D, it's a nice mask example, but it not a good idea to use for performance reasons.
I've changed your code a little bit. I've hittested a bunch of points on the bottom part of the ball, bear in mind that the ball's 0,0 inside the movieclip is centered on the balls center.
package
{
import flash.display.MovieClip;
import flash.events.*;
import flash.geom.Point;
public class ball extends MovieClip
{
public var vX:Number = 0;
public var vY:Number = 2;
private var gravity:Number = 0;
public function ball()
{
// constructor code
addEventListener(Event.ENTER_FRAME, loop);
}
public function loop(e:Event)
{
this.x += vX;
this.y += vY;
this.vY += gravity;
if (this.x > stage.stageWidth || this.x < 0 || this.y < 0 || this.y > stage.stageHeight)
{
removeSelf();
}
/*we will now check something like 18 points on the bottom side of the ball for colision
instead of just the bottom, you can probably guess why... if you cant, replace i on line
43 (var angleInRadians...) with 270 to test and then drop balls on a slopped ground surface... of course
you should definitely juse a physics engine like http://www.box2dflash.org/ for anything more complex.
*/
for (var i:int = 180; i<=360; i+=10)
{
/*keep in mind that ball.rotation property has 0 at the top of the ball, while here for these we are using the standard
Cartesian coordinate system. The effect of this on rotation would be that it is +90 and for X(yes, X) it would be Math.SIN(),
not Math.COS()!!!, and for Y it would be Math.sin() with a minus in front because of the flash coordinate system y axis rotation.
It's not really related to this, but it's a point that has anoyed me to no end in trig related stuff in flash when I was starting.
*/
var angleInRadians:Number = i / 180 * Math.PI;
var posX:Number = this.x + Math.round(Math.cos(angleInRadians) * width / 2);
var posY:Number = this.y - Math.round(Math.sin(angleInRadians) * height / 2);//minus because y in flash is upside down
Engine.ground.hitTestPoint(posX,posY, true);
if (Engine.ground.hitTestPoint(posX,posY, true))
{
stopBall();
}
else
{
}
}
/*if (Engine.ground.hitTestObject(this))
{
trace('yep')
stopBall()
}
else
{
trace('nope')
}*/
}
public function removeSelf()
{
removeEventListener(Event.ENTER_FRAME, loop);
this.parent.removeChild(this);
}
public function stopBall()
{
gravity = 0;
vY = 0;
vX = 0;
}
}
}
Unrelated to this above, you need to rethink your OOP a little bit, you are doing things a little bit wrong :). A project this small would cope, but anything bigger would give you headaches. Don't take this as an attack, I want to try to guide you to the "right" path and show you the logical fallacies in your code, I am not mentioning this to "pwn" you cause your oop is bad :).
For example, gravity, inside class ball? Yikes... what if you wanted to have gravity for ninjas, shurikens, ragdolls, etc? Are you going to subclass Ninjas out of the ball class?
Ninja extends ball? That would be fun.
Do you think that there might be a better place for that gravity thing? Is "gravity" a property of the ball at all(err, tehnically it is, but not this homogeneous gravity you are putting here, it's more like a permeating, all present thing, cause there is some huge body that we are too close to)? It should be in the Engine...
Also, you did Engine.ground thing... now, this is a static variable... this is another bad thing, very bad thing :)
The reason is similar to the previous example, but a little bit turned around. What if you want to reuse ball inside, say Engine2? Or Crytek engine? or UDK? I think it might be sligtely problematic, don't you think? You would have to go in and change the ball class... Imagine if every code you used forced you to do that...
Basically, you probably could have done something like this:
var bla:ball = new ball(ground);
this is better, muuuch better... now we can use it in crytek, udk and Engine2 easily...
Technically, you want to avoid things like static variables of this kind in your code, the idea is called encapsulation. The idea is that you hide the internal workings of classes from other classes that are unrelated to it or should not know about it. The less you NEED to know, the more portable, reusable (yada yada) your classes are. Engine.ground is a huge encapsulation breaker because ball has absolutely zero need to know about class Engine, it should only be concerned with a reference to the ground itself...
In time you will learn all of this, patterns in programming, and particularly , which patterns save time, and which are appalling, and why (static variables have their use, but only for ultra simple stuff, avoid Singletons as well if you know what they are).
http://en.wikipedia.org/wiki/Encapsulation_(object-oriented_programming)
Sorry for the headache I've caused you...
Remember, use a physics engine for more complex code, don't reinvent the wheel(reuse everything for your projects that is good for them/sufficient for them)... It's fun to test stuff like this, but other folks have done it better before you, and unless you specifically need to delve into their domain, you don't need to concern yourself with every little detail. You are probably trying to build games/something, not bother with Newtonian dynamics...
HitTest is just fine for simple stuff though, but this is bordering on simple... :)we are already slightly into physics here, and you might find you need a more robust solution soon...
So generally, try to make your code "robust" by thinking about encapsulation and dependencies between your classes. If a dependency feels unatural (ie ball depends on Engine) then something is probably wrong, and will break later, will break for another game or engine or... I'll end it here...
Take care and have fun.
According to the article Collision detection methods: hit-test and hit-testobject alternatives, that is by design:
As you can see Flash uses the bounding boxes of objects - it takes the highest and lowest possible points, the leftmost and rightmost points and draws a rectangle around them.
The author mentions several alternatives to workaround this limitation, such as using hitTestPoint or Grant Skinner's Shape based collision detection.
精彩评论