I have scripted a controller for opening doors in a TopDown-Shooter.
I rotate pivot points around their local Y-Axis to open the door objects. The doors should stay open, so I do not need the Controller and the Controller object anymore. I want to destroy it after finishing its job.
My script looks this:
public class DoorController : MonoBehaviour
{
public Transform pivotLeftTransform; // the left pivot point
public Transform pivotRightTransform; // the right pivot point
int openAngle = 90; // how far should the door open up?
bool startOpen = false; // start opening?
float smooth = 2; // smooth rotation
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Player")
{
startOpen = true; // when the Player triggers, start opening
}
}
void Update()
{
if (startOpen)
{
OpenDoor(pivotLeftTransform, openAngle);
OpenDoor(pivotRightTransform, -openAngle);
if (pivotLeftTransform.localRotation.y == openAngle && pivotRightTransform.localRotation.y == -openAngle) // when the doors are rotated, destroy this object
{
Destroy(gameObject);
}
}
}
private void OpenDoor(Transform pivotTransform, int rotationAngle)
{
Quaternion doorRotationOpen = Quaternion.Euler(0, rotationAngle, 0); // desired door rotation
pivotTransform.localRotation = Quaternion.Slerp(pivotTransform.localRotation, doorRotationOpen, smooth * Time.deltaTime); // rotate the door to the desired rotation
}
}
My Question is, how can I destroy the object. My code
if (pivotLeftTransform.localRotation.y == openAngle && pivotRightTransform.localRotation.y == -openAngle)
does not seem to work. After having the pivot point rotated to 90 or -90 degrees the statement still stays false. I also tried
pivotTransform.rotation.y
but it does not work neither. Which rotation do I need to pass in?
Thanks.
Two things wrong in your code:
1.Comparing floats
2.Using transform.localRotation or transform.rotation to check for angle.
You can solve this by using eulerAngles or localEulerAngles. Also for comparing floats, use >= instead of = as that may never be true.
Replace
if (pivotLeftTransform.localRotation.y == openAngle && pivotRightTransform.localRotation.y == -openAngle)
{
Destroy(gameObject);
}
with
if (pivotLeftTransform.localEulerAngles.y >= openAngle && pivotRightTransform.localRotation.y >= -openAngle)
{
Destroy(gameObject);
}
If you have problem with the answer above you have to troubleshoot it one by one. Separate the && and see which one is failing like this:
if (pivotLeftTransform.localEulerAngles.y >= openAngle)
{
Debug.Log("pivotLeftTransform");
}
if (pivotRightTransform.localRotation.y >= -openAngle)
{
Debug.Log("pivotRightTransform");
}
The right way to archieve this is adding a small tolerance value:
if (pivotLeftTransform.localEulerAngles.y >= openAngle - 0.1f && pivotRightTransform.localEulerAngles.y <= 360 - openAngle + 0.1f)
This may not be code clean but it works for the moment :)
Related
How would I go about making a rocket player controller rotate around its bottom part?
Heres my code up until now (I know it has problems, it's just a proof of concept for now)
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rocket : MonoBehaviour
{
Rigidbody rigidBody;
void Start()
{
rigidBody = GetComponent<Rigidbody>();
}
void Update()
{
ProcessInput();
}
private void ProcessInput()
{
if (Input.GetKey(KeyCode.Space))
{
rigidBody.AddRelativeForce(Vector3.up);
}
if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
{
if (!Input.GetKey(KeyCode.D) && !Input.GetKey(KeyCode.RightArrow))
{
transform.Rotate(new Vector3(0, 0, 2));
}
}
if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow))
{
if (!Input.GetKey(KeyCode.A) && !Input.GetKey(KeyCode.LeftArrow))
{
transform.Rotate(new Vector3(0, 0, -2));
}
}
}
}
Good news, Unity has
https://docs.unity3d.com/ScriptReference/Transform.Rotate.html
but! more usefully, it has:
https://docs.unity3d.com/ScriptReference/Transform.RotateAround.html
I'm pretty sure that's exactly what you want.
(BTW be aware - it's not that trivial to figure out just what the vector you rotate around will be. You have to have a really good feel for cross products and other Vector manipulation. Unity is annoying like that - it seems really easy to use at first, but you really need a superb grasp of physics (I mean Newtonian physics) and every aspect of vectors and quaternions to really be able to make games - it's tough.)
One tip: you see how you are adding force to move it. Try adding torque (AddTorque) to turn it!
If you have a Rigidbody you should not mix in movements via transform! This breaks the physics and collision detection and leads to strange behavior.
rather use e.g. Rigidbody.centerOfMass to set the center of mass according to your needs.
And then AddTorque or MoveRotation
private void ProcessInput()
{
if (Input.GetKey(KeyCode.Space))
{
rigidBody.AddRelativeForce(Vector3.up);
}
if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow))
{
if (!Input.GetKey(KeyCode.D) && !Input.GetKey(KeyCode.RightArrow))
{
turnLeft = true;
}
}
if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow))
{
if (!Input.GetKey(KeyCode.A) && !Input.GetKey(KeyCode.LeftArrow))
{
turnRight = true;
}
}
}
private bool turnLeft;
private bool turnRight;
private void FixedUpdate ()
{
if(turnLeft)
{
turnLeft = false;
rigidbody.MoveRotation(rigidBody.MoveRotation(rigidbody.rotation * Quaternion.Euler(0, 0, 2));
}
if(turnRight)
{
turnRight = false;
rigidBody.MoveRotation(rigidBody.MoveRotation(rigidbody.rotation * Quaternion.Euler(0, 0, -2));
}
}
Though in general it is easier to use an angle per second like e.g.
rigidBody.MoveRotation(rigidbody.rotation * Quaternion.Euler(0, 0, -45 * Time.deltaTime));
Time.deltaTime is the time passed since the last physics update so this makes sure you rotate with exactly -45° / second.
Alternatively take a look at How to change objects pivot point -> You could simply put your mesh under a parent object and instead use that parent as the rigidbody and move script etc. so you can create any offset relative to the parent you wish.
And yes one also could recalculate the mesh vertex positions themselves to fit your needs but usually the parenting approach is already sufficient ;)
In a script, the script is too long so I'm showing the Update part only I'm checking if the game is running after a loading saved game have been done and then trying to set the player position to 0 on X and Y and keep the same value of Y.
The script is attached to the Player. and I check with a break point and it's doing the code inside once.
private void Update()
{
if(MenuController.LoadSceneForSavedGame == true && setNaviRotation == false)
{
transform.Rotate(0, transform.rotation.y, 0);
setNaviRotation = true;
}
}
When starting the player first time the player rotation is 0,120,0 :
but then when loading a saved game and looking on the player the rotation is not 0 on X and Y even if it does the code in the Update.
You can see the player is a bit lying back and the rotation on X is -9.715 and the Z -0.99100
If I will set them on my own manual they player will stand straight.
Why the setting of the rotation in the Update is not changing anything on the player ?
The solution is yo use Quaternion.Euler this is working perfect.
private void Update()
{
if(MenuController.LoadSceneForSavedGame == true && setNaviRotation == false)
{
transform.rotation = Quaternion.Euler(0, transform.eulerAngles.y, 0);
setNaviRotation = true;
}
}
Overview
Using Unity2D 2019.3.5, I am making a platformer game using C#. I implemented raycast to detect when my player is touching the ground and attempted to make it only so the player can jump only once.
Problem
Although I thought I programmed my character to jump once, after the first jump, the Unity engine still shows a checkmark to my "isGrounded" variable and only turns to false (unchecked) after a second jump before hitting the ground.
My Code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player_Controller : MonoBehaviour
{
public int playerSpeed = 10;
public int playerJumpPower = 1250;
private float moveX;
public bool isGrounded;
public float distanceToBottomOfPlayer = .7f;
// Update is called once per frame
void Update()
{
PlayerMove();
PlayerRaycast();
}
void PlayerMove()
{
// CONTROLS
moveX = Input.GetAxis("Horizontal");
if (Input.GetButtonDown("Jump") && isGrounded == true)
{
Jump();
}
// ANIMATIONS
// PLAYER DIRECTION
if (moveX < 0.0f)
{
GetComponent<SpriteRenderer>().flipX = true;
}
else if (moveX > 0.0f)
{
GetComponent<SpriteRenderer>().flipX = false;
}
// PHYSICS
gameObject.GetComponent<Rigidbody2D>().velocity = new Vector2(moveX * playerSpeed,
gameObject.GetComponent<Rigidbody2D>().velocity.y);
}
void Jump()
{
GetComponent<Rigidbody2D>().AddForce(Vector2.up * playerJumpPower);
isGrounded = false;
}
void PlayerRaycast()
{
// Ray Down
RaycastHit2D rayDown = Physics2D.Raycast(transform.position, Vector2.down);
if (rayDown.collider != null && rayDown.distance < distanceToBottomOfPlayer &&
rayDown.collider.tag == "ground")
{
isGrounded = true;
}
}
}
Extra Info
I did have to change a Unity setting in Edit > Project Settings > Physics 2D > Queries Start In Colliders. I had to turn this setting off (uncheck) in order to get my player to jump using the code I wrote above. I know there are other ways of making my player jump, however, this seemed to be the most efficient while maintaining the readability of the code.
Solutions Tried
What I believe the problem is that I have a raycast issue that I don't know how to fix. I looked at other Stack Overflow posts including the ones recommended after writing this post, but none of them applied to my problem.
Final Notes
As I said before, I know there are other ways to make my player jump only once using different code, however, I would like to stick with this code for my own learning purposes and for future reference.
Because you can't be sure that isGrounded is false when you call Jump().
I think issue lies there.
Try not setting isGrounded flag when calling Jump(). Setting isGrounded is purely PlayerRaycast()'s job.
void Update()
{
// Raycast before moving
PlayerRaycast();
PlayerMove();
}
void PlayerRaycast()
{
// Ray Down
RaycastHit2D rayDown = Physics2D.Raycast(transform.position, Vector2.down);
if (rayDown.collider != null && rayDown.collider.tag == "ground")
{
if( rayDown.distance < distanceToBottomOfPlayer )
{
isGrounded = true;
}
else
{
isGrounded = false;
}
}
}
void Jump()
{
GetComponent<Rigidbody2D>().AddForce(Vector2.up * playerJumpPower);
//isGrounded = false;
}
The first problem is that you add the force and check for the ground at the same frame.
Applied Force is calculated in FixedUpdate or by explicitly calling
the Physics.Simulate method.
So after you press the "Jump" button, the object is still on the ground until the next frame comes.
To fix this, you can simply exchange the order of "move" and "raycast"
void Update()
{
PlayerRaycast();
PlayerMove();
}
The second problem is if the jump power is not large enough, the object can still be close to the ground in the next frame, you should avoid checking the landing when the jump is in ascending state.
void PlayerRaycast()
{
if(GetComponent<Rigidbody2D>().velocity.y > 0)
return;
...
}
I need to set an animation of flipping in a 2D game on Unity. How is the best way to do this?
I'm trying something like this pseudo code:
void FixedUpdate() {
if(lastSide!=currentSide)
flip();
}
void flip() {
if(lastSide == 1) // if is facing right
animator.SetTrigger("flipToLeft");
else if(lastSide == -1) // if is facing left
animator.SetTrigger("flipToRight");
this.player.transform.localScale = new Vector3(lastSide * -1, 1f, 1f);
}
This "works", but the sprite is fliping before the animation of fliping starts.
One option would be to hardcode a delay for the point at which you want the sprite to flip:
void FixedUpdate()
{
if(lastSide != currentSide)
StartCoroutine(Flip());
}
IEnumerator Flip()
{
if(lastSide == 1) // if is facing right
animator.SetTrigger("flipToLeft");
else if(lastSide == -1) //if is facing left
animator.SetTrigger("flipToRight");
yield return new WaitForSeconds(.5f); // delay before flipping sprite
transform.localScale = new Vector3(lastSide*-1,1f,1f);
}
If you don't want to hardcode the timing, you can look into Animation Events which you can use to call functions directly from the animation.
You can use DoTween for fliping in simple way.
a prefab I instantiate (the boss of my game) is following my camera whenever this script is active, which is not what I'm telling it to do. Well, to be more accurate, when the camera goes to the right, the cloned prefab goes to the left, and vice versa. If I have the boss already in the scene and start the game, it does everything I tell it to, and doesn't follow the camera. The prefab only does half the things and yes, I did apply the changes to the prefab several times. I know it's not attaching itself to the camera automatically because I moved the camera in the scene view with this script off and it didn't follow my camera in-game.
The boss isn't even being referenced in this script. I am absolutely positive that the problem isn't even script wise. But it occurs when this script that has nothing to do with the boss is active. So my question is, can I tell the boss script in some way to blind itself to this script? Or better yet, is there something in Unity that can cause this, to which at that end I can fix? The only thing this script does is follow the player's characters based on whenever you switch between them. And the boss does follow the camera regardless of whatever character you have active at the time so it has nothing to do with that either. Lastly, the camera is not being referenced in the boss script at all, or in the script attached to another 2D trigger collider that instantiates the boss.
using UnityEngine;
using System.Collections;
public class cameraFollow : MonoBehaviour {
public Transform player;
public Transform player2;
private bool idleFollow = true;
private bool mountFollow = false;
public theWatcher wMana;
public tulMove tulia;
private bool alphaGirl;
[HideInInspector]
public bool blinking = false;
[HideInInspector]
public bool vTul = true;
void Update ()
{
CameraSwitching ();
}
void CameraSwitching()
{
if (!wMana.alphaGirl && !wMana.switched && vTul && wMana.cur_mana > 0f && Input.GetKeyDown (KeyCode.Space)) {
vTul = false;
return;
}
if (!vTul && wMana.cur_mana > 0f && Input.GetKeyDown (KeyCode.Space)) {
vTul = true;
return;
}
if (!vTul && wMana.cur_mana <= 0) {
vTul = true;
}
if (vTul && wMana.cur_mana <= 0 && Input.GetKeyDown (KeyCode.Space)) {
vTul = true;
}
if (!wMana.paused && vTul){
transform.position = new Vector3 (player.position.x + .5f, player.position.y + .55f, -7.75f);
}
if (!wMana.paused && !vTul) {
transform.position = new Vector3 (player2.position.x + .5f, player2.position.y + .55f, -7.75f);
}
if (wMana.switched) {
transform.position = new Vector3 (player2.position.x + .5f, player2.position.y + .55f, -7.75f);
}
if (!wMana.switched) {
transform.position = new Vector3 (player.position.x + .5f, player.position.y + .55f, -7.75f);
}
}
}
this is the code to spawn my boss. I know it's not this because I used other prefabs besides my boss and those were working fine.
so, since there are no known solutions to a problem with a random, unrelated cause, I just decided to work around the issue. What I did was have the boss already placed in the scene and made a trigger collider activate the existing GameObject. If the player dies, I'll just move the boss back to his original position and deactivate him. It sucks I can't have a prefab of this character for whatever reason, but at least it's a character you only have to beat once anyway.