Click here to Skip to main content
15,867,453 members
Articles / Multimedia / DirectX
Article

Force Feedback in Managed DirectX

Rate me:
Please Sign up or sign in to vote.
4.54/5 (8 votes)
16 Mar 2008CPOL3 min read 78.8K   1.5K   14   16
A C# example of working Force Feedback in Managed DirectX

Introduction

Managed DirectX is a great way to write DirectX applications in .NET languages, and most of the Managed DX libraries are well documented, robust, and have numerous examples floating around the Internet. I've been coding Managed DX off and on since it first arrived in 2002, but never messed with force feedback until recently.

Imagine my surprise to find that all the public examples for force feedback code in C# error out with my game pad (Logitech 2 Rumble). Many other people have posted on various Internet forums about this issue with Managed DirectX and other game pads, so this appears to be a very long running bug in the Managed DirectInput libraries. Though there are a number of people reporting this issue, I was not able to find a solution that worked for me anywhere -- so here's my own solution.

The attached project provides examples of how to create and trigger several different forces, so if you're new to force feedback in C#, this has everything you need to get started all in one place.

Background

If you'd like to see Microsoft's standard example (which doesn't work for me), visit this link.

The solution I eventually found was unique, but triggered in part by a blog post by Ayucar.

Using the Code

The attached project has the entire code, but here are the (somewhat simplified) highlights in case you are already familiar with the examples but just keep getting that "Value does not fall within expected range" error.

Basic Device Initialization

C#
Device Dev = null;
    
foreach ( DeviceInstance instance in Manager.GetDevices( DeviceClass.GameControl,
    EnumDevicesFlags.AttachedOnly ) )
{
    Dev = new Device( instance.InstanceGuid );
}
            
Dev.SetDataFormat( DeviceDataFormat.Joystick );
Dev.SetCooperativeLevel( Parent, CooperativeLevelFlags.Background |
    CooperativeLevelFlags.Exclusive );
Dev.Properties.AxisModeAbsolute = true;
Dev.Properties.AutoCenter = false;
Dev.Acquire();

The above is pretty typical of DirectInput examples. The AutoCenter = false line is particularly needed for force feedback. The Parent variable in the SetDataFormat line must be a System.Windows.Forms.Control.

Finding the Axes

C#
int[] axis = null;

// Enumerate any axes
foreach ( DeviceObjectInstance doi in Dev.Objects )
{
    if ( ( doi.ObjectId & (int)DeviceObjectTypeFlags.Axis ) != 0 )
    {
        // We found an axis, set the range to a max of 10,000
        Dev.Properties.SetRange( ParameterHow.ById,
        doi.ObjectId, new InputRange( -5000, 5000 ) );
    }

    int[] temp;

    // Get info about first two FF axii on the device
    if ( ( doi.Flags & (int)ObjectInstanceFlags.Actuator ) != 0 )
    {
        if ( axis != null )
        {
            temp = new int[axis.Length + 1];
            axis.CopyTo( temp, 0 );
            axis = temp;
        }
        else
        {
            axis = new int[1];
        }
    
        // Store the offset of each axis.
        axis[axis.Length - 1] = doi.Offset;
        if ( axis.Length == 2 )
        {
            break;
        }
    }
}

Everything until the int[] temp is declared is your standard DirectInput axes initialization -- you have to do those things regardless of whether or not you are using force feedback. The axis array calculation is force-feedback-specific, and is all taken directly from the Microsoft examples (I didn't add anything new there).

Creating a Force

C#
InitializeForce( Dev, EffectType.ConstantForce, axis,
    10000, EffectFlags.ObjectOffsets | EffectFlags.Spherical, 250000 ) );
        
public static EffectObject InitializeForce( Device Dev, EffectType Type,
int[] Axis, int Magnitude, EffectFlags Flags, int Duration )
{
    EffectObject eo = null;
    Effect e;

    foreach ( EffectInformation ei in Dev.GetEffects( EffectType.All ) )
    {
        if ( DInputHelper.GetTypeCode( ei.EffectType ) == (int)Type )
        {
            e = new Effect();
            e.SetDirection( new int[Axis.Length] );
            e.SetAxes( new int[1] );

            e.EffectType = Type;
            e.ConditionStruct = new Condition[Axis.Length];
            e.Duration = Duration;
            e.Gain = 10000;
            e.Constant = new ConstantForce();
            e.Constant.Magnitude = Magnitude;
            e.SamplePeriod = 0;
            e.TriggerButton = (int)Microsoft.DirectX.DirectInput.Button.NoTrigger;
            e.TriggerRepeatInterval = (int)DI.Infinite;
            e.Flags = Flags;
            e.UsesEnvelope = false;

            // Create the effect, using the passed in guid.
            eo = new EffectObject( ei.EffectGuid, e, Dev );
        }
    }

    return eo;
}

Here's where my solution comes in. The line that normally errors out is e.SetAxes(new int[Axis.Length]); It usually gives an exception "Value does not fall within expected range", which is evidently a bug in Managed DirectX (at least affecting some game pads). Changing that line so that it always initializes an axis array of length 1 instead of length 2 (as the Microsoft example would do) fixes this issue seemingly without any other side effects to performance or function.

I should note that I have not been able to successfully load FFE files in Managed DirectX due to this same problem. Presumably, in the inner initialization code when loading from FFE, it is trying to load two axes there, as well. This problem exists with all versions of Managed DirectX, as far as I can tell, at least up through March 2008 when this article was written.

Points of Interest

Force feedback is underutilized in most PC games, possibly because the technology was not available in low end game pads until relatively recently. Adding force feedback capabilities to your games is a great way to provide a more immersive experience, and a minor way to stand out in the PC games market.

History

  • Version 1: March 16, 2008

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO Arcen Games, LLC
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questioni have a problem Pin
mechanicalsoldier9-Feb-14 12:27
mechanicalsoldier9-Feb-14 12:27 
QuestionContinuous Force adjustment Pin
bigbro_198522-Jun-11 19:00
professionalbigbro_198522-Jun-11 19:00 
QuestionRe: Continuous Force adjustment Pin
bigbro_198522-Jun-11 21:01
professionalbigbro_198522-Jun-11 21:01 
QuestionDriver Error? Pin
Niles Davis14-Feb-10 22:30
Niles Davis14-Feb-10 22:30 
AnswerRe: Driver Error? Pin
34206604625-Feb-10 10:42
34206604625-Feb-10 10:42 
GeneralGetting FFB information Pin
survcopt6-Dec-09 22:56
survcopt6-Dec-09 22:56 
GeneralVisual Basic 2008 version Pin
chris9913-Oct-09 9:33
chris9913-Oct-09 9:33 
GeneralExcellent! Pin
Johan Bergelo1-Apr-09 3:51
Johan Bergelo1-Apr-09 3:51 
GeneralRe: Excellent! Pin
Chris McElligott Park1-Apr-09 4:13
Chris McElligott Park1-Apr-09 4:13 
GeneralA good & simple example Pin
Marcin Śmiałek15-Sep-08 21:12
professionalMarcin Śmiałek15-Sep-08 21:12 
GeneralRe: A good & simple example Pin
Chris McElligott Park16-Sep-08 1:54
Chris McElligott Park16-Sep-08 1:54 
GeneralRuntime Error Pin
Nate Trimble1-Aug-08 6:26
Nate Trimble1-Aug-08 6:26 
When I try to run the program inside VS 2008 I get this error.

LoaderLock was detected
Message: DLL 'C:\WINDOWS\assembly\GAC\Microsoft.DirectX.DirectInput\1.0.2902.0__31bf3856ad364e35\Microsoft.DirectX.DirectInput.dll' is attempting managed execution inside OS Loader lock. Do not attempt to run managed code inside a DllMain or image initialization function since doing so can cause the application to hang.

This is a new error for me. Ever seen this when debugging your app? Thanks for the interesting code. I've been trying to figure out Force Feedback for a bit.
GeneralRe: Runtime Error Pin
Nate Trimble1-Aug-08 6:33
Nate Trimble1-Aug-08 6:33 
GeneralMessage Closed Pin
1-Aug-08 7:49
Chris McElligott Park1-Aug-08 7:49 
GeneralRe: Runtime Error Pin
Nate Trimble1-Aug-08 8:18
Nate Trimble1-Aug-08 8:18 
GeneralRe: Runtime Error Pin
Chris McElligott Park1-Aug-08 8:33
Chris McElligott Park1-Aug-08 8:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.