I am having trouble with Unity Physics.CheckSphere. I have added a mask "Wall" and assigned it to the object.
In my custom editor window I can select this mask and set values as a grid to check for this object. However, when I click the button I don't get any collisions. But when I change the object's mask to something like "TransparentFX" I get the results I expected.
What am I missing here?
Here is the code for the custom editor window:
private void OnGUI()
{
GUILayout.Label("Generate A*", EditorStyles.boldLabel);
showGridTools = EditorGUILayout.BeginFoldoutHeaderGroup(showGridTools, "Grid Tools");
if(showGridTools)
{
this.GridDimensions = EditorGUILayout.Vector3IntField("Grid Dimensions", GridDimensions);
this.ObstacleMask = EditorGUILayout.LayerField("Obstacle Mask", this.ObstacleMask);
if (GUILayout.Button("Generate Grid"))
CreateGrid();
}
EditorGUILayout.EndFoldoutHeaderGroup();
}
And for checking the collisions:
private void CreateGrid()
{
for (int x = 0; x < this.GridDimensions.x; x++)
{
for (int z = 0; z < this.GridDimensions.z; z++)
{
for (int y = 0; y < this.GridDimensions.y; y++)
{
Vector3Int position = new Vector3Int(x, y, z);
bool obstacle = Physics.CheckSphere(position, 0.3f, this.ObstacleMask);
if(obstacle)
Debug.Log($"Position: {position}, Obstacle: {obstacle}");
}
}
}
}
----------- EDIT -----------
I just did this. Might be pretty slow, I don't know, but at least it
works for now.
private void CreateGrid()
{
this.Grid = new Node[this.GridDimensions.x, this.GridDimensions.y, this.GridDimensions.z];
for (int x = 0; x < this.GridDimensions.x; x++)
{
for (int z = 0; z < this.GridDimensions.z; z++)
{
for (int y = 0; y < this.GridDimensions.y; y++)
{
Vector3Int position = new Vector3Int(x, y, z);
var collisions = Physics.OverlapSphere(position, 0.3f);
bool obstacle = collisions.Where(c => c.gameObject.layer == this.ObstacleMask).Count() > 0;
if (obstacle)
Debug.Log($"Position: {position}, Obstacle: {obstacle}");
}
}
}
}
Related
Iam making a placement system using tilemaps, its almost done but have a small problem.
Placement System
As you can see the tower never be on middle of tiles, i want it always be on middle, even if the size is 2x2 (on that video is 3x3), i dont know what is wrong with my code, so i need help.
void Update()
{
Vector3 mousePosWorldPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3Int cellPos = sniperTower.World.MapGrid.LocalToCell(mousePosWorldPoint);
Vector3 posConverted = sniperTower.World.MapGrid.CellToLocalInterpolated(cellPos);
BoundsInt bounds = new BoundsInt();
bounds.position = cellPos;
bounds.size = sniperTower.BuldingSize;
if (sniperTower.PreviousBounds.position != null && sniperTower.PreviousBounds.position != bounds.position) {
sniperTower.World.clearTiles(sniperTower.PreviousBounds);
sniperTower.Moving(bounds,new Vector3(posConverted.x, posConverted.y, 10));
sniperTower.World.fillTiles("Placing",bounds);
}else if(sniperTower.PreviousBounds.position == null) {
sniperTower.Moving(bounds,new Vector3(posConverted.x, posConverted.y, 10));
sniperTower.World.fillTiles("Placing",bounds);
}
}
//The Moving Method
public virtual void Moving(BoundsInt bounds,Vector3 position){
this.movingBounds = bounds;
this.previousBounds = this.movingBounds;
this.buildingObject.transform.position = position;
}
//The FillTiles and ClearTiles Methods
public void fillTiles(string tileType, BoundsInt area){
for (int x = area.x; x < (area.x + area.size.x); x++) {
for (int y = area.y; y < (area.y + area.size.y); y++) {
placingTilemap.SetTile(new Vector3Int(x,y,0),tiles[tileType]);
}
}
}
public void clearTiles(BoundsInt area){
for (int x = area.x; x < (area.x + area.size.x); x++) {
for (int y = area.y; y < (area.y + area.size.y); y++) {
placingTilemap.SetTile(new Vector3Int(x,y,0),null);
}
}
}
The codes below generate 10000cubes(using gizmos). I'm stuck on how to further expand the code and using gizmos to illustrate the sorting process(movement of the cubes when mouse button is click), appreciate if can give me some hint on how to do it.
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour
{
int x;
int y;
int z;
int[,] array1 = new int[100, 100];
int temp;
void Start()
{
for (x = 0; x < 100; x++)
{
for (y = 0; y < 100; y++)
{
array1[x, y] = Random.Range(0, 2);
Debug.Log(string.Format("{0},{1},{2}", x, y, array1[x, y]));
}
}
}
void OnDrawGizmos()
{
for (x = 0; x < 100; x++)
{
for (y = 0; y < 100; y++)
{
Vector2 pos1 = new Vector2(0 + x, 0 + y);
Gizmos.DrawCube(pos1, transform.position);
Gizmos.color = (array1[x, y] == 1) ? Color.black : Color.white;
}
}
}
void OnMouseDown()
{
for (z = 0; z < 100; z++)
{
for (x = 0; x < 100; x++)
{
for (y = 0; y < 100-z; y++)
{
if (array1[x, y] > array1[x, y + 1])
temp = array1[x, y];
array1[x + 1, y] = array1[x, y];
array1[x, y] = temp;
}
}
}
}
}
1.Possible Solution
I provide an answer in this part, and suggest better alternatives below.
The problem with your code is that the sorting happens synchronously, within one frame. What you can do is move the sorting code to Update, and sort a little between each frame. This will let the Gizmos call draw the correct state.
2.Use Progress Bar instead
You may use EditorUtility.DisplayProgressBar to display a progress bar in the Editor. Since you're using Gizmos which don't show up in the Build, this solution assumes you're working in the Editor.
3.Use Compute Buffers
CatLikeCoding has this fantastic article on using Compute Shaders to draw a Graph in high resolution. You can follow it and draw your sorting progress in a very high resolution. Furthermore, this will work in both editor and runtime.
I have been following the Unity3D Procedural Cave Generation, but I found an error very early on in MapGeneration.cs. Unity says that on line 1 word 1, there is an error: Identifier expected: 'public' is a keyword. I cannot see any difference from my code and the tutorial's code. Here is the link to the tutorial video: [\Tutorial video 1] and here is my code:
using UnityEngine;
using System.Collections;
using System
public class MapGeneration : MonoBehaviour {
public int width;
public int height;
public string seed;
public bool useRandomSeed;
[Range(0,100)]
public int randomFillPercent;
int[,] map;
void Start() {
GenerateMap();
}
void GenerateMap() {
map = new int[width,height];
}
void RandomFillMap() {
if (useRandomSeed) {
seed = Time.time.ToString();
}
System.Random psuedoRandom = new System.Random(seed.GetHashCode());
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y ++) {
map[x,y] = (psuedoRandom.Next(0,100) < randomFillPercent)? 1: 0;
}
}
}
void OnDrawGizmos() {
if (map != null) {
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y ++) {
Gizmos.color = (map[x,y] == 1)? Color.black: Color.white;
Vector3 position = new Vector3(-width/2 + x + .5f,0,-height/2 + y + .5f);
Gizmos.DrawCube(position,Vector3.one);
}
}
}
}
}
The error is public on line one.
You don't have the ; after using System (that, maybe, is also an incomplete import).
I want to generate something like this:
I use Perlin Noise with sharp curve, my code produces those cliffs:
.
for (int x = 0; x < sizeX; x++)
{
for (int z = 0; z < sizeZ; z++)
{
int floorY = map.GetMaxYNotWater(x, z);
float n = hillsNoise.Noise(x, z);
int hillY = (int)(curveHills.Evaluate(n) * 80f);
if (hillY > floorY + 5)
{
for (int y = hillY; y > floorY; y--)
{
map.SetBlock(GetBlock(y), new Vector3i(x, y, z));
}
}
}
}
How can I "cut" them to make hanging things?
I tried to do it like this with additional curve:
for (int x = 0; x < sizeX; x++)
{
for (int z = 0; z < sizeZ; z++)
{
int floorY = map.GetMaxYNotWater(x, z);
float n = hillsNoise.Noise(x, z);
int hillY = (int)(curveHills.Evaluate(n) * 80f);
if (hillY > floorY + 5)
{
int c = 0;
int max = hillY - floorY;
max = (int)(max * curveHillsFull.Evaluate(n)) + 1;
for (int y = hillY; y > floorY && c < max; y--, c++)
{
map.SetBlock(GetBlock(y), new Vector3i(x, y, z));
}
}
}
}
But it produces flying islands.
So what can I do to achieve the first screenshot results?
I can't say how Minecraft does it, but from my own experience with voxel terrain, the best way to approach it is to think of the voxel grid as something like a cloud: each voxel has a density, and when that density is high enough, it becomes a 'visible' part of the cloud and you fill the voxel.
So rather than calculating the min and max Y levels, work on calculating the density value, something like this:
for (int x = 0; x < sizeX; x++)
{
for (int y = 0; y > sizeY; y--)
{
for (int z = 0; z < sizeZ; z++)
{
//This means less density at higher elevations, great for turning
//a uniform cloud into a terrain. Multiply this for flatter worlds
float flatWorldDensity = y;
//This calculates 3d Noise: you will probably have to tweak this
//heavily. Multiplying input co-ordinates will allow you to scale
//terrain features, while multiplying the noise itself will make the
//features stronger and more or less apparent
float xNoise = hillsNoise.Noise(x, y);
float yNoise = hillsNoise.Noise(x, z);
float zNoise = hillsNoise.Noise(y, z);
float 3dNoiseDensity = (xNoise + yNoise + zNoise) / 3;
//And this adds them together. Change the constant "1" to get more or
//less land material. Simple!
float ActualDensity = flatWorldDensity + 3dNoiseDensity;
if (ActualDensity > 1)
{
map.SetBlock(GetBlock(y), new Vector3i(x, y, z));
}
}
}
}
I cannot for the life of me work out how to get the block that is supposed to be drawn with every loop through the array of "Arxl" objects to animate across the grid.
Any suggestions would be really appreciated, not looking for someone to complete the code for me. just a fresh set of eyes.
public partial class Game : Form
{
//attributes
private Bitmap _grid;
private Arxl[,] _cartesianGrid;
private int _arxlAmount;
const int ARXL = 4;
public Game()
{
InitializeComponent();
_arxlAmount = (gridPictureBox.Height / ARXL);//in case height/arxl is not an even number?
_cartesianGrid = new Arxl[_arxlAmount, _arxlAmount];
_grid = new Bitmap(gridPictureBox.Width, gridPictureBox.Height);
int x;
int y;
for (x = 0; x < _arxlAmount; x++)
{
for (y = 0; y < _arxlAmount; y++)
{
_cartesianGrid[x, y] = new Arxl();
}
}
SetSeed(_cartesianGrid);
}
private void SetSeed(Arxl[,] cartesianGrid)
{
_cartesianGrid[1, 1].Active = true;
}
private void DrawArxl(Bitmap _grid, Arxl[,] cartesianGrid,int arxlAmount)
{
int x, y;
x=0;
y=0;
Graphics graphics = Graphics.FromImage(_grid);
graphics.Clear(Color.White);
for (x = 1; x < arxlAmount;x++ )
{
for (y = 1; y < arxlAmount; y++)
{
if (cartesianGrid[x, y].Active==true)
{
cartesianGrid[x, y].Area = new Rectangle(x * ARXL, y * ARXL, ARXL, ARXL);
graphics.FillRectangle(Brushes.Black, cartesianGrid[x, y].Area);
}
else if(cartesianGrid[x,y].Active==false)
{
Pen newPen=new Pen(Color.Black);
cartesianGrid[x, y].Area = new Rectangle(x * ARXL, y * ARXL, ARXL, ARXL);
graphics.DrawRectangle(newPen,cartesianGrid[x, y].Area);
newPen.Dispose();
}
}
}
}
private void timer_Tick(object sender, EventArgs e)
{
//GameOfLife(_cartesianGrid, _arxlAmount);
ScrollBlock(_cartesianGrid, _arxlAmount);
DrawArxl(_grid, _cartesianGrid, _arxlAmount);
gridPictureBox.Image = _grid;
}
private void ScrollBlock(Arxl[,] cartesianGrid, int arxlAmount)
{
int x = 0;
int y = 0;
for (x = 0; x < arxlAmount; x++)
{
for (y = 0; y < arxlAmount; y++)
{
if (_cartesianGrid[x, y].Active == true)
{
if (x>=0)
{
if (x == (arxlAmount-1))
{
_cartesianGrid[x, y].Active = false;
_cartesianGrid[1, y].Active = true;
}
else if(x<(arxlAmount-1))
{
_cartesianGrid[x, y].Active = false;
_cartesianGrid[x+1, y].Active = true;
}
}
}
}
}
}
According to a comment in your code, you want to program the life game. It will not work, if you change the cells in place, because you will have to compute the new state from the unchanged old state. Therefore, you will need to have two game boards, one with the current state and one with the new state. Instead of creating new board all the time, it is better to have two boards and to swap them. In addition, there is no point in storing the Rectangles in the board. Therefore, I declare the boards as Boolean matrix.
const int CellSize = 4;
private int _boardSize;
private bool[,] _activeBoard, _inactiveBoard;
Bitmap _grid;
The form constructor is changed like this
public Game()
{
InitializeComponent();
_boardSize = Math.Min(gridPictureBox.Width, gridPictureBox.Height) / CellSize;
_grid = new Bitmap(gridPictureBox.Width, gridPictureBox.Height);
_activeBoard = new bool[_boardSize, _boardSize];
_inactiveBoard = new bool[_boardSize, _boardSize];
SetSeed();
}
We initialize the game like this (as an example)
private void SetSeed()
{
_activeBoard[0, 0] = true;
_activeBoard[7, 4] = true;
DrawGrid();
}
The timer tick does this
ScrollBlock();
DrawGrid();
The logic in ScrollBlock is completely new. We look at the state on the _activeBoard and set the state of _inactiveBoard. Then we swap the two boards.
private void ScrollBlock()
{
for (int x = 0; x < _boardSize; x++) {
for (int y = 0; y < _boardSize; y++) {
if (_activeBoard[x, y]) {
_activeBoard[x, y] = false;
int newX = x + 1;
int newY = y;
if (newX == _boardSize) {
newX = 0;
newY = (newY + 1) % _boardSize;
}
_inactiveBoard[newX, newY] = true;
}
}
}
SwapBoards();
}
The boards are simply swapped like this
private void SwapBoards()
{
bool[,] tmp = _activeBoard;
_activeBoard = _inactiveBoard;
_inactiveBoard = tmp;
}
And finally DrawGrid draws the _activeBoard
private void DrawGrid()
{
Graphics graphics = Graphics.FromImage(_grid);
graphics.Clear(Color.White);
for (int x = 0; x < _boardSize; x++) {
for (int y = 0; y < _boardSize; y++) {
var rect = new Rectangle(x * CellSize, y * CellSize, CellSize, CellSize);
if (_activeBoard[x, y]) {
graphics.FillRectangle(Brushes.Black, rect);
} else {
graphics.DrawRectangle(Pens.Black, rect);
}
}
}
gridPictureBox.Image = _grid;
}
I've spotted your problem.
The problem is that you're updating a cell position (moving it to the right in this particular initial state), but the next iteration in the for loop finds the updated state from the previous iteration, so it updates the cell again, and again, and when the cycle stops, the cell was scrolled over to its initial cell position!, with no repainting in between.
I'm modifying your code to add an UpdateList that will turn on cells that need to be ON after the grid scan has finished to avoid updating the same "active dot" more than once. This should show a moving dot from left to right.
private void ScrollBlock(Arxl[,] cartesianGrid, int arxlAmount) {
int x = 0;
int y = 0;
List<Point> updateList = new List<Point>();
for( x = 0; x < arxlAmount; x++ ) {
for( y = 0; y < arxlAmount; y++ ) {
if( _cartesianGrid[x, y].Active == true ) {
if( x >= 0 ) {
if( x == (arxlAmount - 1) ) {
_cartesianGrid[x, y].Active = false;
//_cartesianGrid[1, y].Active = true;
updateList.Add(new Point(1, y));
} else if( x < (arxlAmount - 1) ) {
_cartesianGrid[x, y].Active = false;
//_cartesianGrid[x + 1, y].Active = true;
updateList.Add(new Point(x + 1, y));
}
}
}
}
}
foreach( var pt in updateList ) {
_cartesianGrid[pt.X, pt.Y].Active = true;
}
}
In your timer try calling gridPictureBox.Invalidate() after you assign the image to the picturebox. This will force the picturebox to redraw itself.