Imponderabilia V 0.7

Hello all, this is an update to this project. That post contains a link to both the up to date engineering manifest of this project, as well as context to this post. But if you’re not here for the code talk then… it doesn’t matter I guess! I hope who ever may be reading this is having a great day and all is right in their world. Today I bring with me what will likely be the final build of Imponderabilia. The game has been extremely fun to work on, and really has taught me so much about architecture that I really look forward to applying to future projects.

Final build? But this is Version 0.7? Great observation! I wouldn’t feel comfortable dubbing this build as a completed version as we have not worked much (or at all) on the aesthetic of the game. So, as I’ve said in the previous build postings, I’m not an artist, this build is simply a proof of mechanics and architecture.

What has changed from previous build? Version 0.3 was largely an implementation of independent features which utilized the “InputGrouping” class I wrote and posted here.

  • Feedback
    • Emphasis on letting player know when an action has been completed
    • Indicators of when an interaction is available and how engage
  • Features
    • Implemented Rice Prep interaction
    • Implemented dependency between interactions
      • Player must cook rice first, then roll rice with fish, then cut roll.
      • Player can hold as many rice or uncut rolls as desired
    • Score system
      • Simple score system which considers the amount of sushi and quality/completion of required interactions
    • Added a timer which will end game after count down.
  • Bug Fixes
    • Key Sprite Manager (check engineering manifest in link at top of page)
      • Added FlipSprite method which fixed the irregularity in key sprite “pressing” animation
    • Sushi Roller
      • Countdown animation no longer misfiring when checking for input
    • Player Animator
      • Small bug fixes, added front and back walk animations

Here you go! Please enjoy, and direct any feed back you have to aidantakami@gmail.com

For those of you seeking a little more meat, below is my RiceMeter Game Object script. The product of this script can be seen when you approach the diamond colored grill in my build above. It consists of 2 sliders, and is meant to simulate the act of making rice.

By pressing space bar before the game start, the player adds rice to the pot which they will begin to cool once they “Interact”. The interaction begins once the player presses the indicated keys “AWDS” in that order. The player will now have begun the interaction. Players repeat that same motion of AWDS repeatedly to add water and clean the rice. however, once the water is full, the player can no longer add water, nor clean the rice. But players can drain the water by holding down space, which will allow room for more water to be added. But watch out, if you drain too much water, you will lose some rice. The 2 sliders represent the amount of rice, and the amount of water currently in the pot.

By adding rice, the player ends the interaction with more rice and can make more sushi from this rice, however the more rice added, the more tedious it is for the player to balance between adding water and draining it once the pot is full.

This script is only the half of the overall game, the other half is where all specific key strokes are registered, and where the outcome of the game is handled. However, this game object stores and deals with the values pertaining to how much rice and water are present, added, lost/drained, and all the above, making it (hopefully) a more enjoyable script to read. Here’s some code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using UnityEngine.UI;

public class RiceMeterGO : MonoBehaviour
{
    //Sliders for the rice and water level
    public Slider riceLevel;
    public Slider waterLevel;
    
    //TMP used to indicate how much rice is in the pot
    public TextMeshProUGUI riceText;
   
   //float used to determine how much the rice must be washed 
   // before the slider image, below, is lerped to green
    public float requiredWashes;

   //Slider image
    public Image waterFill;

   //How fast water is added and drained
    public float waterAddIncrement;
    public float waterDrainIncrement;

   //Used to progressively lerp slider image
    private float lerpInt = 0;
    private float timesWashed = 0;

    //Color32 used for lerping slider image
    private Color32 unwashedColor;
    private Color32 washedColor;
  
    //Enter key used to tell player when finished
    public SpriteRenderer enterKey;
    public SpriteRenderer enterKeyHighlighted;

    //float used to increment the key sprite switcher 
    private float keySwitchIncrementer;

    public void Start()
    {
        //Set slider max and min
        riceLevel.minValue = 0;
        riceLevel.maxValue = 10;

        waterLevel.minValue = 0;
        waterLevel.maxValue = 10;

       //Instantiate colors
        unwashedColor = new Color32(255, 0, 38, 255);
        washedColor = new Color32(0, 255, 47, 150);

       //reset
        ResetMeter();
    }

