How can I get rid of non-null hitTestObject error

2019-09-02 20:29发布

问题:

I am a beginner/intermediate AS3 "programmer" trying to complete a skeeball-like flash-based game for a university assessment, and I'm doing myself serious mental damage trying to get even basic object (ball) collisions working with scoring targets on my stage. I'm using a power bar type variable to determine the force of the ball roll which is translated into a tween to create a smooth movement up my "lane" and into the scoring area (this is overhead perspective). The targets are instances of movie clips inside a larger movie clip that is made up of all the components of the game table. Even though my game table and scoring components have already been instantiated when I release the ball, I am getting the typical non-null errors:

ballSpeed is 552

targetArray value is [object upperScoringAreaCTarget_mc]

TypeError: Error #2007: Parameter hitTestObject must be non-null.

at flash.display::DisplayObject/_hitTest()

at flash.display::DisplayObject/hitTestObject()

at SkeeBlast_7_fla::MainTimeline/ballTargetScore()

at SkeeBlast_7_fla::MainTimeline/rollBall()

at SkeeBlast_7_fla::MainTimeline/releaseBall()

Here is my ball release function:

function releaseBall(event:MouseEvent):void
{
    rollBall();
    gameElements.removeEventListener(MouseEvent.MOUSE_MOVE, moveBall);
}

function rollBall():void
{
    ballSpeed = rollPower * 12;
    trace("ballSpeed is " + ballSpeed);
    ballFriction();
    ballGravity();
    //ball.y -= ballSpeed;
    //var myBallTween:Tween = new Tween(ball,"y",Strong.easeOut,ball.y,ball.y -     ballSpeed,3,true);
    myBallTween = new Tween(ball,"y",Strong.easeOut,ball.y,ball.y - ballSpeed,3,true);
    myBallTween.start();
    ballTargetScore();

}

and my collision detection and scoring function:

 //match targets to scoring values which should be tied to determineScore()
function ballTargetScore():void
{
var targetValue:String;
var targetArray:Array = new Array(gameTable.upperScoringArea.upperScoringAreaCTarget,
  gameTable.upperScoringArea.upperScoringAreaLtTarget,
  gameTable.upperScoringArea.upperScoringAreaRtTarget,
  gameTable.middleScoringArea.middleScoringAreaTargetTop,
  gameTable.middleScoringArea.middleScoringAreaTargetMiddle,
  gameTable.middleScoringArea.middleScoringAreaTargetLower,
  gameTable.lowerScoringArea.lowerScoringAreaTarget);

if (ball.hitTestObject(gameTable.tableDisplay))
{
    myBallTween.stop();
}
else
{
    for (var i:uint; i < targetArray.length; i++)
    {
        if (targetArray[i] != null)
        {
            trace("targetArray value is " + targetArray[i]);

            if (ball.hitTestObject(gameTable.upperScoringArea.upperScoringAreaCTarget))
            {
                targetValue = gameTable.upperScoringArea.upperScoringAreaC_text.text;
            }
            if (ball.hitTestObject(gameTable.upperScoringArea.upperScoringAreaLtTarget))
            {
                targetValue = gameTable.upperScoringArea.upperScoringAreaLt_text.text;
            }
            if (ball.hitTestObject(gameTable.upperScoringArea.upperScoringAreaRtTarget))
            {
                targetValue = gameTable.upperScoringArea.upperScoringAreaRt_text.text;
            }
            if (ball.hitTestObject(gameTable.upperScoringArea.middleScoringAreaTargetTop))
            {
                targetValue = gameTable.middleScoringArea.middleScoringAreaU_text.text;
            }
            if (ball.hitTestObject(gameTable.upperScoringArea.middleScoringAreaTargetMiddle))
            {
                targetValue = gameTable.middleScoringArea.middleScoringAreaM_text.text;
            }
            if (ball.hitTestObject(gameTable.upperScoringArea.middleScoringAreaTargetLower))
            {
                targetValue = gameTable.middleScoringArea.middleScoringAreaL_text.text;
            }
            if (ball.hitTestObject(gameTable.upperScoringArea.lowerScoringAreaTarget))
            {
                targetValue = gameTable.lowerScoringArea.lowerSA_text.text;
            }
            else
            {
                trace("no hit");
            }
        }
    }
}

//gameElements.removeEventListener(Event.ENTER_FRAME, ballTargetScore);
determineScore(targetValue);
//return targetValue;
}

It's all still a bit messy as I tinker trying to find the right combination.

I need to first get a basic ball<->target or boundary collision working, but ultimately, I'd like to try to figure out how to control the collisions conditionally since many of the targets are aligned vertically like a skeeball table. Will I be able to measure the speed of the ball as it is tweening/rolling? Will I be able to apply gravity correctly so that the ball falls and if it catches one of the arcs beneath the scoring target, it will roll along the arc until it 'falls into the hole'? I think I will have issues with the bounding box of my objects on that one.

