Skip to content

Nicole Makes Games

a devblog

Menu
Menu

Tag: fair weather

How I Made It: A Player Decision Manager

Posted on August 9, 2021January 12, 2022 by Nicole

As I prepare my next update for Fair Weather, I thought it might be nice to dive into one of the features of the game and explain how I brought it together technically. The feature I’ll discuss today is my Player Decision Manager.

What is it?

Well simply put, it’s a multiple choice question presented to the player which will affect the player’s stats. It allows me to represent events aboard the player’s ship without having to build them out mechanically, (because I simply don’t have the resources or time to do so) but also it allows me an opportunity to introduce small snippets of world building. I also use these decisions to inject humor and set the mood of the game. While I aim to build many (hundreds, ideally) of decisions in, I also expect that the player will see these questions multiple times and begin to use them strategically to keep their ship afloat.

The final product

For my next update I decided to add 50 new decisions (a number I selected quite arbitrarily) into the game. It has always been my hope that my game would excel in writing and while I built systems for notes, and quests, and decisions, I haven’t taken a lot of time to sit down and make content, favoring work on features instead. So that’s what I’m doing now. I am inspired by games like Reigns which seem so simple on the surface and still manage to be totally engaging.

Wrangling Data

While on its face this is a relatively straightforward task, a lot of time is lost in the wrangling of data. So what I’m really sharing today is a couple of tools that I’ve made to make the process faster and less error prone. My game expects a json file with decisions laid out. Here’s a sample.

The decisions json file which is interpreted by the game

Initially I used Google Sheets to hold my thoughts for questions and responses and then manually copied the text into the json format that I needed by hand. Of course this lead to a ton of errors due to mistyping. (Shout out to JSONLint. You tha real MVP!)

The Starting Point

So this was my first problem to solve: How to convert the data in my spreadsheet into the format that I needed. I looked into writing a script directly in Google’s scripting ecosystem, but the process of dealing with Google services and scary security messages just to run my own script turned out to be pretty convoluted and not one I wanted to spend more time on. I decided to just export my sheet as a csv and use an online csv to json converter to get my decisions.json. This worked….partially. As I continued to expand the complexity of my decision model I found that the online tool could work for some of the columns, but didn’t map some of the relationships correctly. So I could get part way there and have to manually insert the rest of the data. (Did I mention, I heart JSONLint?)

My next optimization involved writing a python script to read in the csv and spit out a json. It’s been quite a while since I wrote python but after a healthy amount of googling I managed to put together a script that does the job. This is the system currently in use for generating data files for decisions and quests. I feasibly could use this for the ftue (first time user experience) data as well but since that file changes a lot less, I haven’t spent the time to set up the script. There are improvements that I’ll need to address in the near future. The largest being an encoding problem which causes the script to stumble when it encounters smart quotes. Smart quotes are the curly style quotes that some programs like word convert straight quotes to automatically. The default text encoding (ascii) doesn’t know what to do with these characters. Text typed directly into the spreadsheet doesn’t have this problem, but if I copy and paste text from other programs, as I often do, I could inadvertently introduce them. The solution is to change the encoding to UTF-8, but doing so has proved difficult. Using a different library such as pandas to read in my csv could potentially solve this problem, but since it’s a rare issue I’ve delegated it to the backlog for now.

String Replacement

If you’ve scrutinized the screenshots above you’ll notice I developed a simple markup syntax for string replacement. It generally takes the form:

<KEYWORD>

I process all text before it’s displayed through a function that will replace the various keywords with the desired text. This helps with strings that are variable, like your captain’s name which changes each time you die, and also for items which I may change during development of the game, such as your ship name and the name of the company you work for. This is my way of reserving the right to change these items easily before I take the game of this beta-mode that it’s in currently. [Fun fact: The ship name Fair Weather used to be editable much like your crew and captain’s names, but I changed it to be static instead because I think it tells a better story that the Fair Weather is your ship, instead of me telling the story of some arbitrary ship.] Additionally, some of my keywords will automatically generate sector or ship names to vary the text slightly each time you see it.

