Tiled Map Editor and draw order in Unity 3D - c#

I have a similar problem to:
stackoverflow
But in my case I'm using Unity3D + TiledMapEditor + Tiled2Unity.
I'm loading my map to Unity3D by Tiled2Unity program and as a player parameter Order in Layer I can change easily by:
Renderer renderer = GetComponent<Renderer>();
renderer.sortingOrder = -(int)(transform.position.y * 100);
Object "map" can only change the parameter Order In Layer for the individual layers.
For example: floor = 0, wall = 1, collision = 2. I have no idea how to get to a single "tile" the map and change its Order In Layer because of where it is located. To map was drawn from top to bottom (The lower the Order in Layer increased).
The script hooked the object "map":
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace Tiled2Unity
{
public class TiledMap : MonoBehaviour
{
public int NumTilesWide = 0;
public int NumTilesHigh = 0;
public int TileWidth = 0;
public int TileHeight = 0;
public float ExportScale = 1.0f;
// Note: Because maps can be isometric and staggered we simply can't multply tile width (or height) by number of tiles wide (or high) to get width (or height)
// We rely on the exporter to calculate the width and height of the map
public int MapWidthInPixels = 0;
public int MapHeightInPixels = 0;
public float GetMapWidthInPixelsScaled()
{
return this.MapWidthInPixels * this.transform.lossyScale.x * this.ExportScale;
}
public float GetMapHeightInPixelsScaled()
{
return this.MapHeightInPixels * this.transform.lossyScale.y * this.ExportScale;
}
private void OnDrawGizmosSelected()
{
Vector2 pos_w = this.gameObject.transform.position;
Vector2 topLeft = Vector2.zero + pos_w;
Vector2 topRight = new Vector2(GetMapWidthInPixelsScaled(), 0) + pos_w;
Vector2 bottomRight = new Vector2(GetMapWidthInPixelsScaled(), -GetMapHeightInPixelsScaled()) + pos_w;
Vector2 bottomLeft = new Vector2(0, -GetMapHeightInPixelsScaled()) + pos_w;
Gizmos.color = Color.red;
Gizmos.DrawLine(topLeft, topRight);
Gizmos.DrawLine(topRight, bottomRight);
Gizmos.DrawLine(bottomRight, bottomLeft);
Gizmos.DrawLine(bottomLeft, topLeft);
}
}
}
to better understand (because my level of English is poor):
mesh.png
map.png

Related

Using Perlin Noise across multiple Unity Terrain objects

