Archive for the ‘XNA’ Category.

The Beginning of the End

So yesterday Microsoft announced it’s turning off XBLIG. It’s been a great 9 year run, but for those of us that have been keeping track, we’ve been expecting it for a while. Andy posted his thoughts at the end of the blog post, and people like Indie Gamer Chick have commented on it already, so I thought I should add my $.02 on the topic, seeing as how I was one of the guys there at the beginning.

After the announcement at GDC and seeing some of the videos MS released that supposedly showed what XNA was capable of, those of us who wanted to do game development for a living but not belong to a AAA studio got very excited. While what we ended up with in XNA Game Studio allowed us to create almost any game we wanted (except for the inevitable dev that wanted to make an MMO as their first game!) it wasn’t exactly AAA quality. A lot of the devs weren’t exactly AAA developers either and we ended up with a lot of junk released onto XBLIG that gave it a very negative reputation in the industry. There were a lot of great games there but very few gamers wanted to sift through the junk.

Microsoft announcing the first Dream Build Play was another first in the industry. Anyone had the chance to submit a game and win cash and a chance to get their game on XBLA, which meant major exposure. I think the several competitions were a relative success and made many developers’ dream of moving to full-time game development become reality.

While sales data that devs released were all over the place, many games brought in a ton of money for indie standards. Considering costs for indie games aren’t quite up to the 7 figure range that AAA titles hit, many devs seemed to be making out pretty well. I’m not quite sure why, but this didn’t seem to be enough for Microsoft. Support for XBLIG tapered off, both in updates to XNA GS and responses to issues that came up in the community. The XNA team dwindled to nothing and problems with payments and the release pipeline stacked up. For years we couldn’t get a solid answer to what was to become of the XBLIG community.  The writing seemed to be on the wall – XBLIG was dead. Many devs moved on to other technologies and platforms.

Personally, XNA has led to me becoming a twice-published author and tech book editor, Microsoft MVP, and conference speaker. I couldn’t have anticipated what has happened in the past 9 years. While I didn’t become one of the big XNA success stories, I’ve definitely benefited greatly from the existence of XNA and the XBLIG community. I’ve met a lot of great people, got to visit the Microsoft campus several times, and helped out the community in some small way I think.

Although XNA is dead, the community will move on and hopefully continue to create great games. I’ll be there, doing what I can and try to get my games out there for people to enjoy. Who knows, I may eventually become the next great indie game dev. 🙂

World Screen Screenshot

Well, it’s getting there:

WorldScreen

New Dungeon Editor Screenshot

Some progress:

  • Added 2nd layer for regular and secret doors
  • Triggers (X in screenshot) and traps (square in screenshot) now rendering
  • Load and save working, using sharpSerializer since normalize XmlSerializer doesn’t like arrays, among other things.

 

DungeonEditorScreenshot2

Dungeon Editor, 1st Iteration

Behold my awesome graphical skills! 🙂 It’s got a long way to go, but it’ll do for now:

 

DungeonEditor

Picking up Microsoft’s slack

Seems like Quality Control is something MS (at least the Windows Phone/XNA/XBLIG/whoever is left these days) isn’t interested in. They broke a huge amount of content in the split between Windows Phone and XBLIG in the old App Hub site. One of the pieces of content was a Best Practices page for doing XNA games. While it’s still findable using the Way Back Machine, all the links in it are broken, so I figured I’d recreate it here and also use it as an excuse to update this blog which I’ve slacked on.

 

Best Practices for Indie Games 3.1

 

If you are interested in creating an Xbox LIVE Indie Game, use this guide to understand the expectations of the millions of gamers who will be downloading and playing your game, and the actions you can take to make your game look, feel, and play its very best.

This Guide references features in XNA Game Studio 3.1. A guide for XNA Game Studio 4.0 is being developed, but is not yet available.

A few things to remember:

  • The sections in this guide are ordered by priority, but everything in it is important! Please be sure to read it all and verify that your game take these issues into account.
  • While these best practices are suggestions and not requirements, in many cases your game will be unplayable if you do not follow them. While they are not necessary to pass peer review, and reviewers should not use these guidelines to pass or reject games, they may reject games that are unplayable!
  • Use the playtest support available in the submission process on the App Hub website to have other developers help validate and catch issues with your games, before millions of gamers do.


