How to get width difference between the canvas and a scaled image and do an efficient PingPong effect in Unity3D - unity3d

I am trying to get the width difference between the canvas and a image which is scaled by CanvasScaler in order to create translation between the image and his border.
Illustration:
How get the size of the red arrow?
[EDIT 1]
The code snippet bellow give me a possible correct result
var dist = (canvasRectTransform.rect.width - image.sprite.rect.width) / 2;
But It seems to be incorrect:
public class Background : Monobehaviour
{
private float dist;
private float _percentage;
private float _currentLerpTime;`
private readonly Dictionary<LerpDirection, Func<Vector3>> _lerpDirectionActions;
public float lerpTime;
void Awake()
{
var image = GetComponent<Image>();
var canvasRectTransform = GetComponentInParent<RectTransform>();
dist = (canvasRectTransform.rect.width - image.sprite.rect.width) / 2;
_lerpDirectionActions = new Dictionary<LerpDirection, Func<Vector3>>
{
[LerpDirection.Left] = LerpToLeft,
[LerpDirection.Right] = LerpToRight
};
}
private Vector3 Lerp()
{
return Vector3.Lerp(
transform.position,
_lerpDirectionActions[lerpDirection].Invoke(), // will call LerpToRight or LerpToLeft
_percentage
);
}
private float LerpX => Lerp().x;
private Vector3 LerpToRight()
{
return new Vector3(transform.position.x - dist, transform.position.y);
}
private Vector3 LerpToLeft()
{
return new Vector3(transform.position.x + dist , transform.position.y);
}
void Update()
{
_currentLerpTime += Time.deltaTime;
_percentage = _currentLerpTime / lerpTime;
var localPositionX = tranform.position.x;
var mustGoRight = localPositionX <= 0 && lerpDirection == LerpDirection.Right;
var mustGoLeft = localPositionX >= dist && lerpDirection == LerpDirection.Left;
if (mustGoLeft || mustGoRight)
{
direction = direction.Invert(); // invert direction
Reset();
}
tranform.position = new Vector3(LerpX, tranform.position.y)
}
}
The Background script is applied to the Background GameObject.
_lerpDirectionActions[lerpDirection].Invoke()
This code above will invoke the right function for lerping on left or on right.
Illustration:
The translation change his direction but not when the canvas is on the border on the image.

The value you are searching for would be
var difference = (canvas.GetComponent<RectTransform>().rect.width - image.GetComponent<RectTransform>().rect.width) / 2f;
Your script looks quite complicated to be honest.
Why not simply put it all into one single Coroutine using Mathf.PingPong which does exactly what you are currently controlling with your direction flags and actions
PingPongs the value t, so that it is never larger than length and never smaller than 0.
The returned value will move back and forth between 0 and length.
public Canvas canvas;
public Image image;
// Time in seconds to finish the movement from one extreme to the other
public float duration = 1;
private void Start()
{
StartCoroutine (LerpForthAndBack());
}
private IEnumerator LerpForthAndBack()
{
var difference = (canvas.GetComponent<RectTransform>().rect.width - image.GetComponent<RectTransform>().rect.width) / 2f;
var maxPosition = Vector3.right * difference;
var minPosition = Vector3.left * difference;
// Hugh? :O
// -> This is actually totally fine in a Coroutine
// as long as you yield somewhere within it
while(true)
{
image.transform.position = Vector3.Lerp(minPosition, maxPosition, Mathf.PingPong(Time.time / duration, 1));
// "Pause" the routine, render the frame and
// and continue from here in the next frame
yield return null;
}
}
ofcourse you could also do the same still in Update
private Vector3 minPosition;
private Vector3 maxPosition;
private void Start()
{
var difference = (canvas.GetComponent<RectTransform>().rect.width - image.GetComponent<RectTransform>().rect.width) / 2f;
maxPosition = Vector3.right * difference;
minPosition = Vector3.left * difference;
}
private void Update()
{
image.transform.position = Vector3.Lerp(minPosition, maxPosition, Mathf.PingPong(Time.time / duration, 1));
}

Related

Smoothly change the player's speed when he leaves the zone

I'm trying to make special zone located on ground that accelerates the player, entering which the player's speed increases. When he gets out of it, the speed smoothly returns to the original. But now, when the player leaves the zone, the speed instantly becomes the original. Don't know how to realize it. Please help:)
My code:
PlayerMovement:
[SerializeField] private Rigidbody _rigidoby;
public bool inZone = false;
private void Update()
{
if (inZone == false)
{
_rigidoby.velocity = new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0, Input.GetAxis("Vertical") * Time.deltaTime) * 500;
}
}
SpecialZone:
private Vector3 _cachedVelocity;
private Rigidbody _collisionRigidbody;
[SerializeField] private PlayerMovement _player;
private void OnTriggerStay(Collider other)
{
_player.inZone = true;
if (_collisionRigidbody == null)
{
_collisionRigidbody = other.GetComponent<Rigidbody>();
_cachedVelocity = _collisionRigidbody.velocity;
}
_collisionRigidbody.velocity = new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0, Input.GetAxis("Vertical") * Time.deltaTime) * 1000;
}
private void OnTriggerExit(Collider other)
{
_collisionRigidbody.velocity = _cachedVelocity;
_player.inZone = false;
}
A simple solution can be to gradually decrease the speed in the update once you exit the trigger like this:
private void OnTriggerExit(Collider other)
{
//_collisionRigidbody.velocity = _cachedVelocity;
_player.inZone = false;
}
private void Update()
{
if (inZone == false)
{
_rigidoby.velocity = new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0, Input.GetAxis("Vertical") * Time.deltaTime) * 500;
} else {
If (_collisionRigidbody.velocity > _cachedVelocity;)
_collisionRigidbody.velocity -= 0.01;
}
}
Another could be to launch a coroutine when you exit the trigger.
Alternatively, you could use Vector3.Lerp and set the percent of the speed change in the update like this:
Vector3 _startingSpeedBeforeDeceleration = Vector3.zero;
private void OnTriggerExit(Collider other)
{
//_collisionRigidbody.velocity = _cachedVelocity;
_startingSpeedBeforeDeceleration = _collisionRigidbody.velocity;
_player.inZone = false;
}
float percent = 0f; //from 0 to 1
private void Update()
{
if (inZone == false)
{
_rigidoby.velocity = new Vector3(Input.GetAxis("Horizontal") * Time.deltaTime, 0, Input.GetAxis("Vertical") * Time.deltaTime) * 500;
} else {
If (_collisionRigidbody.velocity > _cachedVelocity;) {
_collisionRigidbody.velocity = Vector3.Lerp(_startingSpeedBeforeDeceleration, _cachedVelocity, percent);
percent += 0,01f;
}
}
}
Not debugged code. Note that I kept the departing speed in the variable _startingSpeedBeforeDeceleration, that is optional, and depends on the movement behaviour you want.
You can play around with Vector3.Lerp for example changing the first argument _startingSpeedBeforeDeceleration as a fixed value, and put there rigidbody speed every time it changes like this Vector3.Lerp(_collisionRigidbody.velocity, _cachedVelocity, percent);, that way you have a smooth start and smooth end, and a top speed change at the middle of the path. Also the percent change can be handled to set the movement behaviour.

Is there a way to Move GameObject from point a to b and b to a using Rigidbody?

I am moving a Gameobject from one point to another point. using a rigid body, I am moving a game object from one point to another point using this code. but is not the right way, because I have to stop the object sometimes. and this code does not let that happen. I am using Time. time to move on.
private void Start()
{
rb = GetComponent<Rigidbody>();
pusherinitPos = transform.position;
}
private void FixedUpdate()
{
if (!stopMove)
{
float timeSin = Mathf.Sin(Time.time) / divider;
Vector3 newPos = new Vector3(pusherinitPos.x, pusherinitPos.y,
pusherinitPos.z + timeSin);
rb.MovePosition(newPos);
}
}
Time.time of course continues to run while your object is stopped.
You could rather use
// store your own passed time
private float _time;
private void Start()
{
rb = GetComponent<Rigidbody>();
pusherinitPos = transform.position;
}
private void FixedUpdate()
{
if (stopMove) return;
var timeSin = Mathf.Sin(_time) / divider;
var newPos = pusherinitPos + Vector3.forward * timeSin;
rb.MovePosition(newPos);
// increase by the time passed since last frame
// see https://docs.unity3d.com/ScriptReference/Time-deltaTime.html
_time += Time.deltaTime;
}

Draging camera to a limit (x, y axis) - UNITY

i did a tutorial about doing a camera drag on a 2D or 3D worldmap.
The code is working. Looks like the following:
void Update_CameraDrag ()
{
if( Input.GetMouseButtonUp(0) )
{
Debug.Log("Cancelling camera drag.");
CancelUpdateFunc();
return;
}
// Right now, all we need are camera controls
Vector3 hitPos = MouseToGroundPlane(Input.mousePosition);
//float maxWidth = 10;
//if (hitPos.x > maxWidth)
//{
// hitPos.x = maxWidth;
//}
Vector3 diff = lastMouseGroundPlanePosition - hitPos;
Debug.Log(diff.x+ Space.World);
Camera.main.transform.Translate (diff, Space.World);
lastMouseGroundPlanePosition = hitPos = MouseToGroundPlane(Input.mousePosition);
}
Now my problem is, that you can drag unlimiited into any directions.
I would rather like to difine something like borders on the x and y axis.
Basically maximum values for the camera. If the camera would surpass these values, their position value would be set to the given maximum value.
Unfortunately, i am not sure how it works, especially, since the tutorial set everything into relation to Space.World - and i am not even sure what that is. I mean i understand that "diff" is the change between the current position and the new positon in relation to Space.World and then the camera gets moved accordingly. I would just like to define a max value which the camera can not surpass. Any ideas how to do that. I am unfortunately still learning - so kinda hard for me and i was hoping for help.
If you were to record the X and Y position of the camera as it goes in a variable and use the MathF function. I.e
if you have a map that is 100(x)x150(y)units you could use
xPositionOfCamera = Mathf.Clamp(xPositionOfCamera, -50, 50);
yPositionOfCamera = Mathf.Clamp(YPositionOfCamera, -75, 75);
I'm not 100% sure if that's what you want it to do, but it's how I would limit it.
I write a simple and reliable script for my game to handle camera drag and swipe for any camera aspect ratio. Everyone can use this code easily :) Just adjust the xBoundWorld and yBoundWorld values
using UnityEngine;
public class CameraDragController : MonoBehaviour
{
[SerializeField] private Vector2 xBoundWorld;
[SerializeField] private Vector2 yBoundWorld;
[SerializeField] public bool HorizentalDrag = true;
[SerializeField] public bool VerticalDrag = true;
[SerializeField] public float speedFactor = 10;
private float leftLimit;
private float rightLimit;
private float topLimit;
private float downLimit;
public bool allowDrag = true;
private void Start()
{
CalculateLimitsBasedOnAspectRatio();
}
public void UpdateBounds(Vector2 xBoundNew, Vector2 yBoundNew)
{
xBoundWorld = xBoundNew;
yBoundWorld = yBoundNew;
CalculateLimitsBasedOnAspectRatio();
}
private void CalculateLimitsBasedOnAspectRatio()
{
leftLimit = xBoundWorld.x - Camera.main.ViewportToWorldPoint(new Vector3(0, 0, 0)).x;
rightLimit = xBoundWorld.y - Camera.main.ViewportToWorldPoint(new Vector3(1, 0, 0)).x;
downLimit = yBoundWorld.x - Camera.main.ViewportToWorldPoint(new Vector3(0, 0, 0)).y;
topLimit = yBoundWorld.y - Camera.main.ViewportToWorldPoint(new Vector3(0, 1, 0)).y;
}
Vector3 lastPosView; // we use viewport because we don't want devices pixel density affect our swipe speed
private void LateUpdate()
{
if (allowDrag)
{
if (Input.GetMouseButtonDown(0))
{
lastPosView = Camera.main.ScreenToViewportPoint(Input.mousePosition);
}
else if (Input.GetMouseButton(0))
{
var newPosView = Camera.main.ScreenToViewportPoint(Input.mousePosition);
var cameraMovment = (lastPosView - newPosView) * speedFactor;
lastPosView = newPosView;
cameraMovment = Limit2Bound(cameraMovment);
if (HorizentalDrag)
Camera.main.transform.Translate(new Vector3(cameraMovment.x, 0, 0));
if (VerticalDrag)
Camera.main.transform.Translate(new Vector3(0, cameraMovment.y, 0));
}
}
}
private Vector3 Limit2Bound(Vector3 distanceView)
{
if (distanceView.x < 0) // Check left limit
{
if (Camera.main.transform.position.x + distanceView.x < leftLimit)
{
distanceView.x = leftLimit - Camera.main.transform.position.x;
}
}
else // Check right limit
{
if (Camera.main.transform.position.x + distanceView.x > rightLimit)
{
distanceView.x = rightLimit - Camera.main.transform.position.x;
}
}
if (distanceView.y < 0) // Check down limit
{
if (Camera.main.transform.position.y + distanceView.y < downLimit)
{
distanceView.y = downLimit - Camera.main.transform.position.y;
}
}
else // Check top limit
{
if (Camera.main.transform.position.y + distanceView.y > topLimit)
{
distanceView.y = topLimit - Camera.main.transform.position.y;
}
}
return distanceView;
}
}

Smooth Player Ball Rolling - Unity 3D

I was trying to achieve this kind of player ball movement:
Catch Up (Ketchapp)
From my side I have tried and record a video of my current implementation:
CatchUpBallMovementDemo
Two kinds of problem, I was facing:
ball making so much jerk while moving on the plain track that I hope you have clearly noticed in my recorded video
when ball reach left or right edge and you try to swipe its making jerk again rather than remain restricted because clamping related code already added
I have just created a demo project so here I am providing the link for it so personally you can check and provide me a suggestion for making ball movement perfect.
Demo Project Source Link: CatchUpBallDemo
Demo Project SIZE 20MB
What at present making jerk in ball movement that I can't able to decide, whether its following camera jerk, whether ball not moving properly though I have created a plain track for checking purpose.
Ball Inspector Detail:
Complete code added within the working demo project. Share your suggestions with me to solve this.
Code Scripts:
BallController
[RequireComponent (typeof(Rigidbody))]
public class BallController : MonoBehaviour
{
//
private Rigidbody myRigidBody;
private bool isJumper;
private bool allowSpeedIncrease;
private BallInputHandler ballInputHandler;
private float speed;
private float speedMilestone;
private float jumpCounter;
private float scoreElapsedTime;
[SerializeField]
private bool isGrounded;
//
public float ballHorzRange;
public float ballStartSpeed;
public float ballTopSpeed;
public float smoothnessValue;
public float smoothnessX;
private void Awake ()
{
DoOnAwake ();
}
private void DoOnAwake ()
{
ballInputHandler = GetComponent<BallInputHandler> ();
myRigidBody = GetComponent<Rigidbody> ();
speed = ballStartSpeed;
speedMilestone = ballStartSpeed;
}
public void Start ()
{
DoOnStart ();
}
private void DoOnStart ()
{
// assinging player transform to camera to follow
Camera.main.GetComponent<CameraFollow> ().FollowPlayer (transform);
}
void Update ()
{
// slowly increase ball moving speed
if (allowSpeedIncrease) {
speed += Time.deltaTime;
if (speed >= speedMilestone) {
allowSpeedIncrease = false;
speed = speedMilestone;
}
}
}
void FixedUpdate ()
{
// do jumping
if (isJumper) {
jumpCounter++;
if (jumpCounter >= 3) {
isJumper = false;
jumpCounter = 0;
}
myRigidBody.AddForce (Vector3.up * 700f);
}
// applying continuous forward velocity
Vector3 nextVelocity = myRigidBody.velocity;
nextVelocity.x = ballInputHandler.horizontalInput * smoothnessX;
nextVelocity.z = speed;
if (isGrounded) {
nextVelocity.y = 0;
} else if (!isJumper) {
nextVelocity.y -= speed * 0.1f;
}
myRigidBody.velocity = nextVelocity.normalized * speed;
ClampingBallMovement ();
}
// ball horizontal movement limitation
private void ClampingBallMovement ()
{
Vector3 currRigidbodyPos = myRigidBody.position;
if (currRigidbodyPos.x <= -ballHorzRange || currRigidbodyPos.x >= ballHorzRange) {
currRigidbodyPos.x = Mathf.Clamp (currRigidbodyPos.x, -ballHorzRange, ballHorzRange);
myRigidBody.position = currRigidbodyPos;
}
}
void OnTriggerEnter (Collider other)
{
if (other.CompareTag (GameConstants.TAG_TRACK_SPAWNER)) {
GameController.Instance.SpawnPlateform ();
} else if (other.CompareTag (GameConstants.TAG_TRACK_DESTROYER)) {
Destroy (other.transform.parent.gameObject);
}
}
}
BallMeshRolling
public class BallMeshRolling : MonoBehaviour
{
private Vector3 ballLastPosition;
void Start ()
{
ballLastPosition = transform.parent.position;
}
void Update ()
{
// implementation-1
float speed = Vector3.Distance (transform.parent.position, ballLastPosition) * 30f;
transform.RotateAround (transform.position, Vector3.right, speed);
// float dragDifference = (transform.position.x - ballLastPosition.x) * 30f;
// transform.RotateAround (transform.position, Vector3.forward, dragDifference);
ballLastPosition = transform.parent.position;
}
}
CameraFollow
public class CameraFollow : MonoBehaviour
{
//
private Vector3 newPos;
private Vector3 initialPosition;
//
public Transform player;
public Vector3 offSet;
void Awake ()
{
initialPosition = transform.position;
}
void LateUpdate ()
{
if (!player)
return;
newPos = player.position + offSet;
newPos.x = ReMap (newPos.x);
newPos.y = Mathf.Clamp (newPos.y, initialPosition.y, initialPosition.y + 1f);
// transform.position = newPos;
transform.position = Vector3.Lerp (transform.position, newPos, 10f * Time.deltaTime);
}
public void FollowPlayer (Transform target)
{
player = target;
ResetCamera ();
}
public float ReMap (float value, float from1 = -4f, float to1 = 4f, float from2 = -2.5f, float to2 = 2.5f)
{
return (value - from1) / (to1 - from1) * (to2 - from2) + from2;
}
public void ResetCamera ()
{
transform.position = initialPosition;
}
}
I could solve the second problem by simply adding this to BallController.ClampingBallMovement():
private void ClampingBallMovement ()
{
Vector3 currRigidbodyPos = myRigidBody.position;
if (currRigidbodyPos.x <= -ballHorzRange || currRigidbodyPos.x >= ballHorzRange) {
currRigidbodyPos.x = Mathf.Clamp (currRigidbodyPos.x, -ballHorzRange, ballHorzRange);
myRigidBody.position = currRigidbodyPos;
}
// I ADDED THIS
// Clamp the velocity as well
if (currRigidbodyPos.x <= -ballHorzRange && myRigidBody.velocity.x < 0 || currRigidbodyPos.x >= ballHorzRange && myRigidBody.velocity.x > 0)
{
myRigidBody.velocity = new Vector3(0, myRigidBody.velocity.y, myRigidBody.velocity.z);
}
}
you clamped the position but did not clamp the velocity as well.
I could not reproduce the first jerking arround on my PC.

Lifting platforms is not working as it should

I would like to make some lifting platforms in my game, so if the platform went down, the characters can't go over it. I have written a script for it, but for some reason the "lifting up" is not working as intended. It won't go back to its starting place, but it will go a bit below. And for some reason it won't go smoothly to the place where it should, just "teleport" there and done. I thougt multiplying Time.deltaTime with a const will help, but it is the same.
Here is my code, any help would be appreciated:
public class LiftingPlatform : MonoBehaviour {
private Transform lift;
private bool isCanBeLifted;
private float timeToLift;
public float timeNeededToLift = 5f;
private Vector3 startPos;
private Vector3 downPos;
private Vector3 shouldPos;
private bool isDown;
public GameObject[] collidingWalls;
// Use this for initialization
void Start () {
lift = transform;
isCanBeLifted = true;
timeToLift = 0f;
isDown = false;
startPos = transform.position;
downPos = new Vector3(startPos.x, startPos.y - 5f, startPos.z);
}
// Update is called once per frame
void Update () {
timeToLift += Time.deltaTime;
if (timeToLift >= timeNeededToLift) {
if (isCanBeLifted) {
if (isDown) {
shouldPos = Vector3.Lerp(startPos, downPos, Time.deltaTime * 10);
lift.position = new Vector3(shouldPos.x, shouldPos.y, shouldPos.z);
isDown = true;
}
else if (!isDown) {
shouldPos = Vector3.Lerp(downPos, new Vector3(startPos.x, startPos.y, startPos.z), Time.deltaTime * 10);
lift.position = new Vector3(shouldPos.x, shouldPos.y, shouldPos.z);
isDown = false;
}
}
timeToLift = 0;
}
if (!isDown) {
for (int i = 0; i < collidingWalls.Length; i++) {
collidingWalls[i].SetActive(true);
}
}
else if (isDown) {
for (int i = 0; i < collidingWalls.Length; i++) {
collidingWalls[i].SetActive(false);
}
}
}
void OnTriggerEnter(Collider collider) {
if (collider.tag == "Player" || collider.tag == "Enemy") {
isCanBeLifted = false;
}
}
void OnTriggerExit(Collider collider) {
if (collider.tag == "Player" || collider.tag == "Enemy") {
isCanBeLifted = true;
}
}
}
These lifting platforms are a child of another Platforms object.
It doesn't look like you are updating the object's position every frame. You are only checking if the total time passed is greater than the time needed to lift, and then updating the position to a value that is dependent on the delta time (using the Vector3.Lerp function).
What I would do is in the update step, if timeToLift is greater then timeNeededToLift, subtract the latter from the former and invert the value of isDown. Then, in your Vector3.Lerp, make the third argument (timeToLift / timeNeededToLift) instead of (Time.deltaTime * 10). Can you try that and see if it works?
The third argument for Vector3.Lerp is the "blending factor" between the two vectors, 0 is the first vector, 1 is the second, and 0.5 is in between. If the total time is greater than the time needed to lift, but the delta time is not greater than 1, it will get the position of the platform using a blending factor of less than 1, resulting in a platform that didn't move fully.