Nothin' Worth Doin is Easy



Mother Earth is pregnant for the third time 

For y'all have knocked her up,

I have tasted the maggots in the mind of the universe,

I WAS NOT OFFENDED

For I knew I had to rise above it all

…or drown in my own shit

I know recording work sessions might be a bit cringe, but if we didn’t think our work mattered why would we do it?

I had possibly my longest and one of the most painful dev sessions ever last night, started out as usual, WITH ME TOTALLY ROCKING ASS.

Ok, that’s enough petulant illtemperedtuna, let’s try to do a more reserved blog entry.

It started really well, got right into the todo list and just blazed through what I thought would take a couple hours of tuning up the new gobble mechanics in maybe 15 minutes. It was feeling like it was going to be a great day, and I got a little cocky.

AI has been such a door opener for me, it’s able to throw bits of logic my way that can do all sorts of cool things I never would have attempted before. So one of the key things that’s nagged me over the years is how it’s a bit of a pain to set up bonuses. I need to write by hand the property fields, I need to add the references to other scripts, a single letter off and everything breaks, and when things break it’s not entirely clear why, and long story short it’s just this long arduous process that took the wind out of my sails when I was trying to add all the cool toys to SeaCrit.

And I figured, hey it’ll be a bit painful, but let’s bite this bullet so we never have to worry about this annoying system ever again.

Whew, I don’t even want to get into it, but it was a long night getting this up and running and feeling even remotely good.

But! It’s pretty fully featured! I can now rename my bonuses and with a single click of a button, it revamps the name not just in the core fish script, but it even replaces the enum! And I created a custom data loader that presents it in alphabetical order, and it chekcs the length of the enum array and assigns the proper index and then it alphabetizes the thing. You know, now that i’m out of the thick of it and rattling off the features, I’m actually kind of impressed, maybe yesterday wasn’t as bad a session of spinning wheels in the mud as I thought.

18 hours straight of grueling break things, try again, break things, try again, break things, try again. It was painful but our limits aren’t nearly as bad as they were not too long ago. And now that I’ve used AI a bit, I’m not nearly as terrified of it as I used to be. It’s a tool, nothing more. It’s not going to do your job for you, but what it WILL do you give you a snowball chance of doing those painful tasks you’d think we would have made relatively painless by now.

It still blows my mind that we don’t have more approachable code libraries just built right into the engine, here’s some REALLY nifty stuff I produced yesterday with a bit of help from Grok that is going to make more tools in the future much easier:

    #region  ==============Manipulate Scripts===============

    public static void InsertFieldToScript(string fieldToCreate, string targetScriptPath, string lineToFind = "")

