Wow, I didn’t realize that part 2 would involve writing so much code. The tap control piece was much longer than I had expected, and even now the zoom in feels much faster than the zoom out so I might have a small error some where.
I also had to rewrite the ZoomCamera JS to C# because the TapControl had a code reference to the class and I didn’t realize that you couldn’t have mismatching code classes reference each other, not that I am that surprised that it was a problem when it arose either mind you… maybe I imagined unity was so magical that it wouldn’t be an issue. ^_~
Part 2 converts all three control types into c# along with 2 other classes:
FollowTransform
CameraRelativeControl
PlayerRelativeControl
TapControl
ZoomCamera
I did run into a few small errors in the tutorial, and I made notes by them above their respected snippet or as a comment in the code. Enjoy.
Part 2 Source Files
Page 37
public bool faceForward = false;
private Transform thisTransform;
void Start ()
{
thisTransform = transform;
}
void Update ()
{
thisTransform.position = targetTransform.position;
if( faceForward )
{
thisTransform.forward = targetTransform.forward;
}
}
.
.
Page 39
[RequireComponent( typeof( CharacterController ) )]
/////
public Joystick moveJoystick;
public Transform cameraTransform;
public float speed = 6.0f;
private Transform thisTransform;
private CharacterController character;
void Start ()
{
thisTransform = (Transform)GetComponent( typeof( Transform ) );
character = (CharacterController)GetComponent( typeof( CharacterController ) );
}
.
.
Page 40 (top)
{
Vector3 movement = cameraTransform.TransformDirection( new Vector3( moveJoystick.position.x,
0.0f,
moveJoystick.position.y ) );
movement.y = 0.0f;
movement.Normalize();
}
.
.
Page 40 (bottom)
Mathf.Abs( moveJoystick.position.y ) );
movement *= speed * ( ( absJoyPos.x > absJoyPos.y ) ? absJoyPos.x : absJoyPos.y );
// below is come code explained not snipped right below the above snippet
movement *= Time.deltaTime;
character.Move(movement);
.
.
page 41
{
Vector3 horizontalVelocity = character.velocity;
horizontalVelocity.y = 0;
if( horizontalVelocity.magnitude > 0.1f )
{
thisTransform.forward = horizontalVelocity.normalized;
}
}
///// the little bits of code after the above example in book
public Joystick rotateJoystick;
public Transform cameraTransform;
public Transform cameraPivot;
public Vector2 rotationSpeed = new Vector2( 50, 25 );
.
.
Page 42
camRotation.x *= rotationSpeed.x;
camRotation.y *= rotationSpeed.y;
camRotation *= Time.deltaTime;
cameraPivot.Rotate( 0, camRotation.x, 0, Space.World );
cameraPivot.Rotate( camRotation.y, 0, 0 );
.
.
Page 42
public float inAirMultiplier = 0.25f;
private Vector3 velocity;
// add below in the update function
if( character.isGrounded )
{
if( rotateJoystick.tapCount == 2 )
{
velocity = character.velocity;
velocity.y = jumpSpeed;
}
}
else
{
velocity.y += Physics.gravity.y * Time.deltaTime;
movement.x *= inAirMultiplier;
movement.z *= inAirMultiplier;
}
movement += velocity;
movement += Physics.gravity;
// after faceMovementDirection()
if( character.isGrounded )
{
velocity = Vector3.zero;
}
.
.
Page 44
{
moveJoystick.Disable();
rotateJoystick.Disable();
this.enabled = false;
}
.
.
Page 48
public Joystick rotateJoystick;
public Transform cameraPivot;
public float forwardSpeed = 6;
public float backwardSpeed = 3;
public float sidestepSpeed = 4;
public float jumpSpeed = 4;
public float inAirMultiplier = 0.25f;
public Vector2 rotationSpeed = new Vector2( 50, 25 );
private Transform thisTransform;
private CharacterController character;
private Vector3 cameraVelocity;
private Vector3 velocity;
.
.
Page 49
.
.
Page 49-50
0,
moveJoystick.position.y ) );
movement.y = 0;
movement.Normalize();
//
// Tutorial cuts in here: Next, we apply movement...
//
Vector3 cameraTarget = Vector3.zero;
Vector2 absJoyPos = new Vector2( Mathf.Abs( moveJoystick.position.x ),
Mathf.Abs( moveJoystick.position.y ) );
if( absJoyPos.y > absJoyPos.x )
{
if( moveJoystick.position.y > 0 )
{
movement *= forwardSpeed * absJoyPos.y;
}
else
{
movement *= backwardSpeed * absJoyPos.y;
cameraTarget.z = moveJoystick.position.y * 0.75f;
}
}
else
{
movement *= sidestepSpeed * absJoyPos.x;
cameraTarget.x = -moveJoystick.position.x * 0.5f;
}
.
.
Page 51
{
if( rotateJoystick.tapCount == 2 )
{
velocity = character.velocity;
velocity.y = jumpSpeed;
}
}
else
{
velocity.y += Physics.gravity.y * Time.deltaTime;
cameraTarget.z = -jumpSpeed * 0.25f;
movement.x *= inAirMultiplier;
movement.z *= inAirMultiplier;
}
movement += velocity;
movement += Physics.gravity;
movement *= Time.deltaTime;
character.Move( movement );
if( character.isGrounded )
{
velocity = Vector3.zero;
}
.
.
Page 52 (top)
This is a touch different, the third parameter needs a ref keyword.
pos.x = Mathf.SmoothDamp( pos.x, cameraTarget.x, ref cameraVelocity.x, 0.3f );
pos.z = Mathf.SmoothDamp( pos.z, cameraTarget.z, ref cameraVelocity.z, 0.5f );
cameraPivot.localPosition = pos;
.
.
Page 52 (middle)
{
var camRotation = rotateJoystick.position;
camRotation.x *= rotationSpeed.x;
camRotation.y *= rotationSpeed.y;
camRotation *= Time.deltaTime;
thisTransform.Rotate( 0, camRotation.x, 0, Space.World );
cameraPivot.Rotate( camRotation.y, 0, 0 );
}
.
.
Page 56,57,58
{
WaitingForFirstTouch,
WaitingForSecondTouch,
MovingCharacter,
WaitingForMovement,
ZoomingCamera,
RotatingCamera,
WaitingForNoFingers
}
public float minimumTimeUntilMove = 0.25f;
public bool zoomEnabled = true;
public float zoomEpsilon = 25.0f;
public bool rotateEnabled = true;
public float rotateEpsilon = 10.0f;
private int state = (int)ControlState.WaitingForFirstTouch;
private int[] fingerDown = new int[ 2 ];
private Vector2[] fingerDownPosition = new Vector2[ 2 ];
private int[] fingerDownFrame = new int [ 2 ];
private float firstTouchTime;
void ResetControlState()
{
state = (int)ControlState.WaitingForFirstTouch;
fingerDown[ 0 ] = -1;
fingerDown[ 1 ] = -1;
}
void Start ()
{
ResetControlState();
}
void OnEndGame()
{
this.enabled = false;
}
void Update ()
{
int touchCount = Input.touchCount;
if( touchCount == 0 )
{
ResetControlState();
}
else
{
// everything else is going to go here
}
}
.
.
Page 59
Touch touch;
Touch[] touches = Input.touches;
Touch touch0 = new Touch(); // have to assign a default value to avoid compile time error;
Touch touch1 = new Touch();
bool gotTouch0 = false;
bool gotTouch1 = false;
if( state == (int)ControlState.WaitingForFirstTouch )
{
for( i = 0; i < touchCount; i++ )
{
touch = touches[ i ];
if( touch.phase != TouchPhase.Ended &&
touch.phase != TouchPhase.Canceled )
{
state = (int)ControlState.WaitingForSecondTouch;
firstTouchTime = Time.time;
fingerDown[ 0 ] = touch.fingerId;
fingerDownPosition[ 0 ] = touch.position;
fingerDownFrame[ 0 ] = Time.frameCount;
break;
}
}
}
.
.
Page 60
{
for( i = 0; i < touchCount; i++ )
{
touch = touches[ i ];
if( touch.phase != TouchPhase.Canceled )
{
if( touchCount >= 2 &&
touch.fingerId != fingerDown [ 0 ] )
{
state = (int)ControlState.WaitingForMovement;
fingerDown[ 1 ] = touch.fingerId;
fingerDownPosition[ 1 ] = touch.position;
fingerDownFrame[ 1 ] = Time.frameCount;
break;
}
else if ( touchCount == 1 )
{
Vector2 deltaSinceDown = touch.position - fingerDownPosition[ 0 ];
if( touch.fingerId == fingerDown[ 0 ] &&
( Time.time > firstTouchTime + minimumTimeUntilMove ||
touch.phase == TouchPhase.Ended ) )
{
state = (int)ControlState.MovingCharacter;
break;
}
}
}
}
}
.
.
Page 61, 62, 63
I got a compile time error because of touch0 and touch1 being unassigned… but they get assigned in the Boolean logic above it, I had to assign them to default object to continue… but I don’t feel that is the correct way to progress.
{
for( i = 0; i < touchCount; i++ )
{
touch = touches[ i ];
if( touch.phase == TouchPhase.Began )
{
if( touch.fingerId == fingerDown[ 0 ] &&
fingerDownFrame[ 0 ] == Time.frameCount )
{
touch0 = touch;
gotTouch0 = true;
}
else if( touch.fingerId != fingerDown[ 0 ] &&
touch.fingerId != fingerDown[ 1 ] )
{
fingerDown[ 1 ] = touch.fingerId;
touch1 = touch;
gotTouch1 = true;
}
}
if( touch.phase == TouchPhase.Moved ||
touch.phase == TouchPhase.Stationary ||
touch.phase == TouchPhase.Ended )
{
if( touch.fingerId == fingerDown[ 0 ] )
{
touch0 = touch;
gotTouch0 = true;
}
else if( touch.fingerId == fingerDown[ 1 ] )
{
touch1 = touch;
gotTouch1 = true;
}
}
}
if( gotTouch0 )
{
if( gotTouch1 )
{
Vector2 originalVector = fingerDownPosition[ 1 ] - fingerDownPosition[ 0 ];
Vector2 currentVector = touch1.position - touch0.position;
Vector2 originalDir = originalVector / originalVector.magnitude;
Vector2 currentDir = currentVector / currentVector.magnitude;
float rotationCos = Vector2.Dot( originalDir, currentDir );
if( rotationCos < 1 )
{
float rotationRad = Mathf.Acos( rotationCos );
if( rotationRad > rotateEpsilon * Mathf.Deg2Rad )
{
state = (int)ControlState.RotatingCamera;
}
}
if( state == (int)ControlState.WaitingForMovement )
{
float deltaDistance = originalVector.magnitude - currentVector.magnitude;
if( Mathf.Abs( deltaDistance ) > zoomEpsilon )
{
state = (int)ControlState.ZoomingCamera;
}
}
}
}
else
{
state = (int)ControlState.WaitingForNoFingers;
}
}
.
.
Page 64
state == (int)ControlState.ZoomingCamera )
{
for( i = 0; i < touchCount; i++ )
{
touch = touches[ i ];
if( touch.phase == TouchPhase.Moved ||
touch.phase == TouchPhase.Stationary ||
touch.phase == TouchPhase.Ended )
{
if( touch.fingerId == fingerDown[ 0 ] )
{
touch0 = touch;
gotTouch0 = true;
}
else if( touch.fingerId == fingerDown[ 1 ] )
{
touch1 = touch;
gotTouch1 = true;
}
}
}
if( gotTouch0 )
{
if( gotTouch1 )
{
// Call our Camera Control Function
}
}
else
{
state = (int)ControlState.WaitingForNoFingers;
}
}
// Character control happens at the end of the update function
.
.
Page 65 – 66
the AnimaitonControl class is defined here, but it hasn’t been created yet.
Also this is the function where I realized I had to rewrite the ZoomCamera class.
public Transform cameraPivot;
public GUITexture jumpButton;
public float speed;
public float jumpSpeed;
public float inAirMultiplier = 0.25f;
public float minimumDistanceToMove = 1.0f;
public float zoomRate;
private ZoomCamera zoomCamera;
private Camera cam;
private Transform thisTransform;
private CharacterController character;
//private AnimationController animationController;
private Vector3 targetLocation;
private bool moving = false;
private float rotationTarget;
private float rotationVelocity;
private Vector3 velocity;
// put the following in the start function before ResetControlState call.
thisTransform = transform;
zoomCamera = (ZoomCamera)cameraObject.GetComponent( typeof(ZoomCamera) );
cam = cameraObject.camera;
character = (CharacterController)GetComponent( typeof(CharacterController) );
void FaceMovementDirection()
{
Vector3 horizontalVelocity = character.velocity;
horizontalVelocity.y = 0;
if( horizontalVelocity.magnitude > 0.1 )
{
thisTransform.forward = horizontalVelocity.normalized;
}
}
.
.
Page 67
the tutorial was missing the definition for the var ray, looked at the final JS code and added it in.
{
int count = Input.touchCount;
if( count == 1 && state == (int)ControlState.MovingCharacter )
{
Touch touch = Input.GetTouch(0);
if( character.isGrounded && jumpButton.HitTest( touch.position ) )
{
velocity = character.velocity;
velocity.y = jumpSpeed;
}
else if( !jumpButton.HitTest( touch.position ) && touch.phase != TouchPhase.Began )
{
RaycastHit hit;
Ray ray = cam.ScreenPointToRay( new Vector3( touch.position.x, touch.position.y ) );
if( Physics.Raycast( ray, out hit ) )
{
float touchDist = ( transform.position - hit.point ).magnitude;
if( touchDist > minimumDistanceToMove )
{
targetLocation = hit.point;
}
moving = true;
}
}
}
}
.
.
Page 68, 69
Now after this part in the tutorial they ask you to try it. Make sure you Player object has some values set for speed and jumpSpeed other than 0 or it may seem like nothing is working.
if( moving )
{
movement = targetLocation - thisTransform.position;
movement.y = 0;
float dist = movement.magnitude;
if( dist < 1 )
{
moving = false;
}
else
{
movement = movement.normalized * speed;
}
}
if( !character.isGrounded )
{
velocity.y += Physics.gravity.y * Time.deltaTime;
movement.x *= inAirMultiplier;
movement.z *= inAirMultiplier;
}
movement += velocity;
movement += Physics.gravity;
movement *= Time.deltaTime;
character.Move( movement );
if( character.isGrounded )
{
velocity = Vector3.zero;
}
FaceMovementDirection();
.
.
Page 69 (bottom) 70 (top)
don’t for get to add CameraControl function call where the comment you made previously told you to put it ^_^ Make sure values for TapControl have some default values, thought stuff wasn’t working until I noticed things with values of 0.
{
if( rotateEnabled && state == (int)ControlState.RotatingCamera )
{
// rotate stuff
Vector2 currentVector = touch1.position - touch0.position;
Vector2 currentDir = currentVector / currentVector.magnitude;
Vector2 lastVector = ( touch1.position - touch1.deltaPosition) -
( touch0.position - touch0.deltaPosition );
Vector2 lastDir = lastVector / lastVector.magnitude;
float rotationCos = Vector2.Dot( currentDir, lastDir );
if( rotationCos < 1 )
{
Vector3 currenctVector3 = new Vector3( currentVector.x, currentVector.y );
Vector3 lastVector3 = new Vector3( lastVector.x, lastVector.y );
float rotationDirection = Vector3.Cross( currenctVector3, lastVector3 ).normalized.z;
float rotationRad = Mathf.Acos( rotationCos );
rotationTarget += rotationRad * Mathf.Rad2Deg * rotationDirection;
if( rotationTarget < 0 )
{
rotationTarget += 360;
}
else if( rotationTarget >= 360 )
{
rotationTarget -= 360;
}
}
}
else if( zoomEnabled && state == (int)ControlState.ZoomingCamera )
{
// zooming stuff
float touchDistance = ( touch1.position - touch0.position ).magnitude;
float lastTouchDistance = ( ( touch1.position - touch1.deltaPosition ) -
( touch0.deltaPosition - touch0.deltaPosition ) ).magnitude;
float deltaPinch = touchDistance - lastTouchDistance;
zoomCamera.zoom += deltaPinch * zoomRate * Time.deltaTime;
}
}
.
.
Page 71 (bottom)
Here is another temp value since the JS version is modifying some protected values.
{
Vector3 tempVector3 = cameraPivot.eulerAngles;
tempVector3.y = Mathf.SmoothDampAngle( cameraPivot.eulerAngles.y,
rotationTarget,
ref rotationVelocity,
0.3f );
cameraPivot.eulerAngles = tempVector3;
}