Why is my airplane moving only partway and not all the way? - c#

So I've spent 2 days looking over this script, and I still don't see what I did wrong. I want to make a simple AI plane that will fly from one point to another. After arriving on Waypoint 1, it should fly towards Waypoint 2. I'll include my script below:
using UnityEngine;
using System.Collections
public class AICustomScript : MonoBehaviour
{
//first waypoint
public Transform Waypoint;
//second waypoint
public Transform Waypoint2;
void Start()
{
rb = GetComponent<Rigidbody>();
//make plane point in direction of first waypoint
transform.LookAt(Waypoint);
}
//allow me to select amount of force in editor
public float AddForceAmount;
//allow me to select plane in editor
public Rigidbody rb;
/void FixedUpdate()
{
//make plane move
rb.AddForce(transform.forward * AddForceAmount);
}
//PART OF CODE TO DETECT ARRIVAL ON WAYPOINT
void OnTriggerEnter(Collider other)
{
if (other.gameObject)
{
//destroys waypoint
Destroy(other.gameObject);
//makes plane look at 2nd waypoint (Waypoint2)
transform.LookAt(Waypoint2);
}
}
}
//SO WHAT'S WRONG?
I've included my logical thinking in the comments. However, when the plane arrives at Waypoint 1, it turns toward Waypoint 2, but only partially. So while it is flying towards the 2nd waypoint, it never "hits" it. Why?
Screenshots:

You might want to use spherical interpolation to make the airplane face the waypoint while changing its position. instead of adding forward force.
using UnityEngine;
using System.Collections;
public class AICustomScript : MonoBehaviour
{
public Transform[] Waypoints;
public float minimumTouchDistance = 2.0f; // minimum distance between the airplane and the waypoint
public float airplaneSpeed = 10.0f;
private Transform currentWaypoint; // keep track of current waypoint
private int currentIndex; // current position in the waypoint array
void Start()
{
currentWaypoint = Waypoints[0]; // set initial waypoint
currentIndex = 0; // set initial index
}
void Update()
{
faceAndMove();
if (Vector3.Distance(currentWaypoint.transform.position, transform.position) < minimumTouchDistance)
{
currentIndex++;
if (currentIndex > Waypoints.Length - 1)
{
currentIndex = 0;
}
currentWaypoint = Waypoints[currentIndex];
}
}
void faceAndMove()
{
Vector3 deltaPosition = currentWaypoint.transform.position - transform.position;
Vector3 calcVector = deltaPosition.normalized * airplaneSpeed * Time.deltaTime;
this.transform.position += calcVector;
this.transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(deltaPosition), 4 * Time.deltaTime);
}
}

Related

Unity Game Engine: 3rd Person Player Controller Script Using Rewired

