So I created a nice collision system shown here. Now I have my own character sprite, which has messed up the collision on all sides.
edit: because people are misunderstand what I want, I WANT it to overlap on the bottom and top, it gives it a 3D effect. My problem is that it's colliding incorrectly with the bitmap
I've tried using the pixel perfect collision system but I have a problem with it:
It only detects collision right at the edge, as you can see in the video, the ball can go slightly in front and behind the wall like it wasn't just a flat plane.
code responsible for the current collision (it did have some other stuff but that's been removed):
for each (var wall in Walls)
{
if (wall.hitTestPoint(Character.x, Character.y, true)) //col right
{
Character.x+=CharacterSpeed;
}
if (wall.hitTestPoint(Character.x, Character.y, true)) //col left
{
Character.x-=CharacterSpeed;
}
if (wall.hitTestPoint(Character.x , Character.y, true)) //col bottom
{
Character.y+=CharacterSpeed;
}
if (wall.hitTestPoint(Character.x, Character.y, true)) //col top
{
Character.y -= CharacterSpeed;
}
}
That is correct (and I mean that's what the code does) and that effect depends on framerate as well.
The principle is easy to understand. ex: You move an object by 5 pixels, the colliding object is 3 pixels away, when you test for collision you have an overlap of 2 pixels. To correct this you need what's called a TOI (time of impact) algorithm.
As of right now there's no fixing your code because you apply a motion is the inverse way it's supposed to work. You move first then you test while the correct way is you test then you move. For example:
you are about to move the object by x pixels.
test if the object + x pixels will collide.
if it will collide calculate by how much you should move it then move it by x pixels - corrected pixels.
if it will not collide then move it by x pixels.
As you see you do the opposite:
move by x pixels.
test for collision.
The result is overlapping objects.
What you can do as a hack without changing your code is calculate the overlapping and then correct the object position.
you can try draw your objects to a BitmapData using BitmapData.draw method, and than use BitmapData.hittest, here a sample:
import flash.display.BitmapData;
import flash.geom.Point;
var myBitmapData:BitmapData = new BitmapData(100, 80, false, 0x00CCCCCC);
var mc_1:MovieClip = this.createEmptyMovieClip("mc", this.getNextHighestDepth());
mc_1.attachBitmap(myBitmapData, this.getNextHighestDepth());
var mc_2:MovieClip = createRectangle(20, 20, 0xFF0000);
var destPoint:Point = new Point(myBitmapData.rectangle.x, myBitmapData.rectangle.y);
var currPoint:Point = new Point();
mc_1.onEnterFrame = function() {
currPoint.x = mc_2._x;
currPoint.y = mc_2._y;
if(myBitmapData.hitTest(destPoint, 255, currPoint)) {
trace(">> Collision at x:" + currPoint.x + " and y:" + currPoint.y);
}
}
mc_2.startDrag(true);
function createRectangle(width:Number, height:Number, color:Number):MovieClip {
var depth:Number = this.getNextHighestDepth();
var mc:MovieClip = this.createEmptyMovieClip("mc_" + depth, depth);
mc.beginFill(color);
mc.lineTo(0, height);
mc.lineTo(width, height);
mc.lineTo(width, 0);
mc.lineTo(0, 0);
return mc;
}
Related
I want to make a tracing game. I want my circle to follow the path as the user traces the letter (path of the letter). The user can not go back to the area which is already traced
import flash.events.Event;
import flash.geom.Point;
var i: Number;
var size: int = 80;
var down: Boolean = false;
var up: Boolean = true;
var inside: Boolean = true;
var outside: Boolean = true;
var circle: Shape = new Shape();
stage.addEventListener(Event.ENTER_FRAME, loop);
stage.addEventListener(MouseEvent.MOUSE_UP, mouseup);
char.addEventListener(MouseEvent.MOUSE_DOWN, mousedown);
function loop(e: Event) {
if (down == true) {
// Checks if mouse pointer is on path i.e 'S' alphabet
if (s.hitTestPoint(stage.mouseX, stage.mouseY, true)) {
inside = true;
outside = true;
var point: Point = maskobj.globalToLocal(new Point(stage.mouseX, stage.mouseY));
var point2: Point = new Point();
//Checks if mouse pointer is completely outside of drawn area
for (i = 0; i < 2 * Math.PI; i += (2 * Math.PI) / 10) {
point2.x = stage.mouseX + (size / 3) * Math.cos(i);
point2.y = stage.mouseY + (size / 3) * Math.sin(i);
if ((maskobj.hitTestPoint(point2.x, point2.y, true))) {
outside = false;
break;
}
}
//Checks if mouse pointer is completely inside drawn area
for (i = 0; i < 2 * Math.PI; i += (2 * Math.PI) / 10) {
point2.x = stage.mouseX + (size / 3) * Math.cos(i);
point2.y = stage.mouseY + (size / 3) * Math.sin(i);
if (!(maskobj.hitTestPoint(point2.x, point2.y, true))) {
inside = false;
break;
}
}
//Character will be moved only if mouse position not to far from current position
if (outside == false) {
if (inside == false) {
//Increases drawn area by drawing a circle shape in 'maskobj' MovieClip
circle.graphics.beginFill(0x0000ff);
circle.graphics.drawCircle(point.x, point.y, size);
circle.graphics.endFill();
maskobj.addChild(circle);
//Moves character to new position
char.x = stage.mouseX;
char.y = stage.mouseY;
}
}
}
}
}
function mouseup(e: MouseEvent): void {
up = true;
down = false;
}
function mousedown(e: MouseEvent): void {
down = true;
up = false;
}
When I trace the path,the motion is not smooth. Can someone please suggest a way to make the motion smooth OR suggest another way to achieve the same. Thank you in advance.
I've created a drawing game before that allowed the user to draw a path.
Not sure why Wicked's answer was down-voted, as the first thing you need to do is to use the highest frame rate that you can get away with. The higher the frame rate, the smoother your curve.
I see that your code draws a circle at the current position if the conditions are met. It might be better to draw a line from the last point.x/point.y to the current one instead of just a circle, so that you don't have any holes in your path.
I couldn't get around the fact that the line was jagged (a series of straight lines) as it was being drawn, but as soon as the user lifted their finger I was able to take the points along the line they had drawn and replace them with a smooth bezier Path (a series of simple bezier curves), which worked well. You could also do this on-the-fly once you have 3 points (you need 3 points to draw a curve).
Here is a good reference on how to achieve this, with theory and code samples. See further down the page for bezier paths. You'll need to convert to AS3, but it shouldn't be difficult.
Another tip is to do as little calculation as possible within the ENTER_FRAME. You could pre-calculate the two values used by your loops (2 * Math.PI) and ((2 * Math.PI) / 10) as these are constants. You could also calculate (size/3) once at the top of the function, and especially pre-calculate the 10 values for Math.sin(i) and Math.cos(i) and store them in an Array (basically a LUT - Look Up Table) as these are the heaviest math ops you're doing.
My final tip is that your code doesn't check if the point being drawn is very close to the last point that was drawn. I would recommend you do this, and only draw a point after the mouse has moved a minimum distance (e.g. 2 pixels). Otherwise you could get the mouse sitting still in one spot and your code is drawing circle upon circle on top of itself needlessly.
Try increasing the FPS in your document to atleast double what you currently have
Modify>Document...>Frame Rate
Helly everyone! I'm trying to dynamically add (and later remove) some movieclips inside of a triangle. Simple movieclip inside of a movieclip ain't working (it's a square in the end). Drawing a triangle is simple, addChild method is crystal clear too. The tough part comes after. Here's the code I'm trying to develop:
btn_toys_2.confirm.addEventListener(MouseEvent.MOUSE_UP, confirmToys);
import flash.display.Graphics;
var point1:Point = new Point(466, 65);
var point2:Point = new Point(370, 540);
var point3:Point = new Point(570, 540);
var vertices:Vector.<Number> = Vector.<Number>([point1.x, point1.y, point2.x, point2.y, point3.x, point3.y]);
var triangle:Sprite = new Sprite();
triangle.graphics.beginFill(0x00ff00, 1);
triangle.graphics.drawTriangles(vertices);
triangle.graphics.endFill();
addChild(triangle);
function confirmToys(e:MouseEvent){
var toy:MovieClip = new shar_001;
triangle.addChild(toy);
toy.x = Math.random()*30;
toy.y = Math.random()*30;
}
The "toy" movieclip is for some reason placed outside the triangle (0-30 x axis and 0-30 y axis).
The important part is to make "toys" appear within a triangle, doesnt have to be a movieclip. A way around this would be great too!
Thanks in advance!
It is happening this way because you have made your triangles anchor point at zero. You did this when you did
addChild(triangle);
That will always put the added child at (0,0). The only reason you triangle doesn't appear there is because you have added a cushion of empty pixels by making your points be greater than zero. Instead, you will use
addChild(triangle);
triangle.x = 370;
triangle.y = 65;
The point you want the top left corner of your triangle to be at is (370, 65). You should make your triangle points be (96, 0), (0, 475), (200, 475). Now the top left corner of the triangle is at (0,0) on the stage. Now set the triangle to (370, 65) after adding the triangle to the stage. Now the triangles anchor point is still the top left corner of the triangle, not the stage, so when you add the toy, it will be in reference to the point you expect.
// let the minimum x and y be zero, and adjust the others relative to that.
var point1:Point = new Point(96, 0);
var point2:Point = new Point(0, 475);
var point3:Point = new Point(200, 475);
var toyArray:Array = new Array();
var vertices:Vector.<Number> = Vector.<Number>([point1.x, point1.y, point2.x, point2.y, point3.x, point3.y]);
var triangle:Sprite = new Sprite();
triangle.graphics.beginFill(0x00ff00, 1);
triangle.graphics.drawTriangles(vertices);
triangle.graphics.endFill();
addChild(triangle);
// position anchor point on stage
triangle.x = 370;
triangle.y = 65;
function confirmToys(e:MouseEvent){
var p:Point = new Point(Math.random()*triangle.width,Math.random()*triangle.height);
if (isInsideTriangle(Point1,Point2,Point3,p))
{
var toy:MovieClip = new shar_001;
triangle.addChild(toy);
toyArray.push(toy);
toy.x = p.x;
toy.y = p.y;
}
}
private function isInsideTriangle(A:Point,B:Point,C:Point,P:Point):Boolean {
var planeAB:Number = (A.x-P.x)*(B.y-P.y)-(B.x-P.x)*(A.y-P.y);
var planeBC:Number = (B.x-P.x)*(C.y-P.y)-(C.x - P.x)*(B.y-P.y);
var planeCA:Number = (C.x-P.x)*(A.y-P.y)-(A.x - P.x)*(C.y-P.y);
return sign(planeAB)==sign(planeBC) && sign(planeBC)==sign(planeCA);
}
private function sign(n:Number):int {
return Math.abs(n)/n;
}
Removing the toys from the triangle should be pretty straight forward depending on the method you want to use. I added a toyArray that you can iterate through to remove them.
Checking if a position is within the desired boundaries and rejecting it if it's not is certainly a solution. However, this stops the program from being deterministic, because you never know how many tries it takes before a position within the boundaries is found.
Does that mean the program could run forever? Possibly yes, but this is so unlikely that it's not going to happen. Depending on how much of its bounding box a triangle fills, it will still produce quite a few misses though. Misses that have to be checked, rejected and tried again.
I'm not advising against this strategy because it might be a performance problem (and it might actually be one), but rather because it seems to miss the point: if positions in a triangle should be found, let's just do exactly that. All this trial and error and testing and rejecting is counterintuitive.
You only have one pseudo random number generator built in: Math.random().
That produces an evenly distributed random number between 0 and 1. (let's ignore whether the boundaries are possible values or not)
To create a 2D distribution, it's very easy to simply use two of those.
Now the problem with the even distribution is that it's even. To form a non-rectangular shape, transformations have to be applied.
Consider two edges of the triangle to be two vectors. A random point in the triangle is found by combining those two vectors linearly in a random way.
Obviously, with the untransformed random numbers, that would yield a diamond shaped boundary for the random points. To compensate for the fact that the vectors meet at one point and diverge in the other direction the square root is applied to one random number. The math behind that is not too complicated but ain't trivial either. I choose to omit it here. For more information ask a new question, math.se is probably a good place to do this.
Here's a full fledged example code to be used as a document class, which puts 1000 circles into a triangle boundary:
package
{
import flash.display.Shape;
import flash.display.Sprite;
import flash.geom.Point;
public class Main extends Sprite
{
public function Main()
{
var distribution:EvenDistribution2DTriangleBoundary = new EvenDistribution2DTriangleBoundary(new Point(100, 100), new Point(400, 50), new Point(250, 350));
for (var i:int = 0; i < 1000; ++i)
{
// create an object in each iteration of the loop
var circle:Shape = new Shape();
//add some graphics (this is unnecessary if you use a library symbol)
circle.graphics.beginFill(0xff0000, .6);
circle.graphics.drawCircle(0, 0, 3);
circle.graphics.endFill();
// add it to the display list
addChild(circle);
// reposition it with the help of the distribution object
distribution.positionDisplayObject(circle);
}
}
}
}
import flash.display.DisplayObject;
import flash.geom.Point;
internal class EvenDistribution2DTriangleBoundary
{
private var u:Point;
private var v:Point;
private var position:Point;
public function EvenDistribution2DTriangleBoundary(a:Point, b:Point, c:Point)
{
// consider corner "a" as the position of the triangle, this is arbitrary decision, but has to be consistent with the rest of this constructor
position = a;
// create two vectors from the corner that is the position to the other two corners respectively
u = b.subtract(a);
v = c.subtract(a);
}
public function getRandomPosition():Point
{
// random position formula with two random variables: position + (u + (v-u) * random1) * sqrt(random2)
var r1:Number = Math.random();
// the sqrt transforms the probability density function of the even distribution f(x) = 1 into a triangle g(y) = 2y
var r2:Number = Math.sqrt(Math.random());
// applying the above formula to create an evenly distributed random position within the triangle
return position.add(new Point((u.x + (v.x - u.x) * r1) * r2, (u.y + (v.y - u.y) * r1) * r2));
}
// convenience function to position a display object at a random position in the triangle
public function positionDisplayObject(object:DisplayObject):void
{
var position:Point = getRandomPosition();
object.x = position.x;
object.y = position.y;
}
}
Creating the random distribution is a class of its own. For the sake of simple testing, it's an internal class, thus the entire example is a single class that goes into a single file. Of course, in production code, this should be better organised.
Here are 4 results that I got:
it seems I need to transfer all of my frames/timeline code (and there's a lot!) into the external class
That isn't necessary although it is recommended. You should eventually only use class based code, but of course making that transition within a project isn't very practical.
In my example above, there are two classes: Main and EvenDistribution2DTriangleBoundary. You are only interested in the latter one. Main is just there to use the other class, create and display the circles, etc: it's a demo.
To use EvenDistribution2DTriangleBoundary in your project, create a new text file in the same directory as your .fla file named EvenDistribution2DTriangleBoundary.as with the following content:
package
{
import flash.display.DisplayObject;
import flash.geom.Point;
public class EvenDistribution2DTriangleBoundary
{
private var u:Point;
private var v:Point;
private var position:Point;
public function EvenDistribution2DTriangleBoundary(a:Point, b:Point, c:Point)
{
position = a;
u = b.subtract(a);
v = c.subtract(a);
}
public function getRandomPosition():Point
{
var r1:Number = Math.random();
var r2:Number = Math.sqrt(Math.random());
return position.add(new Point((u.x + (v.x - u.x) * r1) * r2, (u.y + (v.y - u.y) * r1) * r2));
}
public function positionDisplayObject(object:DisplayObject):void
{
var position:Point = getRandomPosition();
object.x = position.x;
object.y = position.y;
}
}
}
Now you can use that class like any other class in your project. For example, you can add the code from Main's constructor to your timeline and it should work:
var distribution:EvenDistribution2DTriangleBoundary = new EvenDistribution2DTriangleBoundary(new Point(100, 100), new Point(400, 50), new Point(250, 350));
for (var i:int = 0; i < 1000; ++i)
{
// create an object in each iteration of the loop
var circle:Shape = new Shape();
//add some graphics (this is unnecessary if you use a library symbol)
circle.graphics.beginFill(0xff0000, .6);
circle.graphics.drawCircle(0, 0, 3);
circle.graphics.endFill();
// add it to the display list
addChild(circle);
// reposition it with the help of the distribution object
distribution.positionDisplayObject(circle);
}
You should see a triangle of red circles similar to those I posted in the image of results above. Does that work?
So Im having trouble figuring this out.
Im trying to make the stage.y and stage.y at the bottom right corner of the stage, so whenever I add a like this
box.x = 100
box.y = 100
this.addChild(box);
it would be 100px from the bottom left corner of the stage.
i tried with
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.BOTTOM_LEFT;
but it did`nt work.
Am I missing something?
Your comment of "making a moviecclip and making its Registration point at bottom left ? and adding the things in this movie clip" is certainly one way to do it. Probably the easiest an first method that comes to mind and lets you move it around easily on demand and have any contained objects update.
Another method would be to create a point object and reference that to position everything. Something like:
var origin:Point = new Point(0, this.stage.stageHeight);
and then for your box:
box.x = origin.x + 100;
box.y = origin.y - 100;
However, if you want to change this, you'll have to add some code that goes and updates all the positions of your existing objects.
If you're really insisting on that +y goes upwards and -y going downwards on the stage, you could use a trick, but there may be a hefty overhead mental cost:
Use the first method, but give the movieclip a negative 100 y scaling before positioning at the lower left corner Note that everything will now be upside down though so you'll have to handle/factor that side effect in. Not recommended, but just throwing that out there.
You could also create a static method and call it always when you need, example:
public class DisplayObjectUtils
{
public static function setRegPoint(obj:DisplayObjectContainer, newX:Number, newY:Number):void
{
var bounds:Rectangle = obj.getBounds(obj.parent);
var currentRegX:Number = obj.x - bounds.left;
var currentRegY:Number = obj.y - bounds.top;
var xOffset:Number = newX - currentRegX;
var yOffset:Number = newY - currentRegY;
obj.x += xOffset;
obj.y += yOffset;
var totalChildren:uint = obj.numChildren;
for (var i:int = 0; i < totalChildren; i++)
{
obj.getChildAt(i).x -= xOffset;
obj.getChildAt(i).y -= yOffset;
}
bounds = null;
}
}
And to use, try something like:
//register point in the center of the object //box.width >> 1, box.height >> 1
DisplayObjectUtils.setRegPoint(box, box.width >> 1, box.height >> 1);
I am developing an android app with Adobe Flash cs6 and actionscript3. I have multiple moviclips at various locations on stage. Now I need to add a zoom feature that will zoom all movieclips as one movieclip. I cannot combine all movieclips together into one movieclip. My zoom feature works but it does not translate the movieclips to a new position. (Meaning they zoom at their original positions only) How can I accomplish this? Following is my zooming code :
/* ZOOM FEATURE */
Multitouch.inputMode = MultitouchInputMode.GESTURE;
zoomer.addEventListener(TransformGestureEvent.GESTURE_ZOOM , onZoom);
function onZoom (e:TransformGestureEvent):void{
mc1.scaleX *= (e.scaleX+e.scaleY)/2;
mc1.scaleY *= (e.scaleX+e.scaleY)/2;
mc2.scaleX *= (e.scaleX+e.scaleY)/2;
mc2.scaleY *= (e.scaleX+e.scaleY)/2;
mc3.scaleX *= (e.scaleX+e.scaleY)/2;
mc3.scaleY *= (e.scaleX+e.scaleY)/2;
}
Not the cleanest code I've written, but it should do the trick. scaleInPlace() accepts 4 arguments, 2 required, two optional:
obj: the display object you want to scale
scaleFactor: just as it sounds, how big/small do you want it?
fromX: the X coordinate where you want to scale from. Left, middle, right? If omitted, it operates from the middle of the compiled stage size.
fromY: same as the previous, but for top, middle, bottom.
This will preserve your DisplayList hierarchy while allowing you to scale to your heart's desire. If you had a lot of objects to scale, I'd probably parent all of them to the container first, and then run the scale and reparenting operations. While it works, this is the reason I feel it's not the "cleanest" solution. In my own classes, I've written a purely mathematical solution that doesn't include adding/removing DisplayObjects, but it's fairly tied up in my own classes I couldn't pull it out here and have it work.
Cheers!
// Let's scale myObject to 50% of its original size.
scaleInPlace(myObject, 0.5);
function scaleInPlace(obj:DisplayObject, scaleFactor:Number, fromX:Number = NaN, fromY:Number = NaN):void {
// If no coordinates from where to scale the image are provided, start at the middle of the screen
if (isNaN(fromX)) { fromX = loaderInfo.width/2; }
if (isNaN(fromY)) { fromY = loaderInfo.height/2; }
var father:DisplayObjectContainer = obj.parent;
var rect:Rectangle; // Coordinates for tracking our object
var index:int = getChildIndex(obj) // Where this object should go when we put it back
// Create the container
var container:Sprite = new Sprite();
father.addChild(container);
// Place the origin of the scale operation
container.x = fromX;
container.y = fromY;
// Get the coordinates of our object relative to our container
rect = obj.getRect(container);
// Parent and move into place
container.addChild(obj);
obj.x = rect.x;
obj.y = rect.y;
// Scale
container.scaleX = container.scaleY = scaleFactor;
// Get the coordinates and size of our scaled object relative to our father
rect = obj.getRect(father);
// Cleanup the display list
father.addChildAt(obj, index);
father.removeChild(container)
// Apply the new coordinates and size
obj.x = rect.x;
obj.y = rect.y;
obj.width = rect.width;
obj.height = rect.height;
}
I'm current building a game in as3; the proplem I have right now is when I roll the virtual dice, the player(marker) moves accross the board but what I need to know is: is there a way to find the instance name of the object(box) that the player lands on?
And Sorry my english isn't good.
It depends a lot on how your board is laid out. One way is to put all of the objects your player can land on into an array, then check the player's x and y coordinates to see if they fall inside of each object's box.
For example:
var boardObjects:Array; // This would contain references to all the objects the
// player object might land on. Initialize it, then use boardObjects.add(object)
// on each one until they're all in the array.
// once the player has moved:
for(var i:int = 0; i < boardObjects.size; i++) {
var obj:* = boardObjects[i];
if (player.x >= obj.x && player.x <= obj.x + obj.width) {
if (player.y >= obj.y && player.y <= obj.y + obj.height) {
// If these if statements are all true, the Player's top-left corner
// is inside the object's bounding box. If this is a function,
// here is a good spot to put a return statement.
}
}
}
You may want to calculate it based on the middle of the player rather than their top-left corner, in which case just add half the player's width to their x position and half their height to their y position.
For performance (and avoiding unnecessary code), if it's tile based / dice why not do something like this
private function rollDice(){
var results:Array = [Math.ceil(Math.random() * 6), Math.ceil(Math.random() * 6)] //Accurately simulates two 6 sided dice
dice1.rollAnimation(results[0]);
dice2.rollAnimation(results[1]);
player.position += results[0] + results[1];
}
The board would be an array, and in Player you can use getters/setters to 'wrap' the board like this
private var _position:int = 0;
public function get position():int{
return _position;
}
public function set position(value:int){
_position = value;
while(_position > GameBoard.TILES){
_position -= GameBoard.TILES;
}
x = //Whatever you determine the positioning of the player..
}