Thanks for any help,

Alan


Here's the response to Vesper with a cleaned up function (thanks) and new error.

function ballTargetScore():void
{
    var targetValue:String;
    var targetArray:Array = new Array(gameTable.upperScoringArea.upperScoringAreaCTarget,
      gameTable.upperScoringArea.upperScoringAreaLtTarget,
      gameTable.upperScoringArea.upperScoringAreaRtTarget,
      gameTable.middleScoringArea.middleScoringAreaTargetTop,
      gameTable.middleScoringArea.middleScoringAreaTargetMiddle,
      gameTable.middleScoringArea.middleScoringAreaTargetLower,
      gameTable.lowerScoringArea.lowerScoringAreaTarget);
    var targetTextArray:Array =  new Array(gameTable.upperScoringArea.upperScoringAreaC_text.text,
        gameTable.upperScoringArea.upperScoringAreaLt_text.text,
        gameTable.upperScoringArea.upperScoringAreaRt_text.text,
        gameTable.middleScoringArea.middleScoringAreaU_text.text,
        gameTable.middleScoringArea.middleScoringAreaM_text.text,
        gameTable.middleScoringArea.middleScoringAreaL_text.text,
        gameTable.lowerScoringArea.lowerSA_text.text);

    for (var i:uint; i < targetArray.length; i++)
    {
        if (targetArray[i] != null)
        {
            trace("targetArray value is " + targetArray[i]);
            if (ball.hitTestObject(targetArray[i]))
            {
                targetValue = targetTextArray[i];
                trace('targetValue becomes',targetValue);
            }
        }
    }

    determineScore(targetValue);
}

and the error:

ballSpeed is 432

targetArray value is [object upperScoringAreaCTarget_mc]

targetArray value is [object upperScoringAreaLtTarget_mc]

targetArray value is [object upperScoringAreaRtTarget_mc]

targetArray value is [object middleScoringAreaTargetTop_mc]

targetArray value is [object middleScoringAreaTargetMiddle_mc]

targetArray value is [object middleScoringAreaTargetLower_mc]

targetArray value is [object lowerScoringAreaTarget_mc]

ArgumentError: Error #2025: The supplied DisplayObject must be a child of the caller.

at flash.display::DisplayObjectContainer/removeChild()

at SkeeBlast_7_fla::MainTimeline/determineScore()

at SkeeBlast_7_fla::MainTimeline/ballTargetScore()

at SkeeBlast_7_fla::MainTimeline/rollBall()

at SkeeBlast_7_fla::MainTimeline/releaseBall()

Thanks for the assistance.


The rules, which I'm sure are necessary, are making me turn this into a really long question. :)

Anyway, I sort of figured out the error about the DisplayObject, but the program still isn't working correctly.

I fixed the error by adding my gameElements MC to my removeChild call in the determineScore function, which is below; however, the ball still isn't removed, even with the error gone, and the targetValue and the score never update (or show up in the trace from above).

function determineScore(scoreEvent:String):void
{
    if ( scoreEvent == "D-O" || scoreEvent == "2XB" || scoreEvent == "?")
    {
        if (scoreEvent == "D-O")
        {
            ballCount +=  1;
            gameTable.tableDisplay.BR_text.text = ballCount - 1;
            gameTable.tableDisplay.BR_text.embedFonts = false;
            gameElements.removeChild(ball);
            if (ballCount > 0 )
            {
                initBall();
            }
            else
            {
                drawEnd();
            }
        }
        else if (scoreEvent == "2XB")
        {
            ballCount +=  2;
            gameTable.tableDisplay.BR_text.text = ballCount - 1;
            gameTable.tableDisplay.BR_text.embedFonts = false;
            gameElements.removeChild(ball);
            if (ballCount > 0 )
            {
                initBall();
            }
            else
            {
                drawEnd();
            }
        }
        else
        {
            determineScore(allRandomScore(allScoresArray));
        }
    }
    else
    {
        scoreTotal +=  Number(scoreEvent);
        ballScore = Number(scoreEvent);
        gameTable.tableDisplay.Score_text.text = scoreTotal;
        gameTable.tableDisplay.Score_text.embedFonts = false;
        gameElements.removeChild(ball);
        if (ballCount > 0 )
        {
            initBall();
        }
        else
        {
            drawEnd();
        }
    }
}

Thanks for looking at this. I feel like I'm finally making a little bit of progress.


Ok, continuing along with yet another update.

gameElements is the primary empty MC that I add all the other MC, like the game table and the ball, to so that when I go back to the main menu, I can remove everything at once.

