Patrol using children waypoints - unity3d

In my game, I have multiple objects that should patrol different waypoints.
public Transform[] targets;
public float speed = 1;
private int currentTarget = 0;
IEnumerator StartMoving ()
{
while (true)
{
float elapsedTime = 0;
Vector3 startPos = transform.position;
while (Vector3.Distance(transform.position, targets[currentTarget].position) > 0.05f )
{
transform.position = Vector3.Lerp(startPos, targets[currentTarget].position, elapsedTime/speed);
elapsedTime += Time.deltaTime;
yield return null;
}
yield return new WaitForSeconds(delay);
}
}
The code works fine, but for organizing reasons I would like each object to have its waypoints as children of that object, but the problem is because the waypoints are children of that object, they move with it, which results in an unwanted behavior.
Is there any workaround for this?

You'd have to add a level of hierarchy. One parent item as container for the character and the waypoints. Then the code moves the character only.
- Container with Scripts
- Character with mesh
- Waypoints
- WaypointA
- WaypointB
- ...

Well, if you really want to parent them.. you can move the waypoints in the opposite direction of the patrolling objects:
//Remember prev pos
Vector3 prevPosition = transform.position;
transform.position = Vector3.Lerp(startPos, targets[currentTarget].position, elapsedTime/speed);
//Move the waypoints in the opposite direction
foreach(Transform childWaypoint in ...)
{
childWaypoint.position -= transform.position - prevPosition;
}
But a better solution would be the assign an array of Vector3 for your patrolling objects..
public class Patrollers : Monobehaviour
{
Vector3[] _waypoints;
//..
}

Related

How to make the enemy look at the player if the player is not hiding behind a wall?

The enemy must look at the player if the player comes into view. Below is the code how I did it. The problem is that the enemy sees the player through the wall. How can I fix the code so that the enemy does not see the player through obstacles?
I came up with this solution - to filter out all hits to the player, and then additionally filter out those that do not pass through the barrier. However, I don't know how to implement it.
RaycastHit[] hitsInfo = Physics.SphereCastAll(head.position, sightDistance, transform.forward, sightDistance);
for (int i = 0; i < hitsInfo.Length; i++)
if (hitsInfo[i].transform.gameObject.tag == "Player") {
transform.LookAt(hitsInfo[i].transform.position);
transform.eulerAngles = new Vector3(0, transform.eulerAngles.y, 0);
Debug.DrawLine(head.position, hitsInfo[i].transform.position, Color.red, 0.05f, true);
break;
}
Below is an illustration: the player is white, the enemy is blue, the red is a raycast visualization.
You can use math to check whether the player in sight or not and than use raycast to filter all obstacles.
Here is an example:
using UnityEngine;
public class Enemy : MonoBehaviour
{
[SerializeField] private Transform _objectOfInterest;
[SerializeField] private float _sightAngle = 60f;
[SerializeField] private float _maxDistance = 10f;
private void Update()
{
if (InSight(_objectOfInterest))
{
Debug.Log("InSight");
if (BehindObstacle((_objectOfInterest)))
{
Debug.Log("BehindObstacle");
}
}
}
private bool InSight(Transform objectOfInterest)
{
var forwardDirection = transform.forward;
var directionToTarget = objectOfInterest.position - transform.position;
var angle = Mathf.Acos(Vector3.Dot(forwardDirection.normalized, directionToTarget.normalized));
if (angle * 100 <= _sightAngle) return true;
return false;
}
private bool BehindObstacle(Transform objectOfInterest)
{
var direction = objectOfInterest.position - transform.position;
var raycastDistance = direction.magnitude < _maxDistance ? direction.magnitude : _maxDistance;
var ray = new Ray(transform.position, direction.normalized);
var hits = new RaycastHit[16];
var hitsAmount = Physics.RaycastNonAlloc(ray, hits, raycastDistance);
Debug.DrawRay(transform.position, direction.normalized * raycastDistance);
if (hitsAmount == 0) return true;
foreach (var hit in hits)
{
if (hit.transform.TryGetComponent<Player>(out Player player) == false) // Player is empty MonoBehaviour in my case
{
return true;
}
return false;
}
return false;
}
}
All the math are in InSight method. forwardDirection is the direction where an enemy is looking. When we use "targetposition" - "myposition" in directionToTarget we getting vector that pointing from our position to target. Than we use simple function to get the angle at which the player is in relation to the enemy. The function is - angle = Acos(Dot(A, B)) where A and B are normalized vectors.
In BehindObstacle we just making raycast to player position and if there are any obstacle returning true. You can set sight angle and max distance on your opinion.
And don't use this in Update method (i'm using it only for test) or very often, it can cause performance issues.

