I looking for the programming logic behind the following scenario: I'm trying to change the color of sprites from Blue -> Red as they are get more and more far away from a particular point on space.
So, as the sprite as at a further distance from a particular point in screen, the color of their SprriteRenderer should change accordingly.
This is what I've done right now:
if (distanceBetweemCenterAndSprites > 10.0F)
{
sprites[pos]
.GetComponentInChildren<SpriteRenderer>()
.color = new Color(1.0F, 0.0F, 0.0F);
}
The code is simply calculates the distance between the centre (the point) and the sprites. If Distance > 10.0F, the color of all the sprites become red. What I want is a progressive change in color (from Blue -> Red) but I can't seem to find a logic to do so.
public class ColorShifter : MonoBehaviour
{
public float MinDistance = 1f;
public float MaxDistance = 10f;
public Transform Target;
protected SpriteRenderer SpriteRenderer;
protected void Awake()
{
SpriteRenderer = GetComponent<SpriteRenderer>();
}
protected void Update()
{
var distance = Vector3.Distance(transform.position, Target.transform.position);
var ratio = Mathf.Clamp01((distance - MinDistance) / (MaxDistance - MinDistance));
var inverseRatio = 1f - ratio;
SpriteRenderer.color = new Color(ratio * ratio, 0f, inverseRatio * inverseRatio);
}
}
Assign this script to a sprite and don't forget to set Target.
This is a basic color interpolation. This topic can get really tough depending on how deep you want to dive. Search for color interpolation techniques if you think it won't be enough. But I hope this code gives some idea.
Related
So I want to make a cube scaling by my mouse drag. However, the cube would be scaling in the up and down direction, I only want to scale in the up direction.
Here is my code, could someone please help me?
// Start is called before the first frame update
void Start()
{
InitialScale = transform.localScale;
InitialPos = transform.localPosition;
mainCamera = Camera.main;
CameraZDistance = mainCamera.WorldToScreenPoint(transform.position).z;
}
void Update()
{
onMouseDrag();
}
private void onMouseDrag()
{
if (Input.GetMouseButton(0)) {
Vector3 MouseScreenPosition = new Vector3(Input.mousePosition.x, Input.mousePosition.y, CameraZDistance);
Vector3 mouseWorldPosition = mainCamera.ScreenToWorldPoint(MouseScreenPosition); // Screen point convert to world point
//change transform
float distance = Vector3.Distance(InitialPos, mouseWorldPosition);
transform.localScale = new Vector3(InitialScale.x, distance / 2f, InitialScale.z);
}
}
By default, when you create a gameobject (cube in this case), it's pivot is the center of the cube. So when you scale it in the y direction, it scales up and down equally. To scale only upwards, you want to put the pivot at the bottom of the cube, at position (0.5, 0, 0.5). To change the pivot position, you can do it in the transform section of the inspector panel in the unity editor. Alternative you can also do it through scripting by similarly referencing the transform of that cube.
Edit:
#Llama solution would work (moving the cube upwards as you scale it). I've used this solution before and if I recall correctly, there were no performance issues, however using the pivots would be the proper way to do it and I would assume it would perform better and more smoothly.
I am using c# in unity and I need to calculate the lowest y position of the camera (2D) however I only know how to find the height i.e.
private Vector2 screenBounds;
void Start()
{
screenBounds = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, Camera.main.transform.position.z));
}
void Update()
{
screenBounds = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, Camera.main.transform.position.z));
if (transform.position.y < screenBounds.y)
Destroy(this.gameObject);
}
}
I am trying to use this code to despawn old objects.
What you get is the top-right corner
Screenspace is defined in pixels. The bottom-left of the screen is (0,0); the right-top is (pixelWidth,pixelHeight).
Also I assume your camera position is e.g. z = -10 and you want a world point in front and not behind the camera
So if you want the bottom border you would rather use
screenBounds = Camera.main.ScreenToWorldPoint(new Vector3 (0, 0, -Camera.main.transform.position.z);
If your camera is orthogonal anyway then you don't have to care about the z at all and can just use
screenBounds = Camera.main.ScreenToWorldPoint(Vector2.zero);
In order to not have to calculate twice if you also want to check later if it is above the screen you could also go the other way round though which is often even easier:
// Other than the ScreenToWorldPoint here it doesn't matter whether
// the camera is orthogonal or perspective or how far in front of the camera you want a position
// or if your camera is moved or rotated
var screenPos = Camera.main.WorldToScreenPoint(transform.position);
if(screenPos.y < 0) Debug.Log("Below screen");
else if(screenPos.y > Screen.height) Debug.Log("Above screen");
if(screenPos.x < 0) Debug.Log("Left of screen");
else if(screenPos.x > Screen.width) Debug.Log("Right of screen");
However, using only the transform.position is a bit unreliable since you already would destroy objects that are still (half) visible.
Instead you could use GeometryUtility.CalculateFrustumPlanes to get the planes surrounding the actual camera frustum and then use GeometryUtility.TestPlanesAABB to check wether your objects' Renderer.bounds are actually visible to the camera like e.g.
Renderer _renderer;
Camera _camera;
void Update()
{
if(!_renderer) _renderer = GetComponent<Renderer>();
if(!_camera) _camera = Camera.main;
var planes = GeometryUtility.CalculateFrustumPlanes(_camera);
if (!GeometryUtility.TestPlanesAABB(planes, _renderer.bounds))
{
Debug.Log($"\"{name}\" is outside of the screen!", this);
// and e.g.
Destroy(gameObject);
}
}
If I understand correctly you want to save the initial box the camera sees and use that as a boundary?
If that's the case then it's easy to do in 2D, but gets much more complex in 3D. So below is the 2D solution.
The camera is in the center of the viewport. Meaning that the top boundry is the Camera position plus half the height of the viewport.
// Get the camera height
float height = Screen.height;
// Now we get the position of the camera
float camY = Camera.Main.transform.position.y;
// Now Calculate the bounds
float lowerBound = camY + height / 2f;
float upperBound = camY - height / 2f;
I am working in a Game which is pretty similar to Mario. So when player touches the coin object in World Space, I need to animate by moving that coin object to Coin meter, when the render mode of Canvas is Screen Space - Overlay, I can get the sprite object position easily with below code
CoinSprite Code
GameObject coinCanvasObject = Instantiate(prefab, canvas.transform);//Instantiate coin inside Canvas view
coinCanvasObject.transform.position = Camera.main.WorldToScreenPoint(coinSpriteObject.transform.position);//getting coin position from World Space and convert to Screen Space and set to coinCanvasobject position
AnimateCoin animate = coinCanvasObject.GetComponent<AnimateCoin>();
animate.animateCoin(coinSpriteObject.transform.position);
coinSpriteObject.SetActive(false);
AnimateCoin
public class AnimateCoin : MonoBehaviour
{
private float speed = 0f;
private bool isSpawn = false;
private Vector3 screenPos;
public void animateCoin(Vector3 screenPosTemp, Camera cam, Canvas canvas)
{
screenPos = Camera.main.WorldToScreenPoint(screenPosTemp);
isSpawn = true;
}
private void Update()
{
if (isSpawn)
{
speed += 0.025f;
transform.position = Vector3.Lerp(screenPos, targetObject.transform.position, speed);
if (Vector3.Distance(transform.position, targetObject.transform.position) <= 0)
{
StartCoroutine(deActivateCoin());
}
}
}
private IEnumerator deActivateCoin()
{
isSpawn = false;
yield return new WaitForSeconds(0.2f);
gameObject.SetActive(false);
}
}
Since I need to bring particle effect into Canvas view, I am changing the Canvas render mode to Screen Space - Camera.
When I change the Canvas to this render mode I could not get the exact sprite object position to trail the coin effect.
Hope this helps:
public Camera cam; // Camera containing the canvas
public Transform target; // object in the 3D World
public RectTransform icon; // icon to place in the canvas
public Canvas canvas; // canvas with "Render mode: Screen Space - Camera"
void Update()
{
Vector3 screenPos = cam.WorldToScreenPoint(target.position);
float h = Screen.height;
float w = Screen.width;
float x = screenPos.x - (w / 2);
float y = screenPos.y - (h / 2);
float s = canvas.scaleFactor;
icon.anchoredPosition = new Vector2(x, y) / s;
}
PD: It worked perfectly for me in a 2D video game, I didn't test it in a 3D game, but I think it should work too.
I rewrote my previous solution because it might not work correctly on some devices with non-standard resolutions.
This code should always work.
uiObject.anchoredPosition = GetUIScreenPosition(myPin.position, cam3d, uiObject.anchorMin);
public static Vector2 GetUIScreenPosition(Vector3 obj3dPosition, Camera cam3d, Vector2 anchor)
{
Vector2 rootScreen = _rootCanvasRect.sizeDelta;
Vector3 screenPos = cam3d.WorldToViewportPoint(obj3dPosition);
return (rootScreen * screenPos) - (rootScreen * anchor);
}
We take the sizeDelta of our UI Canvas, because it may differ from the screen resolution of the device.
Then we cast the WorldToViewportPoint from our 3d camera to get the relative position on the screen in the format from 0 to 1 by X and Y.
With anchors in the lower left corner ((0,0)(0,0)) this is our final anchoredPosition. However with anchors for example in the center ((0.5,0.5)(0.5,0.5)) we need to adjust the positions by subtracting half the canvas size.
In this example, we will get an unpredictable result when using different min and max anchors in the final object. For example ((0,25,0.25)(0.75,0.75)). But I sincerely doubt that you really need such anchors on an object with a dynamic position depending on the 3d object.
I want an UI canvas to follow the camera so it will be in front of the head always and also interactable like VR menu. I'm using the following code to do so.
public class FollowMe : MonoBehaviour
{
public GameObject menuCanvas;
public Camera FirstPersonCamera;
[Range(0, 1)]
public float smoothFactor = 0.5f;
// how far to stay away fromt he center
public float offsetRadius = 0.3f;
public float distanceToHead = 4;
public void Update()
{
// make the UI always face towards the camera
menuCanvas.transform.rotation = FirstPersonCamera.transform.rotation;
var cameraCenter = FirstPersonCamera.transform.position + FirstPersonCamera.transform.forward * distanceToHead;
var currentPos = menuCanvas.transform.position;
// in which direction from the center?
var direction = currentPos - cameraCenter;
// target is in the same direction but offsetRadius
// from the center
var targetPosition = cameraCenter + direction.normalized * offsetRadius;
// finally interpolate towards this position
menuCanvas.transform.position = Vector3.Lerp(currentPos, targetPosition, smoothFactor);
}
}
Unfortunately, the canvas is flickering in front fo the camera and it is not properly positioned. How do I make the menu to follow the camera?|
If there is no reason against it you can use a ScreenSpace - Camera canvas as stated in the docs. Then you can reference your FPS camera as the rendering camera for the canvas.
Easy way to do this is using Screen Space - Camera mode which you can setup from Canvas component and in Render Mode properties.
Second way if you want more control over how your canvas should behave then you can use Canvas Render Mode - "World Space" and then using script you can handle canvas a some gameobject.
I wish to implement a way to create a hoverbike.
my current code to hover is
readonly float yForce = 80;
Physics.Raycast(hoverbike.transform.position, Vector3.down, out hit);
Debug.Log(hit.distance);
if (hit.distance < 10 && hit.distance > 0)
{
if (hoverbike.velocity.y < 0.1)
{
hoverbike.AddForce(0, yForce, 0, ForceMode.Acceleration);
Debug.Log("applying force!");
}
}
This works, but not well, the vehicle bounces up and down. I also tried to subtract the exact same force as the bike's y velocity, but the vehicle slowly drifted down, and did not go up to my desired height of 10 units from the ground. How can I achieve this?
Simply counteracting its current velocity is easy, but how do I make it float back up to the desired height?
It is much easier to simply turn off gravity than to constantly be fighting against it; this frequent readjustment is likely the cause of your bounciness. Upon bike activation you can take the object's y velocity calculations fully into your own hands as so:
public class Bike : MonoBehaviour
{
private Rigidbody hoverbike;
private bool isBikeActive = false;
[SerializeField] private float verticalSpeedMultiplier = 1f;
[SerializeField] private float hoverHeight = 10f;
[SerializeField] private float hoverTolerance = 0.5f;
[SerializeField] private float maximumVerticalVelocity = 10f;
private void Awake()
{
hoverbike = GetComponent<Rigidbody>();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space)) { ToggleBike(); }
if (isBikeActive)
{
Physics.Raycast(hoverbike.transform.position, Vector3.down, out RaycastHit hit);
Vector3 modifiedVelocity = hoverbike.velocity;
if ((hit.distance > hoverHeight - hoverTolerance) && (hit.distance < hoverHeight + hoverTolerance))
{
modifiedVelocity.y = 0f;
}
else
{
modifiedVelocity.y = -(hit.distance - hoverHeight) * verticalSpeedMultiplier;
modifiedVelocity.y = Mathf.Clamp(modifiedVelocity.y, -maximumVerticalVelocity, maximumVerticalVelocity);
}
Debug.Log($"Distance from ground: {hit.distance}, Bike Velocity.y: {modifiedVelocity}");
hoverbike.velocity = modifiedVelocity;
}
}
private void ToggleBike()
{
isBikeActive = !isBikeActive;
hoverbike.useGravity = !isBikeActive;
}
}
Your bike will now always try to move towards the point hoverHeight units above the object below it, until it is within hoverTolerance from that point. It will also move much more smoothly towards this point, moving faster the further away it is from the intended height.
If you wish for the bike to still bob up and down a little, this can be achieved by modifying the hoverHeight slowly over time, perhaps through use of a Sine function.
Hovering is (essentially) a visual effect
Make the collider extend below the vehicle so that when it rests on the ground the bike appears to be hovering at the desired height. The physics engine only does physics. It doesn't care about what those colliders are, it just wants them to behave in a physics-y way, and if that means falling until it reaches the ground, then let them fall until they reach the ground. Take advantage of the physics engine instead of going around it and then trying to solve the bugs created by going around the physics engine.
First, apply your dampening force scaled by the downward velocity, then apply an additional force scaled by how far it needs to travel back upwards. Keep track of how much force/acceleration you apply through this process and cap that amount at some constant.
readonly float yForce = 80f; // requires tuning
readonly float dampenFactor = 0.8f; // requires tuning
readonly float offsetFactor = 0.5f; // requires tuning
readonly float targetHeight = 10f
Physics.Raycast(hoverbike.transform.position, Vector3.down, out hit);
Debug.Log(hit.distance);
if (hit.distance < targetHeight && hit.distance > 0)
{
float availableForce = yForce;
// cancel out downward velocity
if (hoverbike.velocity.y < 0)
{
// Cap out upward force based on yForce
float cappedDampenForce = Mathf.Min(dampenFactor * -hoverbike.velocity.y,
availableForce);
// How much force is available for the offset?
availableForce -= cappedDampenForce;
hoverbike.AddForce(Vector3.up * cappedDampenForce, ForceMode.Acceleration);
Debug.Log("applied dampening force");
}
// Find upward force scaled by distance left to target height, and cap that amount
float cappedOffsetForce = Mathf.Min(offsetFactor * (targetHeight - hit.distance),
availableForce);
hoverbike.AddForce(Vector3.up * cappedOffsetForce, ForceMode.Acceleration);
Debug.Log("applied offset force");
}