I am attempting to follow a few unity3d exmaples on c# scripting for tower defense style games. I need a turret to 'aim' at another gameobject. The examples I find do not seem to account for a origin that is not at 0,0,0. Meaning, when the turret is in a different location, it aims based on a starting point, not its current location.
how it is behaving now:
http://screencast.com/t/Vx35LJXRKNUm
In the script I am using, how do I give Quaternion.LookRotation information about the current location of the turret for it to include in it's calculation? script, function CalculateAimPosition, line59 :
using UnityEngine;
using System.Collections;
public class TurretBehavior : MonoBehaviour {
public GameObject projectile;
public GameObject muzzleEffect;
public float reloadTime = 1f;
public float turnSpeed = 5f;
public float firePauseTime = .25f;
public Transform target;
public Transform[] muzzlePositions;
public Transform turretBall;
private float nextFireTime;
private float nextMoveTime;
private Quaternion desiredRotation;
private Vector3 aimPoint;
// Update is called once per frame
void Update ()
{
if (target)
{
if (Time.time >= nextMoveTime)
{
CalculateAimPosition(target.position);
transform.rotation = Quaternion.Lerp(turretBall.rotation, desiredRotation, Time.deltaTime * turnSpeed);
}
if (Time.time >= nextFireTime) {
FireProjectile();
}
}
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "TurretEnemy")
{
nextFireTime = Time.time +(reloadTime *.5f);
target = other.gameObject.transform;
}
}
void OnTriggerExit(Collider other)
{
if (other.gameObject.transform == target) {
target = null;
}
}
void CalculateAimPosition(Vector3 targetPosition)
{
aimPoint = new Vector3 (targetPosition.x, targetPosition.y, targetPosition.z);
desiredRotation = Quaternion.LookRotation (aimPoint);
}
void FireProjectile()
{
nextFireTime = Time.time + reloadTime;
nextMoveTime = Time.time + firePauseTime;
foreach(Transform transform in muzzlePositions)
{
Instantiate(projectile, transform.position, transform.rotation);
}
}
}
The error is in the usage of Quaternion.LookRotation.
The function takes two Vector3 as input which are a forward direction in world space (and an optional up vector - default Vector3.up), and returns a Quaternion representing the orientation of such a reference frame.
You are instead supply a world space position as input (targetPosition), which makes no sense. Accidentally a normalized position vector expressed in world space is the direction from origin to the given point, so it works correctly when the tower is placed on the origin.
What you need to use as LookRotation parameter is the world space direction from the tower to the target:
Vector3 aimDir = (targetPosition - transform.position).normalized;
desiredRotation = Quaternion.LookRotation (aimDir );
Related
Here I share my AiPathfinding script. I want my AI walks to a destination (target) when game starts. however, when AI collide an certain object (finding with tag="bullet") then I want to set new destination. Although code does not give error, when playing AI goes to first destination then stops there even though it collides with certain object on the way.
Can someone have a look please?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Pathfinding;
public class AIPathfinding : MonoBehaviour
{
[SerializeField] public Transform target;
[SerializeField] public Transform target2;
[SerializeField] public float speed = 200f;
[SerializeField] public float nextWayPointDistance = 3f;
[SerializeField] Path path;
[SerializeField] int currentWaypoint = 0;
[SerializeField] bool reachedEndOfPath = false;
[SerializeField] Seeker seeker;
[SerializeField] Rigidbody2D rb;
// Start is called before the first frame update
void Start()
{
seeker = GetComponent<Seeker>();
rb = GetComponent<Rigidbody2D>();
InvokeRepeating("UpdatePath", 0f, 0.5f);
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "bullet")
{
UpdatePath(target2);
}
}
public void UpdatePath(Transform target2)
{
if (seeker.IsDone())
{
seeker.StartPath(rb.position, target.position, OnPathComplete);
}
}
void OnPathComplete(Path p)
{
if (!p.error)
{
path = p;
currentWaypoint = 0;
}
}
void Update()
{
if (path == null)
{
return;
}
if (currentWaypoint >= path.vectorPath.Count)
{
reachedEndOfPath = true;
return;
}
else
{
reachedEndOfPath = false;
}
Vector2 direction = ((Vector2)path.vectorPath[currentWaypoint] - rb.position).normalized;
Vector2 force = direction * speed * Time.deltaTime;
rb.AddForce(force);
float distance = Vector2.Distance(rb.position, path.vectorPath[currentWaypoint]);
if (distance < nextWayPointDistance)
{
currentWaypoint++;
}
}
}
My guess is that you never stopped the Coroutine InvokeRepeating("UpdatePath", 0f, 0.5f). So do call CancelInvoke() when the bullet hits and change the target of the seeker (Start InvokeRepeating(...) again).
I assume NavMeshAgent.SetDestination(Vector3 position) is called in the function seeker.SetTarget(). And somehow Path gets calculated and set.
You could also just move the body of Update Path into the Update function or call it from there. Nevermind the Interval. Or do call it in your custom interval. Just calculate the next time you want to call it so if the nextUpdatePathTime (Time.time plus your cooldown) is smaller than Time.time then call it and calculate the next nextUpdatePathTime.
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.
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);"
This was probably asked a dozen of times, but I can't seem to find the answer anywhere.
I have a dedicated server for a space game written in C# console application. The problems that I'm facing is the synchronisation of GameObject rotations. I'm suspecting the issue being related to gimbal lock, but I am not sure.
Here I have my player movement/rotation controller:
public class PlayerMovement : MonoBehaviour {
[SerializeField] float maxSpeed = 40f;
[SerializeField] float shipRotationSpeed = 60f;
Transform characterTransform;
void Awake()
{
characterTransform = transform;
}
void Update()
{
Turn();
Thrust();
}
float Speed() {
return maxSpeed;
}
void Turn() {
float rotX = shipRotationSpeed * Time.deltaTime * CrossPlatformInputManager.GetAxis("Vertical");
float rotY = shipRotationSpeed * Time.deltaTime * CrossPlatformInputManager.GetAxis("Horizontal");
characterTransform.Rotate(-rotX, rotY, 0);
}
void Thrust() {
if (CrossPlatformInputManager.GetAxis("Move") > 0) {
characterTransform.position += shipTransform.forward * Speed() * Time.deltaTime * CrossPlatformInputManager.GetAxis("Move");
}
}
}
This script is applied to my character object which is a ship. Note that the character object has a child object which is the ship itself and has fixed rotation and position that do not change. When character has moved/rotated I send the following to the server: position(x, y, z) and rotation(x, y, z, w).
Now here is the actual script that receives network packet information and updates the other players in game:
public class CharacterObject : MonoBehaviour {
[SerializeField] GameObject shipModel;
public int guid;
public int characterId;
public string name;
public int shipId;
Vector3 realPosition;
Quaternion realRotation;
public void Awake() {
}
public int Guid { get { return guid; } }
public int CharacterId { get { return characterId; } }
void Start () {
realPosition = transform.position;
realRotation = transform.rotation;
}
void Update () {
// Do nothing
}
internal void LoadCharacter(SNCharacterUpdatePacket cuPacket) {
guid = cuPacket.CharacterGuid;
characterId = cuPacket.CharacterId;
name = cuPacket.CharacterName;
shipId = cuPacket.ShipId;
realPosition = new Vector3(cuPacket.ShipPosX, cuPacket.ShipPosY, cuPacket.ShipPosZ);
realRotation = new Quaternion(cuPacket.ShipRotX, cuPacket.ShipRotY, cuPacket.ShipRotZ, cuPacket.ShipRotW);
UpdateTransform();
Instantiate(Resources.Load("Ships/Ship1/Ship1"), shipModel.transform);
}
internal void UpdateCharacter(SNCharacterUpdatePacket cuPacket) {
realPosition = new Vector3(cuPacket.ShipPosX, cuPacket.ShipPosY, cuPacket.ShipPosZ);
realRotation = new Quaternion(cuPacket.ShipRotX, cuPacket.ShipRotY, cuPacket.ShipRotZ, cuPacket.ShipRotW);
UpdateTransform();
}
void UpdateTransform() {
transform.position = Vector3.Lerp(transform.position, realPosition, 0.1f);
transform.rotation = Quaternion.Lerp(transform.rotation, realRotation, 0.5f);
}
}
Do you see anything wrong with the code ?
My experience with 2 players in game is the following:
When I start the game with two players they spawn at the same location (0,0,0) and same rotation (0,0,0).
Lets say that The other player is rotating continuously around the X-axis only. What I am experiencing is:
First 90 deg. of rotation it renders fine.
Next 180 deg. of rotation the object stays in place
The last 90 deg. of rotation renders fine
In the first version I did not send the 'w' value of Quaternion, but then added it in the second version and it did not help. Note that there is no restrictions in rotation and users can rotate endlessly in all directions.
Btw the positioning works fine.
I might not understand Quaternions fully, but I thought that they had a purpose in avoiding the gimbal lock issue.
Any help would be appreciated.
To answer my own question. I used transform.localEulerAngles instead of transform.rotation, and everything seems to work just fine.
Now the only thing that I am unsure is if gimbal lock could happen with this change.
I am trying to implement a RADAR sensor in unity. I am currently using sphere cast to identify the object and measure its velocity and distance. At the moment even though there are several objects in within the radius of the sphere cast ,It only identifies the nearest one. How can i make it recognize all the objects within the sphere cast.?
Any help would be appreciated..
This is what i have done now which just identifies identify the closest object.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class spherecast : MonoBehaviour
{
Rigidbody rb;
public GameObject curobject;
public float radius;
public float maxdist;
public LayerMask layermask;
public float velocity;
public Time deltatime;
public Vector3 previous;
private Vector3 origin;
private Vector3 direction;
private float hitdist;
// Use this for initialization
void Start()
{
previous = curobject.transform.position;
}
// Update is called once per frame
void Update()
{
velocity = hitdist/Time.deltaTime;
origin = transform.position;
direction = transform.forward;
RaycastHit hit;
if (Physics.SphereCast(origin, radius, direction, out hit, maxdist, layermask, QueryTriggerInteraction.UseGlobal))
{
curobject = hit.transform.gameObject;
hitdist = hit.distance;
Debug.Log("Distance" + hitdist);
velocity = ((curobject.transform.position - previous).magnitude) / Time.deltaTime;
previous = curobject.transform.position;
Debug.Log("Velocity" + velocity);
// Debug.Log("Velocity = " + velocity);
}
else
{
hitdist = maxdist;
curobject = null;
}
}
private void ondrawgizmosselected()
{
Gizmos.color = Color.red;
Debug.DrawLine(origin, origin + direction * hitdist);
Gizmos.DrawWireSphere(origin + direction * hitdist, radius);
}
}
How can i make it recognize all the objects within the sphere cast.?
I believe you need to use SphereCastAll instead of SphereCast for that.
https://docs.unity3d.com/ScriptReference/Physics.SphereCastAll.html
Answer:
"Like Physics.SphereCast, but this function will return all hits the sphere sweep intersects."
It returns a RaycastHit[].