I never thought development of web games can be that simple until I started development of my first game with Adobe Flex. I made two games, without any prior experience with Action script, in only ten months: "Fitter" and "RGB".

I found many answers to my questions on different forums and web sites, and it is time to share my knowledge with Flex community.

Sunday, March 21, 2010

Pause, continue and settings

There is not much more to say about Flex game development, at least from the technical point of view. If you followed this tutorial from the beginning, you should have enough knowledge to make a great game. You just need a good idea and will to realize it.

As far as programming is concerned, Snake's Adventure is almost complete. But overall, it is still far from finished. It requires more content, graphics, sound effects and music.

There are still few things to do, before this tutorial is complete. At the beginning of this tutorial, we were working on game menu. While we were exploring the basics of graphics and event handling, we implemented new game option in the game menu, but continue and settings options still remain unimplemented.



Pausing the game

When the player has to pause the game for a moment, he will most probably press Escape or Space key on the keyboard. Our program is currently able to detect when mouse key is pressed or released. If we wish it would be able to detect key events, we have to implement Keyboard event handler. Piece of cake...

Open file SnakesAdventure.mxml and add new event handler to mx:Canvas element:

keyUp="KeyUp(event)"

Whenever any key on the keyboard is released, program executes KeyUp function, implemented in file SnakesAdventure.as:

private function KeyUp(event:KeyboardEvent):void
{
  switch (state)
  {
  case GAME:
    KeyUp_Game(event);
    break;
  }
}

We've used the same approach here, that was used in all other event handlers. Whenever the event occurs, the event handling routine executes the appropriate function, according to value of state variable (active state). KeyUp_Game function belongs at the end of the file Game.as:

private function KeyUp_Game(event:KeyboardEvent):void
{
  if (event.keyCode == Keyboard.ESCAPE)
  {
    saveState = gameState;
    ChangeState_Game(GAME_FADE_OUT);
  }
}

KeyboardEvent has many properties, one of them is keyCode - the index of key that caused the event. For example, ESCAPE key has index 27, SPACE key has index 32... but I prefer using public constants, defined in the Keyboard class.

When ESCAPE key is pressed, active game state (gameState) is stored into saveState variable and GAME_FADE_OUT state becomes active. Add definition of saveState variable to other definitions, at the beginning of the file.

private var saveState:int = 0;

Value of variable needs to be reseted, whenever the game begins. Add this statement into New_Game function:

saveState = 0;

In the current version, Reset_Game function is executed, after the game state GAME_FADE_OUT ends. Now, the program needs to distinguish whether the screen faded out because the player made the mistake or because the ESCAPE key was pressed. We can use saveState variable for this. In the Draw_Game function, replace the GAME_FADE_OUT case in switch statement, with this version:

case GAME_FADE_OUT:
  Apply_FadeEffect(gameTime);

  if (gameTime >= 1)
    if (saveState == 0)
      Restart_Game();
    else
    {
      state = GAME_MENU;
      ChangeState_GameMenu(TITLE_SHOW);
    }
  break;

If the value of saveState variable is zero, function Reset_Game is executed, otherwise active state of the program is changed to GAME_MENU.

Recompile and run the program and press ESCAPE key, while the game is running. Key should trigger screen fade-out event and after few seconds game menu should become visible. But it doesn't, because the game panel does not have the focus. Adding this statement at the end of UpdateFrame function in file SnakesAdventure.as, should solve the problem:

gamePanel.setFocus();

Continue the game

To be honest, the functionality that we implemented so far, does not pause the game. The player cannot return into the game, because continue option is disabled. We will make some changes in file GameMenu.as, that will enable continue option, when value of saveState variable is set (not zero).

In Draw_GameMenu function, change the statement:

if (i == OPTION_CONTINUE)
  srcY = 0;

to

if ((i == OPTION_CONTINUE) && (saveState == 0))
  srcY = 0;

In the switch statement, replace:

case OPTION_CONTINUE:
  srcY = 100;
  break;

with

case OPTION_CONTINUE:
  srcY = saveState != 0 ? 50 : 100;
  break;

From now on, continue option will be displayed as enabled, whenever the "paused" mode is active. To activate the option in the "paused" mode, we have to make a change in function MouseDown_GameMenu. Replace the second if statement:

if (i != OPTION_CONTINUE)

with

if ((i != OPTION_CONTINUE) || (saveState != 0))

When the "paused" mode is active, the continue option looks active and responds to mouse events. We just have to implement the transition from game menu, back into the game.

Add new case into inner switch statement in MouseUp_GameMenu function:

case OPTION_CONTINUE:
  selectedOption = OPTION_CONTINUE;
  ChangeState_GameMenu(GAMEOPTIONS_OUT);
  break;

This will activate GAMEOPTIONS_OUT game menu state, after mouse button is released. Eventually, game menu options will disappear and title screen will fade out. At this point we have to reactive GAME program state in Draw_GameMenu function. Add new case into switch statement under case TITLE_HIDE block:

case OPTION_CONTINUE:
  state = GAME;
  ChangeState_Game(GAME_CONTINUE);
  break;

GAME_CONTINUE game state is new in the project. During this state, game screen will fade in and after one second, game state index, that was previously stored into saveState variable, will become active again.

Add the definition of constant GAME_CONTINUE, at the beginning of Game.as file.

private static const GAME_CONTINUE:int = 8;

and new case into switch statement inside Draw_Game function:

case GAME_CONTINUE:
  Apply_FadeEffect(1 - gameTime);

  if (gameTime >= 1)
  {
    ChangeState_Game(saveState);
    saveState = 0;
  }
  break;

Settings

This is the last option in the game menu that still remain unimplemented. In this section we will enable the option to display settings menu with sound and music volume control. Menu will also feature option that will close settings menu and reactivate game menu.

Settings menu functionality will be implemented is implemented in separate file - SettingsMenu.as. This file is part of project package for this chapter, the content of the file will be explained later. For now, just add new include statement at the beginning of SnakesAdventure.as:

include "SettingsMenu.as";

Implementation of settings menu will be similar to implementation of game menu. SETTINGS_MENU constant value will represent 'settings menu' state of the program. Add the definition to definitions of other program state constants:

private static const SETTINGS_MENU:int = 2;

Value two is already used in GAME constant, that is why you have to change its value to 3. Before we focus on the implementaton of settings menu, we have to call appropriate functions in event handling routines, the same way we did for all other states.

We have to add new cases into switch statement inside:
- Draw function:
  case SETTINGS_MENU:
    Draw_SettingsMenu(ellapsedTime);
    break;

- MouseDown function:
  case SETTINGS_MENU:
    MouseDown_SettingsMenu(event);
    break;

- MouseUp function:
  case SETTINGS_MENU:
    MouseUp_SettingsMenu(event);
    break;

- MouseMoved function:
  case SETTINGS_MENU:
    MouseMoved_SettingsMenu(event);
    break;

All four functions are implemented in SettingsMenu.as. The file contains almost two hundred lines of code, that is not much different from the code in GameMenu file. I will display and explain all the code in this tutorial, I will rather focus on important parts.

Settings menu contains its own state automaton with three possible sub-states:
- SETTINGSOPTIONS_IN becomes active first and it last only one second. Within this second, all three options move from the left edge of the frame to the centre.
- while SETTINGSOPTIONS_WAIT state is active, player can change the sound and music volume. When 'Return' option is selected, it activates
- SETTINGSOPTIONS_OUT state and all three options move from the centre of the frame to the right edge, until they disappear. At this moment, program activates GAME_MENU state and eventually, game menu options become visible again.

Draw_SettingsOptions takes care of displaying settings menu options. It is not much different from Draw_GameMenu, even the same algorithm is used to shift the options to the centre and away from the centre of the frame.

Image file SettingsOptions.png contains option labels and two volume bars. This image is part of the project package and has to be embedded into the project, before it can be used (Images.as).


Options and labels inside each option are drawn the same way, that was used to draw game menu options. The only important difference is the code, that displays volume control at the bottom of sound and music volume option. This code can be found at the end of Draw_SettingsOptions function:

switch (i)
{
case OPTION_SOUNDVOLUME:
  screenBuffer.copyPixels(settingsOptionsBitmap,
                        new Rectangle(0, 150, 260, 25),
                        new Point(110 + srcX,277 + i * 70));

  screenBuffer.copyPixels(settingsOptionsBitmap,
                        new Rectangle(24, 183, int(212 * soundVolume), 7),
                        new Point(134 + srcX,285 + i * 70));
  break;
case OPTION_MUSICVOLUME:
  screenBuffer.copyPixels(settingsOptionsBitmap,
                        new Rectangle(0, 150, 260, 25),
                        new Point(110 + srcX,277 + i * 70));

  screenBuffer.copyPixels(settingsOptionsBitmap,
                        new Rectangle(24, 183, int(212 * musicVolume), 7),
                        new Point(134 + srcX,285 + i * 70));
  break;
}