I have a class project in which we are supposed to use Unities Terrain 3D objects and create a 3x3 smoothly generated terrain. For this we have been told to create a central Terrain the has adjacent terrains in the 8 cardinal directions. I have gotten the Perlin Noise to work through this method
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TerrainNoiseGeneration : MonoBehaviour
{
private TerrainData myTerrainData;
public Vector3 worldSize;
public int resolution = 129;
private float userInput = (float)4.2;
public float offsetX;
public float offsetZ;
// Start is called before the first frame update
void Start()
{
myTerrainData = gameObject.GetComponent<TerrainCollider>().terrainData;
worldSize = new Vector3(100, 50, 100);
myTerrainData.size = worldSize;
myTerrainData.heightmapResolution = resolution;
float[,] heightArray = new float[resolution, resolution];
heightArray = PerlinNoise(userInput, offsetX, offsetZ);
myTerrainData.SetHeights(0, 0, heightArray);
}
// Update is called once per frame
void Update()
{
float[,] heightArray = new float[resolution, resolution];
heightArray = PerlinNoise(userInput, offsetX, offsetZ);
myTerrainData.SetHeights(0, 0, heightArray);
}
float[,] PerlinNoise(float userInput, float offsetX, float offsetZ)
{
float[,] heights = new float[resolution, resolution];
for (int z = 0; z < resolution; z++)
{
for (int x = 0; x < resolution; x++)
{
float nx = (x + offsetX) / resolution * userInput;
float ny = (z + offsetZ) / resolution * userInput;
heights[z, x] = Mathf.PerlinNoise(nx, ny);
}
}
return heights;
}
This code allows me to Generate a smooth terrain in the first Terrain object but when I try entering the offset values so that the edges can line-up they do not have the same values.
I would appreciate any assistance on this issue as I have tried a lot of different solutions, none of which are working
Update: I was able to solve the problem with a rather simple solution of the fact that I needed to use my resolution as the offset not the distance between the terrains
I needed to set the OffsetX and OffsetZ equal to that of their respective resolution positions instead of their unity positions.
For example my terrains are 100x100 so I was setting offset to 100 or -100 depending on its location but instead I needed to use 128 or -128 to keep it in line with the resolution

Apply texture in a quad mesh from a texture atlas

I'm trying to apply dynamically a texture from a texture atlas to a quad mesh in Unity3D.
When I do the same in a mesh of a cube, the front face works very fine but the other ones get distorted. So I had the idea to use a simple quad and now I'm facing this scenario:
The image should be displayed like this:
I'm placing the texture by the code below. The math is working fine:
public int offsetX = 0;
public int offsetY = 0;
private const float offset = 0.0625f; // the rate of each texture square
void Start ()
{
Mesh mesh = GetComponent<MeshFilter>().mesh;
Vector2[] UVs = new Vector2[mesh.vertices.Length];
UVs[0] = new Vector2(offsetX * offset, offsetY * offset);
UVs[1] = new Vector2((offsetX * offset) + offset, offsetY * offset);
UVs[2] = new Vector2(offsetX * offset, (offsetY * offset) + offset);
UVs[3] = new Vector2((offsetX * offset) + offset, (offsetY * offset) + offset);
mesh.uv = UVs;
}
What should I do to place the texture in the quad mesh as the image reference?
For those looking for an answer:
I've fixed that changing the tail (offset) and scale of the shader. Example:
using UnityEngine;
public class Cube : MonoBehaviour {
public int offsetX = 0;
public int offsetY = 0;
private Renderer _rend;
private Material _material;
private const float Offset = 0.0625f;
// Use this for initialization
private void Start ()
{
_rend = GetComponent<Renderer>();
_material = _rend.material;
_material.mainTextureScale = new Vector2(Offset,Offset);
}
private void Update()
{
_material.mainTextureOffset = new Vector2(offsetX*Offset,offsetY*Offset);
}
}

Eye detection using OpenCVSharp in Unity (fps issues)

I'm currently working on a project involving integrating OpenCVSharp into Unity, to allow eye tracking within a game environment. I've managed to get OpenCVSharp integrated into the Unity editor and currently have eye-detection (not tracking) working within a game. It can find your eyes within a webcam image, then display where its currently detected them on a texture, which I display within the scene.
However its causing a HUGE fps drop, mainly because every frame its converting a webcam texture into an IPLimage so that OpenCV can handle it. It then has to convert it back to a 2Dtexture to be displayed within the scene, after its done all the eye detection. So understandably its too much for the CPU to handle. (As far as I can tell its only using 1 core on my CPU).
Is there a way to do all the eye detection without converting the texture to an IPLimage? Or any other way to fix the fps drop. Some things that I've tried include:
Limiting the frames that it updates on. However this just causes it
to run smoothly, then stutter horribly on the frame that it has to
update.
Looking at threading, but as far as I'm aware Unity doesn't allow it.
As far as I can tell its only using 1 core on my CPU which seems a bit silly. If there was a way to change this it could fix the issue?
Tried different resolutions on the camera, however the resolution that the game can actually run smoothly at, is too small for the eye's to actually be detected, let alone tracked.
I've included the code below, of if you would prefer to look at it in a code editor here is a link to the C# File. Any suggestions or help would be greatly appreciated!
For reference I used code from here (eye detection using opencvsharp).
using UnityEngine;
using System.Collections;
using System;
using System.IO;
using OpenCvSharp;
//using System.Xml;
//using OpenCvSharp.Extensions;
//using System.Windows.Media;
//using System.Windows.Media.Imaging;
public class CaptureScript : MonoBehaviour
{
public GameObject planeObj;
public WebCamTexture webcamTexture; //Texture retrieved from the webcam
public Texture2D texImage; //Texture to apply to plane
public string deviceName;
private int devId = 1;
private int imWidth = 640; //camera width
private int imHeight = 360; //camera height
private string errorMsg = "No errors found!";
static IplImage matrix; //Ipl image of the converted webcam texture
CvColor[] colors = new CvColor[]
{
new CvColor(0,0,255),
new CvColor(0,128,255),
new CvColor(0,255,255),
new CvColor(0,255,0),
new CvColor(255,128,0),
new CvColor(255,255,0),
new CvColor(255,0,0),
new CvColor(255,0,255),
};
const double Scale = 1.25;
const double ScaleFactor = 2.5;
const int MinNeighbors = 2;
// Use this for initialization
void Start ()
{
//Webcam initialisation
WebCamDevice[] devices = WebCamTexture.devices;
Debug.Log ("num:" + devices.Length);
for (int i=0; i<devices.Length; i++) {
print (devices [i].name);
if (devices [i].name.CompareTo (deviceName) == 1) {
devId = i;
}
}
if (devId >= 0) {
planeObj = GameObject.Find ("Plane");
texImage = new Texture2D (imWidth, imHeight, TextureFormat.RGB24, false);
webcamTexture = new WebCamTexture (devices [devId].name, imWidth, imHeight, 30);
webcamTexture.Play ();
matrix = new IplImage (imWidth, imHeight, BitDepth.U8, 3);
}
}
void Update ()
{
if (devId >= 0)
{
//Convert webcam texture to iplimage
Texture2DtoIplImage();
/*DO IMAGE MANIPULATION HERE*/
//do eye detection on iplimage
EyeDetection();
/*END IMAGE MANIPULATION*/
if (webcamTexture.didUpdateThisFrame)
{
//convert iplimage to texture
IplImageToTexture2D();
}
}
else
{
Debug.Log ("Can't find camera!");
}
}
void EyeDetection()
{
using(IplImage smallImg = new IplImage(new CvSize(Cv.Round (imWidth/Scale), Cv.Round(imHeight/Scale)),BitDepth.U8, 1))
{
using(IplImage gray = new IplImage(matrix.Size, BitDepth.U8, 1))
{
Cv.CvtColor (matrix, gray, ColorConversion.BgrToGray);
Cv.Resize(gray, smallImg, Interpolation.Linear);
Cv.EqualizeHist(smallImg, smallImg);
}
using(CvHaarClassifierCascade cascade = CvHaarClassifierCascade.FromFile (#"C:\Users\User\Documents\opencv\sources\data\haarcascades\haarcascade_eye.xml"))
using(CvMemStorage storage = new CvMemStorage())
{
storage.Clear ();
CvSeq<CvAvgComp> eyes = Cv.HaarDetectObjects(smallImg, cascade, storage, ScaleFactor, MinNeighbors, 0, new CvSize(30, 30));
for(int i = 0; i < eyes.Total; i++)
{
CvRect r = eyes[i].Value.Rect;
CvPoint center = new CvPoint{ X = Cv.Round ((r.X + r.Width * 0.5) * Scale), Y = Cv.Round((r.Y + r.Height * 0.5) * Scale) };
int radius = Cv.Round((r.Width + r.Height) * 0.25 * Scale);
matrix.Circle (center, radius, colors[i % 8], 3, LineType.AntiAlias, 0);
}
}
}
}
void OnGUI ()
{
GUI.Label (new Rect (200, 200, 100, 90), errorMsg);
}
void IplImageToTexture2D ()
{
int jBackwards = imHeight;
for (int i = 0; i < imHeight; i++) {
for (int j = 0; j < imWidth; j++) {
float b = (float)matrix [i, j].Val0;
float g = (float)matrix [i, j].Val1;
float r = (float)matrix [i, j].Val2;
Color color = new Color (r / 255.0f, g / 255.0f, b / 255.0f);
jBackwards = imHeight - i - 1; // notice it is jBackward and i
texImage.SetPixel (j, jBackwards, color);
}
}
texImage.Apply ();
planeObj.renderer.material.mainTexture = texImage;
}
void Texture2DtoIplImage ()
{
int jBackwards = imHeight;
for (int v=0; v<imHeight; ++v) {
for (int u=0; u<imWidth; ++u) {
CvScalar col = new CvScalar ();
col.Val0 = (double)webcamTexture.GetPixel (u, v).b * 255;
col.Val1 = (double)webcamTexture.GetPixel (u, v).g * 255;
col.Val2 = (double)webcamTexture.GetPixel (u, v).r * 255;
jBackwards = imHeight - v - 1;
matrix.Set2D (jBackwards, u, col);
//matrix [jBackwards, u] = col;
}
}
}
}
You can move these out of the per frame update loop :
using(CvHaarClassifierCascade cascade = CvHaarClassifierCascade.FromFile (#"C:\Users\User\Documents\opencv\sources\data\haarcascades\haarcascade_eye.xml"))
using(CvMemStorage storage = new CvMemStorage())
No reason to be building the recognizer graph each frame.
Threading is the logical way to go moving forward if you want real speed updates, unity itself is not threaded, but you can fold in other threads if your careful.
Do the texture -> ipl image on the main thread then trigger an event to fire off your thread.
The thread can do all the CV work, probably construct the tex2d and then push back to main to render.
You should also be able to gain some performance improvements if you use:
Color32[] pixels;
pixels = new Color32[webcamTexture.width * webcamTexture.height];
webcamTexture.GetPixels32(pixels);
The Unity doco suggests that this can be quite a bit faster than calling "GetPixels" (and certainly faster than calling GetPixel for each pixel), and then you don't need to scale each RGB channel against 255 manually.

XNA Isometric tile collision

Im a C#/XNA student and I've recently been working on an isometric tile engine and so far it works fairly well. But im having problem trying to figure out on how to do collision, this is what my tile engine does at the moment:
Draws the world from an image and place a tile depending on what color is on my image. For instance color red would draw a grass tile. (Tiles are 64x32)
Camera following player, and my draw loop only draws what the camera sees.
This is how my game looks if that would be of any help:
I don't know what sort of collision would work best. Should i do collision points, or intersects or any other sort of collision. I've read somewhere that you could do Worldtoscreen/Screentoworld but im far to inexperienced and don't know how that works nor how the code would look like.
Here is my code drawing tiles etc:
class MapRow
{
public List<MapCell> Columns = new List<MapCell>();
}
class TileMap
{
public List<MapRow> Rows = new List<MapRow>();
public static Texture2D image;
Texture2D tileset;
TileInfo[,] tileMap;
Color[] pixelColor;
public TileMap(string TextureImage, string Tileset)
{
tileset = Game1.Instance.Content.Load<Texture2D>(Tileset);
image = Game1.Instance.Content.Load<Texture2D>(TextureImage);
pixelColor = new Color[image.Width * image.Height]; // pixelColor array that is holding all pixel in the image
image.GetData<Color>(pixelColor); // Save all the pixels in image to the array pixelColor
tileMap = new TileInfo[image.Height, image.Width];
int counter = 0;
for (int y = 0; y < image.Height; y++)
{
MapRow thisRow = new MapRow();
for (int x = 0; x < image.Width; x++)
{
tileMap[y, x] = new TileInfo();
if (pixelColor[counter] == new Color(0, 166, 81))
{
tileMap[y, x].cellValue = 1;//grass
}
if (pixelColor[counter] == new Color(0, 74, 128))
{
tileMap[y, x].cellValue = 2;//water
}
if (pixelColor[counter] == new Color(255, 255, 0))
{
tileMap[y, x].cellValue = 3;//Sand
}
tileMap[y, x].LoadInfoFromCellValue();//determine what tile it should draw depending on cellvalue
thisRow.Columns.Add(new MapCell(tileMap[y, x]));
counter++;
}
Rows.Add(thisRow);
}
}
public static int printx;
public static int printy;
public static int squaresAcross = Settings.screen.X / Tile.TileWidth;
public static int squaresDown = Settings.screen.Y / Tile.TileHeight;
int baseOffsetX = -32;
int baseOffsetY = -64;
public void draw(SpriteBatch spriteBatch)
{
printx = (int)Camera.Location.X / Tile.TileWidth;
printy = (int)Camera.Location.Y / Tile.TileHeight;
squaresAcross = (int)Camera.Location.X / Tile.TileWidth + Settings.screen.X / Tile.TileWidth;
squaresDown = 2*(int)Camera.Location.Y / Tile.TileHeight + Settings.screen.Y / Tile.TileHeight + 7;
for (printy = (int)Camera.Location.Y / Tile.TileHeight; printy < squaresDown; printy++)
{
int rowOffset = 0;
if ((printy) % 2 == 1)
rowOffset = Tile.OddRowXOffset;
for (printx = (int)Camera.Location.X / Tile.TileWidth; printx < squaresAcross; printx++)
{
if (tileMap[printy, printx].Collides(MouseCursor.mousePosition))
Console.WriteLine(tileMap[printy, printx].tileRect);
foreach (TileInfo tileID in Rows[printy].Columns[printx].BaseTiles)
{
spriteBatch.Draw(
tileset,
tileMap[printy, printx].tileRect = new Rectangle(
(printx * Tile.TileStepX) + rowOffset + baseOffsetX,
(printy * Tile.TileStepY) + baseOffsetY,
Tile.TileWidth, Tile.TileHeight),
Tile.GetSourceRectangle(tileID.cellValue),
Color.White,
0.0f,
Vector2.Zero,
SpriteEffects.None,
tileID.drawDepth);
}
}
}
}
}
Why don't you just draw stuff just like in normal tile based games, and then rotate the camera with a 45degree? Of course then you'd need to make your graphics a bit odd, but would be easier to handle the tiles.
But if you prefer your way, then I'd suggest using simple math to calculate the "tile to the right", "tile to the left" , "tile to the up" and "tile to the down" ones, you know, the tiles around the player(or another tile). You can simply work with your lists, and with some math, basic math, like getting the next tile, is quite simple.
Edit:
You could get the player's next position's tile value with a code something like this:
tileMap[Math.Floor((player.y+playerVelociy.Y)/tileHeight)]
[Math.Floor((player.x+playerVelocity.X)/tileWidth)]
In this code, I assume that the first tile is at 0,0 and you're drawing to right and down. (If not, then just change the Math.Floor to Math.Ceil)
THIS link could help you get the idea, however it's in AS3.0, only the syntax is different.

2D Tiling Collision detection wont detect

Situation: Within my game, I have tiles that collide with the player which is detected however when I attempt to cause an event to occur when they collide, nothing will happen.
Problem: I currently have a Collision class within each tile class. Within the tile class I have a method which passes in the player rectangle (x, y, width and height) using parameters. I then call the tiles Collision class check collision method. After some testing, I discovered that the collision IS being recognised however within my tileMap class (holds a 2D array of the Tile class) when I check through each tile (using foreach) and call the update method within each tile class, only the first tile collides. Nothing else.
Here is my code:
Tile:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Content;
namespace Project
{
class Tile
{
Texture2D tileTexture;
Rectangle tileRect;
public Collision collision;
private Vector2 pos;
public int tileNumber;
private SpriteFont tileT;
bool box;
public enum tileCollision
{
passable,
nonpassable,
trigger,
}
public tileCollision tileValue;
public Tile(string tileType,int number, Rectangle newTileRect, ContentManager Content)
{
tileTexture = Content.Load<Texture2D>(tileType);
tileT = Content.Load<SpriteFont>("TimesNewRoman");
tileRect = newTileRect;
tileNumber = number;
pos.X = tileRect.X;
pos.Y = tileRect.Y;
//check if tile is within draw block
if (tileNumber >= 9 && tileNumber <= 13)
{
tileValue = tileCollision.passable;
}
if (tileNumber >= 0 && tileNumber <= 8 )
{
tileValue = tileCollision.nonpassable;
System.Console.WriteLine("NON PASSABLE TILE: " + " x: " + tileRect.X + " y: " + tileRect.Y);
}
collision = new Collision(tileRect.X, tileRect.Y, tileRect.Width, tileRect.Height);
}
public void update(Rectangle rect)
{
//System.Console.WriteLine(playerRect.X, playerRect.Y);
if (collision.boundingBoxCollisionCheck(rect.X, rect.Y, rect.Width, rect.Height))
{
//if (tileValue == tileCollision.nonpassable)
//{
// System.Console.WriteLine("COLLISION!!!!!");
//}
box = true;
}
if (box == true)
{
System.Console.WriteLine("COLLISION!!!!! at x: " + tileRect.X + "y: " + tileRect.Y );
}
}
public void draw(SpriteBatch spritebatch)
{
//if tile is within draw block then draw
spritebatch.Draw(tileTexture, tileRect, Color.White);
spritebatch.DrawString(tileT, pos.X.ToString(), pos, Color.White);
}
}
}
tileMap:
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Content;
using System.IO;
namespace Project
{
class TileMap
{
//public Tile[,] tile = new Tile[10,10];
public List<Tile> tiles = new List<Tile>();
int width;
int height;
string[] tileType = { "Tiles/BrickGrassTile1", "Tiles/BrickGrassTile2", "Tiles/BrickGrassTile3", "Tiles/BrickGrassTile4",
"Tiles/BrickGrassTile5", "Tiles/BrickRoofTile1", "Tiles/BrickRoofTile2", "Tiles/BrickRoofTile3",
"Tiles/BrickTile1", "Tiles/GrassPuddleTile1", "Tiles/GrassRockTile1", "Tiles/WallTile1", "Tiles/GrassTile1", "Tiles/GrassTile2", "Tiles/BrickDoor1" };
public void generateMap(int [,]map, int size, ContentManager content)
{
for (int x = 0; x < map.GetLength(1); x++)
{
for (int y = 0; y < map.GetLength(0); y++)
{
int number = map[y, x];
if (number >= 0)
{
tiles.Add(new Tile(tileType[number],number, new Rectangle(x * size, y * size, size, size), content));
}
width = (x + 1) * size;
height = (y + 1) * size;
}
}
}
public void update(Rectangle playerRect)
{
foreach (Tile tile in tiles)
{
tile.update(playerRect);
}
}
public void draw(SpriteBatch spritebatch)
{
foreach (Tile tile in tiles)
{
tile.draw(spritebatch);
}
}
}
}
... each tile (using foreach) and call the update method within each tile class, only the first tile collides. Nothing else.
I suspect your player rectangle is smaller than one of your tiles, which is why you are only getting 1 collision.
EDIT
The way your logic works now, once you collide with an item, it permanently says you are colliding with the item. This is incorrect. Use this:
public void update(Rectangle rect)
{
//System.Console.WriteLine(playerRect.X, playerRect.Y);
if (collision.boundingBoxCollisionCheck(rect.X, rect.Y, rect.Width, rect.Height))
{
System.Console.WriteLine("COLLISION!!!!! at x: " + tileRect.X + "y: " + tileRect.Y );
}
}

Categories