Packt Publishing, the folks who published our Microsoft Silverlight 4 Business Application Development book have included some of the chapters of that book in a new mashup of their best selling Silverlight books. The new book is called Managing Media and Data in Microsoft Silverlight 4 and features great chapters from several different authors’ Silverlight books, covering the gambit from XAML and layouts to SharePoint integration to REST and RIA services.
In addition, they are also offering a discount on all Microsoft technology books during the month of May so be sure to check that out as well at Packt’s Microsoft Carnival!
Silverlight is still a great technology for business development, it eases deployment issues with distributed software, provides an interactive experience for users via a browser while maintaining the power of C# in the development life cycle.
5/10/2012
I have made some decent progress on scaling Perenthia down and re-tooling it for more basic HTML/mobile access. I have the server core complete and am working on the UI components. The first step will be to get the adventuring/combat piece finished and then move on to the crafting system.
The abstract core server pieces I decided to put into a separate library and made the source available on GitHub. I call it “Leo” and it provides the basic game server and provides some interfaces and associated libraries for processing input through the server. Since I am using SignalR for client to server communication I included some code in Leo to provide a input processor and PersistentConnection. The TestConsole project was something I used to initially setup the game libraries. They are now linked into my Perenthia solution and will be updated as I find the need while working on Perenthia.
The Leo source is very early stages and is subject to change frequently. It is also intended primarily for low traffic games such as iOS, Facebook, etc. I will up some instructions once it gets a little more stable.
Here is a screenshot of Perenthia’s new progress so far:
5/4/2012
I ran into some issues attempting to test Facebook Credits with ASP.NET locally during development. I have an MVC3 site that is running in the Azure Development Environment and I wanted to be able to test the credits functionality. I played around with a bunch of settings, etc. and finally got a combination of things working that allows me to test credits locally. Below are the steps I took:
1. Set the web project as the startup project in the Visual Studio solution. Unless you need specific Azure functionality this is much easier since Azure does not want to accept traffic from anything other than localhost and credits testing will not work with localhost.
2. Set your web project to run using IIS or IIS Express bound to http://localhost. (Project Properties -> Web -> Servers) If you want to use IIS Express and have IIS installed you will need to unbind the default web site from port 80. ** I also had to stop the Web Deployment Agent Service as it binds to port 80.
3. I used DynDns.com to provide a host name for my web site and entered this as the domain for both the Canvas Url and Credits Callback Url. The Credits callback will not work with 127.0.0.1 or localhost so you need some external domain. I tried using a made up entry in my HOSTS file but Facebook needs a real address since they initiate the request for the callback.
4. I edited the applicationHost.config file under %userprofile%documentsiisexpress to allow my DynDns name like so (system.applicationHost/sites node):
<site name="WebAppName" id="1">
<application path="/" applicationPool="Clr4IntegratedAppPool">
<virtualDirectory path="/" physicalPath="DirToWeb" />
</application>
<bindings>
<binding protocol="http" bindingInformation="*:80:localhost" />
<binding protocol="http" bindingInformation="*:80:dyndnsname" />
</bindings>
</site>
5. Do any port forwarding on your router and enable port 80 on your firewall.
6. Setup the callback code according to the Facebook Docs, I even used the JS sample code to test.
6/24/2011
After many months of not working on Perenthia I find myself once again desiring to see it complete. The last revision I did on Perenthia was some time ago and involved a Silverlight UI with a C#/SQL 2008 backend.
During the course of development on Perenthia I discovered some of the pitfalls of attempting to develop a role playing game by one self. Mainly, the inability to handle content creation on a large enough scale as to appeal to players and have enough interactivity to make the game fun. I have been thinking lately of ways to reduce that load and still keep the game fun. I have some new ideas I want to put into place and re-release Perenthia with these concepts.
Among the changes I plan to implement will be scaling the game down to function solely in an HTML environment. This will enable me to provide a mobile version that works on all phones with a browser and keep the feature set the same for both phone and web.
I have begun to refactor pieces of the code and am making heavy use of jquery for the character creation screen.
11/30/2010
5/31/2010
With the release of Silverlight 4 RC the Windows Phone 7 developer tools I wanted to take a stab at building a Silverlight application for both the web and phone to see what kind of differences there are between the two. Except for the inability to use the ChildWindow I was able to build out controls and share them between the two applications. The main differences were in the MainPage.xaml that is created, along with the default styles, when you create a new Silverlight application for the web and Windows Phone 7. Of course,
I decided to create a game (called ShapeAttack) to see how it would perform on the phone emulator. Sad to say the performance on the emulator is very poor but I would imagine that it would be better on the physical device but as I do not own a Windows Phone 7 yet the emulator has to do for now. For that reason I would recommend doing this parallel type of development so you can actually test your application.
What I did was create all of the game code in UserControls, including the main game surface, then I linked the files from the standard Silverlight project into the phone project.
The game is very simple and kind of cheesy :D, just click on the shapes to destroy them. And of course you can download the source code for ShapeAttack(2.8MB) or play ShapeAttack online.
3/27/2010
Frank LaVigne has posted a very useful tool to download all of the MIX 10 session content.
3/17/2010
Here is a video of the Pirates game:
[youtube http://www.youtube.com/watch?v=b9LHFS1dKWg&hl=en_US&fs=1&color1=0x2b405b&color2=0x6b8ab6]3/11/2010
On the heels of some great posts by Bill Reiss on Sprites Part 1 and Sprites Part 2 in Silverlight I wanted to post some general base sprite classes that I use. The classes are intended to be used with the SilverSprite framework.
These classes all exist in an assembly I lovingly call the “Shady Engine” (to explain the namespaces)
The base class I used is ingeniously called Sprite. It implements an interface called ISprite. I added the interface in order to create an interface called IPlayer that the main Game class uses.
ISprite.cs
using System.Windows;using Microsoft.Xna.Framework;namespace Shady.Sprites{ public interface ISprite { ISprite Owner { get; set; } Vector2 Position { get; set; } double Rotation { get; set; } System.Windows.Point Scale { get; set; } double Width { get; set; } double Height { get; set; } Rect Bounds { get; } bool IsActive { get; set; } }}.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt{ background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }
And here is the Sprite.cs file:
using System;using System.Windows;using System.Windows.Controls;using System.Windows.Markup;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Shapes;using Microsoft.Xna.Framework;namespace Shady.Sprites{ [TemplatePart(Name = PART_RootElement, Type = typeof(Canvas))] [TemplatePart(Name = PART_ContentElement, Type = typeof(ContentControl))] [TemplatePart(Name = PART_DebugCenter, Type = typeof(Ellipse))] [ContentProperty("Content")] public class Sprite : Control, ISprite { public const string PART_RootElement = "PART_RootElement"; public const string PART_ContentElement = "PART_ContentElement"; public const string PART_DebugCenter = "PART_DebugCenter"; protected Canvas RootElement { get; set; } protected ContentControl ContentElement { get; set; } protected Ellipse DebugCenterElement { get; set; } protected TranslateTransform TranslateTransform { get; set; } protected RotateTransform RotateTransform { get; set; } protected ScaleTransform ScaleTransform { get; set; } protected double HalfWidth = 0; protected double HalfHeight = 0; public ISprite Owner { get; set; } public object Content { get { return (object)GetValue(ContentProperty); } set { SetValue(ContentProperty, value); } } public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(object), typeof(Sprite), new PropertyMetadata(null)); public bool Debug { get { return (bool)GetValue(DebugProperty); } set { SetValue(DebugProperty, value); } } public static readonly DependencyProperty DebugProperty = DependencyProperty.Register("Debug", typeof(bool), typeof(Sprite), new PropertyMetadata(false, new PropertyChangedCallback(Sprite.OnDebugPropertyChanged))); private static void OnDebugPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { var sprite = obj as Sprite; if (sprite == null) return; if (sprite.DebugCenterElement != null) sprite.DebugCenterElement.Visibility = (bool)e.NewValue ? Visibility.Visible : Visibility.Collapsed; } public Vector2 Position { get { var x = (double)GetValue(Canvas.LeftProperty); var y = (double)GetValue(Canvas.TopProperty); return new Vector2((float)x, (float)y); } set { SetValue(Canvas.LeftProperty, (double)value.X); SetValue(Canvas.TopProperty, (double)value.Y); } } public virtual double Rotation { get { return this.RotateTransform.Angle; } set { this.RotateTransform.Angle = value; } } public System.Windows.Point Scale { get { return new System.Windows.Point(this.ScaleTransform.ScaleX, this.ScaleTransform.ScaleY); } set { this.ScaleTransform.ScaleX = value.X; this.ScaleTransform.ScaleY = value.Y; } } public new double Width { get { return base.Width; } set { base.Width = value; HalfWidth = Width * 0.5; TranslateTransform.X = -HalfWidth; if (this.DebugCenterElement != null) Canvas.SetLeft(this.DebugCenterElement, HalfWidth); } } public new double Height { get { return base.Height; } set { base.Height = value; HalfHeight = Height * 0.5; TranslateTransform.Y = -HalfHeight; if (this.DebugCenterElement != null) Canvas.SetTop(this.DebugCenterElement, HalfHeight); } } public Rect Bounds { get { Vector2 position = this.Position; return new Rect(position.X - HalfWidth, position.Y - HalfHeight, this.Width, this.Height); } } private WriteableBitmap _bitmap; protected internal virtual WriteableBitmap Bitmap { get { if (_bitmap == null && this.ContentElement != null) { var content = this.ContentElement.Content; if (content != null && content is Image) { _bitmap = new WriteableBitmap((int)this.Width, (int)this.Height); _bitmap.Render((content as Image), new TranslateTransform()); _bitmap.Invalidate(); } } return _bitmap; } } private bool _isActive = true; public bool IsActive { get { return _isActive; } set { _isActive = value; this.Visibility = _isActive ? Visibility.Visible : Visibility.Collapsed; } } public Sprite() { this.DefaultStyleKey = typeof(Sprite); this.TranslateTransform = new TranslateTransform(); this.RotateTransform = new RotateTransform(); this.ScaleTransform = new ScaleTransform(); } public override void OnApplyTemplate() { base.OnApplyTemplate(); this.RootElement = GetTemplateChild(PART_RootElement) as Canvas; this.ContentElement = GetTemplateChild(PART_ContentElement) as ContentControl; this.DebugCenterElement = GetTemplateChild(PART_DebugCenter) as Ellipse; if (DebugCenterElement != null && !Double.IsNaN(this.Width) && !Double.IsNaN(this.Height)) { Canvas.SetLeft(DebugCenterElement, HalfWidth - 1.5); Canvas.SetTop(DebugCenterElement, HalfHeight - 1.5); } if (this.RootElement != null) { var group = new TransformGroup(); group.Children.Add(TranslateTransform); group.Children.Add(RotateTransform); group.Children.Add(ScaleTransform); this.RootElement.RenderTransform = group; this.RootElement.RenderTransformOrigin = new System.Windows.Point(0, 0); // At 0,0 because the translate transform positions the sprite. } this.Initialize(); } public virtual void Initialize() { } public virtual void Update(GameTime gameTime) { } public virtual void Draw(GameTime gameTime) { } /// <summary> /// Re-initializes the sprite. /// </summary> public virtual void Reset() { this.IsActive = true; this.Owner = null; } protected static void OnDependencyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { var sprite = obj as Sprite; if (sprite == null) return; sprite.Initialize(); } }}.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt{ background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }
Because Sprite is a templated control there is also some XAML to go along with it (You will need to place this in a themes/generic.xaml file):
<Style TargetType="sprites:Sprite"> <Setter Property="Background" Value="{x:Null}"></Setter> <Setter Property="Foreground" Value="{x:Null}"></Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="sprites:Sprite"> <Canvas x:Name="PART_RootElement" Background="{TemplateBinding Background}"> <ContentControl x:Name="PART_ContentElement"/> <Ellipse x:Name="PART_DebugCenter" Width="3" Height="3" Fill="Red" Visibility="Collapsed"/> </Canvas> </ControlTemplate> </Setter.Value> </Setter> </Style>.csharpcode, .csharpcode pre{ font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/}.csharpcode pre { margin: 0em; }.csharpcode .rem { color: #008000; }.csharpcode .kwrd { color: #0000ff; }.csharpcode .str { color: #006080; }.csharpcode .op { color: #0000c0; }.csharpcode .preproc { color: #cc6633; }.csharpcode .asp { background-color: #ffff00; }.csharpcode .html { color: #800000; }.csharpcode .attr { color: #ff0000; }.csharpcode .alt{ background-color: #f4f4f4; width: 100%; margin: 0em;}.csharpcode .lnum { color: #606060; }
That is my basic Sprite class, I will post my animated sprite class next.
3/10/2010
One of the first games I started building in Silverlight I called Pirates! Since working on Perenthia and various other tasks I have not re-visited the game for a long time. I really would like to get this game finished so I have to decided to spend some time working on it. I hope to include some videos soon that show the game in varied stages of development. The first screen shot displays what currently exists after implementing some path finding and the SilverSprite library:
Here is the post from the game blog: Pirates Game in Silverlight
3/10/2010
Mad Laumann has a new post up about the development progress of his game Little Longhorn, a tower defense game written for XNA and Silverlight using the SilverSprite framework. I have been following his progress with the game and have been able to play the early versions of it (both XNA and Silverlight) and have found it quite fun and challenging. The game has grown quite a bit over the last few months with game play and graphics improving all the time. Needless to say Mads is becoming an authority on XNA/Silverlight combination platform development so be sure to check out his blog A Silverlight Playground.
2/1/2010
For those of you out there wanting to get into Silverlight development but just do not know where to start, Frank LaVigne and I have just about completed our book titled Microsoft Silverlight 4 Business Application Development – Beginner’s Guide(link to pre-order). This book will be ideal for current Windows or ASP.NET developers who want to learn Silverlight and because the book is centered on Business Application Development we will empower you with knowledge so you can recommend Silverlight for the next business project at your company.
Gaining the basics required to develop in Silverlight, make use of data binding, WCF and RIA Services you will be prepared to lead the Silverlight charge at your company. Be warned, once you start developing in Silverlight you will have a hard time going back to normal ASP.NET development.
1/18/2010
I have been working on a multi-player library for Silverlight games over the past few months, well, off and on working on it while continuing Perenthia development.
I hope to be able to get the code on CodePlex before too much longer along with a tutorial on how to use it and a demo game.
The library will start with Duplex services but I do have plans to implement sockets in the future. Silverlight’s duplex and socket support is sufficient for RPGs and RTS or really any kind of turn based game but until Silverlight can support UDP sockets it is probably not suited to real time action games.
The goal is that the library can be used with any game engine such as SilverSprite or PlayBits and that it will provide easy to setup and use networking capabilities for your game.
The library is coming along well and I am building a demo game to help with implementation and testing that I hope to have finished in the next week or so.
8/9/2009
I thought I would share one of the core concepts I implemented in Perenthia that has been working quite well. Early on in development I decided to create templates and instances in regards to game objects. A template would define common, unchangable properties of an object whereas an instance would be the opposite. An example would be a Sword; there is a template for a Sword that defines its damage value, price, required skill level, etc.
I have one table that stores all the objects for the game. Since objects being actively used are loaded into memory and the table is indexed for those queries it runs pretty fast. Since I store all of the objects into one table I make heavy use of the XML data type in SQL 2005 to provide a bunch of XML Serialized Key/Value pairs that define the properties of an object. On the code side of things a dictionary provides the underlying data store for properties of the object. For instance, the Sword example above has a Power property that is used in combination with the wielder's skill in swords to determine damage. The Power property just fetches the Power value from the properties dictionary using the property name. This works a lot like the dependency properties, just my collection serializes to XML for database storage. When an object is loaded from the database the xml from the template is retrieved and added to the object, then the object instance xml is dropped into the object, overriding any template properties of the same name.
This property dictionary also allows me to store whether or not the property belongs to a template or instance. If the property belongs to a template then when the instance is serialized and saved to the database those template properties are excluded. Likewise saving a template does not serialize and save the instance properties. This structure allows for a lot of flexibility with my objects and has been working out quite well. I probably would not have used serialization and xml the way I am if I was doing a typical call to the database qith each request as the extra overhead would not have been worth the effort. However, since my objects are loaded into memory and my saves are background threaded I experience a lot of benefit of an easy to use object.
I shuld note that players character data is stored in a separate table from the core objects table. It follows the same design buy allows me to migrate and change data in the objects table without overwriting player data, since players are essentially the same as mobiles.
5/15/2009
I have been working to modify the tool I wrote for building the Perenthia world. The tool started life as a WinForms application, migrated to a WPF application but is now a part of the Silverlight Client UI. Since all of the actions are role based and controlled with permission on the server having it in the main client UI allows me to make use of all of the framework already in place for Perenthia.
The world builder UI connects to the server via WCF services tailored for world building. What this will also give me is the ability to open up the world builder to allow users to create custom dungeons, towns, etc.
Some features of the world builder are a background map of the world so I know where to place rooms, zoom capabilities that scale the map, draggable map and the ability to drag items, creatures, NPCs, etc. onto rooms on the map and configure the properties of each.
4/29/2009
Here is a draggable Window control I wrote in Silverlight for Perenthia. A screen shot of the Window being used can be seen at http://cameronalbert.com/post/2008/11/19/Silverlight-Game-Controls.aspx
Here is the XAML from my generics.xaml file:
<!-- Default style for Lionsguard.Window -->
<Style TargetType="lg:Window"><Setter Property="Background" Value="#99000000" /><Setter Property="Foreground" Value="#FFFFFFFF" /><Setter Property="BorderBrush" Value="#FFC38312" /><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="lg:Window"><Grid x:Name="RootElement"><Border HorizontalAlignment="Stretch" Margin="0,24,0,0" Padding="4,4,4,4" VerticalAlignment="Stretch" BorderThickness="2,2,2,2" CornerRadius="0,0,4,4"><ContentControl x:Name="ContentElement" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"/></Border><Border Height="24" HorizontalAlignment="Stretch" Margin="0,0,0,0" VerticalAlignment="Top" CornerRadius="4,4,0,0" BorderThickness="2,2,2,0" x:Name="TitleBarElement" /><Button Height="20" HorizontalAlignment="Right" Margin="0,3,3,0" VerticalAlignment="Top" Width="20" Content="X" FontWeight="Bold" FontFamily="Trebuchet MS" FontSize="14" x:Name="CloseButtonElement"/><TextBlock Height="20" Margin="8,3,27,0" VerticalAlignment="Top" Text="Title" TextWrapping="Wrap" x:Name="TitleLabelElement" FontFamily="Georgia" FontSize="16" FontWeight="Bold" HorizontalAlignment="Left"/></Grid></ControlTemplate></Setter.Value></Setter></Style>
And here is the code for the class:
using System;
using System.Collections.Generic;
using System.Net;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace Lionsguard
{
[TemplatePart(Name = "RootElement", Type = typeof(FrameworkElement))]
[TemplatePart(Name = "ContentElement", Type = typeof(ContentControl))]
[TemplatePart(Name = "TitleBarElement", Type = typeof(Border))]
[TemplatePart(Name = "CloseButtonElement", Type = typeof(Button))]
[TemplatePart(Name = "TitleLabelElement", Type = typeof(TextBlock))]
[ContentProperty("Content")]
public class Window : Control
{
public FrameworkElement RootElement { get; set; }
private ContentControl ContentElement { get; set; }
private Border TitleBarElement { get; set; }
private Button CloseButtonElement { get; set; }
private TextBlock TitleLabelElement { get; set; }
private Point MousePosition { get; set; }
/// <summary>
/// Gets or sets the Content of the Window.
/// </summary>
public UIElement Content
{
get { return (UIElement)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(UIElement), typeof(Window), new PropertyMetadata(null, new PropertyChangedCallback(Window.OnContentPropertyChanged)));
private static void OnContentPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
(obj as Window).SetControlValues();
}
/// <summary>
/// Gets or sets the Title displayed in the title bar of the window.
/// </summary>
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(Window), new PropertyMetadata("Title", new PropertyChangedCallback(Window.OnTitlePropertyChanged)));
private static void OnTitlePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
(obj as Window).SetControlValues();
}
/// <summary>
/// Gets or sets a value indicating whether or not the close button of the title bar should be displayed.
/// </summary>
public bool ShowCloseButton
{
get { return (bool)GetValue(ShowCloseButtonProperty); }
set { SetValue(ShowCloseButtonProperty, value); }
}
public static readonly DependencyProperty ShowCloseButtonProperty = DependencyProperty.Register("ShowCloseButton", typeof(bool), typeof(Window), new PropertyMetadata(true, new PropertyChangedCallback(Window.OnShowCloseButtonPropertyChanged)));
private static void OnShowCloseButtonPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
(obj as Window).SetControlValues();
}
/// <summary>
/// Gets a value indicating whether or not the window is currently open and visible.
/// </summary>
public bool IsOpen
{
get { return this.Visibility == Visibility.Visible; }
}
/// <summary>
/// An event that is raised when the window is closed.
/// </summary>
public event EventHandler Closed = delegate { };
/// <summary>
/// Initializes a new instance of the Lionsguard.Window class.
/// </summary>
public Window()
{
this.DefaultStyleKey = typeof(Window);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.RootElement = base.GetTemplateChild("RootElement") as FrameworkElement;
this.ContentElement = base.GetTemplateChild("ContentElement") as ContentControl;
this.TitleBarElement = base.GetTemplateChild("TitleBarElement") as Border;
this.CloseButtonElement = base.GetTemplateChild("CloseButtonElement") as Button;
this.TitleLabelElement = base.GetTemplateChild("TitleLabelElement") as TextBlock;
if (this.RootElement != null)
{
this.RootElement.MouseLeftButtonDown += new MouseButtonEventHandler(OnRootElementMouseLeftButtonDown);
this.TitleBarElement.MouseLeftButtonDown += new MouseButtonEventHandler(OnTitleBarMouseLeftButtonDown);
this.TitleBarElement.MouseEnter += new MouseEventHandler(OnTitleBarMouseEnter);
this.TitleBarElement.MouseLeave += new MouseEventHandler(OnTitleBarMouseLeave);
this.TitleLabelElement.MouseLeftButtonDown += new MouseButtonEventHandler(OnTitleBarMouseLeftButtonDown);
this.TitleLabelElement.MouseEnter += new MouseEventHandler(OnTitleBarMouseEnter);
this.TitleLabelElement.MouseLeave += new MouseEventHandler(OnTitleBarMouseLeave);
this.CloseButtonElement.Click += new RoutedEventHandler(OnCloseButtonClick);
this.SetControlValues();
}
}
private void SetControlValues()
{
if (this.ContentElement != null && this.Content != null)
{
this.ContentElement.Content = this.Content;
}
if (this.TitleLabelElement != null)
{
this.TitleLabelElement.Text = this.Title;
ToolTipService.SetToolTip(this.TitleLabelElement, this.Title);
}
if (this.CloseButtonElement != null)
{
if (this.ShowCloseButton) this.CloseButtonElement.Visibility = Visibility.Visible;
else this.CloseButtonElement.Visibility = Visibility.Collapsed;
}
}
private void OnRootElementMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// Set the Z-Index of the window to the top most position.
this.BringToFront();
}
private void OnCloseButtonClick(object sender, RoutedEventArgs e)
{
this.Close();
}
private void OnTitleBarMouseLeave(object sender, MouseEventArgs e)
{
this.Cursor = Cursors.Arrow;
}
private void OnTitleBarMouseEnter(object sender, MouseEventArgs e)
{
this.Cursor = Cursors.Hand;
}
private void OnTitleBarMouseMove(object sender, MouseEventArgs e)
{
Point position = e.GetPosition(null);
// Prevent the mouse from moving outside the bounds of the parent canvas.
if (position.X <= 30) position.X = 30;
if (position.Y <= 30) position.Y = 30;
Canvas parent = this.Parent as Canvas;
if (parent != null)
{
if (position.X >= (parent.Width - 30)) position.X = parent.Width - 30;
if (position.Y >= (parent.Height - 30)) position.Y = parent.Height - 30;
}
System.Diagnostics.Debug.WriteLine("position = {0}", position);
double deltaX = position.X - this.MousePosition.X;
double deltaY = position.Y - this.MousePosition.Y;
Point newPosition = new Point(
((double)this.GetValue(Canvas.LeftProperty)) + deltaX,
((double)this.GetValue(Canvas.TopProperty)) + deltaY);
this.SetValue(Canvas.LeftProperty, newPosition.X);
this.SetValue(Canvas.TopProperty, newPosition.Y);
this.MousePosition = position;
}
private void OnTitleBarMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
((UIElement)sender).ReleaseMouseCapture();
this.Cursor = Cursors.Arrow;
this.TitleBarElement.MouseLeftButtonUp -= new MouseButtonEventHandler(OnTitleBarMouseLeftButtonUp);
this.TitleBarElement.MouseMove -= new MouseEventHandler(OnTitleBarMouseMove);
this.TitleLabelElement.MouseLeftButtonUp -= new MouseButtonEventHandler(OnTitleBarMouseLeftButtonUp);
this.TitleLabelElement.MouseMove -= new MouseEventHandler(OnTitleBarMouseMove);
}
private void OnTitleBarMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.MousePosition = e.GetPosition(null);
this.Cursor = Cursors.Hand;
((UIElement)sender).CaptureMouse();
this.TitleBarElement.MouseLeftButtonUp += new MouseButtonEventHandler(OnTitleBarMouseLeftButtonUp);
this.TitleBarElement.MouseMove += new MouseEventHandler(OnTitleBarMouseMove);
this.TitleLabelElement.MouseLeftButtonUp += new MouseButtonEventHandler(OnTitleBarMouseLeftButtonUp);
this.TitleLabelElement.MouseMove += new MouseEventHandler(OnTitleBarMouseMove);
}
/// <summary>
/// Causes the current window to be re-z-indexed to the top most window.
/// </summary>
protected void BringToFront()
{
// Search the top most "Window" and swap z-indexes.
Panel parent = this.Parent as Panel; // Panel is the base for Canvas, Grid and StackPanel.
if (parent != null)
{
int currentZIndex = (int)this.GetValue(Canvas.ZIndexProperty);
var child = (from c in parent.Children where c is Window select c as Window).OrderByDescending(c => (int)c.GetValue(Canvas.ZIndexProperty)).FirstOrDefault();
if (child != null)
{
int topZIndex = (int)child.GetValue(Canvas.ZIndexProperty);
if (topZIndex == 0) topZIndex = 1; // If the value has not been set then just default it to 1.
if (topZIndex > currentZIndex)
{
this.SetValue(Canvas.ZIndexProperty, topZIndex);
child.SetValue(Canvas.ZIndexProperty, currentZIndex);
}
}
}
}
public void Show()
{
this.Visibility = Visibility.Visible;
// If content is present show that as well.
if (this.Content != null) this.Content.Visibility = Visibility.Visible;
}
public void Close()
{
this.Visibility = Visibility.Collapsed;
// If content is present hide that as well.
if (this.Content != null) this.Content.Visibility = Visibility.Collapsed;
this.Closed(this, EventArgs.Empty);
}
public void ToggleWindow()
{
if (this.Visibility == Visibility.Visible)
{
this.Close();
}
else
{
this.Show();
}
}
}
}
This is how I am using it within the Pernethia UI (The content of window is actually the Character Sheet as displayed in the screenshot from above):
Namespace: xmlns:Lionsguard="clr-namespace:Lionsguard;assembly=Lionsguard.Silverlight"
<Lionsguard:Window Height="375" x:Name="diagCharacterSheet" Visibility="Collapsed" Width="634" Canvas.Left="8" Canvas.Top="19" Title="Character Sheet" Style="{StaticResource WindowStyle}" d:IsLocked="True">
<Perenthia_Dialogs:CharacterSheetDialog x:Name="diagCharacterSheetContent" SkillChanged="diagCharacterSheetContent_SkillChanged"/></Lionsguard:Window>
Enjoy!
4/22/2009
I wrote some classes for handling drag and drop operations in Silverlight that I use within Perenthia. I thought I would go ahead and share them since I found them to be very useful in getting drag and drag working quickly in new projects.
The Drag and Drag classes consist of two interfaces (IDroppable, IDropContainer) and a manager class (DragDropManager). The interfaces are defined as such:
namespace Lionsguard
{
/// <summary>
/// Defines an object that can be dragged and dropped via the DragDropManager.
/// </summary>
public interface IDroppable
{
/// <summary>
/// An event that is raised when the IDroppable item begins the drag operation.
/// </summary>
event BeginDragEventHandler BeginDrag;
/// <summary>
/// Gets the UIElement that will server as the cursor replacement when dragging the current object.
/// </summary>
/// <returns></returns>
UIElement GetDragCursor();
}
/// <summary>
/// Defines an object that accept IDroppable items as part of a drop operation.
/// </summary>
public interface IDropContainer
{
/// <summary>
/// Handles the drop of the specified IDroppable item onto the container.
/// </summary>
/// <param name="item"></param>
void Drop(IDroppable item);
}
}
The DragDropManager (and and associated delegate and event args class) are defined as such:
namespace Lionsguard
{
/// <summary>
/// Represents a class used to manage drag and drop items.
/// </summary>
public class DragDropManager
{
private Panel Host { get; set; }
private Point MousePosition { get; set; }
private UIElement DragCursor { get; set; }
private bool HasProcessedEndDrag { get; set; }
private IDroppable Item { get; set; }
/// <summary>
/// Initializes a new instance of the DragDropManager class.
/// </summary>
/// <param name="host">The hosting panel where the drag cursor instance will be added.</param>
public DragDropManager(Panel host)
{
this.Host = host;
this.Host.LostFocus += new RoutedEventHandler(OnHostLostFocus);
}
private void OnHostLostFocus(object sender, RoutedEventArgs e)
{
EndDrag(false);
}
private void OnDragCursorLostMouseCapture(object sender, MouseEventArgs e)
{
EndDrag(false);
}
private void OnDragCursorLostFocus(object sender, RoutedEventArgs e)
{
EndDrag(false);
}
private void OnDragCursorMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
((UIElement)sender).ReleaseMouseCapture();
this.MousePosition = e.GetPosition(null);
this.EndDrag(true);
}
private void OnDragCursorMouseMove(object sender, MouseEventArgs e)
{
Point position = e.GetPosition(null);
double deltaX = position.X - this.MousePosition.X;
double deltaY = position.Y - this.MousePosition.Y;
Point newPosition = new Point(
((double)this.DragCursor.GetValue(Canvas.LeftProperty)) + deltaX,
((double)this.DragCursor.GetValue(Canvas.TopProperty)) + deltaY);
this.DragCursor.SetValue(Canvas.LeftProperty, newPosition.X);
this.DragCursor.SetValue(Canvas.TopProperty, newPosition.Y);
this.MousePosition = position;
}
/// <summary>
/// Begins a drag operation for the specified IDroppable instance starting at the specified mousePosition.
/// </summary>
/// <param name="droppable">The IDroppable instance to being dragging.</param>
/// <param name="mousePosition">The Point at which the drag operation will begin.</param>
public void BeginDrag(IDroppable droppable, Point mousePosition)
{
if (droppable != null && this.Host != null)
{
System.Diagnostics.Debug.WriteLine("Begin Drag...");
this.Item = droppable;
this.DragCursor = droppable.GetDragCursor();
this.DragCursor.LostFocus += new RoutedEventHandler(OnDragCursorLostFocus);
this.DragCursor.LostMouseCapture += new MouseEventHandler(OnDragCursorLostMouseCapture);
// Add the drag cursor to the host control and set the z-index very high.
this.DragCursor.SetValue(Canvas.ZIndexProperty, 5000);
this.Host.Children.Add(this.DragCursor);
this.Host.Cursor = Cursors.Hand;
this.MousePosition = mousePosition;
this.DragCursor.SetValue(Canvas.LeftProperty, this.MousePosition.X);
this.DragCursor.SetValue(Canvas.TopProperty, this.MousePosition.Y);
this.DragCursor.Visibility = Visibility.Visible;
this.HasProcessedEndDrag = false;
this.DragCursor.CaptureMouse();
this.DragCursor.MouseLeftButtonUp += new MouseButtonEventHandler(OnDragCursorMouseLeftButtonUp);
this.DragCursor.MouseMove += new MouseEventHandler(OnDragCursorMouseMove);
}
}
private void EndDrag(bool processDrop)
{
// This prevents the mouse up event or the end drag event firing more than once from causing the
// events to bubble up and the drop be handled more than once.
System.Diagnostics.Debug.WriteLine("End Drag...");
if (this.HasProcessedEndDrag) return;
this.HasProcessedEndDrag = true;
// Reshow the cursor and hide the source image.
this.Host.Cursor = Cursors.Arrow;
// Hide the drag cursor.
if (this.DragCursor != null)
{
this.DragCursor.Visibility = Visibility.Collapsed;
this.DragCursor.LostFocus -= new RoutedEventHandler(OnDragCursorLostFocus);
this.DragCursor.LostMouseCapture -= new MouseEventHandler(OnDragCursorLostMouseCapture);
this.DragCursor.MouseLeftButtonUp -= new MouseButtonEventHandler(OnDragCursorMouseLeftButtonUp);
this.DragCursor.MouseMove -= new MouseEventHandler(OnDragCursorMouseMove);
}
if (processDrop)
{
System.Diagnostics.Debug.WriteLine("Process Drop...");
// Search within host for an IDropContainer instance within the area of the current mouse position.
var container = (from v in VisualTreeHelper.FindElementsInHostCoordinates(this.MousePosition, this.Host)
where (v as IDropContainer) != null
select v as IDropContainer).FirstOrDefault();
if (container != null)
{
System.Diagnostics.Debug.WriteLine("Drop...");
container.Drop(this.Item);
}
}
}
}
/// <summary>
/// Provides a delegate for handling IDroppable.BeginDrag events.
/// </summary>
/// <param name="sender">The IDroppable initiating the drag operation.</param>
/// <param name="e">Event data for the operation.</param>
public delegate void BeginDragEventHandler(object sender, BeginDragEventArgs e);
/// <summary>
/// Represents event data for a BeginDrag event.
/// </summary>
public class BeginDragEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the IDroppable initiating the event.
/// </summary>
public IDroppable Droppable { get; set; }
/// <summary>
/// Gets or sets the Point at which the drag operation should begin.
/// </summary>
public Point MousePosition { get; set; }
}
}
private void AdminScreen_Loaded(object sender, RoutedEventArgs e){_dragDropManager = new DragDropManager(LayoutRoot);}
avatarItem.BeginDrag += new BeginDragEventHandler(OnDroppableBeginDrag);
When the IDroppable raises the BeginDrag event, on a left mouse button click, this method is called which causes the DragDropManager to being the drag operation for the current IDroppable.
private void OnDroppableBeginDrag(object sender, BeginDragEventArgs e)
{
if (_dragDropManager != null)
{
_dragDropManager.BeginDrag(e.Droppable, e.MousePosition);
}
}
When an IDroppable is dropped onto the very same control this method is executed:
#region IDropContainer Members
public void Drop(IDroppable item)
{
// Do stuff when the object is dropped...
}
#endregion
This is pretty much all there is to it.
4/21/2009
Microsoft announced the release of Internet Explorer 8 this morning, it is available at http://www.microsoft.com/ie8. With this new browser release Microsoft will position themselves on the top of the browser stack again. They have incorporated a lot of safe browsing and ease of browsing features into the new version that will make it the paramount browser for the normal everyday web surfer.
EDIT: And then I started using it and now am back to using Chrome...
3/19/2009
I am flying out to MIX in a few hours and I am really excited. This will be my first time at MIX and I am looking forward to all the great sessions regarding Silverlight 3, mixing it up with other industry professionals and just hanging out in Las Vegas. :)
3/17/2009
I created this snippet for Dependency Properties in Silverlight. If you are creating custom Silverlight controls you might find this snippet useful. I stored my code snippets here "My DocumentsVisual Studio 2008Code SnippetsVisual C#My Code Snippets" so all you need to do is copy the file to this location. Visual Studio should load the snippet the next time you start it up. If it does not then you can go into "Tools", "Code Snippets Manager" and load it up manually.
Now when you type "propsl" and hit tab you will get a stubbed out property like below, where tabbing through the items allows you to change the type and name of the property, set the owning class and provide a default value.
public int MyProperty
{
get { return (int)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(int), typeof(MyClass),
new PropertyMetadata(null, new PropertyChangedCallback(MyClass.OnMyPropertyPropertyChanged)));
private static void OnMyPropertyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
}
Here is the code for the snippet and a link to the file:
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>propsl</Title>
<Shortcut>propsl</Shortcut>
<Description>Code snippet for an automatically implemented dependency property in Silverlight.</Description>
<Author>Cameron Albert</Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal>
<ID>type</ID>
<ToolTip>Property type</ToolTip>
<Default>int</Default>
</Literal>
<Literal>
<ID>property</ID>
<ToolTip>Property name</ToolTip>
<Default>MyProperty</Default>
</Literal>
<Literal>
<ID>ownerClass</ID>
<ToolTip>Owner Class</ToolTip>
<Default>MyClass</Default>
</Literal>
<Literal>
<ID>defaultValue</ID>
<ToolTip>Default Value</ToolTip>
<Default>null</Default>
</Literal>
</Declarations>
<Code Language="csharp">
<![CDATA[public $type$ $property$
{
get { return ($type$)GetValue($property$Property); }
set { SetValue($property$Property, value); }
}
public static readonly DependencyProperty $property$Property = DependencyProperty.Register("$property$", typeof($type$), typeof($ownerClass$), new PropertyMetadata($defaultValue$, new PropertyChangedCallback($ownerClass$.On$property$PropertyChanged)));
private static void On$property$PropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
}
$end$]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
Silverlight Dependency Property Snippet
3/9/2009
Perenthia has a fan site, it is called Beyond Angarath, Angarath being a primary city in within the game world. This is exciting for me and extremely cool! It is very rewarding to see people enjoying your creation enough to put work into a fan site for that creation. The forums have been getting more active lately with folks trying out the Alpha and giving lots of feedback and suggestions. I hope stay close with the community in building this game; I have a vision for it but ultimately I want people to play it and enjoy playing it.
2/19/2009
I am hoping to start writing some articles about building multi-player games in Silverlight. I am planning on charting my development progress and ideas in my design and development of Perenthia.
Silverlight is a great platform for .NET developers to be able to write multi-player browser based games. You get a great tool for building a UI, animations, sounds, etc. and can program everything in your .NET language of choice. Silverlight 3 is supposed to support 3D so that will open it up even more for some great games.
Over the next few months, in between working on Perenthia, I am going to work on the first of a series of articles outlining how I built Perenthia. I will start with the Server component, outlining the core communication architecture and design decisions.
Keep checking back for more information.
2/16/2009
I am planning on attending MIX09 this year, already registered actually. I missed it last year but wanted to be present this time around for the new Silverlight announcements and to meet some of the Silverlight team. Anyway, if you are going I will be there :)
1/27/2009
I uploaded the Perenthia Armorial Version 1.0 to the Perenthia Alpha site tonight. The Perenthia Armorial is a way to provide external access to Perenthia information. The first version includes the Player Bar feature, where you pass a player name to the service and renders back HTML with player details.
The call to http://alpha.perenthia.com/Armorial/Armorial.ashx?n=Aldarian renders the following:
1/18/2009
While developing Perenthia I tried several different communication formats from sending JSON serialized objects back and forth to sending byte arrays containing mostly integers indicating what should be loaded and displayed, what commands to execute etc. What I eventually settled on was a bit of a hybrid. I've created a simple set of "tags" that can be sent to and from the server. The tags are nothing more than pipe delimited strings contained within curly braces and there are a limited number of tags that provide simplicity yet flexibility with the data that is transmitted. The tags are represented in C# by tag objects allow me to create them, query them, etc.
A simple command tag for the SAY command might look like this: {CMD|SAY|"Hello World!"}
I wrote a custom tag writer class that parses the tag objects into strings to be sent to the client and likewise a tag reader that reads the strings sent from the client and parses them into tag objects.
The client can only send commands to the server but the server sends commands, messages and objects to the client. The commands are all the same, the CMD text then the one word name of the command and then a list of arguments, messages are system, chat and tell messages but objects have a bit more information. For instance, an object tag encompasses several different types starting with a base ACTOR, a PLACE, a PLAYER and a PROP or property. The ACTOR, PLACE and PLAYER tags all define the ID, Name and Descriptions of the objects, with some additional data per type but the PROP tag defines the object id of its owner and a name/value pair. An example of a PLAYER tag with properties for x, y and z coordinates might be:
{OBJ|PLAYER|3756|"Aldarian"|"My character description..."}
{OBJ|PROP|3756|"X"|45}
{OBJ|PROP|3756|"Y"|26}
{OBJ|PROP|3756|"Z"|0}
The client can find and parse the player tag and then find the properties associated with that player instance. The way the server works now it will send the full player tag once logged in and a character is selected and then from then on out it just sends property updates.
Using this type of tag structure allows quite a bit of flexibility for the client. I could choose to write a very simple client that only displays MSG tags, much like a MUD, I could write a simple 2D interface client and display and move sprites about the map using the properties from the server, etc. Once Perenthia is up and running I will probably post some information on the tags and how to talk with the server should someone feel the need to write their own client. :)
1/5/2009
For those waiting, Perenthia is almost ready for alpha. I have a few more things to complete and then the first phase of the Alpha will be ready. Hopefully you have had time to look over the various races, attributes and skills on the Perenthia web site and will be ready to create a character and begin your journey.
Here is a list of the remaining tasks I need to complete before Alpha, this does not include testing and fixing bugs with these features, just implementing them:
I thought I would include a couple of screen shots along with this update. The first is a shot of the map window of the world builder tool I wrote for managing the places and things within the Perenthia world. This is the shot of the Alpha testing area, a small village called "Delcor". This view is actually a windows forms application with a WPF (ElementHost) control for rendering the map. The little red dots are NPCs that I can actually drag around into other rooms on the map after I create them, makes for moving the monsters in the dungeon a lot easier. :)
This second shot is the game user interface showing the same map that I was working on in the screen shot above.
12/24/2008
While building the Silverlight UI for Perenthia I have been building some re-usable game controls that I plan on posting before too long. One of the controls I created is a Window control that is just a control that provides a title bar with a close button and provides a Content section to add custom controls. I needed it to provide menu windows or panels but it could be used outside of games for just about anything. The control is draggable, as long as the parent control is a Canvas and will position itself as the top window when clicked, provided other windows exist in the same parent.
The Window control is featured in the screen shot below, the "Spellbook", "Character Sheet" and "Adenturer's"** are the windows. They contain custom controls that determine the content of the windows. The small icons at the very bottom of the screen shot are Slot controls that allow for a background image, primary image and object Item to be set on them. They also respond to clicks so click events can be handled on them. The ChatPanel under the windows is also a re-usable control and allows for adding any type of UIElement as content for the main chat area, not just text.
I hope to get these controls commented and the source up soon.
** The "Adventurer's" window is actually "Adventurer's Backpack", have a font sizing issue :)
11/19/2008
MUD is 30 years old today. For those who don't know what MUD is, it is the forerunner to the virtual worlds and MMORPGs we love and play today.
10/20/2008
Silverlight 2 has officially been released. I've upgraded both Joust and Cameron's Dungeon as well as the Perenthia client. Work on Perenthia continues, albeit a little slow but it does continue. :)
10/14/2008
I didn't get a chance to work on Joust any more before the contest deadline and won't have enough time in the next hour and half. Anyway, as promised here is the source code:
9/8/2008
I was working on a simple sports game for the Team Zone Sports Silverlight 2 Game Contest. The rules specified a sports theme but did not specify a century or the type of sports so I created a jousting game. I haven't completely finished the game, it doesn't have everything I wanted it to have and the horse kind of drives like a car :) The dust trail behind the horse actually adds to the car effect!
The game is Silverlight 2, C# and makes use of the Farseer Physics engine.
Anyway, I am hoping to get it cleaned up a little more before the contest submission deadline but not sure that is going to happen given my current workload.
I will post the source once the submission deadline is over, I want to try and get it completely finished by then but if not I will post the source anyway.
9/6/2008
Scripting in games is not a new concept, most games do this on some level. Scripting allows game objects to execute code that is not compiled as part of the object. In the realm of roe playing games scripting gives objects the ability to react to the world around them. For instance, an object could execute script whenever a player gets near it, allowing a vender to hawk their wares, creatures to attack, etc. There are a variety of scripting languages currently being implement in games such as Lua and Python. Recently I have been researching these two for possible addition to Perenthia to provide me the ability to add custom behavior to objects and NPCs.
Edit: Thanks to some helpful insight and information from Michael Foord I was able to get an IronPython sample working. The sample adds scripting support to an object; the code is very basic but accomplishes the task. Thanks Michael!
Here is a snippet of a console app using the latest IronPython Beta4:
using System;using System.Collections;using System.Collections.Generic;using System.IO;using System.Reflection;using System.Scripting;using IronPython;using IronPython.Hosting;using IronPython.Runtime;using IronPython.Runtime.Exceptions;using Microsoft.Scripting;using Microsoft.Scripting.Hosting;namespace IronPythonGameScripting{ class Program { static void Main(string[] args) { try { Actor player = new Actor("Cam"); Actor mob = new Actor("Mob"); mob.Script = @"class Mob: def onEncounter(self, *value): value[0].Name = ""Mr Groovy"""; Console.WriteLine("Player name was '{0}'", player.Name); mob.Execute(Actor.EVENT_ON_ENCOUNTER, player); Console.WriteLine("Player name is now '{0}'", player.Name); mob.Execute("Test", player); } catch (Exception ex) { Console.WriteLine(ex.ToString()); } finally { Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } } } public static class Engine { private static ScriptEngine _engine; private static ScriptScope _scope; static Engine() { _engine = PythonEngine.CurrentEngine; _scope = _engine.CreateScope(); } public static void Initialize() { } public static void Execute(string script) { ScriptSource source = _engine.CreateScriptSourceFromString(script, SourceCodeKind.Statements); source.Execute(_scope); } public static void Execute(string script, string className, string methodName, params object[] args) { ObjectOperations ops = _engine.Operations; ScriptSource source = _engine.CreateScriptSourceFromString(script, SourceCodeKind.Statements); source.Execute(_scope); if (_scope.ContainsVariable(className)) { object @class = _scope.GetVariable(className); object instance = ops.Call(@class); if (instance != null) { if (ops.ContainsMember(instance, methodName)) { object method = ops.GetMember(instance, methodName); ops.Call(method, args); } } } } } public class Actor { public const string EVENT_ON_ENCOUNTER = "onEncounter"; public string Name { get; set; } public string Script { get; set; } public Actor(string name) { this.Name = name; } public void Execute(string methodName, params object[] args) { Engine.Execute(this.Script, "Mob", methodName, args); } }}
As it turns out I was attempting to use the IronPython libraries compiled against the Silverlight framework rather than the libraries compiled against the normal .NET framework. I was also using an older beta 1 version of the IronPython library which may have attributed to the exceptions I was seeing. Got to make sure you reference the correct assemblies when working in both .NET and Silverlight. Surprised this is the first time I've done this...
I reviewed IronPython because it has Silverlight support with the DLR but it is really designed to be used instead of C# not as a scripting language executed from within C#. With Lua though I may be able to get it working although it still seems like everything was designed to simply execute the game in lua scripts using C# as the core instead of augmenting C# classes.
I really want to just be able to raise events on the mobile objects and have instances handle those events in a custom way. I certainly don't want to compile C# on the fly either as that has its own set of issues. Compiling C# on the fly is like running an exe for each script that is executed and would be way too costly in both execution time to compile and memory consumption.
Also, since both the client and server will need to execute scripts the scripting language needs to work in both Silverlight and ASP.NET.
Perenthia currently handles commands using a Dictionary<int, Command> where the int is command value, from a list of constants, and the Command is a method pointer (delegate). When the game loads up the first time the list of commands is constructed and the methods pointers are set to methods that related to the command. An example would be a chat command. When the command comes in and is validated the method pointer is found using this dictionary and then executed in the context of the connected player. I was thinking of doing the same thing with mobiles and the actions they can take and respond to based on the world around them. It is a bit more complex with mobiles than commands since commands do a specific thing whereas every mobile could execute a different block of code for a given event. I still have some work to do on it but this will probably be what I use in place of scripting. I loose the flexibility of writing quick scripts, changing them on the fly, etc. but gain the ability to have it work within my code framework, rely on only .NET and work in both Silverlight and ASP.NET.
8/26/2008
I thought I would take a little break from working on Perenthia and take part in a Silverlight Game Contest for a sports theme game. This will push out the alpha release date a little but I need a little change of pace.
I am a little late in deciding to do this but figured I would give it a try and see if I can come up with something fun by September 8th. I have an idea for a simple game, hopefully I can make it fun to play because I know it will be fun to build.
Once the game is finished I will also post the source code.
8/23/2008
Here are a few more screen shots of the Perenthia UI. The three screen shots are dialog windows that popup as a result of clicking on icons or buttons in UI. They are use a reusable control I created that has the title bar and X button and then just has a ContentControl for the guts of the dialog. The dialog window is draggable and does the hide and show thing. I need to do some cleanup on it and then I will post the source. I will also post the source for the stat bars shown on the skills dialog screen shot.
Also, I am hoping to have some time to blog about some of what I am doing with the overall game engine. I wanted to provide a development update but haven't moved the development along all that much due to working out some networking issues and trying to clean up some of the code.
The goal for Alpha is to get a working zone complete with quests, monsters, npcs, etc. However, I may do some smaller tests with the alpha registrants before all of this is complete. I was thinking of setting up a tiny zone just for testing where players could login, create a Character and accept a quest, which would be to enter a room where enemies spawn randomly and in various numbers. This would allow me to test a lot of things in one place; Character Creation, Quests, Spawning, Combat and NPC interaction.
Anyway, here are the screen shots; this first one is the Character Sheet that will display your character stats, equipped items and skills. The empty box will show the skills once I bind them to it. :)
This next screen is the actual skills screen where you can see you skill rank and all available skills. If you have Skill Points you can increase or learn new skills from this window.
This last image is just the backpack dialog, all I have is a starting candle right now. :)
8/9/2008
As promised here is a screen shot of the main game UI, click on the image for a full size version. The top left is the current player's avatar and details. The map in the top right corner shows you the current zone, in relation to your z-index. The icons scattered around open other windows that allow you to view your character sheet, skills, spells, backpack, etc. The interface is built in Silverlight 2 Beta 2.
7/27/2008
Frank LaVigne has written a nice Silverlight 2 Cross Domain Web Proxy Utility for making any type of web calls from Silverlight 2. This a great utility if you want to serve images or outside content to your Silverlight 2 apps but do not have the ability to setup a domain policy file on the content hosts server.
3/18/2008
I've been working with Sockets in Silverlight over the past couple of days, in between getting the Pirates game to a workable Silverlight 2 state. One thing I came across today which sent me round and round was BeginInvoke. Anyone who has programmed multi-threaded applications in Windows has used this, mostly to execute code on the UI thread from events.
Anyway, controls in Silverlight 2 do not have the Invoke or BeginInvoke methods but controls do have a Dispatcher reference which lives in System.Windows.Threading. WPF programmers are probably already used to this but it was new for me. The Dispatcher allows you to check to see if you can access the current object on the current thread.
One thing to note about the Dispatcher is that it has a method called CheckAccess() that returns true if you can access the control on the current thread of false if you are not on the control's thread. The CheckAccess() method is decorated with a EditorBrowsableAttribute and has it's value set to Never. This prevents us from being able to see the method via intellisense in Visual Studio but does not prevent the method from being used. Not sure why the folks at MS wanted to hide this method and the documentation does not provide an explanation. The DependancyObject class also provides a CheckAccess method that is also hidden from the editor and just calls the Dispatcher method of the current object. Maybe it's not documented because it might be removed in the future, it is in beta after all.
So, to sum up, if you need to be able to invoke methods on a control's thread from a background thread you can do the following: (snippet from socket testing)
private delegate void AppendTextDelegate(string text); public void AppendText(string text) { if (txtChat.CheckAccess()) { // Append the message to the chat buffer. _content.Append(text).Append(Environment.NewLine); // Re-post the contents of the chat buffer to the chat text and scroll to // the bottom. txtChat.Content = _content.ToString(); txtChat.ScrollToVerticalOffset(txtChat.ScrollableHeight); } else { txtChat.Dispatcher.BeginInvoke(new AppendTextDelegate(this.AppendText), text); } }
The _content control is just a StringBuilder that actually holds the content from the chat control.
Again, Silverlight 2 is in beta 1 so maybe this will change in the future.
3/14/2008
I started working with Sockets in Silverlight 2 tonight and got my Silverlight application connected to the server and sending and receiving commands. I am going to write some code to testing sending commands from the server at the same time commands are coming from the client to see how the app and my code will handle it.
I will post some code as I get further along. Also, I made more progress on getting the Pirates game upgraded to Silverlight 2.
3/12/2008
I wrote this helper class to assist with processing an Http Request via Silverlight 2. Since we can't provide a custom class by deriving from BrowserHttpWebRequest because it is internal I wrote this helper that will create and send the request and raise an event when the request completes. I am using it in my current game to send commands to the server and process the responses.
Edit: I revised the class slightly based on finding from Mike Briseno:
Edit: Some folks have experienced issues with this class after installing Silverlight 2 Beta 2. Be sure to check your domain access policy xml file for the new changes in beta 2 but if you experience issues feel free to contact me and we can try and figure them out.
Edit: Added HttpUtility.UrlEncode to the post values before writing them to the stream. Thanks for the suggestion Stefan!
using System;using System.Collections.Generic;using System.IO;using System.Windows;using System.Windows.Browser;using System.Windows.Controls;using System.Windows.Documents;using System.Windows.Ink;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Animation;using System.Windows.Shapes;using System.Net;namespace Lionsguard{ public class HttpHelper { private HttpWebRequest Request { get; set; } public Dictionary<string, string> PostValues { get; private set; } public event HttpResponseCompleteEventHandler ResponseComplete; private void OnResponseComplete(HttpResponseCompleteEventArgs e) { if (this.ResponseComplete != null) { this.ResponseComplete(e); } } public HttpHelper(Uri requestUri, string method, params KeyValuePair<string, string>[] postValues) { this.Request = (HttpWebRequest)WebRequest.Create(requestUri); this.Request.ContentType = "application/x-www-form-urlencoded"; this.Request.Method = method; this.PostValues = new Dictionary<string, string>(); if (postValues != null && postValues.Length > 0) { foreach (var item in postValues) { this.PostValues.Add(item.Key, item.Value); } } } public void Execute() { this.Request.BeginGetRequestStream(new AsyncCallback(HttpHelper.BeginRequest), this); } private static void BeginRequest(IAsyncResult ar) { HttpHelper helper = ar.AsyncState as HttpHelper; if (helper != null) { if (helper.PostValues.Count > 0) { using (StreamWriter writer = new StreamWriter(helper.Request.EndGetRequestStream(ar))) { foreach (var item in helper.PostValues) { writer.Write("{0}={1}&", item.Key, HttpUtility.UrlEncode(item.Value)); } } } helper.Request.BeginGetResponse(new AsyncCallback(HttpHelper.BeginResponse), helper); } } private static void BeginResponse(IAsyncResult ar) { HttpHelper helper = ar.AsyncState as HttpHelper; if (helper != null) { HttpWebResponse response = (HttpWebResponse)helper.Request.EndGetResponse(ar); if (response != null) { Stream stream = response.GetResponseStream(); if (stream != null) { using (StreamReader reader = new StreamReader(stream)) { helper.OnResponseComplete(new HttpResponseCompleteEventArgs(reader.ReadToEnd())); } } } } } } public delegate void HttpResponseCompleteEventHandler(HttpResponseCompleteEventArgs e); public class HttpResponseCompleteEventArgs : EventArgs { public string Response { get; set; } public HttpResponseCompleteEventArgs(string response) { this.Response = response; } }}
And this is how I am using it in my app:
private void ProcessCommand(short cmd, string msg) { App app = App.Current as App; HttpHelper helper = new HttpHelper(app.ServerUri, "POST", new KeyValuePair<string, string>("authKey", app.AuthKey), new KeyValuePair<string, string>("cmd", cmd.ToString()), new KeyValuePair<string, string>("msg", msg)); helper.ResponseComplete += new HttpResponseCompleteEventHandler(this.CommandComplete); helper.Execute(); } private void CommandComplete(HttpResponseCompleteEventArgs e) { txtAlert.Text = e.Response; }
For the VB Developers out there David Thiessen has converted my code to VB:
' Usage....'Private Sub ProcessCommand(ByVal cmd As Short, ByVal msg As String)' Dim app As App = TryCast(App.Current, App)' Dim helper As New HttpHelper(app.ServerUri, "POST", New KeyValuePair(Of String, String)("authKey", app.AuthKey), New KeyValuePair(Of String, String)("cmd", cmd.ToString()), New KeyValuePair(Of String, String)("msg", msg))' AddHandler helper.ResponseComplete, AddressOf CommandComplete' helper.Execute()'End Sub'Private Sub CommandComplete(ByVal e As HttpResponseCompleteEventArgs)' txtAlert.Text = e.Response'End Sub''' <summary>''' Converted C# code from http://www.cameronalbert.com/post/2008/03/HttpWebRequest-Helper-for-Silverlight-2.aspx''' </summary>''' <remarks></remarks>Public Class HttpHelper Private Property Request() As HttpWebRequest Get Return _Request End Get Set(ByVal value As HttpWebRequest) _Request = value End Set End Property Private _Request As HttpWebRequest Public Property PostValues() As Dictionary(Of String, String) Get Return _PostValues End Get Private Set(ByVal value As Dictionary(Of String, String)) _PostValues = value End Set End Property Private _PostValues As Dictionary(Of String, String) Public Event ResponseComplete As HttpResponseCompleteEventHandler Private Sub OnResponseComplete(ByVal e As HttpResponseCompleteEventArgs) RaiseEvent ResponseComplete(e) End Sub Public Sub New(ByVal requestUri As Uri, ByVal method As String, ByVal ParamArray postValues As KeyValuePair(Of String, String)()) Me.Request = DirectCast(WebRequest.Create(requestUri), HttpWebRequest) Me.Request.ContentType = "application/x-www-form-urlencoded" Me.Request.Method = method Me.PostValues = New Dictionary(Of String, String)() For Each item In postValues Me.PostValues.Add(item.Key, item.Value) Next End Sub Public Sub Execute() Me.Request.BeginGetRequestStream(New AsyncCallback(AddressOf HttpHelper.BeginRequest), Me) End Sub Private Shared Sub BeginRequest(ByVal ar As IAsyncResult) Dim helper As HttpHelper = TryCast(ar.AsyncState, HttpHelper) If helper IsNot Nothing Then Using writer As New StreamWriter(helper.Request.EndGetRequestStream(ar)) For Each item In helper.PostValues writer.Write("{0}={1}&", item.Key, HttpUtility.UrlEncode(item.Value)) Next End Using helper.Request.BeginGetResponse(New AsyncCallback(AddressOf HttpHelper.BeginResponse), helper) End If End Sub Private Shared Sub BeginResponse(ByVal ar As IAsyncResult) Dim helper As HttpHelper = TryCast(ar.AsyncState, HttpHelper) If helper IsNot Nothing Then Dim response As HttpWebResponse = DirectCast(helper.Request.EndGetResponse(ar), HttpWebResponse) If response IsNot Nothing Then Dim stream As Stream = response.GetResponseStream() If stream IsNot Nothing Then Using reader As New StreamReader(stream) helper.OnResponseComplete(New HttpResponseCompleteEventArgs(reader.ReadToEnd())) End Using End If End If End If End SubEnd ClassPublic Delegate Sub HttpResponseCompleteEventHandler(ByVal e As HttpResponseCompleteEventArgs)Public Class HttpResponseCompleteEventArgs Inherits EventArgs Public Property Response() As String Get Return _Response End Get Set(ByVal value As String) _Response = value End Set End Property Private _Response As String Public Sub New(ByVal response As String) Me.Response = response End SubEnd Class
3/7/2008
3/5/2008
I might have mentioned it before but my PBBG engine is basically a commands processor. In other words it accepts commands from the client, processes the information on the server and sends the response back to the clients as JSON. Not really different from a normal web request except it is specific to my PBBG engine and you don't request documents from the server you are telling the server you want your Character to "do" something.
My PBBG engine is essentially just the command processor and the classes surrounding processing commands. I have abstracted it completely from the data source. There are no data providers or data connections at all in the engine. Instead, if it needs to get data it raises events that a deriving library can handle and pass back the required data in the form of simple interfaces. Some assumptions I did make such as ID values defined in interfaces are int values rather than object since I don't design databases with primary keys other than int. :)
One of the things I also did for the CommandManager was to created an abstract Verb class that I can derive from to create command verbs such as move, say, attack, etc. These command verbs can be defined in the deriving library and set in the CommandManager when the game starts up. Another class to assist with commands was a generic Command<T> class where T has to be a derived Verb value. So I can create Command<Move>() and the call an Execute method on the class which in turn creates the Verb instance and executes the code in the Verb class.
This has been working pretty well so far and since the CommandManager raises an event each time if processes a command I can have any number of derived libraries executing their own commands.
2/13/2008
I've been playing around a little with LINQ to SQL for Perenthia and wanted to share a kind of nasty query I just wrote. In Perenthia the typical RPG classes are called Professions. To track these professions I have the following database tables:
The players table links over to the rad_ProfessionLevels table via the ProfessionLevelId foreign key. The rad_ProfessionLevels table also links over the rad_Levels table on the LeveId foreign key. This layout allows me to have professions, define custom names for the levels and give certain levels titles.
When loading up a player record I need to go and get the level number, level name, profession name and title prefix and suffix values from the database. Since I am using LINQ to SQL for my entity classes I wrote the following query to retrieve this information:
var titleQuery = from pl in db.ProfessionLevelsjoin p in db.Professions on pl.ProfessionId equals p.ProfessionIdjoin plt in db.ProfessionLevelTitles on pl.ProfessionLevelId equals plt.ProfessionLevelId into profLevelsfrom x in profLevels.DefaultIfEmpty()join t in db.Titles on x.TitleId equals t.TitleId into titlesfrom y in titles.DefaultIfEmpty()join l in db.Levels on pl.LevelId equals l.LevelId into levelsfrom z in levels.DefaultIfEmpty()where pl.ProfessionLevelId == avatar.ProfessionLevelIdselect new { p.ProfessionName, z.LevelNumber, pl.LevelName, y.Prefix, y.Suffix };
It's kind of a nasty beast but produces the following SQL query:
SELECT [t1].[ProfessionName],[t4].[LevelNumber] AS [LevelNumber],[t0].[LevelName], [t3].[Prefix] AS [Prefix],[t3].[Suffix] AS [Suffix]FROM [dbo].[rad_ProfessionLevels] AS [t0]INNER JOIN [dbo].[rad_Professions] AS [t1]ON [t0].[ProfessionId] = [t1].[ProfessionId]LEFT OUTER JOIN [dbo].[rad_ProfessionLevelTitles] AS [t2]ON [t0].[ProfessionLevelId] = [t2].[ProfessionLevelId]LEFT OUTER JOIN [dbo].[rad_Titles] AS [t3]ON [t2].[TitleId] = [t3].[TitleId]LEFT OUTER JOIN [dbo].[rad_Levels] AS [t4]ON [t0].[LevelId] = [t4].[LevelId]WHERE [t0].[ProfessionLevelId] = @p0
The @p0 value is the ProfessionLevelId value stored with the player record.
The LINQ query is nasty because of the three outer joins I have to perform because not all profession levels have titles. It doesn't look pretty but it does work; as I get into more LINQ writing I will probably find a better way to write this but at least I got my data. :)
1/25/2008
The Silverlight Pirates! prototype is online!
Keep in mind that this is a very early prototype and about the only thing you can do is destroy that other ship that is sitting there. I didn't get the enemy AI quite where I wanted it but it will fire on you if you get close. You can sail around and dock at ports, although you can't buy anything yet. Once your ammo runs out you will have to refresh the page to reload the game. You will also have to refresh if the enemy ship kills you.
On my TODO list are:
If you find any funky bugs please let me know and of course, you will need Silverlight 1.1 in order to run the prototype.
Silverlight Pirates! Prototype
1/23/2008
1/17/2008
Looks like Microsoft wants to join the ranks of companies with really crappy customer service such as Bank of America, Sprint, Comcast, Dell. Full story...
12/3/2007
I've seen a lot of stuff out there regarding the SQL 2005 XML data type but most of it is just regurgitates the MSDN documentation. That's fine and all but what about practical uses of it? Well, I have a practical use sample. In building my persistent browser based game Perenthia I have a concept of a Place. A place is a virtual space in which objects are stored. For Perenthia the places represent the various rooms or tiles players move around on. The place or room has exits defined that allow the player to move from one place to the next. The exits are the typical directions; north, south, up, down, etc. In the database I have a Places table and a PlaceExits table. The Places table stores all the information regarding a place and the PlaceExits table stores the placeId along with a directionId and destinationId so I know what exits are available in any room and what rooms they lead to.
The simplified schema for the places would be:
In the stored procedure that retrieves the place information I use the following query snippet in the select clause:
SELECT
p.*,
(
SELECT
e.DirectionId AS "@directionId",
e.DestinationId AS "@destinationId"
FROM
dbo.PlaceExits e
WHERE
e.PlaceId = p.PlaceId
FOR XML PATH('exit'), ROOT('exits')
) AS ExitsXml
FROM dbo.Places p
This creates an XML fragment I can then parse in the application to fill a collection of Exits on the Place object.
When saving place information I pass XML generated from the Exits collection in a stored procedure like so:
CREATE PROCEDURE dbo.Places_SavePlace (@PlaceId int, @ExitsXml xml)
From within the save procedure I perform an update or insert of the place data and then execute the following sql to insert and update the exits for the current place:
-- Exits
-- Process the existing exits first
UPDATE
dbo.PlaceExits
SET
DestinationId = e.ex.value('(@destinationId)[1]', 'int')
FROM
@ExitsXml.nodes('/exits/exit') as e(ex)
WHERE
PlaceId = @PlaceId
AND DirectionId = e.ex.value('(@directionId)[1]', 'tinyint')
-- Process any new exits
INSERT INTO dbo.PlaceExits
(
ObjectId, DirectionId, DestinationId
)
SELECT
@PlaceId,
e.ex.value('(@directionId)[1]', 'tinyint'),
e.ex.value('(@destinationId)[1]', 'int')
FROM
@ExitsXml.nodes('/exits/exit') as e(ex)
WHERE
e.ex.value('(@directionId)[1]', 'tinyint') NOT IN
(
SELECT DirectionId FROM dbo.PlaceExits WHERE PlaceId = @PlaceId
)
This is working pretty well and keeps me from having to loop through the exits in the application and make multiple database calls.
11/14/2007
In between updating Perenthia and adding new features I have been pulling parts of the code base into a more generic PBBG Engine I am writing in C#. I started working on it when I upgraded the Knights of the Realm game and used parts of it for Perenthia. I am hoping to put Knights of the Realm Beta 2 on the new engine once I get it finished.
I am building the engine as generic as I can but it will incorporate a base rules set and some basic concepts. The base objects will be Avatars, Places and Things. These objects will contain the properties required to function within the rules set and all objects will derive from a base GameObject class that will provide a properties collection for creating custom properties on derived game objects.
The game will be driven by commands sent from the client. Some objects will handle the commands in the engine framework while other commands will cause events to be raised that deriving implementations can handle and provide custom execution or additional execution of the commands.
The egnine will basically be a commands/rules processor that I will hopefully be able to build a variety of games on. I have plans and ideas for several types of games and do not want to continually build the same thing over and over, hence the PBBG engine.
10/17/2007
I ran into a major performance issue with the ASP.NET AJAX Server Controls while testing my persistent browser based game Perenthia. I had initially used update panels fro the various regions on the main game interface such as the player stats, map and chat window. Programming this was simple as I could do everything in the server side code and just send back the results. With the partial page rendering feature of the ASP.NET AJAX Extensions only the update panel html was sent back to the browser.
Once I moved the application into a production environment and had people on there playing and testing the web starting running out of memory, the application was consuming the server memory at an alarming rate. What was happening was that IIS would jump 1 to 2 MB of RAM for each request made by the client, that means every time someone moved on the game map 1 to 2 MB of RAM were being held and not released. I tried a bunch of different stuff from optimizing stored procedures to caching the map data but none of it helped.
I used Firebug to watch the AJAX request and response and to see if I could reduce that down some. The response was simply HTML fragments so I wasn't too worried about that but the request sent the entire ViewState up to the server with each post. The ViewState for the game page could be quite large since I was appending messages to the chat window which was a server control.
I was able to eliminate the ViewState issue with the chat window but the app was still consuming RAM and not releasing it.
I decided to try just a simple JavaScript only AJAX post using the Microsoft AJAX Client Libraries. After doing some basic tests on the chat window I decided to rebuild the game interface using only client side AJAX calls, no update panels. This has resolved the server memory issue, why, I am still not sure, must be something with the way the resources are handled in .NET. I have not encountered the slow downs or crashes from before and the app is actually running a little smoother on the front end as well.
What I settled on was a custom ASHX handler class that handles the commands from the client and returns JSON strings that the UI can then translate and use to update the interface components. I had to rebuild some of the interface components as JavaScript objects but overall it was the right choice. Not too mention that I can now use that same handler with other interfaces such as Flash or Silverlight.
In conclusion, while the AJAX Server Controls might work great for most web sites they are definitely not optimal for a persistent browser based game (PPBG).
10/1/2007
9/11/2007
Here is a screenshot of the PBBG mapping tool I wrote for creating the Perenthia world. The screenshot is the City of Angarath, a starting point in the game. The mapper is a Windows application written in C# 2.0.
9/11/2007
I've decided to use Flash to provide the UI for my persistent browser based game Perenthia. I went back and forth between Flash and AJAX and even looked into Silverlight a little and Flash just has the maturity needed for a good PBBG interface. I originally had the main game UI written using AJAX but found it to be a little cumbersome when a lot of activity was occuring on the back end, just too much traffic generated for one user. I looked into Silverlight a little but I am going to wait until the 1.1 version is released so I can program in C# on the backend. Flash seems to be able to provide me with what I need and I wrote custom ASHX handlers on the ASP.NET side to handle commands from the Flash UI. The UI is basically just an advanced MUD client, in that the primary output is text based. However, with Flash I will be able to provide a better map and add some additional graphical features later on down the road. Since the command handler is a custom ASHX handler in .NET I could really allow any type of client to connect, as long as that client can send XML as an HTTP POST and receive the and parse the XML response from the page.
I will post a screen shot of the UI in the next day or so.
8/26/2007
8/24/2007
8/2/2007
After doing some research on available socket servers for Flash I decided to write my own in C#. Most of the server out there are written in Java and while there are some open source implementations I prefer to stick with a code base I know and understand.
I got the Flash movie to connect to the C# server yesterday using JSON protocal instead of XML as my transport mechanism. I used a .NET JSON library and an ActionScript 2.0 JSON library to build the objects on both sides. I created a C# class for passing data, serialize as a JSON string and send it the Flash movie. On the ActionScript side I created an AS class that mimics my C# data gram class and use the AS JSON lib to serialize and send that same object structure to the C# server. Working pretty good so far, I am able to login to the server and send and receive messages.
My next steps will be creating a basic flash game that I can use to run around and do battle while sending and receiving messages from the server.
I have plans to make a Flash based PBBG with real time combat, we'll see how it goes. :)
7/27/2007
I created a struct in C# for storing the X, Y and Z coordinates for characters, objects and rooms within my current PBBG project Perenthia. The struct, called Vector, is serializable and can be used as the key value in a sorted or generic dictionary. Here is the code for the class.
[Serializable] public struct Vector : IEquatable<Vector> { public static readonly Vector Empty = new Vector(0, 0, 0); public Vector(int x, int y, int z) { _x = x; _y = y; _z = z; } public void SetLocation(int x, int y) { this.SetLocation(x, y, this.Z); } public void SetLocation(int x, int y, int z) { _x = x; _y = y; _z = z; } public Vector Copy() { return new Vector(this.X, this.Y, this.Z); } public static Vector FromString(string value) { if (!String.IsNullOrEmpty(value)) { string[] parts = value.Split(','); if (parts != null && parts.Length == 3) { int x, y, z; if (Int32.TryParse(parts[0], out x)) { if (Int32.TryParse(parts[1], out y)) { if (Int32.TryParse(parts[2], out z)) { return new Vector(x, y, z); } } } } } return Vector.Empty; } #region GetHashCode public override int GetHashCode() { // The Y value should always come first in any kind of sorting, comparison or hashing operations // followed by X and then Z because typical loops would start with the Y value. return (this.Y.GetHashCode() + this.X.GetHashCode() + this.Z.GetHashCode()); } #endregion #region Equals public bool Equals(Vector obj) { // The Y value should always come first in any kind of sorting, comparison or hashing operations // followed by X and then Z because typical loops would start with the Y value. if (obj != null) { if (obj.Y == this.Y) { if (obj.X == this.X) { return (obj.Z == this.Z); } } } return false; } public override bool Equals(object obj) { if (obj is Vector) { return this.Equals((Vector)obj); } return base.Equals(obj); } #endregion #region ToString public override string ToString() { return String.Format("{0},{1},{2}", _x, _y, _z); } public string ToString(bool forDisplay) { if (forDisplay) { return String.Format("X = {0}, Y = {1}, Z = {2}", _x, _y, _z); } return this.ToString(); } #endregion #region Operators public static Vector operator +(Vector v1, Vector v2) { return new Vector(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z); } public static Vector operator -(Vector v1, Vector v2) { return new Vector(v1.X - v2.X, v1.Y - v2.Y, v1.Z - v2.Z); } public static bool operator ==(Vector v1, Vector v2) { return v1.Equals(v2); } public static bool operator !=(Vector v1, Vector v2) { return (!v1.Equals(v2)); } public static bool operator >=(Vector v1, Vector v2) { // The Y value should always come first in any kind of sorting, comparison or hashing operations // followed by X and then Z because typical loops would start with the Y value. if (v1.Y >= v2.Y) { if (v1.X >= v2.X) { return (v1.Z >= v2.Z); } } return false; } public static bool operator <=(Vector v1, Vector v2) { // The Y value should always come first in any kind of sorting, comparison or hashing operations // followed by X and then Z because typical loops would start with the Y value. if (v1.Y <= v2.Y) { if (v1.X <= v2.X) { return (v1.Z <= v2.Z); } } return false; } #endregion #region Properties private int _x; public int X { get { return _x; } set { _x = value; } } private int _y; public int Y { get { return _y; } set { _y = value; } } private int _z; public int Z { get { return _z; } set { _z = value; } } #endregion #region IEquatable<Vector> Members bool IEquatable<Vector>.Equals(Vector other) { return this.Equals(other); } #endregion }
7/16/2007
7/13/2007
I found some good javascript libraries for PBBG or any kind of web development.
This one is a graphics library useful for drawing vector graphics to the browser. Works great, just remember to set the position:relative value of the DIV you wish to draw in. :)
DHTML: Draw Line, Ellipse, Oval, Circle, Polyline, Polygon, Triangle with JavaScript
This site has a collection of useful libraries for all manner of web related activities.
Javascript Toolbox: Reusable Libraries And Scripts Plus Information
7/12/2007
7/10/2007
I've been developing persistent browser based games using ASP.NET and AJAX for a little while now but an emerging technology from Microsoft called Silverlight that I believe will re-define how browser games are built for .NET developers. Silverlight is Microsoft's answer to Flash but with advantages for .NET developers in that I can program C# on the backend and have Silverlight on the front end.
My current PBBG Perenthia will be ASP.NET and AJAX but another game I have in the design phase called Aelerion will feature a Silverlight front end using (hopefully) the same game engine I wrote for Perenthia.
6/30/2007