30 Aug 2011

Chapter 3: Graphics and collisions

Welcome back. 
As usual, you can grab the files at the end of the previous chapter.

This tutorial will cover:
  • Using the Graphics class to draw.
  • Applying filters.
  • Using ternary operators.
  • Collision detection.


Ok, let's practice a bit more with the Graphics class. 
Remember that we used it to draw our button? This time we'll use it to draw the ball, so let's create a class for the ball (Ball.as). 


As usual, add a listener for the ADDED_TO_STAGE event and a function for it. 

package{
        import flash.display.Sprite;
        import flash.display.Graphics;
        import flash.events.Event;
    
   public class Ball extends Sprite{
       
                           
     private function go(e:Event):void {
        removeEventListener(Event.ADDED_TO_STAGE,go);
       } 

    }
}

Now let's draw the ball, for this we will use the drawCircle function of the Graphics class. The drawCircle function takes three arguments: x, y and radius.


A good thing to do when you have a lot of code is to make some constants that handle determined stuff, so you can quickly find the values and change them if you need to. 


Make two constants: 

private const RADIUS:int = 12;
private const COLOR:uint = 0x01A6B2;

Constants are commonly capitalized. 
Colors are represented by "0x" followed by the hex value of the color. You can find this value by googling for "hex color chart" or similar stuff.  
The uint datatype represents unsigned integers, meaning it won't accept negative values.

Now let's configure the graphics:
We'll use the lineStyle function. It takes a lot of arguments, but for now I'll only talk about the first three: (thickness, color, alpha). 
They are pretty self explainitory. If you want more info, you can always read the LiveDocs that I linked at the start. 


Anyway, on your "go" function, add the following:

        graphics.lineStyle(2, COLOR, 1);

See? We used our COLOR constant, so if we ever want to change the color of the circle, we'd only need to change the COLOR value instead of looking around our code trying to find the line that handles it. 


The alpha value is automatically set to 1, so there's no real need to define it again, I just did it to give an example.


Now set the fill color. The beginFill method only takes two argumens: (color, alpha). And just as with lineStyle, alpha is automatically set to one, so all we'll do is:

        graphics.beginFill(COLOR);

Now that everything is configured, let's draw the ball:

        graphics.drawCircle(0, 0, RADIUS);

Now head to the PongGame class and add the following below the "addChild(paddle)" line on your "go" function:

        ball = new Ball();
        addChild(ball);  
        ball.x = stage.stageWidth * .5;
        ball.y = stage.stageHeight * .5;  

Also declare the variable for the ball:


        private var ball:Ball; 

Now run a test. 
We added the ball to the stage and centered it. 
The Ball class used the Graphics class to draw a circle and fill it with the color of our choice.


Well, we got the ball, but let's be fair, it looks lame.
Let's add a simple effect to it. 
For this, we'll be using two filter classes: BevelFilter and GlowFilter.
I picked these because, from my point of view, they're the simplest (others use matrixes and arrays, and the tutorial hasn't covered that yet). 


Let's declare and define our filters, as well as another color:

private const COLOR2:uint = 0x45FCFF;
private const BEVEL:BevelFilter = new BevelFilter(4, 90, COLOR2, 1, COLOR2, 1, 10, 10, 1, 1, BitmapFilterType.INNER, true);
private const GLOW:GlowFilter = new GlowFilter(0xFFFFFF, .6, 0, 0, 5, 1, true);

Most of these arguments refer to colors, alpha values, blur and strength. Please refer to the LiveDocs to see all the information about filters, as talking about them would require a whole chapter.


Now go back to the go function and add the following line:

        filters = [BEVEL, GLOW];

This will apply to the circle the filters that we just created.
Run a test and take a look at the new ball.
Feel free to modify the filters and the colors until you find a combination that you like. 


Ok, now let's allow the players to decide when the ball starts moving. On the PongGame class, add the following below the ball.y = ... line.

 stage.addEventListener(KeyboardEvent.KEY_DOWN, releaseBall);

And make the function:


private function releaseBall(e:KeyboardEvent):void{
        stage.removeEventListener(KeyboardEvent.KEY_DOWN, releaseBall);  
        addEventListener(Event.ENTER_FRAME, enterFrame);
                }  