So I can write statements like:

Singing is against <COMPANY_NAME> policy.

Vacation Request for <CREW_NAME>.

and

Captain, the <RAND_SHIP_NAME> wants to know if we’ll participate in their crew exchange program.

Reward Types

To add consequences, or “rewards” as I call them, for each option I added a few base reward types to specify to the code how handle dishing out rewards. Each option under the decision can have zero to N rewards listed along with associated counts for each reward. These rewards are things like reputation, fuel, and food, but also can be negative stat hits like ship damage and injuries.

A type of ITEM means that the player will get all of the items listed when the choice is selected.

The type RANDOM_SELECT means to choose between the rewards listed. There is an equal chance of each item being selected. In the future I hope to expand this so that the choice can be weighted on an individual basis. This has been a tricky type to navigate, and because of that I use it sparingly. I expect players will encounter a decision more than once as they play, but not knowing that an outcome was random, they could make the incorrect conclusion that their selected outcome is the only option and avoid selecting it if they didn’t get the “good” outcome the first time. To combat this I try to use language like “might” or “could” to signal that there are multiple results.

The final reward type ACTION signals to the code that an in-game event should trigger on selection like a sector jump or an attack. No doubt I’ll add new reward types as I extend the game functionality, but for now these types provide a decent amount of variety.

To test I read the file in and create triggers for each individual decision in my debug menu

Selecting Decisions and their Targets

Decisions are triggered as one of the outcomes when the player hits the scan button. The PlayerDecisionManager then attempts to retrieve a decision from the full set of options. Some decisions have requirements, such as the average ship morale being above or below a certain level, which could cause a decision to be rejected. In cases such as these, to attempt up to four times to retrieve another decision. In the case that one isn’t found (which is pretty rare unless only a small subset of the decisions are loaded) it closes down and proceeds without showing the player anything.

Decisions also might have targets. Targets are one or more Non Player Characters (NPCs). So an outcome in a decision might affect a single crew member, an entire “department”, such as all Biologists, or the entire crew. In the case where everyone is effected, instead of specifying a target the reward type is used to specify this distinction. Instead of rewarding mood I would reward mood_all, for example.

Let’s take a look at some code snippets from the PlayerDecisionManager:

private const string TARGET_SINGLE_CREW = "SINGLE_CREW";
private const string TARGET_RAND_CREW = "RAND_CREW_";
private const string TARGET_DEPARTMENT = "DEPT_";
private const string TARGET_SINGLE_DEPARTMENT = "SINGLE_DEPT_";

public DecisionData? GetDecision()
{
    int reselectAttempts = 0;
    if(String.IsNullOrEmpty(selectedDecision.id))
    { 
        bool success = false;
        while(!success && reselectAttempts < 4)
        {
            selectedDecision = GetDecisionData();
            success = !HasSeenRecently(selectedDecision.id) &&
                      SelectTargets(selectedDecision.target) &&
                      MeetsRequirements(selectedDecision.require);
            reselectAttempts++;
        }

        if(success) 
        {
            MarkDecisionSeen(selectedDecision.id);
            return selectedDecision;
        } 
        else 
        {
            Invoke("CloseDecision", 0.2f);
            return null;
        }
    }
            
    return selectedDecision;
}

