VibrationManager

Dec 8, 2011 at 12:26 AM

Another little quickie i just wrote for my game - a helper manager for gamepad vibration. 
Allows multiple vibrations to be added for each controller and will resolve the most significant active one for each motor.
Vibrations can be fixed or have a linear falloff.
(Since my game is single player it's untested with multiple controllers at the moment)
Still working on the instancing content pipeline stuff, but having to learn far more about the content pipeline and model formats than i ever wanted to know 
so having a break from it while i get other things in place.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SynapseGaming.LightingSystem.Core;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Indiefreaks.Xna.Input
{
    public class Vibration
    {
        internal float InitialLeftMotor { get; set; }
        public float LeftMotor { get; set; }
        internal float InitialRightMotor { get; set; }
        public float RightMotor { get; set; }
        public float TimeRemaining { get; set; }
        public PlayerIndex PlayerIndex { get; set; }
        public bool FallOff { get; set; }
        public float Duration { get; set; }

        public Vibration(PlayerIndex playerIndex, float leftMotor, float rightMotor, float duration, bool fallOff)
        {
            InitialLeftMotor = leftMotor;
            LeftMotor = leftMotor;
            InitialRightMotor = rightMotor;
            RightMotor = rightMotor;
            TimeRemaining = duration;
            PlayerIndex = playerIndex;
            FallOff = fallOff;
            Duration = duration;
        }
    }

    public class VibrationManager : IManagerService, IUpdatableManager
    {
        private IManagerServiceProvider _sceneInterface;
        List<Vibration> _activeVibrations;
        List<Vibration> _toRemove;

        public VibrationManager(IManagerServiceProvider sceneInterface)
        {
            _sceneInterface = sceneInterface;
            ManagerProcessOrder = 50;
            _activeVibrations = new List<Vibration>();
            _toRemove = new List<Vibration>();
        }

        public void Submit(Vibration vibration)
        {
            _activeVibrations.Add(vibration);
        }

        #region Implementation of IManager

        public int ManagerProcessOrder { get; set; }

        public Type ManagerType
        {
            get { return typeof(VibrationManager); }
        }

        public void ApplyPreferences(ISystemPreferences preferences)
        {
        }

        public void Clear()
        {
        }

        public IManagerServiceProvider OwnerSceneInterface
        {
            get { return _sceneInterface; }
        }

        #endregion

        #region Implementation of IUnloadable

        public void Unload()
        {
            Clear();
        }

        #endregion

        #region Implementation of IUpdateableManager

        public void Update(GameTime gameTime)
        {
            _toRemove.Clear();
            foreach(Vibration vibration in _activeVibrations)
            {
                vibration.TimeRemaining -= (float)gameTime.ElapsedGameTime.TotalMilliseconds;
                if (vibration.TimeRemaining < 0)
                {
                    _toRemove.Add(vibration);
                }
                else if (vibration.FallOff)
                {
                    vibration.LeftMotor = MathHelper.Lerp(vibration.InitialLeftMotor, 0, 1-(vibration.TimeRemaining / vibration.Duration));
                    vibration.RightMotor = MathHelper.Lerp(vibration.InitialRightMotor, 0, 1-(vibration.TimeRemaining / vibration.Duration));
                }
            }
            foreach(Vibration vibration in _toRemove) {
                _activeVibrations.Remove(vibration);
            }
            float[] leftMotors = {0,0,0,0};
            float[] rightMotors = {0,0,0,0};

            foreach (Vibration vibration in _activeVibrations)
            {
                leftMotors[(int)vibration.PlayerIndex] = Math.Max(leftMotors[(int)vibration.PlayerIndex], vibration.LeftMotor);
                rightMotors[(int)vibration.PlayerIndex] = Math.Max(rightMotors[(int)vibration.PlayerIndex], vibration.RightMotor);
            }

            GamePad.SetVibration(PlayerIndex.One, leftMotors[0], rightMotors[0]);
            GamePad.SetVibration(PlayerIndex.Two, leftMotors[1], rightMotors[1]);
            GamePad.SetVibration(PlayerIndex.Three, leftMotors[2], rightMotors[2]);
            GamePad.SetVibration(PlayerIndex.Four, leftMotors[3], rightMotors[3]);
        }

        #endregion
    }
}


Coordinator
Dec 9, 2011 at 10:14 AM

I've been thinking and I wondered if it wouldn't make sense to add this Vibration feature to the PlayerInput class instead using a simple method that would let you specify the forces and how long it would vibrate like:

PlayerInput.Vibrate(float leftMotor, float rightMotor, float duration, bool fallOff)

Moreover, we could then have another signature that would let the developer use a delegate to return the computation made during the duration:

PlayerInput.Vibrate(float leftMotor, float rightMotor, float duration, Func<float> computeValues)

We'll then use the returned value from the delegate and apply it to the forces on the motors allowing then the developer to compute a simple Sin function for instance... ;)

Dec 9, 2011 at 12:17 PM
Edited Dec 9, 2011 at 12:20 PM

Consider it done - sounds like a much nicer api. I'll implement it along those lines and post a patch.

I love the idea of using a delegate to allow the user to provide a modifier.

Dec 9, 2011 at 5:29 PM
Edited Dec 9, 2011 at 7:28 PM

Is there a reason behind chosing to pass gameTime.TotalGameTime.Ticks to the PlayerInput.Update method rather than the standard xna GameTime struct? I can see that the tick count is used to identify a helddown period for buttons which i can see would be useful for press and hold type scenarios.

The vibration update is dependant on the elapsed time since the last update which as the framework stands would require me to maintain a previous tick count or modify InputManager.Update(long tick) (Which then really becomes obsolete since all the PlayerInput.Update calls can move back up into InputManager.Update(GameTime)) and PlayerInput.Update(long tick) to use GameTime as a parameter and then modify the UpdateState calls within the PlayerInput class to pass through the TotalGameTime.Ticks since these appear to be the only place which the ticks value is used. I can maintain a previous tick within the vibration code which is simple enough but seems somewhat messy given that that information is already available in the GameTime class if it was passed down 1 stage further.

What would be your preference in this situation ?

Cheers

S.

Edit - I've uploaded a patch with the modifications as described above - it's only a few lines of code changed so easy to switch around if necessary,

Coordinator
Dec 10, 2011 at 7:32 PM

There are no real reasons on using the Ticks except the reasons you mentioned (by the way, well played digging into the code :p).

I guess there are no opposition to apply your patch so I'll make sure to add it for the v0.9 release. Thanks.

Coordinator
Dec 11, 2011 at 10:27 AM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.