I have an object that will fire out projectiles, I was trying to use a list to spawn in the Rockets(projectiles) so I could delete them when they collide with an object. So I first create List<Rectangle> Rockets; I then add in a function for the rockets to be spawned in and fired constantly:
if (Time > 0.2)
{
Time = 0;
Rockets.Add(new Rectangle((int)Position.X, (int)Position.Y, rocketTexture.Width, rocketTexture.Height));
}
I then try to update them so they will move across the screen by doing a foreach:
foreach (Rectangle r in Rockets)
{
}
Question
This is where I get stuck, how do I call upon the x and y value inside the list of each Rocket so i can move it across the screen?
I may be thinking too hard about this and there is an easier way to create a large amount of projectiles and have them despawn when colliding with a way or when they go too far.
In game development, you'd rather implement a Rocket class with a update() method, in which you'd move the rocket by some speed_x and speed_y attribute. You would then in your main run() method check collisions by calling Rocket.getRect() (or just a .Rect property, which could be implemented in a parent class Entity or something).
That being said, i may not have understood the problem.
Here's a code example, hope it helps
class Rocket
{
int _life;
public Rocket(Vector2 position, Vector2 direction)
{
_position = position;
_direction = direction;
//life of rocket. Once it reaches zero the rocket is removed.
_life = 100;
}
Vector2 _position
public Vector2 Position
{
get
{
return _position;
}
}
Vector2 _velocity;
Vector2 Velocity
{
get
{
return _velocity;
}
}
public int Life
{
get
{
return _life;
}
}
public void Update(World world)
{
_life--;
_position += _velocity;
//could check for collisions here, eg:
foreach (Ship ship in world.Ships)
{
if (Intersects(ship))
{
//collision!
//destroy this rocket
_life = 0;
//damage the ship the rocket hit
ship.ApplyDamage(10);
return;
}
}
}
}
class Game
{
List<Rocket> _rockets = new List<Rocket>();
List<Rocket> _deadRockets = new List<Rocket>();
void Update(GameTime gameTime)
{
//.ToArray() is used here because the .net list does not allow items to be removed while iterating over it in a loop.
//But we want to remove rockets when they are dead. So .ToArray() means we are looping over the array not the
//list, so that means we are free to remove items from the list. basically it's a hack to make things simpler...
foreach (Rocket rocket in _rockets.ToArray())
{
//world is an object that contains references to everything on the map
//so that the rocket has access to that stuff as part of it's update
rocket.Update( world );
//if the rocket has run out of life then remove it from the list
if (rocket.Life <= 0)
_rockets.Remove(rocket);
}
}
void FireRocket(Vector2 from, Vector2 direction)
{
Rocket newRocket = new Rocket(from, direction);
_rockets.Add(newRocket);
}
}
do a for loop instead of a foreach loop. I also recommend using arrays(and make a Rocket class instead of just Vector2s).
for (int i = 0; i < Rockets.Count; i++)
{
Rockets[i] = new Rectangle(Rockets[i].X + 20, Rockets[i].Y + 20);
}
Related
I have a spawner object. Every time a gameobject is spawned, I want that object to move randomly (wandering). The problem in my script is that the gameobject movement is very random (jittery). How can I solve this?
void Start ()
{
InvokeRepeating("SpawnNPC", Spawntime, Spawntime);
}
// Update is called once per frame
void Update () {
population = GameObject.FindGameObjectsWithTag("NPCobject");
for (int i = 0; i < population.Length; i++)
{
getNewPosition();
if (population[i].transform.position != pos)
{
population[i].transform.position = Vector3.MoveTowards(population[i].transform.position, pos, .1f);
}
}
}
void getNewPosition()
{
float x = Random.Range(-22, 22);
float z= Random.Range(-22, 22);
pos = new Vector3(x, 0, z);
}
I made the New randomize vector in different method, because I plan to change it with pathfinder function and make it in different thread/task.
You are choosing a new direction every single frame. That will always be very jittery. You wan't to only change direction after, at least, a small interval. Here is a link to one way to do that from Unity's website. https://docs.unity3d.com/ScriptReference/MonoBehaviour.InvokeRepeating.html
What about using Navigation? As you said wandering, I thought it would give you a nice result and also make your code simple.
The following screenshot is a sample with Navigation. The moving game objects are also changing their direction nicely, although it cannot be seen in the sample because the game object is capsule...
Ground game object in the sample program has NavMesh. See here to build NavMesh.
Agent game object has NavMeshAgent Component. See here to set it up.
Th Behaviour class below is for Agent game object.
using UnityEngine;
using UnityEngine.AI;
public class NavAgentBehaviour : MonoBehaviour {
public Transform[] Destinations { get; set; }
// Use this for initialization
void Start ()
{
InvokeRepeating("changeDestination", 0f, 3f);
}
void changeDestination()
{
var agent = GetComponent<NavMeshAgent>();
agent.destination = Destinations[Random.Range(0, Destinations.Length)].position;
}
}
The next Behaviour class is just for spawning the Agent and setting up the destinations. On Unity, set it to whatever game object in the scene, and allocate game objects to the fields.
using UnityEngine;
public class GameBehaviour : MonoBehaviour {
public GameObject Agent;
public Transform SpawnPoint;
public Transform Destination1;
public Transform Destination2;
public Transform Destination3;
// Use this for initialization
void Start()
{
Agent.SetActive(false);
InvokeRepeating("Spawn", 0f, 2f);
}
void Spawn() {
var newAgent = Instantiate(Agent);
newAgent.GetComponent<NavAgentBehaviour>().Destinations = new[] { Destination1, Destination2, Destination3 };
newAgent.transform.position = SpawnPoint.position;
newAgent.SetActive(true);
}
}
Increase the number of destination, to make the moves look more random. By the way, the destinations do not need to be specified by game objects, which is only for making it easy to see the sample program's behaviour.
The source of the jitteriness comes from the fact that you are updating the position to move every frame so your objects never have a consistent location to move to. I would instead suggest attaching a new script to each of your objects that individually handles their movement. In that script you could do something like the following, which has a delay to keep the target position for more than 1 frame.
float delaytimer;
Vector3 pos;
void Start () {
getNewPosition(); // get initial targetpos
}
void Update () {
delaytimer += Time.deltaTime;
if (delaytimer > 1) // time to wait
{
getNewPosition(); //get new position every 1 second
delaytimer = 0f; // reset timer
}
transform.position = Vector3.MoveTowards(transform.position, pos, .1f);
}
void getNewPosition()
{
float x = Random.Range(-22, 22);
float z= Random.Range(-22, 22);
pos = new Vector3(x, 0, z);
}
You are changing the direction they are moving in every frame, thats what is causing the jitter.
You could wait a few moments before you change the direction, perhaps something like this.
// Outside update
float betweenChanges = 2;
float lastChange = 0;
// Inside update
if(Time.realtimeSinceStartup > lastChange)
{
// Change directions
// ...
lastChange = Time.realTimeSinceStart + betweenChanges;
}
You could also solve this by using InvokeRepeating or a Coroutine.
If you dont want all the NPC's to change direction at the same time and still plan on controlling every NPC from the same class like in your example, you should perhaps add a timer for each NPC instance instead and use that to decide when to change its direction.
A better idea would be to let each NPC have its own Movement-script.
Looking for a bit of help to see how I can make this algorithm a little better. So I have some code for a pan tool that moves the camera based on the user dragging the mouse to move the camera around. However it is really jumpy unless it is done in really short drags. I.e. the objects in the scene appear to vibrate, which is more likely to be the camera and canvas 'vibrating' rather than the objects themselves.
The code works by toggling a dragging boolean using the SystemEvent methods OnPointerDown and OnPointerUp and assigns a MouseStart Vector3 in world coordinates and also a CameraStart Vector3.
public void OnPointerDown(PointerEventData EventData)
{
if (!dragging)
{
dragging = true;
MouseStart = new Vector3(Camera.main.ScreenToWorldPoint(Input.mousePosition).x, Camera.main.ScreenToWorldPoint(Input.mousePosition).y, 0f);
CameraStart = TheCamera.transform.position;
}
}
public void OnPointerUp(PointerEventData EventData)
{
if (dragging)
{
dragging = false;
}
}
Then in the update loop while the dragging variable is true, xChange and yChange float values are determined based on the current mouse position compared to the original mouse position, the camera position is then adjusted according to these. My thought process was that because it is relative to a fixed MouseStart (because it is only changed in the single frame where the pointer is clicked and dragging = 0) that if I were to drag and then say keep the mouse still, there would be no change in coordinates as it'd be repeatedly putting the Camera in the same position. The full code looks like this:
private bool dragging;
private string CurrentTool;
private ButtonController[] DrawingTools;
public Camera TheCamera;
public Vector3 MouseStart;
public Vector3 CameraStart;
public float sensitivity;
// Use this for initialization
void Start () {
TheCamera = FindObjectOfType<Camera>();
DrawingTools = FindObjectsOfType<ButtonController>();
}
// Update is called once per frame
void Update () {
for (int i = 0; i < DrawingTools.Length; i++)
{
if (DrawingTools[i].Pressed)
{
CurrentTool = DrawingTools[i].gameObject.name;
}
}
if (dragging && CurrentTool == "PanTool Button")
{
float xChange;
float yChange;
Vector3 MousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if (MousePosition.x > MouseStart.x)
{
xChange = -Mathf.Abs(MousePosition.x - MouseStart.x);
}
else
{
xChange = Mathf.Abs(MousePosition.x - MouseStart.x);
}
if (MousePosition.y > MouseStart.y)
{
yChange = -Mathf.Abs(MousePosition.y - MouseStart.y);
}
else
{
yChange = Mathf.Abs(MousePosition.y - MouseStart.y);
}
TheCamera.transform.position = new Vector3(CameraStart.x + xChange*sensitivity, CameraStart.y + yChange*sensitivity, CameraStart.z);
}
}
public void OnPointerDown(PointerEventData EventData)
{
if (!dragging)
{
dragging = true;
MouseStart = new Vector3(Camera.main.ScreenToWorldPoint(Input.mousePosition).x, Camera.main.ScreenToWorldPoint(Input.mousePosition).y, 0f);
CameraStart = TheCamera.transform.position;
}
}
public void OnPointerUp(PointerEventData EventData)
{
if (dragging)
{
dragging = false;
}
}
Any help is appreciated, thanks.
EDIT: Just to clarify this is a 2d environment
This is happening because the Camera from which you are determining the world position of the mouse is being updated every frame according to the world position of the mouse, which causes a feedback loop (and therefore noise + jitter).
You can reduce noise from the feedback loop by smoothing the Camera's movement over time (effectively a low pass), or try to remove the feedback loop entirely by altering your calculations so the camera position and target position (mouse) don't rely on each other - although I'm not sure how to go about that if it's actually possible for your intent.
Check out Vector3.SmoothDamp.
Gradually changes a vector towards a desired goal over time.
The vector is smoothed by some spring-damper like function, which will
never overshoot. The most common use is for smoothing a follow camera.
This question already has an answer here:
Move GameObject back and forth
(1 answer)
Closed 5 years ago.
I'm trying to move multiple objects, simultaneously from Point A to Point B and back again, looped to serve as obstacles in blocking the player.
I've tried
StartCoroutine(Oscillate(OscillationFunction.Sine, 1f));
public IEnumerator Oscillate(OscillationFunction method, float scalar)
right = GameObject.FindGameObjectsWithTag("MovingObs2");
foreach (GameObject r in right)
{
startPos = r.transform.position;
v = startPos;
v.x = (r.transform.position.x + (Mathf.Cos(Time.time) * scalar));
r.transform.position = v;
yield return new WaitForSeconds(2);
r.transform.position = startPos;
}
and others, but they're all difficult to contain within a desirable distance and speed from the starting position. It's too fast, and too far.
and I tried a seemingly simpler line, which is easier to understand, for me.
v.x = l.transform.position.x + speed * Time.deltaTime;
l.transform.position = v;
but since i'm using an array in a foreach loop, i don't know how to preserve each GameObjects' transform.position, so that it can be used as a condition to reverse the direction of the objects' movement every time it reaches either Point A, or point B.
if (l.transform.position.x <= startPos.x || l.transform.position.x >= startPos.x + endPos.x)
{
speed *= -1;
}
edit: I apologize if I asked a duplicate question, I thought it was different due to the number of objects involved in an array.
I tried to assume to resolve all your question, please see the code below.
Time.deltaTime is not needed, since your code (and my) using the Time.time to calculate each time the position of the object.
I also would recommend (if possible) to not put all in on script, but give each object a swing script and also try to not use the CoRoutine. But to answer your question I show you how to do it.
I have created an internal class, to store in the "Init" the targeted gameobjects and their startPosition in a list. Later you can simply loop throu this list and always have the start position.
You need an endless loop in the "Osciallate" routine and need to wait each turn. In your example you put the Wait on the wrong place, so it waits after moving each object and stops after the first run throu all of this objects.
Here the code:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class OscilateObs : MonoBehaviour {
// internal class where all gameobjects and their startposition will be saved
internal class OscGameObjects
{
public GameObject gameObject;
public Vector3 startPosition;
}
// Here the information of all gameObjects stored in a list
private List<OscGameObjects> objectList;
public float updateSpeed = 0.05f;
public float oscScalar = 2f;
public enum OscillationFunction {
Sine = 1
}
void Start () {
// First, the gameobjects have to saved to our internal List
InitializeOscGameObjects();
// Start the Corotine
StartCoroutine(Oscillate(OscillationFunction.Sine, oscScalar));
}
private void InitializeOscGameObjects()
{
var objects = GameObject.FindGameObjectsWithTag("MovingObs2");
objectList = new List<OscGameObjects>();
foreach (var o in objects)
{
var oscObject = new OscGameObjects();
oscObject.gameObject = o;
oscObject.startPosition = o.transform.position;
objectList.Add(oscObject);
}
}
public IEnumerator Oscillate(OscillationFunction method, float scalar)
{
// Loop forever
while(true)
{
foreach (var element in objectList)
{
var currentPosition = element.gameObject.transform.position;
currentPosition.x = element.startPosition.x + Mathf.Cos(Time.time) * scalar;
element.gameObject.transform.position = currentPosition;
}
yield return new WaitForSeconds(updateSpeed);
}
}
}
EDIT:
I forgot to meantion:
1) I would recommend to use "Animation" Component for the movement and not using C# at all, so you can change the behaviour if needed and you are more flexible.
2) I would also recommend if possible to make a parent "GameObject" and moving only this and put the "MovingOb2" simply as child objects.
EDIT2:
Adding a delay increment for each object, so they not running syncron:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class OscilateObs : MonoBehaviour {
// internal class where all gameobjects and their startposition will be saved
internal class OscGameObjects
{
public GameObject gameObject;
public Vector3 startPosition;
public float waitCount;
}
// Here the information of all gameObjects stored in a list
private List<OscGameObjects> objectList;
public float updateSpeed = 0.05f;
public float oscScalar = 2f;
public float waitIncrementTime = 0.01f;
public enum OscillationFunction {
Sine = 1
}
void Start () {
// First, the gameobjects have to saved to our internal List
InitializeOscGameObjects();
// Start the Corotine
StartCoroutine(Oscillate(OscillationFunction.Sine, oscScalar));
}
private void InitializeOscGameObjects()
{
var objects = GameObject.FindGameObjectsWithTag("MovingObs2");
objectList = new List<OscGameObjects>();
float i = 0;
foreach (var o in objects)
{
i += waitIncrementTime;
var oscObject = new OscGameObjects();
oscObject.gameObject = o;
oscObject.startPosition = o.transform.position;
oscObject.waitCount = i;
objectList.Add(oscObject);
}
}
public IEnumerator Oscillate(OscillationFunction method, float scalar)
{
// Loop forever
while(true)
{
foreach (var element in objectList)
{
var currentPosition = element.gameObject.transform.position;
currentPosition.x = element.startPosition.x + Mathf.Cos(Time.time + element.waitCount) * scalar;
element.gameObject.transform.position = currentPosition;
}
yield return new WaitForSeconds(updateSpeed);
}
}
}
they're all difficult to contain within a desirable distance and speed from the starting position. It's too fast, and too far
Before you start monkeying with the structure of your code, I suggest you stick with the cosine function but educate yourself on the (very simple) manner in which you can manipulate output frequency and amplitude. This knowledge will serve you well (if this question is typical of your average project) and will solve the problem trivially.
If it's too fast, you can reduce the frequency (or increase the wavelength/period) by multiplying the input (which is always absolute time) by a constant coefficient between 0.0 and +1.0 (not inclusive).
If it's too far, you can reduce the amplitude by multiplying the output by a constant coefficient between 0.0 and +1.0 (not inclusive).
You just need to pick two constants and adjust to taste by running the program and seeing how you like it.
I'm Getting a error when i try to instantiate in the manager class. saying
Error CS1061: Type UnityEngine.Object' does not contain a definition forGetComponent' and no extension method GetComponent' of typeUnityEngine.Object' could be found. Are you missing an assembly reference? (CS1061) (Assembly-CSharp)
Add this to your Obstacle class:
void Start()
{
manager = GameObject.FindWithTag("ObstacleManager").GetComponent<ObstacleManager>();
}
The tag obviously has to be the tag of the gameobject the manager is attached to.
Also: Always start class names with a capital letter (I did that in the snippet, keep that in mind, you will get an error right know with that).
Maybe you want to actually change your spawning a bit though. Have two lists, one for free spawnpoints and one for occupied. When you destroy an obstacle, pass the position to the spawning function to move the position to the free list.
Edit:
Another option to create the reference is to set it in your ObstacleManager on spawning. You need to grab a reference to the instantiated obstacle for this. I believe this should work without actually grabbing the obstacle gameobject, but you could do that too.
Obstacle obs = ((GameObject)Instantiate(TypeOfObstacles[j], pointsAvailiable[pointsIndex].position, Quaternion.identity)).GetComponent<Obstacle>();
obs.SetManagerReference(this);
And in Obstacle add
public void SetManagerReference(ObstacleManager obsManager)
{
manager = obsManager;
}
For the free position you can do something like this:
// in Obstacle.cs
public void OnMouseDown()
{
manager.SpawnNewObstacle(transform.position); // you might be able to actually pass the transform, but I'm not sure if it will get destroyed before used in the other function
Destroy(gameObject);
}
In the Manager:
public int noOfObsacles;
public float[] xPercent;
public GameObject[] TypeOfObstacles;
float y;
// to keep track of which spawn points are free and which aren't use these lists
private List<Transform> freePositions;
private List<Transform> occupiedPositions;
private void Start()
{
freePositions = new List<Transform>(spawnPoints);
occupiedPositions = new List<Transform>();
SpawnObstacles();
}
private void SpawnObstacles()
{
// just use this for initial obstacles
// call Spawn as often as needed
for(int i = 0; i < noOfObstacles; i++)
{
Spawn();
}
}
// you call this function from the obstacle that gets destroyed
public void SpawnNewObstacle(Vector3 freePos)
{
// find the spawnpoint in the occupied points
// and move it to the free ones since the obstacle got destroyed
for(int i = 0; i < occupiedPositions.Count; i++)
{
if(occupiedPositions[i].position == freePos)
{
freePositions.Add(occupiedPositions[i]);
occupiedPositions.RemoveAt(i);
break;
}
}
// and call Spawn
Spawn();
}
private void Spawn()
{
y = Random.value;
int pointsIndex = Random.Range (0, freePositions.Count);
for (int j =0; j<xPercent.Length; j++)
{
if ( y < xPercent[j])
{
// these 4 lines are essential for the spawning
Obstacle obs = ((GameObject)Instantiate(TypeOfObstacles[j], freePositions[pointsIndex], Quaternion.identity).GetComponent<Obstacle>();
obs.SetManagerReference(this);
occupiedPositions.Add(freePositions[pointsIndex]);
freePositions.RemoveAt(pointsIndex);
break;
}
}
}
had a bracket issue! my bad
obstacle obs = ((GameObject)Instantiate(TypeOfObstacles[j], freePositions[pointsIndex].position, Quaternion.identity)).GetComponent();
I have run into a slight issue with the collision resolution in my game engine. If two objects collide and that collision causes the velocity to go to zero, the edges of the objects will overlap each other and they'll be stuck.
Is there a way to implement a catchall for this kind of a situation? i.e. move the objects just enough in the right direction so they are not stuck.
Here is how I am checking collisions and moving objects. When update is called on an entity, it moves the (x,y).
public static void Update()
{
for (var iterator = 0; iterator < PhysicsEntities.Count; iterator++)
{
for (var index = iterator + 1; index < PhysicsEntities.Count; index++)
{
if (!Collision.ResolveCollision(PhysicsEntities[iterator],
PhysicsEntities[index], Detection)) continue;
PhysicsEntities[iterator].Update();
PhysicsEntities[iterator].Collided = true;
PhysicsEntities[index].Update();
PhysicsEntities[index].Collided = true;
}
}
foreach (var entity in PhysicsEntities)
{
entity.Update(velocity: true);
entity.Collided = false;
}
}
}
Here is the update function for the entities:
public void Update(bool velocity = false)
{
if(!Movable) return;
if (!Collided)
{
var moveX = Velocity.X / Universe.UpdateInterval;
var moveY = Velocity.Y / Universe.UpdateInterval;
Position.Move(moveX, moveY);
BoundingBox.Move(moveX, moveY);
}
if(velocity) UniversalForces();
}
private void UniversalForces()
{
Velocity.Scale(1 - Universe.Friction);
Velocity.Add(Universe.GravityMag, Universe.GravityDir);
}
Finally, here is a image of one simulation where the objects get stuck. As you can see, it is just the edges that are getting stuck:
The quick solution is to move both objects back to the previous tic's position and any other object that causes a collision with to move back as well. It works, but it looks messy and causes some behavior that looks really bad - things like pushing directly at a wall leaves a gap, but angling towards a wall leaves a smaller gap. Very messy.
The better solution is to move both objects back just far enough along their negative velocity vector so that they are no longer touching. Usually some dot product math can give you what you need for this, though iterating backwards can work (slow).
Long story short, don't ever allow objects to overlap. Take care of it before it happens and you avoid stuck jitters, can't move stuff, etc.
When one object collides with another, get it to backtrack up its own movement vector so that the distance between the centroids of both objects is equal to the two radius. This works if its a circle - if its a complex polygon you really need to do edge collision detection instead of bounding sphere detection. If the bounding spheres collide, then you move to complex edge detection. In the end the trick is the same; check for collision then back up the movement vector until you find the exact (or nearly exact) point of collision.
I was able to figure it out with the suggestions people made. Some of the changes I made include having each object collide with any other object only once per update, and moving the object after the collision until it is no longer colliding. Here is the code that I used to do that, feel free to use this on any project and let me know if you have any questions about it.
public static void Update()
{
foreach (var a in PhysicsEntities)
{
foreach (var b in PhysicsEntities)
{
if (a.Equals(b) ||
!Collision.ResolveCollision(a, b, Detection) || b.Collided) continue;
while (Detection == Detection.BoundingBox ?
Collision.BoundingBox(a, b) :
Collision.PixelPerfect(a, b))
{
const float moveBy = .5F;
var moveX = a.Position.X > b.Position.X ? moveBy : -moveBy;
var moveY = a.Position.Y > b.Position.Y ? moveBy : -moveBy;
if (a.Movable)
{
a.Move(moveX, moveY);
a.Velocity.Scale(-1);
}
else if (b.Movable)
{
b.Move(moveX * -1, moveY * -1);
b.Velocity.Scale(-1);
}
}
a.Update();
b.Update();
a.Collided = a.Movable;
}
}
foreach (var entity in PhysicsEntities)
{
entity.Update(velocity: true);
entity.Collided = false;
}
}