Oct
22
2012

Qix Prototype

Qix test : Qix (wikipedia), See gameplay video on c64 (youtube)

Using the same c# floodfill from here (comments section).
Diagonal movement not allowed (using part of this script from unifywiki)

Still missing collisions, enemies etc.

Just 1 bigger problem to think about:
– How do I get the position to apply floodfill to?
– Which side to fill (or fill both and check which side the enemy is.. still need to get some pixel position to “drop” the flood fill to..)

Webplayer:
http://unitycoder.com/upload/demos/Qix_prototype_unity/ (v1.0 : doesnt fill correctly)

Source: (javascript)


#pragma strict

public var target:Transform;
private var tex:Texture2D;
private var texsize:int=64;

private var f:FloodFiller = new FloodFiller();

private var MAP_SIZE:int = texsize; // only powers of two are supported
private var map:byte[] = new byte[MAP_SIZE * MAP_SIZE];

private var speed : float = 20.0;

private var pos:Vector3;
private var oldpos:Vector3;
private var gridpos:Vector3;

private var drawing:boolean = false;
private var startPos:Vector3; // we started drawing from here

function Start ()
{
tex = new Texture2D (texsize, texsize);
target.renderer.material.mainTexture = tex;
target.renderer.material.mainTexture.filterMode = FilterMode.Point;

for (var y:int=0;y<texsize;y++)
for (var x:int=0;x<texsize;x++)
{
map[texsize * x + y] = 0;
tex.SetPixel(x,y, new Color(0,0,0.2,1));

if (x==0 || y== 0 || x==texsize-1 || y==texsize-1)
{
map[texsize * x + y] = 33; // border
tex.SetPixel(x,y, Color.gray);
}
}

tex.Apply();

pos = transform.position;
gridpos = pos;
oldpos = -pos;

}

function Update ()
{
// get movements
var moveX : float = Input.GetAxisRaw ("Horizontal") * speed;
var moveY : float = Input.GetAxisRaw ("Vertical") * speed;

// limit diagonal
if (Mathf.Abs(moveX) > Mathf.Abs(moveY))    moveY = 0.0;    else    moveX = 0.0;

// move
transform.Translate(Vector3(moveX,0,moveY) * Time.deltaTime, Space.World);

// get gridpos
gridpos= new Vector3( Mathf.RoundToInt(transform.position.x) , 0 , Mathf.RoundToInt(transform.position.z));

// limit on walls
transform.position.x = Mathf.Clamp(transform.position.x,0,texsize-1);
transform.position.z = Mathf.Clamp(transform.position.z,0,texsize-1);

// we have moved?
if (gridpos!=oldpos)
{

// we are in empty spot
if (map[texsize * gridpos.x + gridpos.z]==0)
{
var hit : RaycastHit;
var fwd = transform.TransformDirection (-Vector3.up);
if (Physics.Raycast (transform.position+Vector3(0,0.5,0), fwd, hit))
{
var pixelUV:Vector2 = hit.textureCoord;
pixelUV.x *= texsize;
pixelUV.y *= texsize;
//print (pixelUV);

// TODO: separate maps?
map[texsize * Mathf.RoundToInt(pixelUV.x) + Mathf.RoundToInt(pixelUV.y)] = 10;

tex.SetPixel(Mathf.RoundToInt(pixelUV.x), Mathf.RoundToInt(pixelUV.y), Color.green);
tex.Apply();

if (!drawing) startPos = gridpos;
drawing = true;


}
}else{ // we are in pre-filled area

// TODO: if neighbour cells are already painted, we should floodfill (because we entered 1x1 hole?)

// we had been drawing before coming here
if (drawing)
{
drawing = false;

var paintpos:Vector3 = oldpos;

Debug.DrawLine (startPos, gridpos, Color.red,10);

if (startPos.z<gridpos.z) paintpos.z -= 1;

//if (gridpos.x) paintpos.y = gridpos.y+1;
//if (startPos.y<gridPos.y) paintpos.y = gridPos.y+1;


print ("paintpos:"+paintpos+" gridpos:"+gridpos+" oldpos:"+oldpos);
f.FloodFill(map, paintpos.x, paintpos.z,10, 99);
checkAreas();
}
}

oldpos = gridpos;

}


/*
if (Input.GetMouseButtonUp (0))
{
//        print ("fill");
f.FloodFill(map, 10, 10, 10, 99);
checkAreas();
}
*/


}



