Pre-Alpha Progress Update 4: Scope Creep


Hello World

So it’s a week after I had originally planned to do an update, but here I am, with… something.

I won’t bore everyone with why progress has been so slow, real life and other work commitments is where I’ll drop the subject.

That said, this week I had a considerable amount of time to push forward on the project, which I have, although not quite in the way I planned.

From the last post you’ll remember I had a list of features I’m working from, and I did start on those, but I ended discovering some issues that were more pressing. And I’ll go into some of them here, hence the Game Design designation for the post type.

Scope Creep

For those unaware, scope creep is “adding additional features or functions of a new product, requirements, or work that is not authorized (i.e., beyond the agreed-upon scope)”.

This can come in many flavors, and usually refers to when you agree on something with clients and then ask for more and you have to squeeze it, but there are other types, and the one that bit me this time was a combination of technical debt and inexperience.

Technical debt, for those unsure, is the “implied cost of future reworking required when choosing an easy but limited solution instead of a better approach that could take more time”.

So in this case my “agree-upon scope” is the last few features I need to test my entire concept.

My tech debt is making a simple UI to prototype and then having to redo everything the first time you try it on Mobile, and making a simple dice weighting mechanic only to discover players can cheese the hell out of it.

I don’t recall where I first heard it, and have no idea where it originated, but a guideline I use a lot when solving complex problems is

First make it work, then make it right, lastly make it fast.

What I had, worked. I thought that was enough. Turns out I needed to make it right sooner than planned.

Mobile “first”

Okay, so when I had he idea for this game, the intention was always to make it a mobile game. I wanted the experience in the space, and I thought the idea lends itself well to the platform. But building for Windows is easier, and faster, and I’ve done it before.

So when of my early play testers asked if it worked on Android, I told them “not yet”, and then decided to give it a go. I’d already started with some #if UNITY_ANDRIOD directives for things I know will work different, so I figured, how hard can it be?

Turns out, a lot.

The Problem

Funny enough, most of my code logic actually worked, my biggest issues was actually just getting the build and test to work. I’ve done some Flutter dev in the past, so everything is supposedly set up, but I have new Android device I use for Dev, and this one would just not work.

After hours of Googling, Youtube and GPT4 I finally got it running, and then just discovered that my biggest issue wasn’t things like button presses and using Touch instead of MouseOver or whatever, the biggest two issues where

  1. Weird aspect ratios and screen oddities like notches
  2. All my text referring to keyboard and mouse and all my UIs optimized for PC

Here I hit a hurdle, and that was that I don’t know what is “best practice” for having UIs for different devices.

The “solution”

For the resolution issues as first example, I could set reference resolution to 1920x1080 and then the CanvasScaler.ScreenMatchMode to MatchWidthOrHeight. As long as my components had their anchors set properly, things would move around and resize and so on, but for some reason, on my Windows machine with a 2560x1440 resolution, and for my Android phone which is 1650x720 in portrait (yes, this is a cheap crap phone), I need to set CanvasScaler.matchWidthOrHeight to 1 (match Height 100%).

But when I then went to build on my Macbook Pro (which is a barely running 2014 13”) which has a Retina display with a native res of 2560x1600 and a scaled res of 1680x1050, it needs the 0 (match Width 100%).

jackiechan.jpeg

I haven’t even tried Linux yet.

So that’s fine, I just need a bunch of conditional compilation preprocessor defines inside my camera scripts Awake function to set this up, but it feels… dirty. Doesn’t feel like a best practice, but no amount of Googling showed me a better way.

For point 2, I first thought sure, let’s do the same thing, store some text and add some logic to Awake or OnEnable, but then I needed references or (supposedly) expensive Find calls… Again, Google and GPT4 were less than helpful on what industry best practice is here, but then I thought about one of the games I’ve really been enjoying lately, Honkai Star Rail.

The UIs between PS5 and iOS differ vastly. As far as I know, that is built in Unity, so just thinking about how I personally experience the difference in interfaces and thinking logically about how I would go about doing it, the answer was…

Build two UIs.

So yeah. Using the Pause Menu as an example, I created two different panels, Pause Menu UI and Pause Menu UI Mobile, added them to different GameObjects, then updated my script to take references to these.

image.png

And then I have some code logic to determine which one I use.

        private void Start()
        {
#if UNITY_IOS || UNITY_ANDROID || UNITY_TVOS
            _uiObjectToUse = pauseMenuUIMobile;
            pauseButton.gameObject.SetActive(true);
#else
            _uiObjectToUse = pauseMenuUI;
            pauseButton.gameObject.SetActive(false);
#endif
        }

Is it right? Who knows. Does it work? Yes.

So I moved on.

Weighted Dice (or: Maths = Hard)

I have forgotten everything I learned in Statistics at university. Except that my professor always loved reminding us that “statistics are useless to the individual” and “sample size matters”. But I digress.

