PIGSquad Finish Your Game Jam 2024 Devlog
Introduction
Paula The Possum has been my passion project since September 2020. It’s a love letter to Rayman 2, the 3D Platformer I grew up with, and it’s the project that helped me use what I learned about Unreal. For this year’s PIGSquad Finish Your Game Jam, I decided to get some of my friends to help and make a playable part of a full game.
A small tangent on what kind of 3D Platformer Paula is
From what I've seen on the internet, whenever people talk about 3D Platformers, it's generally about movement tech, Sonic, somewhat cookie-cutter games, Super Mario, etc.. And while I appreciate that there's been a boom of indie 3D Platformers in recent years, very few of them scratched my itch, and that's the itch of exploration. (can you tell I like NitroRad's videos about 3D Platformers?)
Rayman 2, for example, is not exactly a complex game. The combat is incredibly basic, and the movement mechanics are very straightforward. But the main thing I think most fondly about in that game is just how fun it was to explore the game world.
The Dreamcast version in particular added some very nice background detail in some levels that just really sells the scale of the world, making you think "what else could be there?".
That is the kind of game that Paula The Possum is. The wonder of exploration is the feeling that I want to evoke in players.
The Project before the Jam
Here’s an overview of the project as it was before the jam:
- There are 2 complete blockouts of levels
- The player can run, jump, grab ledges, swing, climb and swim. There’s a modified Character Movement Component that (in my and other people's opinion) significantly improves the feel of the movement.
- There’s one boss fight
- There’s a checkpoint system that resets the state of the world when the player respawns
- There’s a system for scripting movements for any character including the player
- The player can travel between levels with scripted movement and camera angles
- And many more that I would love to talk about, but it’d be a whole thing…..
- It does run on the Nintendo Switch at 1080p60 and I am very proud of that!
All of the above was the result of about 9 months of on-and-off work since January this year, and I got to show it off at the Portland Retro Gaming Expo!
So I wanted to try something new for this jam. I also wanted to do a long-form write-up about the year that I've been working on this game and I think people would enjoy seeing behind the veil.
Working with a Team
This has been a very new experience for me. Thank you to cthomlan, giwake, Gmad, Lithish, Papyesh and too.much.candy for helping me out on this, I'm so grateful! Having other people working on the project has been a great motivator and I have a newfound appreciation for working with Narrative Designers.
We've done a bit of narrative exploration, figuring out what kind of game this should be, the story themes, all that, which made me really excited for the potential full game. It was a bit overwhelming at first and I needed to take a few days off from the project in the second week of the jam, but it was a great learning experience and I'm way more confident about getting this game done now.
The Metroidvania Level Design Pivot
It feels ridiculous when I think about it. But I’ve wanted to try doing something with Level Streaming in Unreal for a while, and the two levels I’ve showed off already at PRGE had a (somewhat flawed) metroidvania element with how you had to defeat a boss in the second level to progress in the first. And the work I made to support linear levels in the first place was surprisingly transferrable for a Metroid Prime style world. So that’s what I decided to try this jam!
Unreal’s Level Streaming is mostly a relic from UE4 days (currently in the process of being replaced by World Partition), but even in Unreal 5.4 it has been working surprisingly well so far. Being able to work on individual zones as part of a bigger world has been kinda fun and I can move them to accommodate sizing changes!
Baking lighting is also very straightforward, because Lightmass can do it for all the sub-levels at once. I spun up a Swarm Agent on the mini PC we have at home to help reduce baking times and it helped significantly with iteration times. It does have it’s own potential problem of how all the levels can contribute to the lighting, so I’d need to be careful about creating background detail as it could interfere with shadowing in other zones. It did work perfectly with the Sparse Volume Lighting Samples (the older UE Volume Lighting method that's CPU based), and the Level Streaming is able to load and unload the samples, unlike the Volumetric Lightmap method!
Here's some screenshots of pretty areas that I made during the jam:
All in all, moving to a somewhat open world design didn’t really change how I worked on individual zones. The main things that are different with this approach is that I now have to think about streaming in levels, where to stream them, on top of the lighting scene being practically unified.
I might end up splitting the world into individual maps anyway, as it would let me have different lighting, fog, sky, etc. for areas. And the maps load surprisingly quickly, even on the Nintendo Switch, so I don't think it'd be a problem.
Level Design does still remain one of the more difficult things for me to do, but I keep trying anyway. It's definitely difficult to figure out if a level is good when its all developer textures. But in times like these, it does help to remember that a lot of the games start out as a set of boxes that then get prettied up.
Tools
This has also been quite a huge part of my work this year and some of it has overlapped with the jam. I want to eventually reach a point with my game where adding new areas, new story, etc. wouldn’t require needing much in terms of new assets or programming.
I’d love to make a YouTube video about video game tools sometime, because they're huge time-savers when done right! But for now, enjoy these overviews that may give you some inspiration for your own projects. ;)
I/O System
This is the biggest thing I’ve worked on during (and after) the jam. Inspired by Source Engine’s incredible Input Output system, I made a version of it for Unreal, complete with an editor tab for editing outputs with autocomplete. Theoretically, any Actor can support this, all that’s needed is an InputOutputComponent, and then Inputs and Outputs are determined from functions on the owning Actor class.
So for example, a simple Logic Relay (logic_relay in Source) would need at least a „Trigger” input and an „OnTriggered” output. In the Logic Relay actor class, I’ve added functions called „In_Trigger” and „Out_OnTriggered”. Thanks to Unreal’s Reflection system, I can look them up, and call the In_Trigger function easily when that input gets triggered.
The InputOutputComponent is only really used to store data about outputs. It technically could be added to any object and making it work with vanilla Unreal Actors wouldn’t be too difficult.
I found out that what I made is very similar to Mallet, but it’s been really fun making my own thing for this. Depending on how things go, I might use this on another project I’m very excited about and eventually, hopefully, release it to the public.
Dialogue System
I'm using Steve Streeting's incredible Steve's Unreal Dialogue System (SUDS) plugin for dialogue scripts, but I clearly needed more. So I've made a dialogue event queue system that currently supports animating camera shots, playing "emotes" on actors, teleporting and you can easily reference actors by name, or Gameplay Tags!
Due to the complexity that comes with animating cameras, I made an Editor Utility Widget to set up camera shots that then can be copied to text (both plaintext and base64) that can then be parsed from script. You can also paste it back into the tool to modify it. Pretty nifty!
And below is the dialogue script I'm using for testing:
[set AskedThing false] :Begin [event Actor.Teleport `TEMP`, `GT:Mark.Gym.Second`] [event Actor.Teleport `Paula`, `GT:Mark.Gym.First`] [event Shot.Camera 0, `b64:KENhbWVyYU1vZGU9RURDTV9PcmJpdCxDYW1lcmFSb3RhdGlvbk9mZnNldD0oUGl0Y2g9MC4wMDAwMDAsWWF3PTAuMDAwMDAwLFJvbGw9MC4wMDAwMDApLENhbWVyYUxvY2F0aW9uT2Zmc2V0PShYPTAuMDAwMDAwLFk9MC4wMDAwMDAsWj0wLjAwMDAwMCksQ2FtZXJhRmllbGRPZlZpZXc9NTQuMDAwMDAwLE9yYml0X1RhcmdldEFjdG9yPSJUYXJnZXQ9QWN0b3IgU3BlYWtlcj1URU1QICIsT3JiaXRfUm90YXRpb249KFBpdGNoPS0yLjgyNDkyOCxZYXc9NTUuMzE2MTU3LFJvbGw9MC4wMDAwMDApLE9yYml0X0Rpc3RhbmNlPTEzMS4xNjcxMzAsT3JiaXRfTG9jYWxMb2NhdGlvbk9mZnNldD0oWD0wLjAwMDAwMCxZPTI3LjQ5MjU0NixaPTIzLjA1ODY2MSksTG9va0F0X1RhcmdldEFjdG9yPSJUYXJnZXQ9VmVjdG9yIFg9MC4wMDAgWT0wLjAwMCBaPTAuMDAwIixMb29rQXRfVGFyZ2V0TG9jYXRpb25PZmZzZXQ9KFg9MC4wMDAwMDAsWT0wLjAwMDAwMCxaPTAuMDAwMDAwKSxMb29rQXRfQ2FtZXJhTG9jYXRpb249KFg9MC4wMDAwMDAsWT0wLjAwMDAwMCxaPTAuMDAwMDAwKSk=`] [Wait.Delay 0.15] [Actor.Emote `TEMP`, `Yeah`] TEMP: What can I do for ya? [set UI.ChoiceTitle "What can TEMP do for you?"] * What is this? [set AskedThing true] TEMP: Glad you asked! [goto SUDSExplanation] [if {AskedThing}] * That's all, thank you! [goto Finish] [else] * Nothing, thank you! [goto Finish] [endif] :SUDSExplanation TEMP: So, this conversation, our conversation is using *SUDS* for the dialogue. [event Shot.Camera 0, `b64:KENhbWVyYU1vZGU9RURDTV9PcmJpdCxDYW1lcmFSb3RhdGlvbk9mZnNldD0oUGl0Y2g9MC4wMDAwMDAsWWF3PTAuMDAwMDAwLFJvbGw9MC4wMDAwMDApLENhbWVyYUxvY2F0aW9uT2Zmc2V0PShYPTAuMDAwMDAwLFk9MC4wMDAwMDAsWj0wLjAwMDAwMCksQ2FtZXJhRmllbGRPZlZpZXc9NTQuMDAwMDAwLE9yYml0X1RhcmdldEFjdG9yPSJUYXJnZXQ9QWN0b3IgU3BlYWtlcj1QYXVsYSAiLE9yYml0X1JvdGF0aW9uPShQaXRjaD0tMy45NTc5NTcsWWF3PTEzNi4yNDgxOTEsUm9sbD0wLjAwMDAwMCksT3JiaXRfRGlzdGFuY2U9MTcyLjYwOTI2OCxPcmJpdF9Mb2NhbExvY2F0aW9uT2Zmc2V0PShYPTAuMDAwMDAwLFk9LTI1LjM2NzczNCxaPTIzLjA1ODY2MSksTG9va0F0X1RhcmdldEFjdG9yPSJUYXJnZXQ9VmVjdG9yIFg9MC4wMDAgWT0wLjAwMCBaPTAuMDAwIixMb29rQXRfVGFyZ2V0TG9jYXRpb25PZmZzZXQ9KFg9MC4wMDAwMDAsWT0wLjAwMDAwMCxaPTAuMDAwMDAwKSxMb29rQXRfQ2FtZXJhTG9jYXRpb249KFg9MC4wMDAwMDAsWT0wLjAwMDAwMCxaPTAuMDAwMDAwKSk=`] [Wait.Delay 0.15] [Actor.Emote `Paula`, `Yeah`] Paula: Sounds interesting! [event Shot.Camera 0, `b64:KENhbWVyYU1vZGU9RURDTV9PcmJpdCxDYW1lcmFSb3RhdGlvbk9mZnNldD0oUGl0Y2g9MC4wMDAwMDAsWWF3PTAuMDAwMDAwLFJvbGw9MC4wMDAwMDApLENhbWVyYUxvY2F0aW9uT2Zmc2V0PShYPTAuMDAwMDAwLFk9MC4wMDAwMDAsWj0wLjAwMDAwMCksQ2FtZXJhRmllbGRPZlZpZXc9NTQuMDAwMDAwLE9yYml0X1RhcmdldEFjdG9yPSJUYXJnZXQ9QWN0b3IgU3BlYWtlcj1URU1QICIsT3JiaXRfUm90YXRpb249KFBpdGNoPS0yMi40MzE2MDksWWF3PTU1LjMxNjE1NyxSb2xsPTAuMDAwMDAwKSxPcmJpdF9EaXN0YW5jZT01NjIuNjQwMDc2LE9yYml0X0xvY2FsTG9jYXRpb25PZmZzZXQ9KFg9LTM0LjczOTczMyxZPTg2LjgxMjcxMSxaPS00MC4yMzkwODkpLExvb2tBdF9UYXJnZXRBY3Rvcj0iVGFyZ2V0PVZlY3RvciBYPTAuMDAwIFk9MC4wMDAgWj0wLjAwMCIsTG9va0F0X1RhcmdldExvY2F0aW9uT2Zmc2V0PShYPTAuMDAwMDAwLFk9MC4wMDAwMDAsWj0wLjAwMDAwMCksTG9va0F0X0NhbWVyYUxvY2F0aW9uPShYPTAuMDAwMDAwLFk9MC4wMDAwMDAsWj0wLjAwMDAwMCkp`] [event Shot.Camera 20, `b64:KENhbWVyYU1vZGU9RURDTV9PcmJpdCxDYW1lcmFSb3RhdGlvbk9mZnNldD0oUGl0Y2g9MC4wMDAwMDAsWWF3PTAuMDAwMDAwLFJvbGw9MC4wMDAwMDApLENhbWVyYUxvY2F0aW9uT2Zmc2V0PShYPTAuMDAwMDAwLFk9MC4wMDAwMDAsWj0wLjAwMDAwMCksQ2FtZXJhRmllbGRPZlZpZXc9NTQuMDAwMDAwLE9yYml0X1RhcmdldEFjdG9yPSJUYXJnZXQ9QWN0b3IgU3BlYWtlcj1URU1QICIsT3JiaXRfUm90YXRpb249KFBpdGNoPS0yOC45NTMxNjQsWWF3PTIzLjI4Nzk1MixSb2xsPTAuMDAwMDAwKSxPcmJpdF9EaXN0YW5jZT02MjAuMDM2NjgyLE9yYml0X0xvY2FsTG9jYXRpb25PZmZzZXQ9KFg9LTM0LjczOTczMyxZPTg2LjgxMjcxMSxaPS00MC4yMzkwODkpLExvb2tBdF9UYXJnZXRBY3Rvcj0iVGFyZ2V0PVZlY3RvciBYPTAuMDAwIFk9MC4wMDAgWj0wLjAwMCIsTG9va0F0X1RhcmdldExvY2F0aW9uT2Zmc2V0PShYPTAuMDAwMDAwLFk9MC4wMDAwMDAsWj0wLjAwMDAwMCksTG9va0F0X0NhbWVyYUxvY2F0aW9uPShYPTAuMDAwMDAwLFk9MC4wMDAwMDAsWj0wLjAwMDAwMCkp`] TEMP: Yeah, it's a pretty neat plugin. TEMP: I can reference anything that happens on your journey. [Wait.Delay 0.45] [Actor.Emote `TEMP`, `Yeah`] TEMP: And we can have honestly quite long conversations about things. [event Shot.Camera 0, `b64:KENhbWVyYU1vZGU9RURDTV9PcmJpdCxDYW1lcmFSb3RhdGlvbk9mZnNldD0oUGl0Y2g9MC4wMDAwMDAsWWF3PTAuMDAwMDAwLFJvbGw9MC4wMDAwMDApLENhbWVyYUxvY2F0aW9uT2Zmc2V0PShYPTAuMDAwMDAwLFk9MC4wMDAwMDAsWj0wLjAwMDAwMCksQ2FtZXJhRmllbGRPZlZpZXc9NTQuMDAwMDAwLE9yYml0X1RhcmdldEFjdG9yPSJUYXJnZXQ9QWN0b3IgU3BlYWtlcj1URU1QICIsT3JiaXRfUm90YXRpb249KFBpdGNoPS0xLjg4MTQ4OSxZYXc9NzcuNzk4NzkxLFJvbGw9MC4wMDAwMDApLE9yYml0X0Rpc3RhbmNlPTEwOS4xODM3NzcsT3JiaXRfTG9jYWxMb2NhdGlvbk9mZnNldD0oWD0wLjAwMDAwMCxZPTE2NS45MDY1MzQsWj0yNy4zOTI0NzQpLExvb2tBdF9UYXJnZXRBY3Rvcj0iVGFyZ2V0PVZlY3RvciBYPTAuMDAwIFk9MC4wMDAgWj0wLjAwMCIsTG9va0F0X1RhcmdldExvY2F0aW9uT2Zmc2V0PShYPTAuMDAwMDAwLFk9MC4wMDAwMDAsWj0wLjAwMDAwMCksTG9va0F0X0NhbWVyYUxvY2F0aW9uPShYPTAuMDAwMDAwLFk9MC4wMDAwMDAsWj0wLjAwMDAwMCkp`] [event Shot.Camera 0.35, `b64:KENhbWVyYU1vZGU9RURDTV9PcmJpdCxDYW1lcmFSb3RhdGlvbk9mZnNldD0oUGl0Y2g9MC4wMDAwMDAsWWF3PTAuMDAwMDAwLFJvbGw9MC4wMDAwMDApLENhbWVyYUxvY2F0aW9uT2Zmc2V0PShYPTAuMDAwMDAwLFk9MC4wMDAwMDAsWj0wLjAwMDAwMCksQ2FtZXJhRmllbGRPZlZpZXc9NTQuMDAwMDAwLE9yYml0X1RhcmdldEFjdG9yPSJUYXJnZXQ9QWN0b3IgU3BlYWtlcj1URU1QICIsT3JiaXRfUm90YXRpb249KFBpdGNoPS0xLjg4MTQ4OSxZYXc9NzcuNzk4NzkxLFJvbGw9MC4wMDAwMDApLE9yYml0X0Rpc3RhbmNlPTEwOS4xODM3NzcsT3JiaXRfTG9jYWxMb2NhdGlvbk9mZnNldD0oWD0wLjAwMDAwMCxZPTI5LjM1MjE3NixaPTI3LjM5MjQ3NCksTG9va0F0X1RhcmdldEFjdG9yPSJUYXJnZXQ9VmVjdG9yIFg9MC4wMDAgWT0wLjAwMCBaPTAuMDAwIixMb29rQXRfVGFyZ2V0TG9jYXRpb25PZmZzZXQ9KFg9MC4wMDAwMDAsWT0wLjAwMDAwMCxaPTAuMDAwMDAwKSxMb29rQXRfQ2FtZXJhTG9jYXRpb249KFg9MC4wMDAwMDAsWT0wLjAwMDAwMCxaPTAuMDAwMDAwKSk=`] [Wait.Delay 0.35] [Actor.Emote `TEMP`, `Yeah`] [event Shot.Camera 3, `b64:KENhbWVyYU1vZGU9RURDTV9PcmJpdCxDYW1lcmFSb3RhdGlvbk9mZnNldD0oUGl0Y2g9MC4wMDAwMDAsWWF3PTAuMDAwMDAwLFJvbGw9MC4wMDAwMDApLENhbWVyYUxvY2F0aW9uT2Zmc2V0PShYPTAuMDAwMDAwLFk9MC4wMDAwMDAsWj0wLjAwMDAwMCksQ2FtZXJhRmllbGRPZlZpZXc9NTQuMDAwMDAwLE9yYml0X1RhcmdldEFjdG9yPSJUYXJnZXQ9QWN0b3IgU3BlYWtlcj1URU1QICIsT3JiaXRfUm90YXRpb249KFBpdGNoPS0xLjg4MTQ4OSxZYXc9NzcuNzk4NzkxLFJvbGw9MC4wMDAwMDApLE9yYml0X0Rpc3RhbmNlPTEwOS4xODM3NzcsT3JiaXRfTG9jYWxMb2NhdGlvbk9mZnNldD0oWD0wLjAwMDAwMCxZPTI0Ljc0MjIxNyxaPTI3LjM5MjQ3NCksTG9va0F0X1RhcmdldEFjdG9yPSJUYXJnZXQ9VmVjdG9yIFg9MC4wMDAgWT0wLjAwMCBaPTAuMDAwIixMb29rQXRfVGFyZ2V0TG9jYXRpb25PZmZzZXQ9KFg9MC4wMDAwMDAsWT0wLjAwMDAwMCxaPTAuMDAwMDAwKSxMb29rQXRfQ2FtZXJhTG9jYXRpb249KFg9MC4wMDAwMDAsWT0wLjAwMDAwMCxaPTAuMDAwMDAwKSk=`] TEMP: The sky's the limit, lemme tell ya. [event Shot.Camera 0, `b64:KENhbWVyYU1vZGU9RURDTV9PcmJpdCxDYW1lcmFSb3RhdGlvbk9mZnNldD0oUGl0Y2g9MC4wMDAwMDAsWWF3PTAuMDAwMDAwLFJvbGw9MC4wMDAwMDApLENhbWVyYUxvY2F0aW9uT2Zmc2V0PShYPTAuMDAwMDAwLFk9MC4wMDAwMDAsWj0wLjAwMDAwMCksQ2FtZXJhRmllbGRPZlZpZXc9MzIuMzg3NjU3LE9yYml0X1RhcmdldEFjdG9yPSJUYXJnZXQ9QWN0b3IgU3BlYWtlcj1QYXVsYSAiLE9yYml0X1JvdGF0aW9uPShQaXRjaD0tMTMuMjUwMzUyLFlhdz0xMjcuNDE4MzEzLFJvbGw9MC4wMDAwMDApLE9yYml0X0Rpc3RhbmNlPTIwOC45NDQ2NTYsT3JiaXRfTG9jYWxMb2NhdGlvbk9mZnNldD0oWD0wLjAwMDAwMCxZPS0yNS4zNjc3MzQsWj0yOS40MDYwMjApLExvb2tBdF9UYXJnZXRBY3Rvcj0iVGFyZ2V0PVZlY3RvciBYPTAuMDAwIFk9MC4wMDAgWj0wLjAwMCIsTG9va0F0X1RhcmdldExvY2F0aW9uT2Zmc2V0PShYPTAuMDAwMDAwLFk9MC4wMDAwMDAsWj0wLjAwMDAwMCksTG9va0F0X0NhbWVyYUxvY2F0aW9uPShYPTAuMDAwMDAwLFk9MC4wMDAwMDAsWj0wLjAwMDAwMCkp`] Paula: (I should make a note of that.) [goto Begin] :Finish TEMP: See ya later!
I would have preferred to have plain text camera shot data, but due to issues with SUDS's Name parser related to commas, I needed to add base64 as a supported data format to avoid said issues.
Combo Graph and Ability System
Using FlowGraph, I've made a new asset type called "Combo Graph". Combined with my custom Gameplay Ability System (different than the already included GAS, but follows very similar ideas), it allows me to easily set up combos that can be chained together!
Individual Combo Actions are Abilities, mostly self-contained assets that handle executing an action. I've made a base class for a Combo action that handles the basic and reusable behavior, making it relatively easy to add individual actions.
The Ability System in general is also really easy to make AI actions/attacks with! Here's the State Tree I made for the boss that the players had to defeat in the PRGE demo:
What's next?
As much as I'd like to work more on this game, I can't keep on working on it forever without an income. So I'm aiming to make a small demo to pitch to people to get funding. But outside of that, this project may need to take a back seat, while I look for work.
It has been great hearing people's feedback, how polished and professional the game feels, how smooth the character movement is, how even the relatively primitive art direction looks. It's clear that a lot of people want to play a game like this.
I want to make it happen, one way or another. If I manage to get funding for the game and I get to hire folks, I'm confident that we can pull it off in less than 18 months. If not, I'm gonna chip away at it over time for a few years, with occasional outside work here and there.
Thank you for reading, I really appreciate it. <3
Take care!
Vivian
If you like my work, please donate to my Ko-fi! Anything is appreciated.
https://ko-fi.com/vivitheheinouswitch
You can also sign up for the project's newsletter on the girlfriend.games website to be notified when anything major happens!
https://girlfriend.games
Credits
Paula The Possum
A Retro-Style 3D Adventure Platformer in development
Status | In development |
Author | Vivian, The Enby Witch |
Genre | Platformer |
Comments
Log in with itch.io to leave a comment.
Thanks for all your thoughts on the game dev process!
One area where level streaming still seems to be the only option still is if your design has verticality. Like if you want to stream levels that are above or below - world partition only loads based on a 2D grid so if you have a stack of levels they all live in the same square and get loaded together.
World Partition is certainly a no go for me until they implement baked lighting support for it. I’m also not really aiming for the open world type of levels that World Partition seems perfect for 😅