I would like MyCharacter.cs script to move my 3D character forward, backwards, left, and right. I am using Rewired, an advanced input system for Unity.
It works, but rather moving my character forward and backwards, it moves them up and down. I'm still understanding how to access the y, x, and z axis in relation to the code. I thought that:
moveVector.x = player.GetAxis("Horizontal"); // get input by name or action id
moveVector.y = player.GetAxis("Vertical");
Would allow me to go forward and backwards, not up and down.
Here is the code:
// MyCharacter.cs - how to get input from Rewired.Player
using UnityEngine;
using System.Collections;
using Rewired;
//using AC;
[RequireComponent(typeof(CharacterController))]
public class PlayerController : MonoBehaviour
{
public Rigidbody rb;
// The Rewired player id of this character
public int playerId = 0;
// The movement speed of this character
public float moveSpeed = 3.0f;
public float jumpForce;
public float impactVelocity;
// The bullet speed
public float bulletSpeed = 15.0f;
// Assign a prefab to this in the inspector.
// The prefab must have a Rigidbody componet on it in order to work.
public GameObject bulletPrefab;
private Player player; // The Rewired Player
private CharacterController cc;
private Vector3 moveVector;
private bool fire;
private void Awake()
{
//Get the Rewired Player object for this player and keep it for the duration of the character's lifetime
player = ReInput.players.GetPlayer(playerId);
// Get the character controller
cc = GetComponent<CharacterController>();
}
void Update()
{
GetInput();
ProcessInput();
}
private void GetInput()
{
// Get the input from the Rewired Player. All controllers that the Player owns will contribute, so it doesn't matter
// whetther the input is coming from a joystick, the keyboard, mouse, or a custom controller.
moveVector.x = player.GetAxis("Horizontal"); // get input by name or action id
moveVector.y = player.GetAxis("Vertical");
fire = player.GetButtonDown("Fire");
}
private void ProcessInput()
{
// Process movement
if(moveVector.x != 0.0f || moveVector.y != 0.0f)
{
cc.Move(moveVector * moveSpeed * Time.deltaTime);
}
// Process fire
if (fire)
{
GameObject bullet = (GameObject)Instantiate(bulletPrefab, transform.position + transform.right, transform.rotation);
bullet.GetComponent<Rigidbody>().AddForce(transform.right * bulletSpeed, ForceMode.VelocityChange);
}
// Process jump
if(player.GetButtonDown("Jump"))
{
rb.AddForce(Vector3.up * jumpForce);
}
}
}
Use Z coordinate instead Y
moveVector.x = player.GetAxis("Horizontal");
moveVector.z = player.GetAxis("Vertical"); // here

Raycast from the player position to the mouse position within range

I want to make a raycast from the player position to the mouse position but it should only have a certain range.
I have tried the following:
using UnityEngine;
public class Raycasting : MonoBehaviour
{
public GameManager gm;
Vector3 worldPosition;
public Transform player;
void FixedUpdate()
{
//Debug.Log(Camera.main.ScreenToWorldPoint(Input.mousePosition));
Debug.DrawLine(player.transform.position, Camera.main.ScreenToWorldPoint(Input.mousePosition), Color.green);
RaycastHit2D hit = Physics2D.Raycast(player.transform.position, Camera.main.ScreenToWorldPoint(Input.mousePosition), 10f);
if(hit.collider.tag == "Enemy")
{
Debug.Log (hit.collider.gameObject);
gm.Attack();
if (GameManager.enemyhealth <= 0)
{
Debug.Log("Enemy Died!");
Destroy(hit.transform.gameObject);
}
}
}
}
in Debug.DrawLine() it works exactly as I want it to — without the range —, but the raycast dosen't detect the enemies around it.
There is a trick to getting the ray end point to solve your problem. Just make sure your camera is orthographic. Also, by determining the enemy layer, detection problems are eliminated.
public GameObject player;
public LayerMask enemyLayer;
void Update()
{
var point = Camera.main.ScreenPointToRay(Input.mousePosition).GetPoint(1);
point.z = player.transform.position.z;
Debug.DrawLine(player.transform.position, point);
var enemy = Physics2D.Linecast(player.transform.position, point, enemyLayer.value);
if (enemy)
{
// do something...
}
}
Also, if you want to control the distance, please leave a comment.
Limited Distance to pointer
This algorithm limits the distance. For example, if you enter 5 for distance, the maximum magnitude will be 5, and if the mouse approaches below 5, it will set the mouse point to the maximum.
public GameObject player;
public LayerMask enemyLayer;
public float distance = 4.5f;
void FixedUpdate()
{
var point = Camera.main.ScreenToWorldPoint(Input.mousePosition);
point.z = player.transform.position.z;
var clamp = Vector3.ClampMagnitude(point - player.transform.position, distance);
Debug.DrawLine(player.transform.position, player.transform.position+clamp);
var enemy = Physics2D.Linecast(player.transform.position, player.transform.position+clamp, enemyLayer.value);
if (enemy)
{
Debug.Log("Detected..");
}
}
Fixed Distance along pointer direction
This algorithm takes the mouse to the player and then adds the size. Mouse location does not affect size and distance is fixed.
public GameObject player;
public LayerMask enemyLayer;
public float distance = 4.5f;
void FixedUpdate()
{
var point = Camera.main.ScreenPointToRay(Input.mousePosition).GetPoint(1);
point.z = player.transform.position.z;
var direction = (point - player.transform.position).normalized;
Debug.DrawRay(player.transform.position, direction*distance);
var enemy = Physics2D.Raycast(player.transform.position, direction, distance, enemyLayer.value);
if (enemy)
{
Debug.Log("Detected..");
}
}

