Scripting Newcomer

Ok… I’m trying to learn javascript so that I can do more with Unity than just knock a few rigid bodies together with no purpose other than aesthetic appeal.

So here’s my question:

Does this code mean what I’m annotating it to mean?

// Plays an animation only if we are not playing it already.
function OnMouseEnter() { //when the mouse goes over this thing
	if (!animation.isPlaying) //check if the boolean value of "animation" is "yes/1"
		animation.Play(); //if it's not playing (value of no/0), then play it
}

Also, what does the exclamation point in “!animation” do? Does that invert the variable so that it’s actually asking if it’s not playing? What would it look like if I wanted to write a function that checked to see if it was playing and did something if it is rather than if it’s not?

That does what you claim it to. Yes, the ! basically means “NOT”, so what you’re saying is “If NOT animation playing”. To write something that checked if it WAS playing it’d look something like this:

function OnMouseEnter () {
   if (animation.isPlaying) {
      //do something
   }
}

fantastic! :smile: Thank you very much for that.

I have another more complicated question. Why when and where would a user want to use quaternions rather than eulerAngles in a script?

Typically you don’t use quaternions directly (unless you understand them!). You’re using quaternions when you do transform.Rotate(), in which case Unity takes care of the weird stuff for you. One case when you can use them directly is something like “transform.rotation = someOtherObject.rotation”, but in this case all you’re doing is assigning the quaternion from one transform to another, so you don’t have to care about the funky numbers.

–Eric

Ah, well that is excellent news. A guy who’s learning to program it with me got a little worried that he hadn’t taken enough math classes yet to handle that. I was all set to start punching in numbers, but hoping I wouldn’t have to.

Thank you.

Well, I won’t lie…rotations can still be tricky. The good news is that you don’t really have to understand quaternions, but the bad news is that, occasionally, you end up having to do things in a slightly convoluted way (unless you do understand quaternions, of course).

Most of the time it’s not an issue. Just be prepared to engage in some workarounds occasionally.

–Eric

Okay, another couple questions:

var walkClip : AnimationClip;
animation.AddClip(walkClip, "walk");

What does the walk in quotes do and why is it there? Does it just label the new clip as “walk” or something?

My other question is: how do I say that I want to play a specific clip rather than the default one?

Using quaternions isn’t particularly hard, and it really just boils down to one or two common operations. For example, you might use Quaternion.AngleAxis() to set up a rotation around a particular axis. If you want to combine more than one rotation, you can do so by multiplying the quaternions together:

var xAxisRotation = Quaternion.AngleAxis(Input.GetAxis("Mouse X"), transform.right);
var yAxisRotation = Quaternion.AngleAxis(Input.GetAxis("Mouse Y"), transform.up);

transform.rotation = xAxisRotation * yAxisRotation;

When you multiply quaternions, the order of the operands is important. A * B gives a different result to B * A. I usually can’t figure out which way around it should go without trying it, so if it comes out wrong, I just try it the other way around.

You can also multiply quaternions with vectors:

var worldSpaceVector = transform.rotation * localSpaceVector;

This is equivalent to:

var worldSpaceVector = transform.TransformDirection(localSpaceVector);

…but has the benefit that you don’t need an appropriately oriented Transform to do the calculation.

Those few features are all you need to know for most purposes.

Wow, very thorough Neil, thank you. I may actually find that useful in the future.

I find Quaternion.FromToRotation() useful as well. There is a nice example in its docs.

Cheers,
-Jon

Why does the FPSWalker script use it’s own grounded variable and CollisionFlags.CollidedBelow rather than using CharacterController.isGrounded?

var speed = 6.0;
var jumpSpeed = 8.0;
var gravity = 20.0;

private var moveDirection = Vector3.zero;
private var grounded : boolean = false;

function FixedUpdate() {
	if (grounded) {
		// We are grounded, so recalculate movedirection directly from axes
		moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
		moveDirection = transform.TransformDirection(moveDirection);
		moveDirection *= speed;
		
		if (Input.GetButton ("Jump")) {
			moveDirection.y = jumpSpeed;
		}
	}

	// Apply gravity
	moveDirection.y -= gravity * Time.deltaTime;
	
	// Move the controller
	var controller : CharacterController = GetComponent(CharacterController);
	var flags = controller.Move(moveDirection * Time.deltaTime);
	grounded = (flags  CollisionFlags.CollidedBelow) != 0;
}

@script RequireComponent(CharacterController)

It was written before isGrounded was added. The two are equivalent, but isGrounded is less typing. :slight_smile:

Cheers,
-Jon

