I want to program a bouncing ball. The actual bounce shouldn't follow real physic laws but rather build an absolute sine wave. Like in this image
That is pretty easy so far. See the attached code.
However I want to slow down the movement of my ball as soon as I press a button.
I'll reduce the movement speed which will slow down the transformation on the x axis. But the ball is still bouncing pretty fast up and down, because neither frequency nor magnitude of the sine wave has changed. But when I change one or both, this will end in a weird behavior.
Imagine the ball is on the downmove at 20% of the distance from top to bottom. When I now change any sine parameters the ball will instantly move on the y axis which looks really weird and not smooth.
So my actual question is: How can I slow down an absolute sine wave (x/y axis translation) without choppy movement?
float _movementSpeed = 10.0f;
float _sineFrequency = 5.0f;
float _sineMagnitude = 2.5f;
Vector3 _axis;
Vector3 _direction;
Vector3 _position;
void Awake()
{
_position = transform.position;
_axis = transform.up;
_direction = transform.right;
}
void Update()
{
_position += _direction * Time.deltaTime * _movementSpeed;
// Time.time is the time since the start of the game
transform.position = _position + _axis * Mathf.Abs(Mathf.Sin(Time.time * _sineFrequency)) * _sineMagnitude;
}
I guess you want this (more red - lower freq, more blue - higher):
The easiest way to achieve this is to scale deltatime, for example this was generated with:
using UnityEngine;
using System.Collections;
public class Bounce : MonoBehaviour {
// for visualisation of results
public Graph graph;
[Range(0, 10)]
public float frequency = 1;
float t;
void Update()
{
t += Time.deltaTime * frequency;
float y = Mathf.Abs(Mathf.Sin(t));
float time = Time.realtimeSinceStartup;
graph.AddPoint(time, y, Color.Lerp(Color.red, Color.blue, frequency/10));
}
}
And for the completeness, the Graph class:
using UnityEngine;
using System.Collections.Generic;
public class Graph : MonoBehaviour {
struct PosAndColor
{
public Vector3 pos;
public Color color;
}
List<PosAndColor> values=new List<PosAndColor>();
public void AddPoint(float x, float y, Color c)
{
values.Add(new PosAndColor() { pos = new Vector3(x, y, 0), color = c });
}
void OnDrawGizmos()
{
for (int i = 1; i < values.Count; i++)
{
Gizmos.color = values[i - 1].color;
Gizmos.DrawLine(values[i - 1].pos, values[i].pos);
}
}
}
Another aproach would be to change the frequency with the offset of your sine to match the current amplitude. I don't think you'd need this here, but if you want to I can post some more on the subject.
Related
I have a platform that must rotate simultaneously in x, y & z directions in a random order, the maximum degree measure of rotation of vectors x & z is 10 degrees, and rotation in the "y" direction is 360 degrees.
Here is the code:
using UnityEngine;
public class PlaneRotating : MonoBehaviour
{
void Start() {
}
void Update()
{
Vector3 euler = transform.eulerAngles;
euler.z = Random.Range(-10f, 10f);
euler.y = Random.Range(0f, 360f);
euler.x = Random.Range(-10f, 10f);
transform.eulerAngles = euler;
}
}
Everything works perfectly, except for a certain rotation speed, since I don't know how to set it.
Can you tell me how to set the speed?
You could lerp between positions, its not the best way to control the speed because large rotations will be faster and smaller rotations will be slower but you can have a stable rotations, time wise.
using UnityEngine;
public class PlaneRotating : MonoBehaviour
{
public Transform transform;
Vector3 lastPos
Vector3 newPos
float t;
void Start()
{
lastPos = transform.eulerAngles;
NewAngle();
}
void Update()
{
transform.eulerAngles = Vector3.Lerp(lastPos, newPos, t);
t+=0.01f;
if(t>1)
NewAngle();
}
void NewAngle()
{
lastPos = newPos;
newPos = new Vector3(
Random.Range(-10f, 10f),
Random.Range(0f, 360f),
Random.Range(-10f, 10f));
t = 0;
}
}
you can then play with Time.deltaTime to make it consistent this is only an example.
If you want to control speed
you can just add two vectors and you do the randomization periodically every x seconds or x frames. you change the direction. there are plenty of good videos on youtube but you need to be more clear if you want to have a good answer. my first answer doewnt let you control the speed with random numbers, only makes smooth transitions and you can choose how fast they change. for constant speeds you have to do something like the bellow
void Update()
{
transform.eulerAngles += new Vector3(0,1,0);
}
let me know if I can be of any further help.
An illustration of my issue/what I'm trying to achieve
I managed to have my player move around the inside surface of a cylinder using gravity but an issue comes up when trying to rotate the camera to look around.
First, there's a script that simulates gravity by pushing the player against the inside walls of the cylinder. This script also keeps the player upright by changing the "up" direction of the player to always be facing the center of the cylinder. I know this keeps my player from looking up so for now I'm just working on getting them to look left and right.
Second, when the player is on the bottom of the cylinder and parallel with the Y-axis I can look left and right without issue because the camera rotates using the x-axis (see circle on the left side of the image). However when the player moves around the side of the cylinder the camera is still trying to rotate based on the X-axis even though they are not aligned resulting in the camera not accurately rotating (see the circle on the right side of the image), and by the time the player is 90deg around the cylinder cannot rotate at all (thanks I think to the gravity keeping the player perpendicular to the sides of the cylinder), and at 180deg around the rotation is inverted.
I assume there are two possible solutions that I have not been able to successfully implement:
ignore the world xyz axis' and rotate relative to the player's xyz.
do some math to figure out the proper angles when you take into account the player's rotation around the cylinder and the angle of the current "left" direction.
The problem with the first solution is I have not been able to successfully rotate the player independent of the world xyz. I tried adding the Space.Self to the Rotate() method but no success. The problem with the second is math scares me and I've managed to avoid Quaternions & Euler angles so far so I'm not even sure how to begin figuring that out.
If anyone has had a similar issue I would greatly appreciate any insight or suggestions on how to figure it out.
Here's my code for controlling the play movement/camera direction and my code for the gravity:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerController : MonoBehaviour
{
InputManager inputActions;
InputManager.PlayerMovementActions playerMovement;
public GravityAttractor GravityAttractor;
public float moveSpeed = 15;
public float jumpHeight = 10f;
public float sensitivityX = 1f;
public float sensitivityY = 1f;
float mouseX, mouseY;
Vector2 mouseInput;
private bool isJumping = false;
private Vector3 moveDir;
private Vector2 moveInput;
private Rigidbody rbody;
private void Awake()
{
inputActions = new InputManager();
playerMovement = inputActions.PlayerMovement;
playerMovement.Movement.performed += context => moveInput = context.ReadValue<Vector2>();
playerMovement.Jump.performed += ctx => Jump();
//playerMovement.MouseX.performed += context => mouseInput = context.ReadValue<Vector2>();
playerMovement.MouseX.performed += context => mouseInput.x = context.ReadValue<float>();
playerMovement.MouseY.performed += context => mouseInput.y = context.ReadValue<float>();
rbody = GetComponent<Rigidbody>();
isJumping = false;
}
private void Update()
{
moveDir = new Vector3(moveInput.x, 0, moveInput.y).normalized;
if (rbody.velocity.y == 0)
isJumping = false;
}
private void FixedUpdate()
{
rbody.MovePosition(rbody.position + transform.TransformDirection(moveDir) * moveSpeed * Time.deltaTime);
MouseLook(mouseInput);
}
void Jump()
{
//use brackeys method of checking for contact with ground
if(isJumping == false && rbody.velocity.y == 0)
{
isJumping = true;
rbody.velocity = transform.up * jumpHeight;
Debug.Log("player jumped");
}
}
void MouseLook(Vector2 mouseInput)
{
mouseX = mouseInput.x * sensitivityX;
mouseY = mouseInput.y * sensitivityY;
var upTransform = GravityAttractor.transform.position - transform.position;
Vector3 relativeLook = upTransform - transform.forward;
Vector3 qLook = transform.forward - transform.position;
transform.Rotate(transform.up * mouseX * Time.deltaTime, Space.Self);
//transform.Rotate(relativeLook.normalized);
//transform.rotation = Quaternion.LookRotation(qLook);
//transform.Rotate(relativeLook.normalized, mouseX);
}
private void OnEnable()
{
inputActions.Enable();
}
private void OnDisable()
{
inputActions.Enable();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GravityAttractor : MonoBehaviour
{
public float gravityMultiplier = -10f;
private float radius;
public float gravity;
public float distance;
private void Awake()
{
radius = transform.localScale.x/2;
}
public void Attract(Transform body)
{
Vector3 centerOfGravity = new Vector3(transform.position.x, transform.position.y, body.position.z);
distance = Vector3.Distance(centerOfGravity, body.position);
//gravity will match multiplier no matter the radius of cylinder
gravity = gravityMultiplier * (distance/radius);
Vector3 gravityUp = (centerOfGravity - body.position).normalized;
Vector3 bodyUp = body.up;
body.GetComponent<Rigidbody>().AddForce(gravityUp * gravity);
Quaternion targetRotation = Quaternion.FromToRotation(bodyUp, gravityUp) * body.rotation;
body.rotation = Quaternion.Slerp(body.rotation, targetRotation, 50 * Time.deltaTime);
}
}
Problem is that you are trying to Rotate in local space around transform.up, which is in world space.
try to use just Vector3.up, instead of transform.up.
Or you can transform any vector from world to local space with transform.InverseTransformDirection()
transform.Rotate(Vector3.up * mouseX * Time.deltaTime, Space.Self);
I don't know hierarchy of your GameObjects. In order for this to work, you shuld rotate Player GameObject to face up to center of cilinder. And camera should be child of Player GameObject
I'm trying to implement my own gravity for a character.
But for some unknown to me reason the character's landing speed is higher landing than when jumping, by quite a lot (~15 initial jump speed, ~24 final land speed). I'm ultimately trying to replicate behavior shown on the gif below.
https://imgur.com/a/q77w5kS
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class Movement : MonoBehaviour {
public float speed = 3;
public float jumpSpeed = 15;
public float gravity = -1f;
private float ySpeed = 0;
private float jumpTime = 0;
private bool direction = false; //true for going up, false for falling down - gravity
private bool previousDirection = false; //to keep track of changing direction
private CharacterController _characterController;
// Start is called before the first frame update
void Start() {
_characterController = GetComponent<CharacterController>();
}
// Update is called once per frame
void Update() {
float dx = Input.GetAxis("Horizontal");
float dz = Input.GetAxis("Vertical");
float dy = Input.GetAxis("Jump");
// move around
Vector3 movement = new Vector3(dx, 0, dz);
// limit diagonal movement
movement = Vector3.ClampMagnitude(movement, 1);
// speed
movement *= speed;
// change of direction
if(ySpeed>0 && direction!=true)
direction = true;
else if(ySpeed<0 && direction!=false)
direction = false;
// if changed direction - peak of the jump
if(direction!=previousDirection){
Debug.Log("Jump Time = " + jumpTime);
jumpTime = Time.deltaTime;
previousDirection = direction;
}
// jump/on ground
if(_characterController.isGrounded) {
ySpeed = -0.5f;
jumpTime = 0;
if(dy > 0f){
ySpeed = jumpSpeed;
}
}
// gravity - when not on ground
else{
// dv/dt
ySpeed += gravity*jumpTime;
// add jump time
jumpTime += Time.deltaTime;
// Debug.Log(jumpTime);
Debug.Log(ySpeed);
}
movement.y = ySpeed;
// direction adjustment
movement = transform.TransformDirection(movement);
movement *= Time.deltaTime;
_characterController.Move(movement);
}
}
I think I figured out what was wrong with my idea.
ySpeed += gravity*jumpTime
So every frame I'm adding more and more acceleration downwards. This should just be: ySpeed += gravity *Time.deltaTime
(Acceleration due to gravity is then constant, not getting greater as time passes)
It is being integrated over many steps, so each Update() cycle is a slice of time that adds some velocity based on acceleration due to gravity, multiplied by the amount of time taken by that little slice.
In another words...
Gravity is a constant acceleration. I've made it a linear function of the time spent in the air. So, I start the jump with no gravity and end the jump with very high gravity.
I'm writing a script in C# for the trajectory of a cannon ball. The canon ball is a prefab with a script for its trajectory when shoot from a canon. I can make the canon ball shoot at more or less the correct angle in which the canon barrel is tilted. But when I tried to incorporate gravity, successive canon shooting fail. The first canon ball seem to shoot at the correct angle and follows the law of gravity but can't shoot a second canon ball.
The general of how this works is that we have a canon script that create a canon ball prefab when space key is pressed. The canon script passes to the cannon ball script the rotated angle of the barrel and time in which the space key is pressed. The canon script uses Translate method to translate the canon ball according to the angle parameter and incorporate the gravity mechanism by subtracting from the y-velocity, (Time.time-createdtime)^2*gravity.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//This is the script for the cannon
public class PlayerController : MonoBehaviour
{
int time = 1;
float minRotation = -90;
float maxRotation = 0;
Vector3 currentRotation;
public Transform fireBall;
canonTrajectory ct;
public float speed = 5f;
public Transform circle;
// Start is called before the first frame update
void Start()
{
ct = fireBall.GetComponent<canonTrajectory>();
currentRotation = transform.parent.localRotation.eulerAngles;
}
// Update is called once per frame
void FixedUpdate()
{
if(Input.GetKeyDown(KeyCode.UpArrow))
{
currentRotation.z -= 5.0f;
currentRotation.z = Mathf.Clamp(currentRotation.z, minRotation, maxRotation);
transform.parent.localRotation = Quaternion.Euler(currentRotation);
}
if(Input.GetKeyDown(KeyCode.DownArrow))
{
currentRotation.z += 5.0f;
currentRotation.z = Mathf.Clamp(currentRotation.z, minRotation, maxRotation);
transform.parent.localRotation = Quaternion.Euler(currentRotation);
}
if(Input.GetKeyDown(KeyCode.Space))
{
ct.setCreatedTime(Time.time);
ct.setRotationAngle(currentRotation.z);
Transform fireInstance;
fireInstance = Instantiate(fireBall, new Vector3(transform.parent.position.x, transform.parent.position.y+3.0f, transform.position.z), transform.rotation) as Transform;
Transform c = Instantiate(circle, fireInstance.transform.position, Quaternion.identity) as Transform;
c.SetParent(fireInstance.transform);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class canonTrajectory : MonoBehaviour
{
public static float rotationAngle;
float createdTime;
float speed = 150f;
float gravity = -200.0f;
public GameObject turkey;
static int NUM_PARTICLES = 26;
Vector3[] m_position = new Vector3[NUM_PARTICLES];
LineRenderer lineRenderer;
public void setCreatedTime(float time)
{
createdTime = time;
}
public void setRotationAngle(float angle)
{
rotationAngle = angle;
}
public float GetRotationAngle()
{
return rotationAngle;
}
void Start()
{
}
void FixedUpdate()
{
float directionx = Mathf.Cos(-rotationAngle*2.0f*Mathf.PI/360.0f);
float directiony = Mathf.Sin(-rotationAngle * 2.0f * Mathf.PI / 360.0f);
Debug.Log(-rotationAngle * 2.0f * Mathf.PI / 360.0f);
float directionVectorLength = Mathf.Pow(Mathf.Pow(directionx, 2) + Mathf.Pow(directiony, 2), (0.5f));
Debug.Log("created time: "+createdTime);
transform.Translate(-5 * directionx, 2 * directiony-(Time.time-createdTime)* (Time.time - createdTime) * 2.0f , 0.0f);
if (transform.position.x < -300)
Destroy(gameObject);
if (transform.position.y < -77)
Destroy(gameObject);
}
}
Here is a picture of the firing action. As you can see, gravity doesn't work and multiple cannon ball is shot at the same time.
enter image description here
This script works only if you presume that the player is oriented along the X axis.
It could be that you rotate the player and that is why the ball rotates.
Also you could get more help if you could produce some video of the issue. Just screen capture a couple of seconds long gif.
I'm making a simple project in Unity where there is a Ball attached to a SpringJoint2d component the ball is on an angled slope, like in the image below:
I simply want the user to be able to drag the ball backward along the edge of the slope only,in other words I don't want the user to be able to move the ball away from the slope or into it.
I'v been trying several ways I thought could do the job hers the script of the dragging with what I tried:
(This Is the updated version)
public class ball : MonoBehaviour
{
public Rigidbody2D rb;
public Transform spring;
public Transform calcpoint;
private Vector3 start;
private Vector3 end;
private bool isPressed = false;
RaycastHit2D[] hits = new RaycastHit2D[2];
RaycastHit2D[] hits2 = new RaycastHit2D[2];
float factor = 0;
private void OnMouseDown()
{
if (!isPressed)
{
isPressed = true;
rb.isKinematic = true;
}
}
private void OnMouseUp()
{
isPressed = false;
rb.isKinematic = false;
StartCoroutine(release());
}
/// <summary>
/// release the ball from the spring joint after a small amount of time
/// </summary>
/// <returns></returns>
IEnumerator release()
{
yield return new WaitForSeconds(0.1f);
rb.GetComponent<SpringJoint2D>().enabled = false;
}
// Update is called once per frame
void Update()
{
if (isPressed)
{
if (Vector3.Distance(spring.position, rb.position) > 3f || spring.position.x < (rb.position.x - 1)) return;//restrict the dragging of the ball to not go beyond the spring point and not too far back
float angle = 0;
if (checkGround() > 1)//if we hit the slope with the ray cast downward from the mouse/Tap position
{
angle = Mathf.Abs(Mathf.Atan2(hits[1].normal.x, hits[1].normal.y) * Mathf.Rad2Deg); //get angle
factor = (float)(((45 - angle) * 0.02) + 1) * (angle / 45);//an inaccurate formula to offset the ball to be on top of the slope that works just fine with some glitches
rb.position = hits[1].point + new Vector2(0, factor * 1f);//position the ball at the point were the ray cast downward from the mouse hit
//(that puts the ball center on the line of the slope) so I offset it usinf the formula above
}
}
}
private int checkGround()
{
int h = Physics2D.RaycastNonAlloc(Camera.main.ScreenToWorldPoint(Input.mousePosition), -Vector2.up, hits); //cast downwards
return h;
}
}
here are the settings on the ball:
and the slope setup:
The dragging of the ball works fine ,at one point the player could drag it in the air or into the slope, I managed to fix that with the new code so now the player could only drag it on the edge, though my calculations are still a bit flawed and when the slopes angle is changed the ball would dip a bit inside the slope and that causes some problems at release.
The method used to try to solve the problem is simple, when the player start dragging the ball I cast a ray from the mouse downward and pit the ball on the point of impact with the slope ,offsetting it to sit on top of it,right ow the problem is that the offsetting part is not accurate enough.
I hope I explained myself a bit better this time Thanks:)
After lots of trial and error I did manage to come up with a perfect solution to the problem so I thought I might as well share the answer maybe it will help someone.
here is the updated code I changed my method of restricting the movement completely now I use simple linear line equation as shown below:
private void OnMouseDown()
{
if (!isPressed)
{
//cast a ray on the slope
if (checkGround() > 1)
{
angle = Mathf.Abs(Mathf.Atan2(hits[1].normal.x, hits[1].normal.y) * Mathf.Rad2Deg); //get angle
slope = Mathf.Tan(angle * Mathf.Deg2Rad);//get the slope steepiness
}
isPressed = true;
rb.isKinematic = true;
}
}
private void OnMouseUp()
{
isPressed = false;
rb.isKinematic = false;
StartCoroutine(release());
}
void Update() {
if (isPressed)
{
xMove = Camera.main.ScreenToWorldPoint(Input.mousePosition).x - spring.position.x;//get how much the mouse moved backward or forward
if (xMove < -3f ) xMove = -3f; //restrict the drag range to 3 backward
if (xMove > 0.3f) xMove = 0.3f;//restrict the drag range to 0.3 forward
xpos = spring.position.x+xMove;//since the ball and the spring start at exactly the same position the new ball's x position would be the spring x + the x movement we calculated above
ypos = (xMove * slope)- spring.position.y; //the y posistion would be y=mx+b so the the x movement * the slop steepiness - the starting y position
rb.position = new Vector2(xpos, -ypos);//set the new position of the ball
}
}
private int checkGround()
{
int h = Physics2D.RaycastNonAlloc(Camera.main.ScreenToWorldPoint(Input.mousePosition), -Vector2.up, hits); //cast downwards
return h;
}