Attaching dynamic Line Renderer to GameObject - c#

I've created a (3D)-canvas which includes a list. The canvas is located in the world space. It also faces to the camera all the time when it's activated by selecting a GameObject. Now I'd like the add a dynamic Line Renderer between the upper left corner of the canvas and the point of the RaycastHit / selected object (see sketch). The position of the LR on the GO-side should be static, the LR on the list-side should be dynamic. Thus, it should stay attached at the corner even the canvas starts rotating in all axis (x,y,z).
I already stuck at the positioning of the LR on the list-side. I don't get the corner-coordinates to attach the LR there. I've already tried the bounds-method and some others but I'm not getting ahead. My code so far:
//GUI elements
public Canvas canvas_list;
public GameObject content_list;
LineRenderer lr_list;
// Use this for initialization
void Start ()
{
Setup_LR();
}
// Update is called once per frame
void Update ()
{
// Canvas facing to the camera
canvas_list.transform.rotation = ar_camera.transform.rotation;
// LineRenderer is always attachted to canvas (Part 1)
lr_list.SetPosition(1, new Vector3(canvas_list.GetComponent<Collider>().bounds.min.x, canvas_list.GetComponent<Collider>().bounds.max.y, canvas_list.GetComponent<Collider>().bounds.min.z));
// Selecting objects
if (Input.GetMouseButtonDown(0))
{
Selecting_Objects();
}
}
void Selecting_Objects()
{
raycastHit = new RaycastHit();
if (Physics.Raycast(ar_camera.ScreenPointToRay(Input.mousePosition), out raycastHit))
{
if (!EventSystem.current.IsPointerOverGameObject())
{
GameObject selected_object = raycastHit.collider.gameObject;
// LineRenderer is always attachted to building component (Part 2) - avoids that the line renderer gets attached to GUI element
lr_list.SetPosition(0, raycastHit.point);
}
}
}
void Setup_LR()
{
canvas_list.transform.gameObject.AddComponent<BoxCollider>();
lr_list = new GameObject().AddComponent<LineRenderer>();
lr_list.material.color = Color.red;
lr_list.startWidth = 0.01f;
lr_list.endWidth = 0.01f;
}
That's how it looks now:
Thanks!

Related

Show UI in front of player Unity VR

I am trying to show a UI always in front of the player, and facing it, in a Unity3D VR project. I am using Unity 2021.3.5f1.
I have a simple UI: a Canvas, with a Panel and two TextMeshPro inside it. The Canvas is child of an empty GameObject, with a script in it that manages the UI. The hierarchy is as follows:
My aim is to always show the UI in front of the player, facing the player itself. For this reason I wrote this script:
using UnityEngine;
namespace UI
{
public class KpPanelManager : MonoBehaviour
{
[SerializeField] private Transform playerHead;
[SerializeField] private float spawnDistance = 2f;
[SerializeField] private float yOffset = 0f;
[SerializeField] private GameObject panel;
private Vector3 _playerHeadForward;
private void Awake()
{
_playerHeadForward = playerHead.forward;
}
private void Update()
{
// show the panel in front of the player
var position = playerHead.position;
panel.transform.position = position + new Vector3(_playerHeadForward.x, yOffset, _playerHeadForward.z).normalized * spawnDistance;
// rotate the panel to face the player frame by frame
panel.transform.LookAt(new Vector3(position.x, panel.transform.position.y, position.z));
panel.transform.forward *= -1;
}
}
}
However, it does not work properly: it is correctly in front of the player, but it does not follow the player's camera itself when it moves. In the Unity editor I am referencing the Canvas that contains my UI as the panel GameObject in script, and the MainCamera of the XROrigin as the playerHead Transform in the script.
Can anyone help me?
You could use Transform's TransformPoint() to covert position from local to world space
Something like following would work for your case
Vector3 offset = new Vector3(0,0,2); // 2 units in front of camera
panel.transform.position = playerHead.TransformPoint(offset);

How to 'connect' two Edge Collider curves?

