0

I've recently had a breakthrough on the coding for my skee-ball style game. I was having serious issues with collision detection, which the boffins of stackoverflow have helped me solve. I was coding a tween for my object, a ball, to create a smooth roll effect because I hadn't been able to get it to move smoothly otherwise. I finally had a eureka moment and managed to make the object move incrementally instead of teleporting and was able to eliminate my tween. Without a tween, collision detection was working properly. But I still have some issues. First, how do I get gravity/friction to apply to the ball so that it doesn't come to a stop in a non-collision (or scoring) area when it otherwise runs out of steam? Two, how can I make it collide properly with the boundaries so it reverses course at a proper rate? And three, slightly unrelated but still a major challenge so I'll mention it now: if my collision areas (scoring targets) are aligned vertically, can I put conditions in the collision detection to test for rate of speed to effectively collide with a target? How can I determine that rate of speed?

Ok, here's my current wobbly code for rolling my ball and hitting a target:

function moveBall(event:MouseEvent):void
{
    if (this.mouseX <= 172 + gameTable.tableLane.width)
    {
        ball.x = this.mouseX;
    }
    else
    {
        ball.x = 172 + gameTable.tableLane.width;
    }
    if (this.mouseX < 200)
    {
        ball.x = 200;
    }
    //trace(this.mouseX)
}

function releaseBall(event:MouseEvent):void
{
    //rollBall();
    ballSpeed = rollPower * 10;
    //set minimum roll distance
    if (ballSpeed < 313)
    {
        ballSpeed = 313;
    }
    ballStop = ball.y - ballSpeed;
    gameElements.addEventListener(Event.ENTER_FRAME, rollBall);
    gameElements.addEventListener(Event.ENTER_FRAME, ballTargetScore);
    gameElements.removeEventListener(MouseEvent.MOUSE_MOVE, moveBall);
}



function rollBall(event:Event):void
{
    trace("ballSpeed is " + ballSpeed);
    trace("ballStop is " + ballStop);
    if (ball.y > ballStop)
    {
        ball.y -= 6;
        ball.y += friction;
    }

}



//match targets to scoring values which should be tied to determineScore()
function ballTargetScore(event:Event):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);
                gameElements.removeEventListener(Event.ENTER_FRAME, rollBall);
                gameElements.removeEventListener(Event.ENTER_FRAME, ballTargetScore);
            }
        }
    }


}

For more background on my development, please take a look at my previous question thread: How can I get rid of non-null hitTestObject error with already instantiated objects?

Thanks for any help,

Alan


Just want to add my function for determining the speed of the ball roll. It doesn't use a keyboard or any other conventional, that I have found in searching, method to control movement. The number changes dynamically until the mouse button is pressed and then that is the value to work with. This is inside my drawGame function to create the game table.

gameElements.addEventListener(Event.ENTER_FRAME, powerMeter);

function powerMeter(event:Event):void
{
    rollPower +=  speed;
    gameTable.powerMeter.height = rollPower;
    //trace(rollPower);

    if (rollPower == 60 || rollPower == 0)
    {
        speed *=  -1;
        //trace("switch speed to " + speed);
    }
}

and the list of my related global vars:

//physics vars
var rollPower:Number = 0;
var speed:Number = 1;//speed of ball on roll
var ballSpeed:Number;
var ballStop:Number;
var friction:Number = 0.15;
var gravity:Number = 0.70;

Ok, I've sorta fixed it, but it is still messy visually. The ball will now move backwards. I've meant to put in a condition where the ball will be removed when it reaches a threshold which represents the gutter on the table; however, flash doesn't seem to run the condition statement soon enough to catch the ball before it has moved visually beyond the y value. It does remove the ball, but it looks ugly as the ball has rolled back onto the lane before it disappears. How can I get condition checking times sped up?

Here's my modified code. Pointers are appreciated.

function rollBall(event:Event):void
{
    trace("ballSpeed is " + ballSpeed);
    trace("ballStop is " + ballStop);
    if (ball.y > ballStop)
    {
        ball.y -=  6;
        ball.y +=  friction;
    }
    else
    {
        gameElements.removeEventListener(Event.ENTER_FRAME, rollBall);
        gameElements.addEventListener(Event.ENTER_FRAME, rollBackwards);
    }

}//end rollBall

function rollBackwards(event:Event):void
{
    trace("rollBackwards is in effect");
    trace("ball.y is " + ball.y);
    if (ball.y < 313)
    {
        ball.y +=  6 + gravity;
        if (ball.y >= 313)
        {
            gameElements.removeEventListener(Event.ENTER_FRAME, ballTargetScore);
            determineScore(null);
        }
    }
}//end rollBackwards

