top of page
Search

Log 10: Not-Quite-Quick Morphing

  • Writer: Kassandra McCormack
    Kassandra McCormack
  • Nov 19, 2023
  • 5 min read

I decided to create the quick morph first and I, personally, think I did a decent job:

ree

First I had to outline the transformation and figure out how I wanted it to go. I settled on the ranger using their weapon to shatter a crystal that disperses into particles and then forms the armor. I made the initial animation to be too quick to tell what is going on, so I had to set the animation to play at only 0.3x speed. When it comes time to make the final animations it will have to be much clearer.


Next I had to consider how I was going to go from 1 visible model, make it invisible and go to 11 separate visible models. If I used a method similar to the weapon, I would need to set up and animate (programmatically) either 1 large box or a bunch of smaller boxes. Or, I could modify the material code to work with a single plane and just render or not based on what side of the plane the mesh was on, that way I could only need to animate the planes position and not position and scale of boxes. Just a single dot product, easy. In order to leave the materials capable of also working with the boxes, I just had to take the max (civilian) or min (ranger) of the plane and box comparison, that way I only need 1 material for any animation I want to do in the future (read: for the longer morphing sequence).


Next was struggling with the particles. For each stage of the particles, I had to make separate emitters so they could move differently during different stages of the animation. The first stage, the crystal burst I could just copy much of the logic from the Niagara Dissolve Effects package I had bought. I had initially bought it so I could see the logic for its own Morph effect, but I couldn't use it for multiple reasons: 1) I had to go between a static mesh and a skeletal mesh, 2) there is a stage in the middle of the movement that is dependent on the plane's movement, 3) the package uses positions as vectors and not the Position data structure type (and I did not learn about the "Convert-Vector-to-Position" node while I was doing this).


The effect I am going for is that the particles when they spawn, they choose one of the UV coordinates on a given mesh, then hang in the air until the plane gets within a certain distance of the UV coordinate's world position then zip to the mesh.


I wound up creating 4 custom built modules for the second stage of the particle system. The first simply picks a random UV coordinate on a mesh and stores its world space location as a Position and the chosen coordinates as a UV Mesh Tri Coordinate. The second tracks the chosen UV Mesh Tri Coordinate and updates its world space location as it moves each update as a Position. The third tracks the animated plane's location and rotation and if the plane is within a set distance of the chosen coordinate returns true or not. The fourth takes that bool value, and if it is true lerps the particle from its spawn Position to the UV Position from the second module and does it over a given amount of time.


For the longest time I could not get the particles to move at all, because it was not tracking the plane correctly and never returning true. Then I had them vibrating between positions each frame. I found this is because in order to lerp over a set amount of time, I need to keep track of what age the particle was when the plane is close enough, and that age kept getting set one frame and then unset the next. So I had to add an additional check to not change it once its value was greater than 0.

ree

I also hit a weird graphical bug where the particles would overshoot the mesh if their lifetimes were too long, so to correct for that I used a "Saturate Float" node to make sure the lerp value could not go past 1.

ree

And finally...the morph still was not working correctly. Oh, the particles were fine, but I discovered if the player either starts moving or stops moving while the morph animation is going on, they will play the animation a second time. To fix this I wound up just deleting a part of the AnimGraph that had been set up to try and play the animation for both the full body and the upper body (for not moving and moving respectively). This was because Unreal does not calculate irrelevant graph nodes, so once the movement state changed it detects it as completely entering a new state from the beginning. So now the character is angled slightly to the side, but unless I tell someone that was not supposed to be the case it looks intentional so I left it as is instead of giving myself more headaches.


Speaking of headaches, the particles still didn't want to behave properly. They would start dying as soon as they hit 1 second of lifetime (unless the plane had already intersected their mesh by that point) as somehow the fourth module was calculating 1 + -1 >= 1 as true. Then I figured is should consider K.I.S.S. (Keep It Simple Stupid) and just have an intermediate emitter that handled the time the particles should hang in the air. And then I remembered why I hadn't done that originally: I could not send the UV Tri Mesh Coordinates to the child particle to have it move to the correct position through the death event. In order to do that, I would probably have to try creating a custom Niagara particle event in C++, but I had challenged myself to constrain myself only to Blueprints for this project, and if I couldn't do something I wanted in Blueprints then to change my design to what I could do instead. It was a hard lesson I had to learn in school and I still struggle with, hence wanting to stick with the challenge.


Eventually I solved it by comparing the final lerp position to the target position, and if the distance between them is less than or equal to a tiny buffer amount (to account for float rounding inaccuracies) then return true to have the particle killed.

ree

From there it was a matter of balancing timings and plane distance. Also changing the crystal burst's material from the Synty material to the default sprite material, that way I could get the proper color for the burst.



 
 
 

Comments


bottom of page