My First Ludum Dare

The best thing about Ludum Dare is that it ends, and ends quickly. My biggest concern for my first ever game jam was a too large scope for a tiny time frame. That didn't turn out to be the problem at all. My friend Page and I were able to put together a simple but complete game by the end of the second day. The challenge came when we saw what we made in front of us, knew it needed more, but didn't know what it needed more of.

Curling Cubes is inspired by the gym games you played in school, mixed in with a little curling/air hockey. Each player's goal is to put the 8 balls in the middle into their opponent's circle and to keep them out of their own circle. For each ball in your circle, your score goes up. More balls means your score goes up faster. The player with the lowest score at the end of the game wins.

Our theme was "The more you have, the worse it is". On its face, that's a tough mandate. Our minds jumped immediately to making the gameplay harder or worse based on how the player was performing. Judging by some of the other entries, we weren't the only ones with that thought. But that has a spiral effect if you decide to punish the player who is already struggling. Our initial idea was to make movement wonkier (slides are further and turns take longer) for the player who is losing. It turned out that it was very hard to tell that was happening unless we adjusted it so drastically that playing was almost impossible. In testing it felt like you were getting worse at playing when in fact the game was getting harder to control. So we ditched that.

Once we had the gameplay locked in, we went back and forth on what the goal was. First it was to get balls on the other player's side while keeping them out of your own, then it was to get as many balls as you could on your own side. After observing that it took very little skill to just push the balls to one side or the other, we added the circles and greatly decreased the friction on balls within either circle. At this point we were still considering having the balls in your own circle to be a good thing, so the more balls you had, the faster your score would increase.

It was then that we realized we had stripped out any semblance of the theme. Your movement was no longer affected by having more balls, and in fact you wanted more to increase your score. We were a bit stuck. Then one of us had the genius idea of making the person with the lowest score the winner. Ridiculous, you're thinking. Everyone knows a higher score is better. Our solution to this? Tell players that a high score is bad, actually. A couple quick code changes and suddenly the more you have, the worse it is. Page added a challenging but beatable AI for solo players and Curling Cubes was born.

I am happy that we produced a complete package, but the package is definitely thin. When we started I thought we'd end up with an unfinished mess. Instead, we had plenty of extra time to think about what to add. We had ideas that we thought had an equal chance to improve the game or make it worse. But game jams are not conducive to picking a bunch of potential features and testing them all. There are almost no potential features, in fact. If something goes in, it probably has to stay. So we ended up keeping things simple and adding little extras like a pause screen, enemy AI, and an animated background for the menu.

Not knowing what else to add was an unexpected issue. It's a problem I face in my hobby game development when there are no deadlines. But I assumed that with only 72 hours and a partner with a head full of good ideas, we wouldn't have time to fit in all the features we wanted. I'm still not sure how to solve that "developer's block", for lack of a better term. Maybe the thing is to try a bunch of small changes and see if one catches our eye. We got nervous about making the game worse at the end and not being able to fix it, but the cost was making something pretty conservative.

It feels good to having something complete, which is why game jams' arbitrary deadlines are so useful. I hope you'll check out Curling Cubes and tell me what you think.

Destiny 2 Made Me Love Cuphead

I never really enjoyed any of the games that directly inspired Cuphead. Contra and Metal Slug were before my time and as a kid their difficulty always drove me away. I assumed Cuphead would be the same, but I could never resist the simplest argument in its favor. Just look at it.

Cuphead Roller Coaster ARE YOU LOOKING?!

Destiny 2 won my money in a different way. I quit the first Destiny before any of the expansions but always heard how Bungie righted the ship with Taken King. I was eager to give the makers of my good pal Master Chief another chance. I'm glad I did. Destiny 2, for better or worse, is exactly what I wanted. You shoot aliens and get cool stuff. Sometimes alone, sometimes with friends. Sometimes with a bare minimum of brain power, sometimes with more concentration and coordination than you thought you could muster. In that last situation, you are almost always playing with people who want to work with you to get the thing done. That changes failure from a punishment to a learning experience. Giving and getting encouragement helps you push on to the next run.

Where Destiny 2 has friends, Cuphead has...a graph. Hear me out. Each time you die, a line with tick marks shows up and a Cuphead silhouette moves across it, showing how close you got to the end. Each tick mark represents a different phase if you're fighting a boss. It is the most encouraging a horizontal line has ever been. You see yourself literally inching closer to the finish line with each failed attempt.

Cuphead Progress Bar I appreciate that they let you quit the game completely after a failure.

