I've just started learning Unity and encountered some problems.
Here I have a prefab object ParticleAlly with a parent ParticleAllies. The parent object rotates itself, and ParticleAlly rotates too, like a planet and satellites.
Besides, I also want to regenerate each ParticleAlly as soon as it is out of the camera. Then they are in different orbits with only the same angular velocity but in different positions. When it gets into and then get out of the camera, do that again.
The problem is, some ParticleAlly appeared in the middle of the camera directly after they are regenerated. I tried to let them not rendered until they get into the camera from outside. But it seems that SpriteRenderer.isvisible is true only when SpriteRenderer.enabled is true, so I cannot get know when to render them again. Then I tried to judge by position, but I don't know why it doesn't work at all.
I'm totally confused and wasted a whole morning on that. Sorry for my inaccurate description and term using.
It would be appreciated if you could provide me a solution and tell me something about Update(), the rendering, the position/localposition in rotation etc.
// in ParticleAlly
void Update() {
//Debug.Log(trans.localPosition.y);
if(trans.position.y <= globalSettings.RotationLocationY) {
Debug.Log("Under");
if(!isRefreshed) {
refresh();
}
sRender.enabled = true;
}
else {
if(sRender.isVisible) {
isRefreshed = false;
}
}
}
/// <summary>
/// Regenerate the particle
/// </summary>
void refresh() {
isRefreshed = true;
float height = Random.value * 3;
trans.localPosition = (new Vector3(0f, globalSettings.RotationLocationY - height, 0f));
//trans.RotateAround(trans.parent.localPosition, new Vector3(0f, 0f, 1f), globalSettings.getDegree() * Random.value);
trans.localRotation = Quaternion.Euler(new Vector3(0f, 0f, globalSettings.getDegree() * Random.value));
//sRender.enabled = false;
}
// in ParticleAllies
void Update() {
trans.localRotation = Quaternion.Euler(new Vector3(0f, 0f, globalSettings.getDegree()));
}
It's my first time asking here, the tabs are somehow broken in the code?
The issue with Renderer.isVisible is that
As you already noted it only can be true if the renderer is enabled
It can stay true even if the object is not visible itself but has some visual influence on the rendering (such as e.g. shadows)
In case the position would be enough you could simply use e.g.
public static class Extensions
{
pulic static bool IsInFrustum(this Camera camera, Vector3 worldPoint)
{
var screenPos = camera.WoldToscreenPoint(worldPoint);
return screenPos.z >= camera.nearClipPlane
&& screenPos.z <= camera.farClipPlane
&& screenPos.x >= 0
&& screenPos.x <= Screen.width
&& screenPos.y >= 0
&& screenPos.y <= Screen.height;
}
public static bool sInFrustum(this Vector3 worldPoint, Camera camera)
{
return camera.IsInFrustum(worldPoint);
}
}
and would do e.g.
someRenderer.enabled = yourCamera.IsInFrustum(someRenderer.transform.position);
or also
someRenderer.enabled = someRenderer.transform.position.IsInFrustum(yourCamera);
If a single position is not enough, you can get the camera frustum planes using GeometryUtility.CalculateFrustumPlanes and then check if some Bounds e.g. the Renderer.bounds fall within those using GeometryUtility.TestPlanesAABB.
Bounds are not completely accurate always of course but are probably the best approximation you can get.
public static class Extensions
{
public static bool IsInFrustum(this Renderer renderer, Camera camera)
{
var planes = GeometryUtility.CalculateFrustumPlanes(camera);
return GeometryUtility.TestPlanesAABB(planes, renderer.bounds);
}
public static bool IsInFrustum(this Camera camera, Renderer renderer)
{
return renderer.IsVisibleFrom(camera);
}
}
You can now either use on your camera e.g.
someRenderer.enabled = yourCamera.IsInFrustum(someRenderer);
or also
someRenderer.enabled = someRenderer.IsInFrustum(yourCamera);
Related
I can't quite understand how Physics.OverlapSphere works at this point. As shown below in the code, if the player enters the overlapsphere then it should return true, but I keep getting false when the player enters the sphere.
This is the script that call the method
void Update()
{
float distance = Vector3.Distance(target.position, transform.position);
if (distance <= lookRadius)
{
agent.SetDestination(target.position);
if (distance <= agent.stoppingDistance)
{
CharacterStats targetStats = target.GetComponent<CharacterStats>();
Debug.Log(enemy.onPlayerEnter());
if (targetStats != null && enemy.onPlayerEnter())
{//if player exist and the player enters the attack area
Combat.Attack(targetStats);
}
FaceTarget();
}
}
animator.SetFloat("speed", agent.velocity.magnitude);
this is the script of the method:
public bool onPlayerEnter()
{
Collider[] hitColliders = Physics.OverlapSphere(interactionTransform.transform.localPosition, radius);
//Debug.Log(interactionTransform.transform.localPosition);
for(int i = 0; i < hitColliders.Length; i++)
{
if(LayerMask.LayerToName(hitColliders[i].gameObject.layer) == "Player")
{
Debug.Log("Player enter");
return true;
}
}
return false;
}
//visualize the overlapsphere
private void OnDrawGizmosSelected()
{
if (interactionTransform == null) interactionTransform = transform;
Gizmos.color = Color.cyan;
Gizmos.DrawWireSphere(interactionTransform.position, radius);
}
collider with monster
[Collide with player[][1]][2]
https://i.stack.imgur.com/8Fgh0.png
https://i.stack.imgur.com/jnubp.png
For unknown reason, I found that the overlapsphere works at certain position in the map, but the rest of the position does not work at all. I think this probably is a bug in Unity.
weird position
Your sphere gizmo and actual sphere are different: for drawing gizmo you use transform.position, but for overlap sphere - transform.transform.localPosition (no need to double like transform.transform, and position and localPosition can be very different). I think, this is the answer.
Its easy to get mess with LayerMask, its a good approach, but for testing you better use tag, or gameObject.InstanceID, or even gameObject.name. But its minor, in general it looks like you deal with layers right.
Be sure that your agent.StoppingDistance not set too small
It`s a bad practice to use GetComponent every frame, as you do for get targetStats.
I'm making a small sandbox game for kids where they get to spawn some 2d objects, drag them all over the screen and throw them against each other.
I tried to avoid using AddForce() because the algorithms don't seem to fit my problem (if the kid drags one object in multiple directions, the object trajectory will be quite messy and unrealistic when he releases it).
What I went with instead is to spawn an empty object when you click on an object and attach the two with a HingeJoint2D. It works well when it comes to dragging the object across the screen, but the object just fall flat onto the ground when you release it (the empty object with HingeJoint gets deleted when you release it).
// Update is called once per frame
void Update()
{
// MousePosition
Vector3 mousePos = Input.mousePosition;
mousePos.z = 10;
Vector3 screenPos = Camera.main.ScreenToWorldPoint(mousePos);
if (Input.GetMouseButtonDown(0) && !objectInHand)
{
RaycastHit2D hit = Physics2D.Raycast(screenPos, Vector2.zero);
if (hit && hit.collider.tag == "DraggableObject")
{
emptyObject = new GameObject("HingeHolder");
emptyObject.transform.position = screenPos;
emptyObject.AddComponent<HingeJoint2D>();
objectInHand = hit.collider.gameObject;
emptyObject.GetComponent<Rigidbody2D>().isKinematic = true;
emptyObject.GetComponent<HingeJoint2D>().connectedBody = objectInHand.GetComponent<Rigidbody2D>();
emptyObject.GetComponent<HingeJoint2D>().autoConfigureConnectedAnchor = false;
}
}
else if(Input.GetMouseButton(0) && objetInHand)
{
emptyObject.transform.position = screenPos;
}
else if (Input.GetMouseButtonUp(0) && objetInHand)
{
emptyObject.GetComponent<Rigidbody2D>().isKinematic = false;
emptyObject.GetComponent<HingeJoint2D>().connectedBody = null;
Destroy(emptyObject);
objectInHand = null;
}
}
I would like my object to keep its force on release and to work like AddForce(), but I just can't get it to work properly this way. Am I doing this wrong ? Is this possible without using AddForce() ?
Thanks in advance
Another way to do what you want is by adding a script to each box:
using System.Collections.Generic;
public class Draggable: MonoBehaviour, IDragHandler {
public float z = 1f;
public void OnDrag(PointerEventData data)
{
Vector3 mousePosition = Input.mousePosition;
mousePosition.z = z;
transform.position = Camera.main.ScreenToWorldPoint(mousePosition);
}
}
this solution prevents us from needing to use the update (improving performance) and you could adjust the mass, gravity, z and other factors, so that your game.
it would also be good if he had physical limits when his stage is over
Looking for a bit of help to see how I can make this algorithm a little better. So I have some code for a pan tool that moves the camera based on the user dragging the mouse to move the camera around. However it is really jumpy unless it is done in really short drags. I.e. the objects in the scene appear to vibrate, which is more likely to be the camera and canvas 'vibrating' rather than the objects themselves.
The code works by toggling a dragging boolean using the SystemEvent methods OnPointerDown and OnPointerUp and assigns a MouseStart Vector3 in world coordinates and also a CameraStart Vector3.
public void OnPointerDown(PointerEventData EventData)
{
if (!dragging)
{
dragging = true;
MouseStart = new Vector3(Camera.main.ScreenToWorldPoint(Input.mousePosition).x, Camera.main.ScreenToWorldPoint(Input.mousePosition).y, 0f);
CameraStart = TheCamera.transform.position;
}
}
public void OnPointerUp(PointerEventData EventData)
{
if (dragging)
{
dragging = false;
}
}
Then in the update loop while the dragging variable is true, xChange and yChange float values are determined based on the current mouse position compared to the original mouse position, the camera position is then adjusted according to these. My thought process was that because it is relative to a fixed MouseStart (because it is only changed in the single frame where the pointer is clicked and dragging = 0) that if I were to drag and then say keep the mouse still, there would be no change in coordinates as it'd be repeatedly putting the Camera in the same position. The full code looks like this:
private bool dragging;
private string CurrentTool;
private ButtonController[] DrawingTools;
public Camera TheCamera;
public Vector3 MouseStart;
public Vector3 CameraStart;
public float sensitivity;
// Use this for initialization
void Start () {
TheCamera = FindObjectOfType<Camera>();
DrawingTools = FindObjectsOfType<ButtonController>();
}
// Update is called once per frame
void Update () {
for (int i = 0; i < DrawingTools.Length; i++)
{
if (DrawingTools[i].Pressed)
{
CurrentTool = DrawingTools[i].gameObject.name;
}
}
if (dragging && CurrentTool == "PanTool Button")
{
float xChange;
float yChange;
Vector3 MousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
if (MousePosition.x > MouseStart.x)
{
xChange = -Mathf.Abs(MousePosition.x - MouseStart.x);
}
else
{
xChange = Mathf.Abs(MousePosition.x - MouseStart.x);
}
if (MousePosition.y > MouseStart.y)
{
yChange = -Mathf.Abs(MousePosition.y - MouseStart.y);
}
else
{
yChange = Mathf.Abs(MousePosition.y - MouseStart.y);
}
TheCamera.transform.position = new Vector3(CameraStart.x + xChange*sensitivity, CameraStart.y + yChange*sensitivity, CameraStart.z);
}
}
public void OnPointerDown(PointerEventData EventData)
{
if (!dragging)
{
dragging = true;
MouseStart = new Vector3(Camera.main.ScreenToWorldPoint(Input.mousePosition).x, Camera.main.ScreenToWorldPoint(Input.mousePosition).y, 0f);
CameraStart = TheCamera.transform.position;
}
}
public void OnPointerUp(PointerEventData EventData)
{
if (dragging)
{
dragging = false;
}
}
Any help is appreciated, thanks.
EDIT: Just to clarify this is a 2d environment
This is happening because the Camera from which you are determining the world position of the mouse is being updated every frame according to the world position of the mouse, which causes a feedback loop (and therefore noise + jitter).
You can reduce noise from the feedback loop by smoothing the Camera's movement over time (effectively a low pass), or try to remove the feedback loop entirely by altering your calculations so the camera position and target position (mouse) don't rely on each other - although I'm not sure how to go about that if it's actually possible for your intent.
Check out Vector3.SmoothDamp.
Gradually changes a vector towards a desired goal over time.
The vector is smoothed by some spring-damper like function, which will
never overshoot. The most common use is for smoothing a follow camera.
I have been working on programming a graph in Unity that rotates based on head movement. I have been having multiple issues with the rotation aspect of it.
A sample of the Autowalk class, which I am using to find the angle that the graph needs to rotate based on where the user is facing:
public class AutoWalk : MonoBehaviour {
//VR head
private Transform vrHead;
//angular displacement from normal
public float xAng, yAng, zAng;
//previous values
public float xOrig, yOrig, zOrig;
// Use this for initialization
void Start () {
//Find the VR head
vrHead = Camera.main.transform;
//finding the initial direction
Vector3 orig = vrHead.TransformDirection(Vector3.forward);
xOrig = orig.x;
yOrig = orig.y;
zOrig = orig.z;
}
// Update is called once per frame
void Update () {
//find the forward direction
Vector3 forward = vrHead.TransformDirection(Vector3.forward);
float xForward = forward.x;
float yForward = forward.y;
float zForward = forward.z;
//find the angle between the initial and current direction
xAng = Vector3.Angle(new Vector3(xOrig, 0, 0), new Vector3(xForward, 0, 0));
yAng = Vector3.Angle(new Vector3(0, yOrig, 0), new Vector3(0, yForward, 0));
zAng = Vector3.Angle(new Vector3(0, 0, zOrig), new Vector3(0, 0, zForward));
//set new original angle
xOrig = xAng;
yOrig = yAng;
zOrig = zAng;
}
From there I go to the ReadingPoints class, which contains all of the points on the graphs (spheres) and axes (stretched out cubes):
public class ReadingPoints : MonoBehaviour {
// Update is called once per frame
void Update () {
float xAngl = GameObject.Find("GvrMain").GetComponent<AutoWalk>().xAng;
float yAngl = GameObject.Find("GvrMain").GetComponent<AutoWalk>().yAng;
float zAngl = GameObject.Find("GvrMain").GetComponent<AutoWalk>().zAng;
if ((xAngl != prevXAngl) || (yAngl!=prevYAngl) || (zAngl!=prevZAngl))
{
//rotate depending on the angle from normal
foreach (GameObject o in allObjects)
{
o.transform.Rotate(new Vector3(xAngl, yAngl, zAngl), Space.World);
prevXAngl = xAngl;
prevYAngl = yAngl;
prevZAngl = zAngl;
}
}
allObjects, as the name implies, contains the points and the axes.
Anyway, the first issue that is upon running the program is that the graph appears to be torn apart.
How the graph is supposed to look (this is what it looks like when o.transform.Rotate(...) is commented out)
Here is how it actually looks :( Also, the graph does not rotate when I move, and I have no idea why (I thought I might be using the Rotate function improperly but perhaps I also didn't find the correct angles?). Any ideas of what went wrong are much appreciated, thank you!
First of all I think you should start using Quaternions (https://docs.unity3d.com/ScriptReference/Quaternion.html)
Secondly; while you rotate a object with a Camera attached its line of view will change and eventually the object(Graph) will be out of view. If you want as much of the full graph as possible to remain in view of camera as long as possible. You should give the Quaternion of the graph the opposite rotation of that applied to your Camera or the object it is attached to.
Make sure your all elements of your graph are children of a central gameobject in your graph, and only manipulate that point.
I'm facing a problem in my scene. What i'm doing is there are two panels both panel has one button which performing OnClick() listener with the method name RotateCamera().
Also in hierarchy there is a MainMenu gameobject which attached with one script. When I play the scene and click on panel one button than MainCamera rotate with 90 angle to the direction of second panel. Script works fine but I want to smoothly rotate camera to that panel when I click on that button - right now, it is rotating instantly. I don't know what I'm doing wrong on this script.
public class CameraSmooth : MonoBehaviour {
private bool check = false;
private Transform from;
private Transform to;
void Start(){
from = Camera.main.transform;
}
void Update(){
if (to != null) {
Camera.main.transform.rotation = Quaternion.Slerp (from.rotation, to.rotation, 3 * Time.deltaTime);
}
}
public void RotateCamera(){
if (!check) {
Camera.main.transform.Rotate (0,90f,0f);
to = Camera.main.transform;
check = true;
}
else if (check) {
Camera.main.transform.rotation = Quaternion.identity;
to = Camera.main.transform;
check = false;
}
}
}
The main problem here is that you're calling Camera.main.transform.Rotate() in RotateCamera(). This causes the rotation to be applied to the camera immediately, resulting in the instant rotation you're seeing.
Another small misconception - since Transform is a reference type, from and to always actually point to the same instance (the Transform of the camera)! Lastly, the value of 3 * Time.deltaTime will always average around 3/60, and will likely never end up close to 1, which is needed for an interpolation to be complete. As such, the following line:
Camera.main.transform.rotation = Quaternion.Slerp (from.rotation, to.rotation, 3 * Time.deltaTime);
will not be able to do anything even if the first issue is addressed.
The situation calls for a different solution, in my view:
Storing the target rotations in Quaternion variables rather than setting them directly on the Transform, and keeping track of the camera's initial rotation
Maintaining a timer which keeps track of the progress of the rotation (or you can abandon Slerp() and just apply an incremental rotation directly, that's also a valid approach)
Here's my suggested update to your code:
public class CameraSmooth : MonoBehaviour {
private bool check = false;
private float rotationProgress = 1;
private Quaternion initial;
private Quaternion from;
private Quaternion to;
void Start(){
// Cache the starting rotation, so we can calculate rotations relative to it
initial = Camera.main.transform.rotation;
}
void Update(){
if (rotationProgress < 1) {
rotationProgress += 3 * Time.deltaTime;
Camera.main.transform.rotation = Quaternion.Slerp (from, to, rotationProgress);
}
}
public void RotateCamera(){
from = Camera.main.transform.rotation;
rotationProgress = 0;
if (!check) {
to = initial * Quaternion.Euler(0,90f,0f);
check = true;
}
else if (check) {
to = initial;
check = false;
}
}
}
(Haven't tested this code, so please let me know if there are any issues.) Hope this helps! Let me know if you have any questions!