Archive for the ‘Coding’ Category.

3 "S"’s RPG Sample – Step 1

So like any game development project, the first steps we’ll take in implementing our 3 “S”‘s game will be putting together a design document and starting the shell of our actual game. Since we’ll be developing the game using Unity (shocker, I know) the first steps are pretty easy:

  • Create the project
  • Set up the Assets structure
  • Create the typical scenes

I have to assume that you have at least some basic knowledge about using Unity. If I had to start from the very beginning, we’d never actually finish building a game. The game interface and visuals of the game will be very simple as that’s not what we’re concentrating on here. Getting a working game completed is more important than a visually stunning game that never gets finished.

Start a new Unity project, select the 2D mode, give it a name. After Unity starts, create the following folders:

  • Audio
  • Prefabs
  • Scenes
  • Scripts
  • Sprites

In the Scenes folder create the following scenes:

  • CreateCharacter
  • Game
  • MainMenu

Open the Menu scene and add a couple of buttons:

  • New Game
  • Load Game
  • Options
  • Exit

We’ll look into what it takes to start a new game in a bit. For the Load Game menu, it’s up to you how you want to handle saving games and this will influence how the loading of games works. If you want to allow the player to save at any time with an unlimited number of saves, you could potentially have a lot of save files to deal with. You probably don’t want to allow this however. It’s more typical to give the player a limited amount of saves that he can overwrite. This is much easier to deal with when constructing your Load Game scene, although the theory is the same as if you were allowing an unlimited amount of save files. The player just ends up with a smaller list to scroll through.

What options you allow the player to tweak is up to you. At the very least, however, you’ll want to give the player some options for audio and video. Turning on and off and adjusting the volume level of audio is pretty standard. Setting the resolution and allowing the player to toggle between full-screen and windowed mode is the other usual group of option settings.

We need to add some scripts to hook up the basic UI elements. I usually name my scripts for this the same as the scene they’re used in. If you want to append “Scene” to a script to be more descriptive that’s fine. Add C# files for the MainMenu, Options, and Game scene.

The MainMenu script is pretty simple. It just transitions to the appropriate scene for the button clicked:

using UnityEngine.SceneManagement;
using UnityEngine;
using UnityEngine.UI;

public class MainMenu : MonoBehaviour {

    public GameObject ButtonPanel;
    public GameObject LoadGamePanel;

        void Start ()
    {
            //check to see if game has already been started and adjust buttons accordingly
       
        }
       
    public void NewGameButtonClick()
    {
        SceneManager.LoadScene("CreateCharacter");
    }

    public void LoadGameButtonClick()
    {
        LoadGamePanel.SetActive(true);
        ButtonPanel.SetActive(false);
    }

    public void OptionsButtonClick()
    {
        SceneManager.LoadScene("Options");
    }

    public void ExitButtonClick()
    {
#if UNITY_EDITOR
        UnityEditor.EditorApplication.isPlaying = false;
#else
        Application.Quit();
#endif
    }

    public void SavedGamesCancelButtonClick()
    {
        LoadGamePanel.SetActive(false);
        ButtonPanel.SetActive(true);
    }

    public void SavedGamesOKButtonClick()
    {

    }
}

Hook each button to the appropriate method. We’ll be adding more to this for loading saved games, but this will do for now.

Open the Options scene and add an OK and Cancel button, add the following to the Options script, then hook the buttons to the methods:

using UnityEngine;
using UnityEngine.SceneManagement;

public class Options : MonoBehaviour {

    private GameOptions _options;

    void Start()
    {
        //load current settings

    }

    public void OKButtonClick()
    {
        //save changed settings

        SceneManager.LoadScene("Menu");
    }

    public void CancelButtonClick()
    {
        SceneManager.LoadScene("Menu");
    }
}

We’ll need to do the same thing for the CreateCharacter scene. As the player will be performing a number of steps to fill in all of the character info, we’ll add Next and Previous buttons as well:

Note that the OK button has been disabled. It will only enable once all of the steps for creating the character have been completed.

The code to handle navigation for the buttons is pretty simple at this point:

using UnityEngine;
using UnityEngine.UI;

public class CreateCharacter : MonoBehaviour {

    private int _curStep;

    public Button NextButton;
    public Button PreviousButton;

        void Start ()
    {
        _curStep = 0;  
        }
       
        public void NextStepButtonClick()
    {
        _curStep++;
        if(_curStep == 3)
        {
            PreviousButton.interactable = true;
            NextButton.interactable = false;
        }

        ProcessStep();
    }

    public void PreviousStepButtonClick()
    {
        _curStep--;
        if (_curStep == 0)
        {
            PreviousButton.interactable = false;
            NextButton.interactable = true;
        }

        ProcessStep();
    }

    private void ProcessStep()
    {
        switch(_curStep)
        {
            case 0:
                {

                    break;
                }
            case 1:
                {

                    break;
                }
           case 2:
                {

                    break;
                }
            case 3:
                {

                    break;
                }      
        }
    }
}

We’ll start with 4 steps and can add more later if needed. The ProcessSteps method will simply toggle panels for each step as needed.

The last piece we’ll start on is the pause menu so we have a way to get out of the Game scene. This is a simple Panel with some buttons:

In the Game script is the typical button handler:

public class Game : MonoBehaviour
{

    public GameObject PausePanel;
    private bool _gamePaused;


        // Use this for initialization
        void Start ()
    {
               
        }
       
        // Update is called once per frame
        void Update ()
    {
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            if(_gamePaused)
            {
                ResumeButtonClick();
            }
            else
            {
                PausePanel.SetActive(true);
                _gamePaused = true;
            }
        }
    }

    public void ResumeButtonClick()
    {
        PausePanel.SetActive(false);
        _gamePaused = false;
    }

    public void SaveGameButtonClick()
    {

    }

    public void ExitButtonClick()
    {
        SceneManager.LoadScene("MainMenu");
    }
}

We check each frame to see if the Esc key has been pressed and, if so, toggle the PausePanel based on whether or not the game is already paused.

This is the basic skeleton for our game that allows navigation between the current scenes. We’ll add more to this soon, but we need to do some work on the first two “S”’s before that happens. That’s coming up next.

Azure Mobile Services Tip o’ the Day

So I’ve been creating the database tables for the RPG I’ve been working on and finally got connected to the database in Visual Studio using the Server Explorer rather than creating the tables in the Azure Portal. Spent about 2 hours working on the Spell database and hooked up the UI in the game to query it. Imagine my surprise when I tried to pull some data from the table and got:

Rest Response:{"code":404,"error":"Error: Table ‘Spell’ does not exist."}

I went back and queried other tables (that I’d created in the Portal) and had no problems. Scratching my head, I turned to my friend Bing. Turns out tables not created through the portal don’t get hooked up to the Mobile Service automatically. Created the table in the Mobile Service and the query worked fine.

While it’s a bit easier to use the tools in Visual Studio or SQL Service Management Studio an extra step will be required to be able to use the tables in your Mobile Service. Whether that is worth it is going to be a YMMV type of thing I guess. Maybe creating the table in the Portal and fleshing it out with other tools is a good tradeoff.

So Many Stars