    //Will add rice to the rice level slider.
    //Max rice that can be added is 5
    //Tis will be start of the game for UI
    public void AddRice()
    {
        //Adds rice if under or equal to 5 cups
        if(riceLevel.value <= 5)
        {
            //Increments
            riceLevel.value += 1;

            //If this is the first increment
            if(waterLevel.value == 0)
            {
                //set to 0.5 higher
                waterLevel.value = 1.5f;
            }
            else
            {
                //else just increase
                waterLevel.value += 1;
            }
        }
        //Else  increment
        else
        {
            riceLevel.value = 1;
            waterLevel.value = 1.5f;
            Debug.Log("Rice reset");
        }

        //Sets Rice Text UI to rice level
        riceText.text = riceLevel.value.ToString();
    }

    //Will return the number of rice in meter
    public float GetNumberOfRice()
    {
        return riceLevel.value;
    }

    //Will add water to the Water slider
    public void AddWater()
    {
        //If water level is okay add water
        if (!isWaterFull())
        {
            waterLevel.value += waterAddIncrement;
            lerpInt += waterAddIncrement;

            //Change color & value of water
            waterFill.color = Color32.Lerp(unwashedColor, washedColor, lerpInt * 0.04f);

            //increments times washeds
            timesWashed++;
        }
        else Debug.Log("Full");
    }

    public void DrainWater()
    {
        //If water level okey drain water
        if (isRiceLevelOkay())
        {
            waterLevel.value -= waterDrainIncrement;
        }
        else LoseRice();
    }

    //Used to check if water level is acceptable
    private bool isWaterFull()
    {
        //If water is higher than rice
        if (waterLevel.value == waterLevel.maxValue) return true;
        else return false;

    }

    private bool isRiceLevelOkay()
    {
        if (waterLevel.value > riceLevel.value) return true;
        else return false;
    }


    //Will return int representing the quality of the rice
    /*
     * Retrun 0: bad rice, no cookie
     * Retrun 1: okay rice... still no cookie for now
     * Retrun 2: good rice, cookie
     * Return 3: Master rice, cookies
     * 
     */
    public int RiceQualityCheck()
    {
        if(timesWashed <= 49)
        {
            return 0;
        }
        else if(timesWashed <= 64)
        {
            return 1;
        }
        else if(timesWashed <= 79)
        {
            return 2;
        }
        else if(timesWashed > 80)
        {
            return 3;
        }
        else
        {
            return 0;
        }
    }

    private void Update()
    {
        //Will indicate to player when they have washed rice enough, triggers right before lerp is finished
        if(timesWashed > 80)
        {
            
            //Triggers enter key flashing
            if (keySwitchIncrementer < 1f)
            {
                enterKeyHighlighted.gameObject.SetActive(false);
                enterKey.gameObject.SetActive(true);
                enterKey.transform.position = new Vector2(gameObject.transform.position.x + 0.2f, gameObject.transform.position.y - 0.2f);
            }
            else if(keySwitchIncrementer < 2f)
            {
                enterKeyHighlighted.gameObject.SetActive(true);
                enterKey.gameObject.SetActive(false);
                enterKeyHighlighted.transform.position = new Vector2(gameObject.transform.position.x + 0.2f, gameObject.transform.position.y - 0.2f);
            }
            else
            {
                keySwitchIncrementer = 0;
            }

            keySwitchIncrementer += Time.deltaTime;
        }
    }




    public void LoseRice()
    {
        if(riceLevel.value >= 2)
        {
            riceLevel.value--;
            //Sets Rice Text UI to rice level
            riceText.text = riceLevel.value.ToString();
        }
        else
        {
            //Game End
        }
    }

    public void ResetRiceValue()
    {
        riceLevel.value = 1;
    }

    public void ResetWaterValue()
    {
        waterLevel.value = 1.5f;
    }

    public void ResetMeter()
    {
        ResetRiceValue();
        ResetWaterValue();

        lerpInt = 0;
        timesWashed = 0;
        waterFill.color = unwashedColor;
        enterKey.gameObject.SetActive(false);
        enterKeyHighlighted.gameObject.SetActive(false);
        riceText.text = "1";
    }