Unity 2D instantiate prefabs order

I'm instantiating enemy prefabs in waves, but when I do, the sprites are not consistent on the Z axis. Sometimes the second enemy is on top of the first one, and sometimes behind.
I want the new instance always behind the other.
Keep the instance position and instantiate the gameobject behind.
GameObject myGo1 = GameObject.Instantiate(prefabRef, Vector3.Zero,
Quaternion.Identity);
float distance = 1.0f;
Vector3 myGo2Pos = myGo1.position - myGo1.transform.forward * distance;
GameObject myGo2 = GameObject.Instantiate(prefabRef, myGo2Pos, Quaternion.Identity);
For the 1st GO instatiation, where Vector3.Zero you can choose the position of your choice. For whatever pos and rot you instantiate myGo1 with, the code Vector3 myGo2Pos = myGo1.position - myGo1.forward * distance; should instantiate the 2nd one distance meters behind.
Not debugged code, just for your inspiration.
Probably you might need the 2nd GO to look to the first one, in that case you can do:
GameObject myGo2 = GameObject.Instantiate(prefabRef, myGo2Pos, myGo1.transform.rotation);
Alright i figured it out. had to add a sprite renderer component like so:
SpriteRenderer spriteRenderer;
and where i instantiate:
void FollowPath()
{
if (!isAttacking)
{
if (waypointIndex < waypoints.Count)
{
**spriteRenderer = GetComponent<SpriteRenderer>();
spriteRenderer.sortingOrder++;**
Vector3 targetPosition = waypoints[waypointIndex].position;
float delta = waveconfig.GetMoveSpeed() * Time.deltaTime;
transform.position = Vector2.MoveTowards(transform.position, targetPosition, delta);
if (transform.position == targetPosition)
{
waypointIndex++;
}
}
}
}

How to optimize jump like in Flappy Bird in Unity?

I can write somehow this code for optimization?
If not use coroutines, when I click on space the next jump has more force and so on.
If use rb.MovePosition, the character will move as if 15 fps. I know, change Time in settings. But I want to know if exist another method...
private void Update() {
if(Input.GetKeyDown(KeyCode.Space)) {
StopAllCoroutines();
StartCoroutine(Jump());
}
}
private IEnumerator Jump() {
if(rb.bodyType != RigidbodyType2D.Dynamic) {
rb.bodyType = RigidbodyType2D.Dynamic;
}
rb.constraints = RigidbodyConstraints2D.FreezePositionY;
_pos = transform.position;
for (float t = 0; t < 1; t += Time.deltaTime * 4f)
{
transform.position = Vector3.Lerp(transform.position, new Vector3(transform.position.x, _pos.y + .35f, transform.position.z), t);
yield return null;
}
rb.constraints = RigidbodyConstraints2D.None;
}
Rigidbodies exist so you don't need to directly adjust an object's transform. Since you have a Rigidbody2d you can just set the velocity directly:
public float jumpSpeed = 5f; // Whatever feels right
private void FixedUpdate() {
if(Input.GetKeyDown(KeyCode.Space)) {
rb.velocity = Vector2.up * jumpSpeed;
}
}
(Edited to use velocity instead of AddForce)

Unity: Diagonal movement issue

I have a test 2D game where my player moved from left to right and when he reached the end of the screen it would just transform on the other side. I had a change of heart and made my player move diagonally. It did work, but I have no idea how to make the player stop when it hits the end of the screen. I don't want it to transform on the other side, but rather just stop. So far all my results are either some glitching on the edges or not stopping at all. I have provided my PlayerController script. Right now my player moves diagonally and he will just continue after the edge of the screen. If anyone can assist me I would be very grateful. I never thought I will deal with diagonal movement, but I really want to learn how to do it.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayerController : MonoBehaviour {
public float speed = 7;
public float speedy = 7;
public event System.Action OnPlayerDeath;
float screenHalfWidthInWorldUnits;
Rigidbody2D rb;
void Start () {
rb = GetComponent<Rigidbody2D>();
float halfPlayerWidth = transform.localScale.x / 2f;
screenHalfWidthInWorldUnits = Camera.main.aspect * Camera.main.orthographicSize;
}
void Update()
{
float inputX = Input.GetAxisRaw("Horizontal");
float velocity = inputX * speed;
transform.Translate(Vector2.right * velocity * Time.deltaTime);
}
public void MoveRight()
{
rb.velocity = new Vector2(speed, speedy);
}
public void MoveLeft()
{
rb.velocity = new Vector2(-speed, -speedy);
}
public void Stop()
{
rb.velocity = Vector2.zero;
}
void OnTriggerEnter2D(Collider2D triggerCollider)
{
if (triggerCollider.tag =="Box")
{
if (OnPlayerDeath != null)
{
OnPlayerDeath();
}
Destroy(gameObject);
}
}
}
You can check if the player is at what you define as the border of the map.
If you check this for the x and y axis respectively, you can then lock his x or y axis to the border and not further.
Here is an example of a script I made earlier that does that.
If I understand you correctly you would like to be able to move diagonally. In my sample script below you can move both straight and diagonally, you can also warp between the edges or stop at the edges as you spoke of wanting to.
This script is a bit more advanced than what you need probably, so let me know if something about it confuses you.
Please note that if you set the boolean _ShouldWarp to false he will stop at the border, otherwise he will warp from edge to edge of the map.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public float _Speed = 5f;
public WorldBounds _WorldBounds;
public bool _ShouldWarp; //If _ShouldWarp is false, will block players movement instead.
void Update()
{
Move();
WarpIfAtBoundary();
}
void Move()
{
float horizontal = Input.GetAxisRaw("Horizontal");
float vertical = Input.GetAxisRaw("Vertical");
transform.Translate(Vector3.right * Time.deltaTime * _Speed * horizontal);
transform.Translate(Vector3.up * Time.deltaTime * _Speed * vertical);
}
void WarpIfAtBoundary()
{
//X Axis
//If player is at positive X boundary
if (transform.position.x > (_WorldBounds.xPlus + _WorldBounds.xBuffer))
{
if (_ShouldWarp) //Teleport/warp player is set to enabled
{
transform.position = new Vector3(_WorldBounds.xMinus, transform.position.y, transform.position.z);
}
else //Otherwise keep player in border position
{
transform.position = new Vector3(_WorldBounds.xPlus + _WorldBounds.xBuffer, transform.position.y, transform.position.z);
}
}
//Else if player is at negative X boundary
else if (transform.position.x < (_WorldBounds.xMinus - _WorldBounds.xBuffer))
{
if (_ShouldWarp)//Teleport/warp player is set to enabled
{
transform.position = new Vector3(_WorldBounds.xPlus, transform.position.y, transform.position.z);
}
else //Otherwise keep player in border position
{
transform.position = new Vector3(_WorldBounds.xMinus - _WorldBounds.xBuffer, transform.position.y, transform.position.z);
}
}
//Y Axis
//If player is at positive Y boundary
if (transform.position.y > (_WorldBounds.yPlus + _WorldBounds.yBuffer))
{
if (_ShouldWarp)//Teleport/warp player is set to enabled
{
transform.position = new Vector3(transform.position.x, _WorldBounds.yMinus, transform.position.z);
}
else //Otherwise keep player in border position
{
transform.position = new Vector3(transform.position.x, _WorldBounds.yPlus + _WorldBounds.yBuffer, transform.position.z);
}
}
//Else if player is at negative Y boundary
else if (transform.position.y < (_WorldBounds.yMinus - _WorldBounds.yBuffer))
{
if (_ShouldWarp)//Teleport/warp player is set to enabled
{
transform.position = new Vector3(transform.position.x, _WorldBounds.yPlus, transform.position.z);
}
else //Otherwise keep player in border position
{
transform.position = new Vector3(transform.position.x, _WorldBounds.yMinus - _WorldBounds.yBuffer, transform.position.z);
}
}
}
}
//Set as serializable so it displays correctly in Unity's inspector window.
[System.Serializable]
public class WorldBounds
{
[Header("Bounds")]
public float xMinus = -9.4f;
public float xPlus = 9.4f;
public float yMinus = -9.4f;
public float yPlus = 9.4f;
[Header("BufferZone")]
public float xBuffer = 1f;
public float yBuffer = 1f;
}
EDIT:
With your additions will I be able to assign the movement to my two buttons. One is up and right and the other is down and left.
void Move()
{
float horizontal = Input.GetAxisRaw("Horizontal");
transform.Translate(Vector3.right * Time.deltaTime * _Speed * horizontal);
transform.Translate(Vector3.up * Time.deltaTime * _Speed * horizontal);
}
This should work to move diagonally left and down as well as up and right.
The change i made is to use the Horizontal input for both X and Y movement.
I don't need the warp. Just to step at the defined borders
You can set the Warp boolean to false or remove the warp parts from the code then :-), should work.

unity changing weapons.prefab issues

So i'm trying to change weapons for my 2d top down space shooter. by weapon I just mean my bullet prefab so it shoots a different bullet which I can then add damage to etc.
here is the code I have. when I press number 2 it shoots my prefab clone in the hierarchy but its greyed out and nothing shows up in the game view. Below is my playerShoot code.
public class playerShoot : MonoBehaviour {
public Vector3 bulletOffset = new Vector3 (0, 0.5f, 0);
float cooldownTimer = 0;
public float fireDelay = 0.25f;
public GameObject bulletPrefab;
int bulletLayer;
public int currentWeapon;
public Transform[] weapons;
void Start () {
}
void Update () {
if (Input.GetKeyDown(KeyCode.Alpha1)){
ChangeWeapon(0);
}
if (Input.GetKeyDown(KeyCode.Alpha2)){
ChangeWeapon(1);
}
cooldownTimer -= Time.deltaTime;
if (Input.GetButton("Fire1") && cooldownTimer <= 0){
cooldownTimer = fireDelay;
Vector3 offset = transform.rotation * bulletOffset;
GameObject bulletGO = (GameObject)Instantiate(bulletPrefab, transform.position + offset, transform.rotation);
bulletGO.layer =gameObject.layer;
}
}
public void ChangeWeapon(int num){
currentWeapon = num;
for (int i = 0; i < weapons.Length; i++){
if (i ==num)
weapons[i].gameObject.SetActive(true);
else
weapons[i].gameObject.SetActive(false);
}
}
}
Keeping the rest of the code same, just change the following line
GameObject bulletGO = (GameObject)Instantiate(bulletPrefab, transform.position + offset, transform.rotation);
to
GameObject bulletGO = (GameObject)Instantiate(weapons[currentWeapon].gameObject, transform.position + offset, transform.rotation);
What the change does is use the transforms for the weapons in your weapon array, rather than using the bulletPrefab.
The problem occured because you were Instantiating a single prefab, which was the same as the Transform you used for the first element in your weapons array. So, when you call ChangeWeapon(1), the prefab would get deactivated. This reulted in inactive GameObjects being instantiated.
What I would suggest you do is to have two separate prefabs and spawn those accordingly.