While many game services and platforms have built-in ratings system, there may come a time when you, like me currently, need a way to allow a user to rate your game using a standard 5 star system. I took a bit of time today to put together a scene to handle this.

First I created filled and outline star graphics:

 

Next I created a scene and laid out a couple of buttons and the 5 star outlines:

The stars are Image UI components. The OK button is not interactable until a rating is selected.

Next I added some event triggers to the stars:

The only unusual thing about this is I added a parameter to the standard event handler:

public void StarEnter(GameObject obj)

public void StarExit(GameObject obj)

public void StarClick(GameObject obj)

Since I’m using the same 3 events for all 5 stars I needed a way to tell which star is being handled. I didn’t want to have to go through some convoluted chunk of code to figure out which star the mouse was over. This would have involved doing a raycast from the mouse location and seeing it the ray hit something, then figuring out which star it hit. Once I set up the empty events in my script the Unity UI automatically recognized the need for a GameObject parameter.

Here’s the entire Rating class:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using UnityEngine.EventSystems;
using System;
using System.Collections.Generic;

 

public class Rating : MonoBehaviour {

 

    public Image Star1;
    public Image Star2;
    public Image Star3;
    public Image Star4;
    public Image Star5;

 

    public Sprite Star;
    public Sprite StarOutline;

 

    private int _ratingSelected = 0;

 

    public Button OKButton;

 

    void Start()
    {
        //check for existing rating and disable if exists
        if (PlayerPrefs.HasKey("Rating"))
        {
            StarEnter(GameObject.Find(PlayerPrefs.GetInt("Rating").ToString() + "Star"));

 

            _ratingSelected = PlayerPrefs.GetInt("Rating");
        }
    }

 

    public void StarEnter(GameObject obj)
    {
        if (_ratingSelected == 0)
        {
            switch (obj.name)
            {
                case "1Star":
                {
                    Star1.sprite = Star;

 

                    break;
                }
                case "2Star":
                {
                    Star1.sprite = Star;
                    Star2.sprite = Star;

 

                    break;
                }
                case "3Star":
                {
                    Star1.sprite = Star;
                    Star2.sprite = Star;
                    Star3.sprite = Star;

 

                    break;
                }
                case "4Star":
                {
                    Star1.sprite = Star;
                    Star2.sprite = Star;
                    Star3.sprite = Star;
                    Star4.sprite = Star;

 

                    break;
                }
                case "5Star":
                {
                    Star1.sprite = Star;
                    Star2.sprite = Star;
                    Star3.sprite = Star;
                    Star4.sprite = Star;
                    Star5.sprite = Star;

 

                    break;
                }
            }
        }

 

    }

 

    public void StarExit(GameObject obj)
    {
        if (_ratingSelected == 0)
        {
            switch (obj.name)
            {
                case "1Star":
                {
                    Star1.sprite = StarOutline;

 

                    break;
                }
                case "2Star":
                {
                    Star1.sprite = StarOutline;
                    Star2.sprite = StarOutline;

 

                    break;
                }
                case "3Star":
                {
                    Star1.sprite = StarOutline;
                    Star2.sprite = StarOutline;
                    Star3.sprite = StarOutline;

 

                    break;
                }
                case "4Star":
                {
                    Star1.sprite = StarOutline;
                    Star2.sprite = StarOutline;
                    Star3.sprite = StarOutline;
                    Star4.sprite = StarOutline;

 

                    break;
                }
                case "5Star":
                {
                    Star1.sprite = StarOutline;
                    Star2.sprite = StarOutline;
                    Star3.sprite = StarOutline;
                    Star4.sprite = StarOutline;
                    Star5.sprite = StarOutline;

 

                    break;
                }
            }
        }
    }

 


    public void StarClick(GameObject obj)
    {
        _ratingSelected = Convert.ToInt32(obj.name.Substring(0, 1));
        OKButton.interactable = true;
    }

 

    public void OKButton_Click()
    {
        //save rating
        PlayerPrefs.SetInt("Rating", _ratingSelected);
        Application.LoadLevel("Menu");
    }

 

    public void CancelButton_Click()
    {
        Application.LoadLevel("Menu");
    }
}
}

Since we don’t want the player to be able to change a rating once it’s made, the first thing we do is check to see if the player has already rated the game. I’m storing this in the PlayerPrefs for convenience sake. Obviously it should be in a place a little more secure. If the player has already rated the game, we call the StarEnter handler, passing the control for that number of stars, which sets the related number of stars. We then set the _ratingSelected member with the value from the PlayerPrefs key. Since the event handlers don’t do anything if the _ratingSelected member has a value other than 0, this effectively disables them.

The StarExit handler resets the images to the star outline. We could have simply not passed a parameter to it and just reset all of the stars, but I like the consistency of having a parameter for all 3 handlers.

If the player clicks a star, the _ratingSelected member is set and the OK button enabled, allowing the player to save the rating.

Pretty straightforward. If you use this as is, you’ll have to have a scene named “Menu” and Image controls with the names I used or you’ll get an error.

Feel free to comment – is this useful? Would you have done something different? Am I wasting my time here? 🙂

Rolling the Dice With Unity

No, I’m not talking about taking a chance using Unity to do game development. That decision was made once MS stopped caring about XNA 🙁 (obligatory links here and here on it’s impending demise). I’m talking about having rolling dice in your game. In this case it’s just one die and it’s 2D not 3D. Still, it might prove useful to some people so here we go.

So I’m working on a 2D board game that uses a die to determine the number of squares a player moves. I wanted to show the player something so they’d know the game is rolling a die rather than just generating a random number and showing it. Turns out it’s fairly easy to do using Unity.

First off, I needed some pictures of the 6 sides of a die:

     

I didn’t want to take up any space on the board and also wanted to let the player know in a rather obvious way that it was time to roll the die, so I decided to slide in a panel with the image of the die on it and allow him to tap or click it to start the rolling process. Here’s what I ended up with:

Notice the panel is far off to the left. It’ll slide in from and out to the left. This is done with a simple animation:

At the end of the animation the x position of the panel is set to 0 which, since it’s anchored in the center of the Canvas moves it to the middle of the screen. I do the exact opposite to slide the panel back off the screen in the animation to close the panel, setting the x position to –1000 at the end of the animation. If you’ve never used animations before, there are a ton of resources to help you learn them. As always, your best first step is the Unity site. Here’s a link to the manual section for animations. Here’s a nice tutorial that covers slide UI controls in and out as I’m doing, as well as a ton of other concepts.

Once I had the animations set up, all that was needed was a small amount of code to do the die rolling:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

 

public class DieManager : MonoBehaviour {

 

    public Sprite[] Dice;

 

    private bool _isRolling;

 

    private float _totalTime;
    private float _intervalTime;

 

    private int _curDie;

 

    Image _die;

 

    private bool _dieRolled;

 


	// Use this for initialization
	void Start ()
    {
        Init();
	}

 

    private void Init()
    {
        _totalTime = 0.0f;
        _intervalTime = 0.0f;
        _curDie = 0;
        _dieRolled = false;
        _die = GameObject.Find("DieImage").GetComponent<Image>();
        _die.sprite = Dice[_curDie];
    }

 

