Archive for the ‘Game Design’ Category

Getting Lost and Fluttering Butterflies

These last couple of weeks I’ve been working on improvements to the little maze SRP project. Adding new art and effects, while continuing to learn about Unity’s Scriptable Rendering Pipeline (SRP).

The project also finally has a title. The Maze Where The Minotaur Lives.

Navigating Improvements

Many of my play-throughs of the maze would often leave me completely lost, with little indication that I’d be walking in circles. So I spent some time this week working on ideas to try and fix the problem.

I know getting lost in a maze is the point, but exploration is about being in control while also being lost. When you’re no longer in control, you’re no longer exploring, meaning it’s no longer “fun”, which means it’s no longer a game.

My initial ideas were a little overkill (as usual), such as adding a map and compass system. But I decided to keep it simple and update the art instead, hoping that a few art changes, and more visual variation, would help.

A variety of bushes with flowers found near the boundary of the maze.

The maze now has clear boundaries with a fence line. I also updated the bushes to have different colored flowers, creating variation along the path that previously wasn’t there.

After a bit of playtesting, the additional variation has helped create loose landmarks that can help figure out if you’re going in circles sooner. But more is still needed.

The boundaries also help make the size of the maze obvious, encouraging you to move into the maze once you’ve realized you’ve reached a boundary.

The whole exercise has reinforced my understanding of how important small artistic changes are in communicating subtle gameplay cues.

Fluttering Butterflies

With all the flowers, I decided to add butterflies to add a little more visual polish to the maze.

I also wanted to experiment with particle systems more, to see if I could spawn the butterflies as particles and animate their fluttering wings using a shader. It was something I hadn’t done before, so it was a good opportunity to test my shader programming skills and learn something new.

It took a bit of experimentation and feedback, but it turned out better than I thought it would.

Here’s how it’s works.

The fluttering is done using a custom shader that is driven by a particle system’s noise impulse value, passed it in using a Custom Vertex Stream. I used a motion texture mask to make sure that only the wings would animate, using the textures RGB colors channels as XYZ motion instead. How far and fast the wings can flutter is specified on the shader as adjustable values. Combining all these together creates the fluttering motion for each butterfly particle.

The noise impulse also influences the movement and fluttering speed of the butterfly. So if the impulse is high, the butterfly will flutter faster in sync with it’s movement.

The mesh with the butterfly pixel art (painted in Asprite). The black and green texture below the mesh is the motion mask used in animating the wings with the custom shader.

Updated Maze Textures

I updated the mazes hedge and ground pixel art, using some new techniques I’ve learned to make it look better. I also used this as a chance to optimize the textures, packing them all into a single texture sheet to improve rendering.

The individual ground texture, with not so good pixel art.
All the textures packed into one. With better pixel art.

Originally the maze was made up of 10 different materials and textures, creating a lot of extra work for the rendering pipeline to perform. Now it’s done using a single texture and material, creating less work for the rendering pipeline and improving the overall frame rate.

Camera Crossfade

I wanted to make it so that when the player finished the maze the camera would transition to different locations, showing places they may have missed, or even seeing the maze from nice camera angles.

So I added a camera cross-fade effect, to fade between different cameras in the maze.

I had to make a few changes to the camera system to make it easier to work with and maintain all the existing effects, such as the camera shake and post-processing effects.

The exercise taught me a lot more about working with cameras in SRP, and how to manage cameras and camera transitions.

And that’s been some of the things I’ve been working these last two weeks.

Next, I’m hoping to work on a few new 3d models and adding landmarks to the maze.

Portal and Treasure Chest Effects

This week I worked on adding effects to the exit portal and the treasure chests.

The first thing I worked on was the gateway portal effect.

The exit portal. It looks a lot faster here then it actually is.

The effect uses a combination of scrolling textures and a particle effect. I also added normal map distortion to the shader to manipulate the UV position to create a noise-y scattered effect.

I wanted to make it obvious it was an exit, fading the middle of the portal to make it look like it was passable despite its glow. I also made the particles move towards the portal to help create a sense of motion towards it.

Next I worked on the treasure chests.

Looks like it hasn’t been opened for some time.

I wanted to improve the feeling of opening the chest, adding a little anticipation to the animation and causing dust particles to burst out the first time you open them.

I had to update my SRP implementation to address some issues with rendering order. I was running into issues with the sky rendering over transparent objects in the scene.

The sky clipping out the portal effect.

