Lets say I have a movieclip with multiple objects nested inside.
Which is the best technique to get a touch on an object inside the Movieclip?
Example:
I have a Movieclip named "ast" (parent), a nested MC called "green" (child/parent) and a button named "butt" inside green (child).
I want to SCREEN_TAP "butt".
I tried this:
if(pointX >= this.ast.green.butt.x &&
pointX <= this.ast.green.butt.x + this.ast.green.butt.width &&
pointY >= this.ast.green.butt.y &&
pointY <= this.ast.green.butt.y + this.ast.green.butt.height)
{ trace ("your butt is touched!");}
But the result is a negative "touch". No trace.
Any idea in how to detect this?
Regards!
Considering the touch point coordinates are in stage coords, you will have to use localToGlobal() on the button's x, y, x+width, y+height. Or you will have to add up all the parent objects' coordinates (more work).
Or you could also use hitTestObject()/hitTestPoint() and check it with that.
Related
I m currently moving my CPU image post processing frame from AS2 to AS3. I used an array to handle all the objects that need to be rendered each frame.
I sort the array by the object's depth on _root, and then render them all onto one bitmapdata in order to draw them in the correct order.
In my AS3 game project I used displayobjectcontainers to handle "depths". for example I have multiple movieclips acting as containers on MovieClip(root), and then add the child to those movieclips to sort them. I also have other child containers inside those containers, forming an hierarchy.
But now I find it hard to sort the render list array because I cannot simply input an absolute "depth" value for the object. I use parent.getChildIndex, but this just gives me the index of the child in one of those containers.
Ofcourse you can make a big function and finally sort the array, but is there anyway to give the object's absolute index in relation to the root/stage?
Thanks a lot.
You could try something like this, which will start at a given DisplayObjectContainer and generate a list of all descendants in order of depth:
function collateChildren(container:DisplayObjectContainer):Vector.<DisplayObject>
{
var list:Vector.<DisplayObject> = new <DisplayObject>[];
for(var i:int = 0; i < container.numChildren; i++)
{
var child:DisplayObject = container.getChildAt(i);
if(child is DisplayObjectContainer && (child as DisplayObjectContainer).numChildren > 0)
{
// Target contains additional children.
list = list.concat(collateChildren(child as DisplayObjectContainer));
}
else
{
// Target is a child.
list.push(child);
}
}
return list;
}
This will not include children who contain other children, and can be used like:
var children:Vector.<DisplayObject> = collateChildren(stage);
trace(children);
A game world would consist of some layers,like role layer, map layer, and each layer has it's depth in the game world. When you try to add some displayobjects to the world, add the displayobjects to the targe layer.
At most time, the layer count won't be large, like 5 or 6. So you can draw the objects on each layer by layer depth.
I have a function where I can control my character.
I also have a movieclip on the stage called assassin_table.
I want to make it so that the character can't move in the table, a.k.a make the table work like a wall.
I have this code:
if(!this.hitTestObject(_root.assassin_table))
{
if(upKeyDown)
{
gotoAndStop(4);
y-=Math.cos(rotation/-180*Math.PI)*(mainSpeed +7);
x-=Math.sin(rotation/-180*Math.PI)*(mainSpeed +7);
}
if(!upKeyDown)
{
gotoAndStop(3);
}
}
However, if I touch the table, then I can't move at all.
I know it's because if(!this.hitTestObject(_root.assassin_table)), but I don't understand the logic behind NOT moving through objects. I'd much rather have a near pixel-perfect collision detection system, but since it's so hard to find any good info online which isn't confusing, I'll stick with hitTestObject for now :)
EDIT: Tried something, didn't really work that well.
if(!_root.assassinDead && !teleporting && this.currentFrame != 5)
{
if(this.hitbox.hitTestObject(_root.assassin_table))
{
_root.assassin_table.gotoAndStop(2);
if(this.x > _root.assassin_table.x)
{
trace("Can't move right");
canMoveRight = false;
}
if(this.x <_root.assassin_table.x)
{
trace("Can't move left");
canMoveLeft = false;
}
if(this.y > _root.assassin_table.y)
{
trace("Can't move up");
canMoveUp = false;
}
if(this.y < _root.assassin_table.y)
{
trace("Can't move down");
canMoveDown = false;
}
}
else
{
canMoveRight = true;
canMoveLeft = true;
canMoveUp = true;
canMoveDown = true;
}
}
This causes me to sometimes be able to walk through the table. I figure it's because my character can move in essentially every possible angle (since he's always facing the mouse and there are no tiles/grids).
How would I make it so that it would work with the advanced movement I have?
Moving up runs this:
y-=Math.cos(rotation/-180*Math.PI)*(mainSpeed +7);
x-=Math.sin(rotation/-180*Math.PI)*(mainSpeed +7);
And the rotation is decided by this:
this.rotation = Math.atan2((stage.mouseY - this.y), (stage.mouseX - this.x)) * 180/ Math.PI + 90;
You should seperate your hittest functions for four different moving directions.
I mean, you shouldn't use this "hitTestObject" stuff, that only returns a boolean value "true" or "false", and that's not going to work for you.
You need a function like "testPoint(player.x, player.y);" and returns the object at the given position, so you can implement it for your game like that
if (upKeyDown && testPoint(player.x, player.y - mainSpeed +7) == null) y-=Math.cos(rotation/-180*Math.PI)*(mainSpeed +7);
player.y - mainSpeed +7 // that checks for plus mainSpeed+7 because of trying to stop your player before it get stack inside object
Basically your logic flow should be like this:
Sample input (key press)
Move character
Check for collisions
If collision then move character to it's "outside" the object that it's touching
In your particular case, if your character is on the left of the table, and you're moving right, then first things first, you move your character. At your new position, check for any collisions. If you have a collection, then because you were moving from the left, we want to be on the left of the object that we colliding with, in this case the table, so we position our character to the left of the table.
The first part of that (checking if the character has hit the table) is called collision detection. Moving the character so that it's outside the bounds of the table is called collision response. Collision detection is a pretty big field and really depends on the type of game you're making; you can do grid-based, hit-test based (if you don't have a ton of objects), physics-based, math-based etc. Collision response can be anything and everything, depending on how you want to react to a collision - you can destroy the object (balloon on a spike), change its speed (character running through mud), bounce off it (ball off wall), or stop any further movement (character against wall).
To make things a bit easier:
Separate your systems - your input shouldn't be dependant on your collision for example. If the up key is down, just register that fact - what you do with it later (make your character move) is up to you
Separate your objects position in memory from its position on screen - this will let you move it around, react to collisions etc, and only when everything is good, update the graphics (stops things like graphics entering a wall only to jump out the next frame)
Solve for one axis at a time - e.g. collide on the x axis first, then the y
Pixel perfect collision is rarely needed :) Non-rotated boxes and circles will work a lot more than you'd think
Somewhat related - the shape of your object doesn't have to be the shape that you're colliding with - e.g. your table collision shape could just be a box; your character collision shape could just be a circle
Update
This causes me to sometimes be able to walk through the table
Assuming that we're going to collide our character and table as boxes, you need to take into account their sizes - i.e. we don't just compare the x values, but the right side of our character box against the left side of the table box etc. Something like:
// assumes objects have their anchor point in the top left - if its
// not the case, adjust as you see fit
if( char.x + char.width > table.x ) // right side of the char is overlapping the left side of the table
canMoveRight = false;
else if( char.x < table.x + table.width ) // left side of char vs right side of table
canMoveLeft = false;
// etc
When working with collisions, it's always nice to see the actual boxes, for debugging. Something like this:
var debug:Shape = new Shape;
debug.addEventListener( Event.ENTER_FRAME, function():void
{
debug.graphics.clear();
debug.graphics.lineStyle( 2.0, 0xff0000 );
// char (again assuming anchor point is top left)
debug.graphics.drawRect( char.x, char.y, char.width, char.height );
// table
debug.graphics.drawRect( table.x, table.y, table.width, table.height );
});
// add our debug. NOTE: make sure it's above everything
stage.addChild( debug );
I have two draggable objects, and when your drag one them it generates a line based off where your mouse is, and the line is anchored to the other object. What Id like this code to do, is generate the line at the rear of the symbol
I got this
but I need this
if ((mouseX-targetPointX<0 && mouseY-targetPointY>0) || (mouseX-targetPointX>=0 && mouseY-targetPointY<=0)) {
line.moveTo(mouseX-offset,mouseY-offset);
line.curveTo(mouseX-offset,targetPointY-offset,targetPointX-offset,targetPointY-offset);
line.lineTo(targetPointX+offset,targetPointY+offset);
line.curveTo(mouseX+offset,targetPointY+offset,mouseX+offset,mouseY+offset);
} else {
line.moveTo(mouseX-offset,mouseY+offset);
line.curveTo(mouseX-offset,targetPointY+offset,targetPointX-offset,targetPointY+offset);
line.lineTo(targetPointX+offset,targetPointY-offset);
line.curveTo(mouseX+offset,targetPointY-offset,mouseX+offset,mouseY-offset);
}
line.endFill();
};
Instead of using the mouse position as reference to draw your curve, you can use a custom Point object with the coordinates from where you want the curve to start from.
moveTo(myPoint.x, myPoint.y);
You can create any Point you want, for example at (50,200) using the relative coordinates from your Sprite, and then find the global coordinates using localToGlobal.
var globalPoint:Point = mySprite.localToGlobal(new Point(50,200));
trace(globalPoint.x,globalPoint.y);
OK so i have a character that moves with the mouse. I need it to stay in the center of the screen(kind of like a platformer game). I can't figure out how to access the camera and move it. (Note: I have tried Vcam and moving all of the other objects but Vcam makes the file slow or something [or so i have heard] and moving the other objects in kind of like cheating [and for my needs is insufficient]) I don't have any code because i don't know where to start. Maybe someone can point me into the right direction.
Thanks,
Thor
One way is to store everyhting in one DisplayObject and then move that single object based on the camera movement. Instead of moving the camera, move the main container the opposite direction of the camera. I'm not sure why you seem to suggest a strategy like this is "cheating" as it is a perfectly suitable way to doing this.
This is my previous answer on a similar question found here.
What I do here is:
Create a Map class with a property camera which is another custom class MapCamera.
The MapCamera has five properties:
_x
_y
map - a reference to the instance of Map owning this MapCamera
offsetX
offsetY
The offset values represent the x and y spacing from the left and top edges of the screen, which should be set to half of the stage width and height so that the camera will centre on the stage correctly.
The _x and _y properties are private, and have getters and setters.
The getters are pretty basic:
public function get x():Number{ return _x; }
public function get y():Number{ return _y; }
The setters are where the viewport will be altered, like so:
public function set x(n:Number):void
{
_x = n;
map.x = -(_x + offsetX);
}
public function set y(n:Number):void
{
_y = n;
map.y = -(_y + offsetY);
}
From here, you add your children into the Map container and then can simply go:
map.camera.x = player.x;
map.camera.y = player.y;
Which will cause the player to always be in the centre of the screen.
Your camera is only a vector that modifies position of all renderable objects.
myMovieClip.x = movingClipPosition.x + camera.x
So if the camera.x is moved to the right, this will make the object move the left, giving the impression of a "camera".
I'm just trying to avoid rolling my own dragging functionality. Does anyone know of any libraries out there that have a startDrag() equivalent where you can use, say, a circular radius for the drag bounds, rather than a rectangular box?
(For circular drag area) - What you need to do is:
a) Mouse_down: Store start position. Start listening to Enter_frame.
b) Enter_Frame: Check distance from mouse position of mouse to start pos (use pythagoras)
c) only move your object if the distance is less than x
d) Mouse_up: Stop listening to enterframe
You can use a simple circular collision detection routine, it works out the hit area using the radius of the objects and distance between them. Maybe you will have to manually do this calculation in your onDrag method and stop the drag on collision with the circular bounds calculated below.
var deltax : Number = targetCentreCoord.x - hitTestCentreCoord.x;
var deltay : Number = targetCentreCoord.y - hitTestCentreCoord.y;
//works out if our circles are colliding, distance between the circles inc radius
if (((deltax * deltax) + (deltay * deltay)) <= ((((targetRadius) + (hitTargetRadius)) * ((targetRadius) + (hitTargetRadius)))))
{
Log.info("collision occured with " + candidate.name + " target coords " + targetCentreCoord + " candidate coords " + hitTestCentreCoord);
return true;
}
return false;
Nope, you need to do pixel-perfect collision (or in this case, mouse clicking) in order to do that. By nature all display objects always have rectangular bounds to them. So basically you'd have to do something like this:
mySprite.addEventListener(MouseEvent.MOUSE_DOWN, mousedDown);
function mousedDown(e:MouseEvent):void
{
//Draw my sprite to a bitmap, then check the bitmap colour at mouseX/mouseY
uint colour = myBitmap.getPixel32(mouseX, mouseY);
if(colour != TRANSPARENT){
//We've actually clicked on the object, drag it
Sprite(e.currentTarget).startDrag();
}
}
Note this is just pseudo code, you'll have to figure out what uint value transparent comes up as, and also you'll have to account for where the origin point of the sprite is when drawing to bitmap. Say you have a sprite and the contents are inside, you're going to need to create a Matrix object that has a X and Y offset that are negative .5 times the width of your sprite in order to draw it properly.
This can be done without ENTER_FRAME event.
Have a MOUSE_DOWN listener, check boundaries there, if within boundaries, then
add a MOUSE_MOVE listener.
Also, start out with a MOUSE_UP listener to remove the MOUSE_MOVE listener.