I am using Unity's Sprite Shapes with Edge Colliders as terrain for my orthographic 2d sidescrolling game, with multiple 'tiles' (i.e. a gameobject with the Sprite Shape and Edge Collider classes).
The intention is, when the camera is one screen's width away from the end of the current 'tile', to instantiate a random terrain from a selection terrains, then place the first vector of the next collider at the same position of the last vector of the previous collider. That way, they connect.
However, when the camera gets to the specified point, the script continues to load terrains infinitely and in the wrong place.
Please let me know if further detail is required.
using System.Collections.Generic;
using UnityEngine;
public class TerrainManager : MonoBehaviour
{
public Transform[] terrains;
public Transform terrainVisualOutput;
private Transform m_lastTerrain { get { return m_displayedTerrains[m_displayedTerrains.Count - 1]; } }
private List<Transform> m_displayedTerrains = new();
static System.Random m_rnd = new();
void FixedUpdate()
{
// If within frame width's distance of last point, add terrain and remove previous terrain
if (Camera.main.transform.position.x + GetScreenWidth() > m_lastTerrain.GetComponent<EdgeCollider2D>().points[^1].x)
{
GenerateNextTerrain();
}
}
void GenerateNextTerrain()
{
// Instantiate new terrain object
Transform _newTerrain = Instantiate(terrains[m_rnd.Next(0, terrains.Length - 1)], terrainVisualOutput, true);
// Reposition it at the end of the last terrain
_newTerrain.transform.position =
m_lastTerrain.GetComponent<EdgeCollider2D>().points[^1]
- _newTerrain.GetComponent<EdgeCollider2D>().points[0];
// Rename and add to list
_newTerrain.name = $"{m_displayedTerrains.Count}";
m_displayedTerrains.Add(_newTerrain);
}
private float GetScreenWidth()
{
Camera _camera = Camera.main;
float _halfHeight = _camera.orthographicSize;
return _camera.aspect * _halfHeight * 2;
}
}

Unity Align prefab to top left (2D Game)

I'm making my first game where obstacles (which are prefabs) are placed by a script into a scene. They are all different sizes in a 2D environment. I am placing them using this code below
Instantiate(normal1, new Vector3(x, y , 0), Quaternion.identity);
This works perfectly, except that I need all of the obstacles to be positioned from the top left. So if I would have 0, 0 for my x and y, only the obstacle's corner would be covering the 0, 0 position, not the entire thing. From the little I've worked with GUI elements, you can align to however you like. Is this the same with prefab, and if so, how? Or can you somehow move the origin of the object?
I assume you are talking about non-UI elements.
The easiest thing would be to give your objects a parent GameObject and arrange them in a way so the parent GameObject already has the pivot where you want it (the top-left corner). You do this by first positioning the (future) parent object correctly and then simply drag the child object into it in the hierachy (while it keeps its current position).
Then in your script you have to use Camera.ScreenToWorldPoint in order to find the top-left screen corner position in the 3D world like
// an optional offset from the top-left corner in pixels
Vector2 PixelOffsetFromTopLeft;
// Top Left pixel is at
// x = 0
// y = Screen height
// and than add the desired offset
var spawnpos = new Vector2(0 + PixelOffsetFromTopLeft.x, Screen.height - PixelOffsetFromTopLeft.y);
// as z we want a given distance to camera (e.g. 2 Units in front)
spawnedObject = Instantiate(Prefab, camera.ScreenToWorldPoint(new Vector3(spawnpos.x, spawnpos.y, 2f)), Quaternion.identity);
Full script I used as example
public class Spawner : MonoBehaviour
{
public GameObject Prefab;
public Vector2 PixelOffsetFromTopLeft = Vector2.zero;
private GameObject spawnedObject;
private Camera camera;
private void Start()
{
camera = Camera.main;
}
private void Update()
{
if (Input.anyKeyDown)
{
// you don't need this I just didn't want to mess up the scene
// so I just destroy the last spawned object before placing a new one
if (spawnedObject)
{
Destroy(spawnedObject);
}
// Top Left pixel is at
// x = 0
// y = Screen height
// and than add the desired offset
var spawnpos = new Vector2(0 + PixelOffsetFromTopLeft.x, Screen.height - PixelOffsetFromTopLeft.y);
// as z we want a given distance to camera (e.g. 2 Units in front)
spawnedObject = Instantiate(Prefab, camera.ScreenToWorldPoint(new Vector3(spawnpos.x, spawnpos.y, 2f)), Quaternion.identity);
}
}
}
This will now always spawn the object anchored to the top left. It will not keep it this way if the camera moves or the window is resized.
If your question was about keeping that position also when the window is resized you can use the same code e.g. in an Update method (later you due to efficiency you should only call it when really needed / window actually was resized) like
public class StickToTopLeft : MonoBehaviour
{
private Camera camera;
public Vector2 PixelOffsetFromTopLeft = Vector2.zero;
private void Start()
{
camera = Camera.main;
}
// Update is called once per frame
private void Update()
{
var spawnpos = new Vector2(0 + PixelOffsetFromTopLeft.x, Screen.height - PixelOffsetFromTopLeft.y);
// This time simply stay at the current distance to camera
transform.position = camera.ScreenToWorldPoint(new Vector3(spawnpos.x, spawnpos.y, Vector3.Distance(camera.transform.position, transform.position)));
}
}
This now keeps the object always anchored to the top-left corner also after resizing the window and allows to adjust its offset afterwards.

How to make trail renderer not affected by z axis motion in unity 3d

