Friday, December 11, 2009

Lots of Fishes...

Lots of Fishes

Just some stuff I'm messing around with...

Friday, November 6, 2009

Music-based Gameplay

Music-based Gameplay

My latest project is a game that uses music as the gameplay content. I use spectrum information to detect energy pulses in the music and use peaks to trigger emission of particles. The object of the game is pretty simple - collect the particles while avoiding the red boulder-thingies. Grabbing stars increases a score multiplier.

Music-based Gameplay

Collecting the particles, in addition to increasing your score, fuels three kinds of colored energy - shown by the three lines inside your circle. This energy can be expended to use a shield, give you a speed burst, or temporarily suck particles toward you.

There are also cooperative and competitive multiplayer gameplay modes. This is a mode where players are "bungied" together:

Music-based Gameplay

Here is a gameplay video:

Tuesday, October 13, 2009

Steam and 3rd-Party DRM - Risen and Tages Activation

This past Saturday morning, I decide that I'd like to play Risen, the latest game from Piranha Bytes. I check on Steam, and they have it for $49.99. Amazon has it for $46.99. Boo, digital distribution! You aren't going to ship me a box, and you are still going to charge more? Still, I've been pretty happy with Steam in the past, so I was willing to spend the few extra dollars to get it quickly.

So, I make the purchase. A half-hour or so later, my download was complete. Yay, digital distribution! I launch the game, and it brings up an activation check that needs to authenticate to a server online. When I proceed, it pops up an error saying that it can't connect. There is also an "offline" activation mode, which involves entering data on a webpage (not really "offline", but whatever...), but the website can't be reached.

I check the Risen forum on Steam, and a handful of other people are having the same problem.. Turns out that all this is Tages DRM being applied to the game on top of what Steam does. And it looks like their authentication servers are down. Lovely. If you buy a physical copy of the game, it doesn't authenticate online (it uses a disk check). Boo, digital distribution! I fire off a support ticket on Steam (after having to create a support account, which is different from my Steam account).

10 or so hours later, I try again. Still not working. I'm thinking that if you are going to require online authentication, your authentication server bloody well better be up. And if it isn't, someone better be getting paged. And, of course, I had no response from Steam support. They are happy to take my money on a Saturday, but don't seem inclined to provide support on a Saturday.

The next morning, the authentication server is up, and I can play the game (which is pretty good, by the way). Still, the whole incident left me with ill will toward all parties involved. Tages, for their broken DRM. The publisher, Deep Silver, for using it. And Valve, for allowing 3rd-party DRM that is out of their control to be used on a product they sell. And for having crappy customer support.

Speaking of their customer support, I just got a response from them (3 days later) - a useless canned response about running Steam as administrator if I'm using Vista or Windows 7. Thanks, Valve!

Tuesday, September 29, 2009

Fractal Rendering on the GPU - Mandelbrot and Julia Sets in an HLSL Shader

Julia Set Shader

I've been playing with HLSL shaders recently, and it occurred to me that the horsepower of the GPU could probably be harnessed to render fractals like the Mandelbrot Set. It turns out to be a perfect task for a shader, since it involves lots of completely localized calculations - exactly what graphics cards are good at.

A bit of web searching turned up the not surprising fact that I'm not the first person to have this realization. I found some nice code examples here and here. My version takes inspiration from both of these approaches, and adds a few tweaks of my own.

The image above is of the Julia set, rendered completely by the GPU. Below is, of course, the Mandelbrot Set.

Mandelbrot Set Shader

And another view of the Mandelbrot set zoomed in a bit:

Mandelbrot Set Shader

On my GeForce 8800GT, I can zoom and pan around at a rock-solid 60 frames per second. Here is a video exploring the Julia set. The warping you see is me transforming the seed used to render the set.



Here is the shader code I ended up with. It is pretty simple. I'm using the Normalized Iteration Count Algorithm to get nicely smoothed coloring. The parameters are set to sensible default values, but you will want to pass them in from your application and map them to some sort of input device. For rendering, simply draw a full screen quad using the shader. In my application I also gild the lily a bit and apply a bloom effect.