Ok, I wanted to put an update in here. I basically "solved" my rollBackwards timing issues by adjusting the Y coordinate. I either measured wrong, or got my orientation in opposition to the direction I was meant to be moving. It mostly works now.

function rollBackwards(event:Event):void
{
    //trace("rollBackwards is in effect");
    //trace("ball.y is " + ball.y);
    if (ball.y < 240)
    {
        ball.y +=  6 + gravity;
        if (ball.y >= 235)
        {
            gameElements.removeEventListener(Event.ENTER_FRAME, ballTargetScore);
            determineScore(null);
        }
    }
}//end rollBackwards

What is still a little ugly looking is when/where the ball is removed after making collision with a target. My, perhaps, awkward conditional chain of coordinate calculations largely works from an effectiveness POV, but the ball always seems to be removed several pixels(?) prior to the actual collision being measures - even accounting for bounding boxes as opposed to actual shapes. I also attempted to turn the ball into a tween just before it was removed to make it fade away in a pretty manor, but that never seemed to work. And I never even tried to tackle turning the arc-shaped "bumpers" under each target into actual collision objects that the ball could roll along if the aim was off. That was just put into the "too hard" basket. Below is my code for the collision measuring and the tweening, just in case anyone has any keen insights to add. Otherwise, I'm relatively happy with the end result.

//called during determineScore event
function disappearBall():void
{
    myBallTween = new Tween(ball,"alpha",Strong.easeOut,ball.y + 5,ball.y,8,false);
    myBallTween.obj.alpha = 0;
    myBallTween.start();
}//end disappearBall

function ballTargetScore(event:Event):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]))
            {
                //handle vertically aligned targets
                if (centerTargetArray.indexOf(targetArray[i]) >= 0)
                {
                    var centerIndex:uint = centerTargetArray.indexOf(targetArray[i]);
                    //trace("centerTargetArray check works " + centerTargetArray[centerIndex]);
                    if (ballStop <= 60 && ball.hitTestObject(gameTable.upperScoringArea.upperScoringAreaCTarget))
                    {
                        targetValue = targetTextArray[i];
                        //trace('targetValue becomes ' + targetValue);
                        determineScore(targetValue);
                        gameElements.removeEventListener(Event.ENTER_FRAME, rollBall);
                        gameElements.removeEventListener(Event.ENTER_FRAME, ballTargetScore);

                    }
                    if (ballStop > 60 && ball.hitTestObject(gameTable.middleScoringArea.middleScoringAreaTargetTop))
                    {
                        targetValue = targetTextArray[i];
                        //trace('targetValue becomes ' + targetValue);
                        determineScore(targetValue);
                        gameElements.removeEventListener(Event.ENTER_FRAME, rollBall);
                        gameElements.removeEventListener(Event.ENTER_FRAME, ballTargetScore);

                    }
                    if (ballStop > 100 && ball.hitTestObject(gameTable.middleScoringArea.middleScoringAreaTargetMiddle))
                    {
                        targetValue = targetTextArray[i];
                        //trace('targetValue becomes ' + targetValue);
                        determineScore(targetValue);
                        gameElements.removeEventListener(Event.ENTER_FRAME, rollBall);
                        gameElements.removeEventListener(Event.ENTER_FRAME, ballTargetScore);

                    }
                    if (ballStop > 135 && ball.hitTestObject(gameTable.middleScoringArea.middleScoringAreaTargetLower))
                    {
                        targetValue = targetTextArray[i];
                        //trace('targetValue becomes ' + targetValue);
                        determineScore(targetValue);
                        gameElements.removeEventListener(Event.ENTER_FRAME, rollBall);
                        gameElements.removeEventListener(Event.ENTER_FRAME, ballTargetScore);

                    }
                    if (ballStop > 161 && ball.hitTestObject(gameTable.lowerScoringArea.lowerScoringAreaTarget))
                    {
                        targetValue = targetTextArray[i];
                        //trace('targetValue becomes ' + targetValue);
                        determineScore(targetValue);
                        gameElements.removeEventListener(Event.ENTER_FRAME, rollBall);
                        gameElements.removeEventListener(Event.ENTER_FRAME, ballTargetScore);

                    }
                }
                else
                {
                    targetValue = targetTextArray[i];
                    //trace('targetValue becomes',targetValue);
                    determineScore(targetValue);
                    gameElements.removeEventListener(Event.ENTER_FRAME, rollBall);
                    gameElements.removeEventListener(Event.ENTER_FRAME, ballTargetScore);
                }
            }
        }
    }
}//end ballTargetScore

