Isometric Unity 3D Game. How to shot a projectile? - unity3d

I'm making an isometric 3D game. I made two joystick, one to move the player and the other one to shoot a projectile when the joystick is released. I made 3 attemps to achieve this result,but there's every time a problem. first try was with this :
clone.velocity = transform.TransformDirection(newpos);
but this need a rigidbody and a projectile can't be a rigidbody because it start from inside of the player.
Second try was this:
clone.transform.Translate(dir * (launchForce));
but this doesn't have a "speed" so it just move instantly to the position, not by moving, but translating
and the same happens with the third attemp:
clone.transform.position=Vector3.MoveTowards(Player.transform.position,newpos,10f);
This is the best solution until now because it gives me the possibility to choose a max range between the start posititon and the newposition.
Here's the full code:
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
public class shoot : MonoBehaviour, IDragHandler, IPointerUpHandler, IPointerDownHandler
{
private Image bgImg;
private Image joystickImg;
private Vector3 inputVector;
public GameObject proiettile;
private Vector3 dir = Vector3.zero;
private Vector3 newpos;
public float launchForce;
public Rigidbody Player;
private GameObject clone;
private void Start()
{
bgImg = GetComponent<Image>();
joystickImg = transform.GetChild(0).GetComponent<Image>();
}
public virtual void OnDrag(PointerEventData ped)
{
Vector2 pos;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(bgImg.rectTransform, ped.position, ped.pressEventCamera, out pos))
{
pos.x = (pos.x / bgImg.rectTransform.sizeDelta.x);
pos.y = (pos.y / bgImg.rectTransform.sizeDelta.y);
inputVector = new Vector3(pos.x * 2 +1, 0, pos.y * 2 - 1);
inputVector = (inputVector.magnitude > 1.0f) ? inputVector.normalized : inputVector;
// Move joystickImg
joystickImg.rectTransform.anchoredPosition =
new Vector3(inputVector.x * bgImg.rectTransform.sizeDelta.x / 3
, inputVector.z * (bgImg.rectTransform.sizeDelta.y / 3));
}
}
public virtual void OnPointerDown(PointerEventData ped)
{
OnDrag(ped);
}
public virtual void OnPointerUp(PointerEventData ped)
{
dir.x = Horizontal();
dir.z = Vertical();
newpos = dir * (launchForce);
clone = Instantiate(proiettile, Player.transform.position, Player.transform.rotation);
//third attempt
//clone.transform.position=Vector3.MoveTowards(Player.transform.position,newpos,10f);
//second attempt
//clone.transform.Translate(dir * (launchForce));
//first attempt
//clone.velocity = transform.TransformDirection(newpos);
// joystick come back to start position
inputVector = Vector3.zero;
joystickImg.rectTransform.anchoredPosition = Vector3.zero;
//temporary solution to replace the absence of a max range for projectile
clone.timeoutDestructor = 5;
}
public float Horizontal()
{
if (inputVector.x != 0)
return inputVector.x;
else
return Input.GetAxis("Horizontal");
}
public float Vertical()
{
if (inputVector.z != 0)
return inputVector.z;
else
return Input.GetAxis("Vertical");
}
}

