Develop a Flexible 2.5D Scene Editor Targeting Silverlight RPG Games – Part 2

Share on TwitterShare on TumblrSubmit to StumbleUponSave on DeliciousDigg This

In this article, I’m going to introduce to you how to construct such a 2.5D RPG game scene editor that targets Silverlight and Silverlight for Windows Phone 7 from the very beginning.


Contents [hide]



In the first article of this series, you’ve visualized the main screenshots of our game scene editor. And at the same time, you’ve got a sketch outline of how the 2.5D application works. In this article, I’m going to introduce to you how to construct such a 2.5D RPG game scene editor that targets Silverlight and Silverlight for Windows Phone 7 from the very beginning.

As implied in the previous article, the prerequisites of writing such an application are that you should first have a good command of object-oriented programming, as well as be proficient in the Silverlight framework. With these tools in your hand, you will find creating a 2.5D RPG scene editor is not as difficult as you initially image.

NOTEThe sample development environments in this series involve:

1. Windows 7;

2. .NET 4.0;

3. Visual Studio 2010 Ultimate;

4. Silverlight 4.0;

5. Silverlight for Windows Phone 7;

6. Microsoft Expression Blend 4 (optional).

Set up the Scene Editor Project Sketch

Open Visual Studio 2010, click “File |New | Project”, and then click the “Silverlight” project node to create a Silverlight application (see Figure 1).

Figure 1: Select the proper project template for the game scene editor

Select the proper project template for the game scene editor

Next, in the subsequent ‘New Silverlight Application’ dialog set the host project type as ‘ASP.NET Web Application Project’. And then, select the Silverlight version as 4.0. Remember not to tick the ‘Enable WCF RIA Services’ option since our application will not persist data using the WCF RIA Services technique. Refer to Figure 2 below.

Figure 2: Select the proper host project and make related options for the game scene editor

Select the proper host project and make related options for the game scene editor

In our case, we nearly keep all the system automatically-generated stuff unchanged except that we remove the file MainPage.xaml from the Silverlight project. As you’ve seen, the system will automatically generate in the project two files: App.xaml and MainPage.xaml. Here, the App.xaml file is the Silverlight application’s startup configuration file. Now, open the related file App.xaml.cs, and we will see the following:

1.private void Application_Startup(object sender, StartupEventArgs e) {
2.  this.RootVisual = new MainPage();

As is seen, the Silverlight application’s default startup class is named MainPage. A special note is that, all Silverlight XAML interfaces with Code-Behind support are user controls inherited from UserControl. .NET programmers know all the classes in C# are single inheritance, which means that if we want to achieve the afore-mentioned game setup (in the first article), we cannot use it. Therefore, we should first remove MainPage.xaml, and then right click on the Silverlight project RPGSceneEditor and select “Add | New Item | Class” to add a new class named Window.cs, as shown in Figure 3.

Figure 3: Add our own class instead of the original MainPage.xaml

Add our own class instead of the original MainPage.xaml

So, the start class changes, but the machine does not know. Accordingly, we have to manually revise the previously mentioned file App.xaml.cs, as follows:

1.this.RootVisual = new Window();

This is the start point of the whole game scene editor, all the interactions between objects will happen inside the instance of Window. For example, a mouse click on the window results in the sprite moving to the corresponding coordinates, moving the mouse to the window edge will tract the map, the blood length will be reflected to the blood panel when the leader is attacked, and so on. In order to allow us a clearer understanding of the scene on a framework for the axis of the game, I specially create a diagram to describe the relationship between them.

Figure 4: Components in the multiple-layer based game scene architecture and their relationships

Components in the multiple-layer based game scene architecture and their relationships

Next, we will add the other classes, GameBase, DynamicObject, StaticObject, FloatableWindow, HUD, Scene, and Sprite in the same way as adding the class Window. All these classes are put in the Controls folder of the Silverlight project. Note these classes are all important ones in setting up a strong and extensible RPG scene editor. Regrettably, due to space limit, you have to research into them by yourselves. Joyfully, they are not as difficult as you image.

Add the RPGSceneEditor.Tools Class Library

The real game development is usually a very complex course. In order to improve system efficiency, we often need to introduce some third-party libraries into our own project as the supplement tools. The A* path finding method (which I have detailed into in a previous article at dotnetslackers) related library, for example, will be introduced into our game scene editor project. To introduce that project into our project here just needs you to follow up the following steps.

Right click our solution node RPGSceneEditor, and then select “Add | New Project”. From the subsequent dialog select the “Silverlight Class Library”, named it RPGSceneEditor.Tools, as shown in Figure 5.

Figure 5: Add a new Silverlight Class Library for the A* path finding method

Add a new Silverlight Class Library for the A* path finding method

Next, right click the RPGSceneEditor.Tools project to add a new folder, named AStar. And then, copy the four files in our above-mentioned project, IPathFinder.cs, PathFinder.cs, PathFinderFast.cs, and PriorityQueueB.cs to the newly-created AStar folder, and modify their original namespaces to RPGSceneEditor.Tools.AStar. Of course, to make this library available to our game scene editor, you should have to build the project first, and then add a reference in the project RPGSceneEditor to the RPGSceneEditor.Tools.AStar project.

NOTEMainly due to space limit, we’ll not delve into the A* algorithm; interested readers can refer to the downloadable source project yourselves.

Well, now the whole solution architecture has been initially accomplished. Figure 6 exhibits the rough folder structure.

Figure 6: The rough folder architecture of the scene editor solution

The rough folder architecture of the scene editor solution

NOTEThere is a special folder in the project RPGSceneEditor.Web named RPGSceneEditorResource, which will be used to hold some game resources to be downloaded on time according to the game requirement. It will be covered a moment later.

Start from the next section, we will explore how to further sort out the logical framework with bits and pieces.

Shape the Game Objects Using Object-Oriented Ideas

Game sprites should be familiar with you game developers and fans. Yes, like us human beings, an attractive sprite should behave like the man in the real world, who can stand, move, fight, be injured, die, etc. In our case, I’ve create the following Sprite class, with the related class diagram shown in Figure 7.

Figure 7: Class diagram for the Sprite class

Class diagram for the Sprite class

Each person lives in their own city where there are roads, bridges, mountains, water, and more – a variety of objects constitute the structure of the city’s façade. Similarly, a game sprite exists in its scene, with each scene having a vivid background map showing the beautiful scenery. Sprites are living freely in such a scene coordinate system. As long as they are happy they can roam to different scenes visiting friends and relatives or making adventure tourism. Therefore, the “scene” should manage all of its internal objects, such as sprites, magic, etc. As the bearer of important elements in the game hub, the scene has extraordinary significance. Figure 8 illustrates our design of the Scene class.

Figure 8: Class diagram for the Scene class

Class diagram for the Scene class

In the .NET world, if all the objects in the game world need to interact with other external objects, we just need to add a related delegate event. Where there is a need of this event we just need to trigger the event of the game window. Take a very simple example, if a sprite is constantly moving, you can easily manipulate its own method: sprite.MoveTo. However, if the “God” wants, by the “radar map panel”, to locate the exact location of the sprite, we cannot use the method sprite.TellRadar because the “radar map panel” is a property of the “game world” rather than the internal resource of the sprite. However, we can solve this thorny problem through another approach – define the corresponding coordinate change event and the related delegate.

Listing 1: Sile sprite.cs

01.public delegate void CoordinateEventHandler(Sprite sprite, DependencyPropertyChangedEventArgs e);
02.public event CoordinateEventHandler CoordinateChanged;
03.public override Point Coordinate {
04.    get { return (Point)GetValue(CoordinateProperty); }
05.    set { SetValue(CoordinateProperty, value); }
07.public static readonly DependencyProperty CoordinateProperty = DependencyProperty.Register(
08.    "Coordinate",
09.    typeof(Point),
10.    typeof(Sprite),
11.    new PropertyMetadata(ChangeCoordinateProperty)
13.private static void ChangeCoordinateProperty(DependencyObject d, DependencyPropertyChangedEventArgs e) {
14.    Sprite sprite = (Sprite)d;
15.    Point oldGameCoordinate = (Point)e.OldValue;
16.    Point newGameCoordinate = (Point)e.NewValue;
17.    if ((int)newGameCoordinate.X > sprite.LocatedScene.Matrix.GetUpperBound(0) || (int)newGameCoordinate.Y > sprite.LocatedScene.Matrix.GetUpperBound(1)) { sprite.Stand(); return; }
18.    if (sprite.LocatedScene.Matrix[(byte)newGameCoordinate.X, (byte)newGameCoordinate.Y] == 0) {
19.        sprite.Stand();
20.        sprite.Coordinate = new Point((int)oldGameCoordinate.X, (int)oldGameCoordinate.Y);
21.        Point end = sprite.GetGameCoordinate(sprite.LocatedScene.Gradient, sprite.destination, sprite.LocatedScene.GridSize);
22.        sprite.AStarMoveTo(sprite.destination);
23.    } else {
24.        Point newWindowCoordinate = sprite.GetWindowCoordinate(sprite.LocatedScene.Gradient, newGameCoordinate, sprite.LocatedScene.GridSize);
25.        Canvas.SetLeft(sprite, newWindowCoordinate.X - sprite.Center.X + sprite.CenterXAmendment);
26.        Canvas.SetTop(sprite, newWindowCoordinate.Y - sprite.Center.Y + sprite.CenterYAmendment);
27.        sprite.Z = (int)newGameCoordinate.X + (int)newGameCoordinate.Y;
28.        sprite.Direction = sprite.GetDirection(oldGameCoordinate, newGameCoordinate);
29.    }
30.    if (sprite.CoordinateChanged != null) {
31.        sprite.CoordinateChanged(sprite, e);
32.    }

Next, the most important class Window plays the role of the overall game platform for communication between all objects.

Figure 9: Class diagram for the Window class

Class diagram for the Window class

Up till now, you may have roughly sorted out the relationships between the most basic and indispensable elements in the three game worlds, Sprite, Scene, and Window. In fact, with the help of OOP, the solid and flexible fundamental components provided by Silverlight, and an earnest thinking of the real-world phenomena and principles, you will discover that everything is so relaxed and comfortable.

Import Local Map Resource

In the first article, I have covered the main reason why our scene editor is so highly versatile and marvelous a one. The core lies in its core function – dynamic coordinate system. By dynamically changing certain parameters of its internal scene and map, we can achieve the target of any inclination of the scene, changeable cell size, dynamic reference system and 3D rotation, as well as the multiple modes of the game leader’s position and display. Refer to the code in Listing 4 below.

Listing 2: file controls/dynamicobject.cs

01.public Point GetGameCoordinate(double angle, Point p, double gridSize) {
02.    if (angle == 0) {
03.        return new Point((int)(p.X / gridSize), (int)(p.Y / gridSize));
04.    } else {
05.        double radian = GetRadian(angle);
06.        return new Point(
07.            (int)((p.Y / (2 * Math.Cos(radian)) + p.X / (2 * Math.Sin(radian))) / gridSize),
08.            (int)((p.Y / (2 * Math.Cos(radian)) - p.X / (2 * Math.Sin(radian))) / gridSize)
09.        );
10.    }
12.public Point GetWindowCoordinate(double angle, Point p, double gridSize) {
13.    if (angle == 0) {
14.        return new Point(p.X * gridSize, p.Y * gridSize);
15.    } else {
16.        double radian = GetRadian(angle);
17.        return new Point(
18.            (p.X - p.Y) * Math.Sin(radian) * gridSize,
19.            (p.X + p.Y) * Math.Cos(radian) * gridSize
20.        );
21.    }

However, only the above is not enough. In fact, our game scene editor can also support dynamically importing game maps. Refer to the following code.

Listing 3: File controls/Window.cs

01.u00.Click += (s, e) => {
02.    OpenFileDialog openFileDialog = new OpenFileDialog() {
03.        Multiselect = false,
04.        Filter = "Image Files(*.jpg *.png)|*.jpg;*.png"
05.    };
06.    try {
07.        if ((bool)openFileDialog.ShowDialog()) {
08.            FileInfo fileInfo = openFileDialog.File;
09.            using (Stream stream = fileInfo.OpenRead()) {
10.                BitmapImage bitmapImage = new BitmapImage();
11.                bitmapImage.SetSource(stream);
12.                if (bitmapImage.PixelWidth < this.Width || bitmapImage.PixelHeight < this.Height) {
13.                    MessageBox.Show(string.Format("Map load fail!Picture size is less than the window size:{0} * {1}", this.Width, this.Height));
14.                } else {
15.                    scene.MapSource = bitmapImage;
16.                    mapDetailsOutPut.Text = string.Format("Width:{0}px  Height:{1}px", scene.MapWidth = bitmapImage.PixelWidth, scene.MapHeight = bitmapImage.PixelHeight);
17.                }
18.                stream.Close();
19.            }
20.        }
21.    } catch {
22.        MessageBox.Show("Map load fail! Please check the format.");
23.    }

The above code is easy to understand. In Silverlight, we can import local resources through OpenFileDialog. A point to remember is the current Silverlight version can only support a small number of image formats (see the file type filter above). Since the pictures are on the local machine, as soon as we select the picture (via bitmapImage.SetSource(stream)) we will know immediately the image size (via bitmapImage.PixelWidth and bitmapImage.PixelHeight), which is very important information, because the current game window size is limited to 800 * 580. If the map image width and height are less than the value there will be no sense for the movable mode of the game map.

Edit the terrain

Besides the above, our scene editor should also have the ability to edit terrain. Yes, I define, in the current version, five most common types of terrain: obstacles, plains, grasslands, deserts and rivers:

1.public enum Terrains {
2.    Obstacle = 0,
3.    Flat = 1,
4.    Grassland = 2,
5.    Desert = 3,
6.    River = 4,
7.    None = 100,

Next, what we need to do is set the reference system to “Box” and then select the appropriate drawing objects. We can draw the appropriate terrain in the coordinate system of the map by clicking the left mouse button.

Figure 10: Edit the terrain on the map

Edit the terrain on the map

Send out different sounds across different terrains

Another interesting question is raised: can we let the game leader send out different sounds when he walks across different terrains? The answer is yes and simple. What we do is add a sound property to the class GameBase, so that each sub class inherited from this class owes such a sound “source.” So, in our case, the main game window can play the game’s background music, while the scene is responsible for the local environment related sound – the wind sound of the sprite wielding ax, the cry when injured, walking footsteps and the explosion when applying magic, etc. Refer to the following code.

01.MediaElement sound = new MediaElement() {
02.     IsHitTestVisible = false,
03.     Visibility = Visibility.Collapsed,
04.     AutoPlay = true,
06.public void MakeSound(string uri, bool loop) {
07.    sound.Source = new Uri(string.Format(@"../Media/{0}", uri), UriKind.Relative);
08.    sound.Position = TimeSpan.Zero;
09.    SoundStart(loop);
11.public void SoundStart(bool loop) {
12.    if (loop) { sound.MediaEnded += sound_MediaEnded; }
13.    sound.Play();

HLSL effect

Have you remembered HLSL in DirectX? That’s a Super-powerful tool in terms of game special effect. Joyfully, WPF and Sivlerlight also support most functions of HLSL. Due to space limit, I’m not going to delve into HLSL, interested readers can refer to my previous article related HLSL at dotnetslackers. In our product, we’ve also provided the support for HLSL effect. Just refer to the snapshots in Figure 11 below.

Figure 11: Plentiful HLSL support in the scene editor

Plentiful HLSL support in the scene editor

Dynamically download the game resources

In most large and real-case games, there will be tons of game resources required to be downloaded in the dynamic way to gain better user experience. In our case, this is not a question. Refer to the following code.

01.static Dictionary<string, bool> downLoadImages = new Dictionary<string, bool>();
02.public void SetImage(Image image, string uri, string legend) {
03.    if (downLoadImages.ContainsKey(uri)) {
04.        if (!downLoadImages[uri]) {
05.            image.Source = new BitmapImage((new Uri(string.Format(@"../SceneEditorImages/{0}", legend), UriKind.Relative))) {
06.                CreateOptions = BitmapCreateOptions.None
07.            };
08.        } else {
09.            image.Source = new BitmapImage((new Uri(string.Format(@"../SceneEditorImages/{0}", uri), UriKind.Relative))) {
10.               CreateOptions = BitmapCreateOptions.None
11.           };
12.       }
13.   } else {
14.       BitmapImage bitmapImage = new BitmapImage((new Uri(string.Format(@"../SceneEditorImages/{0}", uri), UriKind.Relative)));
15.        bitmapImage.ImageOpened += (s, e) => {
16.            image.Source = s as BitmapImage;
17.            downLoadImages[uri] = true;
18.        };
19.        image.Source = bitmapImage;
20.        image.Source = new BitmapImage((new Uri(string.Format(@"../SceneEditorImages/{0}", legend), UriKind.Relative))) {
21.            CreateOptions = BitmapCreateOptions.None
22.        };
23.        downLoadImages.Add(uri, false);
24.    }

Our idea is before the download is complete we use some dummies to present the real stuffs. When the download is complete the objects show the original true faces. Figure 12 shows this case.

Figure 12: Friendly dynamically downloading resources support

Friendly dynamically downloading resources support

The main work is finished with the help of the WebClient class. The related code is listed below.

1.WebClient webClient = new WebClient();
2.webClient.OpenReadCompleted += (s, e) => {
3.    BitmapImage bitmapImage = new BitmapImage();
4.    bitmapImage.SetSource(e.Result);
5.    image.Source = bitmapImage;
6.    downLoadImages[uri] = true;
8.webClient.OpenReadAsync(new Uri(string.Format(@"../SceneEditorImages/{0}", uri), UriKind.Relative), uri);

On the whole, the approach of using WebClient to download game resources is more stable than other means, especially when the client-side speed is dissatisfactory. For more materials concerning WebClient, please check related articles at the online MSDN; we are not to detail into it any more. By the way, as you may have noticed that we put most of image resources to be dynamically downloaded in the folder RPGSceneEditorResource in the project RPGSceneEditor.Web.


In this article, you’ve learned the main techniques in constructing the PRG scene editor. However, due to space limit, we’ve not introduced the detailed code associated with some interesting techniques in the application. Anyway, the most important parts have been scratched. A last word is the PRG scene editor is more than a scene editor; by further looking into the whole sample application you will find you can even create a high-quality game with a slight modification.