function checkAreas()
{
var found:boolean=false;
var countArea:int=0;

for (var y:int=0;y<texsize;y++)
for (var x:int=0;x<texsize;x++)
{
var val = map[texsize * x + y];

// its still empty, so its inside building (or its wall?)
if (val==0)
{
map[texsize * x + y] = 0;
//tex.SetPixel(x,y,new Color(0,1,0,1));

found=true;
countArea++;
}

//it was filled, clean it up
if (val==99)
{
map[texsize * x + y] = 0;
tex.SetPixel(x,y,new Color(0,0,1,1));
}
}

print ("countArea:"+countArea);
tex.Apply();

}

FloodFiller.cs (put in Plugins/ folder)


using System;
using System.Diagnostics;
using System.Linq;

// orig: http://pastebin.com/KHD8axSL

//namespace FloodFill
//{
//internal class Program
public class FloodFiller
{
//        private const int MAP_SIZE = 1024; // only powers of two are supported

/*
private static void Main()
{
var map = new byte[MAP_SIZE * MAP_SIZE];

var stopwatch = Stopwatch.StartNew();
{
FloodFill(map, startX: 123, startY: 123, fromValue: 0, toValue: 1);
}
stopwatch.Stop();

Console.WriteLine(stopwatch.ElapsedMilliseconds);

// Test results
if (map.Any(item => item == 0))
{
Console.WriteLine("Error");
}
else
{
Console.WriteLine("Ok");
}

Console.ReadLine();
}
*/

public void FloodFill(byte[] map, int startX, int startY, byte fromValue, byte toValue)
{
int shift = (int) Math.Round(Math.Log(map.Length, 4)); // if the array's length is (2^x * 2^x), then shift = x
int startIndex = startX + (startY << shift);

if (map[startIndex] >= fromValue)
{
return;
}



// initialize flood fill
int size = 1 << shift;
int sizeMinusOne = size - 1;
int xMask = size - 1;
int minIndexForVerticalCheck = size;
int maxIndexForVerticalCheck = map.Length - size - 1;

// initialize queue
int capacity = size * 2;
int mask = capacity - 1;
uint tail = 0;
uint head = 0;
var queue = new int[capacity];

map[startIndex] = toValue;
queue[tail++ & mask] = startIndex;

while (tail - head > 0)
{
int index = queue[head++ & mask];
int x = index & xMask;

//if (x > 0 && map[index - 1] == fromValue)
if (x > 0 && map[index - 1] < fromValue)
{
map[index - 1] = toValue;
queue[tail++ & mask] = index - 1;
}
//if (x < sizeMinusOne && map[index + 1] == fromValue)
if (x < sizeMinusOne && map[index + 1] < fromValue)
{
map[index + 1] = toValue;
queue[tail++ & mask] = index + 1;
}
//if (index >= minIndexForVerticalCheck && map[index - size] == fromValue)
if (index >= minIndexForVerticalCheck && map[index - size] < fromValue)
{
map[index - size] = toValue;
queue[tail++ & mask] = index - size;
}
//if (index <= maxIndexForVerticalCheck && map[index + size] == fromValue)
if (index <= maxIndexForVerticalCheck && map[index + size] < fromValue)
{
map[index + size] = toValue;
queue[tail++ & mask] = index + size;
}
}




}
}
//}

&nbsp;


5 Comments + Add Comment

  • Hi! This looks great!

    I’m trying to make a game qix-like too. I have been working about a script I found and it works ok in corona, but I’m new in unity and I’m having problems adapting to c# and all the other unity stuff. Maybe I can help you if you share youre code. My code is based on this:

    http://luis.peralta.pt/sandbox/js/flood.html

    Thanks!

    John

    • Flood fill stuff from here:
      http://unitycoder.com/blog/2012/10/10/flood-fill-algorithm/
      (check comments, also that Bitmap Drawing API (free) might be easy to use)

      Or which part you are having problems with?
      * added those old webplayer demo scripts, would need to remake them..

      • Thank you for the info and code!

        Basically I have problems with the basics of unity I guess. How many GameObjects I need to make this code run? I understand I need one that use mesh with renderer no? What is the target on your code?

        Thank you again!

        • script is attached to player gameobject (cube in the webplayer demo, no collider on it),
          target is the plane (used CreatePlane from unity to make “Plane1x1W64L64HBL”)

  • Ok, I runned! It works well but fills the largest area, it should be the tinniest. I’m doing my investigations on the code. It’s a bit complicated because usually I use a 2 dimensional array and you are using a byte array. The bitwise operations are harder to understand, but I supose It’s a decision made for improving performance…

    Thanks!

Leave a comment

Connect

Twitter View LinkedIn profile Youtube Github Join Discord Twitch Instagram BlueSky

UnityLauncherPro

Get UnityLauncherPRO and work faster with Unity Projects!
*free unity hub alternative

@unitycoder_com

Subscribe to Blog via Email

Enter your email address to subscribe to this blog and receive notifications of new posts by email.