0
\$\begingroup\$

The Goal (for context)

Get input from a USB joystick device in the Unity Game Engine and activate Force Feedback functionality on the joystick from Unity.

The Method

I am using the SharpDX.DirectInput managed library to access DirectX. Using Direct Input was listed as the only USB solution the device currently supports according to the response I got from the company who makes the device. (The device was initially developed for use with specific simulator applications, not generic use. I did not make the decision of using this device with the simulation we are developing).

From all my research, SharpDX seems to be the best option as it's the most recently supported managed wrapper for DirectX and it was recommended by the the device's support team.

Where I'm At

After a week of struggling I have been able to successfully get input from the device via SharpDX. It's been an uphill battle against limited documentation and my own inexperience in this area but I was able to finally decipher examples to get that working. I have also been able to confirm, via SharpDX, that the device has force feedback capabilities.

However, I can't find even a single code example for sending force feedback commands via SharpDX even though it is supposedly possible (there are various structs, enums, classes, and methods that all seem to be related to it within the SharpDX API).

The only code sample I found was using the DirectX 9.0 for managed code API. I was not able to translate it to SharpDX. (The answer also included a link to two other supposedly useful examples, one here, and one resurrected from the Internet Archive here both of which appear to be using the DirectX 9.0 API).

I have every reason to believe that it is possible but haven't been able to figure it out of my own accord. Any input, advice, or directions would be very much appreciated. Thanks in advance!

\$\endgroup\$
0

1 Answer 1

1
\$\begingroup\$

I have succeeded in my efforts and wanted to share my code incase anyone else is ever doing this again.

To recap, I am scripting in C# using the SharpDX.DirectInput library. Additionally, for context, the device I am interfacing with is a Brunner CLS-E MK II Rudder with Toe Brakes which has been put in DirectX Mode. The 'rudderDeviceName' in this case is thus "CLSE MK II Rudder DirectX".

This script is placed on a GameObject in the Unity Game Engine.

Please note that I'm still working through the exact units which the effect parameters need to be sent in and this only includes and example of the periodic force effect. That being said, this should go a long way to getting someone started with what they are trying to do.

using System;
using System.Linq;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using SharpDX.DirectInput;

public class PedalManager : MonoBehaviour
{
    [SerializeField] string rudderDeviceName;
    [SerializeField] TextMeshProUGUI steerText;
    [SerializeField] TextMeshProUGUI leftBrakeText;
    [SerializeField] TextMeshProUGUI rightBrakeText;

    Joystick rudder;
    Effect effect;
    EffectParameters effectParams;
    PeriodicForce periodicForce;
    Envelope envelope;

    int[] actuators;
    int[] dirVector;

    [SerializeField] float duration = 5;
    [SerializeField] float fadeIn = .2f;
    [SerializeField] float fadeOut = .2f;
    [SerializeField] int direction = 0;
    [SerializeField] float gain = 50;
    [SerializeField] float amplitude = 50;
    [SerializeField] float frequency = 10; //hz
    [SerializeField] float phase = 0;
    [SerializeField] float offset = 0;

    int AttackTime => (int)(duration / 2 * fadeIn * 10000);
    int ReleaseTime => (int)(duration / 2 * fadeOut * 10000);
    int Direction => (direction + 180) * 100;
    int Gain => (int)(gain * 100);
    int Duration => (int)(duration * 1000000); //seconds -> microseconds
    int Amplitude => (int)(amplitude * 100);  
    int Period => (int)(1.0 / frequency * 1000000);
    int PeriodicOffset => (int)(offset * 100);
    int Phase => (int)(phase * 100);

    bool isAcquired = false;

    private void Start()
    {
        SetupDevice();
        AcquireDevice();
        InitTestEffect();
    }
    private void OnEnable()
    {
        AcquireDevice();
    }
    private void Update()
    {
        if(rudder != null && isAcquired)
        {
            JoystickState joyState = rudder.GetCurrentState();
            steerText.text = joyState.X.ToString();
            leftBrakeText.text = joyState.RotationX.ToString();
            rightBrakeText.text = joyState.RotationY.ToString();
        }
    }
    private void OnDisable()
    {
        StopEffect();
        ReleaseDevice();
    }