    //Used to set slider active or not
    public void SetRMActive(bool setActive)
    {
        waterLevel.gameObject.SetActive(setActive);
        riceLevel.gameObject.SetActive(setActive);
        riceText.gameObject.SetActive(setActive);

    }
}

Imponderabilia V 0.3

Hello all, I just wanted to post an update to my post here about my current game project, as I do now have a build ready to share! Obviously, this is a super early build, but it will hopefully provide some meaning to the code I posted last week. But once again please keep in mind this project has only been in motion for a week, we have only used free or self made art assets, and exemplified here is only a proof of mechanics and basic architecture for those mechanics.

A good designer really shouldn’t provide tips to players before a play test, but since I’m not there to observe people play, I want to make a few notes about how this game is meant to be played, as it’s likely different from most games you have been exposed to!

You will play as a sushi chef, and be able to engage in the interactions that will eventually make up the foundation of the mechanics of this game! So be prepared to treat your keyboard as your work station as you go into this experience.

With your right hand, navigate using arrow keys.

With your left hand, drag fingers along letter keys to engage “active interactions”.

Please don’t rush, treat your sushi gently.

Please report any bugs you find to me! aidantakami@gmail.com.

This is a VERY early build, there are bound to be lots of bugs and problems, but by play testing early, these bugs can be weeded out early, and save me the trouble later.

…. and with that… here’s a link to the Google Drive where you will find both PC and Mac Builds.

For my tech crowd, please take note that Input Manager at work there can be found in my last post, linked at the top of post. Additionally that post contains a link to my Engineering Manifest for the project, which contains summaries of all classes written for the project, as well ad general architecture overviews where necessary.

… and of course I would never leave you all without a script update. Today I’ll share with you guys a script which saved my life. After playing the build above, I’m sure you noticed the keys which animate alongside the player while they are close to an “active interaction”. Generally, one would either instantiate the specific key sprites when needed(both pressed and unpressed) or have each sprite referenced in code and pool them somewhere in the scene to use when needed, and be out of the view of player when not.

My KeySpriteManager sort of does both… but is useful for me as it can can take KeyCodes as args and return the corresponding “key sprite”. That’s right, public SpriteRenderer GetLetterSprite(KeyCode keyCode, bool keyPressed). Basically, my KeySpriteManager contains a List of SpriteRenderers of all the “key sprites” (which if you don’t get by now are the keys of a keyboard, individually cut out and used to emphasize the pressing of keys). Each letter contains both a pressed and unpressed sprite, so I allow access to the pressed sprites as well with the bool keyPressed.

Oh wow you made a list that takes KeyCodes and returns pictures who cares. I know. But this is incredibly useful for my game as InputGroupings (the class I shared in my last post (linked at top of post)) contain only keyCodes, and I can simply access these KeyCodes from any script and get the appropriate, matching key sprite from my KeySpriteManager. In addition to storing sprites, public SpriteRenderer FlipSprite(SpriteRenderer sr) will take a KeySprite, find it in the List, and based upon the spot it finds it in the List, return either the pressed or unpressed version of that key sprite, opposite of whatever was given to the function.

The code is quite repetitive, but I thought it would be good to share because of how greatly it works in conjunction with my InputGroupings class…. which I shared…. last week… link up top….. sorry. I’ll spare you guys the repetition of this code and trip it down, but you will certainly get the idea… Thanks for reading, here’s some code and a picture from the build I posted above!

using System.Collections.Generic;
using UnityEngine;

public class KeySpriteManager : MonoBehaviour
{
    //keySprites must have letter sprites entered in alphabetical order to function
    [SerializeField]
    public List<SpriteRenderer> keySprites = new List<SpriteRenderer>();


    //Will return corresponding SR for the KeyCode given
    //List is in aplhabetical order, A to Z, then 1 - 10
    //Array  must have list[x] = un pressed key, list[x+1] = pressed key
    public SpriteRenderer GetLetterSprite(KeyCode keyCode, bool keyPressed)
    {
        //This will be repetitive
        //Return Corresponding KeyCode
        if (keyCode == KeyCode.A)
        {
            //Returns unpressed key Sprite
            if (!keyPressed)
            {
                return keySprites[0];
            }

            //Returns pressed Key Sprite, sets ket to pressed
            return keySprites[1];

        }

        //Return Corresponding KeyCode
        else if (keyCode == KeyCode.B)
        {
            //Returns unpressed key Sprite
            if (!keyPressed)
            {
                return keySprites[2];
            }

            //Returns pressed Key Sprite, sets ket to pressed
            return keySprites[3];

        }

/*
 *
 *
 *      I told you I would spare you the repetition...
 *
 *
 */

        //Catch
        else return keySprites[0];

    }

