3 Home
tompaana редактировал(а) эту страницу 2014-11-12 05:08:40 -08:00
Этот файл содержит невидимые символы Юникода!

Этот файл содержит невидимые символы Юникода, которые могут быть отображены не так, как показано ниже. Если это намеренно, можете спокойно проигнорировать это предупреждение. Используйте кнопку Экранировать, чтобы показать скрытые символы.

Match'em Poker is a game originally written for iOS and later ported to Qt. The goal of this project was to port the Qt version to Windows Phone 7. The objective was to port the application with minimal effort, reusing all of the original assets and modifying the code as little as possible.

Start view  Playing  Level completed

About the game

Match'em Poker is a traditional match-3 type of game with a rectangular grid. The player can swap the position of any two cards. When a 'poker hand' is formed, it will disappear from the level and the player's score increases. A card will drop down if there is an empty space below it. New cards will appear in empty spaces in the first row. The level starts with a timer value of 40. This number decreases with time, and increases when the player scores. The level is completed when the timer reaches a value of 100. If the timer drops to zero, the game is over. Accepted 'hands' are: 4 or more of the same colour, 3 or more of the same number, a straight of 4 or more. The hands can appear either horizontally or vertically.

The difficulty increases when the player reaches higher levels. The time spent will affect the timer more and more, and destroyed cards will increase the timer less and less. Theoretically the game can continue forever. In practice, it will get very difficult around level 20.

There are increasing numbers of empty cards in the levels. An empty card will not be accepted to be a part of a hand until it's opened (by tapping it). When an empty card is opened, a small amount of time is removed from the timer (as a payment).

Porting from Qt to Windows Phone (XNA)

Match'em Poker XNA is a port from Match'em Poker Qt version that was implemented on top of the Qt GameEnabler. This page describes observations and issues faced during the port.

This is the second port of Match'em Poker since it was originally written for iOS (iPhone) and the Qt version is ported from that one.

The aim was to copy the features and overall feeling of the application faithfully from the original version with a minimal amount of work, thereby studying XNA framework's possibilities.

Technologies used

  • XNA Framework
  • SpriteBatch

Porting

The project was started by getting the WP7 SDK. The installation didn't cause any problems with the already installed Visual Studio C++ express.

The Qt version of Match'em Poker doesn't have many game-specific source files.

In all phases, the code was compiled from time to time to make sure everything was working and the project was going forward.

Step by step

  • A new XNA project was created with the Wizard's functionality.
  • C++ header files were renamed to .cs and added into this new project.
  • New .cs files were edited from C++ to C#. At this phase, the .cs files didn't contain more than members of classes and empty implementations for all the methods. If a nontrivial error was discovered (for example, using a pointer), it was left to the todo phase and modified just a little bit to make the code compile.
  • Piece by piece, without checking if the logic actually works, functionalities of methods were copied; method by method. The code was always modified as little as possible, just enough to make it compile again. This was definitely the most time-consuming part of the porting project.
  • In Match'em Poker, 'platform-dependent' functionality (for example, rendering) is mapped through an interface called ITileRenderer. When the port from iOS to Qt was done, this was the most work-consuming part to rewrite. After that, most of the functionality worked as expected. iOS and Qt (with Qt GameEnabler) both use OpenGL ES 2.0, but in XNA this wasn't possible. The decision was made to use XNA's object SpriteBatch for OpenGL Es 2.0 replacement since the original idea of rendering 'tiles' is very similar to sprites.
  • TileRendererXNA was derived from ITileRenderer and platform-dependent parts were implemented. Some platform-dependent method calls were added to the game itself to make SpriteBatch work (this required beginFrame and endFrame calls).
  • Graphics were added to the project with SDK's own content system. The original Match'em Poker images are PNG files so this was very simple.
  • New SpriteBatch based rendering was tested with temporary code to make sure that the results are similar with OpenGL ES 2.0 rendering.
  • When most of the code was in place and in a state where something might work, the debugging started. The flow of the program was followed to find places where logic failed because of the port. All of the parts left at unknown state were also fixed part by part when their functionality was required.
  • The original space (window size) of the game was 65536x116508 since everything was done with fixed points. The scale was changed to 1x1.66 since fixed points were removed and replaced with floats. This was initially done by 'just' dividing all of the constants with 65536 and adding typecasts to floats when required.
  • Constants divided by 65536 were calculated open to make the code cleaner. At the same time, lots of constants were added to give these 'hat values' more meaning.
  • Sounds were added to the project and the platform-dependent method EffectNotify was overridden from TileRendererXNA to play sounds when something happends.
  • Next, the working game was tested over and over again. Encountered bugs were fixed and code cleaned when something messy was found.
  • When the project was in good enough shape, a package was built and published.