	void Update () 
    {
        if (_isRolling)
        {
            _intervalTime += Time.deltaTime;
            _totalTime += Time.deltaTime;

 

            if (_intervalTime >= 0.1f)
            {
                //change die
                _curDie = Globals.Rnd.Next(0, 6);

 

                //set image to selected die
                _die.sprite = Dice[_curDie];

 

                _intervalTime -= 0.1f;
            }

 

            if (_totalTime >= 2.00f)
            { 
                _isRolling = false;
                _dieRolled = true;
            }
        }

 

	}

 

    public void DieImage_Click()
    {
        if(!_dieRolled)
            _isRolling = true;
    }
}

The Dice member holds the 6 images of the die. It’s public as I set them in the Unity UI:

The script is attached to the Canvas object. The die images are then dragged into place from the folder in the game Assets where I’d placed them.

The _isRolling member is used to determine whether or not the time is tracked between frames in order to know when it’s time to change the die image (every 1/10 of a second) or when the die rolling is completed (after 2 seconds).

The _totalTime and _intervalTime members are added to each frame to know when it’s time to change the die image and when the die rolling is completed.

The _curDie member holds the value of the most recently generated number between 0 and 5 (remember kids, arrays are numbered starting at 0, see the screenshot above!) and is the index into the Dice array for displaying the image.

_die is the reference to the Image control in the Panel.

_dieRolled is used to determine if the animation is done for this turn of the game so the player can’t just keep clicking the image to roll again.

The DieImage_Click event is used in an Event Trigger on the DieImage object:

So how does the panel slide in and out since it doesn’t happen automatically? Two simple events for the buttons in the scene:

    public void PanelTestButton_Click()
    {
        GameObject panel = GameObject.Find("DiePanel");
        Animator animator = panel.GetComponent<Animator>();
        animator.Play("DiePanelOpen");
    }

 

    public void PanelOKButton_Click()
    {
        GameObject panel = GameObject.Find("DiePanel");
        Animator animator = panel.GetComponent<Animator>();
        animator.Play("DiePanelClose");
    }

 

I’ve left a bug in the logic as an exercise for the reader. You can only click the die once. It doesn’t reset when you close the panel. How would you solve this? I’ll post a comment later. 🙂

Creating a Space Shooter Weapon System

So a question came up the other day from Dave Voyles about how to design a weapon system for a space shooter. He needed to figure out how to track what weapons a player had collected and how to switch between them. I got to thinking about it and decided to see what I could come up with. Here’s the basic requirements that I figured would be good to have:

  • A ship could start out with no weapons or 1 or more weapons, with or without ammo
  • A weapon can have infinite ammo or require ammo pickups
  • The player shouldn’t be able to switch to a weapon that hasn’t been equipped on the ship or if it doesn’t have ammo
  • An ammo type could be shared between weapons (think universal energy cells for energy powered weapons)
  • Ammo pickups could have different amounts of ammo for an ammo type
  • Switching between weapons could wrap around both forward or backward. For example, using an Xbox controller, the left and right bumpers could be used to switch to the previous or next weapon – if the first weapon is selected and the left bumper is pressed, the last weapon that’s equipped would be selected.
  • An icon on the game’s HUD would show the currently selected weapon and the ammo available for that weapon would be displayed.

Starting from an empty Unity project, we’ll only need one scene and some GUI elements in that scene. I’m using buttons for all of the player input in this sample, but you would want to handle whatever method of input you’re targeting for your game, touch included. Here’s what my example looks like:

The 5 images on the right are used to display the currently selected weapon, which is the blank image between the Next and Previous buttons. Notice that the button to fire the weapon is initially set to disabled. Whether or not to enable this will be handled by the script we’ll attach to the scene. The small images above the currently selected weapon image will show the weapons that have been equipped on the ship. The buttons to pickup weapons and ammo would be replaced in a real game with sprites or some other element that the player’s ship would interact with.

The next thing we’ll need is a script to control everything and a script to define weapons and ammo. We’ll look at the latter first:

using UnityEngine.UI;

public enum AmmoType
{
    Regular,
    Shotgun,
    Sniper,
    Energy
}

public enum WeaponType
{
    Vulcan,
    SoloGun,
    Sniper,
    ShotGun,
    Seeker
}

public class Weapon
{
    public WeaponType Type;
    public Image HUDIcon;
    public AmmoType Ammo;
    public int AmmoCount;
}

 

For our basic example this is all we’ll need. More than likely your weapon class would have more members and functionality and you might even have an ammo class.

Notice that we only have 4 ammo types. The SoloGun and the Seeker I decided were energy weapons and will share whatever energy ammo is available.

The script to control the interaction with the weapons is only a bit more complex in my opinion:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using System;
using UnityEngine.EventSystems;

public class WeaponInventory : MonoBehaviour
{
    private int[] weaponAmmoRate = new int[] { 10, 1, 1, 1, 5 };

    Dictionary<WeaponType, Weapon> weapons;
    WeaponType curWeaponIndex = WeaponType.Vulcan;

    public Image CurWeaponIcon;

    public Image VulcanImage;
    public Image SoloGunImage;
    public Image SniperImage;
    public Image ShotgunImage;
    public Image SeekerImage;


    public Image MiniVulcanIcon;
    public Image MiniSoloGunIcon;
    public Image MiniSniperIcon;
    public Image MiniShotgunIcon;
    public Image MiniSeekerIcon;


    public Button FireButton;

    public Text AmmoDisplay;

    public bool WrapWeapons = true;

    private Dictionary<AmmoType, int> Ammo; //-1 for infinite


	// Use this for initialization
	void Start () {

        weapons = new Dictionary<WeaponType, Weapon>();

        weapons.Add(WeaponType.Vulcan, new Weapon() { Type = WeaponType.Vulcan, HUDIcon = VulcanImage, Ammo= AmmoType.Regular });

        MiniVulcanIcon.color = new Color(1.0f, 1.0f, 1.0f, 1.0f);

        Ammo = new Dictionary<AmmoType, int>();

        Ammo.Add(AmmoType.Regular, 100);
        Ammo.Add(AmmoType.Energy, 0);
        Ammo.Add(AmmoType.Shotgun, 0);
        Ammo.Add(AmmoType.Sniper, 0);

        FireButton.interactable = true;

        GetCurWeaponImage();
	}

    public void NextWeapon()
    {
        if (!WrapWeapons && (int)curWeaponIndex == Enum.GetValues(typeof(WeaponType)).GetUpperBound(0))
            return;
        else
        {
            WeaponType originalIndex = curWeaponIndex;

            curWeaponIndex++;
            while (!weapons.ContainsKey(curWeaponIndex))
            {
                if ((int)curWeaponIndex > Enum.GetValues(typeof(WeaponType)).GetUpperBound(0))
                    curWeaponIndex = 0;
                else
                { 
                    curWeaponIndex++;
                    if (curWeaponIndex == originalIndex)
                        break;
                }
            }

            GetCurWeaponImage();
            CheckWeapon();
        }
    }

