I'm working on a vertical infinite runner, I'm attempting to grab the distance the player has moved in the y axis but only upwards, I don't want to the distance counter to count down, only up
Since it is a vertical game, jumping is used to get up to platforms, this means that I can't just base the distance counter on how long the player has stayed alive, since they could just stand there while the platforms just drag him down at a constant rate of -27f
I've attempted to just add -27 * Time.deltaTime to my count but that appeared to do absolutely nothing. Below is a counter that is working but only if my player is actually moving upwards, not stationary as I mentioned
public class Distance : MonoBehaviour {
public float distanceCount;
public Transform playerHeight;
public Text height;
public Text shadowHeight;
void Start () {
distanceCount = 0;
}
void Update()
{
distanceCount = (playerHeight.position.y + 0.09500122f) / 200f;
distanceCount = Mathf.Round(distanceCount * 10f) / 1f;
height.text = distanceCount.ToString() + " m";
shadowHeight.text = distanceCount.ToString() + " m";
}
}
If I could get some advice or an update to my current code that might solve for this I would really appreciate it, thank you.
Related
EDIT: I have updated the code, but it still doesn't quite work properly.
I am almost done with this project but am stuck on the last part.
Basically, the parameters for what I'm working on is as follows:
Instantiate one cube prefab every frame into the scene.
The cubes need to be named as “Cube1”, “Cube2”… based on the order they are generated.
The cubes need to be generated at a random locations within 1 unit from the origin [0,0,0].
Each cube should have a random color.
The cube size (localScale) shrink 10% in each frame.
When the cube’s scale is less than 10% of its original scale, the cube is destroyed.
I am stuck on six. The cubes are created, colored and named, and shrink over time, but will not always destroy themselves after they shrink enough.
I've gotten them to delete SOME of the cubes, but others will now just stop shrinking and stay in the scene indefinitely.
if this helps, I noticed that after it deletes a certain element (cube0 for example)it will reuse that name again and then the list of objects gets all messed up looking and things stop deleting.
My code so far:
using JetBrains.Annotations;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CubeCreator : MonoBehaviour
{
Vector3 origin;
private List<GameObject> cubeList;
public float targetScale = 0.1f;
public float shrinkSpeed = 0.1f;
GameObject newCube;
// Start is called before the first frame update
private void Awake()
{
cubeList = new List<GameObject>();
}
void Start()
{
origin = new Vector3(0, 0, 0);
}
// Update is called once per frame
void Update()
{
AddNewCube();
for (int i = 0; i < cubeList.Count; i++)
{
cubeList[i].name = "cube" + i;
cubeList[i].transform.localScale = Vector3.Lerp(cubeList[i].transform.localScale, new Vector3(targetScale, targetScale, targetScale), Time.deltaTime * shrinkSpeed);
if (cubeList[i].transform.localScale.x <= targetScale + 0.5f)
{
cubeList.RemoveAt(i);
RemoveCube(newCube);
print("cube " + i + " removed");
}
}
}
private void SetRandCubeState(GameObject cube)
{
float randScale = Random.Range(0.8f, 1.0f);
Vector3 randomScaler = new Vector3(randScale, randScale, randScale);
cube.transform.localScale = randomScaler;
Vector3 randomPosition = new Vector3(Random.Range(origin.x - 1.0f, origin.x + 1.0f), Random.Range(origin.y - 1.0f, origin.y + 1.0f), Random.Range(-1.0f,1.0f));
cube.transform.localPosition = randomPosition;
Renderer render = cube.GetComponent<Renderer>();
render.material.SetColor("_Color", Random.ColorHSV());
}
private void AddNewCube()
{
GameObject newCube = GameObject.CreatePrimitive(PrimitiveType.Cube);
cubeList.Add(newCube);
SetRandCubeState(newCube);
}
private void RemoveCube(GameObject oldCube)
{
Destroy(oldCube);
}
}
Note that the tasks says nothing about a random initial scale. It further says the cubes should destroy after reaching < 10% of the original scale. So if you use random initial scale then you would also need to store the original scale somewhere.
It also says the cube should shrink 10% each frame not based on seconds. So how I understand it the first frame it goes 1 -> 0.9, the second frame it goes 0.9 -> 0.81 etc so either the task is bad formulated or you are doing something else ^^
Lerp takes a factor between 0 and 1 and linear interpolates the start and end value accordingly. You are using it wrong ^^
Given an e.g. 60 frames per second you are all the time interpolating between the current scale and 0.1 with a (more or less constant) factor of about 0.0017 .. your cubes will barely scale down and get slower and slower the closer you get to the target. Definitely not scaling down 10 % per frame ;) You are also lerping against the value 0.1 instead of 0. So your threshold might never be reached at all or only very very slow.
Also the cubes should be named accordingly to the order they are created in -> you shouldn't iterate through the list and rename them.
// Store existing cube references together with their minimum scale
// before getting destroyed
private Dictionary<Transform, float> cubes = new Dictionary<Transform, float>();
// Count created objects for correct naming
private int cubeSpawnCounter;
void Update()
{
AddNewCube();
foreach(var kvp in cubes)
{
// Scale down 10% per frame
kvp.Key.localScale *= 0.9f;
// Check if reached below threshold of 10% of original scale
if (kvp.Key.localScale.x <= kvp.Value)
{
RemoveCube(kvp.Value);
}
}
}
private void SetRandCubeState(GameObject cube)
{
// Multiply the vector that saves you a lot of typing ;)
cube.transform.localScale = Vector3.one * Random.Range(0.8f, 1.0f);
// storing the origin is redundant
cube.localPosition = new Vector3(Random.Range(-1.0f, 1.0f), Random.Range(-1.0f, 1.0f), Random.Range(-1.0f,1.0f));
// Only go via the Shader properties if really necessary
var render = cube.GetComponent<Renderer>();
render.material.color = Random.ColorHSV());
}
private void AddNewCube()
{
var newCube = GameObject.CreatePrimitive(PrimitiveType.Cube);
SetRandCubeState(newCube);
// Count the created cubes and set the name ONCE
cubeSpawnCounter++;
newCube.name = $"Cube{cubeSpawnCounter}";
// store the destroy threshold for each cube (10% of original scale)
cubes[newCube.transform] = randomScale * 0.1f;
}
private void RemoveCube(Transform oldCube)
{
// Remove from the Dictionary
cubes.RemoveKey(oldCube);
print($"{oldCube.name} removed");
Destroy(oldCube.gameObject);
}
It appears that the value never reaches .1 due to the lerp. You could try adding a float to the conditional check (I used a large number, but using .01f could work depending on your use case)
if(cubeList[i].transform.localScale.x <= targetScale + .3f)
{
cubeList.RemoveAt(i);
RemoveCube(newCube);
print("cube " + i + " removed");
}
I currently learn unity3d and start to create an endless game, and I want to add score by a distance that player has traveled on + x axis, so I want to calculate only how far the player traveled on + x axis.. how to calculate it?
this is my scripts
public class Score : MonoBehaviour
public float distance;
public Transform player;
private float score = 0f;
public Text scoreText;
void Awake()
{
distance = Vector3.Distance(player.position, transform.position);
}
void Update()
{
if (distance > player.transform.position.x)
{
score = distance ++;
scoreText.text = distance.ToString();
}
}
}
You can have something like this to get you started. I set it to 0.1 distance to get +1 score but that number can be anything of course.
private const float DISTANCE_REQUIRED_FOR_1_POINT = 0.1f;
private Vector3 previousPlayerPosition;
private float distanceSinceLastScore;
public Transform player;
private float score = 0f;
public Text scoreText;
void Awake()
{
//At the start save the start position.
previousPlayerPosition = player.position;
}
void Update()
{
//Calculate the distance travvelled since last update.
distanceSinceLastScore += player.position.x - previousPlayerPosition.x;
//Set the previous position the current.
previousPlayerPosition = player.position;
int scoreToAdd = 0;
while (distanceSinceLastScore > DISTANCE_REQUIRED_FOR_1_POINT)
{
//Calculate how many points to add in this update. (Likely always 1 or none)
++scoreToAdd;
distanceSinceLastScore -= DISTANCE_REQUIRED_FOR_1_POINT;
}
if (scoreToAdd > 0)
{
score += scoreToAdd;//Add to the total score.
scoreText.text = score.ToString();//refresh the text display.
}
}
To be honest, you gave very little information about the game. But I will try to come up with some possible scenarios and solutions.
First of all, you stated that this is an endless game, then I don't know what distance are you calculating between the player and a point. So, your code didn't make much sense to me, please explain if I'm missing something.
Now, you said you want to calculate the score based on the distance that the player has traveled on + x-axis. So, take a look at this code:
float previousPlayerX;
public Transform player;
private float score;
void Awake()
{
previousPlayerX= player.position.x;
score = 0;
}
void Update()
{
if (previousPlayerX < player.position.x)
{
score += player.position.x - previousPlayerX;
previousPlayerX = player.transform.position.x
}
}
This code will increase the score as long as the player is running to +x. If the player stops or even turns back, the score will not be updated. Also, note that if your starting position is 0, then you don't need to subtract the two positions to add the score, you can simply go ahead and say score = player.position.x and it should give you the same result. Furthermore, I would implement this code in a coroutine with a little bit of wait time to avoid possible bugs maybe.
Now, let's think about another scenario. I don't think the character doesn't go to infinity in endless run games, generally, the character object is stable and you kinda make that illusion of endless flow. So, let's say your character's position is not changing actually, but in the game, it seems like your character is running non-stop. In such a case, you may want to increase the score based on the time passing.
private float score;
void Awake()
{
score = 0;
StartCoroutine(UpdateScore());
}
IEnumerator UpdateScore()
{
while(true)
{
score += 1;
yield return new WaitForSeconds(1);
}
}
As you can see in the second code, I'm just assuming that my character runs non-stop and I'm just increasing the score by 1 every second. You can do it in every half a second, you can increase the score by multiplying with a speed value or you can put an if statement and don't increase the score if the character is stunned for example. So, it's all up to you.
I hope that answer helps and gives you a general idea. Let me know in the comments if you have any other questions related to this.
I wish to implement a way to create a hoverbike.
my current code to hover is
readonly float yForce = 80;
Physics.Raycast(hoverbike.transform.position, Vector3.down, out hit);
Debug.Log(hit.distance);
if (hit.distance < 10 && hit.distance > 0)
{
if (hoverbike.velocity.y < 0.1)
{
hoverbike.AddForce(0, yForce, 0, ForceMode.Acceleration);
Debug.Log("applying force!");
}
}
This works, but not well, the vehicle bounces up and down. I also tried to subtract the exact same force as the bike's y velocity, but the vehicle slowly drifted down, and did not go up to my desired height of 10 units from the ground. How can I achieve this?
Simply counteracting its current velocity is easy, but how do I make it float back up to the desired height?
It is much easier to simply turn off gravity than to constantly be fighting against it; this frequent readjustment is likely the cause of your bounciness. Upon bike activation you can take the object's y velocity calculations fully into your own hands as so:
public class Bike : MonoBehaviour
{
private Rigidbody hoverbike;
private bool isBikeActive = false;
[SerializeField] private float verticalSpeedMultiplier = 1f;
[SerializeField] private float hoverHeight = 10f;
[SerializeField] private float hoverTolerance = 0.5f;
[SerializeField] private float maximumVerticalVelocity = 10f;
private void Awake()
{
hoverbike = GetComponent<Rigidbody>();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space)) { ToggleBike(); }
if (isBikeActive)
{
Physics.Raycast(hoverbike.transform.position, Vector3.down, out RaycastHit hit);
Vector3 modifiedVelocity = hoverbike.velocity;
if ((hit.distance > hoverHeight - hoverTolerance) && (hit.distance < hoverHeight + hoverTolerance))
{
modifiedVelocity.y = 0f;
}
else
{
modifiedVelocity.y = -(hit.distance - hoverHeight) * verticalSpeedMultiplier;
modifiedVelocity.y = Mathf.Clamp(modifiedVelocity.y, -maximumVerticalVelocity, maximumVerticalVelocity);
}
Debug.Log($"Distance from ground: {hit.distance}, Bike Velocity.y: {modifiedVelocity}");
hoverbike.velocity = modifiedVelocity;
}
}
private void ToggleBike()
{
isBikeActive = !isBikeActive;
hoverbike.useGravity = !isBikeActive;
}
}
Your bike will now always try to move towards the point hoverHeight units above the object below it, until it is within hoverTolerance from that point. It will also move much more smoothly towards this point, moving faster the further away it is from the intended height.
If you wish for the bike to still bob up and down a little, this can be achieved by modifying the hoverHeight slowly over time, perhaps through use of a Sine function.
Hovering is (essentially) a visual effect
Make the collider extend below the vehicle so that when it rests on the ground the bike appears to be hovering at the desired height. The physics engine only does physics. It doesn't care about what those colliders are, it just wants them to behave in a physics-y way, and if that means falling until it reaches the ground, then let them fall until they reach the ground. Take advantage of the physics engine instead of going around it and then trying to solve the bugs created by going around the physics engine.
First, apply your dampening force scaled by the downward velocity, then apply an additional force scaled by how far it needs to travel back upwards. Keep track of how much force/acceleration you apply through this process and cap that amount at some constant.
readonly float yForce = 80f; // requires tuning
readonly float dampenFactor = 0.8f; // requires tuning
readonly float offsetFactor = 0.5f; // requires tuning
readonly float targetHeight = 10f
Physics.Raycast(hoverbike.transform.position, Vector3.down, out hit);
Debug.Log(hit.distance);
if (hit.distance < targetHeight && hit.distance > 0)
{
float availableForce = yForce;
// cancel out downward velocity
if (hoverbike.velocity.y < 0)
{
// Cap out upward force based on yForce
float cappedDampenForce = Mathf.Min(dampenFactor * -hoverbike.velocity.y,
availableForce);
// How much force is available for the offset?
availableForce -= cappedDampenForce;
hoverbike.AddForce(Vector3.up * cappedDampenForce, ForceMode.Acceleration);
Debug.Log("applied dampening force");
}
// Find upward force scaled by distance left to target height, and cap that amount
float cappedOffsetForce = Mathf.Min(offsetFactor * (targetHeight - hit.distance),
availableForce);
hoverbike.AddForce(Vector3.up * cappedOffsetForce, ForceMode.Acceleration);
Debug.Log("applied offset force");
}
Trying to create simple endless moving platform with 3 cubes of scale 70 on z(Player will not move forward, will just move left/right). The RepositionPlatform script is attached to each platform/cube which is responsible for movement and checks the z position of each platform and if it is <= -100.0f, then position is changed to (0,0,200.0f).
Problem is sometimes there is a little gap between the platforms(cubes) or there is a little overlap which I don't want.
Platforms should be placed one after each other without any gap or overlap!!!
Can anyone help find the issue looking at the script or suggest any other better way ?
The script below is attached to 3 platform game objects!!!
public class RepositionPlatform : MonoBehaviour
{
private GameObject platformGO;
[SerializeField]
private float speed;
// Start is called before the first frame update
void Start()
{
platformGO = this.gameObject;
Debug.Log("In RepositionPlatform Start method - "+ platformGO.name);
}
// Update is called once per frame
void Update()
{
Debug.Log("In RepositionPlatform Update Method- " + platformGO.name);
platformGO.transform.Translate(Vector3.back * Time.deltaTime * speed);
Transform platformTransform = platformGO.transform;
if(platformTransform.position.z <= -100.0f)
{
platformTransform.position = new Vector3(0,0,200.0f);
}
}
}
Probably because speed is a floating point value. You should read up on them if you haven't already.
Long story short, you aren't accounting for how far below -100 the value might have gone, you're just resetting it.
If you translate it instead, you will preserve any extra distance beyond -100 that the transform might have gone.
Try this instead:
If (transform.position.z < -100){
transform.Translate(new Vector3(0,0,200));
}
Edit
Should be Z value, not X
I'm trying to simulate swimming in Unity (using c#) by actually having the movements of the object create drag forces which then propel the object through the liquid.
to do this, I'm using the formula
F = -½ * C * d * velocity squared * A
where C is a coefficient of drag, d is the density of liquid, and A is the object's surface area that faces the direction of motion. A is calculated by projecting the 3D object onto a 2D plane perpendicular to the velocity vector.
Here's an image explaining A:
https://www.real-world-physics-problems.com/images/drag_force_2.png
Now I suspect Unity has a built in way to do this type of projection (since it does that every time there's a camera in the scene).
My question is:
How do I do this? Searches have not helped me with this (unless you're trying to do it with a camera)
Is there a built in function in Unity?
Is this computationally expensive? I am going to be doing this individual for possibly thousands of objects at a time.
I DO NOT need it to be very accurate. I'm just trying to make it a bit realistic, so I want objects with much bigger A to have more drag than ones with much lower A. Slight differences are inconsequential. The objects themselves won't be super complex, but some may have very different areas depending on orientation. So like a cone, for example, could change quite a bit depending on which direction it's moving. I could approximate the A with a simple shape if needed like ellipsoid or rectangle.
If it is computationally expensive, I read a journal article that used a cool way to approximate it. He created a grid of points (which he called voxels) within the objects spaced evenly, which effectively split the object into equal-sized spheres (which always have a cross-sectional surface area of a circle (easy to calculate). Then he calculated the drag force on each of these spheres and added them up to find the total drag (see images).
Images from THESIS REPORT ON: Real-time Physics-based Animation of a
Humanoid Swimmer, Jurgis Pamerneckas, 2014
link https://dspace.library.uu.nl/bitstream/handle/1874/298577/JP-PhysBAnimHumanSwim.pdf?sequence=2
This successfully estimated drag for him. But I see one problem, that the "voxels" that are deep in object are still contributing to drag, where only the ones near the leading edge should be contributing.
So, I thought of a possibility where I could project just the voxel points onto the 2Dplane (perpendicular to velocity) and then find a bounding shape or something, and approximate it that way. I suspect projecting a few points would be faster than projecting a whole 3d object.
this raises a few more questions:
Does this seem like a better method?
How would I create voxels in Unity?
Is it computationally faster?
Any better ideas?
Another thought I had was to do raycasting of some sort, though I can't think of how to do that, perhaps a grid of raycasts parallel to the velocity vector? and just count how many hit to approximate area?
UPDATE
I managed to implement basic drag force by manually typing in the value for A, now I need to approximate A in some way. Even with manual typing, it works surprisingly well for very basic "swimmers". In the image below, the swimmer correctly spins to the right since his left arm is bigger (I gave it double the value for A).
UPDATE 2
Based on #Pierre's comments, I tried computing A for the overall shape using the object's vertices (and also by selecting a few points on the vertices), projecting them onto a plane, and calculating the overall area of the resulting polygon. However, This only calculated the overall drag force on the object. It didn't calculate any rotational drag caused by certain parts of the object moving faster than others. For example, think of a baseball bat swing, the farthest part of the bat will be creating more drag since it's swinging faster than the handle.
This made me go back to the "voxel" idea, since I could calculate local drag sampled at several parts of the object.
I'm playing around with this idea, estimating the voxel's surface area by a circle. But still having a few issues making this estimate relatively accurate. Despite it being inaccurate, this seems to work quite well.
First, I'm using recasts to determine if the voxel can "see" in the direction of the velocity to determine if it's on the leading face of the object. If so, then I take the voxel's local (circular) surface area, and multiplying this by the dot product of the circle's normal and the local velocity vector. This scales the area based on how much it's actually facing the direction of motion.
The inaccuracies so far are due to the circles not actually estimating the local surface area very well, especially for weirdly elongated objects. The further vertices are from each other then the worse the estimation becomes. Any help in this department would be appreciated.
Also, I need to optimize this computationally. Right now, doing it with every vertex is proving to be fairly expensive. I'll keep updating as I progress, and any input would be very helpful! I'll post some code soon once I get a bit farther.
UPDATE 3
I did a fairly accurate implementation using voxels which I manually placed on the surface of the object, and manually estimated the local A when facing that voxel. I then used the dot product to estimate how much of that Area was facing the direction of motion. This worked very well. But the problem then was that even voxels that weren't on the leading edge of the object were contributing to drag. So I used Physics.Raycasts to pop a small distance away from the voxel in the direction of velocity, and then raycast back at the voxel. If this raycast hit the collider of the actual object (not the voxel) it meant it was on the leading edge. This worked fantastically and yielded surprisingly accurate natural looking behaviour of drag. Strangely shaped objects would eventually rotate to minimize drag just like you'd expect. However, as soon as I increased the resolution of voxels and/or added a few more objects into the scene, my frame rate dropped to nearly 3fps. The profiler showed that the brunt of the calculations were due to the raycasting step. I've tried to think of other ways to determine if the voxels are on the leading edge, so far to no avail.
So TLDR, I simulated drag really well, but not in a computationally fast manner.
I never figured out a way to speed up the calculations, but the simulation works great as long as the voxel count is low.
The simulation calculates drag based on the velocity of each voxel. It checks whether it's on the leading edge of the object, and if so applies its drag force.
The code is probably a bit difficult to follow but should at least get you started if you want to try it out. Let me know if you have any questions or need clarifications.
This code is a slightly cleaned up version from my Update#3 above.
In action:
At start of simulation (object moving in straight line towards bottom right of screen)
you can see the force arrows added for visualization and the circles representing the voxels. The force is correctly proportional to the surface area the voxels roughly represent. and only leading edges of the shapes are contributing drag
As the simulation continues, the shape correctly rotates into the most aerodynamic position because of the drag, and the rear sections stop contributing drag.
Drag Enabled Shape Class
this is dragged on main objet (rigidbody) to enable drag. You can either have it create voxels in a spread around a sphere shape. Or load in your own custom Voxels which are game objects with the Voxel Script attached, and are children of this object.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
[RequireComponent (typeof(Rigidbody))]
public class DragEnabledShape : MonoBehaviour {
const float EPSILON = 0.0001f;
public Voxel voxelPrefab;
public float C = 1f;
public float d = 0.5f;
public int resolutionFactor = 2;
public float maxDistanceFromCenter = 10f;
public bool displayDragVisualization = false;
public float forceVisualizationMultiplier = 1f;
public bool displayVoxels = false;
public bool loadCustomVoxels = false;
List<Voxel> voxels;
Rigidbody rb;
// Use this for initialization
void Awake () {
voxels = new List<Voxel> ();
rb = GetComponent<Rigidbody> ();
}
void OnEnable () {
if (loadCustomVoxels) {
var customVoxels = GetComponentsInChildren<Voxel> ();
voxels.AddRange (customVoxels);
if (displayDragVisualization) {
foreach (Voxel voxel in customVoxels) {
voxel.DisplayDrag (forceVisualizationMultiplier);
}
}
if (displayVoxels) {
foreach (Voxel voxel in customVoxels) {
voxel.Display ();
}
}
}
else {
foreach (Transform child in GetComponentsInChildren<Transform> ()) {
if (child.GetComponent<Collider> ()) {
//print ("creating voxels of " + child.gameObject.name);
CreateSurfaceVoxels (child);
}
}
}
}
void CreateSurfaceVoxels (Transform body) {
List<Vector3> directionList = new List<Vector3> ();
for (float i = -1; i <= 1 + EPSILON; i += 2f / resolutionFactor) {
for (float j = -1; j <= 1 + EPSILON; j += 2f / resolutionFactor) {
for (float k = -1; k <= 1 + EPSILON; k += 2f / resolutionFactor) {
Vector3 v = new Vector3 (i, j, k);
directionList.Add (v);
}
}
}
//float runningTotalVoxelArea = 0;
foreach (Vector3 direction in directionList) {
Ray upRay = new Ray (body.position, direction).Reverse (maxDistanceFromCenter);
RaycastHit[] hits = Physics.RaycastAll (upRay, maxDistanceFromCenter);
if (hits.Length > 0) {
//print ("Aiming for " + body.gameObject.name + "and hit count: " + hits.Length);
foreach (RaycastHit hit in hits) {
if (hit.collider == body.GetComponent<Collider> ()) {
//if (GetComponentsInParent<Transform> ().Contains (hit.transform)) {
//print ("hit " + body.gameObject.name);
GameObject empty = new GameObject ();
empty.name = "Voxels";
empty.transform.parent = body;
empty.transform.localPosition = Vector3.zero;
GameObject newVoxelObject = Instantiate (voxelPrefab.gameObject, empty.transform);
Voxel newVoxel = newVoxelObject.GetComponent<Voxel> ();
voxels.Add (newVoxel);
newVoxel.transform.position = hit.point;
newVoxel.transform.rotation = Quaternion.LookRotation (hit.normal);
newVoxel.DetermineTotalSurfaceArea (hit.distance - maxDistanceFromCenter, resolutionFactor);
newVoxel.attachedToCollider = body.GetComponent<Collider> ();
if (displayDragVisualization) {
newVoxel.DisplayDrag (forceVisualizationMultiplier);
}
if (displayVoxels) {
newVoxel.Display ();
}
//runningTotalVoxelArea += vox.TotalSurfaceArea;
//newVoxel.GetComponent<FixedJoint> ().connectedBody = shape.GetComponent<Rigidbody> ();
}
else {
//print ("missed " + body.gameObject.name + "but hit " + hit.transform.gameObject.name);
}
}
}
}
}
void FixedUpdate () {
foreach (Voxel voxel in voxels) {
rb.AddForceAtPosition (voxel.GetDrag (), voxel.transform.position);
}
}
}
Voxel class
This script is attached to small gameObjects placed around a shape. They represent the locations at which drag is computed. SO for complex shapes these should be at any extremities, and should be fairly spread out over the object. The voxel object's rigid body's mass should approximate the portion of the object this voxel represents.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Voxel : MonoBehaviour {
Vector3 velocity;
public Collider attachedToCollider;
Vector3 drag;
public Vector3 Drag {
get {
return drag;
}
}
float dragMagnitude;
public float DragMagnitude {
get {
return dragMagnitude;
}
}
bool leadingEdge;
public bool LeadingEdge {
get {
return leadingEdge;
}
}
bool firstUpdate = true;
public float localSurfaceArea;
Vector3 prevPos;
public VoxelForceVisualizer forceVisualizer;
public VoxelVisualizer voxelVisualizer;
const float AREA_COEFFICIENT = 1.1f;
const float EPSILON = 0.001f;
const float FAR_DISTANCE = 5f;
const float MAX_FORCE = 100f;
public void DetermineTotalSurfaceArea (float distanceFromCenter, float resolution) {
float theta = (Mathf.PI / 4) / resolution;
float localR = distanceFromCenter * Mathf.Tan (theta) * AREA_COEFFICIENT;// * (resolution / 0.01f);
localSurfaceArea = Mathf.PI * localR * localR;
}
bool IsVisibleFromPlane () {
if (attachedToCollider == null) {
throw new MissingReferenceException ("attached to collider not set");
}
bool visibleFromPlane = false;
//checks if this is leading edge of this part of object.
Ray justOutsideSurface = new Ray (this.transform.position, velocity).Reverse (EPSILON);
RaycastHit hit;
if (Physics.Raycast (justOutsideSurface, out hit, EPSILON * 2f)) {
if (hit.collider == attachedToCollider) {
//checks if other parts of this object are in front, blocking airflow.
//Ray wayOutsideSurface = new Ray (this.transform.position, velocity).Reverse (FAR_DISTANCE);
//RaycastHit firstHit;
//if (Physics.Raycast (wayOutsideSurface, out firstHit, FAR_DISTANCE * 2f)) {
//if (firstHit.collider == attachedToCollider) {
visibleFromPlane = true;
//}
//}
}
}
//}
leadingEdge = visibleFromPlane;
return visibleFromPlane;
}
void FixedUpdate () {
if (firstUpdate) {
prevPos = transform.position;
firstUpdate = false;
}
velocity = (transform.position - prevPos) / Time.deltaTime;
prevPos = transform.position;
}
public Vector3 GetDrag () {
if (IsVisibleFromPlane ()) {
float alignment = Vector3.Dot (velocity, this.transform.forward);
float A = alignment * localSurfaceArea;
dragMagnitude = DragForce.Calculate (velocity.sqrMagnitude, A);
//This clamp is necessary for imperfections in velocity calculation, especially with joint limits!
//dragMagnitude = Mathf.Clamp (dragMagnitude, 0f, MAX_FORCE);
drag = -velocity * dragMagnitude;
}
return drag;
}
public void Display () {
voxelVisualizer.gameObject.SetActive (true);
}
public void TurnOffDisplay () {
voxelVisualizer.gameObject.SetActive (false);
}
public void DisplayDrag (float forceMultiplier) {
forceVisualizer.gameObject.SetActive (true);
forceVisualizer.multiplier = forceMultiplier;
}
public void TurnOffDragDisplay () {
forceVisualizer.gameObject.SetActive (false);
}
}
VoxelForceVisualizer
This is a attached to prefab of a thin arrow that I put as a child of the voxels to allow force arrows to be drawn during debugging the drag force.
using UnityEngine;
public class VoxelForceVisualizer : MonoBehaviour {
const float TINY_NUMBER = 0.00000001f;
public Voxel voxel;
public float drag;
public float multiplier;
void Start () {
voxel = this.GetComponentInParent<Voxel> ();
}
// Update is called once per frame
void Update () {
Vector3 rescale;
if (voxel.LeadingEdge && voxel.Drag != Vector3.zero) {
this.transform.rotation = Quaternion.LookRotation (voxel.Drag);
rescale = new Vector3 (1f, 1f, voxel.DragMagnitude * multiplier);
}
else {
rescale = Vector3.zero;
}
this.transform.localScale = rescale;
drag = voxel.DragMagnitude;
}
}
VoxelVisualizer
this is attached to a small sphere object as a child of the voxel empty. It's just to see where the voxels are, and let the above scripts show/hide the voxels without disabling the drag force calculations.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class VoxelVisualizer : MonoBehaviour {
}
DragForce
This calculates the drag force
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class DragForce {
const float EPSILON = 0.000001f;
public static float Calculate (float coefficient, float density, float vsq, float A) {
float f = coefficient * density * vsq * A;
return f;
}
public static float Calculate (float vsq, float A) {
return Calculate (1f, 1f, vsq, A);
}
}