I have a working drag and drop game, but it's not perfect. All my movieclips drag and drop to their targets when you are exactly lined up with the target.
However, if you let go of the mouse up when dragging the mc outside of the target zones, it will sometimes, but not always, throw the 1010 term undefined error and will not snap the mc back to its original start x/y coordinates (it just leaves the mc in the spot it was during mouse up). I ran the debugger and it deals with this line in my drop function:
if (event.currentTarget.dropTarget != null && MovieClip(event.currentTarget.dropTarget.parent).allowed.indexOf(event.currentTarget) >= 0){
FYI, allowed is a set of target arrays since I wanted "zones" for targets and not specific targets for some of the movieclips.
Any ideas?
Updated Code Below:
if (event.currentTarget.dropTarget != null) {
var mc:MovieClip=event.currentTarget.dropTarget as MovieClip;
if (mc==null) { // typecast fails. Say there's a Sprite below
reply_txt.textColor = 0xEE1212
reply_txt.text = "Oops! Try Again!";
event.currentTarget.alpha = 1;
event.currentTarget.x = startX;
event.currentTarget.y = startY;
return; // nothing to do here
}
mc=mc.parent;
if (mc && mc.allowed) {
// this MC has "allowed" property not "undefined" - we're in the grid
// so now we can check indexOf() safely
if (mc.allowed.indexOf(event.currentTarget)>=0){
reply_txt.textColor = 0x33BC10
reply_txt.text = "Good Job!";
event.currentTarget.alpha = 1;
event.currentTarget.removeEventListener(MouseEvent.MOUSE_DOWN, pickUp);
event.currentTarget.removeEventListener(MouseEvent.MOUSE_UP, dropIt);
event.currentTarget.buttonMode = false;
event.currentTarget.x = MovieClip(event.currentTarget.dropTarget.parent).x;
event.currentTarget.y = MovieClip(event.currentTarget.dropTarget.parent).y;
stored.push(event.currentTarget);
startXarray.push(startX);
startYarray.push(startY);
counter++;
}
}
}
Yes, when you stopDrag an object, it checks what kind of a DisplayObject is below the cursor, and that one is returned as dropTarget property in your event. So, if your object is dropped onto another MC that does not have allowed property, your 1010 error is thrown. You need to check this situation in a nested if statement like this:
if (event.currentTarget.dropTarget != null) {
var mc:MovieClip=event.currentTarget.dropTarget as MovieClip;
if (mc==null) { // typecast fails. Say there's a Sprite below
returnThisBack();
return; // nothing to do here
}
mc=mc.parent;
if (mc && mc.allowed) {
// this MC has "allowed" property not "undefined" - we're in the grid
// so now we can check indexOf() safely
if (mc.allowed.indexOf(event.currentTarget)>=0) snapThisToGrid();
} else returnThisBack();
} else returnThisBack();
Related
I have multiple objects to drag to multiple targets.
I have a code without error.
I am using multiple functions. But I wonder if I pass the objects and the specific target with one function like dropIt since I have more objects and duplicated functions.
This picture is what I want to implement.
and the code is as follows.
Thanks in advance.
var obj1:Array = [obj_1, obj_10];
var obj2:Array = [obj_2, obj_20];
for each(var redsMC:MovieClip in reds)
{
obj1MC.buttonMode = true;
obj1MC.addEventListener(MouseEvent.MOUSE_DOWN, pickUp);
obj1MC.addEventListener(MouseEvent.MOUSE_UP, dropIt);
obj1MC.startX = obj1MC.x;
obj1MC.startY = obj1MC.y;
}
for each(var orangesMC:MovieClip in oranges)
{
obj2MC.buttonMode = true;
obj2MC.addEventListener(MouseEvent.MOUSE_DOWN, pickUp);
obj2MC.addEventListener(MouseEvent.MOUSE_UP, dropIt1);
obj2MC.startX = obj2MC.x;
obj2MC.startY = obj2MC.y;
}
function pickUp(event:MouseEvent):void
{
event.target.startDrag(true);
event.target.parent.addChild(event.target);
}
function dropIt(event:MouseEvent):void
{
event.target.stopDrag();
if(event.target.hitTestObject(target1)){
event.target.buttonMode = false;
event.target.x = target1.x;
event.target.y = target1.y;
}else if(event.target.hitTestObject(target10)){
event.target.buttonMode = false;
event.target.x = target10.x;
event.target.y = target10.y;
}
else
{
event.target.x = event.target.startX;
event.target.y = event.target.startY;
event.target.buttonMode = true;
}
}
function dropIt1(event:MouseEvent):void
{
event.target.stopDrag();
if(event.target.hitTestObject(target2)){
event.target.buttonMode = false;
event.target.x = target2.x;
event.target.y = target2.y;
}else if(event.target.hitTestObject(target20)){
event.target.buttonMode = false;
event.target.x = target20.x;
event.target.y = target20.y;
}
else
{
event.target.x = event.target.startX;
event.target.y = event.target.startY;
event.target.buttonMode = true;
}
}
You should somehow make your draggable objects know their targets, thus when your SWF registers an end drag event, the object that was being dragged would check against its target and if not colliding, then float/jump back. Since your objects derive from MovieClips, it's possible to add custom properties to them without doing any declarations, but be sure to check if there is something in a custom property before using it. Let's say you have assigned each draggable object a desiredTarget as whatever target you need them to be dragged. Then, you can do like this:
function dropIt(e:MouseEvent):void {
var desiredTarget:MovieClip=e.target.desiredTarget as MovieClip; // get where this should be placed
e.target.stopDrag(); // we still need to release the dragged object
if (!desiredTarget) return; // no target - nothing to do (also helps with debug)
if (e.target.hitTestObject(desiredTarget)) {
e.target.buttonMode=false;
e.target.x=desiredTarget.x;
e.target.y=desiredTarget.y;
} else {
// move dragged object back to starting position
e.target.x=e.target.startX;
e.target.y=e.target.startY;
}
}
Despite the fact Vesper's answer is already accepted, I think it to be far too brief and insufficient, on top of that it doesn't actually answer how to design a system where any number of objects could be dropped to any number of targets, without substantial changes to the code.
// Unlike the Object class, that allows String keys only
// the Dictionary class allows you to store and
// access data by the object instance.
var theValids:Dictionary = new Dictionary;
// We'll store the original (x,y) coordinates here.
var theOrigin:Point = new Point;
// The Sprite class is the superclass of MovieClip, furthermore,
// the startDrag method defined for Sprite class, so unless you
// create your own dragging code, you are bound to use Sprites,
// while you cannot drag SimpleButtons and TextFields this way.
// We'll store the current dragged object here.
var theObject:Sprite;
// This first argument is the object you want to be draggable.
// The "...targets:Array" means you can call this method with
// any number of arguments, the first one is mandatory, the
// rest will be passed in a form of Array (empty Array if you
// call this method with a single argument).
function setupDraggable(source:Sprite, ...targets:Array):void
{
// Make the object draggable.
source.addEventListener(MouseEvent.MOUSE_DOWN, onDown);
source.mouseChildren = false;
source.mouseEnabled = true;
source.buttonMode = true;
// Keep the list of the object's targets so it can be
// retrieved later by the key of the object itself.
theValids[source] = targets;
}
// Ok, let's setup the objects and link them to their designated
// targets. The whole point of the rest of the code is to make
// this one part as simple as it possible: you just edit
// these lines to tell which one objects go where.
// This object can be dropped to a single target.
setupDraggable(obj_1 , target1);
// These objects can go to two targets each.
setupDraggable(obj_10, target1, target10);
setupDraggable(obj_2 , target2, target20);
// This one object can be dropped to any of targets.
setupDraggable(obj_20, target1, target10, target2, target20);
// The MOUSE_DOWN event handler.
function onDown(e:MouseEvent):void
{
// Get the reference to the object under the mouse.
theObject = e.currentTarget as Sprite;
// Keep the object's initial position.
theOrigin.x = theObject.x;
theOrigin.y = theObject.y;
// Put the dragged object on top of anything else.
// We are operating in the parent context of all these
// objects here so there's no need to address anObj.parent.
setChildIndex(theObject, numChildren - 1);
// Start dragging.
theObject.startDrag(true);
// Listen to the MOUSE_UP event, which could happen offstage
// and out of the dragged object, so the only reliable
// way is to listen it from the Stage. That's why we
// are keeping theObject reference as an additional
// variable, without relying on event's data.
stage.addEventListener(MouseEvent.MOUSE_UP, onUp);
}
// The MOUSE_UP event handler.
function onUp(e:MouseEvent):void
{
// Unsubscribe the MOUSE_UP event handler.
stage.removeEventListener(MouseEvent.MOUSE_UP, onUp);
// Stop the dragging process.
theObject.stopDrag();
// Let's assume there could be more than a single collision.
// We need to figure the one target that is closest.
var theTarget:DisplayObject;
var theDistance:int = 100000;
// Store the dragged object position so we can
// measure distances to the valid collisions, if any.
var thePlace:Point = theObject.localToGlobal(new Point);
// Now, the magic. Lets browse through the
// valid targets and see if there's a collision.
for each (var aTarget:DisplayObject in theValids[theObject])
{
if (theObject.hitTestObject(aTarget))
{
// Let's see if the current collision is closer
// to the dragged object, than the previous one
// (if any, that's what initial 100000 for).
var aPlace:Point = aTarget.localToGlobal(new Point);
var aDistance:int = Point.distance(aPlace, thePlace);
if (aDistance < theDistance)
{
theTarget = aTarget;
theDistance = aDistance;
}
}
}
// If there's at least one collision,
// this variable will not be empty.
if (theTarget)
{
// Make the object non-interactive.
theObject.removeEventListener(MouseEvent.MOUSE_DOWN, onDown);
theObject.mouseEnabled = false;
theObject.buttonMode = false;
// Glue the dragged object to the center of the target.
theObject.x = theTarget.x;
theObject.y = theTarget.y;
}
else
{
// If we're here, that means there was no valid collisions,
// lets return the object to its designated place.
theObject.x = theOrigin.x;
theObject.y = theOrigin.y;
}
// Clean-up. Remove the reference, the object is no longer
// being dragged, so you won't need to keep it.
theObject = null;
}
P.S. I didn't test it, but I think I put enough comments to explain the whole idea.
I'm creating a game where the user bakes a cake. If the user drags something (for example a jug of water) onto the next object (e.g saucepan) it should disappear from stage. The section of code in question is:
function mouseDownHandler(event:MouseEvent):void {
event.currentTarget.startDrag(true);
}
function mouseUpHandler(event:MouseEvent):void{
var obj = event.currentTarget;
var target = obj.dropTarget;
if (target != null){
test_match(target, obj);
}
obj.stopDrag();
trace(dropTarget);
}
function test_match(target, obj){
if (target == saucePan && obj == jug)
{
removeChild(obj);
}
}
The trace inside the mouseUpHandler function shows up "null" whenever I drop the jug on an object on the stage, hense why I don't think the code executes and removes the jug from stage.
obj.dropTarget vs. dropTarget
These are two different things.
In the code below I have several MovieClips that are all TheBeetle(). They are in another MovieClip called gamelevel and also pushed in an array called bArray. previously I have indexed them in the gamelevel but after the event listener is called I cannot index them anymore and receive the error "1118: Implicit coercion of a value with static type Object to a possibly unrelated type flash.display:DisplayObject.". As the user clicks them, they die (and change the frame) and the dead body goes under other alive bodies, thats the reason I need to index them to 1 as they die. I understand what the error says but how can i do what i need to do?
The code works just fine but it wouldn't in the two lines i've mentioned in it, so take a look please:
public function clicked (event:MouseEvent)
{
if (event.target is TheBeetle && event.target.currentFrame <= 2)
{
var mc:Object = event.target
// TheBeetle is actually a MovieClip but i cannot write: var mc:MovieClip = event.target, if i do i receive 1118
if (mc.currentFrame == 1)
{
mc.gotoAndStop (Math.floor(Math.random() * 3 + 4));
}
else
{
mc.gotoAndStop (3);
}
mc.filters = null;
// Here i need to index the TheBeetle as i did before like gamelevel.setChildIndex(mc,1) but i'd receive 1118!
bArray.splice (bArray.indexOf(mc),1);
if (bArray.length == 0)
{
removeEventListener (Event.ENTER_FRAME,frameHandler);
waveTimer.removeEventListener (TimerEvent.TIMER_COMPLETE, changeLocation);
}
}
}
You need to explicitly cast target to the MovieClip Class:
var mc:MovieClip = MovieClip(event.target);
You may need to do this BEFORE the line that checks target's currentFrame, as 'Object' doesn't have a currentFrame method.
I would recommend to use soft casting when you work with events and targets. By soft casting, if you have catched wrong target, you will not have problems - cast process simply returns null.
public function clicked (e:MouseEvent){
var beetle: TheBeetle = e.target as TheBeetle;
if(beetle != null && beetle.currentFrame <= 2){
//Work with beetle as you want
}
}
I m currently converting my old game project which is in AS2 into AS3 by myself. And there was a problem. In the AS2 version of my game I used to check for a movieclip's sub movieclip's property and use it for some calculations, using
if (mc1.mc2.prop == undefined){
//do something
}
and during somepoint of the game the mc1 or mc2 is removed.
but in AS3 this no longer works because I cannot access the prop after mc1 or mc2 is removed.
Anyhelp? thanks.
It's hard to give a more concise answer without knowing how your game actually works, but this function will allow you to check if a hierarchical value exists on an object:
function hasProp(target:Object, prop:String):Boolean
{
var tests:Array = prop.split('.');
var test:* = target;
for each(var p:String in tests)
{
if(test.hasOwnProperty(p))
{
test = test[p];
}
else return false;
}
return true;
}
Used like:
if( hasProp(mc1, "mc2.prop") )
{
// Property exists.
}
I'm creating a simple music player which will play just one song. Stage timeline has got just one frame. There is main graphic for player which is movieClip_1 (it has 4 frames in it's own timeline) and that works ok. I've got button (button_2) which starts and pauses the song and the movieClip_1 (works ok). And i also have got a graphic (it's called tube - i have changed it to movie clip, it has got one frame inside it's timeline) as a seekbar component which I just want it to move correspondingly to channel.position of this song on the x axis which it does but gives me triple error of:
TypeError: Error #1009: Cannot access a property or method of a null object reference.
Debug points at this line:
tube.x = (chan.position/song.length) * 83;
I would really appreciate a tip regarding error and also what method to use in order for user to be able to navigate (with mouse) through song by moving tube on x axis and going to different part of song. I read about .startDrag and .stopDrag would that be good idea with my present code?
My code so far:
movieClip_1.stop();
var itsstoped:Boolean;
var youpausedit:Boolean;
var counter:Timer = new Timer(100);
var chan:SoundChannel;
var song:Sound = new Sound();
song.load(new URLRequest("wmc2.mp3"));
song.addEventListener(Event.COMPLETE, soundloaded);
function soundloaded(event:Event)
{
trace("Song has been loaded.");
}
counter.addEventListener(TimerEvent.TIMER,countcounter);
itsstoped = true;
youpausedit = false;
button_2.addEventListener(MouseEvent.CLICK, presser);
function presser(event:MouseEvent):void
{
if(itsstoped == true && youpausedit == true)
{
movieClip_1.gotoAndPlay(1);
chan = song.play(chan.position);
itsstoped = false;
}
else if(itsstoped == false)
{
movieClip_1.gotoAndStop(1);
chan.stop();
itsstoped = true;
youpausedit = true;
}
else if(itsstoped == true && youpausedit == false)
{
movieClip_1.gotoAndPlay(1);
chan = song.play();
counter.start();
itsstoped = false;
}
}
function countcounter(e:TimerEvent):void
{
trace(chan.position/song.length);
var percentage:uint = 100 * (chan.position / song.length);
trace(percentage);
if(percentage == 100)
{
movieClip_1.gotoAndStop(1);
itsstoped = true;
youpausedit = false;
counter.stop();
}
}
tube.buttonMode = true;
tube.useHandCursor = true;
addEventListener(Event.ENTER_FRAME, movewhenplay);
function movewhenplay(e:Event):void
{
tube.x = (chan.position/song.length) * 83;
}
Regarding the error, you're accessing an object that has yet to be instantiated, i.e. there is no data to fetch as it does not exist.
As for the searchbar, I'm guessing you're using some kind of mask for the search bar, as you're using its x position to display the song position. I would suggest you instead use its width parameter, and set the center point to the far left.
To be able to then search through the song, you can add a copy of this searchbar, place it on top of the first one. Set its alpha to 0, to make it invisible, but still clickable. Add mouseEvent listeners to that, and when the user clicks it, you can set the song to play from the position which you can calculate using mouseX relevant to the invisible search bar.
I.e. if you want the clicked position in percents
percent = (mouseX - invisiSearchBar.x) / invisiSearchbar.width