var gameElements:MovieClip = new MovieClip();
var gameTable:MovieClip = new table_mc();
var ball:MovieClip = new blastBall();

and from my drawGame function:

...
stage.addChildAt(gameElements, 1);
gameElements.addChild(gameTable);
initBall();
...

and initBall:

function initBall():void
{
    //resize ball
    ball.height = 18;
    ball.width = 18;
    //place ball on table in correct location
    ball.x = gameTable.x;
    ball.y = gameTable.height - 20;
    gameElements.addChild(ball);
    //reduce number of remaining balls;
    ballCount -=  1;
    //hide the mouse and connect its movement to ball
    Mouse.hide();
    gameElements.addEventListener(MouseEvent.MOUSE_MOVE, moveBall);
}

Hope there's no limit to this entry. :)

Here's the little addition I put in determineScore to pick up the "results" from ballTargetScore (or lack thereof, really):

if (scoreEvent == null)
    {
        trace("scoreEvent is null");
        gameTable.tableDisplay.BR_text.text = ballCount - 1;
        gameTable.tableDisplay.BR_text.embedFonts = false;
        gameElements.removeChild(ball);
        if (ballCount > 0)
        {
            initBall();
        }
        else
        {
            drawEnd();
        }
    }

(haven't cleaned up anything else yet) Still trying to get that first collision to work. When I started catching the null value, initBall and drawEnd sort of started to work (ok, it still doesn't really do what I want but at least there was a response).

回答1:

Well, you have an array of your targets, and the error you receive means that some of its elements are null. But, when you iterate through the array, you for some reason check against the entire set of targets, not checking if any of them are null. I don't understand why. Seeing that you need a text variable to be assigned if a hit is detected, I advise you making another array with corresponding indexes that will contain all the texts you want to assign.

var targetTextArray:Array=(gameTable.upperScoringArea.upperScoringAreaC_text.text,
    gameTable.upperScoringArea.upperScoringAreaLt_text.text,
    gameTable.upperScoringArea.upperScoringAreaRt_text.text,
    gameTable.middleScoringArea.middleScoringAreaU_text.text,
    gameTable.middleScoringArea.middleScoringAreaM_text.text,
    gameTable.middleScoringArea.middleScoringAreaL_text.text,
    gameTable.lowerScoringArea.lowerSA_text.text);

Then, when you iterate through targetArray, you no longer need to check what was that target in order to retrieve the correct text. You do:

for (var i:uint; i < targetArray.length; i++)
{
    if (targetArray[i] != null)
    {
        trace("targetArray value is " + targetArray[i]);
        if (ball.hitTestObject(targetArray[i])) {
            targetValue=targetTextArray[i];
            trace('targetValue becomes',targetValue);
        }
    }
}

Update: Okay, let's clean up your determineScore() function. It looks like you are correctly adding and removing your ball, but it's possible you do something of this twice. Removing the ball twice without adding it back will net you this error. Currently I can't get if gameElements.removeChild(ball); gets called twice within that function.

function determineScore(scoreEvent:String):void
{
    var reBall:Boolean=false; // should we make another ball roll, given the score
    var reDisplay:Boolean=false; // should we set new text
    if ((scoreEvent==null)||(scoreEvent=="")) return; // we haven't scored
    if ( scoreEvent == "D-O" || scoreEvent == "2XB" || scoreEvent == "?")
    {
        if (scoreEvent == "D-O")
        {
            ballCount +=  1;
            reBall=true;
            reDisplay=true;
        } 
        else if (scoreEvent == "2XB")
        {
            ballCount +=  2;
            reBall=true;
            reDisplay=true;
        }
        else
        {
            determineScore(allRandomScore(allScoresArray));
            // hey, what are we doing here? We seemingly receive a "?" as an input,
            // and we roll a random value out of a set given elsewhere. Okay
            return; // as good measure, because we don't have to do anything more
        }
    }
    else
    { // if we're here, we assume we have a numeric score passed inside, right?
        scoreTotal +=  Number(scoreEvent);
        ballScore = Number(scoreEvent);
        reBall=true;
        reDisplay=true;
    }
    // Now look, we always have to launch a new ball or display end, and
    // we always have to redraw score textfield. But look, you are always calling 
    // determineScore with some value, while you should do it only if you hit something!
    // So I added starting clause of "if null or empty string".
    if (reDisplay) {
        // we have to update text
        gameTable.tableDisplay.BR_text.text = ballCount - 1;
        gameTable.tableDisplay.BR_text.embedFonts = false;
    }
    if (reBall) {
        // we have to remove ball and launch another, if we have any left
        gameElements.removeChild(ball);
        if (ballCount > 0 )
        {
            initBall();
        }
        else
        {
            drawEnd();
        }
    }
}