How to spawn enemies in a certain range but not in-camera? - c#

so I'm making a 2D platformer and I would like to spawn enemies on the whole map but not in the camera range.
I have to mention I am using Cinemachine to follow the player.
This is my script for the spawner:
*public class MobSpawner : MonoBehaviour
{
public GameObject Mob;
float randX;
Vector2 whereToSpawn;
public float spawnRate = 2f;
float nextSpawn = 0.0f;
public GameObject Camera;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Time.time > nextSpawn)
{
nextSpawn = Time.time + spawnRate;
randX = Random.Range(-36f, 62);
whereToSpawn = new Vector2(randX, transform.position.y);
Instantiate(Mob, whereToSpawn, Quaternion.identity);
}
}
}*
As you can see I'm taking 2 points and in between them I spawn enemies. As this range would basically be the entire map, the camera will always be in it.
I want enemies to NOT spawn inside the camera yet spawn outside of it. How can I manage to do this?

I'd say the simplest solution would be to just calculate vector distance between the player position and spawning position. Since this is a 2d space and you are spawning them only on one axis it should be fine.
So you could do
float distanceAwayFromCameraView = 5;
// you will need a method, reference or just get the object via tag
var playerPos = GameObject.Find("Player").transform.position;
randX = Random.Range(-36f, 62);
whereToSpawn = new Vector2(randX, transform.position.y);
while ((Vector2.Distance(whereToSpawn, playerPos) < distanceAwayFromCameraView){
randX = Random.Range(-36f, 62);
whereToSpawn = new Vector2(randX, transform.position.y);
}
Instantiate...
I'm not at my pc to test this right now, please let me know if there's errors

Related

Unity: Unable to get GameObject to spawn

I am trying to get my GameObject (asteroid) to spawn continuously in the game. I looked up and followed this tutorial: https://pressstart.vip/tutorials/2018/09/25/58/spawning-obstacles.html. To make the asteroid move, I created this script (AsteroidObject) and to spawn the objects, I created this script (DeployAsteroids). There are no errors and the Debug.Log appears in the console. But the asteroid game object cannot be seen and will not spawn. Anyone can help? Thanks in advance!
Asteroid Object Codes:
public class AsteroidObject : MonoBehaviour
{
public float speed = 10.0f; //how fast the asteroid will move
private Rigidbody2D rb;
private Vector2 screenBounds; //screenbounds calculation
// Start is called before the first frame update
void Start()
{
rb = this.GetComponent<Rigidbody2D>(); //find rigidbody 2d and set it to rb reference by using the getcomponent
rb.velocity = new Vector2(-speed, 0); //moving the asteroid from right to left by setting the x value, leaving the y value 0 so that it will not move up and down
screenBounds = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, Camera.main.transform.position.z)); //defines the boundaries of the screen on the x and y axis
}
// Update is called once per frame
void Update()
{
//transform.position = new Vector3(Mathf.Clamp(transform.position.x, -9f, 9f),
//Mathf.Clamp(transform.position.y, -4f, 4f), transform.position.z);
if (transform.position.x < screenBounds.x) //check if it is moving to the left of the screen
{
Destroy(this.gameObject);
Debug.Log("hello world");
}
}
}
DeployAsteroid codes:
public class DeployAsteroids : MonoBehaviour
{
public GameObject asteroidPrefab;
public float respawnTime = 1.0f;
private Vector2 screenBounds;
// Start is called before the first frame update
void Start()
{
screenBounds = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, Camera.main.transform.position.z));
StartCoroutine(asteroidWave());
}
private void spawnEnemy()
{
GameObject a = Instantiate(asteroidPrefab) as GameObject;
a.transform.position = new Vector2(screenBounds.x * -2, Random.Range(-screenBounds.y, screenBounds.y));
}
IEnumerator asteroidWave()
{
while (true)
{
yield return new WaitForSeconds(respawnTime);
spawnEnemy();
//Debug.Log("Hello World");
}
}
}
You even say it yourself
the Debug.Log appears in the console.
Well, you log in the moment you destroy your object => it is already gone.
Destroy(this.gameObject);
Debug.Log("hello world");
Unless you mean the out commented log after the spawn method. You still immediately destroy the object. I think you would spot it immediately if you would use useful logs and not twice the same one
Debug.Log("Spawned a new object");
and
Debug.Log("Destroyed an object");
So what exactly is happening then?
You immediately destroy the new spawned objects!
you do
screenBounds = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, Camera.main.transform.position.z));
which stores the position at the right border of the screen.
Let's say e.g. somewhere at x = 2; (total random imaginary example number).
Then right after spawning you set it to
a.transform.position = new Vector2(screenBounds.x * -2, Random.Range(-screenBounds.y, screenBounds.y));
So for the x you set it to (using our example value) x = -4;
This is not even the left boarder of the screen but even beyond!
Additionally you tell the asteroid to move
rb.velocity = new Vector2(-speed, 0);
so assuming the value speed is positive even more into negative x direction.
And finally you do
// Even without the Rigidbody this is already comparing
// if(-2 * screenBounds.x < screenBounds.x)
// Or with our example numbers
// if(-4 < 2)
if (transform.position.x < screenBounds.x)
{
Destroy(this.gameObject);
Debug.Log("hello world");
}
=> This condition will always be true and immediately destroys your objects in the next frame.
So what should I do instead?
I assume you are trying to spawn the asteroid at the right boarder.
And want to destroy it after it passed the left boarder. So it should probably be
private void spawnEnemy()
{
GameObject a = Instantiate(asteroidPrefab);
Vector3 rightEdgeWorldPoint = Camera.main.ScreenToWorldPoint(
new Vector3(Screen.width, Random.Range(0, Screen.height),
Camera.main.nearClipPlane);
rightEdgeWorldPoint.z = 0f;
a.transform.position = rightEdgeWorldPoint;
}
And in the asteroid
if (Camera.main.WorldToScreenPoint(transform.position).x < 0)
{
Destroy(this.gameObject);
Debug.Log("Left the screen -> destroyed");
}
Note: Typed on smartphone but I hope the idea gets clear
Go to the scene hierarchy and check if the asteroids can be seen there. May be you do not see them within the game or the scene camera, but if they are in the scene they are spawn somewhere. It would be interesting info to update in the question.
I did not dig into the code but if they are there, you need to check or set the position in which they are spawned respect to the camera, for the asterioids to be in the field of view.
Check the posistion given in this line: a.transform.position = new Vector2(screenBounds.x * -2, Random.Range(-screenBounds.y, screenBounds.y)); and if it fits in the camera field of view.

How to make a thrown object rotate automatically based on the direction its thrown in?

so what I am trying to do is pull a spear back(like a slingshot) and send it flying, all the while making it rotate while it's flying in the air to mimic a real thrown spear. So far, I have only been able to make it rotate after a few seconds to a certain position but I can already tell that this isn't really a permanent fix and will definitely lead to problems down the line. I have tried looking for better ways of rotating the object but I can't really find anybody else who had a problem like this and it's hard to understand the unity documentation regarding rotation as it's for 3D objects.
Here is a gif showing how it looks like:
https://gfycat.com/chiefglasshorsemouse
This is the code that I have attached to my spear object that's responsible for letting me pull it back and launch it with the mouse:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.Scripting.APIUpdating;
public class spear : MonoBehaviour
{
Rigidbody2D rb;
Vector3 velocity;
private Vector3 _initialPosition;
[SerializeField] private float _launchPower = 500;
bool rotating = false;
public GameObject objectToRotate;
private float _timeSittingAround;
private bool _spearWasLaunched;
[SerializeField] private float _spearRotation = 360;
private void Awake()
{
_initialPosition = transform.position;
rb = GetComponent<Rigidbody2D>();
}
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
//rotates the spear
IEnumerator rotateObject(GameObject gameObjectToMove, Quaternion newRot, float duration)
{
if(rotating)
{
yield break;
}
rotating = true;
Quaternion currentRot = gameObjectToMove.transform.rotation;
float counter = 0;
while(counter < duration)
{
counter += Time.deltaTime;
gameObjectToMove.transform.rotation = Quaternion.Lerp(currentRot, newRot, counter / duration);
yield return null;
}
rotating = false;
}
//reloads the scene if spear goes out of bounds or lays dormant for 2 seconds
void Update()
{
GetComponent<LineRenderer>().SetPosition(0, transform.position);
GetComponent<LineRenderer>().SetPosition(1, _initialPosition);
if (_spearWasLaunched &&
GetComponent<Rigidbody2D>().velocity.magnitude <= 0.1)
{
_timeSittingAround += Time.deltaTime;
}
if(transform.position.y > 30 ||
transform.position.y < -12.5 ||
transform.position.x > 40 ||
transform.position.x < -20 ||
_timeSittingAround > 2)
{
string currentSceneName = SceneManager.GetActiveScene().name;
SceneManager.LoadScene(currentSceneName);
}
}
//slingshot mechanic
private void OnMouseDrag()
{
Vector3 newPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
transform.position = new Vector3(newPosition.x, newPosition.y);
}
private void OnMouseDown()
{
GetComponent<SpriteRenderer>().color = Color.red;
GetComponent<LineRenderer>().enabled = true;
}
//launches the spear when mouse is released as well as begins the rotating mechanic
private void OnMouseUp()
{
Vector2 directionToInitialPosition = _initialPosition - transform.position;
GetComponent<Rigidbody2D>().AddForce(directionToInitialPosition * _launchPower);
GetComponent<Rigidbody2D>().gravityScale = 1;
_spearWasLaunched = true;
GetComponent<LineRenderer>().enabled = false;
Quaternion rotation2 = Quaternion.Euler(new Vector3(0, 0, _spearRotation));
StartCoroutine(rotateObject(objectToRotate, rotation2, 3f));
}
}
There are a few ways to do it. You could try setting up rigidbodies and maybe joints and weighting different parts so that the tip of the spear naturally rotates the spear. More info on that here: https://answers.unity.com/questions/14899/realistic-rotation-of-flying-arrow.html
There's also another solution in that link for doing it with one line of code, making it rotate in the direction of movement.
In the top of your class, add this:
public Rigidbody2D mySpear;
Then in the inspector, drag the spear object into that slot to assign it. Then in your code, remove all your rotation code. In Update() add this:
mySpear.transform.right =
Vector2.Slerp(mySpear.transform.right, mySpear.rigidbody.velocity.normalized, Time.deltaTime)
A third way to do it is...
Figure out the rotation (on the z axis) of the object when it's pointing directly down. Put that down as a constant called downRotation or something.
Set a minimum velocity and maximum velocity that will be used by the rotation.
I recommend also adding an AnimationCurve variable.
Instead of having a special function for rotation, just put it in FixedUpdate. If the spear's velocity.x is not 0, then rotate.
The speed of the rotation should be based on the speed of the object. If the object's x velocity is very slow then it would probably fall (rotate) more quickly. If it's going very fast, the rotation would not change much.
Don't use this exact code, but it'd look something like this:
// This part figures out how fast rotation should be based on how fast the object is moving
var curve = speedCurve.Evaluate(1 - ((rigidBody.velocity.x - minVelocity) / (maxVelocity - minVelocity)));
// Rotates the spear
var rotate = rigidBody.rotation.eulerAngles;
var speed = curve * rotateSpeed * Time.fixedDeltaTime;
rotate.z = Mathf.MoveTowards(rotate.z, downRotation, speed);
transform.rotation = Quaternion.Euler(rotate);
Side note: You should use rigidbody rotation like I am here, and you should be assigning components to variables in the inspector instead of calling GetComponent every frame.

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!

Shooting a projectile but goes in wrong direction - 2D game

I am trying to make a simple game where I am shooting a projectile when the user touches the screen, I first spawn 8 projectiles (sprites) and when the user touches the screen I would like the top projectile to fly in the touch direction. I was able to do this; However, every time I shoot, the projectile goes in the wrong direction, here is an image which will illustrate the issue.
Obviously the image is still here but the object will continue flying until it goes out of the screen and then gets destroyed.
Here is the code snippet that handles this
GameplayController.cs
if (Input.GetMouseButtonDown(0))
{
Vector3 position = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0);
position = Camera.main.ScreenToWorldPoint(position);
GameObject target;
target = new GameObject();
target.transform.position = position;
Arrow comp = currentArrows[0].GetComponent<Arrow>();
comp.setTarget(target.transform);
comp.GetComponent<Arrow>().arrowSpeed = 12;
comp.GetComponent<Arrow>().shoot = true;
currentArrows.RemoveAt(0);
Destroy(target);
}
I know I am getting the mouse input here and not the phone touch and that's fine for me, later I will convert it to the touch input.
Arrow.cs
public bool shoot = false;
public float arrowSpeed = 0.0f;
public Vector3 myDir;
public float speed = 30.0f;
private Transform target;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if(shoot)
{
transform.position += transform.right * arrowSpeed * Time.deltaTime;
}
}
public void setTarget(Transform targetTransform)
{
this.target = targetTransform;
Vector3 vectorToTarget = target.position - transform.position;
float angle = Mathf.Atan2(vectorToTarget.y, vectorToTarget.x) * Mathf.Rad2Deg;
Quaternion q = Quaternion.AngleAxis(angle, Vector3.forward);
transform.rotation = Quaternion.Slerp(transform.rotation, q, Time.deltaTime * speed);
}
private void OnBecameInvisible()
{
print("Disappeared");
Destroy(gameObject);
Gameplay.instance.isShooting = false;
}
Vector3 position = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0);
I think that your problem is that you're getting the screen coordinates by click, not the world coordinates, which is actually two different things. In order to direct your projectile correctly you need to convert your screen coordinates to world like it's done here.
The next thing is how you move the projectile:
transform.position += transform.right * arrowSpeed * Time.deltaTime;
You're moving the projectile to the right and then rotating it somehow. Maybe you should try to move it with Vector3.Lerp, which will be easier and rotate it with Transform.LookAt.