public bool SelectTargets(string decisionTarget)
{
    if(!String.IsNullOrEmpty(decisionTarget))
    {
        selectedTargets = new List<NPCModel>();
        if(decisionTarget == TARGET_SINGLE_CREW)
        {
            selectedTargets.Add(Main.Instance.crewCtrl.GetRandomCrew()[0]);
        }
        else if(decisionTarget.Contains(TARGET_RAND_CREW))
        {
            int count = 0;
            Int32.TryParse(decisionTarget.Substring(TARGET_RAND_CREW.Length), out count);

            if(count < 0)
            {
                NLog.LogError("INCORRECT COUNT FOR SELECTED TARGET RAND");
                count = 1;    //fallback in case of error
            }
            count = Math.Min(count, Main.Instance.crewCtrl.CrewCount);
            selectedTargets = Main.Instance.crewCtrl.GetRandomCrew(count);
        }
        else if(decisionTarget.StartsWith(TARGET_DEPARTMENT))
        {
            string departmentType = 
                decisionTarget.Substring(TARGET_DEPARTMENT.Length);
            selectedTargets = Main.Instance.crewCtrl.GetCrewOfType(departmentType);
            if(selectedTargets.Count == 0)
            {
                return false;
            }
        }
        else if(decisionTarget.StartsWith(TARGET_SINGLE_DEPARTMENT))
        { 
            string departmentType =
                decisionTarget.Substring(TARGET_SINGLE_DEPARTMENT.Length);
            selectedTargets = Main.Instance.crewCtrl.GetCrewOfType(departmentType, 1);
            if(selectedTargets.Count == 0)
            {
                return false;
            }
        }
        
        selectedDecision.npcs = selectedTargets;
    }
    
    return true;
}

HasSeenRecently simply checks to see if the selected decision id is present a list recounting the last five decisions seen. While the PlayerDecisionManager selects a decision and processes the outcome, a separate popup view displays the decision and its options to the player. The selectedDecision object also stores selected NPC targets so that outcomes can be applied to them, and also so that string replacements can be done as needed with their names.

This system allows for a variable number of crew to be specified as targets for a decision in the spreadsheet. Take the target type TARGET_RAND_CREW in the code above. In the spreadsheet, I can enter a target like RAND_CREW_2 or RAND_CREW_3. That number is then is parsed out and used to select crew members that meet the desired type via helper functions in my crew controller.

Wrapping Up

That’s essentially it! When one of the multiple options is selected in the view, it calls back to the PlayerDecisionManager to dish out the rewards and complete the process. Like all things code, this will undoubtedly change in the future and if it’s interesting enough I’ll let you know about it. I hope you check out Fair Weather if you haven’t already. That’s all for now.

↑↑ ↓↓ ←→ ←→ B A

Posted on June 19, 2020August 9, 2021 by Nicole

With Fair Weather in the App Store I thought it might be nice to share a few cheats to help along any players who find themselves stuck.

I built a hidden console into the game inspired by the Sims. You can access it by repeatedly tapping on the app version in the settings menu. (Yeah, Android, I took your thing. What you gonna do about it?) Once there you can enter commands to give you a boost in the game!

Running low on fuel, but you gotta go fast? Try fillherup

Got the munchies? Try nomnomnom

Can’t wait for Trader Ruth to show up? Try ruth

Did you kill your crew and now you’re all alone, huddled around a trash fire with nothing but your thoughts to echo off the cold empty hull? Try friend

These are just a few and surely I’ll be adding more in the future, so stay tuned. 😉

Staying Organized

Posted on June 11, 2018November 16, 2020 by Nicole

I’m a list-oriented person. I carry around a small notebook for jotting down ideas, and laying out my tasks for the day. I also have a digital version of this list, the master list, that I keep on Wunderlist. It’s easy for me to spend more time sorting my todos into lists and sketching out designs, than actually coding on the tasks. This is partly procrastination but mostly because I can plan from anywhere (at lunch, on the train, in line at the store), whereas I can only realistically work on the game for a couple of hours each day if at all.

Image of a Wunderlist list
Wunderlist

My schedule goes like this: I get home from work around 6:30 pm. I decompress for an hour or so. Then I go for a run or work out, which I don’t do everyday, but I’m trying to do more often. That lasts until 8:30 or 9. Then I have to make dinner and eat. (Yes, I know I shouldn’t eat so late, but I can’t do it before working out). That gives me approximately 2 hours to work on the game before going to bed around midnight. That’s all assuming I didn’t stay late at work, or go on date night with my beau, or have to do my taxes or whatever. I try to catch up on the weekend, working for long stretches at Starbucks, but there’s plenty distractions and chores to do on the weekends as well. Life, am I right?

