JaydenMaalouf / JoystickPlugin

Native Input Joystick Plugin for Unreal Engine 4/5

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Restructure the ForcedFeedbackEffect class

JaydenMaalouf opened this issue · comments

To make the base classes more specific to their data structures, we should make the base effect templated and let the compiler handle the heavy lifting.

As part of this, we should create data structures specific to each effect type. Potentially create a base struct if there is a lot of overlap.

Templating is not possible with blueprintable classes. Still want to split the data structures out though.

Restructure has been done in the latest commit to develop

I'm not a big fan of effect input data being int instead of float. It's quite a pain to work with int (especially in BP), we tend to work mostly with float and not care about actual device range. The previous [-1..1] or [0..1] ranges were perfect as far as I'm concerned. Now I have to have to convert my floats to int16, which I'm guessing is what most users will end up doing, so I'd say it's better if the plugin handles it instead.

Other than that it all looks great, but I'm working from home today so I don't have any decide to test this on. I'll do that tomorrow.

Yeah, I agree for some variables - it makes sense - like Length (duration of the effect). It's measured in milliseconds but multiplying it by 100 from a float would be a nicer UX than someone having to think how many milliseconds in a second hahah

I'll make some changes to the data structures tomorrow - with more thought on how the data is being consumed.

Side note: I still haven't implemented LeftRight or Custom effects properly. The data structures exist but don't actually do anything in SDL yet.

As part of this, I was thinking of exposing several implementable events in the effect blueprints.

Some that come to mind are:

  • OnInitialised
  • PreStartedEffect
  • PostStartedEffect
  • OnStartedEffect
  • PreStoppedEffect
  • PostStoppedEffect
  • OnUpdated
  • OnDestroyed

I did also want to have some overridable function for an effect that has an infinite duration.

For example, Steering Wheel feedback. You can either drive the effect each frame which I don't think is optimal - or you can have an infinite duration effect that every frame is able to query whatever and update the SDL effect accordingly.

I had this previously with the GetEffect function that was BlueprintImplementable. Basically allowed you to get the base data of the effect and add modifications to effect data before it is sent to SDL.

However, I think hooking into one of the Pre/OnUpdate events should be enough. The player vehicle will call UpdateEffect on tick or on a timer or whatever, then the effect will apply it's modifications to it's data and send it to SDL that way. This should be cheap enough.

I did consider having the effect have their own Tick (ie. Make them actors). But then we'd have effects within scenes that don't do much more than provide data to SDL. It would be nice for initialisation on BeginPlay, though.

Sorry for the brain dump hahaha

Yeah, I agree for some variables

I'd say most variables, except obvious ones like DeviceId :D I think the most obvious and commonly understood ranges always are [0..1] and [-1..1]. That's why they're used in UE for axis values, even though underlying APIs work in int16 ranges. I think it's better to stick with float ranges for UE consistency.

I did consider having the effect have their own Tick (ie. Make them actors). But then we'd have effects within scenes that don't do much more than provide data to SDL. It would be nice for initialisation on BeginPlay, though.

In my latest implementation, all user-hardware (e.g. FFB, actuators) is handled by dedicated components on the controller. For example, here's my component for FFB, where I can update the effect data and call UpdateEffect whenever I want, and make sure they're both always done at the same time.

image

With it being a component, I can even set the tickrate if I want to. The component could also have multiple effects and swap between them, or offer a new abstraction level with custom effect (e.g. crash, drift) that could be triggered from gameplay or anything really.

My controller has components for all user-hardware, and just has to call Initialize on them on its On Possess event. Then, all components are self-sufficient and don't require more input from anyone.

image

So as far as I'm concerned, the current design is pretty good and very flexible. With FFB being UObject, the end-user really has the freedom to implement effects as they wish. Maybe it's too much freedom though, and for novice user they might have trouble finding just how to get it working properly.

Great work!

all user-hardware (e.g. FFB, actuators) is handled by dedicated components

Thats an awesome idea! I've just implemented that in the latest commit.

I also reverted all majority of the data changes back to float (I think Custom is the only one with ints now).

I've made the system very extensible but also very basic if you just need to run an effect on startup.
Can also now spawn effects with the a single function (I still personally prefer Construct Object from Class but I'll keep it anyway).
image

Thanks heaps for the inspiration for this!

I've merged your changes on my fork and tested at my small work setup: everything is working as expected 👍

I've encountered a few issues though. First, when I use the Create Effect function, my following calls to Set Effect Data fails with the following (all in editor BTW)

Blueprint Runtime Error: "Attempted to access missing property 'EffectData'. If this is a packaged/cooked build, are you attempting to use an editor-only property?". Blueprint: BP_FFB_Component Function: Execute Ubergraph BP FFB Component Graph: EventGraph Node: Set

If I use the Construct Object from Class (which I also prefer), it works fine though. I've made sure that I provide the correct Class Type and that my Effect variable has the right type. It could be caused by the DeterminesOutputType = "classType" meta specifier on the function, but I'm not really familiar with that.

Another weird thing I noticed while inheriting from the new Component, is that there's something messing up the call order in BeginPlay. Here's a small example:

image

You would assume that Effect would be valid as it's created by the parent's BeginPlay, but it's not. If I put breakpoints on both the BP's PrintString and in the C++ parent component's BeginPlay, the PrintString breakpoint is hit first! So in the BP BeginPlay, I don't have access to a valid Effect. I'm not really sure what causes this.

If you want I can try to dig up more information about both issues.

Also, since the Effect in the component is of type UForcedFeedbackEffectBase, user have to manually cast it to their specified type in order to access its data. Really not a big deal, but there might be a way to improve that, like you did with the CreateEffect (which also has its problems, but the idea there is great).

Even with those small issues, amazing work!

Attempted to access missing property 'EffectData'

I have a feeling this is an issue with the way the UFUNCTION macro handles the output type. I don't think it's doing a proper cast and so the property isn't actually accessible. I could fix this with a custom K2 node but I was trying to avoid that haha

Another weird thing I noticed while inheriting from the new Component, is that there's something messing up the call order in BeginPlay

This is actually a fun tidbit with Blueprints. The Blueprint Event BeginPlay isn't actually the same signature that is called natively. If it was, everyones Blueprints would break because they aren't calling Super by default.

The workaround is to use the built in event in the component OnInitalisedEffect. This is called immediately after the effect is ready.

Also, since the Effect in the component is of type UForcedFeedbackEffectBase user have to manually cast it

Yeah, again one of the limitations with Blueprint. I could achieve this if Blueprints allowed templated native classes. The alternative, again, would be a custom K2 node for CreateEffect haha

I will likely be slowing down on changes to the repository as I'm quite happy with how it is right now.
The only other change I want to make to the plugin itself would require a massive restructure that I'm not too bothered to attempt just yet (although I did start a branch for it manager-refactor)

Will prioritise the CI/CD process in the meantime!
Thanks heaps again for your input @brifsttar - it's made this plugin much more user friendly!

Closing this as I believe the work for this has been done.