The code I'm using is
public void Strech(GameObject sprite, Vector3 initialPosition, Vector3 finalPosition)
{
Vector3 centerPos = (initialPosition + finalPosition) / 2f;
sprite.transform.position = centerPos;
Vector3 direction = finalPosition - initialPosition;
direction = Vector3.Normalize(direction);
sprite.transform.right = direction;
distance = Vector3.Distance(initialPosition, finalPosition);
Debug.DrawLine(initialPosition, finalPosition);
sprite.GetComponent<RectTransform>().sizeDelta = new Vector3(distance, 40f);
}
I can't seem to figure out why the image won't tile to or from either centers of the UI objects.
What am I doing wrong? I call the Stretch function in the Update loop.
The red line itself is a UnityEngine.UI Image type.
I want the image to tile as shown by the Debug.DrawLine
Edit 1: Here's how the rect is being displayed
I fixed it!
I had to multiply a scalefactor, because the Canvas was using a Canvas Scaler with reference resolution of 1280 x 720
Related
hi everyone I make 2D platformer games and the camera I use now always makes the player in the middle. I want my camera to show the front of the player wider.
public Transform target;
Vector3 velocity = Vector3.zero;
public float smoothTime = 0.3f;
void FixedUpdate()
{
Vector3 targetPos = target.position;
targetPos.z = transform.position.z;
transform.position = Vector3.SmoothDamp(transform.position, targetPos, ref velocity, smoothTime);
}
Just move the targetPos in the according direction e.g. using
// Adjust this via the inspector
public float Offset = 1;
// Depending on your setup you might have to change "forward" to "right" or "up"
Vector3 targetPos = target.position + target.forward * Offset;
targetPos.z = transform.position.z;
Create a float variable public float offset = 10.0f. Subtract offset value from your targetPos.z as such: targetPos.z=transform.position.z - offset;
Note that the value of offset is something you have to experiment with to get right. Advisably, make the offset variable public, that way you can play the game in unity editor, play with the variable till its okay, copy the value of offset, stop the game the change the value of offset in your code to the new value. Goodluck
If you're using an Orthographic camera projection, you should change the viewport Size of the Camera.
You can update this value by code : Camera.main.orthographicSize = myNewSize;
Put a smaller value to zoom in, and a larger value to zoom out.
The value represent the half-screen projection on the vertical axis.
It's mean that a value of 0.5 will make a 1m cube fit the screen.
I want to display a 2D arrow on the screen that always moves to point to a 3D object in the AR scene.
The issue is how to measure the angle that should the arrow rotate to point the desired 3D object.
Thanks in advance.
One strategy is to project the position of your object into screen space.
Then calculate the vector between the position of your arrow and that projected position. You can use this vector to calculate an angle of rotation from, for example, a vertical direction, using the Dot Product, followed by an acos.
Finally, you'd need to do a little cross-product calculation to decide whether the above rotation is clockwise or anticlockwise.
Here is some sample code:
public GameObject Target;
RectTransform rt;
void Start()
{
rt = GetComponent<RectTransform>();
}
void Update()
{
// Get the position of the object in screen space
Vector3 objScreenPos = Camera.main.WorldToScreenPoint(Target.transform.position);
// Get the directional vector between your arrow and the object
Vector3 dir = (objScreenPos - rt.position).normalized;
// Calculate the angle
// We assume the default arrow position at 0° is "up"
float angle = Mathf.Rad2Deg * Mathf.Acos(Vector3.Dot(dir, Vector3.up));
// Use the cross product to determine if the angle is clockwise
// or anticlockwise
Vector3 cross = Vector3.Cross(dir, Vector3.up);
angle = -Mathf.Sign(cross.z) * angle;
// Update the rotation of your arrow
rt.localEulerAngles = new Vector3(rt.localEulerAngles.x, rt.localEulerAngles.y, angle);
}
For the above code, I suppose that:
You are only using one main camera, you may need to change this
Your arrow is on the Canvas, by default pointing upwards (when its rotation is (0, 0, 0))
You are using a Canvas in Render Mode: Screen Space - Overlay. The above code would be different if the Canvas were in World Space.
As a high-level overview:
Find direction from UI/view-plane centre to 3D object
Project direction onto UI/view-plane (using forward as normal vector), and normalize
Point 2D arrow toward projected direction
Thank You all Guys, I got an Answer for two Situations :
First One: When the two objects are in 3D
public GameObject flesh;
public GameObject target;
// Start is called before the first frame update
void Start()
{
flesh.transform.position = Camera.main.ScreenToWorldPoint(new Vector3( Screen.width/2, Screen.height/2,1));
}
// Update is called once per frame
void Update()
{
var dir = Camera.main.WorldToScreenPoint(target.transform.position) -
Camera.main.WorldToScreenPoint(flesh.transform.position);
var angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
flesh.transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
}
Second: When the Flesh is an Image that Has RectTransform,
, this solution Inspirational from #kevernicus
public GameObject Target;
RectTransform rt;
void Start()
{
rt = GetComponent<RectTransform>();
}
void Update()
{
// Get the position of the object in screen space
Vector3 objScreenPos = Camera.main.WorldToScreenPoint(Target.transform.position);
// Get the directional vector between your arrow and the object
Vector3 dir = (objScreenPos - rt.position).normalized;
float angle = Mathf.Rad2Deg * Mathf.Atan2(dir.y, dir.x);
rt.rotation = Quaternion.AngleAxis(angle, Vector3.forward);
}
Use Unity's built in Transform.LookAt function
I have an image UI in a canvas with Screen Space - Camera render mode. What I like to do is move my LineRenderer to the image vertical position by looping through all the LineRenderer positions and changing its y axis. My problem is I cant get the correct position of the image that the LineRenderer can understand. I've tried using ViewportToWorldPoint and ScreenToWorldPoint but its not the same position.
Vector3 val = Camera.main.ViewportToWorldPoint(new Vector3(image.transform.position.x, image.transform.position.y, Camera.main.nearClipPlane));
for (int i = 0; i < newListOfPoints.Count; i++)
{
line.SetPosition(i, new Vector3(newListOfPoints[i].x, val.y, newListOfPoints[i].z));
}
Screenshot result using Vector3 val = Camera.main.ScreenToWorldPoint(new Vector3(image.transform.localPosition.x, image.transform.localPosition.y, -10));
The green LineRenderer is the result of changing the y position. It should be at the bottom of the square image.
Wow, this was annoying and complicated.
Here's the code I ended up with. The code in your question is the bottom half of the Update() function. The only thing I changed is what was passed into the ScreenToWorldPoint() method. That value is calculated in the upper half of the Update() function.
The RectTransformToScreenSpace() function was adapted from this Unity Answer post1 about getting the screen space coordinates of a RectTransform (which is exactly what we want in order to convert from screen space coordinates back into world space!) The only difference is that I was getting inverse Y values, so I changed from Screen.height - transform.position.y to just transform.position.y which did the trick perfectly.
After that it was just a matter of grabbing that rectangle's lower left corner, making it a Vector3 instead of a Vector2, and passing it back into ScreenToWorldPoint(). The only trick there was because of the perspective camera, I needed to know how far away the line was from the camera originally in order to maintain that same distance (otherwise the line moves up and down the screen faster than the image). For an orthographic camera, this value can be anything.
void Update () {
//the new bits:
float dist = (Camera.main.transform.position - newListOfPoints[0]).magnitude;
Rect r = RectTransformToScreenSpace((RectTransform)image.transform);
Vector3 v3 = new Vector3(r.xMin, r.yMin, dist);
//more or less original code:
Vector3 val = Camera.main.ScreenToWorldPoint(v3);
for(int i = 0; i < newListOfPoints.Count; i++) {
line.SetPosition(i, new Vector3(newListOfPoints[i].x, val.y, newListOfPoints[i].z));
}
}
//helper function:
public static Rect RectTransformToScreenSpace(RectTransform transform) {
Vector2 size = Vector2.Scale(transform.rect.size, transform.lossyScale);
Rect rect = new Rect(transform.position.x, transform.position.y, size.x, size.y);
rect.x -= (transform.pivot.x * size.x);
rect.y -= ((1.0f - transform.pivot.y) * size.y);
return rect;
}
1And finding that post from a generalized search on "how do I get the screen coordinates of a UI object" was not easy. A bunch of other posts came up and had some code, but none of it did what I wanted (including converting screen space coordinates back into world space coordinates of the UI object which was stupid easy and not reversibe, thanks RectTransformUtility!)
I have a scene with multiple GameObjects with x-y-z position. I'm in 2D so the z is not used.
For now, I worked with the resolution 1024/768 and when I add a GameObject like :
GameObject star = GameObject.CreatePrimitive(PrimitiveType.Sphere);
Vector3 position = new Vector3 (x, y, z);
star.transform.localPosition = position;
It's ok, the GameObject is in the right position on my scene.
But nox, I'm trying to change the resolution (1920/1080) and all my GameObjects are moved far on the top right, out of my camera.
What's the problem ? How can I fix this ?
For me, when I change the resolution, the 0-0-0 change for my GameObjects....and something weird, If my GameObject has a LineRenderer with positions, they are good on all resolutions...
Edit : This is the function I use for generate my GameObjects, it's a galaxy generator :
for(int i = 0; i < numberOfStars; i++) {
bool checkPosition = false;
while (!checkPosition) {
// Choose a distance from the center of the galaxy.
float distance = Random.Range(5.0f, (float) galaxySize / 2 - 5);
// Choose an angle between 0 and 2 * PI.
float angle = Random.Range(0.0f, 100.0f) * 2 * Mathf.PI;
Vector3 position = new Vector3 (Mathf.Cos (angle) * distance, Mathf.Sin(angle) * distance, 9);
if (! Physics.CheckSphere(position, 1)) {
GameObject star = GameObject.CreatePrimitive(PrimitiveType.Sphere);
star.AddComponent<SolarSystem>();
star.name = i + "_" + PlanetNameGenerator.GenerateName();
star.GetComponent<SolarSystem>().name = i + "_" + PlanetNameGenerator.GenerateName();
star.transform.parent = GameObject.Find ("Targets").transform;
// On change le scale en random
float randomScale = Random.Range (0f, 0.5f);
star.transform.localScale += new Vector3(randomScale,randomScale,randomScale);
star.transform.localPosition = position;
star.tag = "SolarSystem";
checkPosition = true;
}
}
}
This is the scene I see in 1024/768, with my Sphere GameObjects and in pink LineRenderer between them :
This is what I see in 1920/1080, LineRenderer are always in the same position, but GameObjects move away :
The problem was this line :
star.transform.parent = GameObject.Find ("Targets").transform;
I moved the parent out from Canvas, chnage x-y-z positions of "Targets" to 0 et it's ok.
How to draw circle in Unity 3d?
I want to draw a circle around different objects.
The radiuses of the circles are different and the circles have textures - squares.
I found a big error with this code. The number of points (Size) shouldn't be "(2 * pi / theta_scale) + 1" because this causes the circle to draw 6.28 times. The size should be "1 / theta_scale + 1". So for a theta_scale of 0.01 it needs to draw 100 points, and for a theta_scale of 0.1 it needs to draw 10 points. Otherwise it would draw 62 times and 628 times respectively.
Here is the code I used.
using UnityEngine;
using System.Collections;
public class DrawRadar: MonoBehaviour {
public float ThetaScale = 0.01f;
public float radius = 3f;
private int Size;
private LineRenderer LineDrawer;
private float Theta = 0f;
void Start() {
LineDrawer = GetComponent<LineRenderer>();
}
void Update() {
Theta = 0f;
Size = (int)((1f / ThetaScale) + 1f);
LineDrawer.SetVertexCount(Size);
for (int i = 0; i < Size; i++) {
Theta += (2.0f * Mathf.PI * ThetaScale);
float x = radius * Mathf.Cos(Theta);
float y = radius * Mathf.Sin(Theta);
LineDrawer.SetPosition(i, new Vector3(x, y, 0));
}
}
}
If you modify the number in "Size" that is divided by ThetaScale, you can make a sweeping gauge/pie chart type graphic.
See Unity Answers for a similar question.
Alternatively:
float theta_scale = 0.1; // Circle resolution
LineRenderer lineRenderer = gameObject.AddComponent<LineRenderer>();
lineRenderer.material = new Material(Shader.Find("Particles/Additive"));
lineRenderer.SetColors(c1, c2);
lineRenderer.SetWidth(0.2F, 0.2F);
lineRenderer.SetVertexCount(size);
int i = 0;
for(float theta = 0; theta < 2 * PI; theta += theta_scale) {
x = r*cos(theta);
y = r*sin(theta);
Vector3 pos = new Vector3(x, y, 0);
lineRenderer.SetPosition(i, pos);
i+=1;
}
The LineRenderer requires continuous points. You can modify this code slightly to use cylinder game objects instead of a line renderer. I find the LineRenderer to be a bit hideous.
Lastly, similar to the first link, you could attach a circle texture to a unit plane. Make any part of the texture that isn't part of the circle transparent. Then just scale and align the plane to fit your object. Unfortunately this method isn't great if someone is looking almost parallel to the plane.
Jerdak's solution is good, but the code is messy so I had to tweak a little. Here's the code for a class, where I use i in the loop to avoid a bug.
It also updates the circle's position with its gameObject position.
using UnityEngine;
using System.Collections;
public class CircleDraw : MonoBehaviour {
float theta_scale = 0.01f; //Set lower to add more points
int size; //Total number of points in circle
float radius = 3f;
LineRenderer lineRenderer;
void Awake () {
float sizeValue = (2.0f * Mathf.PI) / theta_scale;
size = (int)sizeValue;
size++;
lineRenderer = gameObject.AddComponent<LineRenderer>();
lineRenderer.material = new Material(Shader.Find("Particles/Additive"));
lineRenderer.SetWidth(0.02f, 0.02f); //thickness of line
lineRenderer.SetVertexCount(size);
}
void Update () {
Vector3 pos;
float theta = 0f;
for(int i = 0; i < size; i++){
theta += (2.0f * Mathf.PI * theta_scale);
float x = radius * Mathf.Cos(theta);
float y = radius * Mathf.Sin(theta);
x += gameObject.transform.position.x;
y += gameObject.transform.position.y;
pos = new Vector3(x, y, 0);
lineRenderer.SetPosition(i, pos);
}
}
}
Using Shader Graph we can now draw pixel perfect circle.
Once you created this graph, create a new material based on this shader.
Then create a new gameobject with a sprite renderer and set the material you just created.
You can scale the circle using the "scale" parameter of the material.
The linerenderer method in the top answers is really simple and exactly what I was looking for. I updated it for newer versions of Unity and some small tweaks to make it a bit more beginner/user friendly.
Specifically:
LineRenderer.SetVertexCount() is deprecated in newer versions of Unity, replaced with positionCount
Replaced theta scale with an actual segment count to remove guesswork
Added loop setting - not sure if this was in older versions of Unity, it can be set in the LineRenderer's inspector
Removed unnecessary Update function - the rendered line is a persistent gameobject
using UnityEngine;
[RequireComponent(typeof(LineRenderer))]
public class DrawRing : MonoBehaviour
{
public LineRenderer lineRenderer;
[Range(6,60)] //creates a slider - more than 60 is hard to notice
public int lineCount; //more lines = smoother ring
public float radius;
public float width;
void Start()
{
lineRenderer = GetComponent<LineRenderer>();
lineRenderer.loop = true;
Draw();
}
void Draw() //Only need to draw when something changes
{
lineRenderer.positionCount = lineCount;
lineRenderer.startWidth = width;
float theta = (2f * Mathf.PI) / lineCount; //find radians per segment
float angle = 0;
for (int i = 0; i < lineCount; i++)
{
float x = radius * Mathf.Cos(angle);
float y = radius * Mathf.Sin(angle);
lineRenderer.SetPosition(i, new Vector3(x, 0, y));
//switch 0 and y for 2D games
angle += theta;
}
}
}
Note this is assumed to be attached to the gameobject you want the ring around. So the Use World Space option in LineRenderer should be unchecked. Also remember that the scale of the gameobject will affect the position of the points and the width of the line.
To put this on the ground (as in a unit selection circle):
Put the script on a separate gameobject
Rotate the gameobject X to 90
Check use world space on the linerenderer
Set the linerenderer Alignment to Transform Z
Add the position of the thing you want to circle to x and y in SetPosition. Possibly along with replacing 0 with 0.1f or a yOffset variable to avoid z-fighting with terrain.
Circle can draw using shader - draw pixel if it on radius from center.
Did the following with a Sprite. Chan is flying in the scene, so she's slightly above the plane. I had her flying so I could get a good screenshot, not because it wouldn't play well with the plane.
I used a low-resolution circle sprite.
X rotation 90
Scale X 15, Y 15, Z 1
Then I set the Sorting Layer, so it will render above the Default Layer. I was testing this out when I came across this post. It doesn't handle shadows well. I'd have to figure out what layer shadows are drawn on to make sure they get rendered onto the sprite.
I have a shader from which I usually start making effects like lens flares, and it makes a circle. Using shader is the best choice because you will get perfectly smooth and round circle.
Also it's easy to experiment with and tune the shader since shader changes don't require recompile and re-entering of play mode.
I recommend ti create extension method to GameObject. Worked good to me.
public static class GameObjectExtension
{
const int numberOfSegments = 360;
public static void DrawCircle(this GameObject go, float radius,
float lineWidth, Color startColor, Color endColor, bool lineRendererExists=true)
{
LineRenderer circle = lineRendererExists ? go.GetComponent<LineRenderer>() : go.AddComponent<LineRenderer>();
circle.useWorldSpace = false;
circle.startWidth = lineWidth;
circle.endWidth = lineWidth;
circle.endColor = endColor;
circle.startColor = startColor;
circle.positionCount = numberOfSegments + 1;
Vector3 [] points = new Vector3[numberOfSegments + 1];
for (int i = 0; i < numberOfSegments + 1; i++)
{
float rad = Mathf.Deg2Rad * i;
points[i] = new Vector3(Mathf.Sin(rad) * radius, 0, Mathf.Cos(rad) * radius);
}
circle.SetPositions(points);
}
}
One More thing to note: If LineRenderer component is not applied last parameter has to be false
create a static class to reuse the code for different game objects. player, enemies... when the class is static, you cannot create the instance of it
public static class CircleGameObject
{
// in static class methods have to be static as well
// "this" refers to the context that we are calling DrawCircle
public static async void DrawCircle(this GameObject container,float radius,float lineWidth)
{
// I provide 360 points because circle is 360 degrees and we will connect them with line
var segments=360;
// LineRenderer is used to draw line
var lineRenderer=container.AddComponent<LineRenderer>();
// now you can use position system relative to the parent game object.
lineRenderer.useWorldSpace=false;
lineRenderer.startWidth=lineWidth;
lineRenderer.endWidth=lineWidth;
lineRenderer.positionCount=segments+1;
// reserve empty array in memory with a size of lineRenderer.positionCount
var points=new Vector3[lineRenderer.positionCount];
// draw all of those points
for(int i=0;i<points.Length;i++)
{
// converting degree to radian because Mathf.Cos and Mathf.Sin expects radian
var radian=Mathf.Deg2Rad*i;
// y direction needs to be 0
// Mathf.Cos(radiant) will give the x position on the circle if the angle size is "radian"
// Mathf.Sin(radiant) will give the y position on the circle if the angle size is "radian"
// after for loop completes we would be getting 360 points
points[i]=new Vector3(Mathf.Cos(radian)*radius,0,Mathf.Sin(radian)*radius);
}
lineRenderer.SetPositions(points);
}
}
then call it in Awake of the context
public class PlayerController : MonoBehaviour
{
private void Awake()
{
GameObject go=new GameObject{
name="Circle"
};
Vector3 circlePosition=Vector3.zero;
go.transform.parent=transform;
// localPosition is relative to the parent
go.transform.localPosition=circlePosition;
go.DrawCircle(2.0f,0.03f);
....
}
}