    public void PreviousWeapon()
    {
        if (!WrapWeapons && (int)curWeaponIndex == 0)
            return;
        else
        {
            WeaponType originalIndex = curWeaponIndex;

            curWeaponIndex--;
            while (!weapons.ContainsKey(curWeaponIndex))
            {
                if ((int)curWeaponIndex < 0)
                    curWeaponIndex = (WeaponType)Enum.GetValues(typeof(WeaponType)).GetUpperBound(0);
                else
                {
                    curWeaponIndex--;
                    if (curWeaponIndex == originalIndex)
                        break;
                }
            }

            GetCurWeaponImage();
            CheckWeapon();
        }
    }

    private void CheckWeapon()
    {
        bool enable = Ammo[weapons[curWeaponIndex].Ammo] > 0;

        FireButton.interactable = enable;
    }

    private void GetCurWeaponImage()
    {
        CurWeaponIcon.sprite = weapons[curWeaponIndex].HUDIcon.sprite;

        UpdateAmmoDisplay();
    }

    public void PickupVulcanWeaponButtonClick()
    {
        if (!weapons.ContainsKey(WeaponType.Vulcan))
        { 
            weapons.Add(WeaponType.Vulcan, new Weapon() { Type = WeaponType.Vulcan, HUDIcon = VulcanImage, Ammo = AmmoType.Regular });
            MiniVulcanIcon.color = new Color(1.0f, 1.0f, 1.0f, 1.0f);
        }
    }

    public void PickupSoloGunWeaponButtonClick()
    {
        if (!weapons.ContainsKey(WeaponType.SoloGun))
        {
            weapons.Add(WeaponType.SoloGun, new Weapon() { Type = WeaponType.SoloGun, HUDIcon = SoloGunImage, Ammo = AmmoType.Energy });
            MiniSoloGunIcon.color = new Color(1.0f, 1.0f, 1.0f, 1.0f);
        }
    }

    public void PickupSniperWeaponButtonClick()
    {
        if (!weapons.ContainsKey(WeaponType.Sniper))
        { 
            weapons.Add(WeaponType.Sniper, new Weapon() { Type = WeaponType.Sniper, HUDIcon = SniperImage, Ammo = AmmoType.Sniper });
            MiniSniperIcon.color = new Color(1.0f, 1.0f, 1.0f, 1.0f);
        }
    }

    public void PickupShotgunWeaponButtonClick()
    {
        if (!weapons.ContainsKey(WeaponType.ShotGun))
        { 
            weapons.Add(WeaponType.ShotGun, new Weapon() { Type = WeaponType.ShotGun, HUDIcon = ShotgunImage, Ammo = AmmoType.Shotgun });
            MiniShotgunIcon.color = new Color(1.0f, 1.0f, 1.0f, 1.0f);
        }
    }

    public void PickupSeekerWeaponButtonClick()
    {
        if (!weapons.ContainsKey(WeaponType.Seeker))
        { 
            weapons.Add(WeaponType.Seeker, new Weapon() { Type = WeaponType.Seeker, HUDIcon = SeekerImage, Ammo = AmmoType.Energy });
            MiniSeekerIcon.color = new Color(1.0f, 1.0f, 1.0f, 1.0f);
        }
    }


    public void FireWeaponButtonClick()
    {
        Ammo[weapons[curWeaponIndex].Ammo] -= weaponAmmoRate[Convert.ToInt32(weapons[curWeaponIndex].Ammo)];

        if (Ammo[weapons[curWeaponIndex].Ammo] == 0)
            FireButton.interactable = false;

        UpdateAmmoDisplay();
    }

    private void UpdateAmmoDisplay()
    {
        AmmoDisplay.text = Ammo[weapons[curWeaponIndex].Ammo].ToString();
    }

    public void PickupRegularAmmoButtonClick()
    {
        Ammo[AmmoType.Regular] += Convert.ToInt32(EventSystem.current.currentSelectedGameObject.tag);
        CheckWeapon();
        UpdateAmmoDisplay();
    }

    public void PickupEnergyAmmoButtonClick()
    {
        Ammo[AmmoType.Energy] += Convert.ToInt32(EventSystem.current.currentSelectedGameObject.tag);
        CheckWeapon();
        UpdateAmmoDisplay();
    }

    public void PickupShotgunAmmoButtonClick()
    {
        Ammo[AmmoType.Shotgun] += Convert.ToInt32(EventSystem.current.currentSelectedGameObject.tag);
        CheckWeapon();
        UpdateAmmoDisplay();
    }

    public void PickupSniperAmmoButtonClick()
    {
        Ammo[AmmoType.Sniper] += Convert.ToInt32(EventSystem.current.currentSelectedGameObject.tag);
        CheckWeapon();
        UpdateAmmoDisplay();
    }

}

I had originally planned on using an enum for weaponAmmoRate, but the duplicate values made that impractical. Obviously the values need to match the order of the WeaponType enum.

The weapons variable holds all the possible weapons a ship can carry, even if they’re not equipped. If you add new weapons to the WeaponType enum, there will automatically be room for them in the possible weapons the ship can equip, no change necessary.

The curWeaponIndex is straightforward, it points to the item in the weapons array that’s currently selected.

CurWeaponIcon is the image in the scene that shows the player the weapon that’s currently selected. We need the member here to allow us to change it when the player uses an input device to switch between weapons. We could grab the Image every time the weapon is switched, but that’s cumbersome, more code than we need to use and wastes time. You’ll see this a lot in Unity, having a member in a script that is hooked to a UI object. This is one of the features of Unity that I really like. The Unity IDE recognizes the public members of a script and allows you to set them by dragging something from the Hierarchy onto it in the Inspector.

The next two chunks of Images are used to set the CurWeaponIcon and display which weapons the ship has equipped respectively.

We have a reference to the button that fires the selected weapon as we need to enable or disable it when the selected weapon is changed or if the selected weapon runs out of ammo.

As the player fires the selected weapon, we use the AmmoDisplay member to update the amount of ammo that’s available for the weapon.

Some games allow the selection of the weapon to wrap from last to first or vice versa when navigating through the equipped weapons. The WrapWeapons member allows us to decide whether or not we enable this feature. The code for handling the navigation between weapons uses this member to correctly set the selected weapon as we’ll see shortly.

The Ammo member holds the amount of ammo for every type of weapon. In the case of the some weapons you might want this is be infinite. This is usually the case with the default or first weapon that’s equipped in the ship. Setting the value to –1 for a weapon allows for infinite ammo for a weapon.

At the start of the scene we create our weapons array, add a Vulcan to it, set all the UI elements to indicate we have a weapon set, and add some ammo for it.

In the NextWeapon and PreviousWeapon methods we first check to see if the current weapon is the last or first respectively and if weapon wrap is not turned on we just return as there’s nothing to do. We then go through the weapons list checking to see if a weapon has been equipped on the ship to select. If we go through the entire list and don’t find one we just re-select the original weapon. We set the select weapon icon and check to see if the weapon has ammo, disabling the fire button if not. We also update the display of the available ammo for the selected weapon.

For each of the method for picking up a weapon we first see if the weapon is already equipped. If not, we create an instance in the weapons list and set the icon to indicate that weapon is available.

When the fire button is pressed we subtract from the ammo for that weapon based on the weapon rate for the weapon and disable the weapon is the ammo has run out. There’s a potential bug in this code. See if you can find it. 😉