Gamers Expect Games to “Just Work” on Any TV


Background: Xbox 360 consoles can be plugged into TV sets of all types, with many different resolutions, aspect ratios, and more. Overscan (edges of the TV screen which don’t draw the entire viewport), stretching of your game’s visuals, and even crashes can occur if resolutions are not handled properly.


Actions:

  • Set your game’s resolution to 1280 x 720 (720p native resolution) to work on all TVs. This gives you a single resolution target for all of your art content.
  • Draw the entire scene to your Viewport size, but draw your critical gameplay features (HUD, main character, and so on) to the region inside
  • Viewport.TitleSafeArea.
  • If using text, use a 14-point or higher font to insure the font can be read on standard-definition TVs. Draw the SpriteFonts at full size.

For Reference, See:


Gamers Expect a Good Trial Mode


Background: Many gamers want to try before they buy, and the Xbox LIVE Indie Games system allows players to try a timed trial of your game before they buy. Currently, the trial mode time is set at eight minutes. When the time runs out, the player is shown a screen where he or she can either purchase the full game, or exit. The only additional restriction in Trial Mode is that Xbox LIVE matchmaking is disabled. During the trial, you have the opportunity to show off the very best your game has to offer.


Actions:

  • Check whether the game is in Trial Mode by checking if the Guide.IsTrialMode property is true. This will always start out as true, and can change shortly after game startup, or as part of a new player signing in, or in response to an in-game purchase. Be sure to check this once every frame, as opposed to just once at startup.
  • Consider ways to give the player the best experience in the limited amount of time available. This may mean you want avoid title screens or exposition on the story and get right to the gameplay as soon as possible. The time limit for trial mode time is currently set to eight minutes, although this may be changed in the future.
  • When the purchase screen opens, Xbox LIVE Indie Games will set Game.IsActive to false. If the player chooses to buy the game and a successful purchase is made, the game will resume and Guide.IsTrialMode will be set to false. You can manually activate the purchase screen before the time limit expires if you wish, by calling Guide.ShowMarketplace. Your game must be able to handle the IsTrialMode changing without re-starting. This may mean that you need to reload or re-initialize the menus, gameplay or other aspects of the game “on-the-fly” when the flag changes.
  • Consider displaying a scene as the gamer exits the game that gives the gamer the option of being taken to Marketplace to purchase the game using the Guide.ShowMarketplace API, if Guide.IsTrialMode property is true.
  • If your game has Xbox LIVE multiplayer support, disable it at the menu if the game is in Trial Mode – if the player selects the option, you can call Guide.ShowMarketplace. Remember to re-enable it if the Guide.IsTrialMode flag changes to false during the game!
  • Test out trial mode by setting Guide.SimulateTrialMode to true, or by using the “Play Trial Game” option to launch your game from the Xbox Dashboard. This forces the Guide.IsTrialMode flag to true. You can also test that the game handles the transition from Trial mode to being purchased by changing the Guide.SimulateTrialMode to false during game play using a button on the controller that game doesn’t ordinarily use. This way, you know that the game will work properly when they purchase the game without re-starting.

For Reference, See:


Gamers Expect a Friendly Menu and Control System


Background: Games are essentially a cycle – from the Main Menu (and alternate menus such as Options and Help), gamers expect to enter a game session, play, and return to the main menu when they are done. Any deviation from this should be intuitive and easy to remember.

For menu navigation, gamers expect to use both the stick and the D-Pad, with A being the “accept” or “go forward” button, and B and Back being the “cancel” or “go back” buttons.

Both successful and unsuccessful menu operations should be reported in some way to the user, in both graphical and audio form. If there are errors, they should be provided to the user in simple, prescriptive language that tells the player what to do.