    //Will returned the flipped sprite from the one provided
    public SpriteRenderer FlipSprite(SpriteRenderer sr)
    {
        //Finds sprite in List
        for(int rep = 0; rep < keySprites.Count; rep++)
        {
            //finds equivalent sprite
            if (keySprites[rep].sprite.Equals(sr.sprite))
            {

                if(rep % 2 == 0)
                {
                    return keySprites[rep + 1];
                }
                else
                {
                    return keySprites[rep - 1];
                }
            }
        }

        Debug.Log("Couldn't find sprite to flip.. probably the .Equals()");
        return keySprites[0];

    }
}

My New Project

Hello all, I hope as you read this blog post all is well in your world, and that hopefully reading about some video game architecture might alleviate some of the stress in your life. I’ve talked about a few projects on this site before, but usually just the specific code or concepts that I contributed to the project. However, this is different. Throughout all of last week, a friend and I were ping ponging ideas off of one another until we came up with an expandable mechanic system, a design to foster that system, and an aesthetic that we hope to use to tell a compelling narrative.

Why is this project special? It’s not. It’s just a small game project with a buddy. This happens all the time. I know that. But it’s special in that I’ve written every script that goes into a simple game, which is significant because I’m starting from scratch and able to take who ever might be reading this, along with me.

The Game:

  • Mechanics built around affordances of the Keyboard.
  • Players will control 2D top down sushi chef with right hand
  • Players will engage in unique “active interactions” with left hand by dragging fingers along keyboard in specific patterns, as specified by each individual “active interaction”.
    • Example: Dragging your pointer, middle, and ring finger along the “QWE” keys and downwards (to “ASD” the to “ZXC”) to simulate the rolling of sushi.
  • Perfection of mechanics yields higher rewards to the player
  • Player will engage in various acts involved in being a sushi chef, from cutting rolls to purchasing fish, and live out a life controlled by the daily requirements and upkeep.

The Aesthetics Pitch: So far most energy has been aimed in the direction of building a system of solid mechanics and enjoyable gameplay, however this is what we DO have for aesthetic: You are a sushi chef, and run your own small shop where people often come and go. You live here, because you always work. However, after a disease sends your nation into quarantine, your daily life becomes controlled by a checklist of repetitious tasks.

NOTE: I’m not an artist, most of the assets being used in my game are from this pack. Very great assets. The Player animations and art are done by Shad.din. I’m actually working with Shad.din to get more animations for the player, and more that pertain specifically to this game.

Engineers Corner

This game is my child. I’ve written every piece of code in it, and I’m very very excited to share it with you. In fact, I’m so excited I’m actually going to make available my Engineering Manifest for this project, which will be live updated with all new content. HERE is the link! In this manifest you will find a comprehensive breakdown of all the architecture at work in the project. On my site, however, you will continue to receive deep dives into specific scripts and concepts, they will just likely all pertain to this game project for the time being.

Where and When Can I Play? I will post the first “early early” build on this site, soon… at the time of this post (check that link above, it’s definitely different now) I have constructed and implemented all the architecture necessary for the unique style of input which this game is built on. It has been implemented in 2 unique “active interactions”, with just very early UI and animations. The player controller and animator are finished. And then of course, SODA is completely implemented, making the entire project modular, adjustable, and “dependency free”…. okay maybe a few dependencies.

UPDATE: Playable Build HERE

Where is the Beef? I get it, why come to this post if there’s no code talk. I respect that. Well, let me tell you about InputGrouping. InputGrouping() is a class I wrote for this project which allows us to detect input from specific, pre determined, groupings of keys. So think, how would you go about detecting when the player presses “Q”, then “A”, then “Z”? Did someone say new List<KeyCode>();? That person is wrong and stupid, you would use a new InputGrouping(); Because it does all the work of a List and more. So with that in mind, try to understand what’s going on here, and how I must be using it.

