I am looking to randomly generate 3 items using a loot table. The loot table currently works as expected, it will randomly generate an item based on the item's rarity. However, I don't want there to be duplicates of the same item. How can I set this up? This script is currently attached to 3 separate GameObject item pedestals.
using System.Collections.Generic;
using UnityEngine;
public class ShopItemSpawner : MonoBehaviour
{
[System.Serializable]
public class DropItem
{
public string name;
public GameObject item;
public int dropRarity;
}
public List<DropItem> ShopItemPool = new List<DropItem>();
private void Start()
{
int itemWeight = 0;
for (int i = 0; i < ShopItemPool.Count; i++)
{
itemWeight += ShopItemPool[i].dropRarity;
}
int randomValue = Random.Range(0, itemWeight);
for (int j = 0; j < ShopItemPool.Count; j++)
{
if (randomValue <= ShopItemPool[j].dropRarity)
{
Instantiate(ShopItemPool[j].item, transform.position, Quaternion.identity);
return;
}
randomValue -= ShopItemPool[j].dropRarity;
}
}
}
You could clone the ShopItemPool list and with each item rolled you remove that item from the list. You then need to go all over again by recalculating the total weight.
I find it useful to have a general purpose class for randomizing items with weights, it allows you do this this:
var randomizer = new WeightRandomizer<DropItem>();
foreach (var shopItem in ShopItemPool) {
randomizer.Add(shopItem, shopItem.dropRarity);
}
randomizer.Roll(); // Get a random element based on weight.
randomizer.Take(); // Get a random element based on weight and remove it from the randomizer.
General Purpose Weight Randomizer:
public class WeightRandomizer<T> {
[Serializable]
public class WeightedElement<T> {
public T value;
public int weight;
public WeightedElement (T value, int weight) {
this.value = value;
this.weight = weight;
}
}
private readonly List<WeightedElement<T>> elements = new();
public void Add (T value, int weight) => elements.Add(new WeightedElement<T>(value, weight));
public void AddRange (IEnumerable<WeightedElement<T>> weightedElements) => elements.AddRange(weightedElements);
public int TotalWeight() => elements.Sum(x => x.weight);
public T Roll() => Pick(false);
public T Take() => Pick(true);
private T Pick (bool remove) {
if (elements.Count == 0) {
Debug.LogWarning($"{nameof(WeightRandomizer<T>)} is missing elements.");
return default(T);
}
var roll = Random.Range(0, TotalWeight());
var selectedIndex = elements.Count - 1;
var selected = elements[selectedIndex].value;
for (var i = 0; i < elements.Count; i++) {
var element = elements[i];
// Found an element with a low enough value.
if (roll < element.weight) {
selected = element.value;
selectedIndex = i;
break;
}
// Keep searching for an element with a lower value.
roll -= element.weight;
}
// Sometimes we want to take and remove the element from the pool.
if (remove) elements.RemoveAt(selectedIndex);
return selected;
}
}
I'm working on a radix sort algorithm with linked list. I have in mind how I'm going to solve it, but I need to be able to delete the whole linked list (deleting node one by one when looping through linked list). The problem which I encountered is my current (node) pointer doesn't get the value first. I have tried many different things, but I guess I don't grasp how to reset the pointers 'first', 'current', 'left', 'right' when deleting a node
Radixsort.cs:
public static void Counting_sort(LinkedList<int> list, int exp)
{
LinkedList<int> D = new LinkedList<int>();
LinkedList<int>[] lists = new LinkedList<int>[10];
for (int i = 0; i < lists.Length; i++)
{
lists[i] = new LinkedList<int>();
}
// Skaiciu daznumas
int number = 0;
for (list.Beginning(); list.Exists(); list.Next())
{
number = (list.Take() / exp % 10);
lists[number].Add(list.Take());
list.Remove(list.TakeNode()); <---------- Removing node one by one till it's empty
}
for (int i = 0; i < lists.Length; i++)
{
for (list.Beginning(); list.Exists(); list.Next())
{
//list.Add(lists)
}
}
}
public static void Radix_sort(LinkedList<NumberPlate> list)
{
LinkedList<int> intList = new LinkedList<int>();
AddIntToLinkedList(list, intList);
for (int exp = 1; exp < Math.Pow(10, 9); exp *= 10)
{
Counting_sort(intList, exp);
}
}
LinkedList.cs
public class LinkedList<T> : IEnumerable<T> where T : IComparable<T>, IEquatable<T>
{
public sealed class Node<T>
{
public Node<T> Right;
public Node<T> Left;
public T data;
public Node(T value, Node<T> left, Node<T> right)
{
Left = left;
Right = right;
data = value;
}
}
private Node<T> first;
private Node<T> last;
private Node<T> current;
public int size;
public LinkedList()
{
first = null;
last = null;
current = null;
size = 0;
}
public void Add(T element)
{
var node = new Node<T>(element, last, null);
if (first != null)
{
last.Right = node;
last = node;
}
else
{
first = node;
last = node;
}
current = node;
size++;
}
public void Remove(Node<T> node)
{
if (node == first)
{
first = first.Right;
}
else if (node == last)
{
last = last.Left;
}
else if (node.Left != null)
{
node.Left.Right = node.Right;
}
else if (node.Right != null)
{
node.Right.Left = node.Left;
}
size--;
}
public void Beginning()
{
current = first;
}
public void End()
{
current = last;
}
public void Next()
{
current = current.Right;
}
public void Previous()
{
current = current.Left;
}
public bool Exists()
{
return current != null;
}
public T Take()
{
return current.data;
}
public Node<T> TakeNode()
{
return current;
}
I think you should be able to do this to clear a list:
foreach (var node in list)
{
list.Remove(node);
}
The Remove method is already implemented for you on the LinkedList class, so you should make use of it.
Because the LinkedList implements IEnumerable, you can iterate over it using foreach.
I am not very experienced with C#. I am trying to build circular lists and I have done it like this:
public List<string> items = new List<string> {"one", "two", "three"};
private int index = 0;
void nextItem() {
if (index < items.Count - 1)
index += 1;
else
index = 0;
setItem();
}
void previousItem() {
if (index > 0)
index -= 1;
else
index = items.Count - 1;
setItem();
}
void Update() {
if (Input.GetKeyDown(KeyCode.RightArrow)) nextItem();
else if (Input.GetKeyDown(KeyCode.LeftArrow)) previousItem();
}
But now I am wondering: Am I reinventing the wheel? Does C# already come with a proper data structure for this?
EDIT: In case some context is needed. I have a game menu, where I display a collection of items, and I want that when I press "next" and Im at the last one, to show the first item again.
With utilization of the % (remainder) operator your code gets quite simple:
void nextItem() {
index++; // increment index
index %= items.Count; // clip index (turns to 0 if index == items.Count)
// as a one-liner:
/* index = (index + 1) % items.Count; */
setItem();
}
void previousItem() {
index--; // decrement index
if(index < 0) {
index = items.Count - 1; // clip index (sadly, % cannot be used here, because it is NOT a modulus operator)
}
// or above code as a one-liner:
/* index = (items.Count+index-1)%items.Count; */ // (credits to Matthew Watson)
setItem();
}
You cann also write your own circular list
public class CircularList<T> : List<T>, IEnumerable<T>
{
public new IEnumerator<T> GetEnumerator()
{
return new CircularEnumerator<T>(this);
}
IEnumerator IEnumerable.GetEnumerator()
{
return new CircularEnumerator<T>(this);
}
}
class CircularEnumerator<T> : IEnumerator<T>
{
private readonly List<T> list;
int i = 0;
public CircularEnumerator(List<T> list){
this.list = list;
}
public T Current => list[i];
object IEnumerator.Current => this;
public void Dispose()
{
}
public bool MoveNext()
{
i = (i + 1) % list.Count;
return true;
}
public void Reset()
{
i = 0;
}
}
public class CircularList<T> : List<T>
{
private int Index;
public CircularList() : this(0) { }
public CircularList(int index)
{
if (index < 0 || index >= Count)
throw new Exception(string.Format("Index must between {0} and {1}", 0, Count));
Index = index;
}
public T Current()
{
return this[Index];
}
public T Next()
{
Index++;
Index %= Count;
return this[Index];
}
public T Previous()
{
Index--;
if (Index < 0)
Index = Count - 1;
return this[Index];
}
public void Reset()
{
Index = 0;
}
public void MoveToEnd()
{
Index = Count - 1;
}
}
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];
}
}