I am having an issue where only the first NavMeshAgent can be created.
First of all, I selected all mesh renderers for the NavMesh bake objects:
I fine-tuned the bake a bit so it had just the right amount of detail. The black gaps around the various objects would be larger if I used a larger radius in the baked agent options:
I have two spawners placed on either side of the back ramp (the one with the white sphere in it). These are invisible but in a script, NavMeshObjects are placed onto these locations on an interval and have their destinations set to one of the other gameobjects.
The first one works fine, but none of the other 17 NavMeshObjects in the spawner queue can have their destinations set.
public class MinionSpawner : MonoBehaviour {
public void spawn (GameObject minion, GameObject target_position_obj) {
// Find the target position from the passed gameobject
MinionTargetPosition target_position = target_position_obj.GetComponent<MinionTargetPosition>();
if (!target_position) { throw new System.Exception("no valid target position given"); }
// Instantiate the given minion at the spawner's origin point
GameObject new_minion = Object.Instantiate(minion, transform.position, new Quaternion(0,0,0,0));
new_minion.SetActive(true);
// Mark the minion at it's target position, for lookup upon collision or elsewhere
MinionAttributes minion_attrs = new_minion.GetComponentInChildren<MinionAttributes>(true);
if (!minion_attrs) { throw new System.Exception("object passed as minion does not have MinionAttributes"); }
minion_attrs.target_position = target_position;
// Configure a NavMeshAgent to navigate the minion from origin to target position.
NavMeshAgent nav_mesh_agent = minion_attrs.gameObject.GetComponent<NavMeshAgent>();
if (!nav_mesh_agent) { throw new System.Exception("minion doesn't have a nav mesh agent"); };
nav_mesh_agent.Warp(transform.position);
// ================================================================
// THIS LINE FAILS:
nav_mesh_agent.destination = target_position.transform.position;
// ================================================================
// mark the minion's position as occupied in the turret's dictionary
Turret turret = target_position.turret;
turret.minion_slots[target_position] = true;
// set the minion's parent to the minion spawner, for organization's sake.
minion.transform.parent = transform;
}
}
I'm getting the error "SetDestination" can only be called on an active agent that has been placed on a NavMesh. and I know there are questions out there about this. But I've tried really everything I've found suggested, including baking the NavMesh, setting the NavMeshAgent's initial position with Warp, and making sure the points are on the mesh.
The issue was something unrelated to the NavMeshagent. It was this line:
minion.transform.parent = transform;
I needed to change it to:
new_minion.transform.parent = transform;
Basically minion is the object that gets cloned and new_minion is the clone. So if I set the parent of minion, I am messing it up for future clones, I guess.
Related
I've tried to make a script to spawn objects at a random position without them colliding with one another. It doesn't work properly as the OverlapBox returns almost every time null even when it touches a square.
Here is the script:
var quadBoundaries = quad.GetComponent<MeshCollider>().bounds;
var squareRadius = new Vector2(1, 1);
foreach (var square in squaresToSpawn)
{
_isOverlapping = true;
while (_isOverlapping)
{
_spawnPoint = new Vector2(Random.Range(quadBoundaries.min.x + 1.5f, quadBoundaries.max.x - 1.5f),
Random.Range(quadBoundaries.min.y + 1.5f, quadBoundaries.max.y - 1.5f));
_collisionWithSquare = Physics2D.OverlapBox(_spawnPoint, squareRadius,
0, LayerMask.GetMask("Square Layer"));
if (_collisionWithSquare is null)
{
square.transform.position = _spawnPoint;
_isOverlapping = false;
}
}
}
The quadBoundaries are the boundaries of a quad I placed so the squares will randomly spawn in a limited space.
My understanding is that I am generating a random point in the quad boundaries and then I check if on that point a square of scale (1,1) will fit without touching any other thing that has a collider and is on the square layer. if it touches then I generate a new point until the collision is null so I can place the square at the designated position.
But a bunch of things that I don't understand are happening.
First, the squares are touching each other. Second, just a few specific squares are registering a collision but even those are being touched by other squares. Third, when I scale up the square radius (for example 10,10) I get a big split between the squares (shown in the picture bellow).
I must add that all squares have a collider, are all on the square layer and the quad is on a different layer.
Can anybody explain to me what I'm not getting here? Thanks a lot!
Before the answer I'd like to say, that such algorithm of spawning is very dangerous, because you can enteran infinity loop, when there will no be place for new square. Minimum, that you can do to make this code more safe is to add a retries count for finding a place to spawn. But I will leave it on your conscience.
To make this algorithm work, you should understand that all physics in Unity are updated in fixed update. So all operations, that you do with Phisycs or Phisics2D are working with the state of objects, that was performed in the last Pyhsics update. When you change the position of the object, physics core don't capture this changes instantly. As a workaround you can spawn each object in the fixed update separately. Like this:
public class Spawner : MonoBehaviour
{
[SerializeField] private GameObject[] _squaresToSpawn;
[SerializeField] private GameObject _quad;
// Start is called before the first frame update
void Start()
{
StartCoroutine(Spawn());
}
private IEnumerator Spawn()
{
var quadBoundaries = _quad.GetComponent<MeshCollider>().bounds;
var squareRadius = new Vector2(1, 1);
Vector2 spawnPoint;
foreach (var square in _squaresToSpawn)
{
yield return new WaitForFixedUpdate();
var isOverlapping = true;
var retriesCount = 10;
while (isOverlapping && retriesCount > 0)
{
spawnPoint = new Vector2(Random.Range(quadBoundaries.min.x + 1.5f, quadBoundaries.max.x - 1.5f),
Random.Range(quadBoundaries.min.y + 1.5f, quadBoundaries.max.y - 1.5f));
var hit = Physics2D.OverlapBox(spawnPoint, squareRadius,
0, LayerMask.GetMask("Square"));
if (hit is null)
{
square.transform.position = spawnPoint;
isOverlapping = false;
}
else
{
retriesCount--;
if (retriesCount == 0)
{
Debug.LogError("Can't find place to spawn the object!!");
}
}
}
}
}
}
But such code will have effect of continious spawning:
To make objects spawning properly within one frame. You should manualy store all spawned objects bounding boxes inside your code, and manualy check if your new spawn bounding box collide with previously spawned objects.
I'm trying to assign a skill to a projectile that, when used, divides the project into 3 (the original projectile and 2 more new ones).
However, when I instantiate these two clones, they keep following the same trajectory. The idea would be for them to take this route:
The green dotted curve indicating the motion of the original bullet, the blue vector indicating the instantaneous velocity of the original bullet at time of special activation, the red vectors indicating the two velocity vectors belonging to each of the newly spawned bullets, and the green angle indicating the direction of the new bullet relative to the original velocity direction
But at the moment, they spawn and continue following the same trajectory as the original. The sprites even rotate to the right angle, but that doesn't seem to make much difference in how the physics is applied.
Does anyone know how I can solve this?
This is my code so far
Ability Script:
public class AirSpecialSplit : MonoBehaviour, IAirSpecial
{
public float SplitAngleInDegrees = 10;
GameObject bird_down;
GameObject bird_up;
public void ExecuteAirSpecial()
{
{
//hold the velocity of the original bird
Vector2 original_velocity = this.gameObject.GetComponent<Rigidbody2D>().velocity;
//clone two new birds
bird_down = Birb.MakeBirbCopy(this.gameObject);
bird_up = Birb.MakeBirbCopy(this.gameObject);
//apply the angle to the clones
bird_down.transform.rotation = Quaternion.AngleAxis(-SplitAngleInDegrees, Vector2.up);
bird_up.transform.rotation = Quaternion.AngleAxis(SplitAngleInDegrees, Vector2.up);
//get the rigidboy from the clones
Rigidbody2D rb_bird_down = bird_down.GetComponent<Rigidbody2D>();
Rigidbody2D rb_bird_up = bird_up.GetComponent<Rigidbody2D>();
rb_bird_down.simulated = true;
rb_bird_up.simulated = true;
rb_bird_down.velocity = new Vector2(original_velocity.x, original_velocity.y);
rb_bird_up.velocity = new Vector2(original_velocity.x, original_velocity.y);
}
}
}
Well you apply the same velocity to both so of course they will move in the same direction. The velocity is in world space!
You probably wanted to rather add the rotation like e.g.
rb_bird_down.velocity = bird_down.transform.forward * original_velocity.magnitude;
And before that you probably should take the current bullet rotation into account like
bird_down.tranform.rotation = transform.rotstion * Quaternion.Euler(0,0, -SplitAngleInDegrees);
So I'm trying to build this basic multiplayer game, it's sort of a mmorpg game.
This is how it works so far..
When I connect to the game, I can see my character walking around just fine, but when my friend connects and stands still and I walk around, I can see my character walking and doing the walk animation, but so is his character, and his character also moves even tho he is standing still on his computer.
This is the setup.. When I walk (Press W) the client sends the input to the server, and then the server sends back a packet to the client which then updates the players position, this is also where I'm trying to update the players animation.
/// <summary>
/// Packet that's received when the server updates the players position.
/// This happens every game tick, even if the player is standing still
/// </summary>
/// <param name="_packet"></param>
public static void PlayerPosition(Packet _packet)
{
//The players id
int _id = _packet.ReadInt();
//The players position
Vector3 position = _packet.ReadVector2();
//GameManager.players[_id].transform.position = position;
//var clientAnimator = GameManager.players[_id].GetComponent<Animator>();
if (position != GameManager.players[_id].position)
{
GameManager.players[_id].GetComponent<Animator>().SetBool("isWalking", true);
GameManager.players[_id].position = position;
}
else
{
GameManager.players[_id].GetComponent<Animator>().SetBool("isWalking", false);
}
}
Is there anything that I'm doing wrong that is super obvious?
To the why:
I bet your player is a prefab so both player instances have an Animator component in which one and the same AnimatorController is referenced.
As a result setting a Bool or changing to any State in it affects both players' Animator. Since this is not synchronized it happens only on your side but for both players simultaneously.
I don't know if this is possible in an easy way on runtime but a fix for this would probably consist in creating a new individual AnimatorController instance (clone) for each player instance.
Maybe it would be possible to simply clone the Animator.RuntimeAnimatorController something like
[RequireComponent(typeof(Animator))]
public class AnimatorSetup : MonoBehaviour
{
// Reference this via the Inspector
[SerializeField] private Animator _animator;
private void Awake()
{
// As fallback get it on runtime
if(!_animator) _animator = GetComponent<Animator>();
// Clonet he currently reference animator controller
var newController = Instantiate(_animator.runtimeAnimatorController);
// Use the new instance of the controller for this animator
_animator.runtimeAnimatorController = newController;
}
}
and put this on the player prefab next to the Animator component.
I'm creating a VR application in Unity using SteamVR. I have implemented a Snap Rotation Script, so, the user can turn himself in-game without doing it phisicaly.
My hierachy is: [CameraRig] -> (Controller left, Controller right, camera)
Now the problem is: when I'm holding an object and i want to throw it, if i rotated the CameraRig earlier, the object throwed behave strangely. Its direction is out of phase, based on the cumulative rotation of the CameraRig.
If I never rotate the CameraRig, the object throwed behave normally
I'm using FixedJoint component to attach the object to the hand.
How can I fix this?
public void Drop() {
// Null Check
if (!m_CurrentInteractable){
return;
}
// Apply velocity
Rigidbody targetBody = m_CurrentInteractable.GetComponent<Rigidbody>();
targetBody.velocity = m_pose.GetVelocity();
targetBody.angularVelocity = m_pose.GetAngularVelocity();
// Detach
m_Joint.connectedBody = null;
// Clear
m_CurrentInteractable.m_ActiveHand = null;
m_CurrentInteractable = null;
}
When calculating your velocity and angularVelocity, try to add one more step afterwards which incorporates the rig:
velocity = Rig.transform.rotation * velocity;
angularVelocity = Rig.transform.rotation * angularVelocity;
Hope this helps!
I am creating a video game where the character has to travel within literal art canvases (the ones that you use for painting) to reach the end goal.
Note that the "canvas" I am referring is not the UI element, but the actual canvases you would see in real life.
I based my code off the concepts of portals. This may not be the most efficient way of dealing with it and I will consider all advices.
This is my current code:
public Transform PortalB;
public CharacterController2D Player;
public GameObject PlayerObject;
private GameObject CloneTemporary;
public Queue<GameObject> Clones = new Queue<GameObject>();
private bool isCreated = false;
// Use this for initialization
void Start () {
Clones.Enqueue(PlayerObject);
}
// Update is called once per frame
void Update () {
// Portal adapts to the player's current position
if (Player == null) {
Player = GameObject.FindWithTag("Player").GetComponent<CharacterController2D>();
PlayerObject = GameObject.FindWithTag("Player");
}
}
void OnTriggerEnter2D (Collider2D other) {
if (other.gameObject.tag == "Player") {
if (Vector2.Distance(transform.position, other.transform.position) > 0.7f) {
if (!isCreated) {
CloneTemporary = Instantiate(PlayerObject, new Vector2 (PortalB.position.x, PortalB.position.y), Quaternion.identity) as GameObject;
Clones.Enqueue(CloneTemporary);
isCreated = true;
}
}
}
}
void OnTriggerExit2D (Collider2D other) {
if (Vector2.Distance(transform.position, other.transform.position) > 0.7f) {
print(Clones.Count);
if (Clones.Count > 1) {
UnityEngine.Object.Destroy(Clones.Dequeue());
}
}
isCreated = false;
}
The "original character" will collide with the portal's box collider and create a copy on the other end of the portal. The box collider is annotated in red in the first image below. Note that the sizes are exactly the same.
Note that it technically is not a portal, but since it's a box which brings a character from one place to another, I might as well call it a portal.
Once the original leaves the box collider, it will get deleted, and the clone will then become the "original".
I am using a queue system to determine which "clone" gets deleted first.
There are a few problems with this:
It is very inefficient. I have to create portals manually for every intersection point in the canvases. Imagine a level full of canvases, and imagine a game with full of levels...
When it touches the portal, a duplicate will get spawned by the original character. There are three characters, but the Clones.Count only registers two.
I am not sure how well the code will work for vertical traverses.
When the character crosses the portal, it should be able to turn back. In this code, the character would glitch through the floor if he were to turn back and not get deleted by the OnExit function. I suspect this has something to do with the size of the portal, but I can foresee that even if it were to be bigger, the character would immediately disappear if he turns back.
I think the OnTriggerEnter function gets activated when the character is teleported on the other side. This might have an effect on the errors I just stated, but it may cause more in the near future.