I considered some "efficiency" measures in the long function above, but I couldn't quite get the logic right in lining up with the intended targets so I just wrote it out "longhand".

Thanks for the help everyone,

Alan

Community
  • 1
  • 1
mrmacross
  • 15
  • 5

1 Answers1

0

Why not try to use Box2D to simulate the friction and gravity? http://box2dflash.sourceforge.net/ You can build a slope with a ramp in the end and simulate the ball moving on that slope with that engine. It may be inaccurate since it isn't meant for 3d physics, however, it will also run smoothly since what you are attempting to simulate has only two bodies, a ball and a skeeball machine.

I think when you want to simulate physics, in most cases, using Box2DFlashAS3 is a good idea. It is also a valuable too have in your satchel for future endeavors.

To answer your question simply, I think you could create a variable for speed, a constant for friction and gravity and simply calculate each turn:

ballX += ballSpeedX;
ballY += ballSpeedY;
if(onGround())
{
if(ballSpeedX > friction)
    ballSpeedX -= friction;
else if(ballSpeedX < - friction)
    ballSpeedX += friction;
else ballSpeedX = 0;
}

if(!onGround()) ballSpeedY -= gravity;
else ballSpeedX -= gravity * Math.cos(slopeAngle);

//do something about ballSpeedY when it hits the ground..
if(ballSpeedY < 0 && onGround()) ballSpeedY *= -0.5;

You may need to lower the bounce and or treat it differently with consideration to the slope's angle.

AturSams
  • 7,568
  • 18
  • 64
  • 98
  • I don't think I will be simulating a bounce at this stage. I mean in an ideal world it would be good to simulate the ball leaving the plane, but I am still in the beginner/intermediate skill level and this assessment is due Friday night (Australian time). – mrmacross Sep 25 '12 at 22:21
  • btw, my view is completely top down. i'm only affecting the Y position with my ball roll (use a followMouse-type function to aim before the roll) – mrmacross Sep 26 '12 at 05:36
  • If your view is top down, what exactly do you want to achieve in this project as far as the balls behavior, what walls can it hit, under which conditions would you like it to stop? Do you want it to slow down gradually, or even move back? – AturSams Sep 26 '12 at 09:48
  • the ball rolls a certain distance on the Y axis colliding with scoring areas. Now, the trick is to ignore the first collisions on the way "up" if there is sufficient power/speed to keep the ball moving. The other issue is if the aiming is off and the ball misses a target it doesn't just come to a stop. It should, in the simulation, roll backwards until it falls into a "gutter" which is another collision that has to be ignored the first time. rollBackwards, above, sorta works... – mrmacross Sep 26 '12 at 12:04
  • ...except for the issue that it takes a bit too long to remove the ball because I am faking the collision by just checking for the Y position (which is supposed to be where the gutter begins). – mrmacross Sep 26 '12 at 12:05
  • Why are you faking the collision instead of checking if there is a collision and the ball is also moving backwards? – AturSams Sep 26 '12 at 12:11
  • because it was what I could work out with my level of ability? – mrmacross Sep 26 '12 at 14:24
  • but i fixed my issue of the ball rolling backwards too far. My Y coordinate needed adjusting. – mrmacross Sep 26 '12 at 14:25
  • Good, work, could you maybe sum up the question, I think the large amount of code and the long description may be detrimental to getting quality answers within the time constraint you described. – AturSams Sep 26 '12 at 14:46
  • yeah, it was very difficult for an intermediate/beginner such as myself. I'll try a full update tomorrow (my time) but everyone can feel free to have a play on my (sorta) finished product at http://authenticrubydesigns.com/portfolio/SkeeBlast.html Cheers! – mrmacross Sep 26 '12 at 15:53
  • The (sorta) finished product looks great and has a good feel. I am really impressed. – AturSams Sep 26 '12 at 15:55
  • In general, you could replace this: if (this.mouseX <= 172 + gameTable.tableLane.width) { ball.x = this.mouseX; } else { ball.x = 172 + gameTable.tableLane.width; } (with this:) ball.x = Math.min(this.mouseX, 172 + gameTable.tableLane.width); – AturSams Sep 26 '12 at 15:57
  • i wish i could figure out how to make the ball's visible position lineup with the scoring target it collides with, but it seems to be removed before it really overlaps. Might be where the registration points are for the individual symbols. *shrug* Also tried to get a tween working as the ball was removed (just before) as a fade out on the alpha, but that didn't seem to work either so I've commented it out for now. I'll update my code tomorrow, should go to bed now (2AM Australia time). Oh, and thanks for the compliment. – mrmacross Sep 26 '12 at 16:02