I've built a fractal based object generator in c# and unity that builds branches of objects that then bounce off each other using Colliders and Rigidbodies. Right now they hit each other and keep moving farther and farther apart. What I'd like to do it assign each object a certain level of gravitational attraction so that even as they're repelled through a collision they draw themselves back in. I've got everything except working except for the gravity side of things. Does anyone have experience with this who wouldn't mind giving me some direction? Thanks!
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class BuildFractal : MonoBehaviour
{
public Mesh[] meshes;
public Material material;
public Material[,] materials;
private Rigidbody rigidbody;
public int maxDepth; // max children depth
private int depth;
public float childScale; // set scale of child objects
public float spawnProbability; // determine whether a branch is created or not
public float maxRotationSpeed; // set maximium rotation speed
private float rotationSpeed;
public float maxTwist;
public Text positionText;
// Create arrays for direction and orientation data
private static Vector3[] childDirections = {
Vector3.up,
Vector3.right,
Vector3.left,
Vector3.forward,
Vector3.back,
// Vector3.down
};
private static Quaternion[] childOrientations = {
Quaternion.identity,
Quaternion.Euler(0f, 0f, -90f),
Quaternion.Euler(0f, 0f, 90f),
Quaternion.Euler(90f, 0f, 0f),
Quaternion.Euler(-90f, 0f, 0f),
// Quaternion.Euler(180f, 0f, 0f)
};
private void Start ()
{
rotationSpeed = Random.Range(-maxRotationSpeed, maxRotationSpeed);
transform.Rotate(Random.Range(-maxTwist, maxTwist), 0f, 0f);
if (materials == null)
{
InitializeMaterials();
}
// Select from random range of meshes
gameObject.AddComponent<MeshFilter>().mesh = meshes[Random.Range(0, meshes.Length)];
// Select from random range of colors
gameObject.AddComponent<MeshRenderer>().material = materials[depth, Random.Range(0, 2)];
// Add a collider to each object
gameObject.AddComponent<SphereCollider>().isTrigger = false;
// Add Rigigbody to each object
gameObject.AddComponent<Rigidbody>();
gameObject.GetComponent<Rigidbody>().useGravity = false;
gameObject.GetComponent<Rigidbody>().mass = 1000;
// Create Fractal Children
if (depth < maxDepth)
{
StartCoroutine(CreateChildren());
}
}
private void Update ()
{
transform.Rotate(0f, rotationSpeed * Time.deltaTime, 0f);
}
private IEnumerator CreateChildren ()
{
for (int i = 0; i < childDirections.Length; i++)
{
if (Random.value < spawnProbability)
{
yield return new WaitForSeconds(Random.Range(0.1f, 1.5f));
new GameObject("Fractal Child").AddComponent<BuildFractal>().Initialize(this, i);
}
/*if (i == childDirections.Length)
{
DestroyChildren();
}*/
// positionText.text = transform.position.ToString(this);
}
}
private void Initialize (BuildFractal parent, int childIndex)
{
maxRotationSpeed = parent.maxRotationSpeed;
// copy mesh and material references from parent object
meshes = parent.meshes;
materials = parent.materials;
maxTwist = parent.maxTwist;
// set depth and scale based on variables defined in parent
maxDepth = parent.maxDepth;
depth = parent.depth + 1;
childScale = parent.childScale;
transform.parent = parent.transform; // set child transform to parent
// transform.localScale = Vector3.one * childScale;
transform.localScale = Vector3.one * Random.Range(childScale / 10, childScale * 1);
transform.localPosition = childDirections[childIndex] * (Random.Range((0.1f + 0.1f * childScale),(0.9f + 0.9f * childScale)));
transform.localRotation = childOrientations[childIndex];
spawnProbability = parent.spawnProbability;
}
private void InitializeMaterials ()
{
materials = new Material[maxDepth + 1, 2];
for (int i = 0; i <= maxDepth; i++)
{
float t = i / (maxDepth - 1f);
t *= t;
// Create a 2D array to hold color progressions
materials[i, 0] = new Material(material);
materials[i, 0].color = Color.Lerp(Color.gray, Color.white, t);
materials[i, 1] = new Material(material);
materials[i, 1].color = Color.Lerp(Color.white, Color.white, t);
}
// materials[maxDepth, 0].color = Color.white;
materials[maxDepth, 1].color = Color.white;
}
}
Depends how accurate your gravity simulation has to be. Assuming all objects in your simulation have the same density, you could use Mesh.bounds to roughly estimate their volume:
Vector3 size = myMesh.bounds.size;
float volume = size.x * size.y * size.z * scale; // scale could be childScale in your case
Since your simulation is a fractal, you will have to apply childScale in each of your fractal's iterations. But you don't have to recalculate the base volume of your mesh if it doesn't change.
As for the gravity simulation:
This might get quite complex with a high number of objects. You would have to simulate a whole gravity field.
The calculation for only two objects interacting with each other is rather simple. The forces applied to the bodies attracting each other can be calculated by the Newtonian formula
F1 = F2 = G * m1 * m2 / r^2
(see: https://en.wikipedia.org/wiki/Gravitational_constant)
But you may have far more objects than two in your system. You would have to calculate the above relationship for each object -- between each object. And for each object, you would have to add all calculated forces and than apply the resulting force.
Let's say you have N objects in your scene, you would have to do (N-1) of the above calculations for each object. That yields N^(N-1) calculations, which will get out of hand quite quickly, especially if you doing it in a fractal structure.
To get hold of this immense complexity, you could introduce a range of influence, so only nearby objects have an effect on each other. Although this will further reduce the accuracy of your simulation.
Related
In my 2D game, the player Prefab has children which are weapons (like swords) so that when the player moves, the weapons translate in world space with his movements while maintaining a constant local position (until of course it is time to attack). The weapons automatically point towards nearby enemies, and swing when they get close enough.
I want the weapon to follow a swing arc by rotating around a pivot point defined at half of the weapon's range in the direction of the enemy. Once the weapon starts the swing, the arc's local position and rotation should remain unchanged and no longer care about the enemy position while the world arc will obviously translate with the player. The weapon should follow this arc purely relative to the player.
RotateAround seems to only work in world space, and thus causes strange problems when trying to rotate around an object in world space while the weapon's world position (as well as my desired pivot point's world position) would be translating with the player. Also, the point that I need it to rotate around needs to be relative to the local space, since when the player moves, the weapon needs to maintain its local arc while also translating with the player.
I also tried using Vector3.Slerp on the weapon's transform.localPosition, which seemed like it would be the perfect solution, but I can't seem to get the arc to match what I envision a good round swing would look like.
The attack consists of three parts: Backswing, Foreswing, and Recovery. The part that I care most about is the foreswing arc, as the others can be acheived easily with simply local rotations and lerping.
const int BACKSWING = 0;
const int FORESWING = 1;
const int RECOVER = 2;
float[] timeFrac = { .15f, .25f, .6f };
float[] rotations = { 120f, -240f, 120f };
float backSwingDistMultiplier = .5f;
//Swing Attack
public override IEnumerator Attack(float startAngle) {
var totalAttackTime = GetAttackTime();
var backSwingDist = backSwingDistMultiplier * Range;
var startPos = transform.localPosition;
var slerpCenterDiff = PerpDir(dir).normalized;
//Interpolation arrays
float[] swingTimes = { timeFrac[BACKSWING] * totalAttackTime,
timeFrac[FORESWING] * totalAttackTime,
timeFrac[RECOVER] * totalAttackTime };
float[] startAngles = { startAngle,
startAngle + rotations[BACKSWING],
startAngle + rotations[BACKSWING] + rotations[FORESWING] };
Vector3[] swingPositions = { startPos - (dir - slerpCenterDiff ) * backSwingDist,
startPos + dir * Range + slerpCenterDiff * backSwingDist };
Vector3[] slerpCenters = { (startPos + swingPositions[BACKSWING]) * .5f + slerpCenterDiff ,
((swingPositions[BACKSWING] + swingPositions[FORESWING]) * .5f) + slerpCenterDiff };
Vector3[] slerpStarts = { startPos - slerpCenters[BACKSWING],
swingPositions[BACKSWING] - slerpCenters[FORESWING]};
Vector3[] slerpEnds = { swingPositions[BACKSWING] - slerpCenters[BACKSWING],
swingPositions[FORESWING] - slerpCenters[FORESWING]};
timer = 0;
float percentDone;
//A swing attack has backswing, foreswing, and recovery
for (int swing = 0; swing <= 2; swing++) {
while (timer < swingTimes[swing]) {
percentDone = timer / swingTimes[swing];
//Backswing and Foreswing will slerp
if (swing < RECOVER) {
transform.localPosition = Vector3.Slerp(slerpStarts[swing], slerpEnds[swing], percentDone);
transform.localPosition += slerpCenters[swing];
} else { //Recover will lerp
transform.localPosition = Vector3.Lerp(swingPositions[FORESWING], startPos, percentDone);
}
transform.localRotation = Quaternion.Euler(0, 0,
startAngles[swing] + rotations[swing] * percentDone);
timer += Time.deltaTime;
yield return null;
}
transform.localRotation = Quaternion.Euler(0, 0, startAngles[swing] + rotations[swing]);
timer -= swingTimes[swing];
}
transform.localPosition = startPos;
}
I would just make an animation out of it, but I need the range to be dynamic, which is next to impossible to achieve with keyframes.
I was able to get my desired outcome by defining the arc centers in localPosition first:
var backswingArcCenter = (startPos + swingPositions[BACKSWING]) * 0.5f;
var foreswingArcCenter = (swingPositions[BACKSWING] + swingPositions[FORESWING]) * 0.5f;
and then calling RotateAround using that position added to the player's world space
if (swing == FORESWING) {
transform.RotateAround(player.transform.position + foreswingArcCenter,
Vector3.forward, Time.deltaTime / swingTimes[swing] * 270f);
} else if (swing == BACKSWING) {
transform.RotateAround(player.transform.position + backswingArcCenter,
Vector3.forward, Time.deltaTime / swingTimes[swing] * -180f);
}
First off, sorry it this isn't written very well, I've spend hours debugging this and I'm very stressed. I'm trying to make a moving platform in unity that can move between way-points, I don't want to have to have tons of gameobjects in the world taking up valuable processing power though so I'm trying to use something I can just add to the script through the editor.
The only problem is that it seems to be doing this at an incredible speed:
Black = The Camera View, Blue = The platform and where it should be going based on waypoints, Red = What it is currently doing.
I've spend hours trying to find a fix but I have no idea why it's doing this.
My Script on the Platform:
public Vector3[] localWaypoints;
Vector3[] globalWaypoints;
public float speed;
public bool cyclic;
public float waitTime;
[Range(0, 2)]
public float easeAmount;
int fromWaypointIndex;
float percentBetweenWaypoints;
float nextMoveTime;
void Start()
{
globalWaypoints = new Vector3[localWaypoints.Length];
for (int i = 0; i < localWaypoints.Length; i++)
{
globalWaypoints[i] = localWaypoints[i] + transform.position;
}
}
void Update()
{
Vector3 velocity = CalculatePlatformMovement();
transform.Translate(velocity);
}
float Ease(float x)
{
float a = easeAmount + 1;
return Mathf.Pow(x, a) / (Mathf.Pow(x, a) + Mathf.Pow(1 - x, a));
}
Vector3 CalculatePlatformMovement()
{
if (Time.time < nextMoveTime)
{
return Vector3.zero;
}
fromWaypointIndex %= globalWaypoints.Length;
int toWaypointIndex = (fromWaypointIndex + 1) % globalWaypoints.Length;
float distanceBetweenWaypoints = Vector3.Distance(globalWaypoints[fromWaypointIndex], globalWaypoints[toWaypointIndex]);
percentBetweenWaypoints += Time.deltaTime * speed / distanceBetweenWaypoints;
percentBetweenWaypoints = Mathf.Clamp01(percentBetweenWaypoints);
float easedPercentBetweenWaypoints = Ease(percentBetweenWaypoints);
Vector3 newPos = Vector3.Lerp(globalWaypoints[fromWaypointIndex], globalWaypoints[toWaypointIndex], easedPercentBetweenWaypoints);
if (percentBetweenWaypoints >= 1)
{
percentBetweenWaypoints = 0;
fromWaypointIndex++;
if (!cyclic)
{
if (fromWaypointIndex >= globalWaypoints.Length - 1)
{
fromWaypointIndex = 0;
System.Array.Reverse(globalWaypoints);
}
}
nextMoveTime = Time.time + waitTime;
}
return newPos - transform.position;
}
struct PassengerMovement
{
public Transform transform;
public Vector3 velocity;
public bool standingOnPlatform;
public bool moveBeforePlatform;
public PassengerMovement(Transform _transform, Vector3 _velocity, bool _standingOnPlatform, bool _moveBeforePlatform)
{
transform = _transform;
velocity = _velocity;
standingOnPlatform = _standingOnPlatform;
moveBeforePlatform = _moveBeforePlatform;
}
}
void OnDrawGizmos()
{
if (localWaypoints != null)
{
Gizmos.color = Color.red;
float size = .3f;
for (int i = 0; i < localWaypoints.Length; i++)
{
Vector3 globalWaypointPos = (Application.isPlaying) ? globalWaypoints[i] : localWaypoints[i] + transform.position;
Gizmos.DrawLine(globalWaypointPos - Vector3.up * size, globalWaypointPos + Vector3.up * size);
Gizmos.DrawLine(globalWaypointPos - Vector3.left * size, globalWaypointPos + Vector3.left * size);
}
}
}
UPDATE: Upon further testing I found that if the first object in my localWaypoint array is set to 0,0,0 and my 2nd object is set to 1,0,0 then the platform will spiral to the right, making sure to hit the waypoints as it's spiraling, and then spiraling out into nowhere like in the image above. But if I set my first object to 0,0,0 and my second object to -1,0,0 then the object will act the same way as before, but will spiral to the left as displayed in this image. (The second image has also bee updated to display how the platfrom makes sure to hit both waypoints before is spirals out into nowhere).
I've also noticed that if I set both waypoints to 0,0,0 then the platform stays still, these 2 things prove that it has somthing to do with the way the waypoints are being handled and not some other script or parent object interfering.
Using the updated numbers ([0,0,0], [1,0,0]) works in my test app. However, if I put a rotation on the object's Y axis, then I see behavior like you are seeing. In Update, if you change:
transform.Translate(velocity);
to
transform.Translate(velocity, Space.World);
You should see your desired behavior. Note that "transform.Translate(velocity)" is the same as "transform.Translate(velocity, Space.Self)". Your translation is being rotated.
If you are curious, take a look at this for more information on how the values in the transform are applied:
https://gamedev.stackexchange.com/questions/138358/what-is-the-transformation-order-when-using-the-transform-class
Before questioning, I declare that I am a beginner.
Please understand that there may be a lack of explanation.
I'll explain what I want to implement with pictures and code.
Below is a picture.
Like the picture above, I have implemented the present missile (prefab) to be generated between 5 and 7 random locations.
Here's the code for that:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
class MissileAnimation : WordGameSingleton<MissileAnimation>
{
private int missile_num = 0;
public List<GameObject> missile_list = new List<GameObject>();
float fixedPosition_z = 1f;
public GameObject missile_parent_area = null;
protected override void Start()
{
StartCoroutine(fireMissiles());
missile_parent_area = new GameObject();
missile_parent_area.name = "MissileAnimation";
}
protected override void Update()
{
}
private IEnumerator fireMissiles()
{
yield return new WaitForSeconds(0.3f);
missile_parent_area.transform.SetParent(this.gameObject.transform);
missile_parent_area.transform.localScale = new Vector3(1f, 1f, 1f);
missile_parent_area.transform.localPosition = new Vector3(this.gameObject.transform.position.x, this.gameObject.transform.position.y, fixedPosition_z);
//GameObject planet_position = GameObject.Find("Center_Planet") as GameObject;position.x
//float planet_position_x = planet_position.transform.position.x;
//float planet_position_y = planet_position.transform.position.y;
missile_num = UnityEngine.Random.Range(5, 7); // Generate between 5 and 7EA missiles
int origin_width = 50;
int origin_height = 100;
for (int i = 0; i < missile_num; ++i)
{
// Generate missile position random position
float angle = Mathf.Round(UnityEngine.Random.Range(90f, 180f) + 45f);
float randomPosition_x = Mathf.Cos(angle) * (Screen.width - origin_width) / 2;
float randomPosition_y = Mathf.Sin(angle) * (Screen.height - origin_height) / 2;
// Center view of the Planet
// How do I implement this part?
// Create a missile prefab
GameObject missile_prefab_load = Resources.Load("Prefabs/Object/Missile") as GameObject;
GameObject missile_prefab_instantiate = Instantiate(missile_prefab_load, this.gameObject.transform.position, Quaternion.identity) as GameObject;
// MonoBehaviour.Instantiate(Resources.Load("Prefabs/Object/Missile"), new Vector3(0f, 0f, 0f), Quaternion.identity) as GameObject;
missile_prefab_instantiate.name = "missile_" + i;
missile_prefab_instantiate.transform.SetParent(missile_parent_area.transform);
missile_prefab_instantiate.transform.localPosition = new Vector3(randomPosition_x, randomPosition_y, 0f);
missile_prefab_instantiate.transform.localScale = new Vector3(2.0f , 2.0f, 2.0f);
missile_list.Add(missile_prefab_instantiate);
// Speed of missile flight
float speed = UnityEngine.Random.Range(1.0f, 1.4f);
// Go Missile
LeanTween.moveLocal(missile_prefab_instantiate, new Vector3(0f, 0f, 0f), speed).setEase(LeanTweenType.easeInCubic).setOnUpdate(crashPlanet).setOnUpdateParam(missile_prefab_instantiate);
// LeanTween.moveLocal(GameObject , Vector , time:float)
// setOnUpdate : <float,Object>
// Reduced size as the rocket fires.
LeanTween.scale(missile_prefab_instantiate, new Vector3(1.0f, 1.0f, 1.0f), speed).setEase(LeanTweenType.easeInCubic);
}
yield return new WaitForSeconds(0.9f);
Vector3 position = this.gameObject.transform.localPosition;
this.transform.position = new Vector3(position.x, 0f, position.z);
}
private void crashPlanet(float obj, object missile)
{
}
}
My question starts from here.
The value of the Z-axis of a missile object created with random coordinates is zero. (Rotation Z)
I want the missiles (prefabs) to point to the center coordinates of the Center_Planet.
For example, as shown in the picture below.
To implement this, I used the LookAT function, also tried using the Quaternion.rotation function.
But the implementation failed.
SO..In order to implement the above, we try to do calculations using the Mathf function, I don't know how to formulate it.
To implement the above, I would appreciate some advice on how to set up the formula.
I would really appreciate it if you could give me advice as a beginner.
You have the possibility to assign the "forward" vector of your 2D element using transform.right = ....
Transform target = GameObject.Find("Center_Planet").transform ;
// ...
for (int i = 0; i < missile_num; ++i)
{
// ...
missile_prefab_instantiate.transform.right = target.position - missile_prefab_instantiate.transform.position ;
}
yield return new WaitForSeconds(0.9f);
// ...
The Circle part put the objects in the air.
How can i make that they will be on the ground ?
They are standing in the air. When using the square formation they are on ground but with the circle they are in the air.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SquadFormation : MonoBehaviour
{
enum Formation
{
Square, Circle
}
public Transform squadMemeber;
public int columns = 4;
public int space = 10;
public int numObjects = 20;
// Use this for initialization
void Start()
{
ChangeFormation();
}
// Update is called once per frame
void Update()
{
}
private void ChangeFormation()
{
Formation formation = Formation.Square;
switch (formation)
{
/*case Formation.Square:
for (int i = 0; i < 23; i++)
{
Transform go = Instantiate(squadMemeber);
Vector3 pos = FormationSquare(i);
go.position = new Vector3(transform.position.x + pos.x, 0, transform.position.y + pos.y);
go.Rotate(new Vector3(0, -90, 0));
}
break;*/
case Formation.Circle:
Vector3 center = transform.position;
for (int i = 0; i < numObjects; i++)
{
Vector3 pos = RandomCircle(center, 5.0f);
var rot = Quaternion.LookRotation(pos - center);
Instantiate(squadMemeber, pos, rot);
}
break;
}
}
Vector2 FormationSquare(int index) // call this func for all your objects
{
float posX = (index % columns) * space;
float posY = (index / columns) * space;
return new Vector2(posX, posY);
}
Vector3 RandomCircle(Vector3 center, float radius)
{
float ang = Random.value * 360;
Vector3 pos;
pos.x = center.x + radius * Mathf.Sin(ang * Mathf.Deg2Rad);
pos.z = center.z + radius * Mathf.Cos(ang * Mathf.Deg2Rad);
pos.y = center.y;
return pos;
}
}
They should be instantiating on the ground(Terrain).
Need to position them on the ground.
Update:
This is what i tried now.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SquadFormation : MonoBehaviour
{
enum Formation
{
Square, Circle
}
public Transform squadMemeber;
public int columns = 4;
public int space = 10;
public int numObjects = 20;
public float yOffset = 1;
// Use this for initialization
void Start()
{
ChangeFormation();
}
// Update is called once per frame
void Update()
{
}
private void ChangeFormation()
{
Formation formation = Formation.Circle;
switch (formation)
{
/*case Formation.Square:
for (int i = 0; i < 23; i++)
{
Transform go = Instantiate(squadMemeber);
Vector3 pos = FormationSquare(i);
go.position = new Vector3(transform.position.x + pos.x, 0, transform.position.y + pos.y);
go.Rotate(new Vector3(0, -90, 0));
}
break;*/
case Formation.Circle:
Vector3 center = transform.position;
for (int i = 0; i < numObjects; i++)
{
Vector3 pos = RandomCircle(center, 5.0f);
var rot = Quaternion.LookRotation(pos - center);
pos.y = Terrain.activeTerrain.SampleHeight(pos);
pos.y = pos.y + yOffset;
Instantiate(squadMemeber, pos, rot);
}
break;
}
}
Vector2 FormationSquare(int index) // call this func for all your objects
{
float posX = (index % columns) * space;
float posY = (index / columns) * space;
return new Vector2(posX, posY);
}
Vector3 RandomCircle(Vector3 center, float radius)
{
float ang = Random.value * 360;
Vector3 pos;
pos.x = center.x + radius * Mathf.Sin(ang * Mathf.Deg2Rad);
pos.z = center.z + radius * Mathf.Cos(ang * Mathf.Deg2Rad);
pos.y = center.y;
return pos;
}
}
I added a offset yOffset and this two lines inside the for loop:
pos.y = Terrain.activeTerrain.SampleHeight(pos);
pos.y = pos.y + yOffset;
Now they are on the ground but lie down on the back/stomach and not standing like they were in the air.
Why when instantiating objects they are in the air and not on ground?
You need to find a way to calculate the height of the terrain then use that as your y-axis value.
After you get your pos, modify the y-axis with the Terrain.activeTerrain.SampleHeight function.
Vector3 pos = RandomCircle(center, 5.0f);
pos.y = Terrain.activeTerrain.SampleHeight(pos);
Maybe add an offset depending on the type of the GameObject(The yOffset should be a float)
pos.y = pos.y + yOffset;
Now, go ahead and instantiate with that new pos:
var rot = Quaternion.LookRotation(pos - center);
Instantiate(squadMemeber, pos, rot);
Note:
Depending on the size of your character,you must and have to keep changing the value of the yOffset until you get the position you want. This position should work well for the-same character. If you want to do this to the GameObjects with different size, you also have to modify the yOffset until you are satisfied with it.
Not an actual answer, but some advice:
I see some basic problem here with your code being kind of confused anyway. There are some things that should be similar that aren't. I think the baseline here is that you need some basic structure.
First things first, you should start with identifying the basic parts you need here.
There are three basic things to resolve:
XY-position
Z-position
rotation
Last one is the issue with your characters lying on the ground. See the rotation by -90 degrees for the square? I assume that this one does this.
But a more pressing matter here, here is a question for you: Aside from the XY-position, what should be the difference between squares and circles? Answer: none. You treat both cases far more different than they should be. The code should look somewhat like this:
Position xyPosition;
switch (formation)
case Circle: xyPosition = randomCirclePosition(someargument)
case Square: xyPosition = randomSquarePosition(someargument)
Position position = rotateUpright(xyPosition)
position = adjustZ(position)
See how the difference is applied only at one part?
The thing that jumps to my eyes immediately are those signatures: Vector2 FormationSquare and Vector3 RandomCircle. In particular, Vector2 and Vector3. Those are two entirely different approaches. Why? This makes your code unnecessarily heterogeneous, makes it harder to read. Does not allow for the clear structure I wrote above. Why did you derive that much? Why didn't you copy the old code of the square and then adjusted it as needed?
Do I assume correct when I say that you copied the square code from the internet? And that you have troubles understanding it for some reason? Maybe because you are not familiar with the math behind it? If so, my "answer" is this: Take the square code and do not continue until you understand any single line it it. What it does, why it is needed, what happens without it. Perfectly fine if you simply go and outcomment lines, then look what happens in the engine. Or ask somebody. But it is most important that you understand every single line in the end.
You also might want to put the code on CodeReview when it is working. I see some issues here and there in style. Like the mysterious 23 that comes out of nothing.
Sorry if this sounded a little headstrong or the like, or if I misjudge the situation here.
I've been trying to use what I learned in the this fractal tutorial to make something I can use for my own project. I want to a script that can generate trees in Unity. I feel I've made a lot of progress, but have hit a wall.
The way I've set up the script is so that it scales the branches according to two parameters:
a) a publich 'childScale' variable.
b) the amount of branches sprouting from the previous branch.
The biggest problem has been that, when children are parented to a non-uniform-scale object, they become distorted in unintended ways. To bypass, I've made the prefab instances (which are 1, 1, 1 by default) children of other GameObjects. These GameObjects are also parents to other GameObjects that contain other prefab instances. This is problematic because the scaling principle of a fractal necessitates a continuous inheritance of scale from the parent, but the child prefab instances never pass anything along. So I end up having to adjust the proportions of the prefab instances to what they would be if they could inherit directly from one another. The below script 'works' because of the exponential modifier (middle of Start(), after 'else'), but only if all branches have the same amount of offshoots, i.e. only if the public min and max Branch Density variables are the same integer.
To summarize, I have two problems that I'd like your input on.
The main problem: How can I maintain the scaling integrity of the fractal principle despite the lack of a continous hierarchy while allowing for non-uniformity in the overall form of the tree?
A secondary problem, by far, is that my 'thicknessScaler' variable makes my branches too thin, especially the more there are. Right now it just divides 1 by the amount of offshoots, so I'd need one that doesn't shave off quite as much.
using UnityEngine;
using System.Collections;
public class TreeFractal : MonoBehaviour
{
public GameObject[] branches; // the last branch is pointed, while all the others are rounded
public int maxDepth;
public float childScale;
public float maxTwist; // OFF temporarily
public Vector3 baseBranchSize; // proportions of instantiated prefab
public int minBranchDensity; // amount of offshoots per node, randomized
public int maxBranchDensity;
public float branchTilt;
private int depth;
private int branchDensity;
private GameObject branch;
private GameObject instance;
private TreeFractal grandparent;
private float displace;
private float thicknessScaler = 1;
private float parentDensity;
private void Start()
{
if (depth < maxDepth)
branch = branches[0];
else
branch = branches[1];
instance = Instantiate(branch);
instance.transform.parent = transform; // prefabs (non-uniform proportions) are made the children of uniform-scaled Game Objects.
if (depth == 0)
{
displace = baseBranchSize.y;
instance.transform.localScale = baseBranchSize;
}
else //Multiplication by density^depth is to make up for the shrinking game objects, as the prefab instances do not pass on their scale
//while the GameObjects do. This only works when all 'depths' of the tree have the same amount of offshoots.
//Because the GameObject must remain uniform, all scaling of the y axis must occur in the prefab instance.
{
displace = baseBranchSize.y * Mathf.Pow(parentDensity, depth);
//if (depth == 2)3
print(baseBranchSize.y * Mathf.Pow(parentDensity, depth));
instance.transform.localScale = new Vector3
(
baseBranchSize.x,
baseBranchSize.y * Mathf.Pow(parentDensity, depth),
baseBranchSize.z
);
}
instance.transform.localPosition = new Vector3(0f, 1f * (displace / 2), 0f); //prefab instance pivots at one end of the GameObject, for rotations.
instance.transform.localRotation = Quaternion.Euler(0f, 0f, 0f);
transform.Rotate(Random.Range(-maxTwist * ((float)(depth + 1)/ maxDepth), maxTwist * ((float)(depth + 1) / maxDepth)), 0f, 0f);
//increases the potential randomized twist more, the smaller the branches get.
if (depth < maxDepth)
{
StartCoroutine(CreateChildren());
}
}
private void Update()
{
}
private IEnumerator CreateChildren()
{
branchDensity = Random.Range(minBranchDensity, maxBranchDensity + 1);
for (int i = 0; i < branchDensity; i++)
{
yield return new WaitForSeconds(Random.Range(0.1f, 0.5f));
Quaternion quaternion = BranchRotater(branchDensity, i);
new GameObject("Fractal Child").AddComponent<TreeFractal>().
Initialize(this, i, quaternion);
}
}
private Quaternion BranchRotater (int density, int childIndex) //returns the rotation of the branch depending on the index and amount of branches.
{
Quaternion quaternion = Quaternion.Euler
(0f,
(360 / density) * childIndex,
branchTilt
);
return quaternion;
}
private void Initialize(TreeFractal parent, int childIndex, Quaternion quaternion)
{
branches = parent.branches;
branchTilt = parent.branchTilt;
maxDepth = parent.maxDepth;
depth = parent.depth + 1;
baseBranchSize = parent.baseBranchSize;
maxTwist = parent.maxTwist;
transform.parent = parent.transform;
childScale = parent.childScale;
minBranchDensity = parent.minBranchDensity;
maxBranchDensity = parent.maxBranchDensity;
thicknessScaler = 1f / parent.branchDensity; // I need a better equation here. This scaler is too small.
transform.localScale = Vector3.one * childScale * thicknessScaler; // reproportions all 3 axes of child GameObject so that
//the child remains of uniform scale. This must then be compensated for in the scaling of said object's child-prefab.
parentDensity = parent.branchDensity;
transform.localPosition = Vector3.up * parent.displace; //positions child relative to its parent
transform.localRotation = quaternion;
}
}
If you build your prefab like this you can apply the parent-to-child scale at the root node of the prefab and the nonuniform at the subnode. If you now attach the child's root to the root node only the uniform scale will carry over.
prefab:
[root]
|--->[non-uni]
| |---> mesh
.
.
[childRoot]