If I were you I would use rigidbody attempt. Just make sure it does not collide with player by setting collision layers/disabling collider for first fraction of second/spawning projectile outside of player.
Why?
Two main reasons:
Rigidbody will handle interpolation for you out of the box (without it movement may be glitchy)
Rigidbody will handle "between frame" collisions with CCD for you out of the box (without it your projectile may go trough walls, or even targets if it's fast enough)
And these two features will save you a lot of time later
One of attempts:
When I spawn projectile I check with SphereCast (or whatever shape it has) if it's colliding with something. If it is I change isTrigger to true and then in "OnTriggerExit" I change isTrigger to false again. If it is not colliding with anything on spawn I just set isTrigger to false at start and that should do the trick.

Related

Why is my Controller drifting with Slerp?

I've been working on this Controller, where I walk around a small planet. I'm using Quaternion.Slerp, and when I'm at certain points on the planet, the controller slowly drifts, rotating around the Y axis. My thought is that it is holding a value based on my starting position, and so when I move to different points, the Slerp function is trying to spin me toward that location? I've tried messing around with the script, moving different portions to their own custom methods to pinpoint the issue, but I'm a bit lost at this point. Any help would be appreciated!
I think the issue is going to be somewhere in the bottom two methods NewRotation, or RunWalkStand.
`
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[SerializeField] private LayerMask _groundMask;
[SerializeField] private Transform _groundCheck;
[SerializeField] private Transform cam;
public float jumpCooldown = 1f;
public float groundCheckRadius = 0.3f;
private float speed = 8;
private float runSpeed = 2;
private float turnSpeed = 800f;
private float jumpForce = 500f;
private Rigidbody rb;
private Vector3 direction;
private GravityBody _gravityBody;
private Animator playerAnimator;
private GameObject planetRecall;
void Start()
{
_gravityBody = transform.GetComponent<GravityBody>();
playerAnimator = GetComponent<Animator>();
planetRecall = GameObject.FindGameObjectWithTag("Planet Recall");
}
void Update()
{
bool isCloseToGround = Physics.CheckSphere(_groundCheck.position, groundCheckRadius, _groundMask);
NewRotation();
if (Input.GetKeyDown(KeyCode.Space) && isCloseToGround)
{
Jump();
}
}
void FixedUpdate()
{
RunWalkStand();
}
private void OnCollisionEnter(Collision collision)
{
if(collision.gameObject == planetRecall)
{
playerAnimator.SetBool("Grounded", true);
}
}
private void Jump()
{
rb.AddForce(-_gravityBody.GravityDirection * jumpForce, ForceMode.Impulse);
playerAnimator.SetTrigger("Fly_trig");
playerAnimator.SetBool("Grounded", false);
}
private void NewRotation()
{
rb = transform.GetComponent<Rigidbody>();
Vector3 mouseRotationY = new Vector3(0f, Input.GetAxisRaw("Mouse X"), 0f);
Quaternion rightDirection = Quaternion.Euler(0f, mouseRotationY.y * (turnSpeed * Time.deltaTime), 0f).normalized;
Quaternion newRotation = Quaternion.Slerp(rb.rotation, rb.rotation * rightDirection, Time.deltaTime * 1000f);
rb.MoveRotation(newRotation);
//Move Side to Side
Vector3 sideToSide = transform.right * Input.GetAxisRaw("Horizontal");
rb.MovePosition(rb.position + sideToSide * (speed * Time.deltaTime));
}
private void RunWalkStand()
{
direction = new Vector3(0f, 0f, Input.GetAxisRaw("Vertical")).normalized;
Vector3 forwardDirection = transform.forward * direction.z;
bool isRunning = direction.magnitude > 0.1f;
//Walking
if (isRunning && !Input.GetKey(KeyCode.LeftShift))
{
rb.MovePosition(rb.position + forwardDirection * (speed * Time.deltaTime));
playerAnimator.SetFloat("Speed_f", 0.5f);
}
//Running
else if(isRunning && Input.GetKey(KeyCode.LeftShift))
{
rb.MovePosition(rb.position + forwardDirection * (speed * runSpeed * Time.deltaTime));
playerAnimator.SetFloat("Speed_f", 1f);
}
//Standing
else if(isRunning == false)
{
playerAnimator.SetFloat("Speed_f", 0f);
}
}
}
`
it might be because a slerp is not a lineair line so its velocity is less when you are close to the endpoint maybe try Quaternion.RotateTowards.
I "Mostly" solved this issue, and it's an unexpected solution. The drift was solved by freezing rotation on the player in the inspector (Still don't know what was causing the player to spin though) However this caused extra jitter. I found two issues.
If I've selected ANY game object in the hierarchy, and play the game, the game is jittery, but if I click off of it onto nothing, the game runs smoother (Not completely better, but much smoother).
I'm using a Gravity script for the planet that applies gravity to the player's Rigidbody, and I believe with multiple things acting on the RB at the same time, it causes jitter. I'm thinkin of trying to greatly simplify the project, but putting the different methods from the scripts into different Update methods helps a good bit depending on the combination (Not as simple as Physics movement in FixedUpdate and camera in LateUpdate unfortunately).

How to control player camera on angled surface/independent of xyz axis in Unity?

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

Unity 3D jump and movement conflict