{

    try

    {

        if (string.IsNullOrEmpty(fieldToCreate) || string.IsNullOrEmpty(targetScriptPath)) 

        {

            Debug.LogError("Source field or target script path cannot be empty");

            return;

        }

        if (!File.Exists(targetScriptPath)) 

        {

            Debug.LogError($"Target script not found at: {targetScriptPath}");

            return;

        }

        string[] lines = File.ReadAllLines(targetScriptPath);

        foreach (string line in lines)

        {

            if (Regex.IsMatch(line.Trim(), $@"\b{fieldToCreate}\b\s*(;|{{|$)")) 

            {

                Debug.Log($"Field '{fieldToCreate}' already exists in {targetScriptPath}");

                return;

            }

        }

        int lineNumber = string.IsNullOrEmpty(lineToFind) ? 1 : GetLineNumber(lines, lineToFind);

        int adjustedLine = Mathf.Clamp(lineNumber - 2, 0, lines.Length);

        string[] newLines = new string[lines.Length + 1];

        for (int i = 0; i < adjustedLine; i++) newLines[i] = lines[i];

        newLines[adjustedLine] = $"[ShowIfGroup(\"showBonuses\")] public Bonus {fieldToCreate};";

        for (int i = adjustedLine; i < lines.Length; i++) newLines[i + 1] = lines[i];

        File.WriteAllLines(targetScriptPath, newLines);

        Debug.Log($"Successfully inserted field '{fieldToCreate}' at line {lineNumber} in {targetScriptPath}");

    }

    catch (Exception e)

    {

        Debug.LogError($"Failed to insert field: {e.Message}");

    }

}

    public static void AddToEnum(string scriptName, string enumName, string newValue)

    {

        try

        {

            scriptName = FindScriptNameAndLocation(scriptName);

            string[] lines = File.ReadAllLines(scriptName);

            for (int i = 0; i < lines.Length; i++)

            {

                if (Regex.IsMatch(lines[i].Trim(), $@"\benum\s+{enumName}\b"))

                {

                    Debug.LogWarning($"Enum {enumName} already exists in {scriptName}");

                    return;

                }

            }

            int classEnd = lines.ToList().FindLastIndex(l => l.Trim() == "}");

            if (classEnd < 0) { Debug.LogError($"No class end found in {scriptName}"); return; }

            string leadingWhitespace = lines[classEnd].Substring(0, lines[classEnd].TakeWhile(char.IsWhiteSpace).Count());

            List<string> newLines = lines.ToList();

            newLines.InsertRange(classEnd, new[] { $"{leadingWhitespace}enum {enumName}", $"{leadingWhitespace}{{", $"{leadingWhitespace}    {newValue}", $"{leadingWhitespace}}}" });

            File.WriteAllLines(scriptName, newLines.ToArray());

            AssetDatabase.Refresh();

            Debug.Log($"Created enum {enumName} with {newValue} in {scriptName}");

        }

        catch (Exception e)

        {

            Debug.LogError($"Failed to modify enum: {e.Message}\nStackTrace: {e.StackTrace}");

        }

    }

    public static void ModifyStringInScript(string scriptName, string fieldName, string newFieldName, bool makeLowercase = false)

    {

        if (makeLowercase)

        {

            fieldName = MakeFirstLetterLowerCase(fieldName);

            newFieldName = MakeFirstLetterLowerCase(newFieldName);

        }

        string scriptPath = FindScriptNameAndLocation(scriptName);

        string scriptContent = File.ReadAllText(scriptPath);

        if (scriptContent.Contains(fieldName))

        {

            string newContent = scriptContent.Replace(fieldName, newFieldName);

            File.WriteAllText(scriptPath, newContent);

            AssetDatabase.Refresh();

            Debug.Log($"Renamed " + fieldName + " to {" + newFieldName + "} in " + scriptName + " script");

        }

        else Debug.LogWarning("Field " + fieldName + " not found in Fish script");

    }

    public static string GetScriptContent(string scriptName)

    {

        var script = FindScriptNameAndLocation(scriptName);

        return File.ReadAllText(script);

    }

    public static void SortLinesAlphabetically(string scriptName, string startLineMarker, string endLineMarker)

    {

        try

        {

            string scriptPath = FindScriptNameAndLocation(scriptName);

            if (string.IsNullOrEmpty(scriptPath)) return;

            string[] lines = File.ReadAllLines(scriptPath);

            int startIndex = GetLineNumber(lines, startLineMarker) + 1;

            Debug.Log("start index" + startIndex);

            int endIndex = GetLineNumber(lines, endLineMarker) - 1;

            Debug.Log("end index" + endIndex);

            int indentLevel = startIndex >= 0 ? lines[startIndex].TakeWhile(char.IsWhiteSpace).Count() : 0;

            if (startIndex < 0 || endIndex < 0 || startIndex >= endIndex)

            {

                Debug.LogError($"Could not find valid range between '{startLineMarker}' and '{endLineMarker}' in '{scriptName}'.");

                return;

            }

            List<string> linesToSort = lines.Skip(startIndex + 1).Take(endIndex - startIndex - 1).ToList();

            linesToSort.Sort((a, b) => a.Trim().CompareTo(b.Trim()));

            string indent = new string(' ', indentLevel + 4);

            for (int i = 0; i < linesToSort.Count; i++)

                linesToSort[i] = indent + linesToSort[i].Trim();

            List<string> newLines = new List<string>(lines);

            newLines.RemoveRange(startIndex + 1, endIndex - startIndex - 1);

            newLines.InsertRange(startIndex + 1, linesToSort);

            File.WriteAllLines(scriptPath, newLines);

            AssetDatabase.Refresh();

            Debug.Log($"Sorted lines between '{startLineMarker}' and '{endLineMarker}' in '{scriptName}' alphabetically.");

        }

        catch (System.Exception e)

        {

            Debug.LogError($"Failed to sort lines in '{scriptName}': {e.Message}");

        }

    }

    public static int FindLargestNumber(string scriptName, string startLineMarker, string endLineMarker)

    {

        try

        {

            string scriptPath = FindScriptNameAndLocation(scriptName);

            string[] lines = File.ReadAllLines(scriptPath);

            int startIndex = GetLineNumber(lines, startLineMarker) + 1;

            int endIndex = GetLineNumber(lines, endLineMarker) - 1;

            int largestNumber = 0;

            for (int i = startIndex; i <= endIndex; i++)

            {

                string line = lines[i];

                // Split the line into words and check each for numbers

                foreach (string word in line.Split())

                {

                    string cleanedWord = word.TrimEnd(',', '.', ';', ':');

                    if (int.TryParse(cleanedWord, out int number))

                    {

                        largestNumber = Math.Max(largestNumber, number);

                    }

                }

            }

            if (largestNumber == int.MinValue)

            {

                Debug.Log($"No numbers found between '{startLineMarker}' and '{endLineMarker}' in '{scriptName}'.");

            }

            else

            {

                Debug.Log($"Largest number found between '{startLineMarker}' and '{endLineMarker}' in '{scriptName}': {largestNumber}");

            }

            return largestNumber;

        }

        catch (System.Exception e)

        {

            Debug.LogError($"Failed to process '{scriptName}': {e.Message}");

            return int.MinValue;

        }

    }

    public static string FindScriptNameAndLocation(string scriptName)

    {

        scriptName = Path.GetFileNameWithoutExtension(scriptName);

        string[] guids = AssetDatabase.FindAssets(scriptName + " t:Script");

        if (guids.Length == 0)

        {

            Debug.LogError($"{scriptName} script not found!");

            return "";

        }

        foreach (string guid in guids)

        {

            string path = AssetDatabase.GUIDToAssetPath(guid);

            if (Path.GetFileNameWithoutExtension(path).Equals(scriptName, StringComparison.OrdinalIgnoreCase))

                return path;

        }

        string[] prefixMatches = Array.FindAll(guids, g => AssetDatabase.GUIDToAssetPath(g).StartsWith(scriptName, StringComparison.OrdinalIgnoreCase));

        if (prefixMatches.Length > 1)

        {

            Debug.LogError($"Multiple scripts found starting with '{scriptName}': {string.Join(", ", prefixMatches.Select(AssetDatabase.GUIDToAssetPath))}. Please specify.");

            return "";

        }

        return prefixMatches.Length == 1 ? AssetDatabase.GUIDToAssetPath(prefixMatches[0]) : "";

    }

    public static int GetLineNumber(string[] lines, string searchString)

    {

        if (lines == null || lines.Length == 0 || string.IsNullOrEmpty(searchString))

        {

            Debug.Log("No Line FOUND!");

            return -1;

        }

        for (int i = 0; i < lines.Length; i++)

        {

            if (lines[i].Contains(searchString))

            {

                return i + 1; // Return line number (1-based for readability)

            }

        }

        Debug.Log("Couldn't find string reference for insertion point!: " + searchString);

        return -1; // Return -1 if the string is not found

    }

    public static string MakeFirstLetterLowerCase(string input)

    {

        if (string.IsNullOrEmpty(input))

            return input;

        return char.ToLower(input[0]) + input.Substring(1);

    }

    public static string MakeFirstLetterUpperCase(string input)

    {

        if (string.IsNullOrEmpty(input))

            return input;

        return char.ToUpper(input[0]) + input.Substring(1);

    }

    public static void RenameFile(string newName, string assetPath)

    {

        if (!string.IsNullOrEmpty(assetPath))

        {

            AssetDatabase.RenameAsset(assetPath, newName);

            AssetDatabase.SaveAssets();

            Debug.Log($"Renamed asset to: {newName}");

        }

    }

    #endregion