Actions:

  • Use a Menu system, such as in the Game State Management Sample.
  • Use the stick and D-pad to move the menu selector, and use A and B/Back to choose menu items or cancel, respectively. When using the stick for menu movement, poll with a delay to keep the menu selector from moving too fast.
  • The active menu item should stand out clearly from other menu options – use patterns, animations, or other graphical items to account for color blind players.
  • Make sure confirmations and errors are clearly shown to the player, with guidance about what the player should do next.
  • Consider changing the Back button on the controller which by default exits the game with no warning. Provide either a confirmation dialog to insure that the player really wanted to leave the game, or provide an exit menu option from the main menu and remove the Back button exit functionality entirely.
  • The game should bring up a pause menu if the active controller becomes disconnected, or if the player presses the Start button.
  • In networked games, gameplay should continue behind the pause menu even though the local player is no longer engaged in the action.
  • Make sure the game respects Game.IsActive and pauses as appropriate when this is set to false, such as when the guide is being displayed over the game.
  • Games should not stop or pause without game-representative, on-screen graphics. When the pause time is greater than five seconds, the game must indicate the reason and provide on-screen feedback/explanation (aka Loading Screen)

For Reference, See:


Gamers Use One Xbox 360 Controller


Background: This may seem obvious, but it is important. Not only do gamers expect to use an Xbox 360 Controller (not a plugged-in keyboard or Chatpad), but in a single-player game, they are going to expect whichever controller they pick up to be the one the game recognizes throughout their gaming session.


Actions:

  • Be aware that the player may not be using the controller that is assigned to PlayerIndex.One. To detect which controller the player is using, create a splash screen or initial menu which prompts to the player to press A or Start. Detect which PlayerIndex pressed the button to start the game and use that as the active controller. Here is an example of code to do this:

PlayerIndex controllingPlayer = PlayerIndex.One;
for (PlayerIndex index = PlayerIndex.One; index <= PlayerIndex.Four; index++)
{
if (GamePad.GetState(index).Buttons.Start == ButtonState.Pressed)
{
controllingPlayer = index;
break;
}
}

  • If your game requires a signed in player profile, you can then look up the profile details for the selected PlayerIndex, or if no profile is currently signed in at that index, call Guide.ShowSignIn to let the player select their profile:

SignedInGamer gamer = Gamer.SignedInGamers[controllingPlayer];
if (gamer != null)
{
playerName = gamer.Gamertag;
}
else
{
Guide.ShowSignIn(1, false);
}

  • Do not require the Keyboard for input.
  • Games should launch successfully and get to their menu and/or attract mode even if a controller is not active (For example: game is launched with an IR remote).
  • Use Guide.ShowSignIn to support the signing-in of multiple players to a local multiplayer game.

For Reference, See:


Be Kind With Audio


Background: Audio, at first glance, looks like a very easy technology to get right. Drop in sounds, and play them when asked. However, audio systems are as diverse as TVs. Volume, position, and frequency ranges may be different across the different systems.

Be sure you’re not playing sounds with wildly different volumes that force players to turn their TV’s volume up or down.

In addition, some gamers don’t like music in their games and will want to turn it off – or replace it with their own through the Xbox 360 Guide.


Actions:

  • Be sure to test your game audio on as many audio configurations as possible (stereo, mono, 5.1, and headphones).
  • Identify and eliminate major volume changes between sounds that would force the player to change audio volume while playing your game. Do this by leveling your sounds to a standard sound. The Xbox 360 startup sound is a good reference. Adjust your speaker volume so that the Xbox 360 startup sound is loud, but not overly so, and adjust your game sounds to match that level.
  • If you have music in your game, you should tag it as such. If you are using XACT, drag the music sounds into the Music category in the tree. If you use the Sound Effects API, then be sure to load your background music files as Song objects, and play them with MediaPlayer.Play. These methods ensure that the player can replace the music through the Xbox 360 Guide.
  • Here’s a tip to lower your game binary size if you are using XACT: enable XMA compression on your wave banks. Add compression in your XACT project in the “Compression Presets” node, and choose the XMA format. Edit the properties of your wavebanks, setting their “Compression Preset” box to the new preset.
  • Be aware that MediaPlayer.Play on the Xbox 360 is asynchronous which means that the song doesn’t immediately start playing. Indeed, you can check the next frame and the song almost certainly won’t have started playing by then and theMediaPlayer.State will probably still be MediaState.Stopped! If you want to start a new song when the old finishes and you check every frame for the current song to be done, each frame you will change songs if you aren’t careful. Instead, add an eventhandler to the MediaPlayer.ActiveSongChanged event and when this event fires, you can begin checking for the current song to be done using MediaPlayer.State == MediaState.Stopped.
  • The content processor for the XNA Framework sound effect API defaults to high compression. Consider how compression affects the overall size and load times of your game, versus the quality of the audio.