int Iterations = 128;
float2 Pan = float2(0.5, 0);
float Zoom = 3;
float Aspect = 1;
float2 JuliaSeed = float2(0.39, -0.2);
float3 ColorScale = float3(4, 5, 6);

float ComputeValue(float2 v, float2 offset)
{
float vxsquare = 0;
float vysquare = 0;

int iteration = 0;
int lastIteration = Iterations;

do
{
vxsquare = v.x * v.x;
vysquare = v.y * v.y;

v = float2(vxsquare - vysquare, v.x * v.y * 2) + offset;

iteration++;

if ((lastIteration == Iterations) && (vxsquare + vysquare) > 4.0)
{
lastIteration = iteration + 1;
}
}
while (iteration < lastIteration);

return (float(iteration) - (log(log(sqrt(vxsquare + vysquare))) / log(2.0))) / float(Iterations);
}

float4 Mandelbrot_PixelShader(float2 texCoord : TEXCOORD0) : COLOR0
{
float2 v = (texCoord - 0.5) * Zoom * float2(1, Aspect) - Pan;

float val = ComputeValue(v, v);

return float4(sin(val * ColorScale.x), sin(val * ColorScale.y), sin(val * ColorScale.z), 1);
}

float4 Julia_PixelShader(float2 texCoord : TEXCOORD0) : COLOR0
{
float2 v = (texCoord - 0.5) * Zoom * float2(1, Aspect) - Pan;

float val = ComputeValue(v, JuliaSeed);

return float4(sin(val * ColorScale.x), sin(val * ColorScale.y), sin(val * ColorScale.z), 1);
}

technique Mandelbrot
{
pass
{
PixelShader = compile ps_3_0 Mandelbrot_PixelShader();
}
}

technique Julia
{
pass
{
PixelShader = compile ps_3_0 Julia_PixelShader();
}
}

Tuesday, September 1, 2009

XNA BloomComponent and Multisampling

The Bloom Postprocess sample provided on the XNA website has issues if you use the component in a game that is using multisampling.

You'll get the following error:

"The active render target and depth stencil surface must have the same pixel size and multisampling type."

The issue is that the render targets created by the component do not respect the multisampling settings of the graphics device. This is easy to fix, however. In the LoadContent() method of BloomComponent.cs, find the code that is initializing renderTarget1 and renderTarget2.

Replace it with the following:

           renderTarget1 = new RenderTarget2D(GraphicsDevice, width, height, 1,
format, GraphicsDevice.DepthStencilBuffer.MultiSampleType, GraphicsDevice.PresentationParameters.MultiSampleQuality);
renderTarget2 = new RenderTarget2D(GraphicsDevice, width, height, 1,
format, GraphicsDevice.DepthStencilBuffer.MultiSampleType, GraphicsDevice.PresentationParameters.MultiSampleQuality);

Friday, August 21, 2009

Cybraphon



A friend I used to work with back in my Edinburgh days has been working on some pretty fantastic stuff recently. He and his collaborators have been using computer-orchestrated electronic actuators to control a variety of physical music-producing devices.

Their latest creation (shown in the video above) is Cybraphon, a one-"man" wardrobe band.

I really like it.

Wednesday, August 19, 2009

SubMariner



I really should be working on Saucer Pilots, but as I've said before, I'm much better at starting projects than finishing them...

Thursday, August 13, 2009

XNA Force Feedback Helper Class

This is a little helper class I whipped up to manage force feedback vibrations in XNA. The XNA libraries themselves just have a method you call to set the instantaneous left/right vibration force. In games, however, you generally want the feedback to last over a period of time, and you may have more than one force feedback event happening at the same time.

This code provides a "fire-and-forget" interface for triggering vibrations. Add a call to update the ForceFeedbackManager in your game's update code. Call AddVibration whenever you want to trigger force feedback.

Note: this code is not threadsafe - you must be triggering feedback from the same thread you call Update from.