As I near the end of what I’m going to call phase 1 of the project—that’s something like an alpha build I suppose—it’s easy to get off track and start adding features to make the game more fun or alive. The feature creep is real, and the lists keep me centered. When I come up with a cool idea I let it live on the ‘Eventually’ list so that it’s not forgotten and I can think on it for a while before jumping in head first.

I try to keep the lists sorted with most important tasks on the top and using Wunderlist’s starred items to indicate which tasks that I should do tonight or before the week’s end. As every programmer knows, every task takes longer than you think it’s going to. Very recently I’ve decided to put time estimates on tasks. This is with the hope that over time I’ll get better at estimating and maybe gain some insight into why things take as long as they do. Also sometimes it’s nice to pick a short task so I can actually see some finished progress.

There are a lot of programs and methods I’ve tried to improve my work process. I’ve considered using a Kanban board but it doesn’t seem useful for a team of one. I used Evernote for a brief period but their newish business model has driven me away. At least once a year I think a day planner is a good idea and then after a month or so the pages go empty.

Side Note: I’m a total office supply freak. I love notebooks and the feel of pages and I have too many. I find bliss walking through the pen aisle of Office Depot, letting my fingers linger too long on highlighters and smooth-writing pens. It’s a problem because it’s so easy to get carried away. This is why the small pocket sized notebook works best. You get immense satisfaction from filling one completely. I try to keep one solely for Fair Weather notes but sometimes life stuff sneaks in and that’s alright too.

So here’s what’s working: I like Wunderlist because it is polished, cross-platform, and free.99. I keep my documents/spreadsheets in Google drive; I use dropbox and a flash drive to keep my files. It’s pretty simple. I might have to adapt when/if someone joins the team, but that’s a bridge I’ll cross later.

image of ftue spreadsheet
ftue spreadsheet

The process is working for now. It’s nice to see my ‘Must Have’ list getting shorter despite a few large tasks still to do like find an artist, incorporate art, find audio. The code itself has a lot of TODOs scattered around it, but for now the focus is on getting something that works, while acknowledging systems that will need optimization or redesign when there’s time.

Ok. That’s it for now. I’ve got shit to do.

Better Late Than Never

Posted on May 16, 2018November 16, 2020 by Nicole

I’m finally doing it. I’m starting a devblog. I’ve been meaning to do this for some time. That is, start documenting the process of developing one of my game ideas. I have a full time job so I only work nights and weekends. It’s already been two years since I started, which is crazy, but I think I’m getting close to the finish line.

I’m full of ideas. I start and abandon projects on a whim. But this one, I want to finish. It’s really important to me. And when I finish I want to release it in the various app stores and submit it to Indiecade (more on that later). My goal isn’t to make money—at least not at the moment. A finished project I can be proud of is the goal. I feel like I’m in the home stretch feature-wise, but the game consists entirely of programmer art and the bugs are abundant. Also, there’s no sound. And I haven’t even thought about optimization. Well, maybe I’m not in the home stretch. I’m working on my own so things go slowly. But I know intimately how things can drag on forever if you can’t just draw that finish line in the sand. (Ask me about my always in progress novel.)

Anyway, let’s do this. I hope to get some audio  posts in too, because hey, why not?

Ok, later alligators…

About Me

Oh hello there! I’m Nicole and I’m a software engineer. This blog documents the development of the game projects that I work on in my spare time. My current project is a game called Fair Weather.

Latest Posts

  • How I Made It: A Player Decision Manager
  • Hand-picked Mobile Games
  • Chef Shelf

Archives

  • August 2021
  • March 2021
  • February 2021
  • June 2020
  • October 2019
  • September 2019
  • June 2019
  • February 2019
  • July 2018
  • June 2018
  • May 2018

More From Me

  • Get Fair Weather on iOS
  • More about Fair Weather
  • Email me!
  • More Me
  • LinkedIn

“If you can’t make a mistake, you can’t make anything.”

– Marva Collins