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.
Related
In few last days trying to implement TinyKeep's dungeon generation algorithm myself.
Link to TinyKeep's dungeon generation algorithm
I don't understand it perfectly by 100%, because of it here am I :)
Firstly I generated set of rooms in circle
Going for each room in set. Pick it and going with another for loop again in set to compare every room intersection.
If room intersects, I added force, in which I will be apply separation. After that nested loop ended, I move current room, from first loop, according to that force vector. And so on. It happening until every room is not overlapped with others.
Also I picked center(Middle) point of rects ( x+width/2f, y+height/2f) to calculate that force vector
If vector is normalized, then rooms tighly packed, but if no - rooms have much space between them...
I wanna just recreate and understand TinyKeep's algorithm, but results not the same...
Also I getting weird results, then tall/wide rooms or always going in corners, tall go upper-left corner of map, wide - bottom-right corner. And all sometimes have overall diagonal separate direction
// Main method
private void Separate()
{
do
{
for (int i = 0; i < rooms.Count; i++)
{
Vector2 force = Vector2.zero;
int overlapCounter = 0;
for (int j = 0; j < rooms.Count; j++)
{
if (i == j) continue;
if (!rooms[i].IsOverlapped(rooms[j])) continue;
force += rooms[j].Middle - rooms[i].Middle;
overlapCounter++;
}
if (overlapCounter == 0) continue;
force /= overlapCounter;
force.Normalize();
force *= -1f;
rooms[i].Move(force);
}
} while (IsAnyRoomOverlapped());
StopTimer(true);
}
// Room.Move()
public void Move(Vector2 move)
{
x += Mathf.CeilToInt(move.x)* TILE_SIZE;
y += Mathf.CeilToInt(move.y)* TILE_SIZE;
}
// Constructor
public Dungeon(int roomAmount, int seed,float radius)
{
StartTimer();
this.roomAmount = roomAmount;
this.radius = radius;
Seed = seed;
InitializeRandom();
PopulateDungeonWithRooms();
}
While trying to figure out how solve this problem I read some questions and look their source code. I find that they not only move room after every overlapped neighbour found but also move neighbour in other direction!
What about directional behaviour of separation? It's my mistake or misunderstanding of rounding to integer
What about perfomance? Generating 512 rooms inside circle with radius of 128 units using normal distribution give 1100 ms
Now, code looks like:
private void SeparateRooms()
{
do
{
for (int current = 0; current < rooms.Count; current++)
{
for (int other = 0; other < rooms.Count; other++)
{
if (current == other || !rooms[current].IsOverlapping(rooms[other])) continue;
var direction = (rooms[other].Middle - rooms[current].Middle).normalized;
rooms[current].Move(-direction, TILE_SIZE);
rooms[other].Move(direction, TILE_SIZE);
}
}
}
while (IsAnyRoomOverlapped());
StopTimer(true);
}
And Room's Move() method:
public void Move(Vector2 move,int tileSize=1)
{
x += Mathf.RoundToInt(move.x)*tileSize;
y += Mathf.RoundToInt(move.y)*tileSize;
}
I would like to retrieve the Transform/Position of an animationClip at a given time/frame.
I can actually set an animationClip to a given time with animationClip.SampleAnimation()(https://docs.unity3d.com/ScriptReference/AnimationClip.SampleAnimation.html)
Therefore I could eventually create a clone, put it on a specific frame and get his position, but it would be too heavy for each frame.
If you have any solutions, or maybe some functions that I didn't see I would be glad.
Thank you
I've found the solution, it takes a lot of process, but as it's done only in Editor it doesn't affect the game.
I am getting the curve of the animationclip and I store 10 position, each corresponding to 10% of the animation.
For this I need to compute each x, y and z axis curves :
void OnValidate()
{
//Check if we are in the Editor, and the animationClip is getting change
if (Application.isEditor && !Application.isPlaying && previousClip != animationClip)
{
previousClip = animationClip;
m_checkPoints = new Vector3[10];
int index = 0;
//Taking the curves form the animation clip
AnimationCurve curve;
var curveBindings = AnimationUtility.GetCurveBindings(animationClip);
//Going into each curves
foreach (var curveBinding in curveBindings)
{
//Checking curve's name, as we only need those about the position
if (curveBinding.propertyName == "m_LocalPosition.x")
index = 0;
else if (curveBinding.propertyName == "m_LocalPosition.y")
index = 1;
else if (curveBinding.propertyName == "m_LocalPosition.z")
index = 2;
else
continue;
//Get the curveform the editor
curve = AnimationUtility.GetEditorCurve(animationClip, curveBinding);
for (float i = 0; i < 10; i++)
{
//Evaluate the curves at a given time
m_checkPoints[(int)i][index] = curve.Evaluate(animationClip.length * (i / 10f));
}
}
}
}
I'm currently working on a random grid generation base for game map and I'm kinda stuck at how do I call an instantiated object based on their in game coordinates (not Vector3).
This is my hex generation script:
public HexCell cellPrefab;
HexCell[] cells;
void CreateCell (int x, int z, int i) {
Vector3 position;
position.x = (x + z * 0.5f - z / 2) * (HexMetrics.innerRadius * 2f);
position.y = 0f;
position.z = z * (HexMetrics.outerRadius * 1.5f);
HexCell cell = cells[i] = Instantiate<HexCell>(cellPrefab);
cell.coordinates = HexCoordinates.FromOffsetCoordinates(x, z);
}
I can call the object fine using index of objects' array but I can't wrap my head around the idea of calling it based on its given coordinates.
This is how I give them coordinates based on each hex position;
public class HexCell : MonoBehaviour {
public HexCoordinates coordinates;
}
public struct HexCoordinates {
[SerializeField]
private int x, z;
public int X {
get { return x; }
}
public int Y {
get { return -X - Z; }
}
public int Z {
get { return z; }
}
public HexCoordinates (int x, int z) {
this.x = x;
this.z = z;
}
public static HexCoordinates FromOffsetCoordinates (int x, int z) {
return new HexCoordinates(x - z / 2, z);
}
}
So how do I call/select desired hex based on HexCell coordinates?
So how do I call/select desired hex based on HexCell coordinates?
This is easy to do since you used int instead of float to represent the coordinate. Just loop over the cells array. In each loop, access the coordinates variable from the HexCell component then compare x, y and z values. If they match, return the current HexCell in the loop. If not, simply return null. You can also do this with linq but avoid it.
Your cell array:
HexCell[] cells;
Get HexCell from coordinate:
HexCell getHexCellFromCoordinate(int x, int y, int z)
{
//Loop thorugh each HexCell
for (int i = 0; i < cells.Length; i++)
{
HexCoordinates hc = cells[i].coordinates;
//Check if coordinate matches then return it
if ((hc.X == x) && (hc.Y == y) && (hc.Z == z))
{
return cells[i];
}
}
//No match. Return null
return null;
}
Since the coordinate is in int instead of float, you can also use Vector3Int(Requires Unity 2017.2 and above) to represent them instead of x, y and z. Note that this is different from Vector3.
HexCell getHexCellFromCoordinate(Vector3Int coord)
{
//Loop thorugh each HexCell
for (int i = 0; i < cells.Length; i++)
{
HexCoordinates hc = cells[i].coordinates;
//Check if coordinate matches then return it
if ((hc.X == coord.x) && (hc.Y == coord.y) && (hc.Z == coord.z))
{
return cells[i];
}
}
//No match. Return null
return null;
}
Alternatively, you could use some built-in functions to make it more readable.
HexCell getHexCellFromCoordinate(int x, int y, int z)
{
return cells.FirstOrDefault(
cell =>
cell.x == x &&
cell.y == y &&
cell.z == z
);
}
HexCell getHexCellFromCoordinate(Vector3Int coord)
{
return cells.FirstOrDefault(cell =>
cell.x == coord.x &&
cell.y == coord.y &&
cell.z == coord.z
);
}
It's worth noting, since I'm not sure where you'll be needing to find the cells, that FirstOrDefault makes heap allocations and therefore invoking it too often on too large of a list (as in tens of thousands of times per frame, and/or against tens of thousands of objects) it could result in undesired garbage collections, which may cause a stutter in your application, and that stutter may be noticeable if combined with other code that uses heap allocations willy-nilly.
So as a general rule, start with something simple. If your application starts to slow down or take up too much memory, go back and optimize starting with the hottest (least performant) code. You can use a profiler to help you find those spots. But please, don't riddle your code with for loops just to save some extra cpu cycles.
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
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);
}