8/3/2020 - Minor Update - Big changes coming
Been working on improving the format of these posts to be more engaging so I will be doing a series of video blogs / tutorials in the next few weeks on my progress with the game. Stay Tuned for more details soon! For the time being, here is a sneak peak on some of the work for the intro. You can also see some work I have been doing in porting some of the logic code to generate the roads to Bolt (Unity's visual script editor).
7/22/2020 - Devlog #2
After what feels like forever not making any 'progress' I finally have something show for the weeks of refactoring. First iteration of the 'city' generator and you can see there are a lot of problems with the initial version.
Observations:
1. Texture tearing / overlapping
2. Missing Textures
3. Performance concerns (low framerate)
Problem 1 and 2 were fixed after lots of debugging and learning about how unity handles translation+rotation when the parent object isn't at world center. Performance concerns were to be expected and I'll deal with later. There are still some problems with some of the algorithms that generate and orient the road / city tiles which you can see in the next image.
Next steps will be
1. Fix orientation of road tiles, make sure certain tiles are being generated that aren't (sidewalks that are adjacent to roads and tiles that are used when 2 roads intersect)
2. Implement chunk manager to address performance issues
3. Add additional tile variety, write building generator
7/9/2020 - Devlog #1
Lots of work done on the code base this week but not a lot to show as far as tangible cool game stuff. Flowcharted out the basic algorithm to determine how tiles should face based on what the surrounding tiles are which is needed for road tiles since I'm currently only using 2 prefabs for roads (one for lanes, and one for 4 way intersections). I prefer to map these out in pseudo code so I can focus on the process before deciding on the implementation. You can see chart and implementation below. Most of the other work was just refactoring. I realized my approach would probably have a huge performance bottleneck since my original design for building the city grid was doing multiple loops through a 256x256 size texture (control texture is what I refer to this as in code). If you work out the number of iterations that is you see the problem so I broke the code out a lot, added additional interfaces and factories to build the tiles from so now to build the city it only goes through the texture once. I still think I will need to actually change how this draws during level generation to a more minecraft chunk approach, a chunk being maybe 32x32 tiles instead of the entire 256x256 size city which means instantiating 65536 prefabs all at once and some of those prefabs will contain multiple meshes. I'll tackle that hurdle when I finish implementing the level generator which is %75 complete.
6/25/2020 - Random UX comment of the day
I don't know why I never noticed in Microsoft Azure the Delete tenant button is right next to 2 other non destructive buttons. This is such a hallmark of bad UI design. It really reminds me of the 2018 emergency alert that was sent to everyone in Hawaii that a ballastic missle was approaching. The resulting investigation confirmed bad UI played a part in this (because the buttons were right next to each other). "In his report, published January 30, Oliveira faulted "insufficient management controls, poor computer software design, and human factors" for the incident.[39]". If you read the wiki article you will note the employee even clicked through at least 1 confirmation prompt, which I am sure the Microsoft button has. I've certainly clicked through those prompts before when administrating systems.
Ideally this button should be placed somewhere else on the page opposite the non-destructive tenant options or located in an entirely separate area so it can't be accidentally clicked.
Devlog #0
aRPG Prototype
Introduction
Staying home for the past several months because of COVID-19 has made my wife and I spend more time playing video games. We generally prefer same screen coop aRPGs but there is a distinct lack of those (Diablo3 and the new Minecraft game come to mind). It doesn't appear that I will need to return to the office anytime soon so I figured now would be a good time to get more familiar with Unity. I found a really good course on Udemy ( Complete C# Unity Developer 3D: Learn to Code Making Games ) taught by Ben Tristem and Rick Davidson . I am reasonably familiar with C# language and capabilities but trying to understand some of the patterns in Unity or what the typical way to create certain game constructs (like shooting, or finite state machines for behavior) can be confusing to figure out just by looking at a bunch of source code. Ben and Rick do a great job explaining game design and spend time on the difference between design and programming which I especially like.
Game Inspiration
1. Dark Spore
2. Diablo III
3. Battletech
4. Warframe
5. Path of exile
6. Destiny 2
Design Philosophy
1. FUN! Game should always be fun above all other design standards. If a choice needs to made between fun and other standards fun should be prioritized over others
2. No penalties. Players should not be penalized for choices they make, reward risk taking and experimentation! Instead of punishment, don't provide a reward or provide a lesser reward instead of a large reward for success. Most choices should not be permanent and can always be changed later without the player having to grind or jump through hoops
3. Customization. Give players lots of choices to design their character how they want including appearance options. Multiple play styles should be valid and balanced
4. Difficulty. Players should be allowed to choose how easy or hard the game is and adjust this at any time without penalty (see #2). Enemy difficulty should scale up and down based on player choice and players should be able to have different difficulties when in multi player (enemy stats scale to player stats ala Destiny)
5. Social trends. Be mindful of emerging social trends (race, gender bias, etc) that may hurt overall reception of the game. Make game unbiased and inclusive where possible (see point 3, eg player character customization)
Feature Requirements
1. Player commands 3 mechs (ala dark spore)
2. Player customization and progression
3. Random generation (enemies and levels)
General Approach to Level Generation
There are so many ways to generate levels procedurally; from very complicated math using multi layered fractals (see 2dMapGen for an example) to very simple algorithms using a few lines of code. I recall a recent Gamesutra article that described some of the level generate code used for Spelunky which didn't use perlin noise or anything fancy just a few logical checks to see when to spawn certain items (is this item next to an empty tile, if yes there is a %25 chance to spawn, etc). With that mind I did some research on what others had come up with for road and city generation and ran into a masters thesis by Craig Martek of the Rochester Institute of Technology which while producing believable road networks looked far to complicated to implement for my prototyping purposes so I decided on a simple approach that layers multiple procedurally generated grids. I looked at the capabilities of Unity's Shader graph to build this but didn't really think it was the best approach for my purposes so I decided to use Substance Designer since it is the Industry standard for material design in the game industry and has good integration with Unity.
CityConverter() Class Prototype Overview
My general thoughts for the game are that there will be several different types of procedurally generated levels (city with roads, industrial zone, wilderness, etc) so a base interface needs to be created to establish some basic requirements for all the control textures that will be used to build the levels from. Maybe don't need to think this far in advanced but since extensibility of the solution should be a primary concern choosing an interface for this upfront makes sense.
Getting the color pixel data from a substance graph is "straight forward" in the sense that it is easy but the documentation provided from Substance for it's API is really really lacking for a professional product. Normal convention is to establish some documentation so that in visual studio when using the object browser there is a general sense of what the various methods and properties do without relying entirely on "self documenting code". After the graph is wired in the Unity inspector a List <Texture2d> is generated and we search for the diffuse texture since we don't care about any other channels.
ConvertControlTexture() iterates through the texture looking at each pixel and uses some logic to determine what type of tile needs to be made based on the pixel color. For prototyping purposes we only are worrying about white and black (white = road, black = building) but this can be added to later. There is another tile type (sidewalk) that is determine by checking if a black pixel is adjacent to a white pixel. After the type of tile has been determined there is a method specific to each type of tile to handle building the tile (choosing the specific GameObject and determining the orientation that the tile should be facing). That is it for now, I am still writing the logic to determine the facing orientation (based on adjacent tiles and which directions the roads are in)
Prototype to convert texture generated from substance graph to a procedurally generated city with roads
using Substance.Game;
using System.Collections.Generic;
using UnityEngine;
using System.Collections.Generic;
using UnityEngine;
public class CityConverter : MonoBehaviour, IControlTexture
{
//converts city control texture from substance graph to tile objects for level generation factories
enum TileType { Building, Road }
enum Orientation { North, South, East, West, none }
{
//converts city control texture from substance graph to tile objects for level generation factories
enum TileType { Building, Road }
enum Orientation { North, South, East, West, none }
public SubstanceGraph AttachedGraph;
List<ITile> _Tiles;
Texture2D _ControlTexture;
List<Texture2D> _TextureList;
public List<ITile> Tiles { get { return _Tiles; } }
TileType FindTileType(Vector2 position)
{
{
}
Orientation FindOrientation(Vector2 position)
{
}
Orientation FindOrientation(Vector2 position)
{
}
ITile MakeRoadTile(Vector2 position, Orientation orientation)
{
}
public void ConvertControlTexture()
{
ITile BuiltTile;
for (int x = 0; x < _ControlTexture.width - 1; x++)
{
for (int y = 0; y < _ControlTexture.height - 1; y++)
{
Vector2 Position = new Vector2(x, y);
TileType TileType = FindTileType(Position);
Orientation Orientation = FindOrientation(Position);
if (TileType == TileType.Road)
{
BuiltTile=MakeRoadTile(Position, Orientation);
}
else if (TileType == TileType.Building)
{
BuiltTile= MakeBuildingTile(Position, Orientation);
}
_Tiles.Add(BuiltTile);
}
}
}
void Start()
{
_TextureList = new List<Texture2D>();
_TextureList = AttachedGraph.GetGeneratedTextures();
var DiffuseName = AttachedGraph.name + " - diffuse";
var ListPosition = _TextureList.FindIndex(i => i.name == DiffuseName); //find which of the returned textures is the diffuse texture
_ControlTexture = _TextureList[ListPosition];
}
public void ConvertControlTexture()
{
ITile BuiltTile;
for (int x = 0; x < _ControlTexture.width - 1; x++)
{
for (int y = 0; y < _ControlTexture.height - 1; y++)
{
Vector2 Position = new Vector2(x, y);
TileType TileType = FindTileType(Position);
Orientation Orientation = FindOrientation(Position);
if (TileType == TileType.Road)
{
BuiltTile=MakeRoadTile(Position, Orientation);
}
else if (TileType == TileType.Building)
{
BuiltTile= MakeBuildingTile(Position, Orientation);
}
_Tiles.Add(BuiltTile);
}
}
}
void Start()
{
_TextureList = new List<Texture2D>();
_TextureList = AttachedGraph.GetGeneratedTextures();
var DiffuseName = AttachedGraph.name + " - diffuse";
var ListPosition = _TextureList.FindIndex(i => i.name == DiffuseName); //find which of the returned textures is the diffuse texture
_ControlTexture = _TextureList[ListPosition];
}
// Update is called once per frame
void Update()
{
}
}
void Update()
{
}
}