public class Vibration
{
float leftMotor;
float rightMotor;
float msLeft;

public float LeftMotor
{
get { return leftMotor; }
set { leftMotor = value; }
}

public float RightMotor
{
get { return rightMotor; }
set { rightMotor = value; }
}

public float MSLeft
{
get { return msLeft; }
set { msLeft = value; }
}

public Vibration(float leftMotor, float rightMotor, float durationMS)
{
this.leftMotor = leftMotor;
this.rightMotor = rightMotor;
this.msLeft = durationMS;
}
}

public class ForceFeedbackManager
{
private PlayerIndex playerIndex;
List<Vibration> vibrations = new List<Vibration>();

public ForceFeedbackManager(PlayerIndex playerIndex)
{
this.playerIndex = playerIndex;
}

public void AddVibration(float leftMotor, float rightMotor, float durationMS)
{
vibrations.Add(new Vibration(leftMotor, rightMotor, durationMS));
}

public void Update(float msElapsed)
{
List<Vibration> toDelete = new List<Vibration>();

foreach (Vibration vibration in vibrations)
{
vibration.MSLeft -= msElapsed;

if (vibration.MSLeft < 0.0f)
{
toDelete.Add(vibration);
}
}

foreach (Vibration vibration in toDelete)
{
vibrations.Remove(vibration);
}

float leftMotor;
float rightMotor;

GetVibration(out leftMotor, out rightMotor);

GamePad.SetVibration(playerIndex, leftMotor, rightMotor);
}

public void GetVibration(out float leftMotor, out float rightMotor)
{
leftMotor = 0.0f;
rightMotor = 0.0f;

foreach (Vibration vibration in vibrations)
{
leftMotor = Math.Max(leftMotor, vibration.LeftMotor);
rightMotor = Math.Max(rightMotor, vibration.RightMotor);
}
}
}

Tuesday, August 11, 2009

sfxr - A Great Free Sound Effect Generator



One of the problems I always have when I'm working on a game project is sourcing sound effects. Recently, I came across sfxr, a very nice program for creating sound effects. It was a great help when I was working on Saucer Pilots.

It is basically a little software synth with a bunch of variables you can tweak to generate a wide variety of sounds. A helpful set of presets are provided for generating commonly needed classes of video game sounds.

Once you find something you like, export it to a WAV file and you are good to go. Very handy little utility.

Thursday, August 6, 2009

Saucer Pilots - XNA Dream Build Play 2009



Whew! I just squeaked an entry in under tonight's deadline for the 2009 XNA Dream Build Play competition.

Last Monday, I was messing around with a new "flying saucer" game prototype idea. It seemed like it had promise, so I kicked into high gear to try to get something submittable for the competition. I initially got the submission date wrong, and thought I had until this Sunday. A few days ago, I realized my error and really had to step things up.

The competition was a good impetus to get me to actually finish something. I'm generally much more fond of starting things than completing them...

Anyway, the game is "Saucer Pilots" - a 1-4 player saucer flying game with five different gameplay modes. Xbox LIVE avatars are used for saucer pilot models (avatars do not appear in this video, as they are not visible on the PC). The physics implementation I'm using is Farseer.

Overall, I think it turned out pretty well for a week-and-a-half effort. The only thing that made that timeline possible was the framework on top of XNA that I've developed over the past year. And help from Sherry.

Oh, and did I mention that it has fishing?

Tuesday, March 24, 2009

Creating Playlists With Linq - Another Great Use For CodeDomProvider



I recently found a new use for dynamically-compiled assemblies using CodeDomProvider. Combined with Linq, it enables the ultimate flexibility in music playlist generation.

My "Code Playlists", are snippets of code that can do whatever they want as long as they return an enumerable collection of song objects. As you can see above, simple Linq expressions make it really easy to generate pretty much any collection of songs that my music metadata can support.

Thursday, March 12, 2009

WPF Progress Bars Revisited

Because some people were having trouble understanding how to use my original WPF progress bar code, I thought I'd post an updated, easier to use version.

The problems people were having pretty much all centered around not understanding how to run their code in a background thread. My updated version handles this for you.

First, please have a look at the original post, as you will need some of the code from there:

WPF Progress Bars

The XAML and IProgressContext code is still the same, but here is an updated partial class for the progress dialog, along with a delegate definition that it requires:

public delegate void ProgressWorkerDelegate(IProgressContext progressContext);

public partial class ProgressDialog : Window, IProgressContext
{
private bool canceled = false;
private ProgressWorkerDelegate workDelegate = null;

public bool Canceled
{
get { return canceled; }
}

public ProgressDialog() : this(null)
{            
}

public ProgressDialog(ProgressWorkerDelegate workDelegate)
: this(workDelegate, false)
{
}

public ProgressDialog(ProgressWorkerDelegate workDelegate, bool startInBackground)
{
this.workDelegate = workDelegate;

InitializeComponent();

CancelButton.Click += new RoutedEventHandler(CancelButton_Click);

if (workDelegate != null)
{
if (startInBackground)
{
new Thread(new ThreadStart(StartWork)).Start();
}
else
{
StartWork();
}
}
}

private void StartWork()
{
workDelegate(this);
}

void CancelButton_Click(object sender, RoutedEventArgs e)
{
canceled = true;
CancelButton.IsEnabled = false;
}

public void UpdateProgress(double progress)
{
Dispatcher.BeginInvoke(DispatcherPriority.Background,
(SendOrPostCallback)delegate { Progress.SetValue(ProgressBar.ValueProperty, progress); }, null);
}

public void UpdateStatus(string status)
{
Dispatcher.BeginInvoke(DispatcherPriority.Background,
(SendOrPostCallback)delegate { StatusText.SetValue(TextBlock.TextProperty, status); }, null);
}

public void Finish()
{
Dispatcher.BeginInvoke(DispatcherPriority.Background,
(SendOrPostCallback)delegate { Close(); }, null);
}
}


The key difference is that you can now pass the dialog a delegate that will be used to do your work, and can have it automatically get run in the background.

To use it, do something like this:

ProgressDialog progressDialog = new ProgressDialog(MyWorkFunction, true);
progressDialog.ShowDialog();


Note that I am using ShowDialog() above to keep the operation modal. If you want the rest of your UI to still accept input, use Show() instead.

Your function to actually do the work would look something like this:

void MyWorkFunction(IProgressContext progressContext)
{
for (int i = 0; i < 100; i++)
{
if (myProgressContext.Canceled)
break;

progressContext.UpdateProgress((double)i / 100.0);
progressContext.UpdateStatus("Doing Step " + i);
}

progressContext.Finish();
}

Wednesday, March 4, 2009

Gravity Game

Gravity Game

My latest hobby project is a gravity-based game in the tradition of old classics like Lunar Lander, Gravitar and Thrust. Currently, the mechanics are pretty simple. Shoot things, avoid being shot, crashing, or being hit by an asteroid, and use your tractor beam to move around colored balls that act as keys to remove barriers.

I've also been playing with water dynamics.

Gravity Game

The game is XNA-based, and I'm using Farseer as my physics engine.

Monday, January 19, 2009

More FM Synth Progress



Ok, my "little" FM synthesizer project is now officially a bit out of control. It has grown to include a drum pattern editor, chord progressions and a mini song sequencer. WPF, with its clean separation between the UI and the underlying code is perfect for doing audio interface work. Too bad the digital audio world is still mired in C++...

Monday, January 12, 2009

WPF Layout Woes - MeasureOverride killed Schrödinger's Cat

A struggle I'm currently having with layout in WPF reminds me of the Observer Effect in physics. It turns out that calling Measure() on a child object has side effects. You would think that the measuring phase of layout would simply involve asking the child how big it wants to be. Instead, you need to pass it a size constraint.

Normally, that would not be a problem, but what if you want to size a set of children based on their relative desired sizes? Catch-22. You can't call Measure() on a child without knowing the maximum size you want it to be. And, in my situation, I don't know what size I want it to be without first measuring it. And, once you have measured an objected, its size "sticks" - changing it when you do Arrange() only alters the size of the clip rectangle (an important distinction if the child actually can be variably sized - such as content within a ScrollViewer).

Very frustrating.

I have found a fairly complicated way of working around the isue, but it seems like a fundamental (if not often encountered) flaw in the way WPF layout works.