I'm working on a prototype of match puzzle game. I'm using Unity. Main mechanic is very simple: grid 7x8 filled with tiles of 5 kinds. When user tap on any tile, it looks for similar adjacent tiles and if there is at least 3 of a kind, then they are removed.
Tiles are regular GameObjects with a SpriteRenderer component and my script which defines what sprite to load. Also it handles click but all other logic runs on the Grid.
So, I've already done: grid initialization, detecting same tiles and removing them. I'm doing it like that:
void RemoveShape() {
foreach (DashItem item in items) {
if (item.shouldBeDestroyed) {
item.GetComponent<SpriteRenderer> ().sprite = null;
item.shouldBeDestroyed = false;
item.alreadyChecked = false;
}
}
StopCoroutine (FindNullTiles ());
StartCoroutine (FindNullTiles ());
}
As you can see I don't destroy actual GameObject, just setting it sprite to null.
public IEnumerator FindNullTiles() {
for (int x = 0; x < gridWidth; x++) {
for (int y = 0; y < gridHeight; y++) {
if (items[x, y].GetComponent<SpriteRenderer>().sprite == null) {
yield return StartCoroutine(ShiftTilesDown(x, y));
break;
}
}
}
}
And now I don't know how to implement ShiftTilesDown().
This method should run through all tiles, check if they sprites == null and shift down to fill the gaps. Then I need to fill top empty sprites.
Thanks
Related
I created a Quad. I assigned this script to the Quad that contains an array of Game Objects:
public class ShapeGrid : MonoBehaviour {
public GameObject[] shapes;
void Start(){
GameObject[,] shapeGrid = new GameObject[3,3];
StartCoroutine(UpdateGrid());
}
IEnumerator UpdateGrid(){
while (true) {
SetGrid ();
yield return new WaitForSeconds(2);
}
}
void SetGrid(){
int col = 3, row = 3;
for (int y = 0; y < row; y++) {
for (int x = 0; x < col; x++) {
int shapeId = (int)Random.Range (0, 4.9999f);
GameObject shape = Instantiate (shapes[shapeId]);
Vector3 pos = shapes [shapeId].transform.position;
pos.x = (float)x*3;
pos.y = (float)y*3;
shapes [shapeId].transform.position = pos;
}
}
}
}
I cloned those Game Objects so that they appear on a grid like this:
When the user clicks an object, it should disappear. What I did was place this script on every element in my array of Game Objects:
public class ShapeBehavior : MonoBehaviour {
void Update(){
if(Input.GetMouseButtonDown(0)){
Destroy(this.gameObject);
}
}
}
But what happens is when I click on an object to destroy it, every clone of that object will be destroyed. I want only the specific clone to be destroyed, not everything. How do I do that?
The problem is on your Input call, "Input.GetMouseButtonDown(0)" is true in every script when you click the button on mouse, no matter the position of mouse. Attach any kind of collider to gameObject and set it up and put the script with OnMouseDown() method, look here for more info : http://docs.unity3d.com/ScriptReference/MonoBehaviour.OnMouseDown.html
You can also use raycasting but that's more advanced way of solving this.
Also replace this.gameObject with just gameObject.
I am developing a game server and currently I need to be able to get spectators within an area, however I fear the one I am using is really ugly and "slow" but I haven't experienced any performance hits yet as I am testing it locally not on a live server.
This is my GetSpectators Function:
public void GetSpectators(ref HashSet<Player> Players, Coordinate_t coordinate, bool MultiFloor = false)
{
for (int x = coordinate.X - 11; x != coordinate.X + 11; x++)
{
for (int y = coordinate.Y - 11; y != coordinate.Y + 11; y++)
{
if (MultiFloor)
{
for (int z = coordinate.Z - 2; z != coordinate.Z + 2; z++)
{
Tile tile = GetTile(x, y, z);
if (tile != null)
{
foreach (Player p in tile.Creatures)
{
Players.Add(p);
}
}
}
}
else
{
Tile tile = GetTile(x, y, coordinate.Z);
if (tile != null)
{
foreach (Player p in tile.Creatures)
{
Players.Add(p);
}
}
}
}
}
}
I have this class Map which holds this other dictionary with class Tile, each tile is represented with X, Y, and Z coordinates, each tile holds a list of this class called Player, some tiles have players some don't.
I need a good way and not ugly to get e.g:
All players within x=100, y=100, z=7 in radius 11 for example.
I think it would be smart to reference tiles in your Player class if you're not already doing so, and then pass the all the players to the GetSpectators() method...
Something like...
public class Player
{
// a reference to the tile that the player is currently on.
public Tile CurrentTile { get; set; }
}
This would allow you to loop through the players instead of so many tiles. And it should be cleaner and more efficient to find players the way you want without all the loop nesting. For ex:
public List<Player> GetSpectators(Hashset<Player> playersInGame, Coordinate coord)
{
var playersInRange = new List<Player>();
// iterate through each player.
foreach (var p in playersInGame)
{
// check if the tile the player is sitting on is in range of radius given.
if ((p.CurrentTile.X < coord.X + 6 || p.CurrentTile.X > coord.X - 6)
&&
(p.CurrentTile.Y < coord.Y + 6 || p.CurrentTile.Y > coord.Y - 6))
{
// Player is within radius.
playersInRange.Add(p);
}
}
return playersInRange;
}
You can add in the additional check for Z coordinate, and any other conditional statements. But as you can see, this would allow you to use one loop rather than 3 nested loops. You may or may not find it useful. But I hope it helps.
You can use circle equation to check whether player lies in the given radius of other player i.e
if
x1=100,y1=100,z=7 and r=11
then
(x-x1)^2+(y-y1)^2+(z-z1)^2=r^2.
Any point which satisfy this equation will lie in the region.
I've spent days trying different techniques to get two X by Z arrays to talk to one-another and have decided to redo it all by making a 2 by X by Z array instead. 0,x,z contains a grid of cubes (prefab Cell) which will store values to be used later for NPC navigation, while 1,x,z contains a grid of tall cuboids (prefab ObstBlock) which are used to detect walls in the scene. If there's a wall, ObstBlock's int Obstructed will become 1. It is this int that I'm trying to reference in the main script.
All Cells and ObstBlocks are generated by an empty object (named "Grid") running GridScript. All Cells run CellScript and all ObstBlocks run ObstBlockScript. Here are my attempts in GridScript:
void DetectObstructions(){
for (int x=0; x<GridSize.x; x++) {
for (int z=0; z<GridSize.z; z++) {
Transform cell;
Transform block;
cell = Grid [0, x, z];
block = Grid [1, x, z];
if (block.GetComponent<ObstBlockScript> ().Obstructed == 1) {
cell.GetComponent<CellScript>().Weight = 1000;
}
else{
cell.GetComponent<CellScript>().Weight = 500;
}
cell.GetComponent<CellScript>().Obstructed = block.GetComponent<ObstBlockScript>().Obstructed;
}
}
}
I get no errors and this appears when I try to use the Debug tool: This request is not supported by the protocol version implemented by the debuggee.
As requested, the grid creation part of the script:
void CreateGrid(){
Grid = new Transform[2,(int)GridSize.x,(int)GridSize.z];
for (int x=0; x<GridSize.x; x++) {
for (int z=0; z<GridSize.z; z++) {
Transform newBlock;
newBlock = (Transform)Instantiate (ObstBlockPrefab, new Vector3 (x/4f, 1.0f, z/4f), Quaternion.identity);
//newBlock.name = string.Format("({0},1,{1})",x,z);
newBlock.parent = transform;
newBlock.GetComponent<ObstBlockScript>().Position = new Vector3(x/4f,1.0f,z/4f);
Transform newCell;
Grid[1,x,z] = newBlock;
newCell = (Transform)Instantiate (CellPrefab, new Vector3 (x/4f, -0.125f, z/4f), Quaternion.identity);
//newCell.name = string.Format("({0},0,{1})",x,z);
newCell.parent = transform;
newCell.GetComponent<CellScript>().Position = new Vector3(x/4f,-0.125f,z/4f);
//if(GameObject.Find("ObstGrid").GetComponent<ObstructionScript> ().ObstGrid[x,z]./*GameObject.*/Find ("ObstBlock").GetComponent<ObstBlockScript>().obstructed == 1){
// newCell.GetComponent<CellScript>().Weight = 83;
//}
Grid[0,x,z] = newCell;
}
}
}
Your code looks pretty straight forward to me. So unless something funky is happening inside CellScript or ObstBlockScript that resets the values, I'd have to guess DetectObstructions() get's executed too early, before any of the ObstBlockScript.Obstructed are set to "1".
This should be easyish to debug using break points or at least by adding Debug.Log to DetectObstructions() to see if any of the ObstBlockScript.Obstructed are 1 at that time the method is called.
I am attempting to create a 2d scrolling XNA game as a learning exercise, but have run into some issues with the scrolling background. I am loading a level from a text file, parsing through to create the appropriate tiles and store them in a matrix (tiles[,]).
I then have an update method which alters the position of the tile so when it is redrawn it will move.
Currently, I loop through all tiles to draw them all before moving. This is clearly not very efficient. Ideally, I only want to draw the tiles on the screen. I can do this by taking the viewport and using the height/width of a tile to determine how many tiles will fit on the screen and only loop through those tiles, as follows:
private void DrawTiles(SpriteBatch spriteBatch)
{
float tileWidth = 40;
float tileHeight = 32;
for (int y = 0; y < (int)Math.Ceiling(mViewport.Height / tileHeight); ++y)
{
for (int x = 0; x < (int)Math.Ceiling(mViewport.Width / tileWidth); ++x)
{
tiles[x, y].Draw(spriteBatch);
}
}
}
However, this only draws the iles in the original viewport. Tiles outside will never be drawn even though their position does come into view.
I think this can be resolved by using a counter to start and end the loop, incrementing it each time the draw method is called. However, I do not think this is a great solution, but alas I cannot think of a better way to ensure only tiles in the viewport are drawn.
You need to keep track of the starting X and Y of the ViewPort, as you're always starting at 0 in your example. e.g.
var startX = 10; // Should increment as viewport scrolls
var startY = 10; // Should increment as viewport scrolls
...
for (int y = startY; y < (int)Math.Ceiling(mViewport.Height / tileHeight); ++y)
{
for (int x = startX; x < (int)Math.Ceiling(mViewport.Width / tileWidth); ++x)
{
tiles[x, y].Draw(spriteBatch);
}
}
On a side note, your ViewPort probably has a Top and Left or X and Y to keep track of this as well. In that case, replace startX and startY with the appropriate property from your ViewPort.
What I implemented in my design was an inherited interface that contained the properties IsInView and a collision property or in your case a position could be substituted. I then created a seperate thread that would loop through and determine if the object is in view. you can then in each object have the InView and OutView add and remove it from a draw list.
Run from seperate thread with a loop. -- This could be adapted to determine if the tile is visible
public void CalculateObjsInView()
{
foreach (Obj o in new List<Obj>(ObjInstanceList))
{
if (o == null)
continue;
if (Camera.CollisionMask.Intersects(o.CollisionMask))
o.IsInView = true;
else
o.IsInView = false;
}
}
In Obj class
private bool _isInView = false;
public bool IsInView
{
get { return _isInView; }
set
{
if (_isInView != value)
{
if (value)
InView();
else
OutView();
}
_isInView = value;
}
}
private void InView()
{
Game.ObjectDrawEventHandler.Add(Draw);
}
private void OutView()
{
Game.ObjectDrawEventHandler.Remove(Draw);
}
Continuing with my game here (a game where the player is a ship and you shoot down meteors). I am currently working on the ship shooting bullets and I am trying to remove the meteors as they get hit by the bullets. So here's what I've done. I am almost there, but I have found an error in my coding. In my game, there spawns meteors outside the right side of the map and they travel to the left and the point is to shoot them down, this is working if you shoot the meteors in right order but not otherwise. Let me explain it with a picture.
If I were to shoot down the second meteor, the meteor marked with number 1 would get destroyed
first
//spawns the enemies.
public void LoadEnemies()
{
int randY = random.Next(100, 500);
if (spawn > 1)
{
spawn = 0;
if (enemies.Count() < 4)
enemies.Add(new Enemies(Content.Load<Texture2D>("meteor"), new Vector2(1110, randY)));
}
//Here's where the error lies because of the bulletcolliding (I think)
for (int i = 0; i < enemies.Count; i++)
{
if (!enemies[i].isVisible || bulletColliding)
{
bulletColliding = false;
enemies.RemoveAt(i);
i--;
}
}
}
Collision method.
public void bulletCollision(GameTime gameTime)
{
foreach (var x in bullets)
{
foreach (var y in enemies)
{
enemy_rect = new Rectangle((int)y.position.X, (int)y.position.Y, 10, 10);
bullet_rect = new Rectangle((int)x.position.X, (int)x.position.Y, 10, 10);
if (bullet_rect.Intersects(enemy_rect))
{
bulletColliding = true;
}
}
}
}
Basically, I am clueless on how to remove the specific meteor that gets hit, and I need your help. I Appreaciate all help I get.
DonĀ“t use only bool bulletColliding. Use also integer which shows the number of enemy which should be destroyed and in each enemy class keep information of his number.
The second option can be keeping a bool ifDestroy in enemy class which will be false initially, and change it to true if the enemy should be destroyed. You can check it in loadEnemies().
Basically this is a further explanation of Sabrina Le's answer.
You want the enemy objects themselves to have a bool called bulletColliding and in your collision code it would look more like...
public void bulletCollision(GameTime gameTime)
{
foreach (var x in bullets)
{
foreach (var y in enemies)
{
enemy_rect = new Rectangle((int)y.position.X, (int)y.position.Y, 10, 10);
bullet_rect = new Rectangle((int)x.position.X, (int)x.position.Y, 10, 10);
if (bullet_rect.Intersects(enemy_rect))
{
y.bulletColliding = true;
}
}
}
}
and then your enemy loading code would look more like...
//spawns the enemies.
public void LoadEnemies()
{
int randY = random.Next(100, 500);
if (spawn > 1)
{
spawn = 0;
if (enemies.Count() < 4)
enemies.Add(new Enemies(Content.Load<Texture2D>("meteor"), new Vector2(1110, randY)));
}
//Here's where the error lies because of the bulletcolliding (I think)
for (int i = 0; i < enemies.Count; i++)
{
if (!enemies[i].isVisible || enemies[i].bulletColliding)
{
enemies[i].bulletColliding = false;
enemies.RemoveAt(i);
i--;
}
}
}
By strictly having a single bulletColliding variable, what is happening is when any meteor is hit, then it is set to true. So when your method checks the meteors sequentially it sees that bulletColliding is set to true even when it checks the first object, and the code inside the if runs. Using enemy specific booleans will let you know which enemy got hit, and will be removed accordingly.