SNM’s original release is now deprecated, please check out CE’s pre-release instead:
Old Post
Greetings, today I wanted to once again share a little project I’ve been working on over the span of a previous month, an open source 3D platformer. And in case you had a deja-vu, yeah, I’m the same guy who released this kit last year.
It’s been a full year, and over the span of it my scripting changed quite a bit (maybe for the better, maybe for the worse, up to you to judge), which meant that maintaining the kit from last year was not a viable option for me anymore. That’s why I just opted to start from scratch while doing something more fun this time around, and what better than just doing a full game that stands alone on it’s own, and works as a little demo. So yeah, that’s pretty much what this is.
This time around I decided to focus on making an extensible, general purpose, and relatively easy to expand upon platformer, that’s designed to be used to make full collect-a-thon/platformer games, while offering a bunch of general purpose utilities that can be useful for any games in general.
I don’t wanna bore you any longer with this essay, so here’s a little preview trailer I made for the game:
Resume
Before we fully dive in on the contents of the post as this is going to be pretty lengthy, here’s a brief resume regarding what I’ll cover so you can skip the parts that don’t interest you:
Game’s Features
The game’s features are split on 3:
- Utilities: The general purpose modules the rest of the game’s code relies on.
- External Resources: The modules/code I used in the game that aren’t made by me, which I’ll cover in the attribution section.
- General Code: The mechanics, systems, and stuff that composes the game, which is what i’m going to be covering on this section.
On top of that, server/client code was split in 2 under the same folder, but I won’t get into that as I think it’s pretty self explanatory. This time around I don’t have a decent computer to record on, so i’m not going to be providing footage (the trailer pretty much showcases everything though).
Moveset
Much like last time, the game comes with a moveset, which consists of the following moves:
- Double Jump: Pressing the jump button again in air will trigger a second jump, boosting you further into the air.
- Dive: Pressing the action button in air, while moving, will trigger an airdive, pushing you forward.
- Body Slam: Pressing the action button in air, while not moving, will trigger a body slam that will rapidly push you into the ground.
- Roll: Pressing the action button while walking, will trigger a roll pushing you forward, this move can gain momentum and can be extended by spamming it.
- Knock: Triggered when a move is cancelled by running into an obstacle, mostly called internally by other moves.
- Tilt: This is technically and effect, but I thought it’d be worth mentioning. It simply titls the character’s orientation when walking for a cartoony effect.
Said moveset is divided on 3 scripts:
- Moveset: The state machine.
- Effects: Module containing the effects the moveset uses, such as the tilt and the trail.
- Moves: Module containing the moves the moveset uses, such as the roll and the dive.
Dependencies:
I’ll go further into these in the Documentation section, but for now, all you need to know is that the moveset relies on these (it prob relies on other stuff too, but these are the most notable ones).
- PublicTables: Move states, useful variables such as the JumpCount, and the ToggleMovement function extension are accessed from the state machine’s public table for ease of use (the move functions themselves still are accessed from the Moves module).
- BindingUtil: “Action” binding used by the moves is setup using BindingUtil.
- CharUtils: Moveset’s ToggleMovement function uses CharUtils ToggleMovement to disable both, it uses other stuff such as the player’s Animations/Sounds as well.
Examples:
To change the state of a move, or to disable the moveset itself, you’d do the following:
--Get PublicTable
local PublicTables = require(Modules:WaitForChild("PublicTables"))
local MovesetTable = PublicTables:GetIdentifier(script.Parent:WaitForChild("Moveset"))
--Change the JumpCount
MovesetTable.JumpCount = MovesetTable.MaxJumps
--Disable Moveset
MovesetTable.MovesetEnabled = false --Moves use Metamethods to ensure your changes apply on the go, meaning ObjectValues are no longer necessary
--Enable Moveset and Movement
MovesetTable.ToggleMovement(nil, true) --This function is originally from the Moves module, it was extended to the table for ease of use
MovesetTable:ToggleMovement(true) --Maybe you can call it without passing self, but I haven't tested it
--There's an issue with the previous implementation though, if we call it too soon it might not exist yet, so in order to prevent that, we can either use the function from Moves, or call it the following way:
local ToggleMovement = PublicTables:WaitForKey(MovesetTable, "ToggleMovement") --Yields until ToggleMovement Exists
ToggleMovement(nil, false)
To trigger a move manually, you’d have to require Moves:
--Get Module
local MovesetMoves = require(Moveset:WaitForChild("Moves"))
--Trigger Jump
MovesetMoves:Jumped(true)
Playlist Zones
Playlist Zones are an area based music system that triggers music when you enter an area. It supports multiple instances to represent a zone, and it supports multiple audios.
How to use:
Place all the audios you wanna play in the folder, and they’ll play when you enter it, like a playlist.
API
There’s no API, only the PlaytestVisible (true by default) variable that shows non-transparent parts while testing in studio. Rest of the system is automatic, you drag in/out the audio and it just works.
Zone Titles
This is a standalone extension of PlaylistZones (you can delete it if you don’t like it) that simply displays the Playlist’s name when you enter it, which I used to display the name of the areas in the game.
Collectibles
Collectibles consist of 2 scripts, the Client-Sided one and the Server-Sided one, the client handles the appearence/trigger of the collectibles, while the server awards/manages them.
There’s 3 collectible types in SNM:
- Coffees: These are the game’s main collectibles, they display a fancy animation with a name/description when obtained, and they are the ones usually used to progress in platformers.
- Toasts: These are the secondary collectibles, they act as an extra for completionists, and are usually hidden at places the player doesn’t frequent too much.
- Money: This is the game’s currency (aka: tertiaty collectible), it acts as a guide for the player indicating where to go, and it acts as an incentive for players to come back in games where the currency can be spent. They respawn every 12 hours by default.
All types can be customized in their appropiate server/client side variables to your liking, and of course forked to fit your game properly.
Notes
Colletibles changed quite a bit since the previous kit, and since there’s no API for it, i’m just going to give you some notes regarding what’s new and i’m going to end the section here.
- Placeholder Boxes: Once you open the project you might realize the collectibles look like boxes, this was done to make placing them easier (you can fork this out pretty easly by removing the section of the script that applies appearence), models/audio for the collectibles are in ReplicatedStorage/Assets and can be customized.
- Placeholder Boxes Delay: Only downside of applying appearences automatically, is that if you add a lot you might notice a small delay when they are being applied (SNM has around 400, and it takes a couple seconds on my current device for them to load), you can either leave it there, or hide it with a loading screen.
- New Identifier Structure: The structure collectibles follow changed since the previous kit to be less annoying, now the name of the collectible acts as the Collectible Identifier for the current level, and the identifier value inside it acts as the Place Identifier.
- Bonus Collectibes Removal: Bonus collectibles are not a thing anymore unlike the previous kit as they didn’t feel necessary.
- Concerns Regarding Exploits: No safety checks are done by default on SNM, in general, it’s up to you to implement server-side checks in order to prevent exploiters abusing collectibles.
- New Datastore: Collectibles now take advantage of an easy to use datastore module in order to improve legibility and ease of use. Refer to said module’s documentation for more details.
Checkpoints
Pretty self explanatory, checkpoints with a cool effect when reached.
How to use:
- Name your initial spawn “Spawn” and set the Enabled property to true.
- Name all other checkpoints “Checkpoint” and set the enabled property to false.
- Done.
Mechanics
SNM comes with 4 base game mechanics by default:
- Bouncy Surfaces: Parts named “BouncySurface” will make the player bounce, bounces can be accumulated to gain extra height.
- Swings: Sticks you can spin on, pressing the jump button will launch you forward, pressing the Interact button will invert their direction.
-
Tight Ropes: They basically let you walk on them and that’s about it, I planned to add the same effect bouncy surfaces have but I ran out of time.
Moving Platforms: Moving platforms you can ride on. They were supposed to be client sided because they ruin the game for folks with high ping otherwise, but they didn’t move the player in the client, so yeah, rip.
Damage
Game’s damage handler with rough support for custom damage/deaths (rough as in not a module or anything, just a regular script with a table for custom stuff).
- When the player touches with an object named Damage, they’ll take 25 damage.
- If an IntValue under the name of DamageValue is placed inside a damage object, it’ll deal the amount specified in there.
- If a custom damage object, such as Water is touched, it’s custom function will be ran.
Quests
These are a last minute addition, studio kept booting up for some reason (context on that down below), so I used that extra time to add an example quest.
There’s only 1 quest in the game, and it was made as an example regarding how to use the Dialogue System’s events to make a quest.
So, how do I make a quest?
There technically isn’t a quest system as it felt unnecessary, the way it’s done in the Walter Quest is as follows:
- You make a LocalScript under Workspace/Quests/YourQuest.
- You store all your Quest Assets in there.
- You hook it to the events the Dialogue System provides to detect when a dialogue starts/ends.
- You use the functions provided by the Dialogue System to Store/Update NPC dialogues.
- Once the quest is complete you use PivotTo to position the earned collectible, and BadgesHandler to award badges.
Of course there are better and cleaner ways to implement quests, but this one felt good enough for me as quests aren’t common enough on my end for this implementation to be an issue. It’s cheap but it works.
Dialogues
The dialogues script is the one in charge of handling the NPC Dialogues, the NPC Pathfinding and the Dialogue Typewritting (the typewritting is done in a separate module to allow using it on cutscenes and such). On top of it, it offers some events to allow for things such as quests.
Events
- DialoguePlayed: Fires when a new dialogue starts.
- DialoguePassed: Fires when a new dialogue line starts.
- DialogueCancelled: Fires when the dialogue ends early.
- DialogueCompleted: Fires when the dialogue completes.
- DialogueEnded: Fires both when the dialogue completes or gets cancelled.
Functions
- StoreDialogue(NPC): Takes the npc’s folder as argument, stores it’s current dialogue.
- RestoreDialogue(NPC): Takes the npc’s folder as argument, restores the previously stored dialogue.
- UpdateDialogue(NPC, Dialogue, StorePrevious): Takes the npc’s folder as argument, the new dialogue (StringValue) you want to replace the current one with, and the optional storeprevious argument calls storedialogue if true is passed.
Modules Documentation
PublicTables
I learned OOP recently, and even though I find it pretty useful for modules, I often find myself on a bottleneck when using it for in-game systems such as the game’s moveset where I need all scripts to access each other’s information. PublicTables are a frankenstein I made mashing together shared, OOP, and instances that solved that issue.
The result is a system with it’s own benefits, it’s own drawbacks, but overall, it gave me what I wanted, and I used it on a big chunk of the project. It’s not intended to be used for modules, just for small in-game systems where you need to store isolated information (an NPC’s state using it’s own model as a table for example).
Benefits:
- Easy to use.
- It allows using any instance in your game, not just modules, as a table that holds shared information. It also accepts string “passwords”.
- Provides a built-in changed event as well as a yielding function to avoid race conditions.
- If you are one of the few folks that still use shared, you’ll probably feel pretty comfortable using it (only difference is that PublicTables force you to isolate your code, which can be done easly either by passing script.Parent, or a string “password”).
Drawbacks:
- No autocompletion.
- Unlike with classes, functions aren’t reused here, which means they take more memory. You can avoid that by defining your functions outside the table.
- Very likely a lot slower than OOP, haven’t really tested, but this is mostly meant for small tasks.
Events
- _Changed(Key, Value): Fires each time one of your PublicTable’s keys changes, returns said key and it’s new value.
API:
- PublicTables:GetIdentifier(Obj): Takes an “Obj”, returns a public table. Obj can be either an: Instance in which case a code will be “generated” for it, assigned to the instance as an attribute, and used in the future to access the PublicTable. A String, in which case said string will be used as the code. Or nil, in which case a code will be “generated” and passed back as a second parameter.
- PublicTables:FindIdentifier(Identifier): Takes your “PublicTable’s” Identifier (aka: it’s code), returns the PublicTable or nil if not found.
- PublicTables:WaitForIdentifier(Identifier): Takes your “PublicTable’s” Identifier (aka: it’s code), yields until it’s found and returns it.
- PublicTables:FindKey(Table, Key): Takes your “PublicTable” and the “Key” you wish to find, returns the key or nil if not found.
- PublicTables:WaitForKey(Table, Key) : Takes your “PublicTable” and the “Key” you wish to yield for, yields until said key is found and returns it.
- PublicTables:Destroy(Table): This one’s untested as idrk how to properly clean tables, but in theory it destroys the “PublicTable” and it’s connections.
Example of usage:
For example the game’s NPCs use PublicTables to remember their current goal and state, here’s a dumbed down chunk of that:
local function Pathfind(NPC, Bool)
local self = PublicTables:GetIdentifier(NPC) --Our PublicTable
self.CurrentGoal = self.CurrentGoal or nil --Our NPC's current goal
self.Cancelled = self.Cancelled or false --Our NPC's state
--[[
Down below the actual pathfinding is done using the NPC's
isolated variables, without any need for a constructor
--]]
end
Miscellaneous
Quite literally what the name implies, a module with general purpose useful functions. They always vary, and some of them were just found on the forum, but I always like having one of these in all my games.
The ones not listed here are useless and I simply forgot to clean them, I’ll prob do it on a patch when I can.
API:
- MiscModule.GetPlatform(): Returns an approximation of what device the player is on, which I commonly use to display controls on tutorials. Returns either “Console”, “Tablet”, “Phone” or “Desktop”.
- MiscModule.SoundToSpace(Sound, Object, Range): Takes a sound, it’s desired parent, and an optional range, plays the sound and destroys it when done.
- MiscModule.ShuffleTable(Table): Takes a table, returns it with its order rearranged.
- MiscModule.NewRandom(Min, Max, LastRandom, Range): Math random, but recursive to prevent repeated values. Min is the smallest desired number, Max the greatest, LastRandom is optional, it lets you pass the last random the function returned, and range is optional too, it let’s you pass a distance between the numbers.
- MiscModule.Debris(Obj, Time): It spawns a new task, waits and deletes the passed instance once the time passes.
- MiscModule.TweenOnce(Tween, Yield): Plays the passed tween and cleans it once it ends, yield lets you yield your script until said tween ends.
- MiscModule.MuteSound(Sound, Bool): Fades the passed sound out, making it muted until false is passed. Used to mute the game’s music when you grab a Main Collectible.
DialogueHandler
This is the dialogue system’s typewritter, isolated into it’s own module to allow using it for cutscenes and such.
API:
- DialogueHandler.Enum: Table that exists to help you figure out what the DialogueTable contains, can’t be actually used to autocomplete keys so don’t even try.
- DialogueHandler.FindWords(String, SpecialWords, Count): Function used by type internally to find special words and replace them with their RichText version.
- DialogueHandler.Type(DialogueTable): Takes a DialogueTable who contains the UI elements and content/assets used for the dialogue, check the Enum table inside the script and the dialogue system itself for reference.
Datastores.rar
I have always struggled using datastores, and I never managed to understand the popular datastore modules everyone seems to use due to their API, in my opinion, being filled with boilerplate. This module aims to solve that, simplifying datastores as much as I could to a level that’s far easier to grasp, reason why it’s called Datastores.rar.
How do I use it?
If all you care about is saving, all you need are 2 functions, Save and Load, with an optional Reset one to wipe data.
Of course, saving each time something in your game changes can oversaturate datastores, which is why the module offers a SharedData variable you can opt to place your data table on in order to cache it and save said data later, a SaveShared function to share its contents, and 2 additional functions that save the data automatically for you AutoSaveSharedData (not recommended) and SaveSharedDataOnShutdownOrLeave.
Why should I use this over X?
You shouldn’t, this system uses raw datastores and has no fancy data loss preventions like session locking and such because I honestly don’t know that much about datastores, in fact, I had to read GEILER123456’s datastore tutorial while making it. It also isn’t heavily tested either, so it’s prone to bugs too.
It’s a simpler solution to it’s overcomplicated alternatives, aimed at folks like myself with no clue how a datastore works. If you got more experience with these than me I welcome you to use a different module or fork this one to your liking.
API:
Constructor:
- DataKey: The name of the datastore.
- PlrKey: The name of the key under which the player data is saved (TDLR: Personal player data name).
- Player: Optional, only used for a safety check when loading the player data.
Functions:
- Save(Data, Retries): Attempts to save the data table you provided the number of retries you provided or 20 by default.
- Load(Retries): Attempts to load the data using the constructor’s key the number of retries you provided or 20 by default.
- Reset(Retries, Default): Only useful for RTE requests, otherwise you can just pass nil to the Save function. Retries the amount you passed or 20 by default.
- SaveShared(Retries): Calls the save function passing SharedData, retries the number of times you passed or 20 by default.
- AutoSaveSharedData(Time, Retries): Calls the SaveShared function each time the interval you provided passes, can be paused setting PauseAutoSave to true or passing nil in the Time param. You figured out by now it retries.
- SaveSharedDataOnShutdownOrLeave(Retries): Calls the SaveShared function both when the player leaves and when the server shuts down, retries blah blah blah…
Parameters/Variables:
- Data: Data to be saved (dictionary expected).
- Retries: How many times you wanna retry before giving up on loading/saving the data (20 by default).
- Default: If your game uses a table by default when the data is initially created, you can pass it again when resetting the data.
- Time: Interval of time between which the game will save automatically if AutoSave and SharedData are on.
- SharedData: Variable holding the shared data table that related functions/your scripts can use.
- PauseAutoSave: Determines weather the autosave shared data function will save next time the interval passes or not, only works if it’s function was called in the first place.
Events:
- Saved: Self Explanatory.
- Loaded: Self Explanatory.
- Erased: Self Explanatory.
- SharedSaved: Self Explanatory.
- SharedLoaded: Self Explanatory.
- AutoSaved: Self Explanatory.
Example of usage:
--Setup
local DataModule = require(Modules:WaitForChild("Datastores.rar"))
local Data = DataModule.new("GameData", Player.UserId, Player)
--Load
local LoadedData = Data:Load(20) --Retry 20 times, returns data
if LoadedData ~= "Error" then --If there weren't errors loading the data
if LoadedData then
--Data already exists
else
--New player
end
else
--Error
end
--Save
Data:Save({Apples = 1, Oranges = 2, Basket = {}}, 20) --Try to save the passed table a max of 20 times
--"Merge"
Data.SharedData = {Apples = 1, Oranges = 2, Basket = {}} --Cache your data
Data:SaveShared(20) --Try to save the shared data table a max of 20 times
--AutoMerge (Not recommended)
Data:AutoSaveSharedData(30, 20) --Autosave shared every 30 secs
--Merge on leave
Data:SaveSharedDataOnShutdownOrLeave(20) --Save on leave/shutdown
ContextBindingUtil
This is pretty much just PseudoPerson’s ContextActionUtility, but with some minor additions on top of it to make rebinding the controls on my games easier. I barely added anything so yeah, full credit to them for their awesome module, been using it for years.
New Features:
- Support for multiple functions.
- Shared name-based binding.
- Updating your previously binded action, which is useful to let your players set their own controls.
- I also wanted to add a function to force a button to have a certain position, but I ran out of time, so maybe I’ll add that on a patch.
API:
- BindingModule:BindAction(ActionName, Functions, MobileButton, Binding1, Binding2): Much like CAS, it takes an action, a function, a boolean to determine if the mobile button shows, and the 2 keys the action will be binded to. Additionally, a table with multiple functions can be passed in the 2nd parameter.
- BindingModule:UpdateAction(ActionName, Binding1, Binding2): Takes a previously binded action and updates it’s keybinds with the passed ones.
- BindingModule:AddFunctionsToAction(ActionName, Functions): Adds the passed function to your previously binded action, can optionally take a table with multiple functions.
- BindingModule:UnbindAction(ActionName): Unbinds the action.
- BindingModule:FindAction(ActionName): Looks for the passed action, returns it’s PublicTable, which to be honest is pretty useless, and if it doesn’t exist it returns nil.
- BindingModule:WaitForAction(ActionName): Yields until the requested action exists.
- CAU’s Functions: The following CAU functions can be called directly from CBU: SetTitle, SetImage, GetButton, DisableAction. They only work if the action’s PublicTable exists and they do the exact same they do in CAU.
CharacterUtils
Like miscellaneous, but for the character.
API:
- CharacterUtils.BundleAnimationIds: Table containing all animations, from all animation packs, including blundle exclusive ones. Useful for NPCs.
- CharacterUtils.Sounds: Table pointing to the LocalPlayer’s sounds (keep in mind it’ll change on death).
- CharacterUtils.Animations: Table pointing to the LocalPlayer’s animations, might rarely return nil (keep in mind it’ll change on death).
- CharacterUtils:ToggleReset(Bool): Toggles the player’s reset button (retries forever, I don’t like limited retries as they often fail on some devices/connections).*
- CharacterUtils:ToggleMovement(Bool): Toggles the player’s movement.
- CharacterUtils:GetFloor(Offset): Casts a raycast down and returns the floor or nil.
- CharacterUtils:GetInputDirection(): Gets the direction the player moves torwards based on their input.
- CharacterUtils.RayParams: Rayparams the GetFloor function uses.
- CharacterUtils.PlayerModule: Player’s PlayerModule (because the module itself uses it).
- CharacterUtils.Controls: Player’s Controls Module (because the module itself uses it).
BadgesHandler
Small wrapper that simplifies BadgeService and allows using it from the client. Due to me not adding badges it’s untested so lmk if you have issues, but the exact same implementation worked fine on my other projects.
How does it work on the client?
Once you require the module at least once in the server, a connection will be made that allows the module to call itself from the client, allowing functions to work both sides normally.
Doesn’t calling it from the client allow exploiters to cheat badges?
Yeah, so do remotes in most cases if not checked on the server, which I assure you most devs don’t do. If you are worried about it I suggest you do the following:
- Add a dictionary with a check function tied to each BadgeID you wanna “protect”.
- If said BadgeID is found in the dictionary when the client calls the server, call said dictionary function.
- Perform a date/distance/validity check to ensure the player can get the badge.
This is assuming each badge needs it’s own individual check, otherwise a general one should work.
API:
- BadgesHandler.HasBadge(Player, BadgeId, MaxRetries): Takes the player, the BadgeID and the max amount of retries before nil is returned, indicating the operation failed. Returns the result (true means you have it, false means you don’t) if successful.
- BadgesHandler.AwardBadge(Player, BadgeId, MaxRetries): Takes the player, the BadgeID and the max amount of retries before nil is returned, indicating the operation failed. Returns the result (true means the badge was awarded, false means the badge is disabled) if successful.
Attribution
This game wouldn’t have been possible without the following resources, so huge shoutout to their respective creators:
- ContextActionUtility (Easy Mobile Buttons)
- GoodSignal (Efficient Signals)
- SimplePath (Easy Pathfinding)
- Circular Transition Screen (Death Transition)
- WindShake (Wind Lines)
- Fluency Icon Library (Icons)
License
SNM relies on external resources, so, to avoid breaching any licenses I decided to split the license into chunks.
- Server/Client side code and Utility Modules written by me are licensed under MIT
- Forked modules, modules that rely heavily on external resources, and the game’s map (which contains some free models), are all Unlicensed.
- External resources, of course, retain their Own licenses if they have any.
What’s the license for:
This time around I don’t care that much about attribution, license is simply there to ensure you, the developer, that you can use the code without worrying about me changing my mind with the code and doing something malicious later.
TDLR for the MIT license:
- You have to keep the license and the copyright notice in your file if you redistribute my code.
- You can’t hold me liable for anything.
- Other than that, do as you please, attribution is optional but appreciated.
Cut Content
If you played the demo already you might have noticed some areas feel unfinished (or you might have noticed this doesn’t have that much content after reading the post). This is because sadly, I had to cut around half of the content planned for the game.
Turns out around 4 days ago while I was getting ready for an early access release, I got greeted with the unpleasant surprise that after months of vague warnings without date, 32 bit support was dropping on the 17th of this month, giving me pretty much no time to polish the release.
I find it a bit depressing that after giving the platform over 4 years of my time, they couldn’t bother giving me said notification a bit earlier, but who knows, maybe i’m to blame for not opening the roblox player that often.
For the previous reason, some of the following content had to be cut from the release (at least until I can afford myself a new computer, which I doubt will be soon):
- Level design is pretty much non-existent and around half of the map had to be cut.
- Some moves from the moveset, some utility modules, additional mechanics, and additional features for already existing ones had to be cut.
- Localization for the game had to be cut.
- Badges for the game were cut.
- There’s certainly room for optimization.
- Long etc, some of it in the proyect’s Trello.
More stuff that I either don’t remember or didn’t get past the drawing board also got cut (for example, another project of mine required a boss battle system, so I was thinking about implementing that here both for personal use and to showcase how it’d be made), but for the most part, that’s about it.
If you want a more lengthy version detailing what was left unfinished, I went ahead and left the project’s trello public for anyone interested in what got cut and what bugs were left unfixed, so feel free to take a look at it:
Extensions
Extensions are additions to the resource that didn’t fit SNM as a game, but were added post-launch as general resources for it.
EgoMov Patch
Egomoose’s moveset helped me a lot when back when I couldn’t script a single line for my life, so I thought it’d be cool to give it a little update with some stuff I wish the moveset had back when I started using it, that’s pretty much what it is.
It’s a patch made by me in like 3-4 days that makes it compartible with SNM, updates deprecated code, adds some QoL features such as mobile support, a simpler installation and some utility functions (there’s a more detailed comment in the installer specifying what I added, so check it out for more info).
If you find any issues with it other than the ones specified in the installer lmk, can’t guarantee I’ll fix it but I’ll try.
SNM Port:
SNM Egomoose Port.rbxl (627.5 KB)
Standalone File:
EgoMov Patch.rbxm (47.3 KB)
Closing
And finally, the parts y’all came here for, the project itself, uncopylocked for your use:
Ignoring the final 2 days of development, this project was a lot of fun to work on, and even though i’m not exactly happy this is the final product i’m releasing, I still hope you can find it of use. I’m unsure weather I’ll come back to it later on or if I’ll just switch to a different platform, but at least i’m glad I managed to release something.
Thanks a bunch to Andrixter for helping me record the trailer for the game and for providing feedback, thanks to the folks who upvoted and commented on the post I did on reddit I couple days ago (first time something I make gets liked that much), thanks to the folks who made the resources mentioned in the post, and thank you for sparing me some of your time.
I kinda forgot the links at the end bc I made the post early by mistake, but if you like my work and wanna see future stuff I make you can find me on Twitter and Reddit, i’m not a very social person so I rarely post/reply, but I post creations there from time to time. Other than that if you wanna support me just playing SNM and upvoting it if you liked it is more than enough.