    void SetupDevice()
    {
        DirectInput directInput = new DirectInput();

        Debug.Log(string.Format("Searching for {0}...", rudderDeviceName));
        DeviceInstance rudderInstance = directInput
            .GetDevices(SharpDX.DirectInput.DeviceType.Joystick, DeviceEnumerationFlags.AllDevices)!
            .First(d => d.InstanceName == rudderDeviceName);

        if (rudderInstance == null) 
        { 
            Debug.Log("Rudder device not found, cannot continue."); 
            return; 
        }
        Debug.Log(string.Format("Found device \"{0}\"", rudderInstance.InstanceName));

        rudder = new Joystick(directInput, rudderInstance.InstanceGuid);


        rudder.Properties.AxisMode = DeviceAxisMode.Absolute;
        rudder.SetCooperativeLevel(GetActiveWindow(), CooperativeLevel.Exclusive | CooperativeLevel.Background);
        rudder.Properties.BufferSize = 128;
    }
    void AcquireDevice()
    {
        if(rudder != null && !isAcquired)
        {
            rudder.Acquire();
            isAcquired = true;
            Debug.Log("Device acquired.");
        }
    }
    void ReleaseDevice()
    {
        if (rudder != null && isAcquired) 
        { 
            rudder.Unacquire();
            isAcquired = false;
            Debug.Log("Device Released.");
        }
    }

    void InitTestEffect()
    {
        effectParams = new EffectParameters();
        periodicForce = new PeriodicForce();
        envelope = new Envelope();
        CreateTestEffect();
    }
    void CreateTestEffect()
    {
        if (!isAcquired)
            return;
        if (effect != null)
        {
            effect.Stop();
            effect.Unload();
            effect.Dispose();
        }
        var deviceObjs = rudder.GetObjects().ToList().Select(o => o.ObjectId).ToList();
        List<int> intList = deviceObjs
            .FindAll(o => o.Flags.HasFlag(DeviceObjectTypeFlags.ForceFeedbackActuator))
            .Select(o => (int)o).ToList();
        actuators = intList.ToArray();
        dirVector = Enumerable.Repeat(Direction, intList.Count).ToArray();

        envelope.AttackLevel = 0;
        envelope.FadeLevel = 0;
        envelope.AttackTime = AttackTime;
        envelope.FadeTime = ReleaseTime;

        effectParams.Flags = EffectFlags.Polar | EffectFlags.ObjectIds;
        effectParams.Duration = Duration;
        effectParams.Gain = Gain;
        effectParams.SetAxes(actuators, dirVector);
        effectParams.SamplePeriod = 0;
        effectParams.StartDelay = 0;
        effectParams.TriggerButton = -1;
        effectParams.TriggerRepeatInterval = 0;
        effectParams.Envelope = envelope;

        periodicForce.Magnitude = Amplitude;
        periodicForce.Offset = PeriodicOffset;
        periodicForce.Phase = Phase;
        periodicForce.Period = Period;

        effectParams.Parameters = periodicForce;

        Guid guid = EffectGuid.Sine;

        effect = new Effect(rudder, guid, effectParams);
        Debug.Log("Periodic effect created.");
    }

    [ContextMenu("Play FFB Effect")]
    public void PlayEffect()
    {
        Debug.Log("Starting FFB effect");
        CreateTestEffect();
        effect.Start(1);
    }
    [ContextMenu("Stop FFB Effect")]
    public void StopEffect()
    {
        Debug.Log("Stopping FFB effect");
        if (effect != null)
            effect.Stop();
    }

    [System.Runtime.InteropServices.DllImport("user32.dll")]
    private static extern IntPtr GetActiveWindow();
}

```
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .