I am using the A* pathfinding algorithm taken from here http://arongranberg.com/astar/docs/ and I am trying to make an object move from a random point to another random point in a loop system in unity.This is the code used to move the object: I tried to put the points into an array but it didnt work. The author says that If I want additional behaviour after the AI reached its destination I should write the code in the OnTargetReached() method, but I am not sure exactly how to. If you've got any ideas, even the smallest would be very helpful.
public virtual void SearchPath () {
//if (target == null)
//{ Debug.LogError ("Target is null, aborting all search"); canSearch = false; return; }
lastRepath = Time.time;
//This is where we should search to
//Vector3 [] position = new Vector3[2];
//position[0] = new Vector3(Random.Range(-2,-7), 0, Random.Range(21,26));
//position[1] = new Vector3(Random.Range(19,23), 0, Random.Range (28,31));
//position[2] =
canSearchAgain = false;
//Alternative way of requesting the path
//Path p = PathPool<Path>.GetPath().Setup(GetFeetPosition(),targetPoint,null);
//seeker.StartPath (p);
//We should search from the current position
seeker.StartPath (GetFeetPosition(),targetPosition);
}
public virtual void OnTargetReached () {
//End of path has been reached
//If you want custom logic for when the AI has reached it's destination
//add it here
//You can also create a new script which inherits from this one
//and override the function in that script
//Vector3 new_targetPosition = new Vector3(Random.Range(19,23), 0, Random.Range (28,31));
//Vector3 new_targetPosition = new Vector3(19,0,28);
seeker.StartPath (GetFeetPosition(),new_targetPosition);
}
stick a bunch of nodes in your scene (just empty unity objects)
name them node1,2,3,4,5 make your loop / path and number them in order.
Make an pathManager script that has a public transform[] nodeLoop; array and drag your nodes onto the array in order.
Now you have a list of node/postions.
Now jsut hook it up to your existing OnTargetReached()
make a function that just gets the next node position...
something like this
void OnTargetReached ()
{
new_targetPosition = pathManager.m.getNextPathPoint()
}
pathmanager has something like this...
int pathPoint=0;
Vector3 getNextPathPoint()
{
pathPoint++;
if(pathPoint >= nodeLoop.length)
pathPoint=0;
return nodeLoop[pathPoint];
}
sorry for the hasty pseudocode, but you should get the idea
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 use Navigation2D.GetSimplePath for my enemy to chase the player once discovered.
using this bit of code I'd expect my enemy to get a Vector2 array containing path info to nav to the player but, my enemy goes in a completely different direction than what I'd expect.
I've tried this:
var from = Enemy.Position;
var to = PlayerRef.Position;
//Nav is my Navigation2d
var paths = Nav.GetSimplePath(from, to);
Enemy.Status.NavPath = new Stack<Vector2>(paths);
for my from and to and but, I've also attempted a lot of conversions
My guess was that my locals need to be converted to the local of the Navigation2d so I tried this:
//Nav is my Navigation2d
var from = Nav.ToLocal(Enemy.GlobalPosition);
var to = Nav.ToLocal(PlayerRef.GlobalPosition);
Since then I've been just bashing my head against the way with converting different global positions to locals of others and using those for the from and to values with similarly off results. I've been looking at this for a really long time (multiple days in my spare time) and I think I'm overlooking something obvious. If possible could anyone provide a second set of eyes and tell me what I've been missing.
Additional note:
This issue only started occurring after my attempt at refactoring. I had this logic all smashed together in a single enemy class till it got to be a pain to maintain. I can provide the original as well.
Original Enemy3.cs Pre-Refactor
My current version of "Chase state" using the FSM pattern:
Enemy using chase state
Here is the full listing of the current file.
using Godot;
using ThemedHorrorJam5.Scripts.Enum;
using ThemedHorrorJam5.Scripts.Patterns.StateMachine;
using ThemedHorrorJam5.Scripts.GDUtils;
using System.Collections.Generic;
using System.Linq;
namespace ThemedHorrorJam5.Entities
{
public class ChaseEnemyState : State
{
private Navigation2D? GetLevelNavigation()
{
var nodeTuples = Enemy.GetTree().GetNavigation2dNodes();
if (nodeTuples.Item1) return nodeTuples.Item2[0];
return null;
}
private Navigation2D Nav { get; set; }
private EnemyV4 Enemy { get; set; }
private PlayerV2 PlayerRef { get; set; }
public ChaseEnemyState(EnemyV4 enemy)
{
this.Name = EnemyBehaviorStates.ChasePlayer.GetDescription();
Enemy = enemy;
(var hasPlayer, PlayerRef) = Enemy.GetTree().GetPlayerNode();
if (!hasPlayer)
{
Logger.Error("Player ref not found on scene tree");
}
(var hasNav, var navNodes) = Enemy.GetTree().GetNavigation2dNodes();
if (hasNav && navNodes != null)
{
Nav = navNodes[0];
}
this.OnEnter += () => this.Logger.Debug("ChaseEnemyState OnEnter called");
this.OnExit += () => this.Logger.Debug("ChaseEnemyState Exit called");
this.OnFrame += ChasePlayer;
}
private void ChasePlayer(float delta)
{
if (Enemy.IsDebugging && Enemy.HasNode("Line2D"))
{
Enemy.Status.Line = (Line2D)Enemy.GetNode("Line2D");
}
if (Nav!=null)
{
//Enemy.Status.Navigation2D = GetLevelNavigation();
//var nav = (Navigation2D)Enemy.Owner.GetNode("Navigation2D");
//Enemy.Status.Navigation2D = (Navigation2D)Enemy.Owner.GetNode("Navigation2D");
//var from = Enemy.Position;
var from = Nav.ToLocal(Enemy.GlobalPosition);
//var from = Enemy.GlobalPosition;
var to = Nav.ToLocal(PlayerRef.GlobalPosition);
//var to = PlayerRef.Position;
//var to = Enemy.Status.Target.Position;
//var to = Enemy.Status.Target.ToLocal(Enemy.Status.Target.Position);
//Enemy.DrawLine(from, to, new Color(255, 255, 255), 3);
//var paths = Enemy.Status.Navigation2D.GetSimplePath(from, to);
var paths = Nav.GetSimplePath(from, to);
Enemy.Status.NavPath = new Stack<Vector2>(paths);
if (Enemy.Status.Line != null)
{
Enemy.Status.Line.Points = Enemy.Status.NavPath.ToArray();
}
var distance_to_walk = Enemy.MoveSpeed * delta;
while (distance_to_walk > 0f && Enemy.Status.NavPath.Count > 0f)
{
var distance_to_next_point = Enemy.Position.DistanceTo(Enemy.Status.NavPath.Peek());
if (distance_to_walk <= distance_to_next_point)
{
var newPosition = Enemy.Position.DirectionTo(Enemy.Status.NavPath.Peek()) * distance_to_walk;
Enemy.Status.VisionManager.UpdateFacingDirection(newPosition.Normalized());
Enemy.Position += newPosition;
}
else
{
var newPosition = Enemy.Status.NavPath.Pop();
Enemy.Status.VisionManager.UpdateFacingDirection(newPosition.Normalized());
if (Enemy.GetSlideCount() > 0)
{
Enemy.HandleMovableObstacleCollision(newPosition);
}
Enemy.Position = newPosition;
}
distance_to_walk -= distance_to_next_point;
}
if (Enemy.IsDebugging)
{
Enemy.Status.DebugLabel.Text =
#$"
|-----------------------------------------------------------
| Enemy Global Position: {Enemy.GlobalPosition}
| Enemy Local Position: {Enemy.Position}
|----------------------------------------------------------
| Target Global Position: {Enemy.Status.Target.GlobalPosition}
| Target Local Position: {Enemy.Status.Target.Position}
|-----------------------------------------------------------
| From {from}
| To {to}
|-----------------------------------------------------------";
}
}
else
{
Logger.Error("Navigation2D not found");
}
if (Enemy.Status.CurrentCoolDownCounter > 0)
{
Enemy.Status.CurrentCoolDownCounter -= delta;
}
}
}
}
Some images to help show what I'm seeing. Player is green and the enemy is blue. From and To correspond to the values of the variables. The yellow is the sight cone for the enemy and is pointing out in the direction the enemy is walking. Target is an alias for player and the red line is a Line2d I've been using to draw the enemy's path.
//Nav is my Navigation2d
var from = Enemy.Position;
var to = PlayerRef.lPosition;
Second attempt results with :
//Nav is my Navigation2d
var from = Nav.ToLocal(Enemy.GlobalPosition);
var to = Nav.ToLocal(PlayerRef.GlobalPosition);
Any insight at all would be helpful as to what I'm doing wrong.
I can only guess this began failing due to the nodes being moved around. Either the order in the scene tree, or the Navigation2D position. I'm guessing that because the code was previously only using local positions. Meaning that their local coordinates used to match, but they no longer do.
Anyway, this code is correct:
var from = Nav.ToLocal(Enemy.GlobalPosition);
var to = Nav.ToLocal(PlayerRef.GlobalPosition);
And this is progress. You had fragile code before (it depended on the coincidence that the local coordinates matched). So this will ultimately be good.
I believe the issue you are facing comes from here:
Enemy.Position.DistanceTo(Enemy.Status.NavPath.Peek());
Which would be be correct if their local coordinates matched, but they don't. So we need to convert the result we get here.
If you are going to work on the enemy local coordinates, you need to convert again:
Enemy.Position.DistanceTo(Enemy.ToLocal(Nav.ToGlobal(Enemy.Status.NavPath.Peek())));
However, I'd suggest to work in global coordinates instead (since you can write to Enemy.GlobalPosition instead of Enemy.Position):
Enemy.GlobalPosition.DistanceTo(Nav.ToGlobal(Enemy.Status.NavPath.Peek()));
You would have to do the same change in other places in your code.
Let us see here:
var newPosition = Enemy.Position.DirectionTo(Enemy.Status.NavPath.Peek()) * distance_to_walk;
Enemy.Status.VisionManager.UpdateFacingDirection(newPosition.Normalized());
Wait, that isn't a position is it? That's a displacement. Don't mix things up.
Well, the question is what coordinates does UpdateFacingDirection expect.
You also have this code:
var newPosition = Enemy.Status.NavPath.Pop();
Enemy.Status.VisionManager.UpdateFacingDirection(newPosition.Normalized());
Sure a position normalized is a direction… From the origin. But which origin? In this case it is the origin of the Navigation2D…
So, I have searched for UpdateFacingDirection in your code. I found a couple implementations which look like this:
public void UpdateFacingDirection(Vector2 newVelocity)
{
this.Rotation = this.Position.AngleToPoint(newVelocity);
}
See? This makes no sense. The code claims to be taking the angle to go from a point to a velocity. I'll believe your instruction and not your naming. In which case UpdateFacingDirection takes a position in local coordinates.
I have also looked for HandleMovableObstacleCollision, and I found this implementation:
public void HandleMovableObstacleCollision(Vector2 motion)
{
this.PrintCaller();
motion = motion.Normalized();
if (GetSlideCollision(0).Collider is PushBlock box && box.CanBePushed)
{
box.Push(PushSpeed * motion);
}
}
So apparently this only cares about the direction of the argument. Now, this is the Push I found:
public void Push(Vector2 velocity)
{
MoveAndSlide(velocity);
}
So that is global coordinates. Meaning that HandleMovableObstacleCollision takes a direction in global coordinates.
Ok, I think I can rewrite:
var from = Nav.ToLocal(Enemy.GlobalPosition);
var to = Nav.ToLocal(PlayerRef.GlobalPosition);
var paths = Nav.GetSimplePath(from, to);
// …
var distance_to_walk = Enemy.MoveSpeed * delta;
while (distance_to_walk > 0f && paths.Count > 0f)
{
var next_point = Nav.ToGlobal(paths.Peek());
var global_direction = Enemy.GlobalPosition.DirectionTo(next_point);
var global_distance = Enemy.GlobalPosition.DistanceTo(next_point);
if (distance_to_walk <= global_distance)
{
var global_displacement = global_direction * distance_to_walk;
var global_new_position = Enemy.GlobalPosition + global_displacement;
var local_new_position = Enemy.ToLocal(global_new_position);
Enemy.Status.VisionManager.UpdateFacingDirection(local_new_position);
Enemy.GlobalPosition = global_new_position;
}
else
{
_ = paths.Pop();
// var global_displacement = next_point - Enemy.GlobalPosition;
var global_new_position = next_point;
var local_new_position = Enemy.ToLocal(global_new_position);
Enemy.Status.VisionManager.UpdateFacingDirection(local_new_position);
if (Enemy.GetSlideCount() > 0)
{
Enemy.HandleMovableObstacleCollision(global_direction);
}
Enemy.GlobalPosition = global_new_position;
}
distance_to_walk -= global_distance;
}
I don't know what are you storing Enemy.Status.NavPath for, if you are calling navigation each frame. So I removed that from the code above. If you do need it, add it back. Similarly, I don't know what to make of Enemy.Status.Line.Points. My guess is that is only for debug, and it should be in local coordinates of the Line2D, if you need it, perhaps you can use the global transforms to convert them (with Xform and XformInv).
About the line:
_ = paths.Pop();
Is a discard. We don't really need a discard. You can simply call Pop:
paths.Pop();
The discard - in this case - is just meant to indicate that we intentionally do not use the returned value. We don't need to, because we already got it from Peek.
By the way, in the second branch I added comment with global_displacement, you will see what you would need that line for below.
There is something else that bothers me:
if (Enemy.GetSlideCount() > 0)
{
Enemy.HandleMovableObstacleCollision(/*…*/);
}
What slide count? In fact, in HandleMovableObstacleCollision I see you use GetSlideCollision, but what slide collisions? If you don't use MoveAndSlide or similar on the Enemy? Instead I see you write Enemy.Position (which I changed to Enemy.GlobalPosition)?
Let us use MoveAndSlide. The catch is that it does not take a displacement, nor a position, it takes a velocity. So we will pass global_displacement / delta (remember that velocity is displacement divided by delta time). So instead of this:
Enemy.GlobalPosition = global_new_position;
Do this:
Enemy.MoveAndSlide(global_displacement / delta);
Also, don't you want to do that before checking Enemy.GetSlideCount()? Well, I don't know. You can finish figuring this out.
The code I am posting is just one of the ways I have tried. For whatever reason I just cannot get the script to access the loop value like I see done with other public variables. Only piece I can add is that the sound that is trying to have the value altered is created in an OnAwake() method in another script.
public static void PlaySound(Sound sound, Vector3 position)
{
if (CanPlaySound(sound))
{
GameObject soundGameObject = new GameObject("Sound");
soundGameObject.transform.position = position;
AudioSource audioSource = soundGameObject.AddComponent<AudioSource>();
audioSource.clip = GetAudioClip(sound);
Debug.Log(soundGameObject.GetComponent<AudioSource>().clip);
if (audioSource.clip.Equals("Level 1 Music"))
{
soundGameObject.GetComponent<AudioSource>().loop = true;
}
audioSource.Play();
Object.Destroy(soundGameObject, audioSource.clip.length);
}
}
It's because of your condition. The equals option actually measures the clip by the clip, not the clip by the name of the clip. Try this option to fix the problem:
if (audioSource.clip.name.Equals("Level 1 Music"))
{
audioSource.loop = true;
}
I am a beginner at Unity in terms of skill so please explain as if you were talking to a child if you can!
PROBLEM
I would like to change these names here:
I would like to rename them for two reasons:
so they are more intelligible
because I am using many different assets from the store and each has a different hierarchy with different names and I want to standardize the names so that I can use the below code to determine which part of the creature's body was shot so that it works for every creature
public void CreatureHit(string bodyPart, GunInfo usedWeapon, float intensity) // for guns
{
usedWeapon.PlayHit(creatureSounds);
if (creatureInfo.healthPoints > 0) // to prevent dead creatures from being shot
{
if ("Torso" == bodyPart || "LeftUpperArm" == bodyPart // if the part that was hit was the arms or torso
|| "RightUpperArm" == bodyPart || "LeftLowerArm" == bodyPart // if the part that was hit was the arms or torso
|| "RightLowerArm" == bodyPart)
{
creatureInfo.healthPoints -= usedWeapon.damage * intensity; // deal standard dmg
if (creatureInfo.healthPoints <= 0)
creatureInfo.deathType = CreatureInfo.BODYSHOT;
}
else if ("Head" == bodyPart) // if the part that was hit was the head
{
creatureInfo.healthPoints -= usedWeapon.damage * 10 * intensity; // deal 10x dmg
audioSource.PlayOneShot(creatureSounds.hitHead, 1);
if (creatureInfo.healthPoints <= 0)
creatureInfo.deathType = CreatureInfo.HEADSHOT;
}
else if ("RightUpperLeg" == bodyPart || "LeftUpperLeg" == bodyPart
|| "RightLowerLeg" == bodyPart || "LeftLowerLeg" == bodyPart)
{
creatureInfo.healthPoints -= usedWeapon.damage / 2 * intensity; // deal half dmg
if (creatureInfo.healthPoints <= 0)
creatureInfo.deathType = CreatureInfo.BODYSHOT;
}
}
}
WHAT I TRIED
I renamed them in the hierarchy but then the animations stopped working. I found an old thread from the Unity forum asking if this was possible in 2015 and the OP was told that it wasn't. There were some later technical replies and I felt overwhelmed so I thought I should just create my own thread.
NOTE: there are multiple dozens of characters each with 10+ animations so ideally I need a very efficient solution.
In general you still can't unfortunately. (At least not that simple see below).
The AnimationClips are based on strings storing the relative path from the Animator to the according GameObject the type of the according component and finally the name of the animated serialized fields and properties.
If any of those change e.g. because you renamed the object or change the hierarchy in general the connection is lost and the animation breaks.
You could implement an editor script method that
goes through the affected Animator (GetComponentInParent) of the object
iterates through all used AnimationClips
iterates through each clips property bindings
redirects the property path accordingly to your renaming
This could look somewhat like this
private static void RenameObject(GameObject gameObject, Animator animator, string newName)
{
if (!gameObject)
{
throw new ArgumentException("No object provided", nameof(gameObject));
}
if (string.IsNullOrWhiteSpace(newName))
{
throw new ArgumentException("Object name may not be empty!", nameof(newName));
}
if (!animator)
{
throw new ArgumentException($"Selected object {gameObject} is not a child of an {nameof(Animator)}!", nameof(gameObject));
}
if (gameObject.transform == animator.transform)
{
return;
}
// get the relative path from the animator root to this object's parent
var path = AnimationUtility.CalculateTransformPath(gameObject.transform.parent, animator.transform);
if (gameObject.transform.parent != animator.transform)
{
path += "/";
}
// then append the old and new names
var oldPath = path + gameObject.name;
var newPath = path + newName;
// get the runtime Animation controller
var controller = animator.runtimeAnimatorController;
// get all clips used by this controller
var clips = controller.animationClips;
var changeableObjects = new List<Object>(clips.Length + 1) { gameObject };
changeableObjects.AddRange(clips);
Undo.RecordObjects(changeableObjects.ToArray(), "Change animated object name");
// Go through all clips
foreach (var clip in clips)
{
var floatBindingInfo = new List<AnimationFloatBindingInfo>();
// Get and store all FLOAT keyframe bindings
foreach (var binding in AnimationUtility.GetCurveBindings(clip))
{
var curve = AnimationUtility.GetEditorCurve(clip, binding);
var curveInfo = new AnimationFloatBindingInfo(binding, curve);
ReplaceBindingPath(curveInfo, oldPath, newPath);
floatBindingInfo.Add(curveInfo);
}
var objectBindingInfos = new List<AnimationObjectBindingInfo>();
// also do the same for all reference keyframe bindings
foreach (var binding in AnimationUtility.GetObjectReferenceCurveBindings(clip))
{
var curve = AnimationUtility.GetObjectReferenceCurve(clip, binding);
var curveInfo = new AnimationObjectBindingInfo(binding, curve);
ReplaceBindingPath(curveInfo, oldPath, newPath);
objectBindingInfos.Add(curveInfo);
}
// a little check to avoid unnecessary work -> are there any affected property curves at all?
if (floatBindingInfo.Count + objectBindingInfos.Count > 0)
{
// Now erase all curves
clip.ClearCurves();
// and assign back the stored ones
AnimationUtility.SetEditorCurves(clip, floatBindingInfo.Select(info => info.Binding).ToArray(), floatBindingInfo.Select(info => info.Curve).ToArray());
AnimationUtility.SetObjectReferenceCurves(clip, objectBindingInfos.Select(info => info.Binding).ToArray(), objectBindingInfos.Select(info => info.Curve).ToArray());
EditorUtility.SetDirty(clip);
}
}
// finally rename the object
gameObject.name = newName;
EditorUtility.SetDirty(gameObject);
}
Since this use case is quite common I took some time to implement an EditorWindow for this. It is still a bit raw but works and supports also undo redo ;) You can find it here
-> Select the object in the Hierarchy -> right click -> "Rename safe for Animator"
You could of course add some shortcut to it etc. that's up to you ;)
Here a little demo of the dialog in action, renaming some nested objects and also performing some undo/redo
However, an alternative in your use case to simply get your code to work with the names as they are might be using tags instead.
As I see it your code is based on three different cases so you could simply have a tag for each like e.g. Head, Arms, Legs and assign and check those accordingly (GameObject.CompareTag) and not touch the names and animations at all.
I have 2 plans.
Create an empty GameObject under the node you want to rename, and attach the collider compoent on it.
CATRigHub001Bone004
└ CATRigHub001Bone004Bone001
└ Rig <-------- Collider
Rename the bone in editor and create a script to automatically rename it to its original name while playing.
public class Rename : MonoBehaviour
{
public string boneName;
[NonSerialized] public string partName;
void Awake()
{
partName = name;
name = boneName;
}
}
I am currently developing a Unity3D based evolutionary algorithm. The simulation is two-dimensional. A single subject is being depicted as car, being a prefab, consisting of 3 sprites(2 wheels and body), and a CarScript. Each sprite has a proper collider(BoxCollider2D for body and CircleCollider2D for wheels). CarBody also has
two WheelJoint2D. Parameters of those colliders are changed by code.
I want this car to be destroyed, if it stops moving or better - advancing. In the Game window, the car is obviously moving downhill. The problem is, that after checking for transform.position of gameobject, this value seems to be constant. It always shows the position of SpawnPoint. SpawnPoint is empty GameObject with SpawnScript, which fragment is below:
public GameObject carprefab; //Set from editor
public Transform[] spawnPoints; //transform of SpawnPoint, just one element. Set from editor.
private void GenerateCar(Chromosome chromosome)
{
int spawnPointIndex = Random.Range(0, spawnPoints.Length);
var instace = (GameObject)Instantiate(carprefab, spawnPoints[spawnPointIndex].position, spawnPoints[spawnPointIndex].rotation);
instace.SendMessage("SetChromosome", chromosome);
foreach (Transform child in instace.transform)
{ //code omitted for clarity, changes some of parameters based on chromosome.
Instantiated object has a CarScript:
// Update is called once per frame
void Update()
{
if (frames%10 == 0)
CheckForMoving();
frames++;
}
private void CheckForMoving()
{
var pos = gameObject.transform.position; //this is never changing - why?
var diff = pos - startingPosition; //difference between local position and starting position
if (CountLength(diff) >= recordLengthYet)
{
Debug.Log("It is moving!");
recordLengthYet = CountLength(diff);
framesTillLastRecord = 0;
}
else
{
Debug.Log("It is not moving");
if (framesTillLastRecord > 4)
Destroy(gameObject, 0f);
framesTillLastRecord++;
}
}
I tried getting the position by any of the following:
var pos = gameObject.transform.position;
var pos = GameObject.FindGameObjectWithTag("player");
var pos = this.transform.position;
The question is - what did I miss, or why this is not changing? I started learning Unity just recently, and had no previous experience with any similiar software. I also wonder, if it is even the right way to do this.
Few days has passed, nobody had uploaded a proper answer, and I managed to get a workaround. As long as it is really dirty, it seems to work.
I placed a tag on child GameObject, instead of prefab, and I am geting position of this child by
var pos = GameObject.FindGameObjectWithTag("player");
I am not sure, if this is really good answer, but I hope somebody might find it useful someday.
Assuming you are using rigidbodies, you should be overriding FixedUpdate() and not Update() as per Unity doco:
This function is called every fixed framerate frame, if the MonoBehaviour is enabled. FixedUpdate should be used instead of Update when dealing with Rigidbody. For example when adding a force to a rigidbody, you have to apply the force every fixed frame inside FixedUpdate instead of every frame inside Update. - Tell me more...
Replace:
// Update is called once per frame
void Update()
{
if (frames%10 == 0)
CheckForMoving();
frames++;
}
With:
// Update is called once per frame
void FixedUpdate()
{
if (frames%10 == 0)
CheckForMoving();
frames++;
}