Frustration

📅 2023-05-01

I’ve been trying to work on slide movement for 4 hours today, and I am not really any closer to getting anywhere. It’s really frustrating, and another reason why you just don’t use middleware unless you’re ready to fight somebody else's code for hours on end. See, the HFPS system has a fairly complicated player controller, with sort of a strange code hierarchy, where everything is either an if (isSliding) else (everything else), presumably because sliding was the last feature they implemented into the player controller. This is a slope controlled slide though, which is not what I’m trying to implement, since I need a flat surface slide, like ice in this case, or other use cases throughout the other environments in ‘Mirrors’.


I did try to implement the slide by adjusting how the slide in the player controller is being set, GPT even had a stab at it, but there were too many conflicting overrides in the existing system to get any sort of behavior that felt correct, due to the aforementioned (in every single blog prior) spaghetti dependencies. So I went from trying to implement it in addition to, then implement it into, and now implement it on top of the current systems in play. But why do I even want a slide?


Well, this is part of expanding the core or "primary" mechanic loops of movement + grabbing by creating interesting variations to the movement systems to build variations of player interaction and feedback. What do most horror games utilize as their primary mechanic loop? Movement. And what do most horror games never expand on when it comes to their primary mechanic loop? Movement. Games like Amnesia or Dead Space buckle this trend of course, but the onslaught of P.T. clones we play on stream always start and end at the most stock standard movement you can get, due to being glorified haunted house simulators. One thing we learn by looking at game genres of all sorts, is that if your primary mechanic loop is jumping, you should try to expand on that as the game progresses. Add interesting things to jump to, have interesting obstacles that make you use jump in different ways. Hell, look at Mario, or Jumpman, and you'll get what I mean. If a horror games central primary mechanic loop is movement + - that movement better be interesting.


But anyway, let’s ignore the sliding mechanic for now. We’ll (hopefully) get back to that. We’ve also added a third mechanic to the primary mechanic loop, that is “throwing” aka, physics throwing. Since we have physics grabbing, it only comes natural that we should expand that by allowing throwing actions. This will flesh out the primary mechanic loop (the moment to moment gameplay) into movement + grabbing + throwing which hopefully leads into a more interesting secondary loop (altar interaction and environmental exploration) and add a more cohesive and interesting overall tertiary loop (the completion of a labyrinth loop).


The throwing itself, is actually a fairly simple implementation, since throwing physics objects is nothing new in games where you can pick up objects. I wanted to build up throw, instead of an instant throw, so how it works is if you’re holding an item, and we hold right click, we start to charge our throw, and then on release we apply the hold duration as a throw force to our object.




As you can see we essentially just release the object on right click release, figure out the direction based on our camera main, and apply some force to its rigidbody. Those who can follow the code can also see we’re adjusting a UI slider here, and that’s because we’ve added some UI. Not just for the throw, but for holding items, and for looking at items in the environment. This took roughly a day of work as well, and after scrapping a 3D text in world alternative that I hated, I went with some raycast checks for the text for when we’re not holding an item:



And an independent hard-coded helditem checker for when we are holding an item:



There were some real headaches here due to the snow system we implemented last time, but we got it working eventually. This was actually due to me using triggerboxes to check if the player was present in a snowpile, instead of what you'd expect and what we use for everthing else, camera raycasts. Purely so when you're holding an item it's easy to add snow. We kept the trigger collisions, but we added some layer masking and raycast ignores, as well as separating the objects into two different controllers to get it working correctly.


One last thing that will be noticed once we press play is some snow particles we added to our impacts when throwing objects. I never did discuss our snow particles in general, which is using Unity’s newish visual graph systems - but I essentially coded a tiny version of that for our hits.




We then wrote a little script that attaches to our greybox item's, so it can detect when to instantiate the particles. Basically, when the object collides with any other object, and the velocity is passed a certain threshold, it instantiates the particles.



And now, here is the new throw mechanic, impact particles, and our UI changes in action.