Loading and saving

The original code used FILE* handles for loading and saving the files since they are supported in all of the C/C++ environments. The actual storing was done (quite messily) by storing 11*4 bytes from the beginning of the first member to be saved. All of the members to be saved were ordered after the first one.

In XNA, IsolatedStorage was used and the overall system became quite similar with the difference that each member is stored separately using BinaryWriter for the file stream:

Original code:

int CTileNpc::save() {
	FILE *file = fopen(SAVE_FILE, "wb" );
	if (!file) return 0;
        fwrite( &m_difficulty, 1,11*sizeof(int), file );
	if (m_gameIsOn==1) m_level->saveToFile( file );
	fclose(file);
	
	return 1;
}

XNA code:

public override bool Save()
        {
            IsolatedStorageFile IS = IsolatedStorageFile.GetUserStoreForApplication();

            try
            {
                using (IsolatedStorageFileStream IS_FS = IS.CreateFile("mpok.dat"))
                {
                    using (BinaryWriter bw = new BinaryWriter(IS_FS))
                    {
                        bw.Write(m_difficulty);
                        bw.Write(m_blockTimerEffect);
                        bw.Write(m_timeTimerEffect);
                        bw.Write(m_currentLevel);
                        bw.Write(m_targetTimer);
                        bw.Write(m_timer);
                        bw.Write(m_score);
                        bw.Write(m_displayScore);
                        bw.Write(m_highScore);
                        bw.Write(m_gameIsOn);
                        bw.Write(m_waitBeforeTimerStartsTime);

                        if (m_gameIsOn == 1)
                        {
                            m_level.SaveToFile(bw);
                        }
                    }
                }
            }
            catch (IsolatedStorageException e)
            {
                return false;
            }     
            
            return true;
        }

The level loading and saving is done accordingly, storing each index of the grid into the file stream:

public void saveToFile( BinaryWriter bw )
        {
            for (int i = 0; i < m_gridWidth * m_gridHeight; i++)
            {
                bw.Write(m_grid[i].index);
            }
        }

Rendering

Since everything was rendered with OpenGL's triangle-fans forming single quads, it was relatively easy to switch the rendering to a SpriteBatch based solution. The only modification to the interface was that SpriteBatch required start and end calls which were done by the runner framework in the original application. OpenGL rendering allowed the user to choose if blending should be additive or alpha. This option was removed from the SpriteBatch version since only one type can exist in a single begin/end pair. The original images were slightly modified to give an 'additive blending' type look to the sprites which were meant to be rendered that way.

Content

Both the graphics and the sounds were automatically processed by XNA's content pipeline. After adding, the contents were accessible with XNA's own loading functions for both items. This was the easiest system to use I've ever encountered.

Conclusion

This was my first C# project, as well as my first project using XNA. There might have been better ways to do the porting, but I didn't have very much time and just had to start working without thinking too much. In my opinion, overall, the porting was very successful: I even managed to clean the code and add comments while porting. I found C# to be very easy for a C++ developer. One only needs to 'adjust' a couple of hardwired habits and then just code.

Working with XNA framework for Windows Phone 7 is easy and straightforward. Everything that this project used worked nicely in the emulator and in the actual device. Debugging was surprisingly easy and worked well. C# was so similar to C++ that the differences are almost unnoticeable after using C# for a while.

For me personally, the most difficult thing was to trust the underlying framework without knowing exactly what it is doing. Also, I found the idea of being placed in a 'sandbox' somehow limiting even though the limits weren't reached in this project.

Sounds effects

All the sounds used by the application are taken from the Freesound Project. The sounds were made by the following users: milton, mattwasser, Christianjinnyzoe. Thank you Freesound!