The reward in Destiny is either the feeling you get from beating a super difficult Strike or Raid, or getting a new thing to look at. A shiny piece of armor, a new weapon, a slightly higher number. It's the same in Cuphead. The sense of relief and accomplishment when you beat that impossible boss is powerful and rare. But Cuphead delivers an even rarer kind of anticipation. I am excited just to see the next level. I find myself bombing my first few attempts (like I wouldn't anyway?) so I can take in every little detail on screen.

I probably would have bought Cuphead based off its look alone. And no matter how much I play of it, I am happy I cast my $20 vote for more games with incredible style. There's still a chance the challenge will put me off, and 2017 has no shortage of alternatives to offer. But I know that without all the space guns I fired in Destiny, I wouldn't still be firing finger guns in Cuphead.

The Thrill of the Chase

I've gotten to a point in MMMbezzlement where the enemy AI does what I want almost all of the time. That's better than I thought I could do when I started this game, so I will document that minor miracle here.

Here's what I needed my fellow coworkers to do as I stole their lunches all over the office floor:

  • Walk a basic path back and forth or in a circuit depending on the route.
  • If I am caught in an enemy's vision cone while eating food, game over.
  • If I am caught in an enemy's vision cone at any other time, the enemy should chase me for three seconds.
  • If I am able to stay out of any chasing enemy's vision cone for three seconds, that enemy will return to the point where they left their path and resume it.

When you look at that short list of behaviors they seem simple. And, once implemented, the code was in fact pretty straightforward. Setting paths for enemies in GameMaker (v1.4.1772) is trivial. Even getting the enemy to chase the player isn't too bad since GML has built-in functions for that. The tricky part turned out to be combining these behaviors and building a level that allows them to work.

Walking the Path

The default state of all the NPCs in a level is to walk a handmade path. I wanted every NPC to have their own path, but I also wanted only one enemy object. Setting a path in the Drag 'n' Drop UI is easy, there's an action explicitly for it. However you can only set the path per object, not per instance. To do this I created an invisible object that executes the following code for its Create event:

// Creates enemy instances and sets their paths
// This code will break if NUMBER_OF_PATHS is higher than the number of path assets!

NUMBER_OF_PATHS = 4;

var i = 0;  
var enemy = -1;  
repeat(NUMBER_OF_PATHS)  
    {
    enemy = instance_create(0, 0, obj_enemy);
    with (enemy) 
        {
        path_start(asset_get_index("pat_enemy" + string(i)), 2, path_action_reverse, true);
        // used in obj_enemy to return an enemy to a path after they have lost the player
        path = path_index;
        }
    i++;
    }

This will spawn one enemy instance for each path resource I have created and set them on it. This does require you to change the NUMBER_OF_PATHS variable every time you create or remove another path. It also sets each path by its name, so to iterate through them you should use a naming standard that includes a number. path_start and asset_get_index are both built-in GML functions.

The last thing I do for each enemy is save their path_index to a different variable. This is necessary for the way I implement the enemies returning to their path once they have "lost" the player.

Giving Chase

Every enemy has a field of vision (fov) instance attached to them. If the player is ever caught inside that fov and is not holding food, then the enemy chases them. If they catch the player it's game over. I'm not going to cover how it works in this post, but when an enemy sees the player the fov's can_see_player variable is set to true. That triggers the following code which runs under the Step event:

if (fov.can_see_player)  
    {
    fov.image_blend = c_red;
    chase_time = room_speed * 3;
    path_end();
    mp_potential_step_object(obj_player.x, obj_player.y, 4, obj_wall);
    }

Let's go through this line-by-line.

  • fov.image_blend = c_red simply changes the fov color from white to red to make it clear to the player that an enemy is chasing them and which enemy it is.
  • chase_time = room_speed * 3; starts the timer of how long the enemy is going to chase the player. This will only count down when the player leaves the fov and will restart if they re-enter it.
  • path_end() does exactly what it sounds like. This has to be called or the next line will be ignored. This is also why I saved path_index to path in the earlier code, because calling this function clears path_index.
  • mp_potential_step_object(obj_player.x, obj_player.y, 4, obj_wall) makes the enemy follow the player. The last parameter tells the enemy to avoid all instances of the wall object in the room.

Finally, the most complex enemy code runs when the player is not within its fov. This is immediately after the previous code:

else  
    {
    chase_time--;
    // chase player if they have not been out of fov longer than chase_time
    if (chase_time > 0)
        {
        mp_potential_step_object(obj_player.x, obj_player.y, 4, obj_wall);   
        }
    // if enemy has lost player, return enemy to point where they left path and resume
    else
        {
        fov.image_blend = c_white;
        if (path_index == -1)
            {
            // The path variable is set in obj_enemypathset to ensure path is not -1. 
            if (mp_potential_step_object(path_get_x(path, prev_path_pos), path_get_y(path, prev_path_pos), 4, obj_wall))
                {
                path_start(path, 2, path_action_reverse, true);
                path_position = prev_path_pos;
                }
            }
        }
    }

Chase time begins to be decremented at every step, but until it reaches zero, the same mp_step_potential_object function is called. If the player stays out of sight long enough, the enemy's fov turns back to white and they are sent back to the spot on their path where they left to chase the player. I needed to provide x and y coordinates to mp_step_potential_object, so I used path_get_x and path_get_y. These functions require the path index which was saved to path, and the position in that path to check. Path position is a value between 0 and 1 and path_position is a variable all instances with a path have. To keep up with this, I just have a line at the top of the Step event code that says prev_path_pos = path_position.

Finally, when the enemy reaches the point where they left the path (mp_potential_step_object returns true when this happens) they are restarted on that path at that point. This is where I use the path variable that I saved back in my enemy path set object.

Result

Enemy Chase Demo

So, the player gets caught by one enemy and avoids them long enough for the chase timer to run out. That enemy returns to their path and resumes it. A second enemy catches the player and chases them for much longer because the player keeps getting caught in its fov.

This is a rough and stripped down interpretation of what happens in a lot of stealth games. It works pretty well for the simple kind of game I am making.

Issues

While this system is passable, there are definitely some problems.

  • Enemies get stuck when navigating. This is less of a problem with the current map layout. It used to be much worse. I think this is just a limitation of mp_potential_step_object.
  • Paths are predictable. This could certainly be randomized by making more paths and spawning fewer enemies than paths each time. I could also program my own pathfinding algorithm.
  • mp_potential_step_object is resource intensive. It's not a problem yet, but I could get into a situation where a lot of enemies are chasing the player at once and the function is being called every step for all of them. A way to fix this might be to make it so that mp_potential_step_object is only called every few steps.

Please let me know if you have any questions about this system or ideas for how to improve it!

Tales From the Digiverse

This past weekend a friend and I visited our local purveyor of realities, Augmentality Labs. Up until then, I had only watched others play games in VR on video. The constant refrain I heard was "This is great and it's impossible to believe it's great until you try it." After doing just that, I think I mostly agree.

First off, I should clarify the circumstances. Augmentality Labs has a pretty great set up with maybe the least possible friction for playing VR games. I booked ahead of time, walked in, and they took my friend and I to the play space. We each got a roughly 6x6 soft mat area to move around and the HTC Vive headset and pair of controllers were ready to go immediately. We got a quick tutorial from an employee and could select any game they had and freely switch within our time slot. The headset wires were held above our heads with a pulley system rigged up in the ceiling. Judging from the videos I've watched of people tripping over cords, this is probably the biggest advantage over a home set up.

Another benefit to trying this at an arcade was the possibility of local multiplayer. On the day that I write this, HTC has announced a $200 price cut to the Vive. That's great but even if more people buy one of these things, they sure aren't likely to buy a $600 spare. So for an extra $20 (the rate for a half hour of play time) my friend and I could enter these ridiculous worlds together. We had time to squeeze in three games: Trickster, Drunkn Bar Fight, and Rec Room. I knew about Drunken Bar Fight and Rec Room before hand but had no idea there were co-op modes.

Trickster - Employees told us this was the most popular co-op experience, but I found it pretty underwhelming. You're dropped into a stock standard medieval fantasy environment and some orcs come at you. You can switch between sword and shield and a bow. Since this was our first game there was some novelty to swinging the sword and drawing the bow. After that wore off, so did the fun. At one point we thought we had accidentally backtracked to the same area, but it was just an exact copy and paste of an earlier environment. This might be a good intro to VR, but it didn't do much to distinguish itself.

Drunkn Bar Fight - This was easily my favorite game we played. You're dropped into a bar with a bunch of people standing around, and you fight them. Almost everything can be used as a weapon. Bottles, chairs, gumball machines, pool cues, and walkers are all tremendously satisfying to pick up and fling around. The character models are floppy and stretchy which amplifies your hits in a hilarious way. Get hit too much by angry patrons and your body falls away from you and crumples on the floor. A friend has to bend down and drag you back to your feet, just like in a real bar! The designers of Bar Fight clearly understand what is fun to do in VR. The level and variety of interactivity won me over. That, and nailing a guy in the forehead with a dart.