Unfortunately, not very impressive to the AAA crowd, the Sisyphean endeavor of spending days coding something that most gamers will shrug at. Why even do this to ourselves, I wonder? Why even make games, when you have to compete for real estate in the heads of COD bros who think games require 3 lines of code and a mountain of energy drinks. I honestly might just move to a jungle somewhere and read literature off the grid for the rest of my life. Sappho's calling.


Anyway, with THAT life change impending, we can also use the throw to complete our snowmen from a distance if we desire, which was the point of introducing the throw mechanic to flesh out our grab mechanic in the first place. We had to code in a bunch of new checks and crush a bunch of bugs to get this to work, but it's all part and parcel of the whole genie gig. 10,000 years will give you such a crick in the neck.



This allows us to add more variety to the environment in the overall Labyrinth, we could include gaps and environmental obstacles now that require the player to navigate them in interesting ways, either throwing objects they need - or holding them as they do what they need to, to get across. On top of the (not-working) ice slide, I also introduced another tile, basically a jump tile, where you need to platform across it.



Unfortunately, this introduces a question of “What do if fall” and I’m not entirely sure of the answer yet. We could submerge the player in ice water, slow down their movement, and have them climb out, but this introduces a whole host of new systems to work on. We could also introduce a death state, but this is very punishing for missing one jump. We could take some health and throw the player back on the last platform they were on, but this feels a little - well what even is the point of falling, then?


I had an idea of falling in, leading to a button mash in which the player is freezing, and if they don’t mash out in time, that leads to a death, otherwise we return to land with some loss of health. That way it isn’t wildly punishing. Of course button mash is now considered taboo in video game development, due to accessibility, so it would have to be a hold command, which feels lacking. Perhaps we just introduce a accessibility option here and lock it behind a toggle in the options.


This would mean, if we were to pseudo-pseudocode for a moment, if the player hits the floor of the water, we lock movement, maybe introduce an ice freezing sound, and maybe a screen effect to show this, as well as a choking sound - actually, that sort of is enough, isn’t it? I’m not sure we need a button prompt at all, we could just fade to black at this point and then put the player back on the ground and fade back in with a little health loss. This would be especially hectic if engaging with the haunt systems during platforming, but not so punishing that we restart the entire labyrinth or can’t just continue where we left off without a bit of a slap on the wrist.


I’ll shoot for that. Also, we have some ugliness as a result of seeing the edges of the other tiles in general, now that we have an unconnected tile, so we may need to fix this by placing some sort of edge onto the jump tiles and hiding the neighboring tile set edges. For now, I’m going to go back and work on the slide mechanic, and hopefully it will be fixed by the end of this blog post.



And, it’s been two hours of additional work. I think we finally have something working when it comes to ice sliding behavior, at least with the player. It took a lot of back and forth and testing different methods, from trigger boxes to raycasts, input blending and resetting player velocities. One of the hard parts of all this was building atop the pre-existing player controller, without messing with it’s own functions at all. So what we did was build a “SlideManager” essentially, first we detect if we’re on Ice or not using raycast layers. We had to use a raycast, because the trigger boxes on tiles would lead to stop start behavior at the tile join points. This is very similar now to a standard ground check, just with Ice.



If we are on Ice, we apply a movement function over the top of our pre-existing movement functions, called ApplyIceMovement, and we apply this in FixedUpdate.



As you can see we are still using the old input systems, so we will need to transfer that over once we cement all this down, but none the less here is how it works. We check if our velocity on the Character Controller is above a certain threshold while we’re on the ice, and then introduce a multiplier to your movement, capping it at a max threshold so we cant slide into warp speed. If you’re not hitting the threshold, we lerp between our slide velocity to our current velocity, so we can avoid jittery movement. We then check if there is input, and if there isn’t, we add a multiplier to our sliding behavior so you don’t stop dead on the ice. Sounds way easier than it was to get the feel right. We also do a collision check so we can stop our velocity when we hit walls.



While it’s hard to visualize how this feels with a gif, here it is in action.