ColeSlaughter

LD 44

The Keyboard Cowboys Officially Sign Off!

fullComic.png

Finally, at long last, we've returned the favor to every single person who was kind enough to rate and comment on our game!

There were a LOT of y'all, so sorry it took so long! Understandably, we're a bit burnt out from rating LD games for the foreseeable rest of the rating period, so if anyone is kind enough to comment on our game after this post, apologies in advance. We will definitely play it at some point, but likely not in time before results are announced. :(

If you commented on our game and for some reason didn't get a comment back from me, please let me know and I will remedy that posthaste! For you we will make an exception!

That being said, we highly encourage people to try out other teams' games instead of ours. :) Knit Worth has gotten plenty of attention these past few weeks (and we love ALL of you for the feedback), but other games out there definitely deserve just as much attention.

With that being said, for our final Keyboard Cowboys Recommend...here's a list of our personal favorite entries we've discovered from LD 44:

BETRAYDED: For hilarious multiplayer action and absolutely excellent name. Creators: @barbiche, @drestin, @entropy, @acoussat, @roilbauk, @karreg

Ghostly Tricks: For innovative puzzle design and elegant mechanics. Creator: @nelson-william

Goals: For engaging, easy to understand puzzles and unique narrative. Creator: @vanleiden

Your Last 10 Seconds: For being hands-down one of the most unique games we played this jam. Creators: @oursbleu, @boris, @guimero64

Knock Knock...The Travelling Soulsman: For unique premise and absolutely delightful character design. Creators: @tbaudon, @sentsu, @turbogros, @bola, @esquimaupeche, @thom-pico, @megaelod, @camille-illustrason, @angry-squirrels

Dr. Ectomy: For excellent puzzle design and deceptively deep mechanics. Creator: @boxedmeatrevolution

Zap Signal: For having the only "audio-based" control scheme we've seen this jam (and for being super duper all-around cool). Creator: @christina-antoinette-neofotistou

Fluffensnuff: For absolutely stellar puzzle design and unique mechanics. On top of having an adorable art style. Creators: @idinakloppstock, @m2e, @kekeldy, @rongo-matane

Sick Business: For most original and "complete" experiences, on top of being darkly hilarious. Creators: @duke, @m2u-84, @byzanth, @collateralmind, @deusprox, @cee-dee, @very-dark-lord

LIFEPOWERED: For retail-ready mechanics, art, and audio working together as one cohesive whole. Seriously all it's short on is additional content, but even what it has here is impressive. Creator: @hawec

Lousy Life Lessons: For being hands-down the most stylish game we've played. Creators: @egonbegon, @awesomealliterationalliance

Sword of Hearts: For downright clever puzzle platformer level design and surprising secrets. Creator: @msiddeek

Good luck to everyone tomorrow! Y'all should be ridiculously proud of what you've been able to accomplish. We'll see you on the other side...

'Til next time! :pointright: :cowboy: :pointright:

Knit Worth results...We're so ecstatic!

Screen Shot 2019-05-21 at 1.38.41 PM.png

Y'all are way too nice, it brings a tear to our eyes...

So glad to see that a lot of you have the same messed up sense of humor as us. :smiling_imp:

If you'd like to try out Knit Worth yourself, you can find it here: https://ldjam.com/events/ludum-dare/44/knit-worth

The Keyboard Cowboys are absolutely looking forward to the next jam in October. Hope to see the rest of you there, too!

'Til next time! :pointright: :cowboy: :pointright:

Ludum Dare 45

The Keyboard Cowboys Ride Again!

keyboardcowboys.jpg

Us hooligans are back at it again for this upcoming Ludum Dare! :pointright: :cowboy: :pointright:

Looking forward to enduring the weekend with everyone and playing some awesome games afterwards!

Down to the wire...thank goodness we remembered to breathe!

HigherDesire_thumbnail.jpg

24 hours of straight coding during the last day, and the Keyboard Cowboys thankfully have something to show for it!

We hope folks enjoy our silly meditation game. Can't wait to play other peoples' stuff (after a very hefty nap...).

See y'all around in the comment sections! :pointright: :cowboy: :pointright:

Higher Desire Post-Jam Version!

Screenshot.png

...What, already?

Yup! It's amazing what you can accomplish if you're given just a feeeeeewwww more hours of time to catch easy bugs and playtest just a little bit!

Here's what we were able to accomplish:

1. Metronome Fixed: That's right, our submitted rhythm-based game only features a mostly-working metronome, how neat! Now it will actually accommodate players who keep rhythm a bit earlier than the actual beat, instead of favoring late time-keepers.

2. Tweaked Difficulty Curve: It's almost like when you have time to playtest, you get a better sense of what is too hard/easy. :open_mouth:

3. Various Bug Fixes: So our game (hopefully) breaks less!

Making this game was a classic lesson in scope creep prevention learned the hard way. We were scrambling until the very end to finish it, which unfortunately necessitated sacrificing some time normally dedicated to polish. We're still enormously proud of what we were able to accomplish though, which is why we've dedicated some time for some quality-of-life improvements!

If you haven't played Higher Desire yet, you can check out our original submission and Post-Jam version at our game page here.

If you're kind enough to give us a rating please make sure you judge us based on our original submission! The Post-Jam version really only exists to satisfy our own personal annoyances. :sweat_smile:

'Til next time. :pointright: :cowboy: :pointright:

Making the Metronome in Higher Desire

Ever wanted to make a rhythm game, but found the task of synchronizing visuals and player input to a constant beat too daunting?

Well fear no longer! For I have suffered immensely so you hopefully don’t have to, and I’m here to share the fruits of my immense pain with you today.

1. The Basics

First, we start with the backbone of the entire game: the Metronome class.

``` using System.Collections; using UnityEngine;

public static class Metronome { public delegate void MetronomeBeat(); public static event MetronomeBeat OnBeat;

private static float beatsPerMinute = 80f;
public static float secondsBetweenBeats = 0f;

public static double currentBeatTime = 0;
public static double nextBeatTime = 0;

public static bool metronomeStarted = false;
public static bool metronomePaused = false;

public static IEnumerator StartMetronome()
{
    Metronome.secondsBetweenBeats = 60.0f / Metronome.beatsPerMinute;

    Metronome.nextBeatTime = AudioSettings.dspTime;

    Metronome.metronomeStarted = true;


    while (true)
    {
        if (Metronome.metronomePaused == false)
        {
            double curTime = AudioSettings.dspTime;
            if (curTime >= nextBeatTime)
            {
                Metronome.currentBeatTime = Metronome.nextBeatTime;
                Metronome.nextBeatTime += Metronome.secondsBetweenBeats;

                if (Metronome.OnBeat != null)
                {
                    Metronome.OnBeat();
                }
            }
        }
        else
        {
            Metronome.nextBeatTime = AudioSettings.dspTime;
        }

        yield return null;
    }
}

public static void ToggleMetronomePause()
{
    Metronome.metronomePaused = !Metronome.metronomePaused;
}

public static void UpdateMetronomeTempo(float newBeatsPerMinute)
{
    Metronome.beatsPerMinute = newBeatsPerMinute;
    Metronome.secondsBetweenBeats = 60.0f / Metronome.beatsPerMinute;
}

} ```

Surprisingly, there’s actually not much going on here in terms of complicated code. Basically we call a coroutine that runs indefinitely and fires off an event every time we hit a beat based on the Beats per Minute (bpm) we specify. We keep a reference to timestamps of the current beat and the next beat for reasons that I’ll explain later. However, there are a few tricky “gotchas” that I’d like to point out.

Gotcha 1: What the heck is dspTIme?

In case you weren’t aware, Unity has a separate Time thread specifically for audio that is sample-based, aka completely frame independent. If you were to use Unity’s main Time thread (using either Time.deltaTime or Time.fixedDeltaTime), the slightest variance in framerate would slowly shift your metronome out of sync. This was a lesson I learned the hard way with my first foray in the “music-based” genre with an earlier LD compo entry I made called Orbitunes. The last thing you want is a frame-dependent rhythm game.

Gotcha 2: Why are you handling “Pause” so weirdly?

For typical Pause functionality, setting Time.timeScale to 0 would effectively stop calls for FixedUpdate() functions, thus pausing your game. It’s quick and a little dirty, but it (mostly) works. However, the dspTime thread can’t be manipulated like that, and is always ticking. If you don’t update the nextBeatTime when you want to pause your metronome, the moment you unpause it the condition (curTime >= nextBeatTime) will fire off a bunch of times until it catches up with the current dspTime, resulting in rapid-fire beats for a few seconds, depending on how long you kept the metronome paused.

Now that we have this Metronome class, anything that subscribes to the event OnBeat() will have a call back that fires exactly in sync with the Metronome. Pretty neat! Now let’s get into some nitty-gritty inputs…

2. The Input Logic

The entirety of the code for input handling is a little overwhelming to look at all at once if you don’t understand the logic of it. You can find the full code for it here, but I’m going to break it down essentially function-by-function in a way that’s hopefully understandable.

First Up!

public void Awake() { InputManager.calibrationKeys = new List<double>(); Metronome.OnBeat += this.ProcessBeat; } Simple enough. Make sure you subscribe to the Metronome’s OnBeat event so that you can sync to the rhythm. We’ll get to calibrationKeys later. ``` public void Update() { if (Input.GetKeyDown(KeyCode.Space)) { if (InputManager.calibrationKeys.Count < 20) { this.UpdateCalibration(); }

        this.adjustedInputTimestamp = AudioSettings.dspTime;

        if (this.IsMostRecentInputOnBeat() == true)
        {
            this.HitSuccess();
        }
        else
        {
            this.HitFail();
        }
    }
}

private void HitSuccess() { this.successSound.PlayScheduled(Metronome.currentBeatTime);

    if (InputManager.OnHit != null)
    {
        InputManager.OnHit();
    }
}

private void HitFail() { this.failSound.PlayScheduled(Metronome.currentBeatTime);

    if (InputManager.OnFail != null)
    {
        InputManager.OnFail();
    }
}

``` The Update loop is a little beefier, but still fairly straightforward. For the first 20 inputs (arbitrarily picked number) we calibrate the player’s inputs so that the game “feels right” for whoever plays it, regardless of their reflexes or machine specs. We’ll go over how to do that later. After that, we process every input, determine whether or not it was a “hit” or a “fail”, and fire off the proper event for each case. And that’s all Update does! Now let’s get into the more complicated stuff for actually determining these hits/fails…

``` private bool IsMostRecentInputOnBeat() { bool undershootTest = ((Metronome.nextBeatTime - INPUTGRACEBUFFER) <= this.adjustedInputTimestamp); bool overshootTest = ((Metronome.currentBeatTime + INPUTGRACEBUFFER) >= this.adjustedInputTimestamp);

    return (undershootTest || overshootTest);
}

``` Not much code here, but the logic of it might be a bit hard to follow, so let me break it down with a poorly-made timeline graph made in Paint.

metronomeTimeline1.png

Firstly, we have to remember that human reflexes are not only really delayed, but also widely varied. As such, we need to have a “grace window” for player inputs that will evaluate to “on beat” when they are pressed.

Once we have this grace window established, we need to know what to check. When the player hits an input, they can be considered “on beat” if they hit slightly after the current beat (overshoot) or slightly before the next beat (undershoot). Since we store the currentBeat and nextBeat timestamps in the Metronome class, this is super easy. If we detect a valid overshoot or undershoot from the player input, we consider it to be “on beat.” Nice!

…But hold on now, what if we also want to detect a lack of any input on a beat, rather than just an off-beat press? That requires a bit of additional logic that will fire on every beat in-time with the Metronome (ie: it subscribes to Metronome.OnBeat()). Here’s what that logic looks like:

``` private void ProcessBeat() { this.clickSound.PlayScheduled(Metronome.currentBeatTime); StartCoroutine(this.DetectBeatMiss()); }

private IEnumerator DetectBeatMiss() { double currentDspTime = Metronome.currentBeatTime; double endOfGraceBuffer = Metronome.currentBeatTime + INPUTGRACEBUFFER;

    //First, wait grace period
    while (currentDspTime < endOfGraceBuffer)
    {
        currentDspTime = AudioSettings.dspTime;
        yield return 0;
    }

    //Then, check to see if the beat was missed.
    //It's possible the player hit the beat within the grace window before and after the beat, so checks both sides
    if (this.WasBeatMissed())
    {
        if (InputManager.OnMiss != null)
        {
            InputManager.OnMiss();
        }
    }
}

private bool WasBeatMissed() { bool withinUndershootThreshold = (this.adjustedInputTimestamp >= (Metronome.currentBeatTime - INPUTGRACEBUFFER)); bool withinOvershootThreshold = (this.adjustedInputTimestamp <= (Metronome.currentBeatTime + INPUTGRACEBUFFER));

    return (!withinUndershootThreshold || !withinOvershootThreshold);
}

``` This is a little tricky, because remember there is a short grace window after a beat that the player can hit and still be "in time." Therefore, we have to wait for that grace window to pass before we can detect a miss due to a lack of input.

After that the logic check in WasBeatMissed() looks very similar to IsMostRecentInputOnBeat(), but there’s one key difference. This time, instead of checking for an overshoot of the current beat and an undershoot of the next beat, we are exclusively checking the grace window before and after the current beat! If we don’t detect any input within this grace window, we conclude that there was no input for the current beat, and fire off an OnMiss() event for other scripts to subscribe to and execute code for.

After all of that’s done, you’re pretty much set up for detecting inputs! Hopefully I was able to make it understandable enough, because I had to draw out timelines and work through it on paper dozens of times before I was able to get the logic right.

Now that we have the Metronome and the Input Logic in place, let’s add ooooone more bit of polish to really make rhythm input feel good.

3. Calibration

As I alluded to earlier, differences in computer power and player reflexes can make a rhythm game feel perfect to some, and completely off for others. In order to combat this, we’re going to add a sneaky calibration processing into the player’s first few inputs. You can theoretically do this wherever you’d like, but my team decided to put it during the player’s first 20 inputs, as we have a short cutscene at the beginning of our game, so it’s the perfect place to seamlessly tune the rhythm to the player’s preferences. Without further ado, let’s get to the code!

First and foremost, you may have noticed in the previous snippets the variable adjustedTimeStamp. This is actually a property in the InputManager with its own special get and set functionality:

``` private double _rawInputTimestamp = 0;

private double adjustedInputTimestamp { get { return (_rawInputTimestamp - this.calibrationValue);

    }
    set { _rawInputTimestamp = value; }
}

This is a handy way of making sure we always apply our calculated calibration value with every reference to player input timestamps. But how do we calculate this calibration value exactly? Let’s find out! Remember in our Update function, we’re calling something called UpdateCalibration() for the first 20 inputs. Let’s see what that function is actually doing now. private void UpdateCalibration() { this.GetCalibrationValue(); this.SetCalibrationAverage(); }

private void GetCalibrationValue() { double calibrationTimestamp = AudioSettings.dspTime; double preBeatCalibration = Metronome.nextBeatTime - calibrationTimestamp; double postBeatCalibration = calibrationTimestamp - Metronome.currentBeatTime;

    if (preBeatCalibration < postBeatCalibration)
    {
        InputManager.calibrationKeys.Add(-preBeatCalibration);
    }
    else
    {
        InputManager.calibrationKeys.Add(postBeatCalibration);
    }
}

private void SetCalibrationAverage() { double runningTotal = 0;

    for (int i = 0; i < InputManager.calibrationKeys.Count; i++)
    {
        runningTotal += InputManager.calibrationKeys[i];
    }

    this.calibrationValue = (runningTotal / InputManager.calibrationKeys.Count);
}

``` It’s a lot of lines of code, but the logic is fairly easy to follow if you take it slow.

So first, when the player hits input at a time that they feel is “on beat,” we calculate how close they actually are by calculating their overshoot and undershoot values. Depending on which value is smaller, we add that to the list of calibration keys and take the average. The resulting calibrationValue is what we will use to adjust the player’s input timestamp to match the timestamp in dspTime that they think they’re hitting. The result is that the player's “off beat” inputs (according to the computer) are actually processed “on beat” if they feel that way to them. Sweet!

Unfortunately, I wasn’t able to finish this calibration portion in time for submission of Higher Desire, resulting in some slightly borked rhythm keeping. If you’d like to see the metronome working correctly, feel free to check out our Post-Jam version found at the same game page!

In the meantime, this should be enough to get you started making your own rhythm games with proper input support! Hopefully it was useful! :smile:

‘Til next time! :pointright: :cowboy: :pointright:

Keyboard Cowboys Recommend...

keyboardcowboys.jpg

It's that time again!

Over the past few weeks the Keyboard Cowboys have been hard at work rating as many Ludum Dare games as we can! This past month has been particularly busy for all of us, so we haven't been playing and rating as much as we would like to be, but that hasn't stopped us from already finding some awesome stand-out titles!

So, without futher ado, here are some games that we've played that we think are totally worth your time:

1. SoundScapes

Team: @euler-moises, @kaish

This is a clever little find-it game where the images your comparing are complete blackness, and you spot the differences between the two using sound. With excellent minimalist presentation and superb sound design, this is a novel little idea that was absolute bliss to play from beginning to end.

2. Full Moon

Team: @thegrandpa, @chambre19, @divic, @slimabob

Easily the funniest game we've found so far this LD. You play through the memories of an easily distracted old man telling a How I Met Your Mother story that becomes increasingly rambling, much to the frustration of the grandson just trying to get a straight answer. This game has a ton of personality, and a punchline for the ages!

3. Barterin' Buckaroo

Team: @bobo-games, @paragonraptors, @fernando-puig, @onlyallygrace, @chelc

A classic game concept done in absolute style. You play as a down-on-his-luck sheriff who's had all of his equipment taken from him after a bad night of gambling. Armed only with a jar of your own piss, you must trade your way up to earn your belongings back, all the while interacting with some of the most stylish and entertaining cast of characters we've seen yet this LD!

4. Dreamiverse

Team: @lakuma, @antoined73

This is a lovely little slow-paced exploration game that has the player reveal the world enshrouded in complete blackness by tagging the walls, furniture, and floors with a luminous paint. The stakes are low and the pacing is leisurely, and every little discovery is wonderful to discover. This is an entry that has the mood category locked down in spades.

5. Untitled Goop Game

Team: @unept

...Holy crap, this game was just made by one person? Untitled Goop Game is a surprisingly deep and wonderfully presented action puzzle game where you control and adorable goopy character that has an irresistible urge to stick to things. You use these powers to create protective barriers, activate switches, and more in a perfect example of an "elegant mechanic." In our opinion, this game is criminally underrated.

...And that's all we have for now! If you're interested in trying the latest Keyboard Cowboys entry, you can play our game Higher Desire on our game page! But before you do, consider giving the above wonderful entries some love.

'Til next time! :pointright: :cowboy: :pointright:

A huge surprise for the Keyboard Cowboys!

Sorry if this is a little off-topic, but something really cool happened that we want to share!

Yesterday, something amazing got crossed off of our bucket list: A famous Youtuber made a video about our game!!

https://www.youtube.com/watch?v=Sj0aY_6lRX8

Serial Dater was an old Ludum Dare 43 entry, and the first game us Cowboys ever made together. It's super surreal to see this almost one full year after its original completion, but we've been ecstatic to see that the video is now sitting at #22 Trending in Gaming!

Naturally, everyone back here is overflowing with excitement right now reading through all of the comments and watching the video over and over. We just had to share with someone! :smile:

Anyhoo, thank you for letting us indulge for a bit.

Now back to the regularly scheduled LD45 programming. :pointright: :cowboy: :pointright:

Ludum Dare 46

Find Your Way Forward...

thumbnail640x512.png

This Ludum Dare, the Keyboard Cowboys present something a little different from our typical affair.

Find your way forward in our open-ended new game, Wayward!

We're all super duper ridiculously proud of what we were able to make this time around, and we hope you all enjoy it!

Play it here!

This is only the beginning, though. One of our favorite part of Ludum Dare is interacting with everyone and playing their games, so be on the lookout for @ruddiculous, @ruddgasm, @alphabetasoup, and @coleslaughter in your comment sections! :pointright: :cowboy: :pointright:

Keyboard Cowboys Recommend...

keyboardcowboys.jpg

It's that time again!

Over the past week, the Keyboard Cowboys (coleslaughter, ruddiculous, ruddgasm, and alphabetasoup) have been playing and rating as many games as we can! Already we've found some stand-out entries, that we'd love to share with y'all!

Now then, in no particular order, here's a list of games that have really stood out to us:

Letter to Thomas

Team: @louiejams

lettertothomas.jpg

Letter to Thomas is a short little narrative adventure game with a compelling story and seriously impressive visuals, all put together by a single person, which is still blowing our minds!!

Keep Our Mother Alive

Team: @daregb, @soyrandom

keepourmother.jpg

Simple, yet gorgeous, Keep Our Mother Alive is a fun little experience bursting with style and humor. Definitely a unique entry that brings more than a few chuckles as the plot progresses!

Mega Kinetic

Team: @blacksheepza, @add-an-a

megakinetic.jpg

Mega Kinetic is a bombastic physics-based action game with a cutesy toony art style. Keeping your little alien buddy alive very quickly gets chaotic and fun as it launches around the arena at high speeds!

Hive Preserver

Team: @for-science, @arcticmattekar

hivepreserver.jpg

Hive Preserver is one ridiculously well-polished game with top-notch level design to boot. It's kind of crazy to think this was all done in a weekend! This game feels complete, with a definitive beginning, middle, and end, and a difficulty that ramps up super naturally. Definitely check this one out!

Disco Let's Go!

Team: @ariake81

discoletsgo.jpg

Disco Let's Go straight-up should be getting more attention. The premise is simple, the vibe is spot-on, and the need to obsess over high scores is real. This is easily one of the unique concepts we've seen so far, and right now it's still looking for 20 ratings! Help 'em out, if you can!

The Bloodhaven Academy Dragon Club

Team: @nota, @nothing, @truefaux

dragonclub.jpg

This game is nuts. There's so much going on in Bloodhaven Academy Dragon Club, from randomly generated monsters, a turn-based battle system, unique character sprites and battle themes, a funny, tongue-in-cheek narrative, and so many monster stats!! This game really has it all, and we're super jealous...

Doin' It Live

Team: @sugarsores

doinitlive.jpg

This game just sticks out immediately the moment you see it, especially in motion. Doin' It Live is brimming with silly personality, and simple yet super fresh gameplay. This one's a quick pick-up, and once you dip your toes in, you'll be smitten with just how much personality the creator was able to inject into its character models and images (by themselves!!)

The Dragon's Curse

Team: @abyss, @james-kusardi, @horus, @aditya-kumar

dragonscurse.jpg

You've probably already seen this game floating around some of the top LD pages already. Truth be told, we like to highlight games that aren't already getting the recognition they deserve, but the folks behind The Dragon's Curse have been going above and beyond to give back to their community with super thoughtful feedback and already over 100 games rated (!!) so we feel that they at least deserve the mention. With some of the sharpest writing we've seen this jam, and some of the most impressive artwork, this game is absolutely worth the few minutes of your time to play it. Show these guys some love!

...And that's all for now!

We've been really impressed with what we've been playing this rating season, and we've been especially super ecstatic about the phenomenal feedback we've been receiving for our game, Wayward! It has been absolutely melting our hearts hearing about how people have been connecting with the game, and we love every last one of your insights. :slight_smile:

We will be continuing to plug away at more games as the weeks progress, and time permitting, we would love to write up another one of these in the future!

'Til next time! :pointright: :cowboy: :pointright:

A Wayward Postmortem

waywardlarge.png

Our team The Keyboard Cowboys have been absolutely adoring the feedback that we’ve been getting for our most recent LD entry Wayward. We took a ton of risks with this game, and now that the dust has settled a little bit (and our sleep schedules are almost back on track), we figured now would be a good time to reflect on our highs, lows, and in-betweens throughout the weekend. So, without further ado…

What went well

Proper Scope

Right out the gate, we started off strong by forcing ourselves to limit our scope as much as possible. We learned our lesson with our previous entry where we bit off a bit more than we could chew, and ended up paying for it dearly with a rather unpolished, confusing experience. This time, we wanted to make something that “even our grandmother could play.”

So we landed on a game that you just needed a mouse to play with. Left click to move, right click to interact. Super-duper simple. From an engineering standpoint, there weren’t too many technical hurdles to jump over (although there were some tricky maths here and there). As a result, we were able to dedicate much more time to developing the more critical aspect of the game, the tone and story.

waywardGif.gif

Making engineering tools accessible for artists

Another aspect of the jam that we focused on was trying to eliminate downtime for anyone. In previous jams, our fantastic artists (@ruddgasm and @ruddiculous) would inevitably run out of assets to create, and would often have to wait around while us engineers frantically tried to squash the last few bugs.

This time, however, we took steps to allow our work to be parallelized. Extra effort was put into creating code snippets that could be modified directly in the inspector for artists to tinker around with when they were done making assets. One such code snippet was a random object generator that allowed us to mass-produce hundreds of trees throughout our scene. By just tweaking a few variables, the artists could generate a plot of trees and scatter them throughout our level without any assistance from the engineers whatsoever.

random3.gif

With helpful tools like this in place, the artists could put together our main level scene while me and @alphabetasoup cracked away at wrapping up the gameplay. The whole process of putting together the scene took several hours for the artists, so it was incredibly fortunate that they didn’t have to wait for us to finish our dev work before we could even begin to think about putting it together. An extra hour or so making our systems accessible to non-engineers ended up saving us several hours of everyone’s time thanks to the bottlenecks that we were able to eliminate.

No lights!

Despite how it might look, Wayward was actually created using absolutely zero lighting objects! Everything you see is a colored texture with some magical shader effects on them. You can learn more about how we pulled this off by reading this awesome write-up by @ruddgasm.

nolights.png

This decision was made super early on, as in the past we’ve been burned almost every time when it came to putting together a WebGL build for people to play. In almost every game we’ve developed, the artists have had to compromise what their vision was when we discovered late in development that the lighting effects we used absolutely tanked WebGL performance. So this time, we completely eliminated lights from the equation, and were able to make something that still looks pretty shockingly lit, all things considered!

The Challenges

Getting a bunch of goofballs to be serious

Anyone who’s familiar with the catalog of Keyboard Cowboys games knows that one of our biggest strengths is just how stupidly silly we can get sometimes. To be fair, at first we approached this jam no differently, and our brainstorming sheet was filled with the usual off-the-wall antics that we were used to running with.

…But somewhere along the way, things slowly got more serious. I blame the circumstances of the jam. For a lot of us on the team, this was going to be the last LD that we’d have time to do together for quite some time. The sense of finality was very sobering, and we began to discuss the possibility of making something on the more serious side. Going in, we had absolutely no idea if we were capable of pulling something like that off, but everyone’s enthusiasm about the prospect meant that we were willing to try.

Story-based games are scary!!

Especially in a 72-hour period, writing a story that successfully conveys its ideas and tone to a player that can theoretically do anything they want is ridiculously tough, and none of us on the team had any professional experience writing stories. Looking back, it’s kind of bonkers that we even tried in the first place (but we’re glad we did!).

whatdoido.png

With our writing experience (or lack thereof) in mind, we decided to focus on writing snippets that conveyed an emotion, or captured the core essence of what we wanted to say. If we tried to make one cohesive story with a beginning/middle/end, we knew we would fail to highlight what we truly wanted the player to feel.

So for our story, we all got very personal and very exposed. The snippets of text you see in the game are an amalgamation of all of us writing our own personal thoughts and experiences. There’s four different voices in there, and they each have something to share. Putting together enough story content for the game was a very therapeutic and eye-opening experience for all of us.

"Wait, what are we making again?"

Throughout development, almost up until the last day, we struggled constantly to communicate what it was we were trying to make. We were all on the same page in terms of overall concept, but executing on that concept was a difficult thing to keep straight in everyone’s heads.

Wayward is so loosely-defined, you’d be forgiven for thinking it’s not even a game at all. We didn’t have a language to fall back on to communicate how we wanted certain mechanics to work, or story beats to play out. Hell, even during brainstorming, I had a completely different idea of what perspective the game was going to be played in compared to my teammates. I had no idea until we all sat down and drew on paper our own interpretations of what we thought it was supposed to look like.

This vague confusion persisted throughout almost the entire weekend. It’s a wonder we were able to put something together in the first place, but the constant back-and-forth between us was fundamental in making what ended up being the final game.

Reception

stopbeingreal.png

Before we even created a new Unity project, we were all on the same page with regards to the fact that we were planning to make an experience that would not have universal appeal. Some people might not connect with the game at all, and find it a boring, unchallenging piece of fluff, while others (hopefully) would be able to connect with it and learn something about themselves from it.

Our goal was to make something that appealed to the latter group. For the people who did find something to connect with, we wanted it to resonate greatly, so we put a lot of focus into that. And so far, it seems to have paid off!

We’ve been seeing so many heartwarming responses from people who have played Wayward, talking about how it impacted them emotionally, got them to consider their path in life, and more. Naturally, we’ve also seen plenty of comments from people who were expecting something a little more “game-y”, and that’s okay too. At the end of the day, we were able to make something that accomplished exactly what we set out to do, and we’re all ridiculously proud of that.

...But what does Wayward’s story even mean?

We’re not tellin’! :wink:

Truth be told, the game means something a little different to all of us on The Keyboard Cowboys. While we started with an initial idea for the tone, it slowly evolved into something completely different and personal to each one of us.

…But we won’t share those thoughts either. :smile:

The absolute most important thing for us is that Wayward might mean something very important to you. We don’t want to take away what someone might adore about it by giving a definitive answer as to what the game is or isn’t about. If you were able to take away some message, and it connected to something close to your heart, then we want to honor that.

We didn’t try to make a game to tell someone our opinion about something.

We tried to make a game that gets the player to discover something about themselves.

And what that thing ends up being is not up to us to decide.

If you haven’t yet, come check Wayward out for yourself! We are all super duper ridiculously proud of what we were able to make this time, and reading about everyone’s thoughts on it has been absolutely wonderful.

Until then, we’re still plenty busy rating peoples’ games in various comment sections. Be on the lookout for more from us very soon!

‘Til next time! :pointright: :cowboy: :pointright:

A Wayward Time Lapse

HAPPY RESULTS DAY, EVERYONE! :tada:

As the curtain closes on yet another Ludum Dare, the Keyboard Cowboys have been reflecting on our experience. This particular jam was a special one for us, because it is likely to be our last jam as a team for quite some time. :frowning2:

A lot of us will be busying ourselves with life over the next few years, and it's currently unknown when we'll all be free to get up to our usual shenanigans again.

To commemorate the end of an era, we've put together another time lapse documenting our descent into game jam madness many weekends ago:

https://www.youtube.com/watch?v=KYeV33Y181U

If you're curious to see the fruits of the labor depicted above, you can play our game Wayward here! We are super duper proud of what we managed to make this time around, and we're so happy to see people connect with it in so many heartwarming ways.

This Ludum Dare has been a fantastic experience, and we played some absolutely stellar games this time around! Regardless of the results, y'all should be super proud of what you've been able to accomplish. :slight_smile:

We'll see you all on the other side.

'Til next time! :pointright: :cowboy: :pointright:

Ludum Dare 47

Thanks for playing Dude Ranch!

dudeRanchSmall.gif

We Keyboard Cowboys tend to make some pretty bizarre games, and it always warms our hearts to see people not only take our weirdness in stride, but actually genuinely enjoy it. :blush:

The love that we've been getting so far for Dude Ranch has been fantastic, and we plan to reciprocate it in the coming weeks as we play and rate more and more games. :heart:

In the meantime, if you haven't already, why not checkout the game for yourself? It's never too late to get in the mood for dude.

'Til next time! :pointright: :cowboy: :pointright:

Critics are raving about Dude Ranch!

The response we've been getting for Dude Ranch has been everything we could have hoped for and more. Here's what just some players have been saying:

dudeRanchResponses.png

dudeRanchTitle.gif

If you haven't yet, come witness the madness for yourself, and get in the mood for dude.

We plan to go through everyone who was kind enough to comment on our game so far and play/rate their games, but it has been a bit of a process, so we appreciate your patience!

'Til next time! :pointright: :cowboy: :pointright:

The Keyboard Cowboys Inagural Streaming Sesh

keyboardcowboys.jpg

Howdy y'all! :cowboy:

2020 has seen the birth of a lot of new hobbies for many. For us, it has manifested in the form of streaming. This weekend, we here at the ranch have decided to dedicate some of our usual weekend time to playing some Ludum Dare games!

If no one's around, we'll be prioritizing games made by folks who were kind enough to play and rate our game, *Dude Ranch*, already. But if you'd like to join in on the shenanigans, we'd be happy to try your game out!

You can find us getting up to our usual pure unadulterated ridiculousness here, where we'll be playing games for the next 2-ish hours!

Hope to see you there! :pointright: :cowboy: :pointright:

streamingScreenshot2.png

The Keyboard Cowboys are back at it again on Twitch

streamingScreenshot3.png

This time with 100% fewer technical difficulties (hopefully...)!

Come join our descent into madness as we play more Ludum Dare games!

Like last time, we'll prioritize anyone who uses the submit form in chat first, but if no one shows up we'll just be going through the list of people we have yet to rate who were kind enough to comment on our game, Dude Ranch. We plan to stream games for ~2 hours before we return to our regular weekend stream plans.

Hope to see you there! :pointright: :cowboy: :pointright:

Making the Snappy Camera of Dude Ranch (Part 1: The Snap)

As the sole engineer this time around for the Keyboard Cowboys, there was a lot of components that I had to contend with to make sure our game, Dude Ranch was functional. Character movement, lasso mechanics, Dude AI, a spawning system, scorekeeping, UI anchoring, etc. It was a lot of work!

Unfortunately, the curse of game design is the fact that a functional game is not necessarily a fun game! As such, I also had to dedicate a large amount of time making these mechanics “pop” in any way that I could. One of the most taxing layers of “juice” that I added to the game had to do with the dynamic camera movement.

When you walk around Dude Ranch, the camera follows the player around the screen as you would expect it to. Right off the bat there’s a bit of a wrinkle to this. Our game is presented with a 70-degree isometric view. Despair! This would cause some problems if we were to attempt to move the camera both horizontally and vertically, as illustrated here:

cameraWeirdness.gif

So what’s a programmer to do? The answer is actually surprisingly simple:

cameraHolder.png

Create a parent CameraHolder object that always maintains a 0-degree rotation! Now you can rotate the camera to your heart’s content, and as long as you reference the CameraHolder for all of your movement, you’ll be able to move parallel with a player character at all times!

cameraGoodness.gif

Okay, so we have a functional camera now. How can we make it feel better? One of the ideas I came up with was to have the camera “snap” right to the action whenever the player managed to latch onto a Dude. No matter where the player was, or where the Dude was wrangled, I wanted to action to be perfectly framed.

wrangleGif.gif

This was accomplished through the magic of midpoints! After dusting off some old high school geometry skills, I took the position of the player character and the position of the wrangled Dude, then averaged the X and Y values in order to find the exact middle between the two every time. After that, I would just lerp the camera’s position to that target point.

midpoint.png

This would ensure that the camera would always be positioned right between the player character and the wrangled Dude, no matter where either of them were at any time. Easy peasy!

But wait!

Our game is presented with a 70-degree isometric view. DESPAIR!!

This method would only work if our camera was looking straight-down at a 90-degree angle! Not a problem! All it takes is a little dusting off of even more high school geometry. Allow me to explain with a poorly-made diagram I made in MS Paint!

cameraDiagram.png

Basically, our camera is looking too far ahead of the target point that it’s snapping to. In fact, it’s looking exactly X distance too far. If we can just move it back X distance, then it would be looking exactly at the target point. Lucky for us, we can do some simple trig to figure out what X is. We already know what Y is, and we know the degree of rotation on the camera, so we can use both of those to solve for X:

CodeCogsEqn.png

Nice, now every time we snap the camera to the midpoint of 2 objects, we can subtract X from the value to make sure that the angled view point of the camera is what lines up with the target point, rather than the camera’s position lining up with it instead!

…But we’re not done yet…

Stay tuned for Part 2, where I talk about what I had to do to make the camera dynamically zoom closer to the player as they struggle to wrangle in the thiccer Dudes on the Ranch.

dudeRanchSmall.gif

Hopefully this was somewhat useful for folks. I’m a tad tired, so apologies if any of this is unclear at all. This camera ate up a lot of my time during this jam, so if I can somehow make it easier for future devs, then the struggles will have all been worth it. :heart:

Until then, feel free to check out the fruits of our labor by playing Dude Ranch here! We’ve been loving the reception we’ve been getting so far, and we’re slowly going through everyone who was kind enough to leave a comment on our page with plans to rate their games in the very near future!

‘Til next time! :pointright: :cowboy: :pointright:

Making the snappy camera of Dude Ranch (Part 2: The Zoom)

Howdy folks! :cowboy:

Back at it again with another breakdown of how I got the camera working for our game, Dude Ranch! This time I’m here to talk about how I implemented the dynamic zoom effect that happens as you mash the mouse buttons to wrangle a particularly thicc Dude into your clutches.

dudeRanchSmall.gif

The logic for this is actually pretty straightforward, so I’ll start with posting the relevant code for you to peruse.

``` private void InitiateImpactZoom() { if (this.ImpactZoomCoroutine == null && this.SnapCoroutine == null) { this.ImpactZoomCoroutine = StartCoroutine(this.ImpactZoom()); } }

private IEnumerator ImpactZoom()
{
    if (this.ImpactReturnCoroutine != null)
    {
        StopCoroutine(this.ImpactReturnCoroutine);
    }

    this.cumulativeYZoom += this.impactZoomAmount;

    Vector3 targetPoint = new Vector3(showcasePoint.x, this.transform.position.y - this.impactZoomAmount, showcasePoint.z);

    while (Mathf.Abs(this.transform.position.y - targetPoint.y) > 0.1f && followPlayer == false)
    {
        this.transform.position = Vector3.Lerp(this.transform.position, targetPoint, this.impactZoomSpeed);
        yield return null;
    }

    this.transform.position = targetPoint;

    this.ImpactReturnCoroutine = StartCoroutine(this.ReturnFromImpactZoom());
    this.ImpactZoomCoroutine = null;
}

private IEnumerator ReturnFromImpactZoom()
{
    Vector3 targetPoint = new Vector3(this.transform.position.x, this.zoomedInYValue, this.transform.position.z);

    while (this.transform.position.y < targetPoint.y && followPlayer == false)
    {
        this.transform.position = Vector3.Lerp(this.transform.position, targetPoint, this.impactReturnZoomSpeed);
        yield return null;
    }

    this.transform.position = targetPoint;
    this.ImpactReturnCoroutine = null;
}

```

I won’t post the full camera script, because honestly it’s a mess of flags and poorly organized code that would probably just serve to confuse rather than educate. But allow me to explain what’s going on here in case any of it’s confusing!

So basically, whenever the player clicks a mouse button to start wrangling a dude in, an event is fired that calls InitiateImpactZoom(). This in turn fires off a coroutine that does the actual zooming. The “showcasePoint” in the ImpactZoom() coroutine is actually the midpoint that we figured out earlier in Part 1. We use this as our anchor to determine what exactly we’re zooming into. Since our game functions on the X/Z plane, we’ll be manipulating the Y position of the camera to zoom in and out.

impactZoomAmount and impactZoomSpeed can be set to whatever you want inside the class, and you can fiddle around with the values to make more bombastic or more understated zoom effects that fit your needs. From there, we just do a simple Lerp in order to ensure that the camera doesn’t jarringly jump to positions and disorient the player.

After the impact zoom was finished, I wanted to have a little “recovery” zoom-out effect that slowly tried to return the camera back to its original position, so it encouraged you to keep wranglin' once you latched onto a Dude.

dudeRanchRecovery.gif

This is called at the end of the ImpactZoom() coroutine, and it slowly “undoes” the effects of the original impact zoom. It’s important to make the value for impactReturnZoomSpeed significantly smaller than the initial impactZoomSpeed, otherwise the camera will look all jumpy zooming in and out between every click. This zoom-out coroutine should also be cancelled if the player clicks again, so it doesn't interfere with the next zoom (this is handled in the first lines of the ImpactZoom() coroutine).

And that’s about it! To be honest, this isn’t the prettiest thing in the world. Every now and then the camera still has its moments where it looks a little jitterier than I would prefer. And as I mentioned before, the whole script that these short functions fit into is an absolute mess, which was the source of many of my frustrating bug hunts…

However, I don’t regret spending so much time fiddling with it and wrestling with bugs, because I like to think the dynamic camera makes a big difference in making our frenetic game feel even more chaotic and playful!

If you’d like, you can see the fruits of our labor by playing Dude Ranch here!

We already have a big ol’ list of games to play from the people who were kind enough to comment on our game, and we’ve already played a ton of super stellar ones already! Be on the lookout for a recommendations post very soon!

‘Til next time! :pointright: :cowboy: :pointright:

THANK YOU FOR 100+ RATINGS ON DUDE RANCH!

dudeRanch100Compressed.gif

Dude.

DUDE!!

We are ecstatic that so many people have played our silly little game. Y'all have left some wonderful comments and some super helpful feedback, and we're all just super duper grateful for it. :heart:

We will be doing our best to go through our comments section and rating everyone's games in there, but the three of us Keyboard Cowboys live some pretty hectic lives, so it might take some time. We appreciate your patience!

In the meantime, if you still want to try wranglin' some dudes, you can do so here!

There have been some pretty ridiculous high scores popping up in the comments section. @jenkinz94 seems determined to claim the throne as the dude-iest rancher of 'em all!

Once again, thank you so much for all of your love and support! We hope to return as much of it as we can in the next 2 weeks!

'Til next time! :pointright: :cowboy: :pointright:

This is Peak Performance Dude Ranch

Every time the Keyboard Cowboys make a high-score based game, our good friend @jenkinz94 seems to make it his life goal to achieve a score in it that us devs could never dream of attaining ourselves.

When we finished making Dude Ranch, we committed the grave sin of taunting him to try and beat us.

Long story short, he made us eat our words. Repeatedly.

The current world record for Dude Ranch now sits at an absolutely filthy 4616 points.

Below is the culmination of this man's madness in pursuit of his dream:

https://www.youtube.com/watch?v=aYSixB5B6lo

Do you think you have what it takes to dethrone him? I honestly feel like I would be scared to meet the person that does...

@arthurds in case you were wondering, this is how it was done...