When ammo is picked up, we grab the Tag property of the GameObject to see how much ammo it contains and add that to the proper element in the Ammo array. One thing that you could change for a real game is add a property to the ammo GameObject to indicate what type of ammo it is and have one method for handling all ammo pickups. Try replacing the buttons with a GameObject that does this.

That’s it. Pretty simple I think, but it should be enough to at least start you out with a space shooter type game. As also, feedback and questions are welcome. 🙂

More GameSparks and Unity Goodness

So if you’ve read the previous post and have a GameSparks project set up with leaderboards and achievements and a Unity project that handles adding achievements and scores you’re all set to complete the basics by adding functionality to display the high scores and the achievements a player has earned.

Add a new scene and set it up like the following:

leaderboards

All that’s needed is a few lines of code to display the scores in the leaderboard. Open the Leaderboards class and add the following:

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using GameSparks.Api;
using GameSparks.Api.Messages;
using GameSparks.Api.Requests;
using GameSparks.Api.Responses;
using GameSparks.Core;

public class Leaderboards : MonoBehaviour {

	// Use this for initialization
	void Start () {

        LeaderboardDataResponse response = new LeaderboardDataRequest().SetLeaderboardShortCode("HighScoreLeaderboard").SetEntryCount(10).Send();

        if(!response.HasErrors)
        {
            foreach (var entry in response.Data)
            {
                Text player = GameObject.Find("Player" + entry.Rank.ToString()).GetComponent<Text>();
                player.text = entry.UserName;
                Text score = GameObject.Find("Score" + entry.Rank.ToString()).GetComponent<Text>();
                score.text = entry.GetNumberValue("Score").ToString();
            }
        }
        else
        {
            Text player = GameObject.Find("Player1").GetComponent<Text>();
            player.text = "No Scores";
        }
	}

    public void OKButtonClick()
    {
        Application.LoadLevel("MainMenuScene");
    }
}

If you don’t have any scores other than the one from the previous post you should add some players through the GameSparks Test Harness and log some scores. To do so use the RegistrationRequest and AuthenticationRequest calls:

new player2

 

authentication

then add scores using the PostHighScoreEarned call under LogEvent:

posthighscore

Make sure you make all three calls in this order or your scores won’t be posted correctly.

On to achievements. I created a couple of additional achievements from the one created in the previous post:

achievements

Of course, we’ll need a scene to display them in:

achievementsscene

The graphic for each achievement is a UI Image object. We set the tag for the graphic to the achievement’s shortcode so we can find it when the scene loads.

Create a script and attach it to the scene’s Canvas as usual. Add the following to it:

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using GameSparks.Api;
using GameSparks.Api.Messages;
using GameSparks.Api.Requests;
using GameSparks.Api.Responses;
using GameSparks.Core;


public class Achievements : MonoBehaviour {

	// Use this for initialization
	void Start ()
    {
        new ListAchievementsRequest().Send((response) =>
        {
            if (!response.HasErrors)
            {
                foreach (ListAchievementsResponse._Achievement achievement in response.Achievements)
                {
                    //if achievement hasn't been earned grey out icon
                    if (achievement.Earned.HasValue && !achievement.Earned.Value)
                    {
                        Image graphic = GameObject.FindGameObjectWithTag(achievement.ShortCode).GetComponent<Image>();
                        graphic.color = new Color(1f, 1f, 1f, .1f);
                    }
                }
            }
        });
	}

    public void OKButtonClick()
    {
        Application.LoadLevel("MainMenuScene");
    }

}

The ListAchievementsRequest call gets all the achievements set up in the system with a member for each achievement that tells if the player has earned it. If he hasn’t we change the alpha component of the color for the achievement’s graphic to indicate that the player hasn’t earned it.

That’s all it takes to get basic achievements and leaderboards working in your game. The project for this post is located here.

There is a lot more functionality in GameSparks that I’d like to cover. I’m thinking I might dig into some of the social functionality like friends and messages next. If you’ve looked into GameSparks and there’s something that you’d like to see covered let me know.

Integrating GameSparks into a Unity game

A big selling point of video games is having things like leaderboards and awards/achievements. Unfortunately, implementing these things is a lot of work. Tools like Azure Mobile Services and 3rd party offerings make this a little easier, but it’s still a lot of work. I originally started looking at Azure Mobile Services, via bitrave, but recently stumbled across something that looked like it might be a bit easier, being built specifically for what I was looking to implement – GameSparks. I’m going to go over using the SDK in a couple of posts, each one being relatively short and sweet.

Obviously, the first thing that needs to be done is to download and install the SDK. Rather than rewrite something, just follow the instructions here and come back when you’re done. Go ahead, I’ll wait. 🙂

OK, so you should be all set up and able to authenticate against the service. The first thing we’ll need is some way to log in to the game. We have to have an ID for the player in order to track things like leaderboards and achievements. We’ll need a scene to allow the user to enter the data for a user ID and password:

login screen

Create a new scene and place some UI controls similarly to the layout in the picture. Notice that I’ve put a GameObject in the scene for the GameSparks functionality as the instructions above stated. Once you have the scene create a Login class and add the following to it:

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using GameSparks.Api;
using GameSparks.Api.Messages;
using GameSparks.Api.Requests;
using GameSparks.Api.Responses;
using GameSparks.Core;

public class Login : MonoBehaviour {

    public InputField LoginIDText;
    public InputField EmailText;
    public InputField PasswordText;
    public Text LoginErrorText;

    public void CreateAccountButtonClick()
    {
        //ensure all items were entered
        if (LoginIDText.text.Length > 0 && EmailText.text.Length > 0 && PasswordText.text.Length > 0)
        {
            RegistrationResponse response = new RegistrationRequest().SetUserName(LoginIDText.text).SetDisplayName(LoginIDText.text).SetPassword(PasswordText.text).Send();

            if (!response.HasErrors)
            {
                Global.UserID = response.UserId;
                Application.LoadLevel("MainMenuScene");
            }
            else
                LoginErrorText.text = "All information must be entered";
        }
    }

    public void OKButtonClick()
    {
        //verify credentials
        if (LoginIDText.text.Length > 0 && PasswordText.text.Length > 0)
        {
            AuthenticationResponse response = new AuthenticationRequest().SetUserName(LoginIDText.text).SetPassword(PasswordText.text).Send();
            if (!response.HasErrors)
            {
                Global.UserID = response.UserId;
                Application.LoadLevel("MainMenuScene");
            }
            else
                LoginErrorText.text = "Incorrect credentials";
        }
    }

    public void ExitButtonClick()
    {
        Application.Quit();
    }
}

Note the references to the GameSparks namespaces added at the top. We’ll need these to send requests to the service. We have the members that we’ll link to our controls so we can retrieve the text in them and 3 methods for handling clicking on each button.

If the player is creating an account, we send the info to the GameSparks service using the RegistrationRequest object and assuming we get a valid response set a global (yes, I use globals. Don’t hate! :D) with the ID so we can use it later, then load the menu scene. If the player already has an account we use the AuthenticationRequest object and, if the credentials are correct, load the menu scene. Fairly straightforward stuff. Once you have the code entered, save and go back to Unity. Drag the class from the Project tab onto the Canvas in the Hierarchy and hook up all the events and drag the input fields onto the Login class members in the Inspector. If you’re not sure how to do this, head over to the Unity Tutorials site and learn the basics of working in the Unity IDE. If you’re really stuck let me know and I’ll try to walk you through the process. 🙂 The interesting stuff happens next.

Before we can use the Achievements and Leaderboards scenes we need to set up some achievements and leaderboards in GameSpark. In the GameSparks Configurator, click on Achievements, then the Add New “+” graphic:

gamesparksachievements

Add some info for the achievement:

achievementinfo

Click the Save button and you’re done with that part. You can add as many other achievements as you’d like of course.

Next we’ll need an event that’ll be called when an achievement is earned. Click on the Events item in the Configurator and create a new Event:

achievementevent

The ShortCode is the same as the Name here since the text area isn’t wide enough to show it. 🙁 We’ll send the Player’s ID and the shortcode for the achievement to a chunk of Cloud Code that’ll save the achievement. After you save the event, go ahead and click on the Cloud Code item. Under the Bindings | Events item, click on the PostPlayerAchievementEarned item and add the following code:

achievmentcloudcode

Save that and that’s it for the GameSparks side of things.

How do we tell GameSparks the player has earned an achievement? Good question, let’s implement that now. Create a new scene that we’ll use to fake earning an achievement (and adding to a leaderboard soon):

testscene

Add a script file call GameSparksTest and add the following to it:

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using GameSparks.Api;
using GameSparks.Api.Messages;
using GameSparks.Api.Requests;
using GameSparks.Api.Responses;
using GameSparks.Core;



public class GameSparksTest : MonoBehaviour {
    public Text ResponseText;
    public void TestAchievementButtonClick()
    {
        LogEventRequest request = new LogEventRequest(); ;
        request.SetEventKey("PostPlayerAchievementEarned");
        request.SetEventAttribute("PlayerID", Global.UserID);
        request.SetEventAttribute("AchievementID", "PlayedGame");

        ResponseText.text = "";

        request.Send((response) =>
        {
            if (!response.HasErrors)
                ResponseText.text = "Achievement Added";
            else
                ResponseText.text = response.Errors.JSON;
        });

        if (ResponseText.text == "")
            ResponseText.text = "Response not received";

    }

}

Fairly straightforward. Create the LogRequest object, set the name of the event to be called, set the parameters, and send it off.

Leaderboards work in a similar fashion. Head back to the GameSparks Configurator and set up an event:

leaderboardevent

Then set up a leaderboard:

leaderboard

That’s it for the GameSparks side of things. Go back to the GameSparksTest class in your Unity project and add a handler for the TestLeaderboardButton (making sure to hook it up to the button):

public void TestLeaderboardsButtonClick()
{
    LogEventRequest request = new LogEventRequest();
    request.SetEventKey("PostHighScoreEarned");
    request.SetEventAttribute("Score", 10);

    request.Send((response) =>
    {
        if (!response.HasErrors)
            ResponseText.text = "High Score Posted";
        else
            ResponseText.text = response.Errors.JSON;
    });

    if (ResponseText.text == "")
        ResponseText.text = "Response not received";
}

It doesn’t get much simpler than that I think. You can use the Test Harness section of the GameSparks site to pull the data for the achievements and leaderboards for the player to verify that the data was saved. Using the Test Harness is pretty straightforward. You’ll have to make an authentication call, then use the List AchievementRequest under the Player section and the ListLeaderboardsRequest under the Leaderboards section, filling in the necessary information in the JSON.

I’ve put the Unity project for this post here. You’ll have to fill in your own GameSparks credentials of course. Feel free to ask any questions about what we’ve gone over.

Next post I’ll add functionality to pull and display the achievements for a player and display the leaderboard information. Take a stab at figuring it out beforehand if you’d like.

Speech Bubble Sample

So I allowed myself to get sidetracked to throw together a sample to answer a question in the App Hub forums. Since I love RPG games, both playing and developing, this will probably be useful to me sometime in the future so that’s how I’m justifying it.

The sample, which you can get from here, shows a quick semi-dynamic speech bubble with a couple of options. The class is less than 150 lines, allows however many lines of text you want, shows a graphic to let the user know if there’s more text that the character has to provide, and shows a pointer to the character that can be placed on either the left or right side of the bottom of the bubble. It uses more of my fantastic programmer art, but should work with whatever graphics you replace it with, assuming you don’t deviate from how I’ve set up the graphics. I could have used just one corner graphic and rotated for the other 3 sides, but I was lazy. Smile

Here’s the class in all its glory:

public enum PointerType
{
    None,
    Left,
    Right
}

public class SpeechBubble
{
    private int _width;

    private Vector2 _location;

    private string[] _text;

    private SpriteFont _font;

    //bubble textures
    private Texture2D _bottomBorder;
    private Texture2D _interior;
    private Texture2D _leftBorder;
    private Texture2D _leftBottomCorner;
    private Texture2D _leftTopCorner;
    private Texture2D _rightBorder;
    private Texture2D _rightBottomCorner;
    private Texture2D _rightTopCorner;
    private Texture2D _topBorder;

    private bool _more;
    private Texture2D _moreGraphic;

    private PointerType _pointerType;
    private Texture2D _pointer;

    public SpeechBubble(ContentManager content, int width, Vector2 location, string[] text, bool more = false, PointerType pointerType = PointerType.None)
    {
        _font = content.Load<SpriteFont>("font");

        _bottomBorder = content.Load<Texture2D>("bottomBorder");
        _interior = content.Load<Texture2D>("interior");
        _leftBorder = content.Load<Texture2D>("leftBorder");
        _leftBottomCorner = content.Load<Texture2D>("leftBottomCorner");
        _leftTopCorner = content.Load<Texture2D>("leftTopCorner");
        _rightBorder = content.Load<Texture2D>("rightBorder");
        _rightBottomCorner = content.Load<Texture2D>("rightBottomCorner");
        _rightTopCorner = content.Load<Texture2D>("rightTopCorner");
        _topBorder = content.Load<Texture2D>("topBorder");

        _moreGraphic = content.Load<Texture2D>("more");

        _pointer = content.Load<Texture2D>("pointer");

        _location = location;
        _width = width;

        _text = text;

        _more = more;

        _pointerType = pointerType;
    }

