Nonogramz
A puzzle-based role playing game.
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
public class ImageToPuzzle : MonoBehaviour
{
//Source image to create a nonogram of
public GameObject image;
//Width in pixels of the source image, feed only square images for this to work
private int image_size;
//Sprite component of image
private Sprite mySprite;
//2D matrix of size: image_size. Stores colour of each pixel in respective elements
private Color[,] table;
//Size of the puzzle to be created, should be specified in editor
public int puzzle_size;
//2D matrix of puzzle_size, holds colour of each tile of the nonogram
private Color[,] puzzle;
//Size of each block of the puzzle, shows the number of pixels to combine into one block per row/column
private int block_size;
//Prefab of square used to display generated nonogram on screen for testing purposes
public GameObject tile;
// Start is called before the first frame update
void Start()
{
}
//Samples the image pixel by pixel and stores the colour in a separate element in the table matrix
void GenerateImageTable()
{
mySprite = image.GetComponent<SpriteRenderer>().sprite;
image_size = (int)mySprite.rect.width;
table = new Color[image_size, image_size];
for (int i = 0; i < image_size; i++)
{
for (int j = 0; j < image_size; j++)
{
table[i, j] = mySprite.texture.GetPixel(j, image_size - i - 1);
}
}
}
//Cuts down the table matrix into desired puzzle size matrix
void GeneratePuzzleTable()
{
block_size = image_size / puzzle_size;
puzzle = new Color[puzzle_size, puzzle_size];
GameObject tileDisplay = new GameObject("TileDisplay");
for (int i = 0; i < puzzle_size; i++)
{
for (int j = 0; j < puzzle_size; j++)
{
puzzle[i, j] = Avg(i * block_size, j * block_size);
GameObject x = Instantiate(tile, new Vector3(1 + j * (float)0.30, 1 - i * (float)0.30, 0), Quaternion.identity);
x.transform.SetParent(tileDisplay.transform);
x.GetComponent<Renderer>().material.color = puzzle[i, j];
}
}
}
//Creates the puzzle's text file from collected information
void GeneratePuzzleText()
{
int count = 1;
string filepath = Application.dataPath + "/Resources/Nonograms/Generated/" + puzzle_size.ToString() + "x" + puzzle_size.ToString() + image.name;
if (!File.Exists(filepath + ".txt"))
{
using (StreamWriter sw = File.CreateText(filepath + ".txt"))
{
sw.WriteLine(puzzle_size.ToString());
sw.WriteLine(getColNumberText());
sw.WriteLine(puzzle_size.ToString());
sw.WriteLine(getRowNumberText());
sw.WriteLine(MapColors());
sw.WriteLine(image.name);
}
}
else
{
while (File.Exists(filepath + count.ToString() + ".txt"))
{
count++;
}
using (StreamWriter sw = File.CreateText(filepath + count.ToString() + ".txt"))
{
sw.WriteLine(puzzle_size.ToString());
sw.WriteLine(getColNumberText());
sw.WriteLine(puzzle_size.ToString());
sw.WriteLine(getRowNumberText());
sw.WriteLine(MapColors());
sw.WriteLine(image.name);
}
}
}
//Generates Column Labels
string getColNumberText()
{
string newString = "";
for (int i = 0; i < puzzle_size; i++)
{
int count = 0;
int commaCount = 0;
if (i > 0)
{
newString += "|";
}
for (int j = 0; j < puzzle_size; j++)
{
if (puzzle[j, i].a == 1)
{
count++;
}
else if (count > 0)
{
if (commaCount > 0)
{
newString += "," + count;
}
else
{
newString += count;
commaCount++;
}
count = 0;
}
}
if (count > 0)
{
if (commaCount > 0)
{
newString += "," + count;
}
else
{
newString += count;
commaCount++;
}
count = 0;
}
else
{
if (commaCount == 0)
{
newString += "0";
}
}
}
return newString;
}
//Generates row labels
string getRowNumberText()
{
string newString = "";
for (int i = 0; i < puzzle_size; i++)
{
int count = 0;
int commaCount = 0;
if (i > 0)
{
newString += "|";
}
for (int j = 0; j < puzzle_size; j++)
{
if (puzzle[i, j].a == 1)
{
count++;
}
else if (count > 0)
{
if (commaCount > 0)
{
newString += "," + count;
}
else
{
newString += count;
commaCount++;
}
count = 0;
}
}
if (count > 0)
{
if (commaCount > 0)
{
newString += "," + count;
}
else
{
newString += count;
commaCount++;
}
count = 0;
}
else
{
if (commaCount == 0)
{
newString += "0";
}
}
}
//Debug.Log(newString);
return newString;
}
//Creates a cut-down version of the Puzzle matrix according to the txt format
string MapColors()
{
List<string> colors = new List<string>();
int[,] newPuzzle = new int[puzzle_size, puzzle_size];
string matrix_ = "";
for (int i = 0; i < puzzle_size; i++)
{
for (int j = 0; j < puzzle_size; j++)
{
if (puzzle[i, j].a > 0)
{
if (!colors.Contains(ColorUtility.ToHtmlStringRGB(puzzle[i, j])))
{
colors.Add(ColorUtility.ToHtmlStringRGB(puzzle[i, j]));
}
newPuzzle[i, j] = colors.IndexOf(ColorUtility.ToHtmlStringRGB(puzzle[i, j])) + 1;
}
else
{
newPuzzle[i, j] = 0;
}
matrix_ += newPuzzle[i, j].ToString() + " ";
}
matrix_ += "\n";
}
string cols = colors.Count.ToString();
foreach (string clr in colors)
{
cols += " " + clr;
}
matrix_ = cols + "\n" + matrix_;
return matrix_;
}
//Returns the average colour value from a block_size x block_size group in the image table
Color Avg(int row, int col)
{
Color sum = new Color();
for (int i = row; i < row + block_size; i++)
{
for (int j = col; j < col + block_size; j++)
{
sum += table[i, j];
}
}
return sum / (block_size * block_size);
}
// Update is called once per frame
void Update()
{
GenerateImageTable();
GeneratePuzzleTable();
GeneratePuzzleText();
Destroy(this); //The generation has to be done only once, so destroy this script after one loop
}
}
Nonogramz is an RPG where a player progresses through the story by solving nonogram puzzles. It is being developed on Unity3D for mobile and PC platforms. I primarily worked on the automation of the creator's engine, where the gamemakers can upload any image and the game converts it into a puzzle, which can later be used during actual gameplay. The code sample above shows how I've dealt with reading colors, pixels and more.
Nonogramz is the first Unity3D game that I worked on that was designed to be played (all others were for research/simulation purposes). I worked with a team of around 15 people, and closely followed the Agile methodolody through this project. I experienced how different teams interact, how the workflow progresses between the design team and the development team. I was leading new team members within days of joining the project thanks to my active contribution and ability to communicate well, and I am still one of two admins overseeing the development of this game.