So anyhow, I’m really proud of the work we did yesterday. Not because it was great or anything, it was a solid day. But what I’m proud of, is that it was really fucking hard. And we pushed through it anyway, and we set our minds on pounding out the system and getting it good and done before we crashed and we barely managed to eek that out. 

Development is going really damned well, and that’s testament to the principles we have lived by on this project. We don’t blame, we own our shit, and we understand that cutting down others, playing politics, or any of the usual petty bullshit that has decimated this industry only serves us in the short term.

I know, easy to say when you’re a solo dev.

But all this solo dev has actually really bummed me out. I really enjoy working with others, I love sharing the craft, and learning from people, and spreading gamedev goodness so we can all uplift one another and become better.

I think it’s a damned shame we’ve gotten to the point where we have to rely on AI. We could have had such better tooling, better environments, better training material, better networks and pipelines for getting better, pipelines for making pipelines better.

How sad is it that after all these years the only thing anyone cares to do any more is point the finger? Gamedev doesn’t feel like the same wondrous exercise of art and magic any more. Just some toy to fight over, not a wild frontier to explore together.

Oh well, just bitching and moaning but what else is new?

Only slept a few hours but all I can think about is getting back to work. And I’m really torn, we can either jump in and start tuning bonuses and get this sucker online, OR… we can use our newly acquired confidence and skills in Unity to produce even more tools to make future content more stable and quality.