I wanted to make an endless runner game where the player can jump and he moves forward automatically, so, for the forward movement i wanted to use the rigidbody velocity function in order to make the movement smoother and for the jump i made a custom function. My problem is, when i use in my update method the velocity function alone, it works, when i use it with my jump function it stops working. What can i do?. I tried changing the material of the floor and of the player's box collider to a really slippery one but nothing
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MovePrima : MonoBehaviour
{
[SerializeField]
private float _moveSpeed = 0f;
[SerializeField]
private float _gravity = 9.81f;
[SerializeField]
private float _jumpspeed = 3.5f;
public float speedAccel = 0.01f;
private CharacterController _controller;
private float _directionY;
public float startSpeed;
public float Yspeed = 10f;
private int i = 0;
public float touchSensibility = 50f;
public int accelTime = 600;
[SerializeField]
Rigidbody rb;
// Start is called before the first frame update
void Start()
{
startSpeed = _moveSpeed;
_controller = GetComponent<CharacterController>();
rb = GetComponent<Rigidbody>();
}
// Update is called once per frame
void Update()
{
rb.velocity = new Vector3(0f, 0f, 1f) * _moveSpeed;
touchControls();
}
public void touchControls()
{
if (SwipeManager.swipeUp && _controller.isGrounded)
{
_directionY = _jumpspeed;
print("Entrato");
}
_directionY -= _gravity * Time.fixedDeltaTime;
_controller.Move((new Vector3(0, _directionY, 0)) * Time.fixedDeltaTime * Yspeed);
i += 1;
if (i % accelTime == 0)
{
_moveSpeed += startSpeed * speedAccel;
i = 0;
}
}
private void OnControllerColliderHit(ControllerColliderHit hit) //Checks if it collided with obstacles
{
if(hit.transform.tag == "Obstacle")
{
GameOverManager.gameOver = true;
}
}
}
You should either use the CharacterController or the Rigidbody. With the CharacterController you tell Unity where to place the character and you have to check yourself if it is a valid position. With the Rigidbody you push it in the directions you want, but the physics around the character is also interfering.
You can use Rigidbody.AddForce to y when jumping instead of _controller.Move.

Unity collider shaking after hitting another collider

I have a SMALL a problem. In Unity 3D 2020 Beta, I've put a player with a sphere collider on it and some cubes (walls) with box colliders. I've added a player controller script to the player object.
I've put the camera above the plane where the player and the walls are on, and I've made that the player should rotate to face the mouse position. I used rigidbody.AddForce for movement in a FixedUpdate function.
The player controller script is attached below:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[Header("Keys")]
public KeyCode forward;
public KeyCode backward;
public KeyCode left;
public KeyCode right;
public KeyCode fire;
[Header("Health")]
public int hitpoints = 3;
[Header("Movement")]
public float speed;
public float turningSpeed;
[Header("Shooting")]
public GameObject bulletPrefab;
public Transform bulletSpawner;
public float bulletSpeed;
public float reloadTime;
private float currentReload;
private Rigidbody rb;
private Quaternion targetRotation;
void Start()
{
rb = GetComponent<Rigidbody>();
currentReload = reloadTime;
}
void LateUpdate()
{
if (hitpoints == 0)
Die();
// Rotation
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
targetRotation = Quaternion.LookRotation(hit.point - transform.position);
Debug.DrawLine(transform.position, hit.point, Color.white);
}
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, turningSpeed * Time.deltaTime);
transform.eulerAngles = new Vector3(0, transform.rotation.eulerAngles.y, 0);
currentReload += Time.deltaTime;
// Shooting
if (Input.GetKeyDown(fire) && currentReload >= reloadTime)
{
currentReload = 0f;
GameObject bulletGO = Instantiate(bulletPrefab, bulletSpawner.position, transform.rotation);
bulletGO.transform.position = bulletSpawner.position;
Bullet bulletScript = bulletGO.GetComponent<Bullet>();
bulletScript.speed = bulletSpeed;
Destroy(bulletGO, 5f);
}
}
void FixedUpdate()
{
// Movement
if (Input.GetKey(forward))
{
rb.AddForce(Vector3.forward * speed, ForceMode.Force);
}
if (Input.GetKey(backward))
{
rb.AddForce(-Vector3.forward * speed, ForceMode.Force);
}
if (Input.GetKey(left))
{
rb.AddForce(Vector3.left * speed, ForceMode.Force);
}
if (Input.GetKey(right))
{
rb.AddForce(Vector3.right * speed, ForceMode.Force);
}
//transform.position = new Vector3(transform.position.x, 10, transform.position.z);
// ON RIGIDBODY I HAVE CONSTRAINS:
// POSITION: Y (thats why I commented the line above)
// ROTATION: X, Z (topdown -> so I want only rotation on Y)
}
private void Die()
{
Destroy(gameObject);
}
}
But the problem is when the player hits very hard a wall, the sphere collider starts shaking and the player does not look at the mouse position exactly (it is somewhere 10 degrees away most of the times - it depends on how hard do I hit the walls).
I can record if it helps. If you want any information, feel free to ask! Any help will be appreciated! :)
The problem here seems to be you're directly altering the transform of your object despite having a Rigidbody component. Generally you should avoid altering a transform directly when you have a Rigidbody attached, especially a non-kinematic one, as by attaching one you are signalling that the object is to be controlled by the physics simulation.
Solutions I would explore:
If you don't need a rigidbody, don't use one
If you can avoid altering the transform directly, then do not do so. You can rotate objects by applying torque and the likes
Try setting your object to kinematic if you don't need collisions to affect the rigidbody's physics
Manually set the torque and velocity of your object to 0 each fixed update