The same approach is used to draw both controls, but different variables are used to display the volume bar and they are displayed in different locations, of course. Variable soundVolume was introduced in chapter 12 - Playing sound effects and it is declared in file Sounds.as. Variable musicVolume is the copy of this variable, and it contains the value that will be used to control the volume of music played.

The complete image, that represents the empty bar, is copied into the screen buffer first. Afterwards, the image of blue bar is copied into the screen buffer. The portion of image, that is used in the process, is calculated from value of soundVolume or musicVolume variable. Value of both variables can range from 0.0 to 1.0, value is multiplied by 212 (the length of the bar image) and calculated value defines the width of the source image that is copied into screen buffer.

Whenever the player clicks inside the sound/music volume control, the bar in the control should change its size and volume of sound or music should change accordingly. MouseDown_SettingsMenu function takes care of this:

private function MouseDown_SettingsMenu(event:MouseEvent):void
{
  var mx:int = event.localX;
  var my:int = event.localY;

  if (settingsMenuState == SETTINGSOPTIONS_WAIT)
  {
    if ((mx >= 130) && (mx <= 341) && (my >= 283) && (my <= 293))
    {
      soundVolume = (mx - 130) / 211;
      StoreData(SOUND_VOLUME);
      PlaySound(OPTIONSELECTED_SOUND);
    }

    if ((mx >= 130) && (mx <= 341) && (my >= 353) && (my <= 363))
    {
      musicVolume = (mx - 130) / 211;
      StoreData(MUSIC_VOLUME);
      PlaySound(OPTIONSELECTED_SOUND);
    }

    if ((mx >= 110) && (mx < 370) && (my >= 390) && (my < 440))
    {
      pressedOption = OPTION_EXIT;
      PlaySound(OPTIONSELECTED_SOUND);
    }
  }
}

When mouse button is pressed and SETTINGSOPTIONS_WAIT state is active, function performs three checks. First it checks, if mouse cursor is positioned inside sound volume bar. If it is, value of soundVolume variable is calculated from horizontal position of the left edge of bar, mouse cursor position and bar length. New value of soundVolume variable is stored into SharedObject, the same way we stored highest score in chapter 21. Second check takes care of music volume control and works the same way. Finally, the function checks if mouse cursor is inside "Return" option and if it is, option index is stores into pressedOption variable. Each time user clicks inside one of this areas, sound effect is played.

Now, that we have settings menu implemented, we just have to activate it, whenever user chooses "Settings" option. In file Game.as add new case into inner switch statement in MouseUp_GameMenu function:

case OPTION_SETTINGS:
  selectedOption = OPTION_SETTINGS;
  ChangeState_GameMenu(GAMEOPTIONS_OUT);
  break;

This will activate GAMEOPTIONS_OUT game menu state, after mouse button is released. After one second, game menu options will disappear and at this point, SETTINGS_MENU program state has to be activated in Draw_GameMenu function. Inside switch statement, change the if statement in case of GAMEOPTIONS_OUT:

if (gameMenuTime >= 1)
  if (selectedOption == OPTION_SETTINGS)
  {
    state = SETTINGS_MENU;
    ChangeState_SettingsMenu(SETTINGSOPTIONS_IN);
  }
  else
    ChangeState_GameMenu(TITLE_HIDE);

Finally, we have to update LoadData and StoreData functions in SnakesAdventure.as.

private function LoadData():void
{
  var so:SharedObject = SharedObject.getLocal("SnakesAdventure");

  if (so.size > 0)
  {
    if (so.data.hiscore != null)
      hiscore = so.data.hiscore;

    if (so.data.soundVolume != null)
      soundVolume = so.data.soundVolume;

    if (so.data.musicVolume != null)
      musicVolume = so.data.musicVolume;
  }
}

private static const HISCORE:int = 1;
private static const SOUND_VOLUME:int = 2;
private static const MUSIC_VOLUME:int = 3;

private function StoreData(what:int):void
{
  var so:SharedObject = SharedObject.getLocal("SnakesAdventure");

  switch (what)
  {
  case HISCORE:
    so.data.hiscore = hiscore;
    break;
  case SOUND_VOLUME:
    so.data.soundVolume = soundVolume;
    break;
  case MUSIC_VOLUME:
    so.data.musicVolume = musicVolume;
    break;
  }

  so.flush();
}

From now on, LoadData function, that is called during initialization phase, will also restore value of sound and music volume from SharedObject. StoreData function is now capable of storing the value of global variable soundVolume or musicVolume into SharedObject. This will happen whenever user changes volume of sound or music in settings menu.

No comments:

Post a Comment