I calculated distance between a unit and a building, I put the distance inside a struct[] and sorted the distance smallest to largest. The AI goes to the closest building each time and this works as it is. Now my question is I want to add a check and remove the last line of code thats hardcoded to index 0. I want the AI to go to the next index each time the current index runs out of resources. How would i go about doing this? The struct populates with sorted distances each time the AI selects the closest building to bring resources from.
Would it be an if statement inside the for loop? Or another for loop with an if statement? Or do you change the indexes in the struct if the building is exhausted of resources?
Destination2 = GameObject.FindGameObjectsWithTag("Storehouses"); // storehouse distance should be based off production
Vector3 position = transform.position;
for (int i = 0; i < Destination2.Length; i++) // put in 10 storehouses, find the closest distance to the colonist
{
Debug.Log("1");
buildingInformation[i].buildingID2 = Destination2[i]; // stores game objects 0,1
Vector3 direction = (Destination2[i].transform.position - position);
float distance = direction.sqrMagnitude;
buildingInformation[i].buildingDistance2 = distance;
Array.Sort<BuildingInformation>(buildingInformation, (x, y) => x.buildingDistance2.CompareTo(y.buildingDistance2));// smallest to largest
}
Supplier = buildingInformation[0].buildingID2;
Some Linqy pseudocode on how I'd approach it:
destination = storehouses
.OrderBy( storehouse.distance )
.FirstOrDefault( storehouse.contains(item) )
Although you're probably better off doing a single-pass iteration:
SomeType destination
foreach storehouse in storehouses
if ( storehouse.contains(item) )
if ( destination is null || storehouse.distance < destination.distance )
destination = storehouse
I have a code that allows me to draw lines and limit the number of lines that can be drawn.
My problem is that I want to create a line (with for example line renderer)
and then allow the user to try drawing a similar (not necessarily exactly the same) line and the code needs to know according to the setting if the line is similar enough or not, but I can't figure it.
I would appreciate any tips.
public class DrawLine : MonoBehaviour
{
public GameObject linePrefab;
public GameObject currentLine;
public LineRenderer lineRenderer;
public EdgeCollider2D edgeCollider;
public List<Vector2> fingerPositions;
public Button[] answers;
public bool isCurrButtonActive;
int mouseButtonState = 0;
void Update()
{
Debug.Log(rfgrhe);
if (isCurrButtonActive)
{
if (Input.GetMouseButtonDown(0))
{
if (mouseButtonState == 0)
{
CreateLine();
}
}
if (Input.GetMouseButtonUp(0))
{
mouseButtonState++;
}
if (Input.GetMouseButton(0))
{
if (mouseButtonState == 1)
{
Debug.Log(Input.mousePosition.ToString());
if (Input.mousePosition.x < 100 || Input.mousePosition.y > 420 || Input.mousePosition.x > 660 || Input.mousePosition.y < 7)
{
return;
}
Vector2 tempFingerPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if (Vector2.Distance(tempFingerPos, fingerPositions[fingerPositions.Count - 1]) > .1f)
{
UpdateLine(tempFingerPos);
}
}
}
}
}
void CreateLine()
{
mouseButtonState++;
currentLine = Instantiate(linePrefab, Vector3.zero, Quaternion.identity);
lineRenderer = currentLine.GetComponent<LineRenderer>();
edgeCollider = currentLine.GetComponent<EdgeCollider2D>();
fingerPositions.Clear();
fingerPositions.Add(Camera.main.ScreenToWorldPoint(Input.mousePosition));
fingerPositions.Add(Camera.main.ScreenToWorldPoint(Input.mousePosition));
lineRenderer.SetPosition(0, fingerPositions[0]);
lineRenderer.SetPosition(1, fingerPositions[1]);
edgeCollider.points = fingerPositions.ToArray();
}
void UpdateLine(Vector2 newFingerPos)
{
fingerPositions.Add(newFingerPos);
lineRenderer.positionCount++;
lineRenderer.SetPosition(lineRenderer.positionCount - 1, newFingerPos);
edgeCollider.points = fingerPositions.ToArray();
}
public void ActivateCurrentButton()
{
// Debug.Log(isCurrButtonActive);
isCurrButtonActive = true;
for (int i = 0; i < answers.Length; i++)
{
if (answers[i].CompareTag("onePoint"))
{
answers[i].GetComponent<MapLvl>().isCurrButtonActive = false;
}
else if (answers[i].CompareTag("TwoPoints"))
{
answers[i].GetComponent<DrawLine>().isCurrButtonActive = false;
}
}
}
}
For example in that case, the blue line is the correct one, the green and the red ones are two options of an answer from the user.
What I want is that the program will acknolage only the green line as a correct answer.
EDIT: Since it's clearer now what we want, here's a way to achieve it:
The function float DifferenceBetweenLines(Vector3[], Vector3[]) below gives you a measure of the "distance between the two lines".
It walks along the line to match with a maximum step length, and for each point, computes the distance from the closest point on the draw line.
It sums the squares of those distances and divide them by the length of the line to match (don't ask me to explain this with mathematical rigor).
The smaller the return value, the closer the first line matches the second -- the threshold is yours to decide.
float DifferenceBetweenLines(Vector3[] drawn, Vector3[] toMatch) {
float sqrDistAcc = 0f;
float length = 0f;
Vector3 prevPoint = toMatch[0];
foreach (var toMatchPoint in WalkAlongLine(toMatch)) {
sqrDistAcc += SqrDistanceToLine(drawn, toMatchPoint);
length += Vector3.Distance(toMatchPoint, prevPoint);
prevPoint = toMatchPoint;
}
return sqrDistAcc / length;
}
/// <summary>
/// Move a point from the beginning of the line to its end using a maximum step, yielding the point at each step.
/// </summary>
IEnumerable<Vector3> WalkAlongLine(IEnumerable<Vector3> line, float maxStep = .1f) {
using (var lineEnum = line.GetEnumerator()) {
if (!lineEnum.MoveNext())
yield break;
var pos = lineEnum.Current;
while (lineEnum.MoveNext()) {
Debug.Log(lineEnum.Current);
var target = lineEnum.Current;
while (pos != target) {
yield return pos = Vector3.MoveTowards(pos, target, maxStep);
}
}
}
}
static float SqrDistanceToLine(Vector3[] line, Vector3 point) {
return ListSegments(line)
.Select(seg => SqrDistanceToSegment(seg.a, seg.b, point))
.Min();
}
static float SqrDistanceToSegment(Vector3 linePoint1, Vector3 linePoint2, Vector3 point) {
var projected = ProjectPointOnLineSegment(linePoint1, linePoint1, point);
return (projected - point).sqrMagnitude;
}
/// <summary>
/// Outputs each position of the line (but the last) and the consecutive one wrapped in a Segment.
/// Example: a, b, c, d --> (a, b), (b, c), (c, d)
/// </summary>
static IEnumerable<Segment> ListSegments(IEnumerable<Vector3> line) {
using (var pt1 = line.GetEnumerator())
using (var pt2 = line.GetEnumerator()) {
pt2.MoveNext();
while (pt2.MoveNext()) {
pt1.MoveNext();
yield return new Segment { a = pt1.Current, b = pt2.Current };
}
}
}
struct Segment {
public Vector3 a;
public Vector3 b;
}
//This function finds out on which side of a line segment the point is located.
//The point is assumed to be on a line created by linePoint1 and linePoint2. If the point is not on
//the line segment, project it on the line using ProjectPointOnLine() first.
//Returns 0 if point is on the line segment.
//Returns 1 if point is outside of the line segment and located on the side of linePoint1.
//Returns 2 if point is outside of the line segment and located on the side of linePoint2.
static int PointOnWhichSideOfLineSegment(Vector3 linePoint1, Vector3 linePoint2, Vector3 point){
Vector3 lineVec = linePoint2 - linePoint1;
Vector3 pointVec = point - linePoint1;
if (Vector3.Dot(pointVec, lineVec) > 0) {
return pointVec.magnitude <= lineVec.magnitude ? 0 : 2;
} else {
return 1;
}
}
//This function returns a point which is a projection from a point to a line.
//The line is regarded infinite. If the line is finite, use ProjectPointOnLineSegment() instead.
static Vector3 ProjectPointOnLine(Vector3 linePoint, Vector3 lineVec, Vector3 point){
//get vector from point on line to point in space
Vector3 linePointToPoint = point - linePoint;
float t = Vector3.Dot(linePointToPoint, lineVec);
return linePoint + lineVec * t;
}
//This function returns a point which is a projection from a point to a line segment.
//If the projected point lies outside of the line segment, the projected point will
//be clamped to the appropriate line edge.
//If the line is infinite instead of a segment, use ProjectPointOnLine() instead.
static Vector3 ProjectPointOnLineSegment(Vector3 linePoint1, Vector3 linePoint2, Vector3 point){
Vector3 vector = linePoint2 - linePoint1;
Vector3 projectedPoint = ProjectPointOnLine(linePoint1, vector.normalized, point);
switch (PointOnWhichSideOfLineSegment(linePoint1, linePoint2, projectedPoint)) {
case 0:
return projectedPoint;
case 1:
return linePoint1;
case 2:
return linePoint2;
default:
//output is invalid
return Vector3.zero;
}
}
The math functions at the end are from 3d Math Functions - Unify Community Wiki
Here is how it can be used to compare a LineRenderer against another:
Array.Resize(ref lineBuffer1, lineRenderer1.positionCount);
Array.Resize(ref lineBuffer2, lineRenderer2.positionCount);
lineRenderer1.GetPositions(lineBuffer1);
lineRenderer2.GetPositions(lineBuffer2);
float diff = DifferenceBetweenLines(lineBuffer1, lineBuffer2);
const float threshold = 5f;
Debug.Log(diff < threshold ? "Pretty close!" : "Not that close...");
A few things to consider:
The performance of SqrDistanceToLine could definitely be improved on
You get a measure of how close the first line matches the second, not the other way around -- that is, the first line can be longer or go for a walk mid-way as long as it comes back on track and "covers" the other line closely enough. You can solve this by calling DifferenceBetweenLines a second time, swapping the arguments, and taking the biggest result of them two.
We could work with Vector2 instead of Vector3
Original answer:
Similar?
As #Jonathan pointed out, you need to be more precise about "similar enough":
does similarity in size matter ?
does orientation matter ?
do similarity in proportions matter (or only the "changes of direction" of the line) ?
...
As you might guess, the fewer of those criteria matter, the harder it will be; because
your concept of similarity will become more and more abstract from the raw positions
you've got in the first place.
For example, if the user needs to draw a cross, with exactly two strokes,
that cover more or less a defined area, the task is as easy as it gets:
You can measure the distance between the area's corners and each stroke's first
and last points, and check that the lines are kind of straight.
If you want to check if the user drew a perfect heart-shape, in any orientation,
it's noticeably trickier...
You might have to resort to a specialized library for that.
Another thing to consider is, does the user really need to make a line similar to another one,
or should it only be close enough that it can be differentiated from other possible lines?
Consider this example:
The user needs to draw either a cross (X) or a circle (O):
If there is only one stroke that comes back close to the starting point, assume a circle.
If there is strokes whose general directions are orthogonal, assume a cross.
In this case, a more involved system would probably be overkill.
A few "raw pointers"
Assuming simple requirements (because assuming the opposite, I wouldn't be able to help much),
here are a few elements:
Exact match
The user has to draw on top of a visible line: this is the easiest scenario.
For each point of his line, find out the distance from the closest point on the reference line.
Sum the square of those distances -- for some reason it works better than summing the distances
themselves, and it's also cheaper to compute the square distance directly.
LineRenderer.Simplify
Very specific to your use-case, since you're using Unity's LineRenderer,
it's worth knowing that it packs a Simplify(float) method, that decreases the
resolution of your curve, making it easier to process, and particularly effective
if the line to match is made of somewhat straight segments (not complex curves).
Use the angles
Sometimes you'll want to check the angles between the different sub-segments of your line,
instead of their (relative) lengths. It will measure changes in direction regardless
of the proportions, which can be more intuitive.
Example
A simple example that detects equilateral triangles:
LineRenderer.Simplify
close the shape if the ends are close enough
check the angles are ~60deg each:
For arbitrary lines, you could run the line to match through the same "filter" as the lines the user draws, and compare the values. It will be yours to decide what properties matter most (angles/distances/proportions...), and what's the threshold.
Personally I would take points along the users line and then figure out the angles on the lines and if the average angle is within a specific range then it is acceptable. If the points you draw angles from are close enough together then you should have a pretty accurate idea whether the user is close to the same line.
Also, if the line needs to be in a particular area then you can just check and make sure the line is within a specified distance of the "control" line. The math for these should be pretty simple once you have the points. I am sure there are many other ways to implement this, but I personally would do this. Hope this helps!
I have this code which I basically got help from this forum from a really old post but I have a question in regard to how exactly does it work. There is a part in the code which we declare a float as Mathf.Infinity and we check to see if the distance between our source and all the objects (for loop) is less than that float then we return that object, but how does that really specify that it's the closest target? (dSqrToTarget < closestDistanceSqr)?
public GameObject GetClosestEnemy(List<GameObject> enemies, Transform fromThis)
{
if (enemiesList == null) return null;
GameObject bestTarget = null;
float closestDistanceSqr = Mathf.Infinity;
Vector3 currentPosition = fromThis.position;
foreach (GameObject potentialTarget in enemies)
{
Vector3 directionToTarget = potentialTarget.transform.position - currentPosition;
float dSqrToTarget = directionToTarget.sqrMagnitude;
if (dSqrToTarget < closestDistanceSqr )
{
closestDistanceSqr = dSqrToTarget;
bestTarget = potentialTarget;
}
}
return bestTarget;
}
The use of Mathf.Infinity is simply to initialize the variable to some invalid starting value that will be greater than any distance you measure between actual objects. If it was a reference type, this would be the equivalent of a null in this context.
Vector3 directionToTarget = potentialTarget.transform.position - currentPosition;
float dSqrToTarget = directionToTarget.sqrMagnitude;
This part here measure the distance between two objects in 3D space using basic vector math.
if (dSqrToTarget < closestDistanceSqr )
{
closestDistanceSqr = dSqrToTarget;
bestTarget = potentialTarget;
}
Compares that computed distance to the current "closest" object. Since the initial value is "invalid", the first object will always be deemed as a "potentially closest" and stored. Subsequent objects in the loop will continue to check compare. If they are closer, then they are stored in the bestTarget value. If not, then the loop continues until no more objects are left to check.
After the loop ends, the value of bestTarget is returned, as it held the minimum distance of anything found within the loop.
I've been programming an ability for a Hack n Slash which needs to check Units within a pie slice (or inbetween two angles with max length). But I'm stuck on how to check whether an unit is within the arc.
Scenario (Not enough, rep for an image sorry im new)
I currently use Physics2D.OverlapSphere() to get all of the objects within the maximum range. I then loop through all of the found objects to see whether they are within the two angles I specify. Yet this has janky results, probably because angles don't like negative values and value above 360.
How could I make this work or is there a better way to do this?
I probably need to change the way I check whether the angle is within the bounds.
Thanks in advance guys! I might respond with some delay as I won't be at my laptop for a couple hours.
Here is the code snippet:
public static List<EntityBase> GetEntitiesInArc(Vector2 startPosition, float angle, float angularWidth, float radius)
{
var colliders = Physics2D.OverlapCircleAll(startPosition, radius, 1 << LayerMask.NameToLayer("Entity"));
var targetList = new List<EntityBase>();
var left = angle - angularWidth / 2f;
var right = angle + angularWidth / 2f;
foreach (var possibleTarget in colliders)
{
if (possibleTarget.GetComponent<EntityBase>())
{
var possibleTargetAngle = Vector2.Angle(startPosition, possibleTarget.transform.position);
if (possibleTargetAngle >= left && possibleTargetAngle <= right)
{
targetList.Add(possibleTarget.GetComponent<EntityBase>());
}
}
}
return targetList;
}
Vector2.Angle(startPosition, possibleTarget.transform.position);
This is wrong. Imagine a line from the scene origin (0,0) to startPosition and a line to the transform.position. Vector2.Angle is giving you the angle between those two lines, which is not what you want to measure.
What you actually want is to give GetEntitiesInArc a forward vector then get the vector from the origin to the target position (var directionToTarget = startPosition - possibleTarget.transform.position) and measure Vector2.Angle(forward, directionToTarget).
I'm working on an isometric game (diamond grid) and I've stumbled across a minor problem regarding a character movement.
I'm using A* to find a path between 2 points and then I want to move my character from point A to point B going through all the tiles which form the path but I can't find a way to do this , I mean a simpler and accurate method.
So far I've scrapped this piece of code but it's kinda "rusty"
public void Destination(tile destination)
{
for (int i = 0; i < 8; i++)
{
if (AdjacentTile[i] == destination)
{
characterDirection = i;
}
}
animation.changeSpriteDirection(characterDirection); //After I found which adjacent tile is the next destination I change the character direction based on it's position (1 = North , 2 = Nort Est etc) .. so the Y of the Animation_sourceRectangle it's changed//
Vector2 Position;
Position.X = current_characterTile.X - destination.X;
Position.Y = current_characterTile.Y - destination.Y;
rotation = (float)Math.Atan2(-Position.X, Position.Y);
moveVector = (Vector2.Transform(new Vector2(0, -1), Matrix.CreateRotationZ(rotation))) * characterSpeed;
movingCommand = 1; // the character is supposed to be moving..
Move(); //this function moves the sprite until the *tile.i and tile.j* of the character is the same as tile.j and tile.i of the destination
//something like this
if ( characterTile.i == destination.i && characterTile.j == destination.j)
movingCommand = 0 //stop
else
character_Position += moveVector;
}
If anyone could give me a hint on what to do or help me I'll be very grateful.
Thank You.
Possibilities:
At each tile, determine the character's speed vector and also determine how much time it will take for the character to move to next tile. When that time elapses, immediately begin moving to the next tile. (This is what I implemented below.)
At each tile, determine the character's speed vector. Then, when the character is sufficiently close to the next tile (say, the difference between their X and Y coordinates is less than 2 pixels?), snap it to the tile and begin moving to the next tile. This will causes artifacts and be in general less precise.
A solution:
Let's assume you already ran your pathfinding algorithm and found a linked list of a tiles that you must go through to arrive at target. Let's also assume those tiles cannot become blocked partway through the movement (it is simple to modify the algorithm if they can, though).
I usually do something like this to handle this problem:
Run the pathfinding algorithm, which returns a List, if a path
exists.
character.Path = theListThatAStarReturned;
character.beginMovingToTarget(character.Path[0]);
character.Path.RemoveAt(0);
The beginMovingToTarget() method will determine the velocity vector and also determine the the time needed to arrive at the tile. When the time is reached, we immediately go to the next tile, until the Path is empty. Let's call this time variable character.timeToArrival.
Update():
if (!character.Moving) return; // Or just don't execute the rest of this code.
character.position += character.speed * elapsedSeconds;
character.timeToArrival -= elapsedSeconds;
// Did the character arrive in a tile?
if (character.timeToArrival <= 0)
{
// This will ensure the character is precisely in the tile, not a few pixels veered off.
character.position = character.movingToTile.position;
if (character.Path.Count == 0)
{
character.Moving = false;
// We are at final destination.
}
else
{
character.beginMovingToTarget(character.Path[0]);
character.Path.RemoveAt(0);
}
}
And the beginMovingToTarget(targetTile) function:
this.movingToTile = targetTile;
Vector2 direction;
direction = targetTile.position - this.position;
this.timeToArrival = direction.Length() / this.speedPerSeconds;
direction.Normalize();
direction *= this.speedPerSeconds;
this.speed = direction;
// Here, you may also want to change the character's animation, if you want to, or you may do that directly in the Draw() method based on its speed vector.
Make sure the division is in floats, not integers.