So... our new function will remove the keyboard listener and add an enter frame listener. If you remember from the previous chapters, an enter frame function will run every frame, (that's 30 times each second on our game). 


And what do we want to happen every frame? 
First of all, let's make the ball start moving towards the player, to do that, we'll have to declare some variables: 

                private var speedX:Number;
                private var speedY:Number;
                private var angle:Number;
                private var ballSpeed:int=5;

As you see, all of these variables will be related to the movement of the ball. 
Head back to the line where we added the enter frame listener. Let's set the starting angle for the ball: 

             angle = Math.random() > .5?90:270;

So... what does that mean? 
It's really simple: First we're using the "random" method of the Math class. This method returns a random number between 0 and 0.99. 
The question mark symbol works as a conditional, so... Math.random()>.5? is exactly the same as if (Math.random()>.5)


The things after the question mark are the things to do depending on the evaluation of the condition. If it evaluates as true, the statement between the question mark and the colon will run, if not, the one after the colon will. 


So, translated, we're telling it to pick a random number between 0 and 0.999 and if that number is bigger than .5, we want it to set the angle to 90 (down), else, if the random number is lower than .5, it will set the angle to 270 (up)... that way we just implemented a method to make the ball move in a random direction everytime a new game starts, that way both players have a chance to hit first. 

Now let's make that enterFrame function: 

      private function enterFrame(e:Event):void { 
        speedX = Math.cos(angle * (Math.PI / 180)) * ballSpeed;
        speedY = Math.sin(angle * (Math.PI / 180)) * ballSpeed;
        ball.y += speedY;
        ball.x += speedX;        
     }

We're setting our speedX variable to the cosine of the angle multiplied by Pi/180. In other words, that's the cosine of our angle converted to radians. Then we just multiply the result by the ballSpeed.  Same for speedY. 


And then we just change the position of the ball depending on the previous values. 
As you can notice, we could've totally skipped those variables (speedX and speedY) and simply update the position of the ball. You can do it that way if you want to, in fact, it would make your game run "faster" (faster as in a couple of nanoseconds faster). I choose to make those variables to make it easier to understand what we are doing.


Run some tests... press any key to let the ball start moving. Sometimes it should go up, sometimes it will go down. 


Ok, now let's talk about collision detection. 
There are two built in methods for detecting collisions: hitTestObject and hitTestPoint. One checks for the bounding box of the objects and the other checks for collisions against a point.  


While these methods may have some applications for basic collisions, it's suggested that you use custom methods for this, as the hitTest ones are useless in some cases (irregular shapes, objects moving at high speeds, etc.). 


So, let's build a really basic method to check for collisions against the paddle:


When programming, try to avoid complex things when they're not necessary. Let's think about our current scenario. We have a circle and a rectangle. Do we need to call expensive functions every frame to see if they are overlapping? 


Also consider that our paddle can't change its vertical position, that makes it even easier. We just have to check if the ball will be within paddle.x and paddle.x + paddle.width when it is at the same y position as the paddle.


Let's translate it to code and make our function:



    private function ballHit(target:DisplayObject):Boolean {
            if (ball.x + speedX >= target.x) {
                    if (ball.x + speedX <= target.x + target.width) {
                            return true;
                    }
            }
            return false;
    }


 Remember what you learned about custom functions and arguments? Well, as you can see, this function takes a DisplayObject as its only argument and returns a Boolean value. 


Take a look at its content. It simply checks if the ball will be within the limits of the target. Really simple stuff. 


But we don't want it to be checked all the time, let's make some conditions for it and also some statements to run in case of a collision. Add the following to your enter frame function: 

        if (speedY > 0 ) {
                if ((paddle.y - ball.y) <= speedY && paddle.y >= ball.y) { 
                        if (ballHit(paddle)) {
                                angle += 180;
                                ball.y = paddle.y - 1 - ball.height * .5;
                                ballSpeed += 1;
                                }                                
                    }
          }

Ok, let's understand it: 
First we check if the ball is moving down.
Second line checks the position of the ball. First it looks if the vertical distance between the paddle and the ball is less than or equal than the speedY (the ball's y velocity). Why? Because there's no point in checking for collisions if the ball is far from the target. 
&& means AND. 

Then, if the ball is close enough to the paddle, it runs our ballHit function and if there's a collision, it adds 180 to the angle, increases the base ball speed (so every time a player hits the ball it starts moving faster) and moves the ball next to the paddle. 

Also, did you notice that our method doesn't check for current collisions but checks if there will be a collision on the next frame? This way we know what will happen, we have total control over the game and can act accordingly.


Now let's add some lines so the ball bounces back from the top border of the stage, while we add the enemy paddle. 

          else if ((ball.y - ball.height*.5) -speedY  < 0) {
                  angle-= 180;
                  ball.y = 0 + ball.height * .5;
          }


That "else" corresponds to the (speedY>0) condition. 
It's pretty much the same logic that we used to keep the paddle on the screen.

Well, now you have a ball that bounces from the paddle to the wall and then back to the paddle.  

That's it for today.


 DOWNLOAD THE FILES: Click here


Suggested practices

Before moving on to the next chapter, it's suggested that you do the following exercises:

On your practice application, use the Graphics class to draw two objects that start moving up or down and that can bounce from the top and bottom borders of the application.

Chapter 2                                                  Chapter 4

9 comments:

  1. Very good tutorial to learn flash develop.
    Great start for people that need to create flash games or programs and want to know what every single line of code stands for.
    Good job. It is a pity that the tutorial it is not finished.
    Fantastic anyways.

    ReplyDelete
  2. When will the next chapter come?

    ReplyDelete
  3. Anonymous5.8.14

    The ball isn't being drawn for me, I've run a trace test and it is being added to the stage but it is invisible. Can someone help?

    ReplyDelete
    Replies
    1. Anonymous21.2.15

      you have to create a public function Ball, just like you did for Paddle.

      Delete
  4. Anonymous26.11.14

    YAY! great tutorial! Ive learned a lot! Thank you and more power! ^_^

    ReplyDelete
  5. I loved this tutorial, thank you dude.

    ReplyDelete
  6. Was this tutorial written by Pseudolonewolf (creator of Mardek)? Because it's very good, but cuts off early at only 3 chapters.

    ReplyDelete