Get the position of an animationClip at a given time in Unity3D - c#

I would like to retrieve the Transform/Position of an animationClip at a given time/frame.
I can actually set an animationClip to a given time with animationClip.SampleAnimation()(https://docs.unity3d.com/ScriptReference/AnimationClip.SampleAnimation.html)
Therefore I could eventually create a clone, put it on a specific frame and get his position, but it would be too heavy for each frame.
If you have any solutions, or maybe some functions that I didn't see I would be glad.
Thank you

I've found the solution, it takes a lot of process, but as it's done only in Editor it doesn't affect the game.
I am getting the curve of the animationclip and I store 10 position, each corresponding to 10% of the animation.
For this I need to compute each x, y and z axis curves :
void OnValidate()
{
//Check if we are in the Editor, and the animationClip is getting change
if (Application.isEditor && !Application.isPlaying && previousClip != animationClip)
{
previousClip = animationClip;
m_checkPoints = new Vector3[10];
int index = 0;
//Taking the curves form the animation clip
AnimationCurve curve;
var curveBindings = AnimationUtility.GetCurveBindings(animationClip);
//Going into each curves
foreach (var curveBinding in curveBindings)
{
//Checking curve's name, as we only need those about the position
if (curveBinding.propertyName == "m_LocalPosition.x")
index = 0;
else if (curveBinding.propertyName == "m_LocalPosition.y")
index = 1;
else if (curveBinding.propertyName == "m_LocalPosition.z")
index = 2;
else
continue;
//Get the curveform the editor
curve = AnimationUtility.GetEditorCurve(animationClip, curveBinding);
for (float i = 0; i < 10; i++)
{
//Evaluate the curves at a given time
m_checkPoints[(int)i][index] = curve.Evaluate(animationClip.length * (i / 10f));
}
}
}
}

Related

how to get a specific material from a meshrenderer using raycasts

I have a Meshrenderer with 9 materials on it and I want to detect which one of those colors is facing forward, but I don't know which one it is going to be so I can't just use Meshrenderer.materials[number].color
here is what I have so far
public GameObject Shot1pos;
RaycastHit hit;
if (Physics.Raycast(Shot1pos.transform.position, Shot1pos.transform.TransformDirection(Vector3.forward), out hit, Mathf.Infinity))
{
Debug.DrawRay(Shot1pos.transform.position, Shot1pos.transform.TransformDirection(Vector3.forward) * hit.distance, Color.yellow);
Wheel1Color = hit.collider.gameObject.GetComponent<MeshRenderer>().material.color;
Debug.Log(Wheel1Color);
}
but this only returns the first material, whichever is facing forward
I found the solution. First of all you need to get the mesh index according to hit.triangleIndex and get the main material index based on it.
For first one just copy this script in class:
public static int GetSubMeshIndex(Mesh mesh, int triangleIndex)
{
if (!mesh.isReadable) return 0;
var triangleCounter = 0;
for (var subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; subMeshIndex++)
{
var indexCount = mesh.GetSubMesh(subMeshIndex).indexCount;
triangleCounter += indexCount / 3;
if (triangleIndex < triangleCounter) return subMeshIndex;
}
return 0;
}
Then use this part for detecting Suitable material:
public void DetectMaterial(RaycastHit hit)
{
if (hit.transform == null) return;
var materials = hit.transform.GetComponent<MeshRenderer>().materials;
var index = hit.triangleIndex;
var mesh = hit.transform.GetComponent<MeshFilter>().mesh;
var subMeshIndex = GetSubMeshIndex(mesh, index);
UnityEngine.Debug.Log(materials[subMeshIndex]);
}
Remember that your file model must have Read / Write Enabled option enabled and use only Mesh Collider.

Separation steering algorithm for separationg set of Rectangles

In few last days trying to implement TinyKeep's dungeon generation algorithm myself.
Link to TinyKeep's dungeon generation algorithm
I don't understand it perfectly by 100%, because of it here am I :)
Firstly I generated set of rooms in circle
Going for each room in set. Pick it and going with another for loop again in set to compare every room intersection.
If room intersects, I added force, in which I will be apply separation. After that nested loop ended, I move current room, from first loop, according to that force vector. And so on. It happening until every room is not overlapped with others.
Also I picked center(Middle) point of rects ( x+width/2f, y+height/2f) to calculate that force vector
If vector is normalized, then rooms tighly packed, but if no - rooms have much space between them...
I wanna just recreate and understand TinyKeep's algorithm, but results not the same...
Also I getting weird results, then tall/wide rooms or always going in corners, tall go upper-left corner of map, wide - bottom-right corner. And all sometimes have overall diagonal separate direction
// Main method
private void Separate()
{
do
{
for (int i = 0; i < rooms.Count; i++)
{
Vector2 force = Vector2.zero;
int overlapCounter = 0;
for (int j = 0; j < rooms.Count; j++)
{
if (i == j) continue;
if (!rooms[i].IsOverlapped(rooms[j])) continue;
force += rooms[j].Middle - rooms[i].Middle;
overlapCounter++;
}
if (overlapCounter == 0) continue;
force /= overlapCounter;
force.Normalize();
force *= -1f;
rooms[i].Move(force);
}
} while (IsAnyRoomOverlapped());
StopTimer(true);
}
// Room.Move()
public void Move(Vector2 move)
{
x += Mathf.CeilToInt(move.x)* TILE_SIZE;
y += Mathf.CeilToInt(move.y)* TILE_SIZE;
}
// Constructor
public Dungeon(int roomAmount, int seed,float radius)
{
StartTimer();
this.roomAmount = roomAmount;
this.radius = radius;
Seed = seed;
InitializeRandom();
PopulateDungeonWithRooms();
}
While trying to figure out how solve this problem I read some questions and look their source code. I find that they not only move room after every overlapped neighbour found but also move neighbour in other direction!
What about directional behaviour of separation? It's my mistake or misunderstanding of rounding to integer
What about perfomance? Generating 512 rooms inside circle with radius of 128 units using normal distribution give 1100 ms
Now, code looks like:
private void SeparateRooms()
{
do
{
for (int current = 0; current < rooms.Count; current++)
{
for (int other = 0; other < rooms.Count; other++)
{
if (current == other || !rooms[current].IsOverlapping(rooms[other])) continue;
var direction = (rooms[other].Middle - rooms[current].Middle).normalized;
rooms[current].Move(-direction, TILE_SIZE);
rooms[other].Move(direction, TILE_SIZE);
}
}
}
while (IsAnyRoomOverlapped());
StopTimer(true);
}
And Room's Move() method:
public void Move(Vector2 move,int tileSize=1)
{
x += Mathf.RoundToInt(move.x)*tileSize;
y += Mathf.RoundToInt(move.y)*tileSize;
}

Unity, get the "actual" current Terrain?

Unity has a function Terrain.sampleHeight(point) which is great, it instantly gives you the height of the Terrain underfoot rather than having to cast.
However, any non-trivial project has more than one Terrain. (Indeed any physically large scene inevitably features terrain stitching, one way or another.)
Unity has a function Terrain.activeTerrain which - I'm not making this up - gives you: the "first one loaded"
Obviously that is completely useless.
Is fact, is there a fast way to get the Terrain "under you"? You can then use the fast function .sampleHeight ?
{Please note, of course, you could ... cast to find a Terrain under you! But you would then have your altitude so there's no need to worry about .sampleHeight !}
In short is there a matching function to use with sampleHeight which lets that function know which Terrain to use for a given xyz?
(Or indeed, is sampleHeight just a fairly useless demo function, usable only in demos with one Terrain?)
Is there in fact a fast way to get the Terrain "under you" - so as to
then use the fast function .sampleHeight ?
Yes, it can be done.
(Or indeed, is sampleHeight just a fairly useless demo function,
usable only in demos with one Terrain?)
No
There is Terrain.activeTerrain which returns the main terrain in the scene. There is also Terrain.activeTerrains (notice the "s" at the end) which returns active terrains in the scene.
Obtain the terrains with Terrain.activeTerrains which returns Terrain array then use Terrain.GetPosition function to obtain its position. Get the current terrain by finding the closest terrain from the player's position. You can do this by sorting the terrain position, using Vector3.Distance or Vector3.sqrMagnitude (faster).
Terrain GetClosestCurrentTerrain(Vector3 playerPos)
{
//Get all terrain
Terrain[] terrains = Terrain.activeTerrains;
//Make sure that terrains length is ok
if (terrains.Length == 0)
return null;
//If just one, return that one terrain
if (terrains.Length == 1)
return terrains[0];
//Get the closest one to the player
float lowDist = (terrains[0].GetPosition() - playerPos).sqrMagnitude;
var terrainIndex = 0;
for (int i = 1; i < terrains.Length; i++)
{
Terrain terrain = terrains[i];
Vector3 terrainPos = terrain.GetPosition();
//Find the distance and check if it is lower than the last one then store it
var dist = (terrainPos - playerPos).sqrMagnitude;
if (dist < lowDist)
{
lowDist = dist;
terrainIndex = i;
}
}
return terrains[terrainIndex];
}
USAGE:
Assuming that the player's position is transform.position:
//Get the current terrain
Terrain terrain = GetClosestCurrentTerrain(transform.position);
Vector3 point = new Vector3(0, 0, 0);
//Can now use SampleHeight
float yHeight = terrain.SampleHeight(point);
While it's possible to do it with Terrain.SampleHeight, this can be simplified with a simple raycast from the player's position down to the Terrain.
Vector3 SampleHeightWithRaycast(Vector3 playerPos)
{
float groundDistOffset = 2f;
RaycastHit hit;
//Raycast down to terrain
if (Physics.Raycast(playerPos, -Vector3.up, out hit))
{
//Get y position
playerPos.y = (hit.point + Vector3.up * groundDistOffset).y;
}
return playerPos;
}
Terrain.GetPosition() = Terrain.transform.position = position in world
working method:
Terrain[] _terrains = Terrain.activeTerrains;
int GetClosestCurrentTerrain(Vector3 playerPos)
{
//Get the closest one to the player
var center = new Vector3(_terrains[0].transform.position.x + _terrains[0].terrainData.size.x / 2, playerPos.y, _terrains[0].transform.position.z + _terrains[0].terrainData.size.z / 2);
float lowDist = (center - playerPos).sqrMagnitude;
var terrainIndex = 0;
for (int i = 0; i < _terrains.Length; i++)
{
center = new Vector3(_terrains[i].transform.position.x + _terrains[i].terrainData.size.x / 2, playerPos.y, _terrains[i].transform.position.z + _terrains[i].terrainData.size.z / 2);
//Find the distance and check if it is lower than the last one then store it
var dist = (center - playerPos).sqrMagnitude;
if (dist < lowDist)
{
lowDist = dist;
terrainIndex = i;
}
}
return terrainIndex;
}
It turns out the answer is simply NO, Unity does not provide such a function.
You can use this function to get the Closest Terrain to your current Position:
int GetClosestTerrain(Vector3 CheckPos)
{
int terrainIndex = 0;
float lowDist = float.MaxValue;
for (int i = 0; i < _terrains.Length; i++)
{
var center = new Vector3(_terrains[i].transform.position.x + _terrains[i].terrainData.size.x / 2, CheckPos.y, _terrains[i].transform.position.z + _terrains[i].terrainData.size.z / 2);
float dist = Vector3.Distance(center, CheckPos);
if (dist < lowDist)
{
lowDist = dist;
terrainIndex = i;
}
}
return terrainIndex;
}
and then you can use the function like this:
private Terrain[] _terrains;
void Start()
{
_terrains = Terrain.activeTerrains;
Vector3 start_pos = Vector3.zero;
start_pos.y = _terrains[GetClosestTerrain(start_pos)].SampleHeight(start_pos);
}
public static Terrain GetClosestTerrain(Vector3 position)
{
return Terrain.activeTerrains.OrderBy(x =>
{
var terrainPosition = x.transform.position;
var terrainSize = x.terrainData.size * 0.5f;
var terrainCenter = new Vector3(terrainPosition.x + terrainSize.x, position.y, terrainPosition.z + terrainSize.z);
return Vector3.Distance(terrainCenter, position);
}).First();
}
Raycast solution: (this was not asked, but for those looking for Solution using Raycast)
Raycast down from Player, ignore everything that has not Layer of "Terrain" (Layer can be easily set in inspector).
Code:
void Update() {
// Put this on Player! Raycast's down (raylength=10f), if we hit something, check if the Layers name is "Terrain", if yes, return its instanceID
RaycastHit hit;
if (Physics.Raycast (transform.localPosition, transform.TransformDirection (Vector3.down), out hit, 10f, 1 << LayerMask.NameToLayer("Terrain"))) {
Debug.Log(hit.transform.gameObject.GetInstanceID());
}
}
At this point already, you have a reference to the Terrain by "hit.transform.gameObject".
For my case, i wanted to reference this terrain by its instanceID:
// any other script
public static UnityEngine.Object FindObjectFromInstanceID(int goID) {
return (UnityEngine.Object)typeof(UnityEngine.Object)
.GetMethod("FindObjectFromInstanceID", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)
.Invoke(null, new object[] { goID });
}
But as written above, if you want the Terrain itself (as Terrain object) and not the instanceID, then "hit.transform.gameObject" will give you the reference already.
Input and code snippets taken from these links:
https://answers.unity.com/questions/1164722/raycast-ignore-layers-except.html
https://answers.unity.com/questions/34929/how-to-find-object-using-instance-id-taken-from-ge.html

Memory overload on for loop

I am making an object spawn script in which on the start of the script the spawning function is called and in it is a for loop which creates an object each iteration. It first picks a random X position for it and then checks if it is in a range of another prefabs coordinates so they don't spawn too close or worse, one in each other. If it is in the same coordinates as another prefab it will return 0 and this same goes out for the Z axis too. It also picks a random Y axis rotation so it doesn't all face the same direction. After this it spawns the prefab and sets it's coordinates and rotation after which it check if the coordinates in the X or Z axis are 0, and if any of those two are 0 it goes back one iteration and the last object to be spawned is destroyed so it doesn't flood. This works perfectly but when you want to set it to spawn too much objects it floods the RAM because there is nowhere to spawn more objects. I tried finding the highest X position and highest Z position and multiplying them, and setting them both to positive, and then dividing them by the space between the prefabs but this doesn't work as it sets it to a really really high number. How would you fix this?
Script:
using UnityEngine;
using System.Collections;
public class PrefabSpawner : MonoBehaviour {
public int amountOfPrefabs;
public int maxAmountOfPrefabs;
private int currentSpawnedPrefab;
public float spaceBetweenPrefabs;
private float positionX;
private float positionZ;
private float maxPositionX;
private float maxPositionZ;
private float multipliedPosXZ;
private bool previousSpawnHadZero;
public GameObject prefab;
private GameObject point1;
private GameObject point2;
private GameObject currentSpawn;
private Vector2[] positions;
void Start () {
currentSpawnedPrefab = 0;
previousSpawnHadZero = false;
point1 = gameObject.transform.GetChild (0).gameObject;
point2 = gameObject.transform.GetChild (1).gameObject;
if (point1.transform.position.x > point2.transform.position.x)
maxPositionX = point1.transform.position.x;
else
maxPositionX = point2.transform.position.x;
if (point1.transform.position.z > point2.transform.position.z)
maxPositionZ = point1.transform.position.z;
else
maxPositionZ = point2.transform.position.z;
multipliedPosXZ = maxPositionX * maxPositionZ;
if (multipliedPosXZ < 0)
multipliedPosXZ += multipliedPosXZ + multipliedPosXZ;
maxAmountOfPrefabs = Mathf.FloorToInt (multipliedPosXZ / spaceBetweenPrefabs);
if (amountOfPrefabs > maxAmountOfPrefabs)
amountOfPrefabs = maxAmountOfPrefabs;
point1.GetComponent<MeshRenderer> ().enabled = false;
point2.GetComponent<MeshRenderer> ().enabled = false;
gameObject.GetComponent<MeshRenderer> ().enabled = false;
positions = new Vector2[amountOfPrefabs];
SpawnPrefabs (amountOfPrefabs);
}
void SpawnPrefabs (int amount) {
for (int i = 0; i < amount; i++) {
if(previousSpawnHadZero)
i -= 1;
currentSpawn = (GameObject)Instantiate (prefab);
positionX = GetRandomPositionX ();
positionZ = GetRandomPositionZ ();
currentSpawn.transform.position = new Vector3 (positionX, this.transform.position.y + currentSpawn.transform.localScale.y, positionZ);
currentSpawnedPrefab += 1;
if (positionX == 0 || positionZ == 0) {
previousSpawnHadZero = true;
currentSpawnedPrefab -= 1;
Destroy (currentSpawn);
}
if (positionX != 0 && positionZ != 0) {
previousSpawnHadZero = false;
positionX = 0;
positionZ = 0;
}
}
}
IEnumerator Pause () {
yield return null;
}
float GetRandomPositionX () {
//Finds a random position for the X axis and then checks it and returns either 0 if the position is taken or the position if not
}
float GetRandomPositionZ () {
//Finds a random position for the Z axis and then checks it and returns either 0 if the position is taken or the position if not
}
bool CheckPositionAvailable (float pos, int axis) {
//Checks if the position is available.
}
}
Code is really long to debug but the problem is clearly visible and is from the SpawnPrefabs function. Currently, when you instantiate a prefab, you check if the generated position is 0. If 0, you subtract 1 from the i in the for loop then destroy the instantiated object and then start the for loop again from the current loop-1.
So the combination of Instantiate, Destroy and repeating it over again in the for loop is causing the memory issue.
What to do:
You have to re-write the whole function and this will require modification in your whole code too. Do not instantiate and destroy object in that loop unless when needed.
1.In the Start() function, create one prefab.
2.Make it to be invisible in the scene by disabling its mesh/sprite renderer.
3.Use that prefab in the for loop to check if the generated position is valid. If it is valid, you can now create/instantiate an object in the loop.
This prevents instantiating and destroying objects in the loop when you only create objects when if (positionX != 0 && positionZ != 0) .

Get data from a X,Y,Z array within given radius

I am developing a game server and currently I need to be able to get spectators within an area, however I fear the one I am using is really ugly and "slow" but I haven't experienced any performance hits yet as I am testing it locally not on a live server.
This is my GetSpectators Function:
public void GetSpectators(ref HashSet<Player> Players, Coordinate_t coordinate, bool MultiFloor = false)
{
for (int x = coordinate.X - 11; x != coordinate.X + 11; x++)
{
for (int y = coordinate.Y - 11; y != coordinate.Y + 11; y++)
{
if (MultiFloor)
{
for (int z = coordinate.Z - 2; z != coordinate.Z + 2; z++)
{
Tile tile = GetTile(x, y, z);
if (tile != null)
{
foreach (Player p in tile.Creatures)
{
Players.Add(p);
}
}
}
}
else
{
Tile tile = GetTile(x, y, coordinate.Z);
if (tile != null)
{
foreach (Player p in tile.Creatures)
{
Players.Add(p);
}
}
}
}
}
}
I have this class Map which holds this other dictionary with class Tile, each tile is represented with X, Y, and Z coordinates, each tile holds a list of this class called Player, some tiles have players some don't.
I need a good way and not ugly to get e.g:
All players within x=100, y=100, z=7 in radius 11 for example.
I think it would be smart to reference tiles in your Player class if you're not already doing so, and then pass the all the players to the GetSpectators() method...
Something like...
public class Player
{
// a reference to the tile that the player is currently on.
public Tile CurrentTile { get; set; }
}
This would allow you to loop through the players instead of so many tiles. And it should be cleaner and more efficient to find players the way you want without all the loop nesting. For ex:
public List<Player> GetSpectators(Hashset<Player> playersInGame, Coordinate coord)
{
var playersInRange = new List<Player>();
// iterate through each player.
foreach (var p in playersInGame)
{
// check if the tile the player is sitting on is in range of radius given.
if ((p.CurrentTile.X < coord.X + 6 || p.CurrentTile.X > coord.X - 6)
&&
(p.CurrentTile.Y < coord.Y + 6 || p.CurrentTile.Y > coord.Y - 6))
{
// Player is within radius.
playersInRange.Add(p);
}
}
return playersInRange;
}
You can add in the additional check for Z coordinate, and any other conditional statements. But as you can see, this would allow you to use one loop rather than 3 nested loops. You may or may not find it useful. But I hope it helps.
You can use circle equation to check whether player lies in the given radius of other player i.e
if
x1=100,y1=100,z=7 and r=11
then
(x-x1)^2+(y-y1)^2+(z-z1)^2=r^2.
Any point which satisfy this equation will lie in the region.

Categories

Resources