I was making a 3D game and I made input collector that has trail renderer component. When there is z action motion the trailer renderer shows some forward motion effects.
Is there any way to make trail renderer only show certain axis motion?
The best way I can think of is to make a separate object with trail, and move it to the position of the object you want to have a trail on before rendering starts, and then, on post render, reset its Z position. Like this:
[RequireComponent(typeof(TrailRenderer))]
public class ZLimitedTrail : MonoBehaviour {
public Transform target;
private void Start() {
Camera.onPreRender += SetPosition;
Camera.onPostRender += ResetZPosition;
}
private void SetPosition() {
// move it to where it should show
this.transform.position = target.position;
}
private void ResetZPosition() {
// reset Z position to 0
this.transform.position =
new Vector3(this.transform.position.x, this.transform.position.y, 0);
}
}
I haven't tested it so let me know if it works.

How can i create an in-game system that lets me draw lines with the cursor that can act as walkable terrain

Hi im creating a 2D platformer, and i want to be able to draw for example lines ingame (playmode) with my cursor (like paint) that can act as walkable terrain. Im pretty new to coding and c# which im using, and im having a really hard time imagining how this can be achieved let alone if its possible? Would appreciate if you guys could give me some ideas and maybe could help me push it in the right direction. Thanks!
EDIT:
So i got got some code now which makes me being able to draw in playmode. The question now is how i can implement a type of collider to this? Maybe each dot can represent a little square or something? How can i go through with it? Heres some code. Thanks.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class DrawLine : MonoBehaviour
{
private LineRenderer line;
private bool isMousePressed;
private List<Vector3> pointsList;
private Vector3 mousePos;
// Structure for line points
struct myLine
{
public Vector3 StartPoint;
public Vector3 EndPoint;
};
// -----------------------------------
void Awake()
{
// Create line renderer component and set its property
line = gameObject.AddComponent<LineRenderer>();
line.material = new Material(Shader.Find("Particles/Additive"));
line.SetVertexCount(0);
line.SetWidth(0.1f,0.1f);
line.SetColors(Color.green, Color.green);
line.useWorldSpace = true;
isMousePressed = false;
pointsList = new List<Vector3>();
}
// -----------------------------------
void Update ()
{
// If mouse button down, remove old line and set its color to green
if(Input.GetMouseButtonDown(0))
{
isMousePressed = true;
line.SetVertexCount(0);
pointsList.RemoveRange(0,pointsList.Count);
line.SetColors(Color.green, Color.green);
}
else if(Input.GetMouseButtonUp(0))
{
isMousePressed = false;
}
// Drawing line when mouse is moving(presses)
if(isMousePressed)
{
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
mousePos.z=0;
if (!pointsList.Contains (mousePos))
{
pointsList.Add (mousePos);
line.SetVertexCount (pointsList.Count);
line.SetPosition (pointsList.Count - 1, (Vector3)pointsList [pointsList.Count - 1]);
}
}
}
}
Regarding means and tools:
Input.mousePosition
Input.GetKey, Input.GetKeyDown, Input.GetKeyUp
Line renderer manual
Line renderer scripting API
Regarding general idea:
Your script may use Input.GetKey to trigger the feature functionality (keyboard key, for example).
When the feature is activated the script awaits of mouse button to be clicked by the means of Input.GetKeyUp and when the event happens it must capture current mouse position using Input.mousePosition.
Only two points are necessary to build a segment of line, so when the script detects input of the second point it may create game object that will represent a piece of walkable terrain.
Visually such an object may be represented with line renderer. To enable iteraction with other game objects (like player character) it is usually enough to enhance object with a collider component (presumably, with a BoxCollider).
Regarding of how-to-add-a-collider:
GameObject CreateLineCollider(Vector3 point1, Vector3 point2, float width)
{
GameObject obj = new GameObject("LineCollider");
obj.transform.position = (point1+point2)/2;
obj.transform.right = (point2-point1).normalized;
BoxCollider boxCollider = obj.AddComponent<BoxCollider>();
boxCollider.size = new Vector3( (point2-point1).magnitude, width, width );
return obj;
}
You can add collider to object with line renderer but you still must orient it properly.
Example of integration in your code:
void Update ()
{
...
// Drawing line when mouse is moving(presses)
if(isMousePressed)
{
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
mousePos.z=0;
if (!pointsList.Contains (mousePos))
{
pointsList.Add (mousePos);
line.SetVertexCount (pointsList.Count);
line.SetPosition (pointsList.Count - 1, (Vector3)pointsList [pointsList.Count - 1]);
const float ColliderWidth = 0.1f;
Vector3 point1 = pointsList[pointsList.Count - 2];
Vector3 point2 = pointsList[pointsList.Count - 1];
GameObject obj = new GameObject("SubCollider");
obj.transform.position = (point1+point2)/2;
obj.transform.right = (point2-point1).normalized;
BoxCollider boxCollider = obj.AddComponent<BoxCollider>();
boxCollider.size = new Vector3( (point2-point1).magnitude, ColliderWidth , ColliderWidth );
obj.transform.parent = this.transform;
}
}
}

Categories