sprite will only move to my first waypoint

I'm trying to get a sprite to move along a set of waypoints after spawned, but whenever the sprite gets to the first waypoint it just stops.
Inspector for the road object that contains all the waypoints
public class MoveEnemy : MonoBehaviour
{
[HideInInspector]
public GameObject[] waypoints;
private int currentWaypoint = 0;
private float lastWaypointSwitchTime;
public float speed = 1.0f;
// Start is called before the first frame update
void Start()
{
lastWaypointSwitchTime = Time.time;
}
// Update is called once per frame
void Update()
{
// 1
Vector3 startPosition = waypoints[currentWaypoint].transform.position;
Vector3 endPosition = waypoints[currentWaypoint + 1].transform.position;
// 2
float pathLength = Vector3.Distance(startPosition, endPosition);
float totalTimeForPath = pathLength / speed;
float currentTimeOnPath = Time.time - lastWaypointSwitchTime;
gameObject.transform.position = Vector2.Lerp(startPosition, endPosition, currentTimeOnPath / totalTimeForPath);
// 3
if (gameObject.transform.position.Equals(endPosition))
{
if (currentWaypoint < waypoints.Length - 2)
{
// 3.a
currentWaypoint++;
lastWaypointSwitchTime = Time.time;
// TODO: Rotate into move direction
}
else
{
// 3.b
Destroy(gameObject);
AudioSource audioSource = gameObject.GetComponent<AudioSource>();
AudioSource.PlayClipAtPoint(audioSource.clip, transform.position);
// TODO: deduct health
}
}
}
}
To solve your problem try replacing
if (gameObject.transform.position.Equals(endPosition))
with
if (Vector2.Distance(gameObject.transform.position, endPosition) < 0.2f)
This should fix the issue where the gameObject stops at the first waypoint. This is because the position of the gameObject and the waypoint may not be the same down to the decimal, so this code will check if it's in a range of 0.2 from the next waypoint.
Also calling these two lines:
Vector3 startPosition = waypoints[currentWaypoint].transform.position;
Vector3 endPosition = waypoints[currentWaypoint + 1].transform.position;
In the update function isn't very efficient. You could instead calculate these values in the start function, and also when you reach the next waypoint.
Hope this helps!

My player prefab moves forward fine until it hits an object, what is going on?

