Once I saw a Google Play game where there was such
good physics. I'm new in Rigidbody2D move and physics. How can I do
the same physics? Now my bullet just explodes and don't boost tank. When I added Rigidbody2D, bullet pushed my tank
on a little bit, but that's all, my tank stops.
I have only default Rigidbody2D move script.
void FixedUpdate()
{
if (moveup1) // bool button for touch controls
{
direction = Mathf.Sign(Vector2.Dot(rb.velocity, rb.GetRelativeVector(Vector2.right)));
rb.AddRelativeForce(-Vector2.left * upspeed * 300); // these vectors looks weird but all work perfectly
}
if (movedown1) // bool button
{
direction = Mathf.Sign(Vector2.Dot(rb.velocity, rb.GetRelativeVector(Vector2.right)));
rb.AddRelativeForce(Vector2.left * downspeed * 300);
}
if (rotateleft1) // bool button
{
steeringAmount = -1;
rb.rotation += steeringAmount * steeringPower;
rb.AddRelativeForce(-Vector2.right * rb.velocity.magnitude * steeringAmount / 2);
}
if (rotateright1) // bool button
{
steeringAmount = 1;
rb.rotation += steeringAmount * steeringPower;
rb.AddRelativeForce(-Vector2.right * rb.velocity.magnitude * steeringAmount / 2);
}
}
Also
numbers should I put in the Rigidbody2D parameters? Now I'm using these params:
I hope for your understanding and patience. Thanks!
Lower Mass and Linear Drag. Also add Force when you instantiate the bullet. Force direction should be negative shooting direction.
If you want to push the tank on impact, the direction would be Vector3 dir = tank.position - impact.position. However the vector is longer on bigger distances, but we want the opposite (the close the explosion, the more force). So we can use float force = 1f / dir.magnitude and then apply it like this
rb.addForce(dir.normalized * force);
In general there is Rigidbody.AddExplosionForce but it's not available for 2D.
Related
Im struggling with this, tried adding force but its not working the way I wanted, tried also changing direction but it changes by 45degrees which in my case is not what I wanted. Im thinking about adding the wind zone in the direction of arrwos, but came here to ask if theres any other(better) way to do it. Any help would be great! Thanks and have a great day!
Heres image for easier understainding
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ArrowGrass : MonoBehaviour
{
public Rigidbody ball;
public float arrowPower;
public float stoppingPower;
private void OnTriggerStay(Collider other)
{
if (other.gameObject.CompareTag("Player"))
{
Vector3 direction = ball.velocity.normalized;
float speed = ball.velocity.magnitude;
Vector3 currentVelocity = ball.velocity;
Vector3 desiredDirection = new Vector3(-1, 0, 0); // set this to the direction you want.
Vector3 newVelocity = desiredDirection.normalized * currentVelocity.magnitude;
ball.AddForce(newVelocity - currentVelocity, ForceMode.VelocityChange);
/*
ball.angularDrag = stoppingPower;
Debug.Log("Colided");
ball.AddForce(new Vector3(-1, 0, 0) * arrowPower * stoppingPower, ForceMode.VelocityChange);
*/
}
}
}
Separate the Forces.
private float userForce = 0f;
void FixedUpdate()
{
if(userForce > 0.1f) // define a minimum force, as we won't reach actual 0.
{
userForce *= 0.95; // no need for Time.deltaTime as we are in FixedUpdate!
}else{
// we can shoot again. User-Force has ended.
userForce = 0; // just make it stop completely eventually.
}
rb.AddForce(userForce * velocity * direction); // you have to define velocity and direction.
}
The OnTriggerStay logic is on the conveyor belt itself, where you can define the force strenght and direction. It is applied to the Physx system and affects the players Rigidbodies velocity. But we can track our userforce whilst ignoring other forces. So the ball can continue to move or at least get pushed to a wall etc.
In the end you can just apply the same principle to a timer as well. Either checking timestamps, or just decreasing a variable using Time.deltaTime and checking if it's < 0.
I'm trying to make an object on the ground follow a flying object, for example a drone leading a human (for now i'm just using shapes - a cube and a capsule). My cube follows the capsule like i desire but i want the cube to follow the capsule on the ground only, rather than go up on the y-axis with the capsule. Right now, it follows the capsule everywhere, I want the capsule to lead while the cube follows along on the ground.
I have done some research on Google and Youtube but I have not seen any results. Please let me know how I can achieve this.
This is the code script attached to the cube(ground object)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class follow_target : MonoBehaviour
{
public Transform mTarget;
float mSpeed = 10.0f;
const float EPSILON = 0.1f;
Vector3 mLookDirection;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
mLookDirection = (mTarget.position - transform.position).normalized;
if((transform.position - mTarget.position).magnitude > EPSILON)
transform.Translate(mLookDirection * Time.deltaTime * mSpeed);
}
}
If the ground is planar, you can just set the y component to 0 (or whatever the ground y vector is).
If the ground changes in topology, you can do a raycast down from the capsule to get the hit point (vector3). You can use the hit point y component for the height. After that you will need to set the cubes rotation so that it is aligned to the ground. You could do that with a raycast as well, there are a number of examples of that online.
I hope that helps get you in the right direction.
Assuming a flat ground on Y = 0
either make sure your objects sticks to the ground so set
private const float EPSILONSQR = EPSILON * EPSILON;
void Update()
{
var difference = mTarget.position - transform.position;
mLookDirection = difference.normalized;
if(difference.sqrmagnitude > EPSILONSQR)
{
// In general be aware that Translate by default moves in the
// objects own local space coordinates so you probably would rather
// want to use Space.World
transform.Translate(mLookDirection * Time.deltaTime * mSpeed, Space.World);
var pos = transform.position;
// reset the Y back to the ground
pos.y = 0;
transform.position = pos;
}
}
or simply already map the direction down on the XZ plane (ignoring any difference in Y) like
private const float EPSILONSQR = EPSILON * EPSILON;
void Update()
{
var difference = mTarget.position - transform.position;
mLookDirection = difference.normalized;
// simply ignore the difference in Y
// up to you if you want to normalize the vector before or after doing that
mLookDirection.y = 0;
if(difference.sqrmagnitude > EPSILONSQR)
{
transform.Translate(mLookDirection * Time.deltaTime * mSpeed, Space.World);
}
}
I'm going to make track follower.
and here's my track in unity
Track image
I want to operate codes 1 ~ 10 at step by step in unity C# script method void Update{},
but I can't find how to make it.
I make rotation code using with trigonometric systems, Sin and Cos.
Rigidbody rigidbody;
public GameObject ball;
Vector3 P1 = new Vector3(-5, 1.5f, 25);
public float theta;
// Start is called before the first frame update
void Start()
{
rigidbody = GetComponent<Rigidbody>();
}
// Update is called once per frame
void Update()
{
ball.transform.position = Vector3.MoveTowards(transform.position, P1, 0.05f); // 1st move
if(theta <210) // 2nd rotation with trigonometic equations
{
theta += 1;
ball.transform.position = Rot(theta);
}
}
Vector3 Rot(float theta) // rotation by trigonometric equations
{
Vector3 P2;
P2.x = (3 * Mathf.Cos(theta * Mathf.PI / 180) - 3 * Mathf.Sin(theta * Mathf.PI / 180)) -8;
P2.y = 1.5f;
P2.z = (3 * Mathf.Sin(theta * Mathf.PI / 180) + 3 * Mathf.Cos(theta * Mathf.PI / 180)) + 22 ;
return P2;
}
if I play that code, the ball just warp to position P1 and immediately start rotation
how can i use that?
and can't use keyboard or something else input handler, only use by codes
To debug in unity you will need to use visual studio debug tool. Click on the play button "Connect with Unity" and play on the unity side. You will of course need to put a debug point where you need it.
Note: Your game will be stuck during the Update debug, if you can and you need to see update in the game view just execute the code that you need to debug in a coroutine.
If you just need to know the value of a variable you should be able to debug it without any problem even in the update.
I'm trying to create a smooth camera movement in 2D. The target I want to hit can potentially move a large distance in a single frame, and is not like a character moving smoothly from A to B.
I'm aware of possible solutions like using Vector2.Lerp(), but that approach only slows down nicely but speeds up abruptly.
_position = Vector2.Lerp(_position, target, 0.5f * Time.deltaTime);
I've tried implementing the "arrive" steering behaviour, but cannot make it work nicely together with acceleration - especially when the target is close to the current position.
I managed to make it work pretty well in one axis, but that approach didn't work when repeated in a second axis.
var decelerateRadius = GetDistanceFromAcceleration(acceleration, Mathf.Abs(_velocity));
var direction = target - _position;
var distance = Mathf.Abs(direction);
var a = acceleration * Time.deltaTime;
if (distance > 0.0005f)
{
if (distance < decelerateRadius.x)
{
_velocity *= distance / decelerateRadius.x;
}
else
{
_velocity += direction.x >= 0.0f ? a : -a;
}
}
else
{
_velocity = 0.0f;
}
// move tracker
_position += _velocity * Time.deltaTime;
And my method for calculating the distance based on acceleration:
private Vector2 GetDistanceFromAcceleration(float a, float vx, float vy)
{
// derived from: a = (vf^2 - vi^2) / (2 * d)
return new Vector2(vx * vx / (2.0f * a), vy * vy / (2.0f * a));
}
My last attempt was making a rolling average of the target, but it suffered the same issue as lerping.
To summarize the requirements:
Must accelerate
Must decelerate and stop at target
Must not "orbit" or in other ways swing around the target, before stopping
Target must be able to move
May be limited by a maximum velocity
Any tips, pointers og solutions on how to achieve this?
I've also asked the question over at game dev
https://gamedev.stackexchange.com/questions/170056/accelerate-decelerate-towards-moving-target-and-hitting-it
The problem with your lerp is also that you actually never reach the target position you just get very very close and small.
I thought about something like this
as long as you are already at the targets position don't move. Enable orbit mode
while not within a certain targetRadius around the target position accelerate from to maxVelocity
if getting within a certain targetRadius around the target position decelerate depending on the distance / radius will be a value between 1 and 0
To get the distance there is already Vector2.Distance you could/should use.
For the movement I would recommend Vector2.MoveTowards which also avoids overshooting of the target.
something like
public class SmoothFollow2D : MonoBehaviour
{
[Header("Components")]
[Tooltip("The target this will follow")]
[SerializeField] private Transform target;
[Header("Settings")]
[Tooltip("This may never be 0!")]
[SerializeField] private float minVelocity = 0.1f;
[SerializeField] private float maxVelocity = 5.0f;
[Tooltip("The deceleration radius around the target.\nThis may never be 0!")]
[SerializeField] private float targetRadius = 1.0f;
[Tooltip("How much speed shall be added per second?\n" +
"If this is equal to MaxVelocity you know that it will take 1 second to accelerate from 0 to MaxVelocity.\n" +
"Should not be 0")]
[SerializeField] private float accelerationFactor = 3.0f;
private float _currentVelocity;
private float _lastVelocityOutsideTargetRadius;
private bool _enableOrbit;
public bool EnableOrbit
{
get { return _enableOrbit; }
private set
{
// if already the same value do nothing
if (_enableOrbit == value) return;
_enableOrbit = value;
// Whatever shall be done if orbit mode is enabled or disabled
}
}
private void Update()
{
if (target == null) return;
var distanceToTarget = Vector2.Distance(transform.position, target.position);
// This is the threshold Unity uses for equality of vectors (==)
// you might want to change it to a bigger value in order to
// make the Camera more stable e.g.
if (distanceToTarget <= 0.00001f)
{
EnableOrbit = true;
// do nothing else
return;
}
EnableOrbit = false;
if (distanceToTarget <= targetRadius)
{
// decelerate
// This will make it slower
// the closer we get to the target position
_currentVelocity = _lastVelocityOutsideTargetRadius * (distanceToTarget / targetRadius);
// as long as it is not in the final position
// it should always keep a minimum speed
_currentVelocity = Mathf.Max(_currentVelocity, minVelocity);
}
else
{
// accelerate
_currentVelocity += accelerationFactor * Time.deltaTime;
// Limit to MaxVelocity
_currentVelocity = Mathf.Min(_currentVelocity, maxVelocity);
_lastVelocityOutsideTargetRadius = _currentVelocity;
}
transform.position = Vector2.MoveTowards(transform.position, target.position, _currentVelocity * Time.deltaTime);
}
// Just for visualizing the decelerate radius around the target
private void OnDrawGizmos()
{
if (target) Gizmos.DrawWireSphere(target.position, targetRadius);
}
}
The MinVelocity is actually necessary for the edge case when the target is moved not further than TargetRadius and lastVelocityOutsideTargetRadius si still 0. In that case no acceleration takes place so lastVelocityOutsideTargetRadius is never updated.
With the values you have to play a bit ofcourse ;)
It might not be perfect yet but I hope it is a good start point to develop that further (only looks laggy due to 15 FPS for the Gif ;) )
right now I am trying to make rts camera zooming with panning when close to the ground. The problem I have now is that I use mouse scroll wheel for zooming and it makes the zooming feel like it's laggy. It looks like it jumps some Y value and teleports rather than smoothly move to the desired position. Also, I would like to know how to make the camera stop at the minimum Y value because what is happening now is that it stops at around 22 rather than 20 which is my minimum Y value for the camera movement.
I tried zooming with + and - on my numpad and it worked how I wanted (smoothly zooming in and out without skipping) but not all players have numpad and I feel it is more suitable to zoom with mouse wheel.
{
private const int levelArea = 100;
private const int scrollArea = 25;
private const int scrollSpeed = 25;
private const int dragSpeed = 70;
private const int zoomSpeed = 50;
// Maximum/minimum zoom distance from the ground
public int zoomMin = 20;
public int zoomMax = 120;
private const int panSpeed = 40;
// Minimal/maximal angles for camera
private const int panAngleMin = 30;
private const int panAngleMax = 90;
void Update()
{
// Init camera translation for this frame.
var translation = Vector3.zero;
// Zoom in or out
var zoomDelta = Input.GetAxis("Mouse ScrollWheel") * zoomSpeed * Time.deltaTime;
if (zoomDelta != 0)
{
translation -= Vector3.up * zoomSpeed * zoomDelta;
}
// Start panning camera if zooming in close to the ground or if just zooming out.
var pan = transform.eulerAngles.x - zoomDelta * panSpeed;
pan = Mathf.Clamp(pan, panAngleMin, panAngleMax);
// When to start panning up the camera
if (zoomDelta < 0 || transform.position.y < (zoomMax -20))
{
transform.eulerAngles = new Vector3(pan, 0, 0);
}
// Move camera with arrow keys
translation += new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
// Move camera with mouse
if (Input.GetMouseButton(2)) // MMB
{
// Hold button and drag camera around
translation -= new Vector3(Input.GetAxis("Mouse X") * dragSpeed * Time.deltaTime, 0,
Input.GetAxis("Mouse Y") * dragSpeed * Time.deltaTime);
}
else
{
// Move camera if mouse pointer reaches screen borders
if (Input.mousePosition.x < scrollArea)
{
translation += Vector3.right * -scrollSpeed * Time.deltaTime;
}
if (Input.mousePosition.x >= Screen.width - scrollArea)
{
translation += Vector3.right * scrollSpeed * Time.deltaTime;
}
if (Input.mousePosition.y < scrollArea)
{
translation += Vector3.forward * -scrollSpeed * Time.deltaTime;
}
if (Input.mousePosition.y > Screen.height - scrollArea)
{
translation += Vector3.forward * scrollSpeed * Time.deltaTime;
}
}
// Keep camera within level and zoom area
var desiredPosition = transform.position + translation;
if (desiredPosition.x < -levelArea || levelArea < desiredPosition.x)
{
translation.x = 0;
}
if (desiredPosition.y < zoomMin || zoomMax < desiredPosition.y)
{
translation.y = 0;
}
if (desiredPosition.z < -levelArea || levelArea < desiredPosition.z)
{
translation.z = 0;
}
// Move camera parallel to world axis
transform.position += translation;
}
}
I would like to have a smooth transition from the position where the camera is now and the desired position after scrolling in/out. And also I would like to know how to make the camera to stop at the minimum/maximum zoom distance rather than to stop close to it. Thank you for helping. Video how the camera movement looks like: https://youtu.be/Lt3atJEaOjA
Okay, so I'm going to do three things here. First and foremost, I'm going to recommend that if you're working with advanced camera behavior, you probably want to at least consider using Cinemachine. I'd walk you through it myself, but given my lack of personal experience with it, I'd probably be doing you a disservice by even trying. There are many good tutorials out there. Youtube and Google should provide.
The second thing I'll do is solve your problem in the most direct way I can manage, and after that, we'll see if we can't come up with a better method for solving your problem.
So, the key here is that Unity's scrollwheel input is pretty binary. When you check a scrollwheel axis, the result is directly based on how many "clicks" your wheel has gone through since the last frame update, but what you really want is something with a bit of give. By default, Unity can actually do this with most of its axis inputs: You might notice that if you use WASD in a default Unity project, there's a sort of "lag" to it, where you'll take your fingers off the keys but you'll still keep receiving positive values from Input.GetAxis() for a few frames. This is tied to the Gravity value in your input settings, and Input.GetAxisRaw() is actually used to circumvent this entirely. For whatever reason, scrollwheel axes don't seem to be affected by axis gravity, so we essentially have to implement something similar ourselves.
// Add this property to your class definition (so it persists between updates):
private float wheelAxis = 0;
// Delete this line:
var zoomDelta = Input.GetAxis("Mouse ScrollWheel") * zoomSpeed * Time.deltaTime;
// And put these three new lines in its place:
wheelAxis += Input.GetAxis("Mouse ScrollWheel");
wheelAxis = Mathf.MoveTowards(wheelTotal, 0f, Time.deltaTime);
var zoomDelta = Mathf.Clamp(wheelAxis, -0.05f, 0.05f) * zoomSpeed * Time.deltaTime;
Right, so we do a few things here. Every update, we add the current scrollwheel values to our wheelAxis. Next, we apply the current Time.deltatime as "gravity" via the Mathf.MoveTowards() function. Finally, we call what's mostly just your old zoomDelta code with a simple modification: We constrain the wheelAxis with Mathf.Clamp to try and regulate how fast the zooming can happen.
You can modify this code in a couple of ways. If you multiply the Time.deltaTime parameter you can affect how long your input will "persist" for. If you mess with the Mathf.Clamp() values, you'll get faster or slower zooming. All in all though, if you just want a smooth zoom with minimal changes to your code, that's probably your best bet.
So!
Now that we've done that, let's talk about your code, and how you're approaching the problem, and see if we can't find a somewhat cleaner solution.
Getting a good camera working is surprisingly non-trivial. Your code looks like a lot of code I see that tries to solve a complex problem: It looks like you added some feature, and then tested it, and found some edge cases where it fell apart, and then patched those cases, and then tried to implement a new feature on top of the old code, but it kinda broke in various other ways, etc etc, and what we've got at this point is a bit messy.
The biggest issue with your code is that the camera's position and the camera's rotation are closely tied together. When working with characters, this is usually fine, but when working with a camera, you want to break this up. Think about where the camera is and what the camera is looking at as very separate things to keep track of.
So here's a working camera script that you should be able to plug in and just run with:
using UnityEngine;
public class RTSCamera : MonoBehaviour
{
public float zoomSpeed = 100f;
public float zoomTime = 0.1f;
public float maxHeight = 100f;
public float minHeight = 20f;
public float focusHeight = 10f;
public float focusDistance = 20f;
public int panBorder = 25;
public float dragPanSpeed = 25f;
public float edgePanSpeed = 25f;
public float keyPanSpeed = 25f;
private float zoomVelocity = 0f;
private float targetHeight;
void Start()
{
// Start zoomed out
targetHeight = maxHeight;
}
void Update()
{
var newPosition = transform.position;
// First, calculate the height we want the camera to be at
targetHeight += Input.GetAxis("Mouse ScrollWheel") * zoomSpeed * -1f;
targetHeight = Mathf.Clamp(targetHeight, minHeight, maxHeight);
// Then, interpolate smoothly towards that height
newPosition.y = Mathf.SmoothDamp(transform.position.y, targetHeight, ref zoomVelocity, zoomTime);
// Always pan the camera using the keys
var pan = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")) * keyPanSpeed * Time.deltaTime;
// Optionally pan the camera by either dragging with middle mouse or when the cursor touches the screen border
if (Input.GetMouseButton(2)) {
pan -= new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y")) * dragPanSpeed * Time.deltaTime;
} else {
var border = Vector2.zero;
if (Input.mousePosition.x < panBorder) border.x -= 1f;
if (Input.mousePosition.x >= Screen.width - panBorder) border.x += 1f;
if (Input.mousePosition.y < panBorder) border.y -= 1f;
if (Input.mousePosition.y > Screen.height - panBorder) border.y += 1f;
pan += border * edgePanSpeed * Time.deltaTime;
}
newPosition.x += pan.x;
newPosition.z += pan.y;
var focusPosition = new Vector3(newPosition.x, focusHeight, newPosition.z + focusDistance);
transform.position = newPosition;
transform.LookAt(focusPosition);
}
}
While I encourage you to go through it in your own time, I'm not going to drag you through every inch of it. Instead, I'll just go over the main crux.
The key idea here is that rather than controlling the camera's height and orientation directly, we just let the scrollwheel dictate where want the camera's height to be, and then we use Mathf.SmoothDamp() to move the camera smoothly into that position over several frames. (Unity has many useful functions like this. Consider Mathf.MoveTowards() for an alternative interpolation method.) At the very end, rather than trying to fiddle with the camera's rotation values directly, we just pick a point in front of us near the ground and point the camera at that spot directly.
By keeping the camera's position and orientation completely independent of each other, as well as separating out the "animation" of the camera's height, we avoid a lot of headaches and eliminate a lot of potential for messy interwoven bugs.
I hope that helps.
Cinemachine is pretty good, but I suggest people learn on your own quite a bit before using Cinemachine so that you better understand what's going on in the background.