Terrain refresh lag unity c# - How effectively refresh the terrain? - c#

In the game, players are able to chop down trees. I then instantiate a falling tree in it's spot.
I remove the tree from the terrain list and refresh the terrain like so:
var treeInstancesToRemove = new List<TreeInstance>(terrain.treeInstances);
treeInstancesToRemove.RemoveAt(closestTreeIndex);
terrain.treeInstances = treeInstancesToRemove.ToArray();
// I refresh the terrain so the collider gets removed...
float[,] heights = terrain.GetHeights(0, 0, 0, 0);
terrain.SetHeights(0, 0, heights);
The terrain is VERY LARGE... This means that whenever a tree is chopped the game freezes for a few seconds and then resumes (as it refreshes). Is there a faster or more effective way I could take a look at? Having a freeze after every tree you chop down is not quite ideal?
THANKS A LOT IN ADVANCE!

The best thing I can suggest is having the world split into chunks that you can update separately. Either that, or have the collider update in a separate thread from the main one.

float hmWidth = grav.currentTerrain.terrainData.heightmapWidth;
float hmHeight = grav.currentTerrain.terrainData.heightmapHeight;
// get the normalized position of this game object relative to the terrain
Vector3 tempCoord = (transform.position - grav.currentTerrain.gameObject.transform.position);
Vector3 coord;
coord.x = tempCoord.x / grav.currentTerrain.terrainData.size.x;
coord.y = tempCoord.y / grav.currentTerrain.terrainData.size.y;
coord.z = tempCoord.z / grav.currentTerrain.terrainData.size.z;
// get the position of the terrain heightmap where this game object is
int posXInTerrain = (int)(coord.x * hmWidth);
int posYInTerrain = (int)(coord.z * hmHeight);
// we set an offset so that all the raising terrain is under this game object
//int offset = size / 2;
// get the heights of the terrain under this game object
float[,] heights = grav.currentTerrain.terrainData.GetHeights(posXInTerrain, posYInTerrain, 1, 1);
grav.currentTerrain.terrainData.SetHeights(posXInTerrain, posYInTerrain, heights); //THIS CHANGES TERRAIN FOR GOOD

Related

How do I Instantiate Objects In Unity?

