다음을 통해 공유


Bizzy Bees Step 6: User interaction (XNA Walkthrough)

This is part of a walkthrough series on creating a game (Bizzy Bees) in XNA

Overview
Step 1: Setting the stage (projects and assets)
Step 2: Drawing the scene
Step 3: Adding flowers
Step 4: Making things move
Step 5: Adding some bees to the mix
Step 6: User interaction
Step 7: Rounding it up

The last step in the series is to add som user interaction so we can actually play the game.

Catching the users input in the Update method

The user interaction consists of taps and you can basically tap two areas of the screen, either the beepicker to select a bee, or the columns to match a bee with the bottommost flower.

In Silverlight if you want to check when the user has clicked or tapped something, the most common way to do this is to add a tap or click eventhandler on a button or any other object, but in XNA there is no such thing as a control or a click event. 
To handle user interaction in XNA you have to do two things.

1. In the Initialize function you enable any gestures that you want to enable, for example Tap

             TouchPanel.EnabledGestures = GestureType.Tap;

This is a bitmap so you can add as many different gesture types as you want, for example if you wanted Tap and FreeDrag you would do GestureType.Tap | GestureType.FreeDrag.

2. In the Update function, you check what gestures the user has done since the last update and act on them

  if (!gameOver)
            {
                //Handle user interaction  while  (TouchPanel.IsGestureAvailable) { GestureSample gesture = TouchPanel.ReadGesture(); if (gesture.GestureType == GestureType.Tap) { HandleInput(gesture.Position); } }  
                foreach (Column c in columns)
                {
                    c.Update();
                    if (c.ReachedBottom)
                    {
                        gameOver = true;
                        break;
                    }
                }
            }

NOTE! By reading the gesture with TouchPanel.ReadGesture() you remove it from the TouchPanels gesture, if you fail to do this, this will be an endless loop

Acting on the input

Ok, so now we are handling the input, it’s time to act on it, and rather than making this extremely complex and check for every single flower touch area or bee touch area we just say

a) did they touch the bee picker area?  if so, handle bee selection

b) do we already have a selected bee? if so, check if they touched a matching flower

c) if neither of the above is true, then we just don’t give a hoot meaning all other taps will be ignored

  private void HandleInput(Vector2 position)
        {
            if (position.X > 0 && position.X < 480 && position.Y > 700 && position.Y < 800)
                HandleBeeSelection(position.X);
            else if (selectedBee != null)
                HandleFlowerSelection(position.X, position.Y);
        }

Even in the BeePicker we wont do any fancy schmanzy collision detection since there is a very easy way to know which bee you picked.  We know we have 5 bees so the index of the bee can be infered from the x position by simply dividing it by 5.
but we’ll get to that in a little bit.

Our HandleBeeSelection method will look like this

  private void HandleBeeSelection(float x)
        {
            //first de-select any previously selected bee if (selectedBee != null)
            {
                selectedBee.IsSelected = false;
                selectedBee = null;
            }
            //Mark and select the new bee
            beePicker.MarkSelectedBee(x);
            selectedBee = beePicker.GetSelectedBee(x);
        }

The selection and deselection here will be used to give a visual cue to the user that a bee is selected

So a couple of housekeeping things to make the above work.

1. Add a “property” to the Bee class called IsSelected that will logically keep track of if the bee is selected or not (used only for drawing)

         public bool IsSelected = false;

2. Implement the methods MarkSelectedBee and GetSelectedBee in the BeePicker

         public void MarkSelectedBee(float x) { GetSelectedBee(x).IsSelected = true; } public Bee GetSelectedBee(float x) { int index = (int)(x / beeDeltaX); return bees[index]; }

3. Modify the BeePicker Draw method so that it draws the bee with a slight tint of DimGray if it is selected

  public void Draw()
        {
            for (int i = 0; i < 5; i++)
            {
                if(bees[i].IsSelected)                    
                    spriteBatch.Draw(beeMap, new Vector2(beeStartX + i * beeDeltaX, beeStartY), new Rectangle(bees[i].Color * 91, 0, 91, 91), Color.DimGray);
                else
                    spriteBatch.Draw(beeMap, new Vector2(beeStartX + i * beeDeltaX, beeStartY), new Rectangle(bees[i].Color * 91, 0, 91, 91), Color.White);
            }
        }

selectedbee

If you followed along so far you should now have something that looks like the above, where if you tap a bee it will turn slightly gray.  Notice that it is only the bee that turns Grey and not the background around it, this is because the area outside of the bee is transparent in the bee png and the color isn’t superimposed on the transparent areas which really works to our favor here.