That’s a HARD decision, on one hand the game is SO CLOSE to being good, you guys have NO IDEA how much shit is about to come online. I don’t touch on teh game here much at all, i just blather, but the swords, the shields, the energy bolts, the pistols, the sniper rifles, the armor that makes you fast, the heavy plate armor that turns you into a bit of a beast, the crazy off the wall bonuses.

EVEN I HAVEN’T SEEN THIS STUFF! I’ve only worked on ‘em till they started to show any potential then I shelved them and immediately moved on to broken stuff. This game as been an exercise in pain and patience for hte past several years. Ever focusing the foundations.

So anyway, what I’m trying to think of in my head right now, and maybe other devs out there can relate. Is it’s a REAL pain in the ass to track down those dials you want to tune up, expose them, and get to making the game better. When you’re in the thick of the action and testing this little acceleration value against this agility value, against this max speed value, it can often take a lot of effort just remembering where they all are, and they often get scattered.

It’s a funny thing. We spend so much time making terrain tools and LOD systems and all the pretty things that anyone can open an engine and dive into and see quality in. But at the end of the day, what determines if a game is any good or not, has a lot more to do with quality debug tools, and property organization and exposure than most would realize.

The less time we spend looking at loading bars, the less time we spend looking around for values and properties, the more energy we have and more time we have to actually test the game, improve those values, iterate on code, and try again!

Oh I know what I can do! We have this addon “HotReload” that’s kinda finicky and I’ve barely used it, but maybe we’ll give it another go, because when it REALLY shines is when you’re doing quick little iterations to combat and systems, so what I think we can do is code right in the editor! Oh man I’m already really in love with this idea!

So usually when you input values in unity, because of serialization and how unity loads variable data, its’ really finicky the values you get on restart, and it’s a chore having to dart back and forth from your project to the property windows and back to your game, and you can only really do one value at a time, because once you stop testing, boom everything resets and all that perfectly tested data vanishes.

So I just had another revelation while typing that. I’m I’m pretty psyched to try it. One thing AI is PHENOMINAL with is just general clean up and rearranging things. It’s actually mind blowing and the only thing stopping me from using AI more is guilt and a general distaste for it, but as a happy servant that exists for the sole purpose of giving people code they want they want, AI is AMAZING.

So here’s my thought, I already asked Grok to format code similar to how I write my code, which I think is really nice, let me see if I can find the convo.

https://i.imgur.com/61HYDba.png

And what I’d like to do is ask grock to compartmentalize all the gameplay specific variables above functions they are associated with, so there would be a “Fury” region, and all variables and all logic related to each bonus would be sorted in one area. 

And here’s the cool part! Once we turn on hotreload, which can be cumbersome when doing more intense coding, but is great for when you’re making tiny adjustments, and you can test those code changes right in the editor.

So our pipeline will be thus:

#region Fury

Float furyDuration => fury.v;

Float maxFuryStr => 2;

Void FuryFunction(){

awesomeSauce = 5xFury + Fury^zz.lerp(shoot, theMoon, hitOrMissItMakesNoDifference)

}