I'm making a unity game based off of TierZoo, and while testing a movement script, i crashed into one of the rigidbody trees for fun... and that made the player prefab stop moving forward, and it started moving in odd directions Here is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewPlayerController : MonoBehaviour
{
private Rigidbody rb;
public GameObject player;
public float thrust = 1.0f;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody>();
}
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.W))
{
rb.AddRelativeForce(player.transform.right * thrust * 2.5f);
}
if (Input.GetKey(KeyCode.A)) { player.transform.Rotate(0, -1, 0); }
if (Input.GetKey(KeyCode.D)) { player.transform.Rotate(0, 1, 0); }
if (Input.GetKey(KeyCode.S))
{
rb.AddRelativeForce(player.transform.right * thrust * -2.5f);
}
}
}
If you can find a way to fix this, let me know.
You could fix the rotation also in Y direction by keeping track of it and setting it hard in FixedUpdate.
In general as said in the comments:
Whenever a Rigidbody is involved you should not
do movements through the transform component
do movements in Update
both breaks the physics engine and you might get strange and unexpected movements or simply no collisions working etc.
Rather do
- go through the Rigidbody component e.g. using MovePosition and MoveRotation
- do these in FixedUpdate
these keeps the physics intact and moves your Rigidbody only e.g. until it collides meanwhile.
Then for the rotation you have framerate-dependent values Rotate(0, -1, 0)! You rather want to use Time.deltaTime in order to get smooth framerate-independent rotation speed - not in 1 degree per frame but rather e.g. 45 degrees per second.
You script could look like
public class NewPlayerController : MonoBehaviour
{
// already reference this via the Inspector
[SerializeField] private Rigidbody rb;
public GameObject player;
public float thrust = 1.0f;
private void Awake()
{
// as fallback get it on runtime
if(!rb) rb = GetComponent<Rigidbody>();
}
bool moveLeft;
bool moveRight;
bool rotateLeft;
bool rotateRight;
private float angle;
void Update()
{
// Get User input here
// (in your case it would be also fine to do it in FixedUpdate.
// What doesn't work in FixedUpdate are mainly the one-time events like GetKeyDown)
moveRight = Input.GetKey(KeyCode.W);
rotateLeft = Input.GetKey(KeyCode.A);
rotateRight = Input.GetKey(KeyCode.D);
moveLeft = Input.GetKey(KeyCode.S);
}
private void FixedUpdate()
{
if(moveRight) rb.AddRelativeForce(rb.rotation * Vector3.right * thrust * 2.5f);
if(moveLeft) rb.AddRelativeForce(rb.rotation * Vector3.right * thrust * -2.5f);
// Now for the rotations keep track of what you rotated already
// and overwrite the rotation with the fix value:
if(rotateLeft)
{
// e.g. for rotating 45°/second
angle -= -45 * Time.deltaTime;
rb.MoveRotation(Quaternion.Euler(0, angle, 0));
}
if(rotateRight)
{
angle += 45 * Time.deltaTime;
rb.MoveRotation(Quaternion.Euler(0, angle, 0));
}
}
}

Why is the camera not rotating to face the first waypoint?

