I've alluded in the past that I got my start in computers because of games; I also ended up getting started in programming because of games. I happen to be a big fan of the WarCraft and StarCraft series produced by Blizzard Entertainment, and it was my association with (and desire to make a good website for!) a group of people on their gaming service called Battle.net that got me involved with programming, and that's where the "Fun" part of this comes from. I'm not sure where "Profit" comes into play.
With the knowledge that StarCraft 2 is on the horizon, and the new announcement of Diablo 3, I've been working on a series of open-source projects collectively named after my cat Jinx (we had the name WAY before the movie), which includes a third-party utility library that manages client connections to Battle.net's chat service, a full-blown client, and some plugins for the client. When I first got into the bot-making community I found a project called WebChannel and WebBot -- these projects allowed clients to broadcast their channel information via another service called BotNet, which then rendered the content over an ISAPI Extension. At the time I only really knew the basics of C#, and I could not find a way to mimic the functionality (WebChannel streamed content over the wire by removing Content-Length from HTTP headers, and then never closing the client connections). To my knowledge, there is not a way to get ASP.NET to not send the Content-Length header; I guess I've never pursued it after about three years ago.
When starting this project, I knew from the start that I wanted to recreate the functionality (particularly because WebChannel has been unavailable for a long time). But I also knew that I wanted to do it without recreating a lot of functionality; in the past, submitting complex types to Web Services caused the complex types to be generated as proxies as part of the client assembly, and there was no support for inheritance/polymorphism. I knew that all of my event arguments would derive from a single class, and so polymorphism seemed to be the way to go. Plus, all complex types serialized to and from XML needed to have a default public constructor and public gettable/settable properties, which in my mind would severely break encapsulation. The question was, how to go about pushing my client content to a web site.
The astute reader might have noticed a lot of qualifying "mostly" asides in that last paragraph. I'll get to that.
The final result - well, not really final - was a very Meebo-esque client that shows a live display of the channel Diablo II USA-1 on the US-East Battle.net server. (If it's not up I apologize). (Also, I definitely take no credit for what is said in the channel; I'm just showing that it can be done).
There have been some trials on this project.
- Proxy generation ignored shared types -- this occurred once I started sending some certain complex types over the wire. When I added the service reference and looked at the Object Browser in Visual Studio, I would see all of my BNSharp.dll-driven classes being recreated. This was what I wanted to avoid! It turned out to be that I was not decorating some enumerations with [EnumMember] - once I did this, proxy generation returned to normal.
- I received an error that "more than one http binding cannot be configured" when attempting to view a WCF service. Because I use a shared web host, and multiple hosts were configured on the same IP, I was getting an error when setting up WCF services on my host. It turned out that this was corrected with a web.config setting in .NET 3.5 called <baseAddressPrefixFilters>.
- I then received an error that my plugin was no longer authorized once I moved it into the shared hosting environment. I eventually tracked this down to using wsHttpBinding (the more secure Web Services HTTP binding), and had to change to basicHttpBinding.
- I then received an error that my application was sending content-type "application/soap+xml, charset='utf-8'", which was not the expected "text/xml, charset='utf-8'". Ultimately this ended up being traced to not using basicHttpBinding on the client as well. That one had to be the most frustrating; honestly, it's the same thing.
What needs to yet be done
I've noticed that the client tends to cache the content returned from the server. I need to access the HttpResponse and set its cacheability.
Other than that, it's just the user interface that needs to be done. We've got the basics, but it needs a channel list and the ability for users to type a password into the client to communicate with the host application and then interact with the channel.
I sat down tonight and in about three hours or so cranked out the first vestiges of something that kind of maybe resembles Breakout, only using graphics that are possibly worse than anything ever shown on Atari 2600 because well, I made them.
I should also point out: I used some sounds from the Spacewar demo in XNA Game Studio 2.0. Those sound AWESOME.
This app is built with a model in a separate assembly; the model portion of the project contains the mathematics, collision detection, and all the game objects on the screen. We've got tiles, a ball, and a paddle. I should also point out that there's a game board (that's the big black empty space on the screen).
The XNA game application, then, instantiates a GameBoard (that's the containing class that owns all of the rest of the objects) and then instantiates a GameBoardPresenter. GameBoardPresenter is part of the game application itself, and it owns a copy of the GameBoard because it's intimately tied to its inner workings.
To keep things relatively efficient, the GameBoard class exposes all of its object lists as properties that return the lists. This way, object references aren't duplicated (except for the references to the Lists), and we're not wasting unnecessary memory and copy operations. (If you're not clear what I mean, here are the properties:)
Since the balls are the only pieces of the game that move, each call to Update() on the GameBoard instance iterates through each Ball object, moving it accordingly and handling any collisions. When a collision is detected, it fires an event corresponding to the type of collision, which is consumed by the GameBoardPresenter.
One other advantage of separating out the presentation and the actual game logic is that I can scale the graphics size to anything I want; for instance, the video above was recorded and 960x720, but I was also able to run it at 1920x1200 without any apparent differences in visualization. The internal game units actually default to a game board size of 4800x4800, and scaling is simply done within the GameBoardPresenter's Draw() method:
The scale is calculated when the backbuffer is sized.
Source code for this isn't quite ready yet; there are some things that I want to refactor (for instance, I want the Model part to define input "actions" and that View/Presenter part to keymap inputs to actions).
Have you ever created a database object and said "OK, this column is going to correspond to this enumeration"? If you're obsessive like me, you might have even gone so far as to create column restrictions for the valid range of values, and created a special SQL type that doesn't really do anything except give you peace of mind.
Well, I've got about three such fields on a couple entities on a recent project. Since I wanted those properties to go into C# enumerations, I tried the natural thing: I typed the enumeration's type name into the "Type" property.
Unfortunately, doing this didn't work. In fact, it seemed to break Visual Studio; updates stopped propagating to my LINQ-to-SQL classes, and in fact since I had done this before a single save, I didn't get any entity classes.
It turns out that Matt Davis found the answer to the problem: qualify the enumeration's type name with the global:: namespace qualifier if it doesn't live in a namespace (for instance, code within an ASP.NET App_Code folder).
Once I added the qualifier to my type names, saving the DBML file correctly updated the LINQ-to-SQL classes, and I was off and running!
There's already rudimentary collision detection in the app, but only for the sides. What if I wanted the balls to detect when they collide?
In the demo video you can already see the balls colliding (and, incidentally, the gravity changing from down, then left, up, right, and down again). But how did I get there? Wouldn't you know that XNA has a built-in structure called BoundingSphere that can handle that for you? I defined the bounding sphere as centered around the ball's center point (which was defined by the texture's dimensions), and used the z-coordinate as 0. Intrinsically, then, the bounding sphere acts like a bounding circle as long as everything is on the same Z-plane.
Next, I created a couple interfaces that are going to help me out. Interfaces such as IPhysical and IPhysicalSphere -- they provide necessary information for collidable objects. Here they are:
By implementing these properties, a separate class can be constructed to perform the necessary math for bouncing balls. When first implemented, colliding balls would simply switch speed vectors. However, that only works if balls collide straight on (so that movement vectors would be inverse of each other). When balls collide like this:
(Awesome MS-Paint artwork, huh?)
When balls collide like that, they don't simply exchange vectors. What actually happens is that the speed vectors are changed according to the normal line between the two circles (or the normal plane between the two spheres).
I created a static class to do this work for me:
This was inspired by a blog post I found that covered it exceptionally. I wish I could name the offer but I couldn't find the author's name!
Calling this method to adjust the speed vectors is done whenever collisions are detected. That part is handled in the Game class during the Update method:
And that's about all there is to it! New source code is up! Here's the next demo:
One other thing -- as a note, the red ball with the orange gradient has a mass of 8.0f; the others have a mass of 4.0f.
My first application in XNA! It's a... well, it's a ball that bounces.
I created a new Windows XNA 2.0 application project. I decided to abstract away a Ball object, as well as a GravitySource object. There's a lot of cross-talk -- I'm not sure if this is good or not -- but it's happy enough for me.
Gravity seems to be pretty straightforward to implement; the applied speed is going to be a vector direction with a constant speed as the magnitude. Here's the code for the gravity source class:
Note that I added a Direction enumeration to make it nice to read.
The game class has a reference to the ball object, and it handles the keyboard input to change the gravity direction all within the Update method:
The game's drawing method is actually quite simple:
And that gets implemented in the Ball class; here are the meat and potatoes:
One kind of odd part is that it's designed to be simply a perfectly elastic ball (m_speed.Y *= -1.0f) but it actually bounces higher over time.
This afternoon, some co-workers were a bit perplexed about why an 'ñ' character in a Spanish name wasn't displaying on a Facebook application we were working on, but rather was coming across as a '?'. Because we were pretty sure that Facebook was UTF-8-encoded, we thought the problem was on our side. Unfortunately, we were on a client's staging/test server, and we weren't able to debug on that machine (we have Visual Studio installed on our own test server). Since we didn't have privileges on the server, we couldn't even jump on WireShark to validate the outgoing packets from our server. We needed an alternative method.
One of the cool things about HTTP Modules in ASP.NET is that they sit between the client and the server; with them, we can get all the information we need. In addition, we can pass the output through a filter - a Stream object through which the page output passes. This is exposed through the HttpResponse.Filter property.
The solution to this problem is straightforward: we'll inject a filter into the response stream and write all of the output to a log file. Then, we'll be able to see whether we're sending the offending ?, or if Facebook is misreading our character and translating it to a ?.
First, I created the HTTP Module:
Then we had to implement an actual filter stream object:
Notable in this class --
- It's not readable or seekable.
- It creates two log files per transaction: a dump of the request headers and the response page, and a dump in hex and binary. The hex-and-binary dump uses the DataFormatter class (username: 'mbncsutil_anonymous', no password) of the MBNCSUtil library, which I won't include here.
- The Write, Flush, and Close operations are overridden to perform those operations on both of the logs and the contained stream.
- The existing Response.Filter object is passed as a constructor to this new filter, and is contained as the member field _sink. If the existing Response.Filter is not used, an exception is raised, because it means that the output isn't getting written to the stream that will eventually be sent to the client.
- I implemented a helper method called WriteHeadersToLog because I wanted to dump both request and response headers to the log file. However, I was rudely informed by ASP.NET that it would require IIS7 Integrated Pipeline mode. So I removed it.
There are some nice files produced. Here's an example of one:
And here's an example of the binary:
0000 0d 0a 0d 0a 3c 21 44 4f 43 54 59 50 45 20 68 74 ....<!DOCTYPE ht
0010 6d 6c 20 50 55 42 4c 49 43 20 22 2d 2f 2f 57 33 ml PUBLIC "-//W3
0020 43 2f 2f 44 54 44 20 58 48 54 4d 4c 20 31 2e 30 C//DTD XHTML 1.0
0030 20 54 72 61 6e 73 69 74 69 6f 6e 61 6c 2f 2f 45 Transitional//E
0040 4e 22 20 22 68 74 74 70 3a 2f 2f 77 77 77 2e 77 N" "http://www.w
0050 33 2e 6f 72 67 2f 54 52 2f 78 68 74 6d 6c 31 2f 3.org/TR/xhtml1/
0060 44 54 44 2f 78 68 74 6d 6c 31 2d 74 72 61 6e 73 DTD/xhtml1-trans
0070 69 74 69 6f 6e 61 6c 2e 64 74 64 22 3e 0d 0a 0d itional.dtd">...
0080 0a 3c 68 74 6d 6c 20 78 6d 6c 6e 73 3d 22 68 74 .<html xmlns="ht
0090 74 70 3a 2f 2f 77 77 77 2e 77 33 2e 6f 72 67 2f tp://www.w3.org/
00a0 31 39 39 39 2f 78 68 74 6d 6c 22 3e 0d 0a 3c 68 1999/xhtml">..<h
00b0 65 61 64 3e 3c 74 69 74 6c 65 3e 0d 0a 09 55 6e ead><title>...Un
00c0 74 69 74 6c 65 64 20 50 61 67 65 0d 0a 3c 2f 74 titled Page..</t
00d0 69 74 6c 65 3e 3c 2f 68 65 61 64 3e 0d 0a 3c 62 itle></head>..<b
00e0 6f 64 79 3e 0d 0a 20 20 20 20 3c 66 6f 72 6d 20 ody>.. <form
00f0 6e 61 6d 65 3d 22 66 6f 72 6d 31 22 20 6d 65 74 name="form1" met
0100 68 6f 64 3d 22 70 6f 73 74 22 20 61 63 74 69 6f hod="post" actio
0110 6e 3d 22 44 65 66 61 75 6c 74 2e 61 73 70 78 22 n="Default.aspx"
0120 20 69 64 3d 22 66 6f 72 6d 31 22 3e 0d 0a 3c 64 id="form1">..<d
0130 69 76 3e 0d 0a 3c 69 6e 70 75 74 20 74 79 70 65 iv>..<input type
0140 3d 22 68 69 64 64 65 6e 22 20 6e 61 6d 65 3d 22 ="hidden" name="
0150 5f 5f 56 49 45 57 53 54 41 54 45 22 20 69 64 3d __VIEWSTATE" id=
0160 22 5f 5f 56 49 45 57 53 54 41 54 45 22 20 76 61 "__VIEWSTATE" va
0170 6c 75 65 3d 22 2f 77 45 50 44 77 55 4a 4e 7a 67 lue="/wEPDwUJNzg
0180 7a 4e 44 4d 77 4e 54 4d 7a 5a 47 52 75 72 71 57 zNDMwNTMzZGRurqW
0190 77 4d 76 39 2b 38 5a 78 6c 47 43 38 62 64 67 34 wMv9+8ZxlGC8bdg4
01a0 41 4a 65 6e 35 76 41 3d 3d 22 20 2f 3e 0d 0a 3c AJen5vA==" />..<
01b0 2f 64 69 76 3e 0d 0a 0d 0a 20 20 20 20 3c 64 69 /div>.... <di
01c0 76 3e 0d 0a 20 20 20 20 20 20 20 20 54 68 69 73 v>.. This
01d0 20 69 73 20 61 20 74 65 73 74 2e 0d 0a 20 20 20 is a test...
01e0 20 3c 2f 64 69 76 3e 0d 0a 20 20 20 20 3c 2f 66 </div>.. </f
01f0 6f 72 6d 3e 0d 0a 3c 2f 62 6f 64 79 3e 0d 0a 3c orm>..</body>..<
0200 2f 68 74 6d 6c 3e 0d 0a /html>..
Some notes about using this code:
- It's not meant to be used in production code. It doesn't even attempt to synchronize file I/O.
- It *can* be used as a starter if, for instance, you wanted to compress HTTP traffic. (This is the same method the Blowery compression module takes).
- You need to add an HTTP Module declaration to your web.config (see below).
- Facebook is difficult to debug.
Setting up your Web.config file
Remember that you need to add the <httpModules> section to your web.config file. If you don't already have one, this will suffice:
This assumes that the module is being added from App_Code.