Spawned Units always move to same target regardless of setting alternate points

I am new to unity and i am trying to create a new game.
using UnityEngine;
using System.Collections;
public class Enemyblue1script : MonoBehaviour {
// Use this for initialization
public float speed; // speed variable
public float RotSpeed = 90f;
public GameObject RocketGO; // Reference to our main character Rocket
void Start ()
{}
void Update()
{
Vector2 Pos = new Vector2 (-10, 5);
gameObject.transform.position = Vector3.MoveTowards (gameObject.transform.position, Pos, speed * Time.deltaTime);
Invoke ("MoveFace", 2f);
}
void MoveFace()
{
if( RocketGO == null)
{
GameObject go = GameObject.FindGameObjectWithTag("Rocket");
if(go != null){
RocketGO = go ;}
}
if( RocketGO == null)
return;
Vector3 dir = RocketGO.transform.position - gameObject.transform.position;
dir.Normalize();
float zAngle = Mathf.Atan2(dir.x , dir.y) * Mathf.Rad2Deg + 360;
Quaternion desiredRot = Quaternion.Euler(0 ,0,-zAngle);
gameObject.transform.rotation = Quaternion.RotateTowards(gameObject.transform.rotation , desiredRot , RotSpeed * Time.deltaTime);
}
}
This is my script for Moving Enemy towards some point (using MoveTowards funtion) and rotating enemy to Players direction. And i have one more script that is for Spawning these enemeies (Two Enemies at a time ) Script is
using UnityEngine;
using System.Collections;
public class Enemy1Spawner : MonoBehaviour {
//our meteorite prefab
public GameObject Enemy1blue;
public float speed;
public GameObject p1;
public GameObject p2;
// Use this for initialization
void Start ()
{}
// Update is called once per frame
void Update ()
{}
void SpawnEnemy1blue()
{
//bottom left point of screen
//Vector2 min = Camera.main.ViewportToWorldPoint (new Vector2 (0, 0));
//top right point of screen
//Vector2 max = Camera.main.ViewportToWorldPoint (new Vector2 (1, 1));
GameObject Enemy1ablue = (GameObject)Instantiate (Enemy1blue);
GameObject Enemy2ablue = (GameObject)Instantiate (Enemy1blue);
Enemy1ablue.transform.position = new Vector2 (-11,9);
Enemy2ablue.transform.position = new Vector2 (11,9);
}
public void startEnemy1blueSpawner()
{
Debug.Log ("Invoked Spawning Blue Enemy");
InvokeRepeating ("SpawnEnemy1blue",5f,30f);
}
//function to stop spawning when game over
public void StopEnemy1blueSpawning()
{
CancelInvoke ("SpawnEnemy1blue");
}
}
So now while Spawning enemies! my enemies are moving towards only one point i.e Vector2 Pos = new Vector2 (-10, 5); which I have defined in Updat() method of Enemyblue1script, and i want to spawn two enemies at a different positions one at Vector2 Pos = new Vector2 (-10, 5) (to the left side); and one at Vector2 Pos = new Vector2 (10, 5) (to the right side);
By using these two scripts i am able to spawn two enemies at a time, but they both move to same point, which is what i do not want to happen. I have tried stuff like in ENEMY1SPAWNER script > SpawnEnemy1blue() function i have also created two points using vector2 and i called two Movetowards functions there for two different enemies but is not working they are not moving.
Vector2 Pos1 = new Vector2 (-10, 5);
Vector2 Pos2 = new Vector2 (10, 5);
GameObject Enemy1ablue = (GameObject)Instantiate (Enemy1blue);
GameObject Enemy2ablue = (GameObject)Instantiate (Enemy1blue);
Enemy1ablue.transform.position = new Vector2 (-11,9);
Enemy2ablue.transform.position = new Vector2 (11,9);
Enemy1ablue.transform.position = Vector3.MoveTowards (Enemy1ablue.transform.position, Pos1, speed * Time.deltaTime);
Enemy2ablue.transform.position = Vector3.MoveTowards (Enemy2ablue.transform.position, Pos2, speed * Time.deltaTime);
It's hard to tell exactly where you put this line:
Enemy1ablue.transform.position = Vector3.MoveTowards(
Enemy1ablue.transform.position, Pos1, speed * Time.deltaTime);
But if it's just in SpawnEnemy1blue() then it will only happen once, so the enemy will move a little closer to the target then stop.
I suggest organizing the code a little differently: add a Vector2 target field to Enemyblue1script, set it when you initialize a new enemy, and move towards it in Enemyblue1script.Update. Instead of creating and using Pos, use target.

Categories

Resources