Im in my veeeeery early days of learning to script and im doing so by trying to create functions for things that id like to use now and in the future…
my scene:
cube(player) and a door.
when the player walks into the doors trigger the door rotates by 90degrees to open.
(theres extra stuff in the code about activating a button(ignore for now) as i have a button popup which will control the door opening and closing.
heres what i have so far:
public class playerScript : MonoBehaviour {
public GameObject DoorButton;
public GameObject Door;
public float smooth;
private Quaternion DoorOpen;
private Quaternion DoorClosed;
void Start() {
Door = GameObject.Find("Object001");
DoorButton = GameObject.Find("DoorButton");
DoorButton.SetActive(false);
}
void OnTriggerEnter(Collider cube)
{
if (cube.tag == "DoorButton")
DoorButton.SetActive(true);
Debug.Log("button activated");
DoorOpen = Door.transform.rotation = Quaternion.Euler(0, -90, 0);
DoorClosed = Door.transform.rotation;
Door.transform.rotation = Quaternion.Lerp(DoorClosed, DoorOpen, Time.deltaTime * smooth);
Debug.Log("Door Opened");
}
This code works but what im having an issue with is this Quaternion madness…i cant seem to get the animation to smooth out, it just pops from 0-90.
I’d recommend you look up “coroutine” and “lerp”. The idea being the trigger starts a coroutine which lerps the rotation.
From the phrasing I’d say you’re on the edge of the “usual” newbie pitfall of trying to get something to occur “overtime” within a trigger function which is all completed within a single frame.
I am not a big one for coroutines, I am much more comfortable using Update. So if anything changes, then you dont have to cancel some coroutine.
using UnityEngine;
using System.Collections;
public class DoorController : MonoBehaviour {
// the door from this object
public GameObject Door;
// the door copied and rotated/moved to be the opened door
public GameObject DoorOpen;
// this will be a copy of the original door so that we have some numbers to work with.
private GameObject DoorClosed;
// this controls if the door is opened or closed.
public bool isOpened = false;
// this is the movement rate (if movemnt is applied to the door)
public float moveSpeed = 3;
// this is the rotation rate (if rotation is applied to the door)
public float rotationSpeed = 90;
void Start() {
// copy the door to keep its position
DoorClosed = Instantiate(Door, Door.transform.position, door.transform.rotation);
// hide both the open and closed door
DoorClosed.SetActive(false);
DoorOpen.SetActive(false);
}
void Update(){
// every frame, move the door towards the Open/Closed door
var target = isOpened ? DoorOpen : DoorClosed;
// these actually do the moving/rotating
Door.position = Vector3.MoveTowards(Door.position, target.position, moveSpeed * Time.deltaTime);
Door.rotation = Quaternion.RotateTowards(Door.rotation, target.rotation, rotateSpeed * Time.deltaTime);
}
void OnTriggerEnter(Collider cube)
{
// whenever anything enters the trigger, open the door
isOpened = true;
}
void OnTriggerExit(Collider cube){
// whenever anything exits the trigger, close the door.
isOpened = false;
}
}
I updated the code and documented what everything does.
There is an actual problem with it, where the logic that says “if anything enters (exits) Open (close) the door” There of course should be a collector which finds if anything is currently inside the trigger.
The setup is simple though. Create a trigger, create a door, set the Door property with that door, copy the door, and rotate/move it to its open position and assign the DoorOpen with that one. Put the script on the trigger.
For replication sake, attack all that to an empty game object.
Note that your code only works in one rotation; if you place the same script on a door frame that starts out rotated differently, the door will open too much or in the wrong direction. If the door is a child of the door frame, you can use it’s local rotation instead - that’ll make it work no matter how you’ve rotated the entire thing.
Thank you for all your help its really appreciated, think im starting to get a grasp on this now.
Baste i will try your modification tomorrow when im at work as that scene is on my work pc.
At home ive created a new scene to play around with it more and try a different solution using an animator. Please let me know what you think. is it an efficient solution or is this a bad way of doing things…
heres what it does:
code:
public class interact : MonoBehaviour
{
public bool open = false;
Animator anim;
void Start ()
{
anim = GetComponentInChildren<Animator> ();
Debug.Log("GOT THE ANIMATOR");
}
void OnTriggerEnter (Collider other)
{
if (other.CompareTag("Player"))
{
anim.SetBool ("open", true);
Debug.Log ("OPENING THE DOOR");
}
}
void OnTriggerExit (Collider other)
{
if (other.CompareTag("Player"))
{
anim.SetBool ("open", false);
Debug.Log ("OPENING THE DOOR");
}
}
}
I would suggest using the animator. Still here is how to do it with coroutines:
public Transform door; //the door
public float endRotation; //the end rotation
public float startRotation; //the start rotation
public float speed; //the speed the door opens
IEnumerator OpenDoor () //declares a coroutine
{
while (door.transform.rotation.y < endRotation) //while the current rotation is less than the end we will continue
{
door.Rotate(0, speed * Time.deltaTime, 0); //rotates towards the end rotation
yield return null; //returns null and will start up here again next frame, yet it will just restart the while loop
}
}
IEnumerator CloseDoor ()
{
while (door.transform.rotation.y > startRotation)
{
door.Rotate(0, -speed * Time.deltaTime, 0);
yield return null;
}
}
Then simply call:
StartCoroutine("OpenDoor");
To open and:
StartCoroutine("CloseDoor");
To close.
Also in OnTriggerExit/Enter just use:
StopAllCoroutines();
Before you start the coroutine you want to then use.
Sorry for bringing back such an old thread. But I haven’t seen other better way to making open and closing door with coroutine.
But I’m having trouble with the IEnumerator OpenDoor ()
The operation while (door.transform.rotation.y < endRotation) seems not to work. My door keeps rotating and never ends. But when triggering the CloseDoor coroutine, the door indeed come back to the origin.
Could it be that door.transform.rotation.y is not the same variable as the float of endRotation? or someting like that?
Yeah, that code is just plain wrong. You don’t do rotations like that.
Rotations are stored internally as Quaternions. They have four coordinates (x, y, z, w), which has nothing to do with the x,y,z you see in the inspector - that’s “euler angles”, which is just a conversion.
The code in the earlier posts in the thread show how to handle rotations properly. I’d suggest using @bigmisterb 's code, with a marker object for the start and end position/rotation. You can do the same thing in a coroutine.
Dang, little me had no idea how to write code (I still probably don’t lol). That was probably the worst way to do it.
I made a game last week for Ludum Dare, here’s how I did it there:
private IEnumerator DoorAnimation(int targetAngle, int animationSpeed) //animates the door to [targetAngle] using amount of frames [animationSpeed]
{
for (int r = 0; r < animationSpeed; r += 1)
{
door.localEulerAngles = new Vector3(0, Mathf.LerpAngle(door.localEulerAngles.y, targetAngle, 5f / animationSpeed), 0);
yield return null;
}
}
Thanks both for your answers. I managed to get @RavenOfCode to work using eulerAngles.y
I like more the “script” aproach for animations than the animator, since I will have lot’s of rotations with triggers and I think animators would not be as flexible. (I’m learnig so I could be totally wrong tho).
But it works only on “positive” rotations. I having trouble to make it work on “negative” rotations. I mean if the idle state is (0 0 0) and I need it to rotate to (0 240 0) but going “counterclockwise”.
I tried Quaternion.Inverse with no luck.
Animations can be more flexible (more options for smaller details and a less robotic look), however I find it easier to use code when doing simple projects like Ludum Dare.
As far as getting negative angles to work just input a negative number. So instead of 240 do -120. This way it knows to rotate counterclockwise (negative angle) rather than clockwise (positive angle).
// Move Function
public IEnumerator Move()
{
RotationPending = true;
AnimationCurve rotationcurve = AnimationCurve.EaseInOut(0, 0, 1f, 1f);
float TimeProgression = 0f;
Transform t = transform;
if (RotationSide == SideOfRotation.Left)
{
InitialRot = Quaternion.Euler(0, -InitialAngle, 0);
FinalRot = Quaternion.Euler(0, -InitialAngle - RotationAngle, 0);
}
if (RotationSide == SideOfRotation.Right)
{
InitialRot = Quaternion.Euler(0, -InitialAngle, 0);
FinalRot = Quaternion.Euler(0, -InitialAngle + RotationAngle, 0);
}
if (DoorScale == ScaleOfDoor.Unity3DUnits && PivotPosition == PositionOfPivot.Centered)
{
t = hinge.transform;
RotationOffset = Quaternion.identity;
}
if (TimesRotated < TimesMoveable || TimesMoveable == 0)
{
// Change state from 1 to 0 and back (= alternate between FinalRot and InitialRot
if (t.rotation == (State == 0 ? FinalRot * RotationOffset : InitialRot * RotationOffset)) State ^= 1;
// Set 'FinalRotation' to 'FinalRot' when moving and to 'InitialRot' when moving back
Quaternion FinalRotation = ((State == 0) ? FinalRot * RotationOffset : InitialRot * RotationOffset);
// Make the door/window rotate until it is fully opened/closed
while (TimeProgression <= (1 / Speed))
{
TimeProgression += Time.deltaTime;
float RotationProgression = Mathf.Clamp01(TimeProgression / (1 / Speed));
float RotationCurveValue = rotationcurve.Evaluate(RotationProgression);
t.rotation = Quaternion.Lerp(t.rotation, FinalRotation, RotationCurveValue);
yield return null;
}
if (TimesMoveable == 0) TimesRotated = 0;
else TimesRotated++;
}
RotationPending = false;
}
As you can see I also use an animation curve that then gets evaluated. This provides me with the smoothness and flexibility of animations, while still having the benefits of physical rotations.