    public void Draw(SpriteBatch sb)
    {
        //top
        sb.Draw(_leftTopCorner, _location, Color.White);
        sb.Draw(_topBorder, new Rectangle((int)_location.X + _leftTopCorner.Width, (int)_location.Y, _width - _leftTopCorner.Width * 2, _leftTopCorner.Height), Color.White);
        sb.Draw(_rightTopCorner, _location + new Vector2(_width - _rightTopCorner.Width, 0), Color.White);

        //lines
        for (int i = 0; i < _text.Length + (_more ? 1 : 0); i++)
        {
            sb.Draw(_leftBorder, new Vector2(_location.X, _location.Y + _leftTopCorner.Height + (i * _leftBorder.Height)), Color.White);
            sb.Draw(_interior, new Rectangle((int)_location.X + _leftBorder.Width,
                                    (int)_location.Y + _leftTopCorner.Height + (i * _leftBorder.Height),
                                    _width - _leftBorder.Width * 2,
                                    _leftBorder.Height),
                    Color.White);
            sb.Draw(_rightBorder, new Vector2(_location.X + _width - _rightBorder.Width, _location.Y + _leftTopCorner.Height + (i * _leftBorder.Height)), Color.White);

            //leave space for more graphic if necessary
            if (i < _text.Length)
                sb.DrawString(_font, _text[i], new Vector2((int)_location.X + _leftBorder.Width, (int)_location.Y + _leftTopCorner.Height + (i * _leftBorder.Height)), Color.Black);
        }


        //bottom
        sb.Draw(_leftBottomCorner, _location + new Vector2(0, _leftTopCorner.Height + (_text.Length + (_more ? 1 : 0)) * _leftBorder.Height), Color.White);

        switch(_pointerType)
        {
            case PointerType.Left:
            {
                sb.Draw(_pointer, _location + new Vector2(_leftBottomCorner.Width, _leftTopCorner.Height + (_text.Length + (_more ? 1 : 0)) * _leftBorder.Height), Color.White);

                sb.Draw(_bottomBorder, new Rectangle((int)_location.X + _leftBorder.Width + _pointer.Width, 
                                                        (int)_location.Y + _leftTopCorner.Height + (_text.Length + (_more ? 1 : 0)) * _leftBorder.Height, 
                                                        _width - _leftBorder.Width - _pointer.Width - _rightBorder.Width, 
                                                        _bottomBorder.Height), 
                                        Color.White);

                break;
            }
            case PointerType.Right:
            {
                sb.Draw(_bottomBorder, new Rectangle((int)_location.X + _leftBorder.Width,
                                                    (int)_location.Y + _leftTopCorner.Height + (_text.Length + (_more ? 1 : 0)) * _leftBorder.Height, 
                                                    _width - _leftBorder.Width - _pointer.Width - _rightBorder.Width, 
                                                    _bottomBorder.Height), 
                                        Color.White);

                sb.Draw(_pointer, _location + new Vector2(_width - _pointer.Width - _rightBorder.Width, _leftTopCorner.Height + (_text.Length + (_more ? 1 : 0)) * _leftBorder.Height), Color.White);

                break;
            }
            case PointerType.None:
            {
                sb.Draw(_bottomBorder, new Rectangle((int)_location.X + _leftBorder.Width,
                                                    (int)_location.Y + _leftTopCorner.Height + (_text.Length + (_more ? 1 : 0)) * _leftBorder.Height,
                                                    _width - _leftBorder.Width - _rightBorder.Width, 
                                                    _bottomBorder.Height), 
                                        Color.White);

                break;
            }
        }

        sb.Draw(_rightBottomCorner, _location +  new Vector2(_width - _rightBottomCorner.Width, _leftTopCorner.Height + (_text.Length + (_more ? 1 : 0)) * _leftBorder.Height), Color.White);

        if (_more)
            sb.Draw(_moreGraphic, new Vector2(_location.X + _width - _rightBorder.Width - _moreGraphic.Width, _location.Y + _leftTopCorner.Height + (_text.Length * _leftBorder.Height)), Color.White);
    }
}

 

Creating an instance is fairly straightforward:

SpeechBubble _bubble1;
SpeechBubble _bubble2;
SpeechBubble _bubble3;

protected override void LoadContent()
{
      ...

      _bubble1 = new SpeechBubble(Content, 150, new Vector2(50, 50), new string[] { "This is a test", "This is only a test", "More text follows..." }, true);
      _bubble2 = new SpeechBubble(Content, 150, new Vector2(250, 250), new string[] { "This is a test", "This is only a test", "Nothing here" },false, PointerType.Left);
      _bubble3 = new SpeechBubble(Content, 150, new Vector2(150, 450), new string[] { "Another test", "Still testing", "More follows..." }, true, PointerType.Right);
}

Simply call the Draw method of the class in your own draw code:

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);

    spriteBatch.Begin();
    _bubble1.Draw(spriteBatch);
    _bubble2.Draw(spriteBatch);
    _bubble3.Draw(spriteBatch);
    spriteBatch.End();

    base.Draw(gameTime);
}

Feel free to let me know if I missed something that would be useful in the class.

Adding Triggers to the Platformer Starter Kit

I’ve seen a lot of posts where people are using the Platformer Starter Kit and as I was reading one, I thought having the ability to have triggers in a level might be something someone would find useful.

Triggers give the level designer the ability to make levels more challenging and diverse, instead of simply having the player run around collecting gems and power-ups in a wide open level. The added dimension of a goal that may have to be reached before exiting the level makes the game more challenging, which leads to the game possibly being more interesting to play.

The first use that I thought of for a trigger was to remove a block( s ) that impedes reaching the exit. Should be simple enough, right? Let’s find out.

I started out by figuring that two kinds of triggers might be good to have – visible and invisible. Even though triggers aren’t tiles, the level designer needs to see them in the level data, which means the code that reads the text files that hold the level data have to account for them. “T” and “I” are what I selected for the two trigger types.

In the Level.cs file’s LoadTile method, add the following to the end of the switch statement before the “default” case:

// visible trigger
case 'T':
   return new Tile(null, TileCollision.Passable);

//invisible trigger
case 'I':
   return new Tile(null, TileCollision.Passable);

 

For the visible trigger, the player will have to have something to see, which means we’ll need a sprite. Behold, the awesome programmer art!

trigger

I placed the file in the Sprites folder in the Content project.

The sprite doesn’t look exactly like this (at least not as I’m typing this). You can open the file in the attached source linked to at the bottom of this post to see it. The important thing is to ensure that whatever you use is sized correctly (I just opened the gem.png file and had at it) and has a transparent background.

The sprite needs an object to load it into and we’ll need something to hold one or more triggers in the level, so add the following at the top of the Level.cs file’s Level class:

private List<Trigger> triggers = new List<Trigger>();
private Texture2D triggerTexture;

 

The Trigger class looks like this:

public enum TileTriggerType
{
    None,
    MoveTile,
    RemoveTile
}

public class Trigger
{
    public Vector2 Location;
    public bool IsTriggerVisible = true;
    public Vector2 TriggerTarget = Vector2.Zero;
    public TileTriggerType TriggerType = TileTriggerType.None;
    public bool IsActive = true;
}

Since the text file that holds the level data is fairly set in stone as far as it’s content, we’ll use separate files to hold the data for a trigger. I decided on something fairly straightforward as far as format:

69,13|2|70,0|1

The pipe character separates the pieces of data. The first piece is the location of the trigger, the second the type of trigger, the third, the location of the tile that the trigger acts upon, the last whether or not the trigger is visible.

The following code reads the trigger file for the current level:

//load level triggers data
try 
{            
    using (Stream stream = TitleContainer.OpenStream("Content/triggers/" + levelIndex.ToString() + ".txt"))
    {
        using (StreamReader sr = new StreamReader(stream))
        {
            string line = sr.ReadLine();

            while (line != null)
            {
                Trigger trigger = new Trigger();

                string[] data = line.Split('|');

                Vector2 vec = new Vector2(Convert.ToInt16(data[0].Substring(0, data[0].IndexOf(','))), Convert.ToInt16(data[0].Substring(data[0].IndexOf(',') + 1)));
                trigger.Location = vec;
                trigger.TriggerType = (TileTriggerType)Convert.ToInt16(data[1]);
                vec = new Vector2(Convert.ToInt16(data[2].Substring(0, data[2].IndexOf(','))), Convert.ToInt16(data[2].Substring(data[2].IndexOf(',') + 1)));
                trigger.TriggerTarget = vec;
                trigger.IsTriggerVisible = data[3] == "1" ? true : false;

                triggers.Add(trigger);

                line = sr.ReadLine();
            }
        }
    }
}
catch (Exception ex)
{
    //do nothing
        
}

triggerTexture = Content.Load<Texture2D>("Sprites/trigger");

 

The last piece of the puzzle is to handle the player hitting the trigger. Add the following to the Update method of the Level class after the UpdateEnemies(gameTime) line:

//check for hitting trigger
if (Player.IsAlive && Player.IsOnGround)
{
    foreach(Trigger trigger in triggers)
    {
        if(trigger.IsActive)
        {
            //offset to center of trigger texture
            if (Player.BoundingRectangle.Contains((int)trigger.Location.X * Tile.Width + Tile.Width / 2, (int)trigger.Location.Y * Tile.Height + Tile.Height / 2))
            {
                trigger.IsActive = false;
                switch (trigger.TriggerType)
                {
                    case TileTriggerType.MoveTile:
                    {


                        break;
                    }
                    case TileTriggerType.RemoveTile:
                    {
                        tiles[(int)trigger.TriggerTarget.X, (int)trigger.TriggerTarget.Y] = new Tile(null, TileCollision.Passable);

                        break;
                    }
                }
            }
        }
    }
}

 

I’ve only handled the RemoveTile trigger type. I’m leaving the MoveTile as an exercise for the reader (that’s my story and I’m sticking to it!).

I modified the first level to add a wall blocking the exit and set the trigger near it so the player can see the tile being removed:

......................................................................#.......
......................................................................#.......
...........................G..........................................#.......
..........................###.........................................#.......
......................G...............................................#....X..
.....................###................G.GDG.............G.G.G...############
.................G....................#########..........#######..............
................###...........................................................
............P...................G.G...............G.G.........................
...........###.................#####.............#####........................
.......G......................................................................
......###...............................GDG.G.............G.G.G.G.G.G.G.G.G.G.
......................................#########.........##.G.G.G.G.G.G.G.G.G..
.1........................................................GCG.G.GTGCG.G.G.GCG.
####################......................................####################

Save everything and give it a try. Assuming I didn’t miss anything you should be able to hit the trigger and see the top block in the wall disappear, enabling you to reach the exit. If I did miss something please let me know. In that case you can download the source from here and give it a try.

Other possible triggers that I could see being useful:

  • Moving blocks to enable the player to reach areas not accessible otherwise
  • Moving blocks to crush enemies (this would be a pretty big modification)
  • Moving blocks to prevent enemies from reaching and area and allowing the player to safely move through that area
  • Moving blocks to allow access to power-ups
    One thing that currently isn’t handled by this code is the ability to have a trigger modify multiple tiles. This isn’t a big enhancement. Simply change the TriggerTarget member of the Trigger class to a List<Vector2>, change the code to read the trigger file to handle one or more pieces of data (possibly by making the target data the last piece of data read) and changing the code in the Level class Update method to loop through this List.
    There are a lot of areas of level design that are opened up by a pretty simple modification to the game code. I’d love to see what people can come up with.
    If anyone can think of other small enhancements to the kit, let me know.

Modifying Platformer Starter Kit–Adding Health

So there was another post on the AppHub forums where someone was asking about how to add health to the character in the Platformer Starter Kit rather than having him die immediately when touching an enemy. I’d cobbled together a bunch of mods that had previously been done and thought this might make another good one.

Implementing this turned out to be pretty simple. Just two files need updating, Level.cs and Player.cs and I added two sprites – a health bar and a 1×1 pixel texture to fill in the healthbar.

healthbar

health

(magnified may times Open-mouthed smile)

 

First, the changes to the Player.cs file. At the top where all the members are declared add:

private int health = 100;
//private int lives = 3;
private int lives = 1;
private bool touchingEnemy;

public bool TouchingEnemy
{
    get { return touchingEnemy; }
    set { touchingEnemy = value; }
}

public int Health
{
    get { return health; }
}

 

Notice, the old “lives” member has been commented out and replaced. The player now only has one life, but can be injured a number of times before dying (which we’ll see in a second).

Previously, when the player was touched by an enemy the OnKilled method was called. We need to add a new method (I put it right under the OnKilled method):

public void OnInjured()
{
    touchingEnemy = true;

    health -= 10;

    if (health == 0)
    {
        killedSound.Play();
        sprite.PlayAnimation(dieAnimation);
        isAlive = false;
    }
}

 

Now we’ll add the textures necessary to render the health bar and the members of the level class to load the textures into.

You can either save the textures above by right-clicking on them, create your own, or grab them out of the zip file attached to this post. Drop them into the “sprites” folder in the content project (or wherever you want, but you’ll have to change the relevant code that loads them).

Add the following code to the Level.cs file’s section where the other members are declared:

private Texture2D healthBar;
private Vector2 healthBarLoc;
private Texture2D healthTexture;

 

Then add the following to the Level.cs constructor:

//load healthbar textures
healthBar = Content.Load<Texture2D>("sprites/healthbar");
healthTexture = Content.Load<Texture2D>("sprites/health");
healthBarLoc = new Vector2(Width * Tile.Width - 205, 5);

 

The Width member of the Level class returns the width of the screen measure in number of tiles, so we have to do a little multiplication and offset from the edges of the screen a bit.

Now add the matching method to injure the player that we had in the Player class (again, I added this right below the OnKilled method):

private void OnPlayerInjured()
{
    Player.OnInjured();
}

 

And we’ll call it in the UpdateEnemies method:

private void UpdateEnemies(GameTime gameTime)
{
    foreach (Enemy enemy in enemies)
    {
        enemy.Update(gameTime);

        // Touching an enemy now just injures the player
        if (enemy.BoundingRectangle.Intersects(Player.BoundingRectangle))
        {
            if (!player.TouchingEnemy)
                //OnPlayerKilled(enemy);
                OnPlayerInjured();
        }
        else
            UpdatePlayerCollision(false);
    }
}

private void UpdatePlayerCollision(bool touchingEnemy)
{
    player.TouchingEnemy = touchingEnemy;
}

 

That should be it. Save everything and run the game. You should see the health bar change every time you touch an enemy. You’ll have to advance to the second level to see one. After ten touches the character should die and you’ll be presented with a dialog. I haven’t changed the dialog or relevant code to restart the game, nor did I remove the sprite rendering the number of lives the player has. I wanted to keep the latter in so that the option to add a life power-up can be done without breaking the game. The former is something for you to implement. Smile

Hopefully someone will find this useful.

 

Source code can be downloaded from here.