I created a diagonal line renderer by attaching the following script to an empty game object. How can I extend the line at both ends for a half its length and how can I also extend the line by say 1 unit along the x-axis? Both over a certain period of time.
public class DiagonalLine : MonoBehaviour {
bool firstLineComplete = false;
LineRenderer diagLine;
public Vector3 startPoint = new Vector3 (0, 0, 0);
public Vector3 endPoint = new Vector3 (1.0f, 1.0f, 0);
public float lineDrawSpeed;
// Use this for initialization
void Start () {
diagLine = gameObject.AddComponent<LineRenderer>();
diagLine.material = new Material (Shader.Find ("Sprites/Default"));
diagLine.startColor = diagLine.endColor = Color.green;
diagLine.startWidth = diagLine.endWidth = 0.15f;
diagLine.SetPosition (0, startPoint);
diagLine.SetPosition (1, endPoint);
}
}
This is basic vector math.
You have the line with (from end to start):
Vector3 v = start - end;
and then you extend on each side by half if it:
extensionA = start + (v * 0.5f);
extensionB = end + (v * -0.5f);
If you need to extend by 1 then normalize:
Vector3 v = (start - end).normalized;
extensionA = start + v;
extensionB = end + (v * -1f);
Break your problem into pieces:
1.Extend line by x units on both sides:
This is done with the Ray class. Create a new Ray instance from the startPoint and endPoint variables then use the Ray.GetPoint function to extend the line. You have to do this on both sides to get the new extended lines.
A simple wrapper for the Ray class to simplify this:
Vector3 extendLine(Vector3 startPoint, Vector3 endPoint, ExtendDirection extendDirection, float extendDistance)
{
Ray ray = new Ray();
//Start
if (extendDirection == ExtendDirection.START_POINT)
{
ray.origin = startPoint;
ray.direction = startPoint - endPoint;
}
//End
else if (extendDirection == ExtendDirection.END_POINT)
{
ray.origin = endPoint;
ray.direction = endPoint - startPoint;
}
//Extend
Vector3 newUnityPoint = ray.GetPoint(extendDistance);
//Debug.DrawLine(ray.origin, newUnityPoint, Color.blue);
return newUnityPoint;
}
public enum ExtendDirection
{
START_POINT, END_POINT
}
Extend to the Left end
Vector3 newStartPos = extendLine(startPoint, endPoint, ExtendDirection.START_POINT, 4);
diagLine.SetPosition(0, newStartPos);
Extend to the Right end
Vector3 newEndPos = extendLine(startPoint, endPoint, ExtendDirection.END_POINT, 4);
diagLine.SetPosition(1, newEndPos);
2.For animating/moving it over time, use coroutine and Time.deltaTime. Increment a variable with Time.deltaTime every frame to then use Vector3.Lerp to lerp the from and to value.
See this function for example.
With both combined, below is a complete function to extend both lines over time:
bool isRunning = false;
IEnumerator extentLineOverTime(LineRenderer targetLineRenderer, float extendDistance, float duration)
{
//Calculate Left from extension length
Vector3 fromValLeftPos = targetLineRenderer.GetPosition(0);
//Calculate Right from extension length
Vector3 fromValRightPos = targetLineRenderer.GetPosition(1);
//Calculate Left to extension length
Vector3 newLeftPos = extendLine(fromValLeftPos, fromValRightPos, ExtendDirection.START_POINT, extendDistance);
//Calculate Right to extension length
Vector3 newRightPos = extendLine(fromValLeftPos, fromValRightPos, ExtendDirection.END_POINT, extendDistance);
//Make sure there is only one instance of this function running
if (isRunning)
{
yield break; ///exit if this is still running
}
isRunning = true;
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
//Move to left overtime
Vector3 tempLeftPos = Vector3.Lerp(fromValLeftPos, newLeftPos, counter / duration);
targetLineRenderer.SetPosition(0, tempLeftPos);
//Move to Right overtime
Vector3 tempRightPos = Vector3.Lerp(fromValRightPos, newRightPos, counter / duration);
targetLineRenderer.SetPosition(1, tempRightPos);
yield return null;
}
isRunning = false;
}
USAGE:
LineRenderer diagLine;
public Vector3 startPoint = new Vector3(0, 0, 0);
public Vector3 endPoint = new Vector3(1.0f, 1.0f, 0);
// Use this for initialization
void Start()
{
diagLine = gameObject.AddComponent<LineRenderer>();
diagLine.material = new Material(Shader.Find("Sprites/Default"));
diagLine.startColor = diagLine.endColor = Color.green;
diagLine.startWidth = diagLine.endWidth = 0.15f;
diagLine.SetPosition(0, startPoint);
diagLine.SetPosition(1, endPoint);
//Extend Line Over time
StartCoroutine(extentLineOverTime(diagLine, 4, 3));
}
The StartCoroutine(extentLineOverTime(diagLine, 4, 3)); will extend the line 4 units away from both sides within 3 seconds.
Related
I am trying to replicate a shooter from any bubble shooter. Dotted line that the bubble will follow.
What I am trying to do is, create a dotted line that bounces (reflects) when getting close the camera limits (camera's limits are the image's limits). Also, to stop creating more dots when hit with a Bubble (blue dot).
The line so far bounces, but not correctly (see corners). Also, it does not stop when hit with a Bubble (blue dot).
private void DrawPoints() {
bool hasReversed = false;
bool reversedLeft = false;
var leftEdge = mainCamera.ScreenToWorldPoint(new Vector3(0, 0, 0));
var rightEdge = mainCamera.ScreenToWorldPoint(new Vector3(Screen.width, 0, 0));
var normalDir = shootDir.normalized;
int count = 0;
for (var i = 0; i < dots.Count; i++) {
var dot = dots[i];
dot.GetComponent<SpriteRenderer>().color = color;
var newPos = new Vector2(normalDir.x, normalDir.y) * i * DotGap;
if (hasReversed) {
newPos.x += reversedLeft ? (-rightEdge.x + Constants.BubbleRadius/2) * 2 * count : (rightEdge.x - Constants.BubbleRadius/2) * 2 * count;
//newPos.x += reversedLeft ? (-rightEdge.x + Constants.BubbleRadius) * 2 : (rightEdge.x - Constants.BubbleRadius) * 2;
}
//newPos += normalDir * delta;
dot.transform.localPosition = newPos;
RaycastHit2D hit = Physics2D.Raycast(newPos, shootDir);
if (hit.collider != null) {
float distance = Vector2.Distance(hit.transform.position, newPos);
if (distance < Constants.WhiteCircleRadius + Constants.BubbleRadius) {
dot.SetActive(false);
Debug.Log("Found!: " + distance + " " + hit.collider.name);
break;
} else {
dot.SetActive(true);
}
}
if (dot.transform.localPosition.x <= leftEdge.x + Constants.BubbleRadius) {
hasReversed = true;
reversedLeft = true;
normalDir = Vector2.Reflect(normalDir, Vector2.left);
count++;
}
else if (dot.transform.localPosition.x >= rightEdge.x - Constants.BubbleRadius) {
hasReversed = true;
reversedLeft = false;
normalDir = Vector2.Reflect(normalDir, Vector2.right);
count++;
}
}
}
The image as follows:
The red dot is the pivot (start of the dotted line). The blue dot, is a bubble.
After a bit of fiddling I managed to get it to work pretty decently. I started from scratch as I have never done anything like this and I needed to work through it to understand the problems you were experiencing. A good amount of code is just a modified version of Kunal Tandon's from https://medium.com/#kunaltandon.kt/creating-a-dotted-line-in-unity-ca044d02c3e2, so props to him for such a good tutorial.
Note: to get the dotted line to stop on bubbles I added a collider and a tag to the bubbles. Then I just check for the tag in the code. To ensure the line bounces, I add a different tag to those and just bounce whenever I hit a wall.
using System.Collections.Generic;
using UnityEngine;
public class DottedLine : MonoBehaviour
{
Vector3 mp;
Vector2 mousePosition;
Vector2 start;
public Sprite Dot;
[Range(0.01f, 1f)]
public float Size;
[Range(0.1f, 2f)]
public float Delta;
List<Vector2> positions;
List<GameObject> dots;
private void Start()
{
start = new Vector2(transform.position.x, transform.position.y);
positions = new List<Vector2>();
dots = new List<GameObject>();
}
void Update()
{
mp = Camera.main.ScreenToWorldPoint(Input.mousePosition);
mousePosition = new Vector2(mp.x, mp.y);
if (Input.GetMouseButtonDown(0))
{
DrawPoints(mousePosition);
}
}
public void DrawPoints(Vector2 mousePosition)
{
DestroyAllDots();
Vector2 point = start;
Vector2 end;
Vector2 direction = (mousePosition - start).normalized;
RaycastHit2D ray = Physics2D.Raycast(start, direction);
Vector3 rayPos = ray.point;
float distance = Vector2.Distance(ray.transform.position, start);
end = new Vector2(rayPos.x, rayPos.y);
//Only needs one line drawn
if (ray.collider.tag == "bottom" || ray.collider.tag == "bubble")
{
DrawOneLine(start, end, direction);
}
else if (ray.collider.tag == "wall")
{
//Will create a new line every time it hits a wall.
DrawOneLine(start, end, direction);
while (ray.collider.tag == "wall")
{
Vector3 newRayPos = ray.point;
Vector2 newStart = new Vector2(newRayPos.x, newRayPos.y);
direction = Vector2.Reflect(direction, Vector2.right);
//I had to put in a tolerances so a new raycast could start,
//there is likely a better way, feedback appreciated.
Vector2 rightTol = new Vector2(-.001f, 0);
Vector2 leftTol = new Vector2(.001f, 0);
if (ray.collider.name == "Right Wall")
ray = Physics2D.Raycast(newStart + rightTol, direction);
else
ray = Physics2D.Raycast(newStart + leftTol, direction);
rayPos = ray.point;
end = new Vector2(rayPos.x, rayPos.y);
DrawOneLine(newStart, end, direction, false);
}
}
Render();
}
public void DrawOneLine(Vector2 start, Vector2 end, Vector2 direction, bool drawFirst = true)
{
//Debug.Log($"Start: {start} End: {end} Direction: {direction}");
Vector2 point = start;
while ((end - start).magnitude > (point - start).magnitude)
{
if (drawFirst)
positions.Add(point);
point += (direction * Delta);
drawFirst = true;
}
}
GameObject GetOneDot()
{
var gameObject = new GameObject();
gameObject.transform.localScale = Vector3.one * Size;
gameObject.transform.parent = transform;
var sr = gameObject.AddComponent<SpriteRenderer>();
sr.sprite = Dot;
return gameObject;
}
private void Render()
{
foreach (var position in positions)
{
var g = GetOneDot();
g.transform.position = position;
dots.Add(g);
}
}
public void DestroyAllDots()
{
foreach (var dot in dots)
Destroy(dot);
dots.Clear();
positions.Clear();
}
}
Your dots are overshooting -- i.e. going past the left/right edges of your play area.
You forgot to include a visualization of said limits in your image, so here's my best guess.
You are checking when dot.transform.localPosition.x goes out of bounds, but when does, you're not correcting the dot's position!
The actual bounce is correctly implemented with Vector2.Reflect.
The simplest way to fix this would be to hide the last white dot:
if (dot.transform.localPosition.x <= leftEdge.x + Constants.BubbleRadius) {
dot.SetActive(false);
/* ... */
}
else if (dot.transform.localPosition.x >= rightEdge.x - Constants.BubbleRadius) {
dot.SetActive(false);
/* ... */
}
Misc remarks, not directly link to your problem:
You don't need to raycast from each dot, one raycast per bounce should be enough. You would then place the dots along that line.
new Vector3(0, 0, 0) can be expressed more succinctly as Vector3.zero
It's customary, instead of comparing a vector's magnitude to a value, to instead compare its sqrMagnitude to the square of that value. The performance delta is likely insignificant, it's a matter of taste.
I'm trying to get an object to move along a path and reset/move back to a starting position, ready to move down the path again.
The line in question reads sign_obj.transform.position = startpos; but it doesn't work even tho the debug line after it is executed!
(I'd pull out my hair if I had any!)
here's the code in full;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/* SignController
*
* controls the placement of and movement of signs
*
* by: Maurice Thompson-Hamilton
* */
public class SignController : MonoBehaviour {
public GameObject sign_center;
public GameObject sign_left;
public GameObject sign_right;
public float sign_distance;
public float sign_speed;
public bool enableSigns = true;
public Vector3 startPos = new Vector3();
private List<GameObject> hazards = new List<GameObject>();
private Vector3 sign_move;
// Use this for initialization
void Start () {
// starting pos of signs
if (startPos == Vector3.zero) { startPos = new Vector3(-12.5f, 14f, 165f); }
// set move distance if not set
if (sign_distance == 0) { sign_distance = 5; }
// set move speed if not set
if (sign_speed == 0) { sign_speed = 1.25f; }
//GameObject tmp = sign_center;
sign_center.transform.Rotate(new Vector3(0, 90f, 0));
sign_left.transform.Rotate(new Vector3(0, 90f, 0));
sign_right.transform.Rotate(new Vector3(0, 90f, 0));
sign_center.transform.position = startPos;
//Debug.Log(string.Format("sign_center {0}", startPos));
startPos.z += sign_distance;
sign_left.transform.position = startPos;
//Debug.Log(string.Format("sign_left {0}", startPos));
startPos.z += sign_distance;
sign_right.transform.position = startPos;
//Debug.Log(string.Format("sign_right {0}", startPos));
// add signs to list
hazards.Add(Instantiate(sign_center, startPos, Quaternion.identity) as GameObject);
hazards.Add(Instantiate(sign_left, startPos, Quaternion.identity) as GameObject);
hazards.Add(Instantiate(sign_right, startPos, Quaternion.identity) as GameObject);
// Debug.Log(string.Format("number of signs :{0}", hazards.Count));
}
// Update is called once per frame
void Update () {
//debug - test movement
//hazards[0].transform.Translate (Vector3.forward);
if (enableSigns) {
Vector3 tmp = new Vector3(0, 0, 0);
Vector3 player_pos = GameObject.FindGameObjectWithTag("PlayerTag").transform.position;
Debug.Log(player_pos.ToString());
// sign counter
int sc = hazards.Count;
if (sc > 0) {
foreach (GameObject sign_obj in hazards) {
// get current sign's pos
tmp = sign_obj.transform.position;
// get sample of new pos
tmp.z -= sign_distance;
// test if sample pos would move sign past player's view
//if (player_pos.z > tmp.z) {
int past_player = player_pos.z.CompareTo(tmp.z);
//Debug.Log(string.Format("past_player={0}, player_pos={1}, tmp.z={2}", past_player, player_pos.z, tmp.z));
if (past_player > 0) {
// if so, set translation vector
sign_move = new Vector3(0, 0, (float)sign_distance * sign_speed * Time.deltaTime);
// move sign
sign_obj.transform.Translate(sign_move);
} else {
// otherwise, reset sign pos to starting pos
sign_obj.transform.position = startPos;
Debug.Log(string.Format("startpos#{0}, sign_obj#{1}", startPos, sign_obj.transform.position));
//tmp.z = 165f;
/*
Destroy(sign_obj);
hazards.Remove(sign_obj);
*/
}
//sign_obj.transform.position = tmp;
//Debug.Log(string.Format("player#{0} - tmp#{1} - sign#{2}",player_pos, tmp, sign_obj.transform.position));
/* if visible
* - move towards player camera
* - if behind player
* - - remove sign (Destroy?)
* else
* */
}
}
}
}
}
I have left in the various tests (commented out) to see where the issue is. Copy, paste and un-comment as necessary.
I'm assuming that you want the signs' initial Z position to be greater than the players' initial Z position, and that the signs must move towards the player.
First thing you gotta do is correct the sign_speed in your code: it should be negative to move towards the players (lower) Z position.
if (sign_speed == 0) { sign_speed = -1.25f; }
Second thing is that you swapped the logic when calculating if the signs are past the player.
int past_player = tmp.z.CompareTo(player_pos.z);
If my assumptions are the inverse of what you want, just adapt my answer according to your needs: verify if the speed is set to the right (positive or negative) value, and check if the past_player comparison is being done right.
void Fire(float firingRate)
{
TimePerFrame += Time.deltaTime;
if(TimePerFrame >= firingRate)
{
Vector3 ProjectileDistance = new Vector3(0, 30, 0); //distance between center of the campion and it's head
GameObject beam = Instantiate(projectile, transform.position + ProjectileDistance, Quaternion.identity) as GameObject;
beam.GetComponent<Rigidbody2D>().velocity = new Vector3(0, projectileSpeed, 0);
// AudioSource.PlayClipAtPoint(fireSound, transform.position);
TimePerFrame = 0;
}
}
void Update ()
{
if (freezePosition == false)
{
Fire(firingRate);
PositionChaning();
firingRate = Mathf.Lerp(minFiringRate, maxFiringRate, 0.1f);
Debug.Log(firingRate);
}
}
I want my firerate to be flexible, i want it to start by shooting fast and let it automatically lower it's fire rate. (the bigger the firingRate float is the slower the speed is)
The problem is that firingRate = Mathf.Lerp(minFiringRate, maxFiringRate, 0.1f);
triggers once and only once. It doesn't seem to change it's value every frame.
Debug.Log(firingRate); tells the value every frame but it seems to remain a constant.
Why does this happen?
The update triggers every Frame, and so does your Mathf.Lerp However you are not changing the interpolation, which in your case is defined as 0.1f.
By changing this interpolation, you will be able to achieve the 'shifting' of fire rate.
In your case you could define a variable t outside the scope of your update, and update it inside the Update() through t += 0.5f * Time.deltaTime;
The Mathf.Lerp documentation has a pretty good sample of how todo so as well
void Update()
{
// animate the position of the game object...
transform.position = new Vector3(Mathf.Lerp(minimum, maximum, t), 0, 0);
// .. and increate the t interpolater
t += 0.5f * Time.deltaTime;
// now check if the interpolator has reached 1.0
// and swap maximum and minimum so game object moves
// in the opposite direction.
if (t > 1.0f)
{
float temp = maximum;
maximum = minimum;
minimum = temp;
t = 0.0f;
}
}
Your problem is here:
firingRate = Mathf.Lerp(minFiringRate, maxFiringRate, 0.1f);
As you can see here your t has to be calculated every frame.
public static float Lerp(float a, float b, float t);
You can change it like this:
private float fireTimer = 1.0f;
public float fireLimiter = 0.05f;
void Fire(float firingRate)
{
TimePerFrame += Time.deltaTime;
if(TimePerFrame >= firingRate)
{
Vector3 ProjectileDistance = new Vector3(0, 30, 0); //distance between center of the campion and it's head
GameObject beam = Instantiate(projectile, transform.position + ProjectileDistance, Quaternion.identity) as GameObject;
beam.GetComponent<Rigidbody2D>().velocity = new Vector3(0, projectileSpeed, 0);
// AudioSource.PlayClipAtPoint(fireSound, transform.position);
TimePerFrame = 0;
}
}
void Update ()
{
if (freezePosition == false)
{
if(fireTimer > 0.0f){
fireTimer -= Time.deltaTime * fireLimiter;
}
Fire(firingRate);
PositionChaning();
firingRate = Mathf.Lerp(minFiringRate, maxFiringRate, fireTimer);
Debug.Log(firingRate);
}
}
Do not forget to reset fireTimer to 1.0f after shooting!
The delay of the firerate can be controlled by fireLimiter
I'm working with Unity, in c#, and this is my setup.
The camera is facing down, to look at the cube. I'll be referring to the cube as the 'player' from now on.
The problem:
I want to spawn something where the camera cannot see it. This means I need to know exactly how far there is from the player to the end of the FOV, at the player's position.y (Which is always 0), in units.
The reason this cannot just be a constant variable is that the camera's position.y is not constant, and thus the player is allowed to see more of the game area the longer he plays.
There is extra info on what I've tried below, but if you have a better solution, I'll gladly take it.
Extra info:
This is the current script I'm sitting with:
using UnityEngine;
using System.Collections;
public class cameraFollowTarget : MonoBehaviour {
private Transform target;
private float defaultHeight = 3.0f;
private float cameraY;
private float playerSize;
private float moveSpeed = 2.0f;
//Playing with FOV//
private float verticalFOVrad;
private float verticalFOV;
private float cameraHeightAt1;
private float horizontalFOVrad;
private float horizontalFOV;
private float angleToPlayer;
private float defaultAngleToPlayer;
private Vector3 currentPos;
private Vector3 targetPos;
private Vector3 targetLookDir;
private Vector3 leftSideToCamera;
private Vector3 rightSideToCamera;
// Use this for initialization
void Start () {
target = GameObject.FindGameObjectWithTag ("Player").transform;
playerSize = target.GetComponent<playerCollect>().getPlayerSizeStart();
transform.position = new Vector3(transform.position.x, defaultHeight, transform.position.z);
cameraY = defaultHeight;
calculateAngleToPlayer();
defaultAngleToPlayer = angleToPlayer;
}
// Update is called once per frame
void FixedUpdate () {
if (transform.position != target.position) {
playerSize = GameObject.FindGameObjectWithTag("Player").GetComponent<playerCollect>().getPlayerSize();
currentPos = transform.position;
calculateAngleToPlayer();
while(angleToPlayer > defaultAngleToPlayer)
{
cameraY += 0.1f;
calculateAngleToPlayer();
}
targetPos = new Vector3(target.position.x, cameraY, target.position.z);
transform.position = Vector3.Slerp(currentPos, targetPos, moveSpeed * Time.deltaTime);
Camera.main.farClipPlane = cameraY + playerSize / 2;
}
if (transform.rotation != target.rotation) {
Vector3 targetRot;
//Not working
//targetRot = Vector3.Lerp (transform.eulerAngles, new Vector3(90, target.transform.eulerAngles.y, 0), turnSpeed * Time.deltaTime);
//Working but ugly
targetRot = new Vector3(90, target.transform.eulerAngles.y, 0);
transform.eulerAngles = targetRot;
}
//Playing with FOV//
getVerticalFOV ();
getHorizontalFOV ();
print ("Horizontal = " + getHorizontalFOV());
print ("Vertical = " + getVerticalFOV());
}
float getVerticalFOV()
{
horizontalFOVrad = Camera.main.fieldOfView * Mathf.Deg2Rad;
cameraHeightAt1 = Mathf.Tan(horizontalFOVrad * 0.5f);
verticalFOVrad = Mathf.Atan(cameraHeightAt1 * Camera.main.aspect) * 2;
verticalFOV = verticalFOVrad * Mathf.Rad2Deg;
return verticalFOV;
}
float getHorizontalFOV()
{
horizontalFOV = Camera.main.fieldOfView;
return horizontalFOV;
}
float calculateAngleToPlayer()
{
Vector3 imaginaryPos = new Vector3(target.position.x, transform.position.y + cameraY, target.position.z);
leftSideToCamera = new Vector3(target.position.x - (playerSize / 2.0f), target.position.y, target.position.z) - imaginaryPos;
rightSideToCamera = new Vector3(target.position.x + (playerSize / 2.0f), target.position.y, target.position.z) - imaginaryPos;
angleToPlayer = Vector3.Angle(leftSideToCamera, rightSideToCamera);
return angleToPlayer;
}
public float getScreenUnitHorizontal(GameObject target)
{
GameObject imaginaryPos = new GameObject ();
imaginaryPos.transform.position = target.transform.position;
imaginaryPos.transform.rotation = Camera.main.transform.rotation;
float screenUnitHorizontal = 0.0f;
Vector3 vecMidToCamera = Camera.main.transform.position - new Vector3 (Camera.main.transform.position.x, 0.0f, Camera.main.transform.position.z);
Vector3 vecImagiToCamera = Camera.main.transform.position - imaginaryPos.transform.position;
while (Vector3.Angle(vecMidToCamera, vecImagiToCamera) <= getHorizontalFOV() / 2) {
screenUnitHorizontal += 0.1f;
imaginaryPos.transform.position = target.transform.forward * screenUnitHorizontal * Time.deltaTime;
vecMidToCamera = Camera.main.transform.position - new Vector3 (Camera.main.transform.position.x, 0.0f, Camera.main.transform.position.z);
vecImagiToCamera = Camera.main.transform.position - imaginaryPos.transform.position;
}
Debug.DrawLine(Camera.main.transform.position, new Vector3 (Camera.main.transform.position.x, 0.0f, Camera.main.transform.position.z), Color.red);
Debug.DrawLine(Camera.main.transform.position, imaginaryPos.transform.position, Color.red);
Destroy (imaginaryPos);
return screenUnitHorizontal;
}
}
"GetScreenUnitHorizontal" is the main problematic function. It is working, but it is not doing what I was intending.
My intention:
Create a gameobject at the player's position. Then move the gameobject forward until it reaches an angle that is greater than the FOV.
This works fine, in (0, 0, 0). It does not if I move away, and rotate.
If I keep rotating one way, the gameobject seem to place itself in some kind of curve. Possibly a sinus curve.
The solution
31eee384 was the one who solved it for me.
The part of my code that is the solution looks like this:
public class cameraFollowTarget : MonoBehaviour {
private Ray lowerLeft;
private Ray lowerRight;
private Ray upperLeft;
private Ray upperRight;
void FixedUpdate () {
lowerLeft = Camera.main.ScreenPointToRay(new Vector3(0, 0));
lowerRight = Camera.main.ScreenPointToRay(new Vector3(Camera.main.pixelWidth, 0));
upperLeft = Camera.main.ScreenPointToRay(new Vector3(0, Camera.main.pixelHeight));
upperRight = Camera.main.ScreenPointToRay(new Vector3(Camera.main.pixelWidth, Camera.main.pixelHeight));
}
Vector3 getScreenCollisionPoint(Ray corner)
{
Plane spawnPlane = new Plane (new Vector3 (0, 1, 0), Vector3.zero);
float _Distance;
Vector3 collisionPoint;
if (spawnPlane.Raycast (corner, out _Distance) == true) {
collisionPoint = corner.GetPoint(_Distance);
}
Debug.DrawLine(transform.position, collisionPoint, Color.red);
return collisionPoint;
}
If the function getScreenCollisionPoint is called in Update(), it will keep showing where the corners are.
In my setup I also have:
print ("upperLeft = " + getScreenCollisionPoint (upperLeft).z + "," + getScreenCollisionPoint (upperLeft).z);
print ("upperRight = " + getScreenCollisionPoint (upperRight).z + "," + getScreenCollisionPoint (upperRight).z);
print ("lowerLeft = " + getScreenCollisionPoint (lowerLeft).z + "," + getScreenCollisionPoint (lowerLeft).z);
print ("lowerRight = " + getScreenCollisionPoint (lowerRight).z + "," + getScreenCollisionPoint (lowerRight).z);
so that I can see what the values of x and z are, for each of the corners.
Check out Camera.ScreenPointToRay. You can call that with four vectors to get a Ray at each corner of the screen (passing <0,0,0> <x,0,0> <0,y,0> <x,y,0> where x = pixelWidth and y = pixelHeight).
To do this with the main camera:
Ray upperLeft = Camera.main.ScreenPointToRay(new Vector3(0, Camera.main.pixelHeight, 0));
Then, you need to do a Plane.Raycast on a x-z (flat) plane to find the position of each corner where y = 0.
Plane spawnPlane = new Plane(new Vector3(0, 1, 0), Vector3.zero);
float distance;
if (spawnPlane.Raycast(upperLeft, out distance))
{
// The cast has collided! Now find out where it hit.
Vector3 collisionPoint = upperLeft.GetPoint(distance);
}
collisionPoint is then the point where y = 0 corresponding to the upper-left corner of the screen!
Doing this for each corner of the screen gives you the square where the camera can see if you connect up the points. (A trapezoid if you choose to rotate the camera.) You can use that "viewable x-z plane shape" to do whatever else you need to do!
To do what you're trying to do now directly, you can instead use ScreenPointToRay with <pixelWidth/2, pixelHeight, 0> to find the ray at the top-center of the camera view.
To see this at work, I made a script that draws debug lines onto the x-z origin plane using this technique. Copy this code into a new component and add it to the camera:
using System.Linq;
using UnityEngine;
public class Test : MonoBehaviour
{
void Update()
{
Camera cam = GetComponent<Camera>();
Ray[] rays = new[]
{
new Vector3(0, 0),
new Vector3(0, cam.pixelHeight),
new Vector3(cam.pixelWidth, 0),
new Vector3(cam.pixelWidth, cam.pixelHeight)
}.Select(ray => cam.ScreenPointToRay(ray)).ToArray();
Plane xz = new Plane(new Vector3(0, 1, 0), Vector3.zero);
foreach (Ray ray in rays)
{
float distanceAlongRay;
if (xz.Raycast(ray, out distanceAlongRay))
{
Vector3 intersect = ray.GetPoint(distanceAlongRay);
Debug.DrawLine(transform.position, intersect, Color.red);
}
}
}
}
Assuming the camera always stays exactly above the player position, the distance from the player position to the edge of the camera's field of view is:
tan(0.5 * FOV) * camera_height
Where FOV is the field of view in the direction you want to move the object, tan is the tangent (Mathf.Tan), and camera_height is the vertical distance from the player to the camera.
I have a Controller which moves my object diagonally. The left arrow should move the player forward and to the left 45 degrees, and the right arrow the same to the right. I would like to move the player relatively to its current position. Right now it moves relatively to the point(0,0,0).
My code:
public class JollyJumper : MonoBehaviour {
protected CharacterController control;
public float fTime = 1.5f; // Hop time
public float fRange = 5.0f; // Max dist from origin
public float fHopHeight = 2.0f; // Height of hop
private Vector3 v3Dest;
private Vector3 v3Last;
private float fTimer = 0.0f;
private bool moving = false;
private int number= 0;
private Vector2 direction;
public virtual void Start () {
control = GetComponent<CharacterController>();
if(!control){
Debug.LogError("No Character Controller");
enabled=false;
}
}
void Update () {
if (fTimer >= fTime&& moving) {
var playerObject = GameObject.Find("Player");
v3Last = playerObject.transform.position;
Debug.Log(v3Last);
v3Dest = direction *fRange;
//v3Dest = newVector* fRange + v3Last;
v3Dest.z = v3Dest.y;
v3Dest.y = 0.0f;
fTimer = 0.0f;
moving = false;
}
if(Input.GetKeyDown(KeyCode.LeftArrow)){
moving = true;
direction = new Vector2(1.0f, 1.0f);
number++;
}
if(Input.GetKeyDown(KeyCode.RightArrow)){
moving = true;
direction = new Vector2(-1.0f, 1.0f);
number++;
}
if(moving){
Vector3 v3T = Vector3.Lerp (v3Last, v3Dest, fTimer / fTime);
v3T.y = Mathf.Sin (fTimer/fTime * Mathf.PI) * fHopHeight;
control.transform.position = v3T;
fTimer += Time.deltaTime;
}
}
}
How can resolve this? Any ideas? Thanks a lot!
The short answer is: you hard-coded two locations you want to jump to: points (1, 1) and (-1, 1). You should create new Vector each time you start jumping. Replace each
direction = new Vector2(1.0f, 1.0f);
with this line:
v3Dest = transform.position + new Vector3(1.0f, 0, 1) * fRange;
and it should work.
While I'm on it, there are some other things I want to point:
There is a lot of floating point error after each jump. Notice that in your code v3T will never be equal to v3Dest (you never actually reach your destination), because you switch the moving flag earlier. You should explicitly set your position to v3Dest when the jump is over.
You are checking jump timers etc. every frame. A more elegent solution is to start a coroutine.
You use a sinusoid as your jump curve, which looks ok, but using a parabola would be conceptually more correct.
Right now it is possible to start next jump mid-air (I'm not sure whether it is intended or not)
Here is some code you may use that avoids those problems:
using System.Collections;
using UnityEngine;
public class Jumper : MonoBehaviour
{
#region Set in editor;
public float jumpDuration = 0.5f;
public float jumpDistance = 3;
#endregion Set in editor;
private bool jumping = false;
private float jumpStartVelocityY;
private void Start()
{
// For a given distance and jump duration
// there is only one possible movement curve.
// We are executing Y axis movement separately,
// so we need to know a starting velocity.
jumpStartVelocityY = -jumpDuration * Physics.gravity.y / 2;
}
private void Update()
{
if (jumping)
{
return;
}
else if (Input.GetKeyDown(KeyCode.LeftArrow))
{
// Warning: this will actually move jumpDistance forward
// and jumpDistance to the side.
// If you want to move jumpDistance diagonally, use:
// Vector3 forwardAndLeft = (transform.forward - transform.right).normalized * jumpDistance;
Vector3 forwardAndLeft = (transform.forward - transform.right) * jumpDistance;
StartCoroutine(Jump(forwardAndLeft));
}
else if (Input.GetKeyDown(KeyCode.RightArrow))
{
Vector3 forwardAndRight = (transform.forward + transform.right) * jumpDistance;
StartCoroutine(Jump(forwardAndRight));
}
}
private IEnumerator Jump(Vector3 direction)
{
jumping = true;
Vector3 startPoint = transform.position;
Vector3 targetPoint = startPoint + direction;
float time = 0;
float jumpProgress = 0;
float velocityY = jumpStartVelocityY;
float height = startPoint.y;
while (jumping)
{
jumpProgress = time / jumpDuration;
if (jumpProgress > 1)
{
jumping = false;
jumpProgress = 1;
}
Vector3 currentPos = Vector3.Lerp(startPoint, targetPoint, jumpProgress);
currentPos.y = height;
transform.position = currentPos;
//Wait until next frame.
yield return null;
height += velocityY * Time.deltaTime;
velocityY += Time.deltaTime * Physics.gravity.y;
time += Time.deltaTime;
}
transform.position = targetPoint;
yield break;
}
}