Gamer Profiles Matter


Background: Gamers spend a lot of time and effort on their Xbox 360 Profiles. Even if they are not connected to Xbox LIVE, profiles are used in-game to identify which player is which, to store saved games, to identify a gamer in a high score list, and, in some cases, store game defaults such as controller sensitivity. Use these profiles to identify with your gamer, and do things that personalize the experience.


Actions:

  • The key to profiles is the GamerServicesComponent. Add it to your Game component list. Then you can track profiles in your game.
  • When you start your game, check Gamer.SignedInGamers to check your active controller for a profile. You can choose to ask the gamer to sign in if a profile is not returned, or you can allow the gamer to play anonymously. Unfortunately, anonymous play prevents you opening a storage container for saving game progress, or looking up the player name (eg. for a high score chart) without them having to enter their name manually.
  • Use the gamer’s name. Check SignedInGamer.Gamertag and use it in your game, such as in a status bar or in high score charts.
  • The SignedInGamer class holds a variety of information about your player, including a GameDefaults property that may be useful in automatically tuning your game’s control scheme or difficulty. The GameDefaults.InvertYAxis property is of special interest to 3D games.
  • There’s even more data inside SignedInGamer.GetProfile, such as their gamer picture. You can use this at your discretion.
  • Respond to SignedInGamer.SignedOut on the active controller PlayerIndex. If the player signs out, you can decide what to do. Most games return to the main menu.

For Reference, See:


Represent Your Game


Background: After doing all the hard work to create your game, make sure you take the steps to represent your game in Xbox LIVE Marketplace. Also, you will want to know when people are playing it! Thumbnails, Box Art, Screenshots, Videos, and Rich Presence are key components to making sure your game shows well on Xbox 360.


Actions:

  • Have a thumbnail. When you submit your game, you will be able to submit a 64×64 jpg file that is your game icon.
  • When submitting your game to the Peer Review process, there are more ways to represent your game – be sure to give your very best! Four screenshots, a short video trailer, and box art. Take the time to create these assets. Your game’s assets are the means by which the Xbox LIVE Marketplace displays your game!
  • Rich Presence is a way for your game to display on Xbox LIVE while it’s being played. For each SignedInGamer playing your game, set Presence.PresenceMode and, if needed, PresenceValue, based on what a gamer is doing in your game at any particular moment. Xbox LIVE Indie Games adds your game title automatically, so everyone knows people are having fun in your game.


Make Loading and Saving Seamless


Background: From the time the game starts to the time the gamer quits the game, there should be good interaction between players and the game – or at the very least, there should be something for gamers to look at.

Most games do a great deal of loading at start-up, so be sure to show something interesting during that time. When saving games, do not pause or freeze the gameplay – keep the action moving and the disk operations in the background.

Players might use Memory Units (MUs) and expect to save their game there. You must use the Storage APIs asynchronously to avoid locking up in the case that your users are using a Memory Unit. Be sure to test your game both with and without an MU connected.


