Related
I am currently programming a game in which an infinite procedural city is generated. so far everything works but because it leads to laggs if there are too many objects in the scene I wanted to make a script in which objects only appear near the player. I watched this video for help:https://www.youtube.com/watch?v=xlSkYjiE-Ck. When I tried to link this to my script (GenerateBuilding script) this error came:ArgumentException:
An item with the same key has already been added. Key: (0.0, 1.0)
System.Collections.Generic.Dictionary...
I need help to make the script work in which the houses are generated as well as the planes do, they should only be showed when the player is nearby
---Relevant Lines---
(Endless City)
calling updateChunk function in update()(updateChunk/building function is in GenerateBuilding script)
public void UpdateBuildings()
{
for (int i = 0; i < buildingObjects.Count; i++)
{
buildingObjects[i].SetVisible(false);
}
buildingObjects.Clear();
}
adding to dictionary line 68-80(UpdateVisibleChunks function)
if (building.cityChunkDictionary.ContainsKey(viewedChunkCoord))
{
building.cityChunkDictionary[viewedChunkCoord].UpdateCityChunk(viewerPosition, viewedChunkCoord, chunkSize, maxViewDst);
if (building.cityChunkDictionary[viewedChunkCoord].IsVisible())
{
building.buildingObjects.Add(building.cityChunkDictionary[viewedChunkCoord]);
}
}
else
{
building.AddTest(viewedChunkCoord, chunkSize);
}
EndlessCity, CityChunk class
CityChunk function, sending position to GenerateBuilding script to instantiate buildings in right position.
building.requestBuildingSquad(positionV3);
GenerateBuilding relevant lines
builderH function, instantiates the buildings
public float builderH(GameObject[] obj, float Height, Vector3 position)
{
Transform objTrans = obj[Random.Range(0, obj.Length)].transform;
//Instantiate house Object
GameObject objekt = Instantiate(objTrans.gameObject, position + new Vector3(xOfsset * spaceBetween, Height, zOfsset * spaceBetween), transform.rotation);
float height = Test.transform.localScale.y;
objectsss.Add(objekt);
return height;
}
AddTest function, adds instantiates objects from builderH to a dictionary
public void AddTest(Vector2 viewedChunkCoord, float chunkSize)
{
for (int i = 0; i < objectsss.Count; i++)
{
cityChunkDictionary.Add(viewedChunkCoord, new Testing(objectsss[i]));
}
}
Testing class, testing function, adds objects to class
public Testing(GameObject obj)
{
MeshObject = obj;
}
that should be all relevant lines
full scripts(really similar)
EndlessCity Script(this scripts generates the planes and gives position for GenerateBuilding script)
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
public class EndlessCity : MonoBehaviour
{
public const float maxViewDst = 10;
public Transform viewer;
private GenerateBuilding building;
public static Vector2 viewerPosition;
int chunkSize;
int chunksVisibleInViewDst;
Dictionary<Vector2, CityChunk> terrainChunkDictionary = new Dictionary<Vector2, CityChunk>();
List<CityChunk> terrainChunksVisibleLastUpdate = new List<CityChunk>();
void Start()
{
chunkSize = 8 - 1;
chunksVisibleInViewDst = Mathf.RoundToInt(maxViewDst / chunkSize);
}
void Update()
{
viewerPosition = new Vector2(viewer.position.x, viewer.position.z);
UpdateVisibleChunks();
}
void UpdateVisibleChunks()
{
building = FindObjectOfType<GenerateBuilding>();
building.UpdateBuildings();
for (int i = 0; i < terrainChunksVisibleLastUpdate.Count; i++)
{
terrainChunksVisibleLastUpdate[i].SetVisible(false);
}
terrainChunksVisibleLastUpdate.Clear();
int currentChunkCoordX = Mathf.RoundToInt(viewerPosition.x / chunkSize);
int currentChunkCoordY = Mathf.RoundToInt(viewerPosition.y / chunkSize);
for (int yOffset = -chunksVisibleInViewDst; yOffset <= chunksVisibleInViewDst; yOffset++)
{
for (int xOffset = -chunksVisibleInViewDst; xOffset <= chunksVisibleInViewDst; xOffset++)
{
Vector2 viewedChunkCoord = new Vector2(currentChunkCoordX + xOffset, currentChunkCoordY + yOffset);
if (terrainChunkDictionary.ContainsKey(viewedChunkCoord))
{
terrainChunkDictionary[viewedChunkCoord].UpdateTerrainChunk();
if (terrainChunkDictionary[viewedChunkCoord].IsVisible())
{
terrainChunksVisibleLastUpdate.Add(terrainChunkDictionary[viewedChunkCoord]);
}
}
else
{
terrainChunkDictionary.Add(viewedChunkCoord, new CityChunk(viewedChunkCoord, chunkSize, transform));
}
if (building.cityChunkDictionary.ContainsKey(viewedChunkCoord))
{
building.cityChunkDictionary[viewedChunkCoord].UpdateCityChunk(viewerPosition, viewedChunkCoord, chunkSize, maxViewDst);
if (building.cityChunkDictionary[viewedChunkCoord].IsVisible())
{
building.buildingObjects.Add(building.cityChunkDictionary[viewedChunkCoord]);
}
}
else
{
building.AddTest(viewedChunkCoord, chunkSize);
}
}
}
}
public class CityChunk
{
private GenerateBuilding building;
public GameObject meshObject;
public Vector3 positionV3;
Vector2 position;
Bounds bounds;
public CityChunk(Vector2 coord, int size, Transform parent)
{
building = FindObjectOfType<GenerateBuilding>();
position = coord * size;
bounds = new Bounds(position, Vector2.one * size);
positionV3 = new Vector3(position.x, 0, position.y);
int xPosition = building.xLength / 2;
int zPosition = building.zLength / 2;
float xOfsset = building.xOfsset;
float zOfsset = building.zOfsset;
float spaceBetween = building.spaceBetween;
//Instantiate plane
meshObject = Instantiate(building.groundObject, positionV3 + new Vector3((xPosition + xOfsset) * spaceBetween, -.5f, (xPosition + 1 + zOfsset) * spaceBetween), Quaternion.identity);
SetVisible(false);
building.requestBuildingSquad(positionV3);
}
public void UpdateTerrainChunk()
{
float viewerDstFromNearestEdge = Mathf.Sqrt(bounds.SqrDistance(viewerPosition));
bool visible = viewerDstFromNearestEdge <= maxViewDst;
SetVisible(visible);
}
public void SetVisible(bool visible)
{
meshObject.SetActive(visible);
}
public bool IsVisible()
{
return meshObject.activeSelf;
}
}
}
GenerateBuilding(this script generates Buildings on the planes)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenerateBuilding : MonoBehaviour
{
public int minHeight = 2;
public int maxHeight = 8;
public int cubeTileX;
public int cubeTileZ;
public int xLength;
public int zLength;
public float spaceBetween;
public float xOfsset;
public float zOfsset;
public GameObject TesObject;
public GameObject[] Base;
public GameObject[] secondB;
public GameObject[] roof;
public GameObject groundObject;
public List<GameObject> objectsss;
public Dictionary<Vector2, Testing> cityChunkDictionary = new Dictionary<Vector2, Testing>();
public List<Testing> buildingObjects = new List<Testing>();
public GameObject Test;
void Start()
{
//requestBuildingSquad(this.transform.position);
}
void Update()
{
if (Input.GetKeyDown(KeyCode.I))
{
//
}
}
public void requestBuildingSquad(Vector3 position)
{
//*getting the middle of the city squad
int xPosition = xLength / 2;
int zPosition = zLength / 2;
//*
for (int z = 0; z < zLength; z++)
{
zOfsset++;
for (int x = 0; x < xLength; x++)
{
GenerateBuildings(position);
}
xOfsset = 0;
}
zOfsset = 0;
}
public void GenerateBuildings(Vector3 position)
{
int bHeight = Random.Range(minHeight, maxHeight);
float bOfsset = 0;
bOfsset += builderH(Base, bOfsset, position);
for (int i = 0; i < bHeight; i++)
{
bOfsset += builderH(secondB, bOfsset, position);
}
bOfsset += builderH(roof, bOfsset, position);
xOfsset++;
}
public float builderH(GameObject[] obj, float Height, Vector3 position)
{
Transform objTrans = obj[Random.Range(0, obj.Length)].transform;
//Instantiate house Object
GameObject objekt = Instantiate(objTrans.gameObject, position + new Vector3(xOfsset * spaceBetween, Height, zOfsset * spaceBetween), transform.rotation);
float height = Test.transform.localScale.y;
objectsss.Add(objekt);
return height;
}
public void AddTest(Vector2 viewedChunkCoord, float chunkSize)
{
for (int i = 0; i < objectsss.Count; i++)
{
cityChunkDictionary.Add(viewedChunkCoord, new Testing(objectsss[i]));
}
}
public void UpdateBuildings()
{
for (int i = 0; i < buildingObjects.Count; i++)
{
buildingObjects[i].SetVisible(false);
}
buildingObjects.Clear();
}
public class Testing
{
public GameObject MeshObject;
Vector2 position;
Bounds bounds;
public Testing(GameObject obj)
{
MeshObject = obj;
}
public void SetVisible(bool visiblee)
{
MeshObject.SetActive(visiblee);
}
public bool IsVisible()
{
return MeshObject.activeSelf;
}
public void UpdateCityChunk(Vector3 viewerPosition, Vector2 coord, int size, float maxViewDst)
{
position = coord * size;
bounds = new Bounds(position, Vector2.one * size);
float viewerDstFromNearestEdge = Mathf.Sqrt(bounds.SqrDistance(viewerPosition));
bool visible = viewerDstFromNearestEdge <= maxViewDst;
SetVisible(visible);
}
}
}
The problem is that you are trying to add twice elements with the same key.
here is the documentation of the Add method for dictionaries, and as it states, trying to add an existing key throws an error.
You can either use the TryAdd method, which adds an item only if the key doesn't exist already in the dictionary, or update the value with the existing key as you can see here.
I wrote this little Unity3D class to fade an array of UI.Image in or out by reducing or increasing the alpha value of their color property.
Then I wanted to use it for an array of materials on meshrenderers but realized that materials and UI.Image, though they both have a color property which can be changed, are totally different classes and you access their color properties differently so I cannot use this class for them both.
I tried making a list of just the colors to be changed, but in unity a Color is a struct and cannot be passed by reference.
Do you have any idea how to make this class generic so it can change the color property on several different classes? I sort of wrote myself into a corner here, I'd rather not just copy the class and change it for meshrenderer.material, that would be ugly
public class FadeThingInOut : MonoBehaviour
{
//public Setting fields
public Image[] ThingsToFade;
public bool Visible = true;
public float FadeTime = 1f;
//private fields
Color[] InColour;
Color[] OutColor;
void Start()
{
if (Visible)
{
SetColours(ref InColour, ref OutColor,0f);
}
else
{
SetColours(ref OutColor,ref InColour,1f);
}
}
void SetColours(ref Color[] one,ref Color[] two,float other)
{
one = new Color[ThingsToFade.Length];
two = new Color[ThingsToFade.Length];
for (int i = 0; i < ThingsToFade.Length; i++)
{
one[i] = ThingsToFade[i].color;
two[i] = Tools.colorfromalpha(one[i], other);
}
}
public void Fade(bool inout)
{
if (inout && !Visible)
{
StartCoroutine(FadeLerp(OutColor,InColour,inout));
}
else if (!inout && Visible)
{
StartCoroutine(FadeLerp(InColour, OutColor, inout));
}
}
IEnumerator FadeLerp(Color[] from, Color[] to,bool endstate)
{
float nowtime = 0f;
while (nowtime < FadeTime)
{
nowtime += Time.deltaTime;
float ratio = nowtime / FadeTime;
for (int i = 0; i < ThingsToFade.Length; i++)
{
ThingsToFade[i].color = Color.Lerp(from[i], to[i], ratio);
yield return null;
}
}
for (int i = 0; i < ThingsToFade.Length; i++)
{
ThingsToFade[i].color = to[i];
}
Visible = endstate;
}
}
UPDATE
Heres what I ended up doing.
I keep an array of the components and then cast them to one of three types that I am dealing with. There is a series of if statements to deal with the different classes differntly.
I'm still interested in a more elegant solution that may use some aspects of .net or C# that I'm not familiar with though
public class FadeThingInOut : MonoBehaviour
{
//public Setting fields
public Component[] ThingsToFade;
public bool Visible = true;
public float FadeTime = 1f;
public float Max = 1f;
//private fields
Color[] InColour;
Color[] OutColor;
void Start()
{
if (Visible)
{
SetColours(ref InColour, ref OutColor,0f);
}
else
{
SetColours(ref OutColor, ref InColour, Max);
}
}
void SetColours(ref Color[] one,ref Color[] two,float other)
{
one = new Color[ThingsToFade.Length];
two = new Color[ThingsToFade.Length];
for (int i = 0; i < ThingsToFade.Length; i++)
{
one[i] = GetColour(i);
two[i] = Tools.colorfromalpha(one[i], other);
}
}
public void Fade(bool inout)
{
if (inout && !Visible)
{
StartCoroutine(FadeLerp(OutColor,InColour,inout));
}
else if (!inout && Visible)
{
StartCoroutine(FadeLerp(InColour, OutColor, inout));
}
}
Color GetColour(int index)
{
if(ThingsToFade[index].GetType() == typeof(MeshRenderer))
{
return (ThingsToFade[index] as MeshRenderer ).material.color;
}
else if (ThingsToFade[index].GetType() == typeof(Image))
{
return (ThingsToFade[index] as Image).color;
}
else if (ThingsToFade[index].GetType() == typeof(RawImage))
{
return (ThingsToFade[index] as RawImage).material.color;
}
return Color.black;
}
void SetColour(int index,Color col)
{
if (ThingsToFade[index].GetType() == typeof(MeshRenderer))
{
(ThingsToFade[index] as MeshRenderer).material.color = col;
return;
}
else if (ThingsToFade[index].GetType() == typeof(Image))
{
(ThingsToFade[index] as Image).color = col;
return;
}
else if (ThingsToFade[index].GetType() == typeof(RawImage))
{
(ThingsToFade[index] as RawImage).material.color = col;
return;
}
}
IEnumerator FadeLerp(Color[] from, Color[] to,bool endstate)
{
float nowtime = 0f;
while (nowtime < FadeTime)
{
nowtime += Time.deltaTime;
float ratio = nowtime / FadeTime;
for (int i = 0; i < ThingsToFade.Length; i++)
{
SetColour(i, Color.Lerp(from[i], to[i], ratio));
yield return null;
}
}
for (int i = 0; i < ThingsToFade.Length; i++)
{
SetColour(i, to[i]);
}
Visible = endstate;
}
}
This isn't super ideal but you can do it this way.
public interface ISetColor
{
public Color color { get; set; }
}
public class ImageSetColor : MonoBehaviour, ISetColor
{
public Image m_Image
public Color color { get {return m_Image.color;} set { m_Image.color = value}
}
public class MaterialSetColor : MonoBehaviour, ISetColor
{
public Material m_Material
public Color color { get {return m_Material.color;} set { m_Material.color = value}
}
Then instead of an Image array you can do an array of public MonoBehaviour[] ThingsToFade then anytime you use ThingsToFade you need to cast it to an ISetColor.
Then any component you want to be able to set colors with will need to create a class that inherits from MonoBehaviour and implements the ISetColor interface
This isn't the easiest solution to use in Unity but it should work
You should not write a class. You should write an extension method. Because structs are immutable though, you cannot change their value, so you should write a static function that has as an argument a reference to color, by using the "ref" keyword. Using structs as mutable objects is not recommended most of the time, but it's the best option here.
I wrote this little Unity3D class to fade an array of UI.Image in or
out by reducing or increasing the alpha value of their color property.
I dont see why to overcomplicate everything.
Edit: This will fade by adjusting the alpha directly, using floats instead of color swapping. Faster, cheaper, shorter.
IEnumerator FadeLerp(float _from, float _to, bool endstate)
{
float nowtime = 0f;
while (nowtime < FadeTime)
{
nowtime += Time.deltaTime;
float ratio = nowtime / FadeTime;
for (int i = 0; i < ThingsToFade.Length; i++)
{
Color _color = ThingsToFade[i].color;
_color.a = Mathf.Lerp(_from, _to, ratio);
ThingsToFade[i].color = _color;
yield return null;
}
}
for (int i = 0; i < ThingsToFade.Length; i++)
{
ThingsToFade[i].renderer.enabled = endstate;
}
}
use color.lerp function.
it will fade the color nicely, and if you know how to use the lerp, with a counter and with an array, there should not be a problem :)
if tomorrow you are still stucked i'll write the lerping function for you, but keep trying :)
It will be a sweet experience if you do it on your own :D
I am learning to make a small variant of chess game using windows forms C#, the game includes only the pawns of both sides, i have drew the board and organized the pieces on there places, but i honestly do not know how to start implementing the moves by clicking the mouse on the piece and then the location where i want to move it.
as references the black pawn is named piece, and white pawn is named pieceW
here is my code for the board
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AIchess
{
public partial class Form1 : Form
{
static System.Drawing.Bitmap piece = AIchess.Properties.Resources.piece;
ChessPiece Piece = new ChessPiece(piece, ChessColor.Black);
static System.Drawing.Bitmap pieceW = AIchess.Properties.Resources.pieceW;
ChessPiece PieceW = new ChessPiece(pieceW, ChessColor.White);
Square[,] square = new Square[8, 8];
public Form1()
{
InitializeComponent();
int i, j;
for (i = 0; i < 8; i++)
{
for (j = 0; j < 8; j++)
{
this.square[i, j] = new Square();
this.square[i, j].BackColor = System.Drawing.SystemColors.ActiveCaption;
this.square[i, j].BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.square[i, j].Location = new System.Drawing.Point(57 + i * 60, 109 + j * 60);
this.square[i, j].Name = i.ToString()+j.ToString();
this.square[i, j].Size = new System.Drawing.Size(60, 60);
this.square[i, j].TabIndex = 2;
this.square[i, j].TabStop = false;
this.Controls.Add(this.square[i, j]);
if (j == 1)
{
this.square[i, j].Image = piece;
this.square[i, j].AllocatedBy = "black";
}
if (j == 6)
{
this.square[i, j].Image = pieceW;
this.square[i, j].AllocatedBy = "white";
}
if (((i+j) % 2) ==0)
this.square[i, j].BackColor = Color.RoyalBlue;
else
this.square[i, j].BackColor = Color.LightBlue;
}
}
}
}
public enum ChessColor
{
White,
Black,
};
class ChessPiece
{
private Image DisplayedImage;
private ChessColor DisplayedColor;
private Point CurrentSquare;
public ChessPiece(Image image, ChessColor color)
{
DisplayedImage = image;
DisplayedColor = color;
}
}
class Square:PictureBox
{
private bool color;
public string AllocatedBy;
}
}
Here's a really simple implementation, I hope you won't mind that I did it from scratch.
Obviously it's very simple, there's no drag and drop and no animation but it fulfills your requirement.
I'll go through each part and explain them
InitializeGame
There you do set your images dimensions (they should be identical obviously)
You add in the dictionary the relationship between piece type/color and your bitmap
Note : the grid will be scaled so you can throw any size of bitmap you like
CreateBoard, DrawGame, DrawPieces
Nothing exceptional in there, note that for keeping things simple I do that every time a user clicks but it shouldn't be much of an issue, it's not Crysis after all :D
PickOrDropPiece
This is the logic where picking/dropping happens, it's really trivial and I'll let you take a look by yourself.
Differences between your code
I've created a Board type which holds the pieces and that you can easily update.
Note : do not remove the equality members in Piece they are here to help the dictionary.
Make sure to use 32-bit bitmaps with transparent borders
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
pictureBox1.MouseDown += pictureBox1_MouseDown;
}
#region Properties
private Board Board { get; set; }
private Piece CurrentPiece { get; set; }
private Dictionary<Piece, Bitmap> PieceBitmaps { get; set; }
private int TileWidth { get; set; }
private int TileHeight { get; set; }
#endregion
#region Events
private void Form1_Load(object sender, EventArgs e)
{
InitializeGame();
DrawGame();
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
PickOrDropPiece(e);
DrawGame();
}
#endregion
#region Methods
private void InitializeGame()
{
TileWidth = 64;
TileHeight = 64;
Board = new Board();
PieceBitmaps = new Dictionary<Piece, Bitmap>();
PieceBitmaps.Add(new Piece(PieceType.Pawn, PieceColor.Black), new Bitmap("pawnblack.png"));
PieceBitmaps.Add(new Piece(PieceType.Pawn, PieceColor.White), new Bitmap("pawnwhite.png"));
}
private void DrawGame()
{
var tileSize = new Size(TileWidth, TileHeight);
Bitmap bitmap = CreateBoard(tileSize);
DrawPieces(bitmap);
pictureBox1.Image = bitmap;
}
private Bitmap CreateBoard(Size tileSize)
{
int tileWidth = tileSize.Width;
int tileHeight = tileSize.Height;
var bitmap = new Bitmap(tileWidth*8, tileHeight*8);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
for (int x = 0; x < 8; x++)
{
for (int y = 0; y < 8; y++)
{
Brush brush = (x%2 == 0 && y%2 == 0) || (x%2 != 0 && y%2 != 0) ? Brushes.Black : Brushes.White;
graphics.FillRectangle(brush, new Rectangle(x*tileWidth, y*tileHeight, tileWidth, tileHeight));
}
}
}
return bitmap;
}
private void DrawPieces(Bitmap bitmap)
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
Board board = Board;
for (int x = 0; x < 8; x++)
{
for (int y = 0; y < 8; y++)
{
Piece piece = board.GetPiece(x, y);
if (piece != null)
{
Bitmap bitmap1 = PieceBitmaps[piece];
graphics.DrawImageUnscaled(bitmap1, new Point(x*TileWidth, y*TileHeight));
}
}
}
}
}
private void PickOrDropPiece(MouseEventArgs e)
{
Point location = e.Location;
int x = location.X/TileWidth;
int y = location.Y/TileHeight;
bool pickOrDrop = CurrentPiece == null;
if (pickOrDrop)
{
// Pick a piece
Piece piece = Board.GetPiece(x, y);
Board.SetPiece(x, y, null);
if (piece != null)
{
label1.Text = string.Format("You picked a {0} {1} at location {2},{3}", piece.Color, piece.Type, x,
y);
}
else
{
label1.Text = "Nothing there !";
}
CurrentPiece = piece;
}
else
{
// Drop picked piece
Board.SetPiece(x, y, CurrentPiece);
label1.Text = string.Format("You dropped a {0} {1} at location {2},{3}", CurrentPiece.Color,
CurrentPiece.Type, x,
y);
CurrentPiece = null;
}
}
#endregion
}
public class Board
{
private readonly Piece[] _pieces;
public Board()
{
_pieces = new Piece[8*8];
PopulatePieces();
}
public Piece GetPiece(int x, int y)
{
int i = y*8 + x;
return _pieces[i];
}
public void SetPiece(int x, int y, Piece piece)
{
int i = y*8 + x;
_pieces[i] = piece;
}
private void PopulatePieces()
{
for (int i = 0; i < 8; i++)
{
SetPiece(i, 1, new Piece(PieceType.Pawn, PieceColor.Black));
SetPiece(i, 7, new Piece(PieceType.Pawn, PieceColor.White));
}
}
}
public class Piece
{
private readonly PieceColor _color;
private readonly PieceType _type;
public Piece(PieceType type, PieceColor color)
{
_type = type;
_color = color;
}
public PieceType Type
{
get { return _type; }
}
public PieceColor Color
{
get { return _color; }
}
protected bool Equals(Piece other)
{
return _color == other._color && _type == other._type;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((Piece) obj);
}
public override int GetHashCode()
{
unchecked
{
return ((int) _color*397) ^ (int) _type;
}
}
public static bool operator ==(Piece left, Piece right)
{
return Equals(left, right);
}
public static bool operator !=(Piece left, Piece right)
{
return !Equals(left, right);
}
}
public enum PieceType
{
Pawn
}
public enum PieceColor
{
Black,
White
}
}
I'm learning to use the as operator, and my goal was to create an option window (non windows form) that can:
Have options added to it (for flexibility, in case I want to use if statements to add menu items)
Be able to display text, textures, or a class (using the classes draw function)
Be controlled through the host GameState
I still haven't added the options for indicating an item is selected, my apologies for not posting a complete work. I also have not sorted the code into regions yet. Sorry again!
Is my code (particularly the draw function) properly using the is and as operators properly, from a performance and readability (non spaghetti code) standpoint?
public class OptionWindow : DrawableGameComponent
{
public Dictionary<int, Option> options;
int selectedOption;
bool windowLoops;
Rectangle drawRectangle;
int spacer;
int totalItemHeight;
SpriteFont sf;
SpriteBatch sb;
public Rectangle DrawRectangle
{
get { return drawRectangle; }
set { drawRectangle = value; }
}
public int SelectedOption
{
get { return selectedOption; }
set
{
if (windowLoops)
{
if (selectedOption >= options.Count())
selectedOption = 0;
if (selectedOption < 0)
selectedOption = options.Count() - 1;
}
else
{
if (selectedOption >= options.Count())
selectedOption = options.Count() - 1;
if (selectedOption < 0)
selectedOption = 0;
}
}
}
public OptionWindow(Game game, bool windowLoops, SpriteFont sf, Rectangle drawRectangle)
: base(game)
{
options = new Dictionary<int, Option>();
this.windowLoops = windowLoops;
this.sf = sf;
DrawRectangle = new Rectangle(drawRectangle.X, drawRectangle.Y, drawRectangle.Width, drawRectangle.Height);
}
public void Add(object option, bool selectable, bool defaultSelection, int height)
{
options.Add(options.Count(), new Option(selectable, option, height));
if (defaultSelection)
SelectedOption = options.Count() - 1;
UpdatePositions();
}
public void UpdatePositions()
{
UpdateTotalItemHeight();
if (options.Count() - 1 != 0)
spacer = (drawRectangle.Height - totalItemHeight) / (options.Count() - 1);
for (int i = 0; i < options.Count(); i++)
{
if (i == 0)
options[i].Position = new Vector2(drawRectangle.X, drawRectangle.Y);
else
{
options[i].Position = new Vector2(
drawRectangle.X,
options[i - 1].Position.Y + options[i - 1].Height + spacer);
}
}
}
public void UpdateTotalItemHeight()
{
totalItemHeight = 0;
for (int i = 0; i < options.Count(); i++)
{
totalItemHeight += options[i].Height;
}
}
protected override void LoadContent()
{
sb = new SpriteBatch(GraphicsDevice);
base.LoadContent();
}
public override void Draw(GameTime gameTime)
{
for (int i = 0; i < options.Count(); i++)
{
if (options[i].OptionObject is string)
sb.DrawString(sf, options[i].OptionObject as string, options[i].Position, Color.White);
if (options[i].OptionObject is Texture2D)
sb.Draw(options[i].OptionObject as Texture2D,
new Rectangle(
(int)options[i].Position.X,
(int)options[i].Position.Y,
options[i].Height,
(options[i].Height / (options[i].OptionObject as Texture2D).Height) * (options[i].OptionObject as Texture2D).Width),
Color.White);
if (options[i].OptionObject is DisplayObject)
(options[i].OptionObject as DisplayObject).Draw(gameTime);
}
base.Draw(gameTime);
}
}
public class Option
{
bool selectable;
object optionObject;
int height;
Vector2 position;
public bool Selectable
{
get { return selectable; }
set { selectable = value; }
}
public object OptionObject
{
get { return optionObject; }
set { optionObject = value; }
}
public int Height
{
get { return height; }
set { height = value; }
}
public Vector2 Position
{
get { return position; }
set { position = value; }
}
public Option(bool selectable, object option, int height)
{
Selectable = selectable;
OptionObject = option;
Height = height;
}
}
It is never adviseable to use is and then as. The usual way to go would be to either of the following:
just use is (if you just want to know the type without subsequent casting)
assign the result of as to a variable and check whether that variable is (not) null
The code analysis tool FxCop helps you find any spots in your code that use is and then as and warns you because of performance concerns.
Note however that a better approach altogether might be to declare your OptionObject property as some abstract class with a Draw method. You could then derive a subclass for strings, one for Texture2D instances and another one for DisplayObject instances and just call Draw in your OptionWindow.Draw method. This would leave the decision which actual drawing operations to execute up to built-in polymorphism features of the framework.
I'm having trouble effectively implementing this generic method featured by Eric Lippert. His blog outlines a very simple and effective means of creating an A Star algorithm (found here). Here's the quick run down.
The code for the actual path finding:
class Path<Node> : IEnumerable<Node>
{
public Node LastStep { get; private set; }
public Path<Node> PreviousSteps { get; private set; }
public double TotalCost { get; private set; }
private Path(Node lastStep, Path<Node> previousSteps, double totalCost)
{
LastStep = lastStep;
PreviousSteps = previousSteps;
TotalCost = totalCost;
}
public Path(Node start) : this(start, null, 0) { }
public Path<Node> AddStep(Node step, double stepCost)
{
return new Path<Node>(step, this, TotalCost + stepCost);
}
public IEnumerator<Node> GetEnumerator()
{
for (Path<Node> p = this; p != null; p = p.PreviousSteps)
yield return p.LastStep;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
class AStar
{
static public Path<Node> FindPath<Node>(
Node start,
Node destination,
Func<Node, Node, double> distance,
Func<Node, double> estimate)
where Node : IHasNeighbours<Node>
{
var closed = new HashSet<Node>();
var queue = new PriorityQueue<double, Path<Node>>();
queue.Enqueue(0, new Path<Node>(start));
while (!queue.IsEmpty)
{
var path = queue.Dequeue();
if (closed.Contains(path.LastStep))
continue;
if (path.LastStep.Equals(destination))
return path;
closed.Add(path.LastStep);
foreach (Node n in path.LastStep.Neighbours)
{
double d = distance(path.LastStep, n);
if (n.Equals(destination))
d = 0;
var newPath = path.AddStep(n, d);
queue.Enqueue(newPath.TotalCost + estimate(n), newPath);
}
}
return null;
}
/// <summary>
/// Finds the distance between two points on a 2D surface.
/// </summary>
/// <param name="x1">The IntPoint on the x-axis of the first IntPoint</param>
/// <param name="x2">The IntPoint on the x-axis of the second IntPoint</param>
/// <param name="y1">The IntPoint on the y-axis of the first IntPoint</param>
/// <param name="y2">The IntPoint on the y-axis of the second IntPoint</param>
/// <returns></returns>
public static long Distance2D(long x1, long y1, long x2, long y2)
{
// ______________________
//d = √ (x2-x1)^2 + (y2-y1)^2
//
//Our end result
long result = 0;
//Take x2-x1, then square it
double part1 = Math.Pow((x2 - x1), 2);
//Take y2-y1, then sqaure it
double part2 = Math.Pow((y2 - y1), 2);
//Add both of the parts together
double underRadical = part1 + part2;
//Get the square root of the parts
result = (long)Math.Sqrt(underRadical);
//Return our result
return result;
}
/// <summary>
/// Finds the distance between two points on a 2D surface.
/// </summary>
/// <param name="x1">The IntPoint on the x-axis of the first IntPoint</param>
/// <param name="x2">The IntPoint on the x-axis of the second IntPoint</param>
/// <param name="y1">The IntPoint on the y-axis of the first IntPoint</param>
/// <param name="y2">The IntPoint on the y-axis of the second IntPoint</param>
/// <returns></returns>
public static int Distance2D(int x1, int y1, int x2, int y2)
{
// ______________________
//d = √ (x2-x1)^2 + (y2-y1)^2
//
//Our end result
int result = 0;
//Take x2-x1, then square it
double part1 = Math.Pow((x2 - x1), 2);
//Take y2-y1, then sqaure it
double part2 = Math.Pow((y2 - y1), 2);
//Add both of the parts together
double underRadical = part1 + part2;
//Get the square root of the parts
result = (int)Math.Sqrt(underRadical);
//Return our result
return result;
}
public static long Distance2D(Point one, Point two)
{
return AStar.Distance2D(one.X, one.Y, two.X, two.Y);
}
}
The PriorityQueue code:
class PriorityQueue<P, V>
{
private SortedDictionary<P, Queue<V>> list = new SortedDictionary<P, Queue<V>>();
public void Enqueue(P priority, V value)
{
Queue<V> q;
if (!list.TryGetValue(priority, out q))
{
q = new Queue<V>();
list.Add(priority, q);
}
q.Enqueue(value);
}
public V Dequeue()
{
// will throw if there isn’t any first element!
var pair = list.First();
var v = pair.Value.Dequeue();
if (pair.Value.Count == 0) // nothing left of the top priority.
list.Remove(pair.Key);
return v;
}
public bool IsEmpty
{
get { return !list.Any(); }
}
}
And the interface that gets nearby nodes:
interface IHasNeighbours<N>
{
IEnumerable<N> Neighbours { get; }
}
This is the part I'm having trouble effectively implementing. I can create a class capable of being used by the path finding, but finding the nearby nodes is becoming a pain. Essentially what I end up doing is creating a class that, in this case, counts as a single tile. However, in order to get all the nearby nodes, I have to pass a value into that tile that includes a list of all other tiles. This is very cumbersome and leads me to believe there must be an easier method.
Here is my implementation using a wrapper for System.Drawing.Point:
class TDGrid : IHasNeighbours<TDGrid>, IEquatable<TDGrid>
{
public Point GridPoint;
public List<Point> _InvalidPoints = new List<Point>();
public Size _GridSize = new Size();
public int _GridTileSize = 50;
public TDGrid(Point p, List<Point> invalidPoints, Size gridSize)
{
GridPoint = p;
_InvalidPoints = invalidPoints;
_GridSize = gridSize;
}
public TDGrid Up(int gridSize)
{
return new TDGrid(new Point(GridPoint.X, GridPoint.Y - gridSize));
}
public TDGrid Down(int gridSize)
{
return new TDGrid(new Point(GridPoint.X, GridPoint.Y + gridSize));
}
public TDGrid Left(int gridSize)
{
return new TDGrid(new Point(GridPoint.X - gridSize, GridPoint.Y));
}
public TDGrid Right(int gridSize)
{
return new TDGrid(new Point(GridPoint.X + gridSize, GridPoint.Y));
}
public IEnumerable<TDGrid> IHasNeighbours<TDGrid>.Neighbours
{
get { return GetNeighbours(this); }
}
private List<TDGrid> GetNeighbours(TDGrid gridPoint)
{
List<TDGrid> retList = new List<TDGrid>();
if (IsGridSpotAvailable(gridPoint.Up(_GridTileSize)))
retList.Add(gridPoint.Up(_GridTileSize)); ;
if (IsGridSpotAvailable(gridPoint.Down(_GridTileSize)))
retList.Add(gridPoint.Down(_GridTileSize));
if (IsGridSpotAvailable(gridPoint.Left(_GridTileSize)))
retList.Add(gridPoint.Left(_GridTileSize));
if (IsGridSpotAvailable(gridPoint.Right(_GridTileSize)))
retList.Add(gridPoint.Right(_GridTileSize));
return retList;
}
public bool IsGridSpotAvailable(TDGrid gridPoint)
{
if (_InvalidPoints.Contains(gridPoint.GridPoint))
return false;
if (gridPoint.GridPoint.X < 0 || gridPoint.GridPoint.X > _GridSize.Width)
return false;
if (gridPoint.GridPoint.Y < 0 || gridPoint.GridPoint.Y > _GridSize.Height)
return false;
return true;
}
public override int GetHashCode()
{
return GridPoint.GetHashCode();
}
public override bool Equals(object obj)
{
return this.GridPoint == (obj as TDGrid).GridPoint;
}
public bool Equals(TDGrid other)
{
return this.GridPoint == other.GridPoint;
}
}
The List _InvalidPoints is where I'm falling flat. I can pass this in to every TDGrid that is created but that seems like a massive waste of resources considering how simple all the rest of the code is. I know this is a lack of knowledge on my part but I haven't been able to search it down.
There must be another way to implement:
interface IHasNeighbours<N>
{
IEnumerable<N> Neighbours { get; }
}
Anyone have any ideas on this?
Edit --
Here's the path finding code:
public void FindPath(TDGrid start, TDGrid end)
{
AStar.FindPath<TDGrid>(start, end, (p1, p2) => { return AStar.Distance2D(p1.GridPoint, p2.GridPoint); }, (p1) => { return AStar.Distance2D(p1.GridPoint, end.GridPoint); });
}
It sounds like you have two separate concerns here. You need to represent the pathways between nodes and the nodes themselves. You may find it easiest to represent these two concepts separately.
For example, in the code below, the Grid class keeps track of how nodes are connected. This could be as simple as storing an hash set of the tiles that are walls (ie. obstructed). To determine if a node is reachable, check if it is in the hash set. This is just a simple example. There are many other ways you could represent a graph, see Wikipedia.
An individual Node can be represented as two coordinates on the Grid, requiring only three values: the row, the column, and the Grid itself. This allows each individual Node to be created on the fly (Flyweight pattern).
Hope that helps!
class Grid
{
readonly int _rowCount;
readonly int _columnCount;
// Insert data for master list of obstructed cells
// or master list of unobstructed cells
public Node GetNode(int row, int column)
{
if (IsOnGrid(row, column) && !IsObstructed(row, column))
{
return new Node(this, row, column);
}
return null;
}
private bool IsOnGrid(int row, int column)
{
return row >= 0 && row < _rowCount && column >= 0 && column < _columnCount;
}
private bool IsObstructed(int row, int column)
{
// Insert code to check whether specified row and column is obstructed
}
}
class Node : IHasNeighbours<Node>
{
readonly Grid _grid;
readonly int _row;
readonly int _column;
public Node(Grid grid, int row, int column)
{
_grid = grid;
_row = row;
_column = column;
}
public Node Up
{
get
{
return _grid.GetNode(_row - 1, _column);
}
}
public Node Down
{
get
{
return _grid.GetNode(_row + 1,_column);
}
}
public Node Left
{
get
{
return _grid.GetNode(_row, _column - 1);
}
}
public Node Right
{
get
{
return _grid.GetNode(_row, _column + 1);
}
}
public IEnumerable<Node> Neighbours
{
get
{
Node[] neighbors = new Node[] {Up, Down, Left, Right};
foreach (Node neighbor in neighbors)
{
if (neighbor != null)
{
yield return neighbor;
}
}
}
}
}
This was the implementation I ended up using, very similar to Special Touch's solution. SpacialObject is a Point.
public class Tile : SpacialObject, IHasNeighbours<Tile>
{
public Tile(int x, int y)
: base(x, y)
{
CanPass = true;
}
public bool CanPass { get; set; }
public Point GetLocation(int gridSize)
{
return new Point(this.X * gridSize, this.Y * gridSize);
}
public IEnumerable<Tile> AllNeighbours { get; set; }
public IEnumerable<Tile> Neighbours { get { return AllNeighbours.Where(o => o.CanPass); } }
public void FindNeighbours(Tile[,] gameBoard)
{
var neighbours = new List<Tile>();
var possibleExits = X % 2 == 0 ? EvenNeighbours : OddNeighbours;
possibleExits = GetNeighbours;
foreach (var vector in possibleExits)
{
var neighbourX = X + vector.X;
var neighbourY = Y + vector.Y;
if (neighbourX >= 0 && neighbourX < gameBoard.GetLength(0) && neighbourY >= 0 && neighbourY < gameBoard.GetLength(1))
neighbours.Add(gameBoard[neighbourX, neighbourY]);
}
AllNeighbours = neighbours;
}
public static List<Point> GetNeighbours
{
get
{
return new List<Point>
{
new Point(0, 1),
new Point(1, 0),
new Point(0, -1),
new Point(-1, 0),
};
}
}
public static List<Point> EvenNeighbours
{
get
{
return new List<Point>
{
new Point(0, 1),
new Point(1, 1),
new Point(1, 0),
new Point(0, -1),
new Point(-1, 0),
new Point(-1, 1),
};
}
}
public static List<Point> OddNeighbours
{
get
{
return new List<Point>
{
new Point(0, 1),
new Point(1, 0),
new Point(1, -1),
new Point(0, -1),
new Point(-1, 0),
new Point(-1, -1),
};
}
}
}
Then in the main program I used:
private void InitialiseGameBoard()
{
GameBoard = new Tile[_Width, _Height];
for (var x = 0; x < _Width; x++)
{
for (var y = 0; y < _Height; y++)
{
GameBoard[x, y] = new Tile(x, y);
}
}
AllTiles.ToList().ForEach(o => o.FindNeighbours(GameBoard));
int startX = 0, endX = GameBoard.GetLength(0) - 1;
int startEndY = GameBoard.GetLength(1) / 2;
_StartGridPoint = new Point(startX, startEndY);
_EndGridPoint = new Point(endX, startEndY);
//GameBoard[startX, startEndY].CanPass = false;
//GameBoard[endX, startEndY].CanPass = false;
}
private void BlockOutTiles()
{
GameBoard[2, 5].CanPass = false;
GameBoard[2, 4].CanPass = false;
GameBoard[2, 2].CanPass = false;
GameBoard[3, 2].CanPass = false;
GameBoard[4, 5].CanPass = false;
GameBoard[5, 5].CanPass = false;
GameBoard[5, 3].CanPass = false;
GameBoard[5, 2].CanPass = false;
}
public IEnumerable<Tile> AllTiles
{
get
{
for (var x = 0; x < _Width; x++)
for (var y = 0; y < _Height; y++)
yield return GameBoard[x, y];
}
}