If you’re authoring multimedia applications in Silverlight, you might be interested in how each of the core game engine services for Legend of the Greasepole is now implemented for the Silverlight 2 Beta.
From C/C++ to a Provider Model-Based .NET Engine
Some brief history to explain how we got here: Greasepole’s first incarnation was manky C/C++ stuff that I’d perf-optimized to bits back in 1997-98. Trainwreck stuff.
Migrating that C/C++ mess to a C# mess involved writing a lot of Visual Studio 2008 macros. These macros could gain surprising insight into non-compiling half-C++, half-C# code, in order to sweep through and perform laborious changes.
What came out the other end of the refactor was a C# core engine for the game that required the implementation of five platform-specific services:
- RenderingService,
- SoundService,
- InputService,
- GameTimerService, and
- GameSettingsService.
I previously implemented those services for the XNA version. Now let’s look at the Silverlight versions of these services.
Silverlight RenderingService
When each frame is rendered, the engine ultimately requests that the RenderingService draws a series of sprites from particular “frame descriptions” or “FrameDesc”s (a vestigial name, essentially meaning bitmaps with metadata) at given (x,y) coordinates:
public void DrawBitmap(TSprite associatedSprite, FrameDesc frameDesc, int nScrx, int nScry, byte[] replaceRGB, byte[] substituteRGB)
The bitmaps themselves are stored as Resources in two assemblies (SilverLegendAssetsMenu.dll and SilverLegendAssetsGame.dll) that I download on-demand using WebClient. This has the advantage of dramatically reducing startup time, and only requiring the game engine to manage the downloading of two files over the web after startup. An equivalent option would have been downloading archives full of bitmaps, but I was concerned that this would require decompressing the bitmaps at runtime.
The RenderingService pre-caches the URIs for the bitmap associated with each FrameDesc (e.g. “SilverLegendAssetsGame;component/Graphics/Frosh4a.BMP”) so that the bitmap can be looked up without string manipulation whenever it is requested by a sprite.
Each game sprite is assigned its own System.Windows.Control.Image by the RenderService, which is initialized by having its UriSource Property set to a System.Windows.Media.Imaging.BitmapImage. When the FrameDesc for the sprite changes (and therefore the sprite’s bitmap appearance has changed), the Source Property of the BitmapImage, a URI, is changed to the appropriate cached Uri. (It looks as though you’re not permitted to re-use BitmapImage instances across multiple Images.)
Mirrored images are achieved by setting a MatrixTransform on the sprite’s Image’s RenderTransform.
Z-Ordering is performed by using the ZOrder property on the Images.
After each frame is rendered, the RenderingService iterates through all the Images on the Canvas, looking for any that weren’t rendered during that frame and therefore should be removed from the Canvas.
But what’s with the byte[] replaceRGB, byte[] substituteRGB parameters on the DrawBitmap call? In all the other versions of Greasepole, the characters became multicultural by performing color-key swapping of their solid skintones. The DirectX engine used two BitBlts, and the XNA engine uses custom shaders. Achieving this in real-time with Silverlight 2 is something I haven’t yet figured out.
Silverlight SoundService
Sounds assets are stored as MP3 Resources in the previously-mentioned download-on-demand assemblies SilverLegendAssetsMenu.dll and SilverLegendAssetsGame.dll.
Just as with with RenderService, the SoundService pre-caches the URIs to all the sound files.
The SoundService keeps a static collection of MediaElements around. These are added as children to an invisible Canvas called SoundCanvas. When the engine requests that a sound is played, the SoundService finds an unused MediaElement in the static collection, sets its Source Property to the correct URI, and Play()s it.
The MediaElement’s MediaEnded event is handled to either loop the sound by either (1) setting .Position=TimeSpan.Zero and calling Play() again, or, (2) if it’s not looping, to add the MediaElement back to the static collection of available Media Elements.
Incidentally, this whole MediaElement juggling trick was a highly unstable process in the Silverlight 2 Alpha which seems to work reliably in the beta.
Silverlight InputService
In the Silverlight 2 Beta, handling input was a piece of cake. Mouse and Keyboard events are handled for events on the main Canvas to inform the InputService of human interaction.
Unfortunately for game developers, keyboard input is more or less crippled when Silverlight is in fullscreen mode. Luckily, we’d designed Greasepole to be playable with just one mouse button.
I also wish I could use the right mouse button for input, but instead it brings up the Silverlight Configuration menu.
Silverlight GameTimerService
To create a game loop, Greasepole uses the “empty Storyboard technique” outlined at Silverlight Games 101. The game loop calls Update enough time to ensure a constant 24 updates-per-second update rate, and Render whenever possible, which triggers the RenderingService (see above).
In every other respect, the game timer is the exact same as in the XNA version. DateTime.Now works as advertised! Here’s to a common set of .NET Framework classes!
Silverlight GameSettingsService
The IsolatedStorageFile API makes it very easy to rapidly save and load game settings in a local sandbox.
See this sample for more information on how to use the virtual file system in Silverlight. The GameSettingsService uses a very similar technique.
Written Mar 25th, 2008 |
[...] сделать стандартными методами и тут нашел небольшую статейку с идеей. Честно еще не сильно разбирался, но хотелось [...]
[...] сделать стандартными методами и тут нашел небольшую статейку с идеей. Честно еще не сильно разбирался, но хотелось [...]