Simply put, I have:
A viewport rectangle where (0,0) is the bottom-left corner, (1,1) is the top-right and (0.5,0.5) is the centre of the screen.
A point (a,b) which is outside of the rectangle.
This is in viewport coordinates so +X is right, +Y is up on the screen.
And I need a function which takes these parameters and returns the point on the edge of the rectangle where the line (between the centre of the rectangle (0.5,0.5) and the point (a,b)) intersects it.
I know how to do this on paper with given coordinates but I just can't figure it out when it comes down to code. Also, I realise questions like this have been addressed in different threads - but I can't find a simple input to output function anywhere.
I'm doing this in the Unity3D engine so preferably in Javascript but any language or pseudocode would be a great help as I can probably hand convert it.
EDIT
To clarify, I'm looking for something like:
function IntersectFromViewportCenter(x : float, y : float) {
...
return Point(x1, y1);
}
Where (x,y) is the point outside of the circle and (x1,y1) is the intersection point.
Thanks
Shift all system to be centered at point (0,0). Calc intersection of ray from origin to (shifted) point (x',y') with box (-1,-1)-(1,1). Scale and shift back. I did not consider trivial case with point inside the box ( is it needed?)
x = x - 0.5
y = y - 0.5
if Abs(x) >= Abs(y) then //vertical box edge
y1 = y/x //care with case both y and x = 0
x1 = Sign(x) //+-1
else // horizontal edge
x1 = x/y
y1 = Sign(y)
x1 = 0.5*x1 + 0.5
y1 = 0.5*y1 + 0.5
Since some general Line/Rect approaches have been provided, here is an approach that is optimized to avoid Raycasts in trivial cases (completely inside or completely outside the Rect): https://gist.github.com/JohannesMP/50dad3175bf2925df508b642091e41c4
It also efficiently provides both the entry and exit point (necessary in my use case):
Here is a basic overview of this approach:
Divide the area around the rect into Sectors as follows (Where S4 is the rect itself):
S0| S1 |S2
--+----+-- ^
S3| S4 |S5 |
--+----+-- y
S6| S7 |S8 x-->
Given the sectors where the line segment begins and ends, we know what raycasts need to be performed (Ex: S0-S2 never needs to raycast, while S4-S1 only needs to raycast the top edge, etc)
Pre-compute this data and store it in a small 9x9 static array. We are basically using a bit of static memory to avoid conditional checks.
At runtime find the Sector that the start and end points of the line occupy, and use the result to Raycast only the necessary edges of the Rect.
Additionally you can simplify the raycast into one dimension if you handle vertical and horizontal lines separately.
In my personal use case (lots of line segments, where the vast majority are either completely inside or completely outside the Rect) this approach is faster than the general case, since Raycasts are only performed when necessary.
MBo has the right idea. Here's a way to implement in in Unity. I don't think UnityScript is worth using – in particular, it doesn't support extension methods – so you really should switch languages. (Also, Unity is actually not named Unity3D.)
This script can go anywhere in the Project:
using UnityEngine;
public static class UnityEngineExtensions {
public static Vector2 Abs(this Vector2 vector) {
for (int i = 0; i < 2; ++i) vector[i] = Mathf.Abs(vector[i]);
return vector;
}
public static Vector2 DividedBy(this Vector2 vector, Vector2 divisor) {
for (int i = 0; i < 2; ++i) vector[i] /= divisor[i];
return vector;
}
public static Vector2 Max(this Rect rect) {
return new Vector2(rect.xMax, rect.yMax);
}
public static Vector2 IntersectionWithRayFromCenter(this Rect rect, Vector2 pointOnRay) {
Vector2 pointOnRay_local = pointOnRay - rect.center;
Vector2 edgeToRayRatios = (rect.Max() - rect.center).DividedBy(pointOnRay_local.Abs());
return (edgeToRayRatios.x < edgeToRayRatios.y) ?
new Vector2(pointOnRay_local.x > 0 ? rect.xMax : rect.xMin,
pointOnRay_local.y * edgeToRayRatios.x + rect.center.y) :
new Vector2(pointOnRay_local.x * edgeToRayRatios.y + rect.center.x,
pointOnRay_local.y > 0 ? rect.yMax : rect.yMin);
}
}
Attach this other script to a Game Object, and set its variables in the Inspector.
#pragma warning disable 0649
using System;
using UnityEngine;
public class VisualizeRectIntersectionWithRayFromCenter : MonoBehaviour {
[SerializeField] Rect rect;
[SerializeField] Vector2 point;
[Serializable] class Colors {
public Color rect, point, intersection;
} [SerializeField] Colors colors;
void OnDrawGizmos() {
Gizmos.color = colors.rect;
Vector2[] corners = {new Vector2(rect.xMin, rect.yMin), new Vector2(rect.xMin, rect.yMax),
rect.Max(), new Vector2(rect.xMax, rect.yMin)};
int i = 0;
while (i < 3) Gizmos.DrawLine(corners[i], corners[++i]);
Gizmos.DrawLine(corners[3], corners[0]);
Gizmos.color = colors.point;
Gizmos.DrawLine(rect.center, point);
Gizmos.color = colors.intersection;
Gizmos.DrawLine(rect.center, rect.IntersectionWithRayFromCenter(pointOnRay: point));
}
}
bool LineRectIntersection(Vector2 lineStartPoint, Vector2 lineEndPoint, Rect rectangle, ref double resultX, ref double resultY)
{
Vector2 minXLinePoint = (lineStartPoint.x <= lineEndPoint.x) ? (lineStartPoint) : (lineEndPoint);
Vector2 maxXLinePoint = (lineStartPoint.x <= lineEndPoint.x) ? (lineEndPoint) : (lineStartPoint);
Vector2 minYLinePoint = (lineStartPoint.y <= lineEndPoint.y) ? (lineStartPoint) : (lineEndPoint);
Vector2 maxYLinePoint = (lineStartPoint.y <= lineEndPoint.y) ? (lineEndPoint) : (lineStartPoint);
double rectMaxX = rectangle.xMax;
double rectMinX = rectangle.xMin;
double rectMaxY = rectangle.yMax;
double rectMinY = rectangle.yMin;
if (minXLinePoint.x <= rectMaxX && rectMaxX <= maxXLinePoint.x)
{
double m = (maxXLinePoint.y - minXLinePoint.y) / (maxXLinePoint.x - minXLinePoint.x);
double intersectionY = ((rectMaxX - ((double)minXLinePoint.x)) * m) + ((double)minXLinePoint.y);
if(minYLinePoint.y <= intersectionY && intersectionY <= maxYLinePoint.y)
{
resultX = rectMaxX;
resultY = intersectionY;
return true;
}
}
if (minXLinePoint.x <= rectMinX && rectMinX <= maxXLinePoint.x)
{
double m = (maxXLinePoint.y - minXLinePoint.y) / (maxXLinePoint.x - minXLinePoint.x);
double intersectionY = ((rectMinX - ((double)minXLinePoint.x)) * m) + ((double)minXLinePoint.y);
if (minYLinePoint.y <= intersectionY && intersectionY <= maxYLinePoint.y)
{
resultX = rectMinX;
resultY = intersectionY;
return true;
}
}
if (minYLinePoint.y <= rectMaxY && rectMaxY <= maxYLinePoint.y)
{
double rm = (maxYLinePoint.x - minYLinePoint.x) / (maxYLinePoint.y - minYLinePoint.y);
double intersectionX = ((rectMaxY - ((double)minYLinePoint.y)) * rm) + ((double)minYLinePoint.x);
if (minXLinePoint.x <= intersectionX && intersectionX <= maxXLinePoint.x)
{
resultX = intersectionX;
resultY = rectMaxY;
return true;
}
}
if (minYLinePoint.y <= rectMinY && rectMinY <= maxYLinePoint.y)
{
double rm = (maxYLinePoint.x - minYLinePoint.x) / (maxYLinePoint.y - minYLinePoint.y);
double intersectionX = ((rectMinY - ((double)minYLinePoint.y)) * rm) + ((double)minYLinePoint.x);
if (minXLinePoint.x <= intersectionX && intersectionX <= maxXLinePoint.x)
{
resultX = intersectionX;
resultY = rectMinY;
return true;
}
}
return false;
}
#chakmeshma, your solution is almost correct, but you must also check whether the intersection point is within the rect to avoid border cases:
private static bool LineRectIntersection(Vector2 lineStartPoint, Vector2 lineEndPoint, Rect rectangle, ref Vector2 result)
{
Vector2 minXLinePoint = lineStartPoint.x <= lineEndPoint.x ? lineStartPoint : lineEndPoint;
Vector2 maxXLinePoint = lineStartPoint.x <= lineEndPoint.x ? lineEndPoint : lineStartPoint;
Vector2 minYLinePoint = lineStartPoint.y <= lineEndPoint.y ? lineStartPoint : lineEndPoint;
Vector2 maxYLinePoint = lineStartPoint.y <= lineEndPoint.y ? lineEndPoint : lineStartPoint;
double rectMaxX = rectangle.xMax;
double rectMinX = rectangle.xMin;
double rectMaxY = rectangle.yMax;
double rectMinY = rectangle.yMin;
if (minXLinePoint.x <= rectMaxX && rectMaxX <= maxXLinePoint.x)
{
double m = (maxXLinePoint.y - minXLinePoint.y) / (maxXLinePoint.x - minXLinePoint.x);
double intersectionY = ((rectMaxX - minXLinePoint.x) * m) + minXLinePoint.y;
if (minYLinePoint.y <= intersectionY && intersectionY <= maxYLinePoint.y
&& rectMinY <= intersectionY && intersectionY <= rectMaxY)
{
result = new Vector2((float)rectMaxX, (float)intersectionY);
return true;
}
}
if (minXLinePoint.x <= rectMinX && rectMinX <= maxXLinePoint.x)
{
double m = (maxXLinePoint.y - minXLinePoint.y) / (maxXLinePoint.x - minXLinePoint.x);
double intersectionY = ((rectMinX - minXLinePoint.x) * m) + minXLinePoint.y;
if (minYLinePoint.y <= intersectionY && intersectionY <= maxYLinePoint.y
&& rectMinY <= intersectionY && intersectionY <= rectMaxY)
{
result = new Vector2((float)rectMinX, (float)intersectionY);
return true;
}
}
if (minYLinePoint.y <= rectMaxY && rectMaxY <= maxYLinePoint.y)
{
double rm = (maxYLinePoint.x - minYLinePoint.x) / (maxYLinePoint.y - minYLinePoint.y);
double intersectionX = ((rectMaxY - minYLinePoint.y) * rm) + minYLinePoint.x;
if (minXLinePoint.x <= intersectionX && intersectionX <= maxXLinePoint.x
&& rectMinX <= intersectionX && intersectionX <= rectMaxX)
{
result = new Vector2((float)intersectionX, (float)rectMaxY);
return true;
}
}
if (minYLinePoint.y <= rectMinY && rectMinY <= maxYLinePoint.y)
{
double rm = (maxYLinePoint.x - minYLinePoint.x) / (maxYLinePoint.y - minYLinePoint.y);
double intersectionX = ((rectMinY - minYLinePoint.y) * rm) + minYLinePoint.x;
if (minXLinePoint.x <= intersectionX && intersectionX <= maxXLinePoint.x
&& rectMinX <= intersectionX && intersectionX <= rectMaxX)
{
result = new Vector2((float)intersectionX, (float)rectMinY);
return true;
}
}
return false;
}
}
Related
I am working in unity/C# and trying to make a function that keeps an character in certain x range (-3 to 3). Below isthe code I got to work. Is there a way to simplify it?
//function creation to limit movement in the x axis
float rangeBoundX(int upperBound, int lowerBound, Vector3 i, float horMoveSpe = 0)
{
//will change velocity to keep the x value in the desired range. - velocity to mvoe away from the upper bound and positive velocity goes away from the lowerBound.
if (i.x > upperBound)
{
horMoveSpe = -1;
}
else if (i.x < lowerBound)
{
horMoveSpe = 1;
}
return horMoveSpe;
}
'private void FixedUpdate()'
{
Vector3 enemyforwardMove = transform.forward * enemySpeed * Time.fixedDeltaTime;
Vector3 horizontalMove = transform.position;
magn = rangeBoundX(3, -3, horizontalMove, magn);
horizontalMove = transform.right * magn * freq;
enemyRB.MovePosition(enemyRB.position + enemyforwardMove + horizontalMove);
}
Yes you can use Math.Clamp to bound any value Like this.
void update(){
float xPos = Mathf.Clamp(transform.position.x, -3, 3);
transform.position = new Vector3(xPos, transform.position.y, transform.position.z);
}
using this code you can bound the position of object horizontaly.
Yes, there is a way to simplify it. You can use Mathf.Clamp method https://docs.unity3d.com/ScriptReference/Mathf.Clamp.html
example usage: float xPos = Mathf.Clamp(xValue, xMin, xMax);
I am doing a Rubik cube generator with unity. Each of the pieces are basically a 1x1 cube which will be repeated in the shape of a bigger cube in my code as children of an empty object. The empty object is in the exact middle of the pieces, and all the pieces have their origins in the exact middle. However, when I put the empty to the center of the scene (0, 0, 0) It shows up in a different place.
Here are some pictures from the editor:
As you can see, the empty is in the center with coordinates set to 0, 0, 0
Now ,when it has children and the coordinates are all still 0, it shows in a different place
Edit:
#derHugo helped me out, but now my code that creates the cubes and sets the empty object to the middle of them does not work.
Here is the full code:
public GameObject PiecePrefab;
public int CubeSize;
Vector3 avg;
Vector3 ijk;
int cubeCount = 0;
// Start is called before the first frame update
void Start()
{
//Vector3 orgpos = gameObject.transform.position;
if (CubeSize <= 0)
{
CubeSize = 1;
Debug.LogError("The cube can not be smaller than 1!");
}
else if (CubeSize > 30)
{
CubeSize = 30;
Debug.LogError("The cube should not be bigger than 30!");
}
avg = new Vector3(0, 0, 0);
for (float k = 0; k < CubeSize; k++)
{
for (float j = 0; j < CubeSize; j++)
{
for (float i = 0; i < CubeSize; i++)
{
if (i == CubeSize - 1 || i == 0)
{
CreatePiece(i, j, k);
}
else if (j == CubeSize - 1 || j == 0)
{
CreatePiece(i, j, k);
}
else if (k == CubeSize - 1 || k == 0)
{
CreatePiece(i, j, k);
}
}
}
}
avg /= cubeCount;
gameObject.transform.position = avg;
var _Go = GameObject.FindGameObjectsWithTag("KuutionPala");
foreach (GameObject KuutionPala in _Go)
{
KuutionPala.transform.SetParent(transform);
}
//gameObject.transform.localPosition = orgpos;
void CreatePiece(float x, float y, float z)
{
ijk = new Vector3(x, y, z);
avg += ijk;
cubeCount++;
Vector3 offset3D;
offset3D = new Vector3(x / CubeSize, y / CubeSize, z / CubeSize);
var Piece = Instantiate(PiecePrefab, offset3D, transform.rotation);
Piece.transform.localScale /= CubeSize;
//Debug.LogFormat("x:" + x);
//Debug.LogFormat("y:" + y);
//Debug.LogFormat("z:" + z);
}
}
}
I think the error is on this row:
gameObject.transform.position = avg;
(Sorry if bad code)
As said there are two pivot modes in Unity (see Positioning GameObjects → Gizmo handle position toggles)
Pivot: positions the Gizmo at the actual pivot point of the GameObject, as defined by the Transform component.
Center: positions the Gizmo at a (geometrical) center position based on the selected GameObjects.
Yours is set to Center so in order to change that click on the button that says Center.
Then to your code
You are currently just hoping/assuming that your parent is correctly placed on 0,0,0.
Then you spawn all tiles in a range from 0 to (CubeSize - 1)/2 and then want to shift the center back.
I would rather go the other way round and calculate the correct local offset beforehand and directly spawn the tiles as children of the root with the correct offset. Into positive and negative direction.
Step 1: What is that local position?
For figuring the general maths out just look at two examples.
Let's say you have 3 cubes with indices 0,1,2. They have extends of 1/3 so actually there positions would need to look like
-0.5 0 0.5
| . | . | . |
Let's say you have 4 cubes with indices 0,1,2,3 and extends 1/4 then the positions would need to look like
-0.5 0 0.5
| . | . | . | . |
So as you can see the simplest way to go would be
start with the minimum position (e.g. -0.5f * Vector3.one)
always add half of the extends for the first offset (e.g. 1/CubeSize * 0.5f * Vector3.one)
add an offsets of the extends multiplied by the indices on top (e.g. 1/CubeSize * new Vector3(x,y,z))
so together something like
// be sure to cast to float here otherwise you get rounded ints
var extends = 1 / (float)CubeSize;
var offset = (-0.5f + extends * 0.5f) * Vector3.one + extends * new Vector3(x,y,z);
Step 2: Directly spawn as children with correct offset
void CreatePiece(float x, float y, float z)
{
var extends = 1 / (float)CubeSize;
var offset = (-0.5f + extends * 0.5f) * Vector3.one + extends * new Vector3(x,y,z);
var Piece = Instantiate(PiecePrefab, transform, false);
// This basically equals doing something like
//var Piece = Instantiate(PiecePrefab, transform.position, transform.rotation, transform);
Piece.transform.localPosition = offset;
Piece.transform.localScale = extends * Vector3.one;
}
Then you can reduce your code to
// Use a range so you directly clamp the value in the Inspector
[Range(1,30)]
public int CubeSize = 3;
// Start is called before the first frame update
void Start()
{
UpdateTiles();
}
// Using this you can already test the method without entering playmode
// via the context menu of the component
[ContextMenu(nameof(UpdateTiles)])
public void UpdateTiles()
{
// Destroy current children before spawning the new ones
foreach(var child in GetComponentsInChildren<Transform>().Where(child => child != transform)
{
if(!child) continue;
if(Application.isPlaying)
{
Destroy(child.gameObject);
}
else
{
DestroyImmediate(child.gameObject);
}
}
if (CubeSize < 1)
{
CubeSize = 1;
Debug.LogError("The cube can not be smaller than 1!");
}
else if (CubeSize > 30)
{
CubeSize = 30;
Debug.LogError("The cube should not be bigger than 30!");
}
// For making things easier to read I would use x,y,z here as well ;)
for (float x = 0; x < CubeSize; x++)
{
for (float y = 0; y < CubeSize; y++)
{
for (float z = 0; z < CubeSize; z++)
{
if (x == CubeSize - 1 || x == 0)
{
CreatePiece(x, y, z);
}
else if (y == CubeSize - 1 || y == 0)
{
CreatePiece(x, y, z);
}
else if (z == CubeSize - 1 || z == 0)
{
CreatePiece(x, y, z);
}
}
}
}
}
private void CreatePiece(float x, float y, float z)
{
var extends = 1 / (float)CubeSize;
var offset = (-0.5f + extends * 0.5f) * Vector3.one + extends * new Vector3(x,y,z);
var Piece = Instantiate(PiecePrefab, transform, false);
Piece.transform.localPosition = offset;
Piece.transform.localScale = extends * Vector3.one;
}
In my program, I want to have triangles that spin and follow your mouse position. I had it working but it was ugly because I didn't use any classes (I'm new) and just pasted the triangle over and changed the variables. This is the class I came up with.
class Enemy {
float x = random(-width, 0);
float y = random(0, height);
float x1;
float x2 = -20;
float x3 = 20;
float y1 = (+(sqrt(3)/3)*40);
float y2 = (-(sqrt(3)/3)*40);
float y3 = (-(sqrt(3)/3)*40);
float speed;
float slope;
float atanSlope;
Enemy(float tempSpeed) {
speed = tempSpeed;
}
void rotateEnemy() {
float x1Rotated = rotateX(x1, y1, theta2, 0);
y1 = rotateY(x1, y1, theta2, 0);
x1 = x1Rotated;
float x2Rotated = rotateX(x2, x2, theta2, 0);
x2 = rotateY(x2, x2, theta2, 0);
x2 = x2Rotated;
float x3Rotated = rotateX(x3, x3, theta2, 0);
x3 = rotateY(x3, x3, theta2, 0);
x3 = x3Rotated;
}
void move() {
slope = (y - mouseY)/(x-mouseX);
atanSlope = atan(slope);
if (slope < 0 && mouseY < y ) {
x += cos(atanSlope)*(speed + speedChange);
y += sin(atanSlope)*(speed + speedChange);
} else if (slope >= 0 && mouseY < y) {
x -= cos(atanSlope)*(speed + speedChange);
y -= sin(atanSlope)*(speed + speedChange);
} else if (slope > 0) {
x += cos(atanSlope)*(speed + speedChange);
y += sin(atanSlope)*(speed + speedChange);
} else {
x -= cos(atanSlope)*(speed + speedChange);
y -= sin(atanSlope)*(speed + speedChange);
}
}
void drawEnemy() {
translate(x, y);
triangle(x1, y1, x2, x2, x3, x3);
translate(-x, -y);
}
void collisionDetect() {
if (abs(mouseX-x) + abs(mouseY-y) < 80)
if (isDeadly) {
respawn();
energy -= height/16;
points += 500;
} else
energy = 0;
}
void respawn() {
int ranQuadrant1 = (int)random(0, 2);
int ranSide1 = (int)random(0, 2);
if (ranQuadrant1 == 0)
if (ranSide1 == 0)
x = random(0, -width/2);
else {
x = random(width, 3*width/2);
y = random(-height/2, 3*height/2);
} else
if (ranSide1 == 0)
y = random(0, -height/2);
else {
y = random(height, 3*height/2);
x = random(-width/2, 3*width/2);
}
}
}
And I use it like this
ArrayList<Enemy> enemies = new ArrayList<Enemy>();
void setup() {
for (i = 0; i<difficulty; i++);
enemies.add(new Enemy(i*5));
for (i = 0; i<enemies.size()-1; i++)
enemies.get(i).respawn();
}
void draw() {
for(i = enemies.size()-1; i>=0; i--) {
enemies.get(i).rotateEnemy();
enemies.get(i).move();
enemies.get(i).drawEnemy();
enemies.get(i).collisionDetect();
}
When I run it, the triangles don't draw. Not only that, some ellipses I wrote that come right after trying to draw the triangles don't draw either. The square that follows your mouse along with a timer and other things DO draw though. Please help. Thank you!
Now, this isn't the whole program. I'm making a game for a project and these triangles are the enemies.
If you want to see the whole program for context/if I didn't put enough, I put it in a pastebin: https://pastebin.com/Bfd4Fk6t
Okay I figured it out, turns out I'm a dummy.
Look at the rotateEnemy function for the 2nd and 3rd points. There are no y's only x's. I was using a lot of find and replace when copying it over so I must have gotten rid of the y's and replaced them with x's. Another error is in drawEnemy, I draw the triangle with parameters (x1,y1,x2,x2,x3,x3). No y's again. Geez I'm a smart guy lol.
I am working on creating an Ofscreen Enemy indicator using the tutorial mentioned on below link. However I can get the indicator to rotate to point to the enemy but the indicator does not move from end to end of screen.
http://gamedevelopment.tutsplus.com/tutorials/positioning-on-screen-indicators-to-point-to-off-screen-targets--gamedev-6644
This is the desired outcome:
Until now i have managed to figure out the below Please help.
var screenCenter:Vector3 = new Vector3(0.5, 0.5, 0f);
//Note coordinates are translated
//Make 00 the centre of the screen instead of bottom left
screenpos -= screenCenter;
//find angle from center of screen instead of bototom left
var angle:float = Mathf.Atan2(screenpos.y, screenpos.x);
angle -= 90 * Mathf.Deg2Rad;
var cos:float = Mathf.Cos(angle);
var sin:float = -Mathf.Cos(angle);
screenpos = screenCenter + new Vector3(sin*150, cos*150, 0);
//y=mx + b format
var m:float = cos/sin;
var ScreenBounds:Vector3 = screenCenter;// * 0.9f;
//Check up and down first
if(cos > 0){
screenpos = new Vector3(ScreenBounds.y/m, ScreenBounds.y, 0);
}else{//down
screenpos = new Vector3(-ScreenBounds.y/m, -ScreenBounds.y, 0);
}
//If out of bound then get point on appropriate side
if(screenpos.x > ScreenBounds.x){//Out of bound must be on right
screenpos = new Vector3(ScreenBounds.x, ScreenBounds.y*m, 0);
}else if(screenpos.x < ScreenBounds.x){//Out of bound must be on left
screenpos = new Vector3(-ScreenBounds.x, -ScreenBounds.y*m, 0);
}
//Remove the co ordinate translation
screenpos += screenCenter;
var DistanceIndicatorRectT = DistanceIndicator.GetComponent(RectTransform);
DistanceIndicatorRectT.localPosition = new Vector3(screenpos.x * scrWidth/2, screenpos.y * scrHeight/2, DistanceIndicatorRectT.localPosition.z * screenpos.z);
DistanceIndicator.transform.rotation = Quaternion.Euler(0, 0, angle*Mathf.Rad2Deg);
I did a bit of a different approach than you, what Carlos suggested but without using physics.
If "t" is your target, this way you can get it's position on screen in pixels (if it's off screen it just goes to negative values or values higher that width)
Vector3 targetPosOnScreen = Camera.main.WorldToScreenPoint (t.position);
And this function that return a bool whether the Vector3 (in pixels) is on screen
bool onScreen(Vector2 input){
return !(input.x > Screen.width || input.x < 0 || input.y > Screen.height || input.y < 0);
}
First thing we should do is check if the target is on screen, if it's not then proceed with code.
if (onScreen (targetPosOnScreen)) {
//Some code to destroy indicator or make it invisible
return;
}
Then a simple calculation of angle between center of screen and target.
Vector3 center = new Vector3 (Screen.width / 2f, Screen.height / 2f, 0);
float angle = Mathf.Atan2(targetPosOnScreen.y-center.y, targetPosOnScreen.x-center.x) * Mathf.Rad2Deg;
Next part of code determines where the object is compared to camera based on angle we just calculated.
float coef;
if (Screen.width > Screen.height)
coef = Screen.width / Screen.height;
else
coef = Screen.height / Screen.width;
float degreeRange = 360f / (coef + 1);
if(angle < 0) angle = angle + 360;
int edgeLine;
if(angle < degreeRange / 4f) edgeLine = 0;
else if (angle < 180 - degreeRange / 4f) edgeLine = 1;
else if (angle < 180 + degreeRange / 4f) edgeLine = 2;
else if (angle < 360 - degreeRange / 4f) edgeLine = 3;
else edgeLine = 0;
http://s23.postimg.org/ytpm82ad7/Untitled_1.png
Image represents what value "edgeLine" will have based on target position (red represents camera's view) and black lines division of space.
And then we have this code which sets Transform "t2" (indicator) to correct position and angle.
t2.position = Camera.main.ScreenToWorldPoint(intersect(edgeLine, center, targetPosOnScreen)+new Vector3(0,0,10));
t2.eulerAngles = new Vector3 (0, 0, angle);
Below we have function "intersect" which code is:
Vector3 intersect(int edgeLine, Vector3 line2point1, Vector3 line2point2){
float[] A1 = {-Screen.height, 0, Screen.height, 0};
float[] B1 = {0, -Screen.width, 0, Screen.width};
float[] C1 = {-Screen.width * Screen.height,-Screen.width * Screen.height,0, 0};
float A2 = line2point2.y - line2point1.y;
float B2 = line2point1.x - line2point2.x;
float C2 = A2 * line2point1.x + B2 * line2point1.y;
float det = A1[edgeLine] * B2 - A2 * B1[edgeLine];
return new Vector3 ((B2 * C1[edgeLine] - B1[edgeLine] * C2) / det, (A1[edgeLine] * C2 - A2 * C1[edgeLine]) / det, 0);
}
We send to this function index of which line of camera's view (rectangle) we need to check intersection with, and construct a line between center of screen and target position.
For better explanation of this function look here : https://www.topcoder.com/community/data-science/data-science-tutorials/geometry-concepts-line-intersection-and-its-applications/
I just modified values of A1, B1 and C1, each of them is now array of 4 and each values represents value needed for one line of camera's view (rectangle).
If you want to implement margins just change the pivot of indicator (put the actual sprite renderer as child and move it in local space as you want).
Next thing would be making this work for array of targets and putting all those targets in given array. Hope this helps and don't be too hard on me, it's my first time posting here :)
Create a rectangle box collider delimiting the borders of the screen and use Physics2D.Raycast in the direction of the enemy.
The point of collision will tell you where the green arrow needs to be drawn.
In the example above, there is an error with the definition of the angle of visibility of a straight rectangle.
private void SetIndicatorPosition(Indicator obj)
{
var target = obj.Target;
var indicator = obj.PointToTarget;
if (target == null)
{
indicator.SetActive(false);
return;
}
Vector3 targetPosOnScreen = cam.WorldToScreenPoint(target.transform.position);
if (onScreen(targetPosOnScreen))
{
indicator.SetActive(false);
return;
}
indicator.SetActive(true);
Vector3 center = new Vector3(Screen.width / 2f, Screen.height / 2f, 0);
float angle = Mathf.Atan2(targetPosOnScreen.y - center.y, targetPosOnScreen.x - center.x) * Mathf.Rad2Deg;
float scale;
if (Screen.width > Screen.height)
scale = Screen.width / Screen.height;
else
scale = Screen.height / Screen.width;
float degreeRange = 360f / (scale + 1);
float angle2 = Mathf.Atan2(Screen.height - center.y, Screen.width - center.x) * Mathf.Rad2Deg;
if (angle < 0) angle = angle + 360;
int edgeLine;
if (angle < angle2) edgeLine = 0;
else if (angle < 180 - angle2) edgeLine = 1;
else if (angle < 180 + angle2) edgeLine = 2;
else if (angle < 360 - angle2) edgeLine = 3;
else edgeLine = 0;
indicator.transform.position = Camera.main.ScreenToWorldPoint(Intersect(edgeLine, center, targetPosOnScreen));
indicator.transform.eulerAngles = new Vector3(0, 0, angle);
}
Vector3 Intersect(int edgeLine, Vector3 line2point1, Vector3 line2point2)
{
float[] A1 = { -Screen.height, 0, Screen.height, 0 };
float[] B1 = { 0, -Screen.width, 0, Screen.width };
float[] C1 = { -Screen.width * Screen.height, -Screen.width * Screen.height, 0, 0 };
float A2 = line2point2.y - line2point1.y;
float B2 = line2point1.x - line2point2.x;
float C2 = A2 * line2point1.x + B2 * line2point1.y;
float det = A1[edgeLine] * B2 - A2 * B1[edgeLine];
var x = (B2 * C1[edgeLine] - B1[edgeLine] * C2) / det;
var y = (A1[edgeLine] * C2 - A2 * C1[edgeLine]) / det;
return new Vector3(x, y, 0);
}
bool onScreen(Vector2 input)
{
return !(input.x > Screen.width || input.x < 0 || input.y > Screen.height || input.y < 0);
}
public class Indicator
{
public GameObject Target { get; private set; }
public GameObject PointToTarget { get; private set; }
public Indicator(GameObject target, GameObject pointToTarget, ObjectTypeEnum type)
{
Target = target;
PointToTarget = pointToTarget;
var texture = pointToTarget.GetComponentInChildren<UITexture>();
if (texture != null)
{
texture.color = Helper.GetHintColor(type);
}
}
}
You can call in update
foreach (var obj in listIndicator)
{
SetIndicatorPosition(obj);
}
I have an Oval and a view on the circumference of the Oval. When you try to drag the view, the view should be moved only on the circumference of the Oval. How can I achieve this?
Any sample equation would be helpful. Thanks.
CGPoint ovalCenter;
CGSize ovalSize;
- (CGPoint)constrainPointToOval:(CGPoint)point
{
float angle = atan2(point.y - ovalCenter.y, point.x - ovalCenter.x);
return CGPointMake(ovalSize.width * cosf(angle), ovalSize.height * sinf(angle));
}
You'll need to set ovalCenter and ovalSize elsewhere. Then run the touch position through this before setting the location of the view.
I have figured out a solution for getting a constrained drag along the sides of a square.
If anyone can improve the code, or have a better solution, you most welcome.
- (CGPoint) constrainPointToSquare:(CGPoint) point
{
float pi = 3.14159265;
float s1,s2;
CGPoint squareDragPoint;
float squareSize = 200.0;
float angle;
angle = atan2 (point.y - mCenter.y, point.x - mCenter.x);
float x1 = point.x;
float x2 = mCenter.x;
float y1 = point.y;
float y2 = mCenter.y;
if (((3*(pi/4) <= angle && pi >= angle) || (-pi <= angle && -3*(pi/4) >= angle)))//left
{
s1 = y2 - squareSize;
s2 = x2 - squareSize * ((y1-y2)/(x1-x2));
squareDragPoint = CGPointMake(s1, s2);
}
else if (((-(pi/4) <= angle && 0.0 >= angle) || (0.0 <= angle && (pi/4) >= angle))) //right
{
s1 = y2 + squareSize;
s2 = x2 + squareSize * ((y1-y2)/(x1-x2));
squareDragPoint = CGPointMake(s1, s2);
}
else if (((-3*(pi/4) <= angle && -(pi/2) >= angle) || (-(pi/4) >= angle && -(pi/2) <= angle))) //top
{
s1 = x2 - squareSize;
s2 = y2 - squareSize * ((x1-x2)/(y1-y2));
squareDragPoint = CGPointMake(s2, s1);
}
else if (((3*(pi/4) >= angle && (pi/2) <= angle) || (pi/4 <= angle && (pi/2) >= angle))) //bottom
{
s1 = x2 + squareSize;
s2 = y2 + squareSize * ((x1-x2)/(y1-y2));
squareDragPoint = CGPointMake (s2, s1);
}
return squareDragPoint;
}