Actions:

  • Never start your game with a device selector screen. First, this is tacky and makes a poor first impression; second by requiring a player to hit start first you will know the active controller and can handle follow-up message boxes properly.
  • Use an animated loading screen when loading content in your game. This keeps the player from thinking the game has crashed or locked up.
  • Test that your game works when more than one memory device is present, such as having a hard drive AND a memory unit, or two memory units.
  • The first time a player’s game data must be saved or loaded, use Guide.BeginShowStorageDeviceSelector asynchronously to prevent locking up.
  • Do not ask for a storage device again after the first time – this may annoy the player.
  • If you are going to auto-save, notify the player of the auto-save, and tell them not to turn off their Xbox 360 Console while it is saving.
  • Make sure you associate saved games to a profile, but do not associate high scores to a profile. That way, everyone can see the high scores.
  • Saving and loading data in text formats can be problematic depending on the region the Xbox 360 is set to. If you convert a floating point number to a string during a save when the Xbox 360 is set to Germany, for example, you will get 1,234 when you might expect 1.234! To insure that that numeric conversions to and from strings generate the same values in all regions, be sure to store the numbers in a culturally invariant manner, or use a binary file format instead of text.

For Reference, See:


Xbox LIVE Multiplayer: Make it Full Featured


Background: Multiplayer is a valuable feature to have in your game. It increases replay value and connects gamers all over the world. However, it can be frustrating for customers if the experience is not optimized and made user-friendly. If you are building multiplayer into your game, be sure to go the extra mile and provide the features that make Xbox LIVE gameplay great.


Actions:

  • Take full advantage of the Xbox LIVE Party system to get likeminded players together in your game. Use the SignedInGamer.PartySize to determine if you should inform the player that with a simple button press they can open up the Party UI and quickly send out game invites! A size of zero means you aren’t in a party, one means you are all alone, and anything greater means you are ready to invite some friends. A simple call to Guide.ShowParty() will open the guide directly to the Party screen to streamline the invites to your fellow party members.
  • Support invites, it improves game visibility and ease of joining others for gameplay. To do so, respond to the NetworkSession.InviteAccepted event, quitting any session that the player is in, and joining the new session by callingNetworkSession.JoinInvited. Players can then send each other invites – the invited player will be given the option to purchase the game if necessary – and can join each other’s games in progress.
  • Have a Quick Match feature – don’t make your player hunt for games. Run the matching process in the background and join up with the best session available. Make quick match the default, to improve the odds of gamers finding other players via the Xbox LIVE service.
  • Have a Custom Match feature with a few parameters such as level and rule sets. Limit the number of sessions returned to make it easy for the player to pick.
  • If you have a pre-game lobby, ensure that you are displaying the match settings so that players entering the lobby know what they are getting into.
  • Optimize, optimize, optimize! The XNA Framework can simulate latency and packet loss – you should make sure to turn on both when testing. You should target 8Kb of bandwidth for your network game has this will cover 90% of Xbox 360s. You should also make sure your game can continue playing well with 500 ms of latency. For more details, see the link to Shawn’s talk below!
  • Hosting and/or joining a Multiplayer Game through Xbox LIVE is not available on Xbox LIVE Silver and some Xbox LIVE child accounts, even when the game is purchased. You can determine if a particular Gamer is allowed to participate in an Xbox LIVE session by checking that the SignedInGamer class’s Privileges.AllowOnlineSessions flag is set to true. For example:

if (Gamer.SignedInGamers[activeController].Privileges.AllowOnlineSessions) …

  • Be sure to use the rich presence to let other gamers know that your game is being played. You can also specify where a particular player is in the game using the rich presence strings. Rich presence will also require the SignedInGamer’s PlayerIndex which is another reason why you need to poll for it at startup and save it during game play. For example:

Gamer.SignedInGamers[activeController].Presence.PresenceMode = GamerPresenceMode.AtMenu;


For Reference, See:


Gamers May Have Special Controllers


Background: Many different types of controllers are supported by the XNA Framework. These controllers, which range from flight sticks to dance pads, all return values inside the GamePadState class. Gamers who have these controllers expect them to work in the types of games for which they are traditionally used.

Regardless of the special types used in your game, you should always support the default Xbox 360 Gamepad, and never require special controllers to be able to play.


Actions:

  • Determine what kind of controllers your game would best be suited for: racing games should support the wheel, flying games should support flight sticks, and so on.
  • When the player starts the game and you know which controller they are using, call GamePad.GetCapabilities and check the GamePadCapabilities.GamePadType returned value to understand what type of controller the player is using.
  • Respond to a special controller type by changing your control scheme to best fit that type. The trigger on an Xbox 360 Controller may not be best suited to a flight stick, for instance. Use the Input Reporter (linked below) to test special controller types to understand their inputs.