Ok… I know that I’ve messed up with the controller variable, but I’m not sure how it’s wrong. What do I need to change to make this script work?

var speed = 6.0;
var airSpeed = 3.5;
var jumpSpeed = 8.0;
var gravity = 20.0;

private var controller : CharacterController;
private var moveDirection = Vector3.zero;


function FixedUpdate() {
	if (controller.isGrounded) {
		moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, 0);
		moveDirection = transform.TransformDirection(moveDirection);
		moveDirection *= speed;
		}
	else {
		moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, 0);
		moveDirection = transform.TransformDirection(moveDirection);
		moveDirection *= airSpeed;
		}
	if (controller.isGrounded) {
		if (Input.GetButton ("Jump")) {
			moveDirection.y = jumpSpeed;
			}
		}

	moveDirection.y -= gravity * Time.deltaTime;

	var controller : CharacterController = GetComponent(CharacterController);
	var flags = controller.Move(moveDirection * Time.deltaTime);
}

@script RequireComponent(CharacterController)

I’m not a Javascript programmer, but if I recall correctly, you can’t initialise a member variable (outside of a function) with something that isn’t a constant value. Function calls return variables, and those aren’t allowable in this particular place. I think you have to split it into two separate lines, like this:

private var controller : CharacterController;
controller = GetComponent(CharacterController);

By the way, it’s helpful if you can tell us what’s happening when things go wrong, such as the exact text of the error message you’re getting. It can be difficult to guess otherwise. :slight_smile:

it says:
Assets/Scripts/SSWalker.js(30) warning BCW0003: WARNING: Unused local variable ‘flags’.

That’s a warning, not an error. It’s complaining that you’re assigning the result of controller.Move() to the flags variable, but then you never use flags for anything else. You can fix this by removing the ‘var flags =’ bit from the front of that line.

You can ignore ‘unused variable’ warnings if you want to, because they don’t stop you from running your game. However, you’ll probably want to eliminate them to make sure that they don’t distract you from actual errors in the Console window.

What Neil said. :slight_smile: It’s helpful to click on the warning/error in Unity, so it pops up the console with all warnings and errors, not just the most last one. Frequently, the first error will cascade and cause other errors, so if you fix the first one, the rest might go away.

As far as the “flags” thing goes, it used to hold the value returned by the Move() function. Anything starting with a capital letter and using () is a function, that can return values. If you look in the docs for CharacterController.Move, you see “function Move (motion : Vector3) : CollisionFlags”. This means you input a variable of type Vector3, and you get back a variable of type CollisionFlags. (If you don’t get anything back from a function, it will say “void” in the docs.)

Therefore, when you moved the character controller, any collision flags that resulted in doing so that frame were assigned to the variable “flags”. Since you’re not using the variable, you can just call the function and not assign the return value to anything, since you don’t care about it.

Another problem is that you’ve got “var controller : CharacterController = GetComponent(CharacterController);” in your FixedUpdate function, which means that variable will only exist inside the function. But you’re already declaring a variable called “controller” outside FixedUpdate, which means you can use it anywhere. So, you can get rid of that line since you don’t need that local variable.

You’ve got a couple of “if (controller.isGrounded)” statements…you only need to test for that once, so those can be combined.

Another problem is the air control, with “moveDirection = new Vector3(Input.GetAxis(“Horizontal”), 0, 0);”. The “moveDirection.y -= gravity * Time.deltaTime;” line says to decrease the y component of moveDirection every fixed frame, but you’re setting moveDirection’s y component to 0 whenever the controller isn’t grounded, by using Vector3. This means gravity isn’t going to work. One solution is to make a new direction and just add that on top of the existing moveDirection, instead of explicitly setting it.

So, this should work:

var speed = 6.0;
var jumpSpeed = 8.0;
var airSpeed = 3.5; 
var gravity = 20.0;

private var moveDirection = Vector3.zero;

private var controller : CharacterController;
controller = GetComponent(CharacterController);

function FixedUpdate() {
	// If controller is grounded, move on x/z axes and check for jump button
	if (controller.isGrounded) {
		moveDirection = Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
		moveDirection = transform.TransformDirection(moveDirection) * speed;
		
		if (Input.GetButton ("Jump")) {
			moveDirection.y = jumpSpeed;
		}
	}
	// If controller isn't grounded, add a local left/right direction
	else {
		var airDirection = Vector3(Input.GetAxis("Horizontal"), 0, 0);
		moveDirection += transform.TransformDirection(airDirection) * airSpeed;		
	}
	
	moveDirection.y -= gravity * Time.deltaTime;
	
	controller.Move(moveDirection * Time.deltaTime);
}

@script RequireComponent(CharacterController)