I’ve mentioned in a previous post how my action mechanic works, but I’ll expand a bit here for clarity.

The Problem

Let’s say you have a six sided die (d6), and the sides are as follow:

  1. Passive (fumble if rolled)
  2. Physical
  3. Magic
  4. Healing
  5. Dynamic
  6. Passive (crit if rolled)

Dynamic means the slot can be either Physical or Magic depending on what the player has equipped.

So the first problem was that rolling for Dynamic made no sense. You don’t roll for side type, your roll for a Damage outcome. So I removed the option to try and roll a 5, and instead made it that you roll for a type, and we look at what the type of side 5 is to determine if it is included.

Normally on a d6 you have an equal 1/6 chance to roll a side. Now for example, if the user chooses to do Healing, we increase his chances of rolling a 4 by weighting the roll. In code I first do List<int> sidesToRoll = GetDiceSides();, which in this case will give me

[1, 2, 3, 4, 5, 6]

and then I add sidesToRoll.AddRange(GetChoiceWeights()[playerChoice]);, where the choice weights for Healing will make the final list

[1, 2, 3, 4, 5, 6, 4, 4, 4, 4]

thus increasing the chance to roll a 4 to being 5/10 when I do a random select inside the List.\

Spot any problems yet?

If you did, congrats, you are a smarter person than I am.

The problem is with the Dynamic side. Let’s say the user has a Physical skill equipped in slot 5, and then tries to roll a Physical attack. With the current logic, we will on the first pass update the list to

[1, 2, 3, 4, 5, 6, 2, 2, 2]

giving them a 4/9 (44%) chance to roll a 2. Then my logic checks what is equipped on the 5 slot, and if it is Physical, also adds the Dynamic choice weights, making the list

[1, 2, 3, 4, 5, 6, 2, 2, 2, 5, 5, 5]

which makes the chance of rolling either a 2 or a 5 around 4/12 (33%). This increases the total likelihood of getting what you want to 66%, much higher than 44% you would have gotten originally.

Now, the above problem is more or less fine on the d6, the issue isn’t too bad, but part of the game mechanic allows you to later equip an eight sided die (d8), and there the distribution is

  1. Passive/Fumble
  2. Physical
  3. Magic
  4. Healing
  5. Dynamic
  6. Dynamic
  7. Dynamic
  8. Passive/Crit

This means that if you add Physical items to 5, 6 and 7, your list might become something like

[1, 2, 3, 4, 5, 6, 7, 8, 2, 2, 5, 5, 6, 6, 7, 7]

which makes Physical roll chance 12/16 (75%), which is MUCH worse than before.

The “solution”

I figured I’d probably just need to do actual maths rather than a very basic result = GetSidesToRoll()[randomIndex];

As I said, I have forgotten everything about stats, so I asked GPT4 for suggestions, and it suggested

  1. Probability Distribution Array
  2. Weighted Random Selection (Using a Weighted Map)
  3. Custom Probability Function
  4. Using a Framework or Library
  5. Simulation-Based Approach

Untitled.jpg

I spent time (more than I should’ve) reading up and going through options 1 to 3, refreshing my memory on what and how, and then eventually decided “screw it”, just use option 4.

So I found a library with a license that allows me to use it in my project which implements an algorithm for sampling from a discrete probability distribution via a generic list, and using this with some additional maths, I can ensure that whatever choice you make, you have a base 60% chance of rolling that.

This more easily scales to the d8 as well, and has the added benefit of allowing new types of Passives, ones that increase your chance to roll your choice for example.

Is that all?

Along with the dice roll changes I also decided to make some changes based on feedback and just my own experience. Crits now apply immediately, we automatically make the second roll immediately, rather than have the player do it on their next turn. This increases the pace of the game, and gives you a better chance of landing a crit on your choice (since the Crit roll would have increased your pity).

What you lose is strategy; previously you could roll for Magic, land a crit, and then use that crit for Healing on your next turn. With the new system, you could still roll Healing, but you can’t increase your chances of doing so.

The last thing I added was an Ultimate Skill. You don’t roll for this, but as you take damage your counter increases, and eventually you unlock the use of the skill. Long term, I want the Ultimate linked to your character, which you unlock as you progress, giving importance to the choice you make.

I also started testing my logic for a d8. While you start the game with a d6, the idea would be that you eventually unlock a d8 as your character progresses, making you more powerful from the start with more skill options.

Next steps

Now that I’ve dealt with my tech debt and things are looking better, I’m back to the features I feel are needed for a demo release

  • Skill unlock mechanic (unlocks skills between runs)
  • Modify dice layout before a new run (pick from the skills you’ve unlocked)
  • Fix animation and audio timing issues + add some skill effects (nothing fancy, just better than I have now)

Will I get this done before 29 Feb? We’ll have to see. I’m doing my best here.

Until next time then.

Get So this is how I DIE

Download NowName your own price

Leave a comment

Log in with itch.io to leave a comment.