Void FuryFunction2(){

awesomeSauce = 5xFury + Fury^zz.lerp(shoot, theMoon, hitOrMissItMakesNoDifference)

}

#endregion

And any adjustments we need to make will be all localized in very easy to find locations, which I will further catalogue with simple searchterms by adding a “z” in front of the name preceded by “//”

“//zfury”

Now any time I need to deal with the nuts and bolts of these bonuses I’ll be able to find it very easily!

And now that I think about it, I should do this for every aspect of the game, which I kind of already do, but the idea of smashing the functions right next to the key variables throughout the entire project is really exciting to me right now.

For so long I’ve just thrown all my variables at the top and all the functions below simply because that’s what everyone else does, but it really doesn’t make much sense! Much better to bring things in close and when everything is in one tiny location it’s so much easier to clean up, improve naming conventions, put together better logic without burning out poking all over the project.

For so long I’ve made a joke about our coding abilities, but I’ve really been feeling it lately, and I think we may actually be quite good in some niche capacities. We are a ways off from being able to create tight, performant and elegant engine code that can produce incredible results.

But we’ve come at this coding thing from a completely different angle than most coders.

Most coders develop surrounded in code. They create their systems and they become ingrained in this digital world around them where they are in total control of the environment around them. And I’m actually kinda jealous, because in another life I think I coulda whooped some arse in that arena and pushed myself to some amazing work doing who knows what, kinda feel like an old dog trying to learn new tricks at this age, but I do have something that makes be a bit unique in this arena, and that’s that I never approached this endeavor as a coder, as this was me trying to bend things to my will.

I came in with a total understanding of the limits of the art side of the Unity engine. You wouldn’t know it from these blogs, but before SeaCrit I was far more artist. I was actually pretty decent, I wish I had some of my old portfolio pieces but I lost most of em and what I cand find isn’t all that great.

Anyhow, so whereas most coders are summoning these crazy pillars of logic that clash with the immaterial forces from other dimensions of reasnoing and functionality. We were using 3d software and Unreal, and photoshop and really diving in deep to the user side of things. I don’t think it can be understated how important it is to game developers to use other quality products, not just great tools, but play other great games.

And at some point you want to start picking things apart in your head. Whenever I play games any more which is really rarely, I always end up peering inside the game and creating this narrative of the team behind the game. Like I’ll play some area with really crappy water effects, but you can tell they spent a TON of time on it. And maybe the sorting is really well done, and the textures are actually quite good, but the spec levels are way out of whack, and they don’t know how to optimize the textures to get 4x the quality/ frames because they’re using full RGBA textures. And I just think of all these people yelling and pointing fingers or i’ll see something with TONS of polish and care put into it, and it’s in the game for like 3 seconds.

The games industry is a giant monkey house. It’s pure chaos, and that’s partly what I love about it. It’s still uniquely human despite all its flaws. And that makes it exciting, because even in the worst projects there are investments of time,  faith, and dreams hidden under the surface of the corporate sludge and the poor plans.

What can ya do? Wish we were teaching our children well, but we’ve kinda spoiled ‘em. Gave ‘em phones and let ‘em think everything is gonna come to you easy in this world. That you just put on a gamedev hat and your’e gonna make great games. Put on a filmmaker hat and you’re gonna make a great film. Put on your robe and wizard hat and you’re going to bust out some great memes. Ok that one's true.

Per usual, I dunno where I’m going with this one, maybe we already said what we planned to and we’re just blathering per usual!

Anyhow, I'm gonna stew on this and get to work soon. I think it’s pretty much a done deal at this point that we will be refactoring our code heavily, but maybe we can find a happy medium.

Maybe as we go bonus to bonus, and implement them, we refactor our code bit by bit and compartmentalize it/ organize/ bookmark it. And by the time we’re done, it’ll be an actual joy to work on the project!

Too often we think of the process of refactoring our code as this horrible cleaning exercise that detracts from getting things done. But if we take a bite by bite approach to it, system to system, function to function as we work on it, and we don’t think of improving code standards as a radical disturbance but rather a slow creeping awesomeness. I think our projects would be in much better order and we’d approach gamedev with a lot less dread.

Get SeaCrit

Leave a comment

Log in with itch.io to leave a comment.