/*
 * An InputGrouping holds the keycodes necesary to execute a specific Advanced Interaction
 * 
 * This will be used by our InputManager Script
 */

using System;
using UnityEngine;
using System.Collections.Generic;



public class InputGrouping 
{
    //List of they keys sought by advanced mechanic, in order.
    public List<KeyCode> desiredInput = new List<KeyCode>();

    //InputGrouping will track it's own "progress" if keys are to be pressed in order
    public int currentStreakInt;

    public KeyCode GetNextKey(int placeInList)
    {
        //Should always be true
        if (desiredInput[placeInList] != null)
        {
            return desiredInput[placeInList];
        }

        //Debug section
        Debug.Log("Input Grouping value could not be found.");
        InputGroupingDebugger();
        return KeyCode.Backspace;

    }

    //Allows us to add keys to the list
    public void AddKey(KeyCode keytoBeAdded)
    {
        //Adds keycode to List
        desiredInput.Add(keytoBeAdded);
    }

    //Returns the size of the array, starting from 1
    public int GetSize()
    {
        return desiredInput.Count;
    }

    //returns progress through List
    public int CurrentStreak()
    {
        return currentStreakInt;
    }
   
    //Increases progress through List
    public void IncreaseCurrentStreak()
    {
        currentStreakInt++;
    }
    //Resets progress through List
    public void ResetCurrentStreak()
    {
        currentStreakInt = 0;
    }

    //Returns true if all keys in desiredInput are being pressed
    public bool AllKeysPressed()
    {
        //Int of how many keys are down 
        int keysCorrect = 0;

        //Iteractes through keycodes
        for(int rep = 0; rep < desiredInput.Count; rep++)
        {
            //increments int if key is registered
            if (Input.GetKey(desiredInput[rep]))
            {
                keysCorrect++;
            }
        }

        //Returns true if int matches desiredInput count
        if(keysCorrect == desiredInput.Count)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
 

   //call for multiple frame window and save highest return
   //Returns how many keys are pressed in List
    public int TotalNumberPressed()
    {
        //Int of how many keys are down 
        int keysCorrect = 0;

        //Iteractes through keycodes
        for (int rep = 0; rep < desiredInput.Count; rep++)
        {
            //increments int if key is registered
            if (Input.GetKey(desiredInput[rep]))
            {
                keysCorrect++;
            }
        }

        return keysCorrect;
    }


    public void InputGroupingDebugger()
    {

        Debug.Log("InputGrouping Debug report:");
        for(int rep = 0; rep < desiredInput.Count; rep++)
        {
            Debug.Log("desiredInput[" + rep + "] = " + desiredInput[rep] + ". ");
        }

        Debug.Log("Report complete. Have a nice day!");
    }

}

It’s not too complicated, but given that my game is heavily dependent on specific keystroke patterns, InputGroupings have proven extremely successful in managing the mess that can come of dealing with complex input detection. And so for an example of how this script appears in my game, I’ll give some snippets below. to note in this code: desidredInput is the name of the List<KeyCode> contained in InputGrouping, as well as currentStreakInt is the internal int which iterates through the List.

            //InputGrouping qazKeys
            if (Input.GetKeyDown(qazKeys.desiredInput[qazKeys.currentStreakInt]))
            {

                //Sets current streak plus 1
                qazKeys.IncreaseCurrentStreak();

                //Resets internal clock, giving player more time to link interaction
                internalClock = Time.time;

                if (qazKeys.currentStreakInt == qazKeys.GetSize())
                {
                    //Begin Interaction
                    Debug.Log("QAZ Input Detected. MakiMaker in range? " + makiMakerInRange.ToString());
                    ResetAllStreaks();

                    if (makiMakerInRange)
                    {
                        //Raise Event
                        makiMaker.Invoke();
                    }
                    
                }
            }

Of course you would have to see some SODA in my scripts. In case you’re unfamiliar with that makiMaker.Invoke() I highly urge you to check out some of my other posts about Scriptable Object Dependent Architecture. Well… that’s it for now. I hope that you enjoyed this post as much as I enjoyed sharing with you. And please check in for updates about the game, including playable builds! Wash your hands!

Image from Asset Pack we will be using, by GuttyKreum, linked above.