Matching flowers and bees

The flower selection is a little bit trickier since we have to remove and replace bees and flowers but let’s look at the structure first

  private void HandleFlowerSelection(float x, float y)
        {
            //verify that we are tapping inside a column if (x > 10 && x < 470 && y > 100 && y < 700)
            {
                int rainbowColor = 6;
                int selectedColumnIndex = (int)((x - 10) / 92);
                Column selectedColumn = columns[selectedColumnIndex];
                Flower selectedFlower = selectedColumn.GetBottomFlower();

                //check if we have a match or if it was a rainbow flower if(selectedFlower != null && (selectedFlower.Color == selectedBee.Color || selectedFlower.Color == rainbowColor))
                {
                    //remove the bottom flower
                    selectedColumn.RemoveBottomFlower();
                    
                    //replace the bee - making sure that there is always a match by passing in a list of all the bottom flower colors List<int> availableFlowers = GetAvailableFlowers();
                    beePicker.RemoveAndReplaceBee(selectedBee, availableFlowers);

                    //deselect the bee
                    beePicker.DeselectAll();
                    selectedBee = null;

                    //if it was a rainbow flower - add points if (selectedFlower.Color == rainbowColor)
                    {
                        score++;
                        //if we reached 10, 20, 30... points, increase the velocity to make the game harder as we go along if ((score % 10) == 0)
                        {
                            foreach (Column c in columns)
                                c.Velocity += 0.1f;
                        }
                    }
                }
            }
        }

We verify that we are in an actual column, and find the bottom flower of that column to see what color it is.
If it matches the bee or if it is a rainbow flower (meaning it matches all bees) we remove the flower and we replace the bee.

In order to not get locked up in a state where we have no matching bees and flowers we pass in a list of all the bottom flowers to ensure that we have at least one match, or else the replacing bee needs to match one of the flowers.

At the end of the function we increase the score (number of rainbow flowers collected) if it was a rainbow flower, and to make the game progressively harder we increase the velocity of all the columns.  Remember? this is the variable that the Columns use in the update function to progress the position of the flowers.

Now we have a few methods that we need to implement to get this working.

In the Column class we implement the methods GetBottomFlower and RemoveBottomFlower, this is a pretty easy task since we use Lists and the bottom flower is always at index 0

         public Flower GetBottomFlower() { if (flowers.Count > 0) return flowers[0]; else
                return null; } internal void RemoveBottomFlower() { if (flowers.Count > 0) flowers.RemoveAt(0); }

The GetAvailableFlowers method in the Game1 class uses the GetBottomFlower function to generate a list of all the bottom flowers in the columns

  private List<int> GetAvailableFlowers()
        {
            List<int> flowerColors = new List<int>();
            foreach (Column c in columns)
            {
                Flower f = c.GetBottomFlower();
                if (f != null)
                    flowerColors.Add(f.Color);
            }
            return flowerColors;
        }

In the BeePicker class we need to implement the DeselectAll and RemoveAndReplaceBee methods

  public void RemoveAndReplaceBee(Bee selectedBee, List<int> availableFlowers)
        {
            int beeIndex = bees.IndexOf(selectedBee);

            //check if we already have a bee that matches the available flowers //remember to skip over the selectedBee since it will be removed in a moment bool match = false;
            int rainbowColor = 6;

            //if there is a rainbow flower it will always match if (availableFlowers.Contains(rainbowColor))
                match = true;
            else
            {
                for (int i = 0; i < bees.Count; i++)
                {
                    if(i != beeIndex && availableFlowers.Contains(bees[i].Color)){
                        match = true;
                        break;
                    }
                }
            }

            int color;
            if(match){
                //we already have a match, just add a random colored bee
                color = r.Next(numberOfBeeColors + 1 );
            }
            else{
                //we have no match so we must pick a color from the available colors
                color = availableFlowers[r.Next(availableFlowers.Count)];
            }

            //set the selected bee to the new color to "create" a new bee
            bees[beeIndex].Color = color;
        }

        internal void DeselectAll()
        {
            foreach (Bee bee in bees)
                bee.IsSelected = false;
        }

The RemoveAndReplaceBee method seems a bit more complex… but what we do here is really to check if the bees we have left match any of the available flowers (to make sure we always have a match)
If we already have a match, we can just add a new random bee to replace the old one, but if we don’t we need to make the new bee match one of the existing flowers.

And finally, yay! we are done, and have a playable game

done

In the last article in the series I will discuss a few problems with the game that I will leave up to the reader to fix, and discuss a few things I have done in the “real” game to make the game, at least in my opinion, a bit more interestingLer