Unity player movement, speed not fixed

I'm making an isometric 3d game like Brawl Stars, and I'm trying to get the same movement, but I have some troubles. My player moves without a fixed speed,but it seem to be increased. How can solve it? The first script if of the virtual joystick and the second one is of the player movement.
Could be a problem of mass?
Joystick script:
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
public class VirtualJoystick : MonoBehaviour, IDragHandler, IPointerUpHandler, IPointerDownHandler
{
private Image bgImg;
private Image joystickImg;
private Vector3 inputVector;
private void Start()
{
bgImg = GetComponent<Image>();
joystickImg = transform.GetChild(0).GetComponent<Image>();
}
public virtual void OnDrag(PointerEventData ped)
{
Vector2 pos;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(bgImg.rectTransform, ped.position, ped.pressEventCamera, out pos))
{
pos.x = (pos.x / bgImg.rectTransform.sizeDelta.x);
pos.y = (pos.y / bgImg.rectTransform.sizeDelta.x);
inputVector = new Vector3(pos.x * 2 + 1, 0, pos.y * 2 - 1);
inputVector = (inputVector.magnitude > 1.0f) ? inputVector.normalized : inputVector;
// Move joystickImg
joystickImg.rectTransform.anchoredPosition =
new Vector3(inputVector.x * bgImg.rectTransform.sizeDelta.x / 3
, inputVector.z * (bgImg.rectTransform.sizeDelta.y / 3));
}
}
public virtual void OnPointerDown(PointerEventData ped)
{
OnDrag(ped);
}
public virtual void OnPointerUp(PointerEventData ped)
{
inputVector = Vector3.zero;
joystickImg.rectTransform.anchoredPosition = Vector3.zero;
}
public float Horizontal()
{
if (inputVector.x != 0)
return inputVector.x;
else
return Input.GetAxis("Horizontal");
}
public float Vertical()
{
if (inputVector.x != 0)
return inputVector.z;
else
return Input.GetAxis("Vertical");
}
}
Player movement's script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Move : MonoBehaviour
{
public float moveSpeed = 20f;
public VirtualJoystick moveJoystick;
public Rigidbody rb;
private Vector3 dir = Vector3.zero;
private bool wasMoving = false;
private void Update()
{
dir.x = moveJoystick.Horizontal();
dir.z = moveJoystick.Vertical();
if (dir.magnitude > 0)
{
wasMoving = true;
}
}
private void FixedUpdate()
{
if (wasMoving && dir.magnitude == 0f)
{
wasMoving = false;
rb.Sleep();
}
rb.AddForce(dir * moveSpeed);
}
}
How to handle character movement in Unity:
Your character is affected by physics? Use Rigidbody as you have, and use Vector3.ClampMagnitude to ensure you don't pass terminal velocity.
Your character isn't affected by physics, but affects other physical objects? Use a kinematic Rigidbody and use RigidBody.MovePosition to move.
Your character has no physics at all? Directly move it via Transform
So in your case, I'm guessing solutions 1 or 2 will work for you.
If you intend on adding force to your rb you'll have to manually limit the maximum speed your character is allowed to achieve, otherwise it will increase indefinitely or until drag is too high. Easiest way is using rb.velocity.magnitude to set a limit and only if your character is below that limit you add force.
Edit, in order to change directions:
rb.AddForce(dir * moveSpeed);
if(rigidbody.velocity.magnitude > maxSpeed){
rigidbody.velocity = Vector3.ClampMagnitude(rigidbody.velocity, maxSpeed);
}
This clamps the maximum velocity of the rigidbody after applying a force, this way you will change directions but not speed up.
Also be aware that using the joystick directions like you are doing might make your character move faster when moving diagonally.
I solved by myself changing "rb.AddForce(dir * moveSpeed);" to "rb.position+= dir*(moveSpeed/10);"