Rec Room - I only saw a very small slice of Rec Room in the form of a co-op quest. The gameplay was a lot like Trickster. You're armed with a sword and bow, and you work your way through the level killing creatures. Luckily, Rec Room is about 1000 times more charming than Trickster. The premise of the game seems to be that you're in the most well-funded community center ever built. The quest takes place in hallways with lockers and water fountains. They're decorated with cardboard cut outs of castle turrets and the enemies are mop buckets on wheels painted to look like orcs. If a friend goes down, no problem. Give them a high five to revive them. Seriously. The whole aesthetic of Rec Room is fantastic and it brought back childhood memories of made up backyard adventures.

I definitely plan to go back and try more games and pay for a longer session. I thought I might be nauseous or that my eyes would be tired after a half hour but I felt like I could have easily kept going. An employee told me they plan on selling alcohol there soon which is...a choice. I think I'll stick with one escape from the real world at a time for now. At least I know that no matter how I use VR, I'll look really cool doing it.

What I Played, What I Made 2

What I Played

Solitairica - If you had told me just a few years ago that I would be into a solitaire combat game with RPG elements, I would have wondered how that game could possibly exist and also laughed at you. These days I am more open-minded when it comes to game selection...and I like stuff I can play on the couch while I watch TV.

Solitairica game board

A glance at the image above might lead you to think "Oh, Hearthstone but with Solitaire." That's not totally wrong. You are battling fantastical creatures that use a wall of cards for defense. But instead of launching attacks at them you uh, play Solitaire at them. When the card wall is gone, you win that battle. The strategy comes in with your spells. Each card you remove grants you energy points in one of four stats. This energy fuels spells that provide some advantage like healing you or destroying a card in the wall. Every time you draw a card and can't play, the enemy gets a turn to attack.

After each match you can shop for more spells and items with gold earned in battle. It's crucial to check the notes on your enemy before you challenge them so you can slot in the spells that help you counter their moves. One enemy has attacks that ignore armor, for instance. It's great getting a card streak going and then neutralizing what would have been a devastating attack with the right combination of spells.

What's that you say? RPG morsels aren't enough to satiate your trend hunger? Maybe a light sprinkling of Rogue will do the trick then. If you are ever defeated, the run ends and you are granted some amount of currency separate from gold. This allows you to buy extra item slots and power ups for certain cards to use in your next run. I have yet to make it past the first "deck" which is comprised of 18 enemies. There are 4 decks included, and another one you can buy for $0.99, at least on iOS.

While Solitairica is maybe the best take on Solitaire I've played, it suffers slightly from still being Solitaire. There are long sequences where I draw card after unplayable card while the enemy beats me down. This is boring at best but it frequently creeps into frustrating. Of course there is randomness to other CCGs, but it feels like you have much less power to plan around bad luck in Solitairica. You're not going to draw a Blue Eyes White Dragon and destroy the board. You're just praying for, like, a 7.


What I Made

Last time I mentioned I had an idea to save the mechanic where the game isn't over if you are caught without food in my 2D stealth game MMMbezzlement. The idea is that if you are seen without food, instead of enemies completely ignoring you, they walk towards you while you're in their Field of View (FOV). If they reach you game over, but if you leave their FOV for some amount of time, they go back to their path. In the context of an office, I see this as coworkers trying to talk your ear off while you've got important things to do like steal and eat their lunch.

Getting enemies to leave their path and chase the player in GameMaker was almost trivial. If the player was in an FOV without food, I just ended the path of the enemy that owned the FOV with path_end and called mp_potential_step_object. This moves them toward the x and y coordinates of the player and also tells them to avoid all instances of a certain object. In my case those objects are walls. This worked pretty well, although my level design choice of making connected cubicles (the 'H' things) really threw them off. We join the chase already in progress:

MMMbezzlement enemy chase glitch I think the angry eyes really sell the manic thrashing on the other side of the wall.

As you can see, every enemy but the bottom leftmost one is after me. While starting the chase is easy, ending it has proven to be a bit tricky. I think I'm almost there, but right now when enemies give up on the player they are not returning to their path. Instead they are returning to their spawn point and all enemies spawn at (0,0) because of the way I set paths. When the game starts they are instantly teleported to their path starting points, so that hasn't been a problem until now.

Having an enemy chase a player and then return to their patrol if they don't catch them is a common mechanic in stealth games and a lot of people online have asked how to do it in GameMaker. Once I have my method down I'll post it and include GML.