When the game starts, a random waypoint is selected from an array. The camera should then rotate to face the selected random waypoint and start moving towards it.
Once the camera has reached the waypoint, it should wait 3 seconds before rotating to face and move towards the next random waypoint.
The problem I have is in Start(). The camera does not rotate to face the first waypoint before it starts moving towards it. Instead, it moves towards the first waypoint backwards. Then, when it reaches the waypoint, it waits 3 seconds to rotate and move towards the next waypoint.
It's working fine except that the camera does not rotate to face the first selected random waypoint. It's moving to it backward without first rotating to face it.
Here's my code:
The waypoints script
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Waypoints : MonoBehaviour
{
public GameObject[] waypoints;
public GameObject player;
public float speed = 5;
public float WPradius = 1;
public LookAtCamera lookAtCam;
private int current = 0;
private bool rot = false;
public void Init()
{
waypoints = GameObject.FindGameObjectsWithTag("Target");
if(waypoints.Length > 0)
{
StartCoroutine(RotateFacingTarget(waypoints[UnityEngine.Random.Range(0, waypoints.Length)].transform));
}
}
void Update()
{
if (waypoints.Length > 0)
{
if (Vector3.Distance(waypoints[current].transform.position, transform.position) < WPradius)
{
current = UnityEngine.Random.Range(0, waypoints.Length);
rot = false;
StartCoroutine(RotateFacingTarget(waypoints[current].transform));
if (current >= waypoints.Length)
{
current = 0;
}
}
if (rot)
transform.position = Vector3.MoveTowards(transform.position, waypoints[current].transform.position, Time.deltaTime * speed);
}
}
IEnumerator RotateFacingTarget(Transform target)
{
yield return new WaitForSeconds(3);
lookAtCam.target = target;
rot = true;
}
}
The look at camera script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LookAtCamera : MonoBehaviour
{
//values that will be set in the Inspector
public Transform target;
public float RotationSpeed;
//values for internal use
private Quaternion _lookRotation;
private Vector3 _direction;
// Update is called once per frame
void Update()
{
//find the vector pointing from our position to the target
if (target)
{
_direction = (target.position - transform.position).normalized;
//create the rotation we need to be in to look at the target
_lookRotation = Quaternion.LookRotation(_direction);
//rotate us over time according to speed until we are in the required rotation
transform.rotation = Quaternion.Slerp(transform.rotation, _lookRotation, Time.deltaTime * RotationSpeed);
}
}
}
How can I fix this?
Let's assume that Waypoints.Init() is being called and your waypoints variable has an array of 3.
Waypoints.Init() starts a coroutine
Your coroutine waits 3 seconds
After 3 seconds, you set your camera target which Slerps to face the position
Update on its first frame says waypoints.Length > 0 == true
It's not close to its target, and rot is false, so it does not move
Now, you're waiting 3 seconds, not rotating, and not moving.
Your coroutine's 3 second wait time is up and starts the rotation toward your target
rot is now true at the start of your rotation, so your Update method starts moving toward the target as well
It would seem that your logic is off in how the order of operations works. If it needs to act as you describe, I suggest that you operate on the target differently.
I've implemented the following using an enum:
Waypoints
public class Waypoints : MonoBehaviour
{
private GameObject[] waypoints;
private Transform currentWaypoint;
private enum CameraState
{
StartRotating,
Rotating,
Moving,
Waiting
}
private CameraState cameraState;
public GameObject player;
public float speed = 5;
public float WPradius = 1;
public LookAtCamera lookAtCam;
private int current = 0;
private bool isCameraRotating = false;
void Start()
{
cameraState = CameraState.StartRotating;
}
void Update()
{
switch (cameraState)
{
// This state is used as a trigger to set the camera target and start rotation
case CameraState.StartRotating:
{
// Sanity check in case the waypoint array was set to length == 0 between states
if (waypoints.Length == 0)
break;
// Tell the camera to start rotating
currentWaypoint = waypoints[UnityEngine.Random.Range(0, waypoints.Length)].transform;
lookAtCam.target = currentWaypoint;
cameraState = CameraState.Rotating;
break;
}
// This state only needs to detect when the camera has completed rotation to start movement
case CameraState.Rotating:
{
if (lookAtCam.IsFinishedRotating)
cameraState = CameraState.StartMoving;
break;
}
case CameraState.Moving:
{
// Move
transform.position = Vector3.MoveTowards(transform.position, currentWaypoint.position, Time.deltaTime * speed);
// Check for the Waiting state
if (Vector3.Distance(currentWaypoint.position, transform.position) < WPradius)
{
// Set to waiting state
cameraState = CameraState.Waiting;
// Call the coroutine to wait once and not in CameraState.Waiting
// Coroutine will set the next state
StartCoroutine(WaitForTimer(3));
}
break;
}
case CameraState.Waiting:
// Do nothing. Timer has already started
break;
}
}
IEnumerator WaitForTimer(float timer)
{
yield return new WaitForSeconds(timer);
cameraState = CameraState.StartRotating;
}
public void RefreshWaypoints()
{
waypoints = GameObject.FindGameObjectsWithTag("Target");
}
}
LookAtCamera
public class LookAtCamera : MonoBehaviour
{
// Values that will be set in the Inspector
public Transform target;
public float RotationSpeed;
private float timer = 0.0f;
public bool IsRotationFinished
{
get { return timer > 0.99f; }
}
// Update is called once per frame
void Update()
{
if (target != null && timer < 0.99f)
{
// Rotate us over time according to speed until we are in the required rotation
transform.rotation = Quaternion.Slerp(transform.rotation,
Quaternion.LookRotation((target.position - transform.position).normalized),
timer);
timer += Time.deltaTime * RotationSpeed;
}
}
}

Categories

Resources