Conceptually the solution was simple, render the background in its own pass, and render everything else on top. But it took a little while to understand what was going on and how to render everything in the correct order in SRP.

The above is a code snippet of the rendering order. “DrawVisibleOpaque” renders all objects that have no transparency. This also optimizes the background rendering, filling in areas the background won’t have to draw in.

“DrawBackground” renders all the objects that compose the background. It also renders its own transparent objects to keep the background elements together.

And then lastly, there’s “DrawVisibleTransparent”, which renders all other transparent objects on top of the opaque and background result, to create the final image.

The final image with the portal effect rendering correctly against the sky.

In the next article, I’ll be writing about culling optimization.

Maze Gameplay #3 – Objectives

Over the last week, I’ve been working on adding small objectives to make escaping the maze more challenging.

In the original project, to escape the maze, the player had to find a key that opened a gate to a portal which let them escape the maze.

A screenshot of the original Minotaur Maze project, with the key near the exit and the minotaur hiding in the background.

I wanted to do one better, initially coming up with a list of ideas that I thought would be fun. Though, many of them I decided not to do because I felt like they weren’t in the spirit of the game. With some requiring gameplay mechanics beyond the scope of the project.

So after talking through some of my ideas with my partner, I decided to stick with the original project’s design. We ended up focusing on smaller ideas around finding the key to open a gate and escape, which kept the project’s scope manageable.

Instead of finding a key, you have to find chests. One chest will have a key in it, while the other chests will have rocks. The key opens the gate to the exit, and the rocks can be used to defend yourself against the minotaur by throwing them at its head to stun it.

The key chest, including some new HUD elements to track health and rocks collected.

Since the maze is procedurally generated, the chests are placed at dead-ends that are in a certain range away from the player. This is to keep them far enough away so they don’t spawn too close to the start, but not so far that they’ll spawn close to the exit too.

The distance field used to determine chest placement. Purple-blues indicate the closest distance to the start, while yellow-reds indicate the distance farthest from it.
The selected range to place chests with out them being too close to the starting area.

With procedural generation, there is always a chance that there may not be any dead-ends in the selected range. If that happens, the code falls back to selecting a random dead-end. This is just to ensure that all generated mazes are playable and completable.

Once the player finds the key chest, they can open the gate at the exit, escaping the maze and the minotaur.

Next, I’ll be moving onto visual improvements, focusing on the art and adding some visual effects.

Maze Gameplay #2 – The Minotaur

Continuing from my last post, I added the minotaur to the maze, focusing on rebuilding its AI and improving its behavior from the original project.

The original AI code was made using a state machine. It got the job done, but as the requirements grew, so did the number of states and their complexity.

For this project, I’m using Behavior Trees instead to drive the Minotaur’s actions and reduce the amount of complexity.

The behavior tree that is driving the minotaur (WIP). It reads left to right, with nodes to the left taking priority if conditions are met.

I wanted to keep it simple, sticking to typical indie horror monster intelligence.

The minotaur will roam around the maze, moving from one dead-end to another, endlessly searching. When it sees the player, it will start chasing them, trying to hit them. If the player is unlucky, they die.

A set of sensors on the minotaur, each with different sensitivity, help detect the player and allow the behavior tree to react and make decisions. If the Minotaur sees the player, it will store that information so that the behavior tree can react. If the minotaur losses sight of the player, that information is updated so that the minotaur can decide what to do next.

The minotaur also has the ability to jump over pits. This is done using customized Off Mesh Links with Unity’s Navigation Mesh to help connect the gaps. Without them, the minotaur would fail to find a path around the maze.

The minotaur can jump pits. Remember that.

The minotaur is not smart, but it’s menacing enough that when you run into it, you’ll want to change directions.

The minotaur has appeared, it's time to run in the other direction.
Time to run away.

Since the major threat in this maze is the Minotaur, I wanted to make sure the player always knows it.

In the original project, there were only loud stomping footsteps that could be heard to emphasize the minotaur’s presence. It helped, but it wasn’t enough, especially if it was played without sound.

To make improvements, I added a camera shake to help communicate visually when the Minotaur’s nearby. Now when it’s near the player or chasing, the screen will shake, increasing in intensity as it gets closer.

Stomp, stomp, stomp!

The player isn’t completely defenseless. They can find rocks that they can use to throw at the minotaur’s head to temporarily stun him, creating an opportunity for the player to run away.

The rocks can also help keep track of paths that they may suspect to loop around. So it can be a useful tool or a defensive weapon.

But how you get these rocks I’ll write about in the next article, “Objectives”.