I am making a top down game in Unity, how do I instantiate objects based on the players current position from the top of the camera view in Unity?
So far I have tried:
Instantiate(prefab, player.transform.position * 2 * Space.World)
but this didn't work. This just spawned objects from the center of the camera.
Space is simply an enum and has nothing to do with what you are trying! (Its purpose is to define the relative transform space for e.g. Transform.Rotate or Transform.Translate)
in that enum afaik
Space.World simply has the int value 0
Space.Self has the int value 1
so what you actually do is
Instantiate(prefab, player.transform.position * 2 * 0);
which equals
Instantiate(prefab, Vector3.zero);
which means the object is instantiated at World position 0,0,0.
Also using
Instantiate(prefab, player.transform.position * 2);
looks a bit strange. Are you sure you want to duplicate the actual position of the player? This would mean the spawned object is always on a line with the player and the World 0,0,0 and always double as far from the center as the player.
To me it sounds more like you rather want to spawn something in front of the player ... depending on the player's view direction (in topdown games usually player.transform.up or player.transform.right) so I guess what you are trying to do instead is something like
Instantiate(prefab, player.transform.position + player.transform.forward * 2);
which would instead spawn the object 2 Unity units in front of the player object
After your comment it sounds like instead you want to spawn the object at the player position on the X-axis but "over it" on the Y-axis so
Instantiate(prefab, player.transform.position + Vector3.Up * 2);
maybe you'll have to tweak the 2 depending how your player can move and how far it has to be to be "off screen". Alternatively you could also use a bit more complex using Camera.ScreenToWorldPoint
// get players position
var playerPos = player.transform.position;
// get camera's position
var cameraPos = Camera.main.transform.position;
// get difference on Z-axis
var cameraDistance = cameraPos.z - playerPos.z;
// Get the world 3d point for the upper camera border
// don't care about the X value
// and as distance we use the z-distance to the player
var cameraTopPoint = Camera.main.ScreenToWorldPoint(new Vector3(0, Camera.main.pixelHeight, cameraDistance);
// use the players X-axis position
cameraTopPoint.x = player.transform.x;
// to spawn it exactly on the Screen border above the player
Instantiate(prefab, cameraTopPoint);
// or to spawn it a bit higher
Instantiate(prefab, cameraTopPoint + Vector3.Up * 1);
Update
you said you want the prefab to be spawned on the "opposide" of the player position on the X axis.
If your Camera is static on world x=0 than this is actually quite simple:
cameraTopPoint.x = -player.transform.x;
If your camera is moving or not on x=0 than we have to calculate it already on the screen position level:
// get players position
var playerPos = player.transform.position;
// additionally convert the player position to screen space
var playerScreenPos = Camera.main.WorldToScreenPoint(playerPos);
var centerX = Camera.main.pixelWidth / 2.0f;
// get the difference between camera an player X (in screen space)
var differenceX = Mathf.Abs(playerScreenPos.x - centerX);
// here calculate the opposide side
var targetX = centerX + differenceX * (playerScreenPos.x < centerX ? 1 : -1);
// or alternatively if you want e.g. that the prefab always
// spawn with a bit of distance to the player even though he is very close
// to the center of the screen you could do something like
//var targetX = centerX + centerX / 2 * (playerScreenPos.x < centerX ? 1 : -1);
// Get the world 3d point for the upper camera border
// with the calculated X value
// and as distance we use the z-distance to the player
var cameraTopPoint = Camera.main.ScreenToWorldPoint(new Vector3(targetX, Camera.main.pixelHeight, playerScreenPos.z);
// to spawn it exactly on the Screen border above the player
Instantiate(prefab, cameraTopPoint);
// or to spawn it a bit higher
Instantiate(prefab, cameraTopPoint + Vector3.Up * 1);
Other Update
Ok so you want the prefab always spawn next to the player in a certain distance. The side depends on which side of the screen the player is so you could actually use the first approach again but just add the desired distance:
// Adjust this is the Inspector (in Unity units)
public float spawnDistanceToPlayer;
...
// get players position
var playerPos = player.transform.position;
// additionally convert the player position to screen space
var playerScreenPos = Camera.main.WorldToScreenPoint(playerPos);
var centerX = Camera.main.pixelWidth / 2.0f;
// Get the world 3d point for the upper camera border
// with the calculated X value
// and as distance we use the z-distance to the player
var cameraTopPoint = Camera.main.ScreenToWorldPoint(new Vector3(playerScreenPos.x, Camera.main.pixelHeight, playerScreenPos.z);
// now add or reduce spawnDistanceToPlayer
// depending on which side of the screen he is
cameraTopPoint.x += spawnDistanceToPlayer * (playerScreenPos.x < centerX ? 1 : -1);
// OR if your camera sits static on X=0 anyway you also could compare
// the player position directly without calculating in screenspace:
cameraTopPoint.x += spawnDistanceToPlayer * (playerPos.x < 0 ? 1 : -1);
// to spawn it exactly on the Screen border above the player
Instantiate(prefab, cameraTopPoint);
// or to spawn it a bit higher
Instantiate(prefab, cameraTopPoint + Vector3.Up * 1);
Typed on smartphone so might not be "copy-paste-able" ;)

How to calculate the (proper) transformation matrix between two frames (axial systems) in Unity3D

For a project in Unity3D I'm trying to transform all objects in the world by changing frames. What this means is that the origin of the new frame is rotated, translated, and scaled to match the origin of the old frame, then this operation is applied to all other objects (including the old origin).
For this, I need a generalized, 3-dimensional (thus 4x4) Transformation-Matrix.
I have looked at using Unity's built-in Matrix4x4.TRS()-method, but this seems useless, as it only applies the Translation, Rotation & Scale to a defined point.
What I'm looking for, is a change of frames, in which the new frame has a different origin, rotation, AND scale, with regards to the original one.
To visualize the problem, I've made a small GIF (I currently have a working version in 3D, without using a Matrix, and without any rotation):
https://gyazo.com/8a7ab04dfef2c96f53015084eefbdb01
The values for each sphere:
Origin1 (Red Sphere)
Before:
Position (-10, 0, 0)
Rotation (0,0,0)
Scale (4,4,4)
After:
Position (10, 0, 0)
Rotation (0,0,0)
Scale (8,8,8)
-
Origin2 (Blue Sphere)
Before:
Position (-20, 0, 0)
Rotation (0,0,0)
Scale (2,2,2)
After:
Position (-10, 0, 0)
Rotation (0,0,0)
Scale (4,4,4)
-
World-Object (White Sphere)
Before:
Position (0, 0, 10)
Rotation (0,0,0)
Scale (2,2,2)
After:
Position (30, 0, 20)
Rotation (0,0,0)
Scale (4,4,4)
Currently I'm simply taking the Vector between the 2 origins, scaling that to the difference between the two origins, then applying that on top of the new position of the original (first) origin.
This will of course not work when rotation is applied to any of the 2 origins.
// Position in original axes
Vector3 positionBefore = testPosition.TestPosition - origin.TestPosition;
// Position in new axes
Vector3 positionAfter = (positionBefore * scaleFactor) + origin.transform.position;
What I'm looking for is a Matrix that can do this (and include rotation, such that Origin2 is rotated to the rotation Origin1 was in before the transformation, and all other objects are moved to their correct positions).
Is there a way to do this without doing the full calculation on every Vector (i.e. transforming the positionbefore-Vector)? It needs to be applied to a (very) large number of objects every frame, thus it needs to be (fairly) optimized.
Edit: Scaling will ALWAYS be uniform.
There might be other solutions but here is what I would do
Wrap your objects into the following hierarchy
WorldAnchorObject
|- Red Sphere
|- Blue Sphere
|- White Sphere
Make sure the WorldAnchorObject has
position: 0,0,0
rotation: 0,0,0
localScale: 1,1,1
position/rotate/scale the Spheres (this will now happen relative to WorldAnchorObject)
Now all that is left is to transform the WorldAnchorObject -> it will move, scale and rotate anything else and keeps the relative transforms intact.
How exactly you move the world anchor is your thing. I guess you want to allways center and normalize a certain child object. Maybe something like
public void CenterOnObject(GameObject targetCenter)
{
var targetTransform = targetCenter.transform;
// The localPosition and localRotation are relative to the parent
var targetPos = transform.localPosition;
var targetRot = targetTransform.localRotation;
var targetScale = targetTransform.localScale;
// First reset everything
transform.position = Vector3.zero;
transform.rotation = Quaternion.Idendity;
transform.localScale = Vector3.one;
// set yourself to inverted target position
// After this action the target should be on 0,0,0
transform.position = targetPos * -1;
// Scale yourself relative to 0,0,0
var newScale = Vector3.one * 1/targetScale.x;
ScaleAround(gameObject, Vector3.zero, newScale);
// Now everything should be scaled so that the target
// has scale 1,1,1 and still is on position 0,0,0
// Now rotate around 0,0,0 so that the rotation of the target gets normalized
transform.rotation = Quaternion.Inverse(targetRotation);
}
// This scales an object around a certain pivot and
// takes care of the correct position translation as well
private void ScaleAround(GameObject target, Vector3 pivot, Vector3 newScale)
{
Vector3 A = target.transform.localPosition;
Vector3 B = pivot;
Vector3 C = A - B; // diff from object pivot to desired pivot/origin
float RS = newScale.x / target.transform.localScale.x; // relative scale factor
// calc final position post-scale
Vector3 FP = B + C * RS;
// finally, actually perform the scale/translation
target.transform.localScale = newScale;
target.transform.localPosition = FP;
}
Now you call it passing one of the children like e.g.
worldAnchorReference.CenterOnObject(RedSphere);
should result in what you wanted to achieve. (Hacking this in on my smartphone so no warranties but if there is trouble I can check it as soon as I'm with a PC again. ;))
Nevermind..
Had to apply the rotation & scale to the Translation before creating the TRS
D'Oh

How to instantiate tiles based on a 2D Array into a Platform Game using Unity?

I'm building a very simple platform game using 2D array to build the map based on it.
There are two simple goals I want and I'm currently not finding the answer:
Ensure that the camera is 16:9 and my scene will be 100% displayed in it
Build a 2D platform tileset as in an array
My environment:
Unity 5.5.0f3 (in 2D Mode)
Camera projection ortographic size 10.9
Game displayed in 16:9
Tileset dimensions are 128x128 px
Here is my current code:
public Camera camera;
public GameObject prefab;
void Start () {
Vector3 pos = camera.ScreenToWorldPoint(new Vector3 (0, 0, 0));
Vector3 nextPosition = pos;
for (int i = 0; i < 32; i++)
{
for(int j=0; j < 18; j++)
{
GameObject a = Instantiate(prefab, new Vector3(nextPosition.x, nextPosition.y, 0), Quaternion.identity);
nextPosition = new Vector3(pos.x+(prefab.GetComponent<Renderer>().bounds.size.x)*i, pos.y+(prefab.GetComponent<Renderer>().bounds.size.y)*j,0);
}
}
}
There are 3 things to notice about it:
I'm using ScreenToWorldPoint to get me the position for 0,0,0
My building order goes from bottom left to top right, each iteration is put as the past position + block width/height (x and y)
I'm using a 16:9 for scheme which is 32:18
Using it, this is my result:
As you can see it stays out of the camera boundary and even tho both camera and code are 16:9, it exceeds 1 column. Also note that the instantiate point is exactly in the middle of my GameObject, so I start instantiating it as half of the gameObject's width and height, meaning:
Vector3 pos = camera.ScreenToWorldPoint(new Vector3 (64, 64, 0));
And the reuslt is the following:
Not what I expected at all, by trial and error I figured out it is suposed to be at 16,16:
Vector3 pos = camera.ScreenToWorldPoint(new Vector3 (16, 16, 0));
Now its a perfect fit, but it exceeds 1 line at the top and 1,5 columns at the right. Which shouldn't because they are both 16:9
I'm clearly doing something wrong but I can't see what, I've been through this problem in the past but I don't remember what I figured out.
"Pos" needs a shift at the start. It can be achieved using Bounds.extents
Vector3 pos = camera.ScreenToWorldPoint(new Vector3 (0, 0, 0));
pos = new Vector3( pos.x + prefab.GetComponent<Renderer>().bounds.extents.x,
pos.y + prefab.GetComponent<Renderer>().bounds.extents.y,
0);
//....the rest is the same as your code
This will be better than using the magic numbers (16,16,0). The first tile will be positioned at the left-bottom corner no matter what scale you use.
128x128 px only tells me that you're using a square tile. So it's 128x128 px but I can fill the whole screen with one tile or I can make it as tiny as I can (thinking in world coordinate). The solution is either to scale the tiles or change the orthognalSize of the camera.
The easy solution is to change the orthographicSize to fit the tiles.
camera.orthographicSize = 18 * prefab.GetComponent<Renderer>().bounds.size.y * 0.5f;
orthographicSize equals half the height in world coordinate and you need 18 tiles in height.
So all code combined :
Bounds bound = prefab.GetComponent<Renderer>().bounds;
camera.orthographicSize = 18 * bound.size.y * 0.5f;
Vector3 pos = camera.ScreenToWorldPoint(Vector3.zero);
pos = new Vector3( pos.x + bound.extents.x, pos.y + bound.extents.y, 0);
//....The rest is the same as your code

C# Collision Programming - Momentum

I'm programming a 3D XNA game and I'm struggling to understand how to implement a momentum-like collision between my two players. Basically the concept is for either player to hit the other and attempt to knock the opponent off the level (Like the Smash Bros games) to score a point.
//Declared Variables
PrimitiveObject player1body;
PrimitiveObject player2body;
Vector3 player1vel = Vector3.Zero;
Vector3 player2vel = Vector3.Zero;
//Created in LoadContent()
PrimitiveSphere player1 = new PrimitiveSphere(tgd, 70, 32);
this.player1vel = new Vector3(0, 135, -120);
this.player1body = new PrimitiveObject(player1);
this.player1body.Translation = player1vel;
this.player1body.Colour = Color.Blue;
PrimitiveSphere player2 = new PrimitiveSphere(tgd, 70, 32);
this.player2vel = new Vector3(0, 135, 120);
this.player2body = new PrimitiveObject(player2);
this.player2body.Translation = player2vel;
this.player2body.Colour = Color.Red;
//Update method code for collision
this.player1vel.X = 0;
this.player1vel.Y = 0;
this.player1vel.Z = 0;
this.player2vel.X = 0;
this.player2vel.Y = 0;
this.player2vel.Z = 0;
if (player1body.BoundingSphere.Intersects(player2body.BoundingSphere))
{
this.player2vel.Z += 10;
}
As you can see all I'm doing is checking for Player1's bounding sphere and when it intersects with Player 2's then player 2 will be pushed back on the Z axis, now obviously this just wouldn't work and isn't very intuitive and is where my problem lies as I'm buggered trying to work out how to come up with a solution, what I want happening is when either player collides with the other they basically swap vectors giving it that bounce effect I'm looking for and not just affecting one axis but both the X & Z.
Thanks for taking the time to read and I'll be grateful for any solutions that anyone can think of.
Notes:
PrimitiveSphere if you're wondering is using (graphics device, diameter of the sphere, tesselation).
Basically, in a collision like the one you are trying to do, you want an elastic collision, in which both the kinetic energy and momentum are conserved. All momentum (P) is is mass * velocity, and Kinetic Energy (K) is 1/2*mass*velocity^2. In your situtation, I'm assuming everything has the same mass. If so, K = 1/2 * v^2. So, Kf = Ki and Pf = Pi. Using the kinetic energy, the velocity's magnitudes of the players are swapped. And as long as the collisions are head on (which based on your code I assume you're fine with), the players will swap directions.
So you could do something as simple as this:
if (player1body.BoundingSphere.Intersects(player2body.BoundingSphere))
{
Vector3 p1v = this.player1vel;
this.player1vel = this.player2vel;
this.player2vel = p1v;
}
This should create a fairly realistic collision. Now the reason I included that info about P and K is so that if you want non-head on collisions or different masses, you should be able to incorporate that. If the masses aren't the same, the velocity's won't simply change magnitudes and directions. There will be a lot more math involved. I hope this helps.

Xna Isometric point to click movement

I'm working on an isometric game (diamond grid) and I've stumbled across a minor problem regarding a character movement.
I'm using A* to find a path between 2 points and then I want to move my character from point A to point B going through all the tiles which form the path but I can't find a way to do this , I mean a simpler and accurate method.
So far I've scrapped this piece of code but it's kinda "rusty"
public void Destination(tile destination)
{
for (int i = 0; i < 8; i++)
{
if (AdjacentTile[i] == destination)
{
characterDirection = i;
}
}
animation.changeSpriteDirection(characterDirection); //After I found which adjacent tile is the next destination I change the character direction based on it's position (1 = North , 2 = Nort Est etc) .. so the Y of the Animation_sourceRectangle it's changed//
Vector2 Position;
Position.X = current_characterTile.X - destination.X;
Position.Y = current_characterTile.Y - destination.Y;
rotation = (float)Math.Atan2(-Position.X, Position.Y);
moveVector = (Vector2.Transform(new Vector2(0, -1), Matrix.CreateRotationZ(rotation))) * characterSpeed;
movingCommand = 1; // the character is supposed to be moving..
Move(); //this function moves the sprite until the *tile.i and tile.j* of the character is the same as tile.j and tile.i of the destination
//something like this
if ( characterTile.i == destination.i && characterTile.j == destination.j)
movingCommand = 0 //stop
else
character_Position += moveVector;
}
If anyone could give me a hint on what to do or help me I'll be very grateful.
Thank You.
Possibilities:
At each tile, determine the character's speed vector and also determine how much time it will take for the character to move to next tile. When that time elapses, immediately begin moving to the next tile. (This is what I implemented below.)
At each tile, determine the character's speed vector. Then, when the character is sufficiently close to the next tile (say, the difference between their X and Y coordinates is less than 2 pixels?), snap it to the tile and begin moving to the next tile. This will causes artifacts and be in general less precise.
A solution:
Let's assume you already ran your pathfinding algorithm and found a linked list of a tiles that you must go through to arrive at target. Let's also assume those tiles cannot become blocked partway through the movement (it is simple to modify the algorithm if they can, though).
I usually do something like this to handle this problem:
Run the pathfinding algorithm, which returns a List, if a path
exists.
character.Path = theListThatAStarReturned;
character.beginMovingToTarget(character.Path[0]);
character.Path.RemoveAt(0);
The beginMovingToTarget() method will determine the velocity vector and also determine the the time needed to arrive at the tile. When the time is reached, we immediately go to the next tile, until the Path is empty. Let's call this time variable character.timeToArrival.
Update():
if (!character.Moving) return; // Or just don't execute the rest of this code.
character.position += character.speed * elapsedSeconds;
character.timeToArrival -= elapsedSeconds;
// Did the character arrive in a tile?
if (character.timeToArrival <= 0)
{
// This will ensure the character is precisely in the tile, not a few pixels veered off.
character.position = character.movingToTile.position;
if (character.Path.Count == 0)
{
character.Moving = false;
// We are at final destination.
}
else
{
character.beginMovingToTarget(character.Path[0]);
character.Path.RemoveAt(0);
}
}
And the beginMovingToTarget(targetTile) function:
this.movingToTile = targetTile;
Vector2 direction;
direction = targetTile.position - this.position;
this.timeToArrival = direction.Length() / this.speedPerSeconds;
direction.Normalize();
direction *= this.speedPerSeconds;
this.speed = direction;
// Here, you may also want to change the character's animation, if you want to, or you may do that directly in the Draw() method based on its speed vector.
Make sure the division is in floats, not integers.

Categories

Resources