Hello everyone. I’m trying to make a D&D mechanic, “free attack”. When a character leaves the zone of defeat, then a blow is carried out on him. I was able to come up with a way through mathematics. But I don’t know math. Is there a way to do it differently?
I’ve seen on the Internet looking for intersection points in other programming languages and using mathematical terms. I have a path which is in the Vector 3 list. The sphere has a center point and radius. Probably you need to do through the loop, check each segment of the path.
I suppose if you could write good AI code for a Game Master that can use a ruler then you could avoid any notion of math and he would just lay the ruler across the circle.
Otherwise I don’t know how to respond. It’s a math question so it has a math solution, and all of those solutions can trivially be found online.
It’s all mathematics. Maybe there are ready-made solutions in C sharp, libraries, functions? I really don’t feel like coding for several days what they did before me many times. Another question is optimization, there can be more than a dozen such circles. How will it work? The original works great. I saw formulas for a ray, I have a segment. Do I need to convert a segment into a ray? Or is it better to find a mathematical forum?
Nothing is clear, but very interesting.
It’s easier for me. My fight is in turn-based mode. All calculations are made when the character is standing still, and when the character does something, it is impossible to change something, just like in the original.
That is, I work with a path that consists of segments. In general, I will look for the necessary formulas and implement them in C Sharp.
I rarely do such math (it’s easily found online as Kurt said), but this solution was entirely made by me.
It will return points of intersection between a ray (defined by point and direction) and a sphere (defined by center and radius). It will return true if a contact exists, false if not. If there is contact, the sphere is pierced, yielding both the entry point and the exit point. It will also return the point on a sphere that is closest to the ray. This is useful when there is no contact, but it will work from the inside as well.
// using System;
// using UnityEngine;
static public bool IntersectRaySphere(
Vector3 ro, Vector3 rd, Vector3 center, float r,
out Vector3 nearest, out Vector3 entry, out Vector3 exit) {
bool isApproxZero(float v) => Math.Abs(v) < 1E-6f;
var o2c = center - ro;
var dot = Vector3.Dot(rd, o2c);
if(isApproxZero(dot)) dot = 0f;
nearest = entry = exit = new Vector3(float.NaN, float.NaN, float.NaN);
var flag = false;
if(dot >= 0f) {
var proj = ro + dot * rd;
var c2p = proj - center;
var o2p = proj - ro;
nearest = center + r * c2p.normalized;
var p2n = nearest - proj;
var contact = Vector3.Dot(p2n, c2p);
if(isApproxZero(contact)) contact = 0f;
if(contact >= 0f) {
var h = p2n.magnitude;
if(contact == 0f && isApproxZero(h)) h = 0f;
float a = (h > 0f)? MathF.Sqrt(-h * (h - 2f * r)) : r;
o2p.Normalize();
var i1 = proj - a * o2p;
var i2 = proj + a * o2p;
if((ro - i1).sqrMagnitude < (ro - i2).sqrMagnitude) {
entryPoint = i1; exitPoint = i2;
} else {
entryPoint = i2; exitPoint = i1;
}
flag = true;
}
}
return flag;
}
There are 3 possible outcomes:
two intersection points (entry/exit)
one intersection point (the ray is a tangent to the sphere)
3a) no intersection points due to ray being cast in the general direction but missing the sphere
3b) no intersection points due to ray being cast in the wrong general direction
It’s been a while since I wrote this, and I can kind of see what to do to make it work with a line segment, but I will probably break it and I don’t have much time atm. So the best solution would be to adapt it to your needs, by simply testing both directions.
You can also change it to use Ray as an argument, instead of ro (ray origin) and rd (ray direction).
Finally, because the segment is of finite length unlike the ray which is infinite in one direction, simply check whether the distance from the origin to the entry point is greater than the length of the segment.
So something like this
public bool IntersectSegmentSphere(Vector3 p1, Vector3 p2, Vector3 center, float r, out Vector3 hitPoint) {
var delta = p2 - p1;
var sqlen = delta.sqrMagnitude;
var dir = delta / MathF.Sqrt(sqlen);
if(IntersectRaySphere(p1, dir, center, r, out _, out hitPoint, out _))
return testDist(p1, hitPoint, sqlen);
if(IntersectRaySphere(p2, -dir, center, r, out _, out hitPoint, out _))
return testDist(p2, hitPoint, sqlen));
bool testDist(Vector3 p, Vector3 h, float l) => (p - h).sqrMagnitude <= l;
hitPoint = new Vector3(float.NaN, float.NaN, float.NaN);
return false;
}
The method itself is brutally cheap, performance-wise, don’t worry about doing it twice in the worst case.
edit: fixed some typos (had a lot of them lol)
edit2: if your segment pierces the sphere on both ends, you will only get one point with the latter method, so there are probably better ways to organize this.
Also judging from your video, are you sure you actually need a sphere?
Because for such zones you could’ve done a much simpler point-circle check.
public bool IsPointInCircle(Vector2 p, Vector2 center, float r)
=> (center - p).sqrMagnitude <= r * r;
You can project your rays to an arbitrary plane by using Vector3.ProjectOnPlane. Which is just a dot basically, but you can do it on the fly, like so
var ray = // used for the Raycast
var groundPoint = Vector3.ProjectOnPlane(ray.direction, Vector3.up) + new Vector3(0f, groundHeight, 0f);
var inside = isPointInCircle(xz(groundPoint), myCenter, myRadius);
Vector2 xz(Vector3 p) => new Vector2(p.x, p.z);
Though this would be a more robust solution
var ray = // used for the Raycast
new Plane(somePointOnGround, groundUpNormal).Raycast(ray, out var groundPoint);
...
Planes are incredibly light for computation and readily available. Also if you can get away with 2D tests, always consider a simpler solution first.
@orionsyndrome Your code is incredibly complex. I see some language construction for the first time. According to wikipedia, there is only one solution, so I think it’s written the same way.
Here is the modified code with visualization.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using System;
public class SferaProb : MonoBehaviour
{
public static Vector3 sferaPoz;
public static float sferaRadius;
public static Vector3 tochkaX = new Vector3();
public static List<GameObject> UnitStat =new List<GameObject>();
Vector3 GoroPos = new Vector3();
Vector3 SashaPos = new Vector3();
Vector3 tochka1 = new Vector3();
Vector3 tochka2 = new Vector3();
void Start()
{
UnitStat = GameObject.FindGameObjectsWithTag("Unit").ToList();
for (int i=0; i<UnitStat.Count; i++)
{
if(UnitStat[i].GetComponent<Goro>())
{
GoroPos = UnitStat[i].transform.position;
}
else if(UnitStat[i].GetComponent<Sasha>())
{
SashaPos = UnitStat[i].transform.position;
}
}
sferaPoz = transform.position;
sferaRadius = transform.localScale.z/2;
// float rastOtCentra = (float)Math.Pow((GoroPos.x - sferaPoz.x), 2)+ (float)Math.Pow((GoroPos.y - sferaPoz.y), 2)+ (float)Math.Pow((GoroPos.z - sferaPoz.z), 2);
// float rastOtCentra2 = (float)Math.Pow((SashaPos.x - sferaPoz.x), 2) + (float)Math.Pow((SashaPos.y - sferaPoz.y), 2) + (float)Math.Pow((SashaPos.z - sferaPoz.z), 2);
//if (rastOtCentra<= sferaRadius* sferaRadius)
//{
// print("Goro in radius.");
// if (rastOtCentra2 <= sferaRadius * sferaRadius) print("Goro did not leave the zone.");
// else print("Goro left the zone.");
//}
//else
//{
var heading = SashaPos - GoroPos;
float A = heading.x * heading.x + heading.y * heading.y + heading.z * heading.z;
print(A);
float B =2*(GoroPos.x*heading.x+GoroPos.y*heading.y+GoroPos.z*heading.z-sferaPoz.x*heading.x-sferaPoz.y*heading.y-sferaPoz.z*heading.z) ;
float C=GoroPos.x*GoroPos.x-2*GoroPos.x*sferaPoz.x+sferaPoz.x*sferaPoz.x+GoroPos.y*GoroPos.y-2*GoroPos.y*sferaPoz.y+sferaPoz.y*sferaPoz.y+
GoroPos.z * GoroPos.z-2*GoroPos.z*sferaPoz.z+sferaPoz.z*sferaPoz.z-sferaRadius*sferaRadius;
float D = B * B - 4 * A * C;
float prom =(float)( Math.Sqrt(D));
float t1 = (float)((-B - prom) / (2.0 * A));
float t2 = (float) ((-B + prom) / (2.0 * A));
tochka1 = new Vector3(GoroPos.x * (1 - t1) + t1 * SashaPos.x, GoroPos.y * (1 - t1) + t1 * SashaPos.y, GoroPos.z * (1 - t1) + t1 * SashaPos.z);
tochka2 = new Vector3(GoroPos.x * (1 - t2) + t2 * SashaPos.x, GoroPos.y * (1 - t2) + t2 * SashaPos.y, GoroPos.z * (1 - t2) + t2 * SashaPos.z);
if (D < 0) print("does not cross");
else if ((t1 > 1 || t1 < 0) && (t2 > 1 || t2 < 0)) print("does not cross");
else if (!(t1 > 1 || t1 < 0) && (t2 > 1 || t2 < 0))
{
print(tochka1);
Instantiate(KeshPriStart.AtStStat, tochka1, Quaternion.Euler(45, 45, 0));
}
else if ((t1 > 1 || t1 < 0) && !(t2 > 1 || t2 < 0))
{
print(tochka2);
Instantiate(KeshPriStart.AtStStat, tochka2, Quaternion.Euler(45, 45, 0));
}
else if (D == 0)
{
print(tochka1);
Instantiate(KeshPriStart.AtStStat, tochka1, Quaternion.Euler(45, 45, 0));
}
else
{
print(tochka1);
print(tochka2);
Instantiate(KeshPriStart.AtStStat, tochka1, Quaternion.Euler(45, 45, 0));
Instantiate(KeshPriStart.AtStStat, tochka2, Quaternion.Euler(45, 45, 0));
}
// }
}
}
Here is an implementation video. So far I don’t see any errors.
Fair enough, but I can assure you nothing is particularly complex with that code. It just looks like that for the untrained eye. It’s just plain linear algebra with a very clean geometric setup.
There is no need for a mathematical proof for this one. It’s as obvious as day and night that a ray (or a segment) would give an entry and an exit point. Segment is just of finite length, but it can still be longer than the sphere’s diameter, and if so, which intersection point should win?
Here’s a slightly better solution for segment-sphere intersection
public bool IntersectSegmentSphere(Vector3 p1, Vector3 p2, Vector3 center, float r, out Vector3 hit1, out Vector3 hit2) {
var delta = p2 - p1;
var midp = (p1 + p2) / 2f;
var sqlenh = delta.sqrMagnitude * .5f;
var dir = delta.normalized;
// underscore character simply means we don't care for this result
if(!(IntersectRaySphere(midp, dir, center, r, out _, out hit1, out _) && testDist(midp, hit1, sqlenh))
hit1 = new Vector3(float.NaN, float.NaN, float.NaN);
if(!(IntersectRaySphere(midp, -dir, center, r, out _, out hit2, out _) && testDist(midp, hit2, sqlenh))
hit2 = new Vector3(float.NaN, float.NaN, float.NaN);
// just a local function embedded in this method
bool testDist(Vector3 p, Vector3 h, float l) => (p - h).sqrMagnitude <= l;
return !(float.IsNaN(hit1.x) && float.IsNaN(hit2.x));
}
edit: nope, I made an error (it’s fixed), it’s even simpler – I should test this anyway.
There the formula from Wikipedia is transferred to the code.I do not know English, for me all the variables look like this, but this is not the difficulty. The complexity is in the abbreviations. =>, ?, :
I don’t know what that means and I don’t even want to learn. I have Windows 7 and C# 8 is only supported there and many new language constructs do not work. I use only the simplest constructions, because I learned from YouTube video “C# in an hour”. That’s what they managed to tell there in an hour, and I know. This is enough for Unity programming. Local functions are tricky for me too.
I looked for a long time and understood. You there somehow replaced the extraction of the square root with a coefficient! This is true?
Can you do this in my code? I find it easier to work with my code than with yours.
Okay, fair point. Believe me, it’s not hard, it’s just you’re not accustomed to certain symbols.
Every code looks, well, like code. This is why it’s called like that. That’s always the hard part in the beginning.
Why would you think like that? How are you supposed to comprehend anything with that attitude?
And if you don’t want to comprehend anything, what do you hope to achieve? You can’t copy/paste forever.
In software in general, and in game development especially, there is always a certain hard barrier where things become VERY HARD to accomplish if you don’t know what you’re doing. I’ve been doing it for tens of years now, and it’s a constant fight onward.
Now the greatest fallacy I see in your attitude is the outright dismissal based on fears of unknown. “I don’t know what that means and I don’t even want to learn.” You say that with such conviction, yet the symbols that frightened you are among the simplest – ironically you already know what they do, just in a different form. People who react like that rarely achieve anything in life, and if they do, it’s probably something to the detriment of humanity. That’s just statistics, take it or leave it.
For starters, learn the language you’re trying to use. I don’t understand how do you imagine to use anything if you don’t know how it’s supposed to be used. Start from the source of it, here.
Regarding the exact symbols => and ? :, these are C# operators, just like + or *.
?: is known as a conditional operator. It is ternary, meaning it accepts three operands (unlike binary operators such as +), which makes it slightly weird. However it’s so ubiquitous that nearly every high-level language on the planet has it. Why?
Because it’s just an IF expression. Notice the word expression, it’s not the same as a statement.
This would be a statement IF
if(test) {
// do
} else {
// don't
}
this would be an expression IF
some_result = test? do : don't;
The main difference is not only syntactic, but assumes that all conditions evaluate logical truths (hence must be of type boolean). While statement IF would evaluate the condition ‘test’, then throw the result away, then proceed with evaluating the corresponding statement (or block), expressions cannot work like that. Namely they cannot stand on their own, rather they need to be part of some other statement. What’s the statement in the 2nd example? The assignment itself.
The assignment (= operator) is a valid form of a statement, by the rules of C#. So we assign the evaluation of the whole sausage on the right to this variable ‘some_result’. What would be the type of such evaluation? Well it depends on the types you used for the latter two ?: operands.
For example
var myFavoriteColor = colorBlindness? Color.blue : Color.red;
Can you deduce what this does?
Some people do their whitespace differently than I do. I like to pose a question, but you’ll commonly see it like A ? B : C, or things shifted in new lines, in some languages you can omit the third operand and so on.
=> is known as lambda operator. But I do not use it for lambdas in the code above. In my context it’s a simple separator (the last description in the first sentence of that link). I think it was introduced at the same time as LINQ, but the features surrounding it have been in development for a long time, up to C# 6 if I recall correctly, which only spiced things up, and did nothing major in its design.
So if it’s a separator, what does it separate? It denotes that the code right of it is to be considered an expression body. Expression bodies are powerful C# constructs that look and feel like any other code, but with some key differences – they are not treated as statements, but are parsed as expressions that can stand on their own. Expression bodies are compiled slightly differently, because their intent is upfront and the stack is thus organized better, allowing for better performance and more readable design, to cut the long story short.
From the perspective of a coder, these two things are pretty much functionally equivalent
string myFunction(float x) {
if(x < 0.5f) {
x = x / 2.0f;
} else {
x = x + 1.0f;
}
int a = (x - x % 1f) + 1;
return "result: " + a.ToString("F3");
}
string myFunction(float x) => $"result: {1 + Mathf.FloorToInt((x < .5f)? x / 2f : x + 1f):F3}";
But they massively differ in their intent and code real-estate, and even somewhat in performance. (This example is deliberately convoluted, it looks cryptic regardless.) Btw the syntactic construct $“” is called string interpolation. It is available since C# 6.
Notice how you can’t do this for everything. Statement blocks cannot be part of expression bodies. The only statements allowed in them are assignments and throws.
Another thing that’s true for all expressions and expression bodies: they must deliver something of value. You can’t just do something like you would with a statement. Expressions always evaluate to a value which must be returned.
This is for example an invalid expression body
void justDo() => _val + 5;
Like you wouldn’t type a blank expression in the middle of code
Knowing this, you can begin to read => as “returns”.
Everything I wrote here is compatible with C# 7. This is the version that enabled local functions.
Why are local functions tricky? What makes them any different from any other function?
Do you mean this?
var delta = p2 - p1;
var sqlen = delta.sqrMagnitude;
var dir = delta / MathF.Sqrt(sqlen);
You need the basics of linear algebra to understand this, but in as few words as possible, in vector geometry we differentiate points, arbitrary vectors, unit vectors, and directions, all of which can be represented by types Vector2 or 3, depending on the number of dimensions.
Points are points, and vectors are imaginary arrows in space which also have length. When this length is exactly 1, these are unit vectors, some of which are used as directions. All directions, in fact, are unit vectors. We use them as yard sticks to measure some distance in some arbitrary direction in space.
That said, when you call magnitude on a vector, you extract its length. Similarly when you call sqrMagnitude, you extract its length but squared (meaning it’s value is length * length). To understand why would anyone use this, you must understand how this actually works. Under the hub, Unity (and everyone else) has to compute a Pythagoras theorem to find length of a vector. The formula is the elementary school one
length = sqrt(x^2 + y^2) // in 2D
length = sqrt(x^2 + y^2 + z^2) // in 3D
And this is what magnitude does. Unlike adding and multiplying, finding a square root is an expensive operation. By invoking sqrMagnitude you avoid having to do it, because this is what it does
sqrLength = x^2 + y^2 // in 2D
sqrLength = x^2 + y^2 + z^2 // in 3D
Now consider you had a vector that had a length of 5. It has a direction you need, but you really don’t like it’s length, you would like it to be a direction vector instead, which means it must be of unit size. To cope with this, you simply divide the vector with its length.
var myVector = new Vector2(4f, 3f);
Debug.Log(myVector.magnitude); // says 5
var myDirection = myVector / 5f;
Debug.Log(myDirection.magnitude); // says 1
// now I can make my vector as long as I want
var myNewVector = 16.2f * myDirection;
Debug.Log(myNewVector.magnitude); // says 16.2
This operation is known as normalization.
var myDirection = myVector / myVector.magnitude; // in general
// is same as
var myDirection = myVector.normalized; // much more readable, no?
So what does normalization do?
normal = vector / sqrt(x^2 + y^2) // in 2D
normal = vector / sqrt(x^2 + y^2 + z^2) // in 3D
If you already have a magnitude it makes sense to do this on your own, to avoid having to do the same thing all over again. Btw, just like all directions are units, all normals are directions. Hence the word, normalization.
Let’s return to my code
var delta = p2 - p1; // we take a vector between two points by subtracting them
var sqlen = delta.sqrMagnitude; // we get a squared sum of the difference (x^2 + y^2 + z^2)
var dir = delta.normalized; // we divide difference with its magnitude
Let’s translate this (pseudo-code)
var delta = p2 - p1;
var sqlen = x^2 + y^2 + z^2;
var dir = delta / sqrt(x^2 + y^2 + z^2);
What I did instead is this
var delta = p2 - p1;
var sqlen = x^2 + y^2 + z^2;
var dir = delta / sqrt(sqlen);
In post #15 I abandon the idea however, because I’ve halved the sqlen result
var sqlenh = delta.sqrMagnitude * 0.5f;
var dir = delta.normalized;
Translates into
var sqlenh = (x^2 + y^2 + z^2) / 2f;
var dir = delta / sqrt(x^2 + y^2 + z^2);
I could’ve written it as
var sqlenh = delta.sqrMagnitude * 0.5f;
var dir = delta / sqrt(sqlenh * 2f);
But I’ve decided against it, because it would look even more cryptic.
Btw why am I sticking ‘f’ to every literal number I use? Because I want C# to know that these numbers are of single-precision floating-point type. If I don’t do it, there is a possibility C# will interpret my number as an integer, and certain operations might react differently to such numbers and give me bad results.
For example
var myNumber = 11;
var myResult = myNumber / 2;
Debug.Log(myResult);
What do you think the result should look like?
If you’re thinking 5.5, you’re wrong.
@Kurt-Dekker , @orionsyndrome This approach is fundamentally wrong, our ancestors made masterpieces without using such operations. And I, like them, do this and copied the mechanics from the old masterpiece by about 80%. People learn the language for half a year and can’t do anything. There are high-skilled specialists, and there are low-skilled ones.
The former write engines, libraries, etc., while the latter use the results of the work of the former and do simpler work. We need both the first and the second.
My goal is to make a game. And games are not made by programmers, they generate code. Games are made primarily by screenwriters, and I consider myself one of them. I was not taken to the coolest studio in the world because of my zero reputation, although they liked my project (not this one) and therefore had to learn programming, etc. Everything is impossible to learn.
And there is! Now I looked closely, I realized that there the solution is divided into two functions. In one of which the square root is extracted. So these are two solutions, one and the same written in different ways. My problem is reduced to a quadratic equation, and without taking the root there is no normal way to solve it. There are, of course, methods of selection, decomposition, etc., but I have not seen how to do this in code.
I haven’t read any books and haven’t used templates. The architecture of the project, inventory, battle log and pathfinding for the squad are unique for me. I have not seen this on the Internet. “var” is better not to use, just because of such cases. Or is there still a way to solve this problem without taking the square root? I will have a lot of iteration in the frame with the extraction of the square root, I think about a hundred.