For Reference, See:


Avatars


Background:Starting with XNA Game Studio 3.1, developers have the opportunity to use Avatars when making their Xbox LIVE Indie Game.


Actions:

  • Users may not have an Avatar so your game should handle this situation. After retrieving the AvatarDescription via the Avatar property on the SignedInGamer the developer should check the IsValid property. If the description is not valid then the player doesn’t have a valid avatar. The developer can replace the users avatar with a random avatar using the AvatarDesctipion.CreateRandom method or load a pre created AvatarDescription.
  • If avatars are used in your networked game users should be able to see the real avatars of the other player. When creating a networked game that uses avatars the developer should send each players AvatarDescription across the network so that each player can display all of the other players avatars. A player should see a players real avatar and not a random avatar.
  • Games should be tested with different size avatars. A players avatar can be a number of different heights and sizes. A game should consider this when developing the game. Collisions may look incorrect with some size avatars.
  • Games should not expect a player to have an avatar with a specific gender. If the game contains gender specific text such as “she”, he”, “her”, or “him” the game should determine if the players gender if female or male using the AvatarDescription.BodyTypeproperty.
  • Keep in mind the rules regarding Avatar Use at all times.

For Reference, See:

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.

Another year, another restart :(

Well, not a complete restart at least.

What I have decided to do, is attempt to focus on and complete one game before moving on to another.  I’ve been fighting the “squirrel” battle for a long time, but I’m hoping to overcome it this year (just in time for the world to end, I know).

 

 

I’m digging up an old game that I’d started a number of times but put on the back burner since it’s a fairly big game, at least from the number of features I’m planning. I’m planning on releasing it on XBLIG (why I still haven’t figure out given it’s a virtual graveyard that’s to the mismanagement and/or uncaring of MS 🙁 ) and then probably IndieCity or possibly Steam if I’m lucky. WP7 is a dream, but I’m not sure how well it would do on that platform. If I can get enough done for the next DreamBuildPlay, so much the better.

The short list of significant features I’m planning on:

  • In-game level editor
  • Level sharing
  • “Global” leaderboards
  • In-game (and possibly out-of-game for the PC version) “store” for players to upgrade their “character”
  • 6 player multiplayer
  • Single player with AI

I’m hoping this game will be my ticket to indie game developer freedom so I can ditch the boring (although fairly well-paying) day job. I’m not holding my breath though. If I can make a decent chunk of money I’ll be happy.

I probably won’t share any more details about the actual game until it’s ready for playtest. I will be posting status updates here though for the 1 person that actually might follow this blog. 🙂

On a personal note, I did receive my MVP award again my contributions to the tech for last year. That’s 6 years in a row for anyone counting. I’m not sure how much longer I’ll be able to maintain it with XNA slowly dying however. I guess I’ll just keep chugging away and see what happens.

Yet Another Post About XBLIG crApps

Following the link in a review begging tweet on #xna I saw the following in the Notes of an XBLIG peer review submission:

“Here’s a quick and dirty Santa flying game I threw together.”

Time to put on the Grinch costume and respond I guess.

While I realize that holiday themed games usually do a little better on XBLIG, if you don’t care enough to take enough pride in your work to do a quality job why should I care enough to review the game? Yes, you might make a few bucks on the game due to holiday theme-ness, but is it really worth it in the long run?

What’s truly unfortunate is that there are enough crApp developers that games like this get passed with relative ease. If I or other developers that really care about the platform can’t find a reason to fail these crApps, there’s little we can do to stop them from passing. This is the downside to Microsoft “democratizing game development”; they obviously didn’t expect, or didn’t care about, people submitting things like flashlight apps, massager “games”, games that take 2 minutes to play through and don’t have any replay value, etc., ad nauseum.

Hopefully other devs that care about the platform will do the same and pass on submissions like this. Maybe I need to start an Occupy XBLIG! :\