[SOLVED]Handling constant change of controllable object

I’m new here so HI BTW :wink:

I want to do something like this:
Player starts the game with the control of some cute character. During the game he can press R to change focus and control from the Cute Character to a cool looking spaceship. The later he can press R again and he will be controlling the Character again.

I used some scripts and all of the assets from 2d platformer tutorial. I set up key: ‘R’ in Project Settings → Input. Then I wrote a function handling R the way I want (described above). It’s working, but many times when You press R a screen goes crazy, camera is working strange. Besides Controllable State even comes back to the original object. Well. Better if You would see it: http://samur.pl/test/platf2.html

I thought, I had to add some delay, so I found yield. I’ve started Coroutine, etc, but still it’s working just the same. Not the way I want it to.

Here I past my Update script that is handling R button and main game loop.

// An internal reference to the attached CameraScrolling script
private var cameraScrolling : CameraScrolling;

// Who is the player controlling
private var selected = 0;

// List of objects to control
var targets : Transform[];

// What to display on the buttons in the window
var targetButtonNames : String[];

// On start up, we send the SetControllable () message to turn the different players on and off.
function Awake () {

	// Get the reference to our CameraScrolling script attached to this camera;
	cameraScrolling = GetComponent (CameraScrolling);
	
	// Set the scrolling camera's target to be our character at the start.
	cameraScrolling.SetTarget (targets[0], true);
}

var buttonActive : boolean;
var message = "";

function Update () {
	onHandleRButton ();
}

private var windowRect = Rect (20, 20, 250, 50);

function onHandleRButton () {
	StartCoroutine ("handleRButton");
}

function activeChar () {
	targets[1].gameObject.SendMessage ("SetControllable", false, SendMessageOptions.DontRequireReceiver);
	targets[0].gameObject.SendMessage ("SetControllable", true, SendMessageOptions.DontRequireReceiver);
	yield;
	cameraScrolling.SetTarget (targets[0], true);	
}

function activeShip () {
	targets[0].gameObject.SendMessage ("SetControllable", false, SendMessageOptions.DontRequireReceiver);
	targets[1].gameObject.SendMessage ("SetControllable", true, SendMessageOptions.DontRequireReceiver);
	yield;
	cameraScrolling.SetTarget (targets[1], true);
}

function handleRButton () {
	var buttonPressed = Input.GetButton ("R");
    
    if ( buttonPressed )
        {
		
          if ( buttonActive )
          {
			// Button was activated
             message = "Unpressed";
			 
			 // Set Character active
			activeChar();
		  
			buttonActive = false;
          } else {
             // Button was deactivated
             message = "Pressed";
			 
			 // Set SpaceShip active
			activeShip ();
    
             buttonActive = true;
          }
			print( message );
        }
}

@script RequireComponent (CameraScrolling)

Here you have a CameraScrolling script. It was with the tutorial.

// The object in our scene that our camera is currently tracking.
private var target : Transform;
// How far back should the camera be from the target?
var distance = 15.0;
// How strict should the camera follow the target?  Lower values make the camera more lazy.
var springiness = 4.0;

// Keep handy reference sto our level's attributes.  We set up these references in the Awake () function.
// This also is very slightly more performant, but it's mostly just convenient.
private var levelAttributes : LevelAttributes;
private var levelBounds : Rect;

private var targetLock = false;

// This is for setting interpolation on our target, but making sure we don't permanently
// alter the target's interpolation setting.  This is used in the SetTarget () function.
private var savedInterpolationSetting = RigidbodyInterpolation.None;

function Awake () {
	// Set up our convenience references.
	levelAttributes = LevelAttributes.GetInstance ();
	levelBounds = levelAttributes.bounds;
}

function SetTarget (newTarget : Transform, snap : boolean) {
	// If there was a target, reset its interpolation value if it had a rigidbody.
	if  (target) {
		// Reset the old target's interpolation back to the saved value.
		targetRigidbody = target.GetComponent (Rigidbody);
		if  (targetRigidbody)
			targetRigidbody.interpolation = savedInterpolationSetting;
	}
	
	// Set our current target to be the value passed to SetTarget ()
	target = newTarget;
	
	// Now, save the new target's interpolation setting and set it to interpolate for now.
	// This will make our camera move more smoothly.  Only do this if we didn't set the
	// target to null (nothing).
	if (target) {
		targetRigidbody = target.GetComponent (Rigidbody);
		if (targetRigidbody) {
			savedInterpolationSetting = targetRigidbody.interpolation;
			targetRigidbody.interpolation = RigidbodyInterpolation.Interpolate;
		}
	}
	
	// If we should snap the camera to the target, do so now.
	// Otherwise, the camera's position will change in the LateUpdate () function.
	if  (snap) {
		transform.position = GetGoalPosition ();
	}
}

// Provide another version of SetTarget that doesn't require the snap variable to set.
// This is for convenience and cleanliness.  By default, we will not snap to the target.
function SetTarget (newTarget : Transform) {
	SetTarget (newTarget, false);
}

// This is a simple accessor function, sometimes called a "getter".  It is a publically callable
// function that returns a private variable.  Notice how target defined at the top of the script
// is marked "private"?  We can not access it from other scripts directly.  Therefore, we just
// have a function that returns it.  Sneaky!
function GetTarget () {
	return target;
}

// You almost always want camera motion to go inside of LateUpdate (), so that the camera follows
// the target _after_ it has moved.  Otherwise, the camera may lag one frame behind.
function LateUpdate () {
	// Where should our camera be looking right now?
	var goalPosition = GetGoalPosition ();
	
	// Interpolate between the current camera position and the goal position.
	// See the documentation on Vector3.Lerp () for more information.
	transform.position = Vector3.Lerp (transform.position, goalPosition, Time.deltaTime * springiness);	
}

// Based on the camera attributes and the target's special camera attributes, find out where the
// camera should move to.
function GetGoalPosition () {
	// If there is no target, don't move the camera.  So return the camera's current position as the goal position.
	if  (!target)
		return transform.position;
	
	// Our camera script can take attributes from the target.  If there are no attributes attached, we have
	// the following defaults.
	
	// How high in world space should the camera look above the target?
	var heightOffset = 0.0;
	// How much should we zoom the camera based on this target?
	var distanceModifier = 1.0;
	// By default, we won't account for any target velocity in our calculations;
	var velocityLookAhead = 0.0;
	var maxLookAhead = Vector2 (0.0, 0.0);
	
	// Look for CameraTargetAttributes in our target.
	var cameraTargetAttributes = target.GetComponent (CameraTargetAttributes);
	
	// If our target has special attributes, use these instead of our above defaults.
	if  (cameraTargetAttributes) {
		heightOffset = cameraTargetAttributes.heightOffset;
		distanceModifier = cameraTargetAttributes.distanceModifier;
		velocityLookAhead = cameraTargetAttributes.velocityLookAhead;
		maxLookAhead = cameraTargetAttributes.maxLookAhead;
	}
	
	// First do a rough goalPosition that simply follows the target at a certain relative height and distance.
	var goalPosition = target.position + Vector3 (0, heightOffset, -distance * distanceModifier);
	
	// Next, we refine our goalPosition by taking into account our target's current velocity.
	// This will make the camera slightly look ahead to wherever the character is going.
	
	// First assume there is no velocity.
	// This is so if the camera's target is not a Rigidbody, it won't do any look-ahead calculations because everything will be zero.
	var targetVelocity = Vector3.zero;
	
	// If we find a Rigidbody on the target, that means we can access a velocity!
	var targetRigidbody = target.GetComponent (Rigidbody);
	if (targetRigidbody)
		targetVelocity = targetRigidbody.velocity;
	
	// If we find a PlatformerController on the target, we can access a velocity from that!
	targetPlatformerController = target.GetComponent (PlatformerController);
	if (targetPlatformerController)
		targetVelocity = targetPlatformerController.GetVelocity ();
	
	// If you've had a physics class, you may recall an equation similar to: position = velocity * time;
	// Here we estimate what the target's position will be in velocityLookAhead seconds.
	var lookAhead = targetVelocity * velocityLookAhead;
	
	// We clamp the lookAhead vector to some sane values so that the target doesn't go offscreen.
	// This calculation could be more advanced (lengthy), taking into account the target's viewport position,
	// but this works pretty well in practice.
	lookAhead.x = Mathf.Clamp (lookAhead.x, -maxLookAhead.x, maxLookAhead.x);
	lookAhead.y = Mathf.Clamp (lookAhead.y, -maxLookAhead.y, maxLookAhead.y);
	// We never want to take z velocity into account as this is 2D.  Just make sure it's zero.
	lookAhead.z = 0.0;
	
	// Now add in our lookAhead calculation.  Our camera following is now a bit better!
	goalPosition += lookAhead;
	
	// To put the icing on the cake, we will make so the positions beyond the level boundaries
	// are never seen.  This gives your level a great contained feeling, with a definite beginning
	// and ending.
	
	var clampOffset = Vector3.zero;
	
	// Temporarily set the camera to the goal position so we can test positions for clamping.
	// But first, save the previous position.
	var cameraPositionSave = transform.position;
	transform.position = goalPosition;
	
	// Get the target position in viewport space.  Viewport space is relative to the camera.
	// The bottom left is (0,0) and the upper right is (1,1)
	// @TODO Viewport space changing in Unity 2.0?
	var targetViewportPosition = camera.WorldToViewportPoint (target.position);
	
	// First clamp to the right and top.  After this we will clamp to the bottom and left, so it will override this
	// clamping if it needs to.  This only occurs if your level is really small so that the camera sees more than
	// the entire level at once.
	
	// What is the world position of the very upper right corner of the camera?
	var upperRightCameraInWorld = camera.ViewportToWorldPoint (Vector3 (1.0, 1.0, targetViewportPosition.z));
	
	// Find out how far outside the world the camera is right now.
	clampOffset.x = Mathf.Min (levelBounds.xMax - upperRightCameraInWorld.x, 0.0);
	clampOffset.y = Mathf.Min ((levelBounds.yMax - upperRightCameraInWorld.y), 0.0);
	
	// Now we apply our clamping to our goalPosition.  Now our camera won't go past the right and top boundaries of the level!
	goalPosition += clampOffset;
	
	// Now we do basically the same thing, except clamp to the lower left of the level.  This will override any previous clamping
	// if the level is really small.  That way you'll for sure never see past the lower-left of the level, but if the camera is
	// zoomed out too far for the level size, you will see past the right or top of the level.
	
	transform.position = goalPosition;
	var lowerLeftCameraInWorld = camera.ViewportToWorldPoint (Vector3 (0.0, 0.0, targetViewportPosition.z));
	
	// Find out how far outside the world the camera is right now.
	clampOffset.x = Mathf.Max ((levelBounds.xMin - lowerLeftCameraInWorld.x), 0.0);
	clampOffset.y = Mathf.Max ((levelBounds.yMin - lowerLeftCameraInWorld.y), 0.0);
	
	// Now we apply our clamping to our goalPosition once again.  Now our camera won't go past the left and bottom boundaries of the level!
	goalPosition += clampOffset;
	
	// Now that we're done calling functions on the camera, we can set the position back to the saved position;
	transform.position = cameraPositionSave;
	
	// Send back our spiffily calculated goalPosition back to the caller!
	return goalPosition;
}

Maybe someone can help? Thanks! :slight_smile:

What’s happening is that you are checking for the R key being pressed every frame. If the frame rate is fast, it rapidly switches between the two objects until the key is fully unpressed. Try checking for key-down or key-up instead, which will only happen once per keypress.

Alternatively, record the value of Time.time when the switch occurs, and ignore the R key until a second or two has elapsed.

Edit: To be more specific, instead of using Input.GetKey, try Input.GetKeyDown or GetKeyUp.

Thanks :slight_smile: It’s working like a charm. But instead of using: GetKeyUp I had to use GetButtonUp. Because as I see I define buttons not keys… Hmm. What’s the difference?

You should be able to use either, according to the documentation. GetKey would check for a particular key being pressed, like “R”.

GetButton is more flexible- it checks for a VIRTUAL button. You can specify in the project’s Input properties which keyboard keys or mouse buttons map to that virtual button. The player can change what physical keys or buttons map to the virtual button in the startup dialog.

So in the project settings you might define a new virtual button called “Switch Character”. By default it could be mapped to the R key AND also the + key on the keypad, or the right-mouse button if you like. But the user could change this and map it to a button on their joystick before starting the game.

I had to do something similar, although I’m not changing control of the character but simply changing what the camera is looking at based on a mouse click. I found various snippets, but thought I would post the code here as well for anyone trying to do something similar.

It’s very simple, create a target object that you want the camera to be looking at. When the user clicks on something else in the scene, the script moves the target to the position of the object the user clicked on.

The camera has the default Smooth Follow script attached, so it does a nice move to the target’s new position.

//empty RaycastHit object which raycast puts the hit details into
private var hit : RaycastHit;
var newTarget : Transform;

function Update () {
	
	    //check if the left mouse has been pressed down this frame
    if (Input.GetMouseButtonDown(0))
    {
        //ray shooting out of the camera from where the mouse is
        var ray : Ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        if (Physics.Raycast(ray, hit))
        {
        	newTarget = hit.transform;
//            StartCoroutine("Lerp", 1.0, newTarget);
			StartCoroutine(Lerp(1.0,newTarget));
            //print out the name if the raycast hits something
            Debug.Log(hit.collider.name);
            Debug.Log(hit.collider.transform.position);
            
            
        }
    }

}


function Lerp (time : float, target : Transform)
{
  var pos = transform.position;
  var rot = transform.rotation;
  var originalTime = time;
 
   while (time > 0.0)
   {
      time -= Time.deltaTime;
      transform.position = Vector3.Lerp(target.position, pos, time / originalTime);
      transform.rotation = Quaternion.Slerp(target.rotation, rot, time / originalTime);
      yield;
   }
}

Hey Guys,I know this threads a little bit old but its exactly what I’m looking for.I am also looking to change the control of the player from 1 object to another with the press of a button.Only thing is I have 0 knowledge of scripting on my own so I don’t even know where to begin.If anybody can point me in the right direction it would be much appreciated. :slight_smile: Thank-you in advance.

Can you give a bit more detail about what is going on in your game? (What type of objects there are, how and when control shifts from one object to another, etc.)

Well I’m trying to get it to start off with your controlling 1 object and then if you press a button you will the control another object and the other way around. For now I only have a cube game-object as my main character(which i want the game to start out with).Then upon pressing a button(R) it will give the controls to a jetpack i have placed in the scene and let you control it and forget about the cube.If that makes sense. Again heres what i have so far I was using the WoW camera script from another forum post :

// Movement Variables:
private var jumpSpeed:float = 9.0;
private var gravity:float = 20.0;
private var runSpeed:float = 5.0;
private var walkSpeed:float = 30.0;
private var rotateSpeed:float = 150.0;
private var grounded:boolean = false;
private var moveDirection:Vector3 = Vector3.zero;
private var isWalking:boolean = true;
private var moveStatus:String = "idle";
private var xSpeed = 250.0;
private var ySpeed = 120.0;
private var yMinLimit = -40;
private var yMaxLimit = 80;
private var x = 0.0;
private var y = 0.0;


// -------------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------- UPDATE ---------------------------------------
// -------------------------------------------------------------------------------------------------------------

function Update () {
   //
   // Only allow movement and jumps while -----------------  GROUNDED -------------
   if(grounded) {
      moveDirection = new Vector3((Input.GetMouseButton(1) ? Input.GetAxis("Horizontal") : 0),0,Input.GetAxis("Vertical"));
       
      // if moving forward and to the side at the same time, compensate for distance
      // TODO: may be better way to do this?
      if(Input.GetMouseButton(1)  Input.GetAxis("Horizontal")  Input.GetAxis("Vertical")) {
         moveDirection *= .7;
      }
       
      moveDirection = transform.TransformDirection(moveDirection);
      moveDirection *= isWalking ? walkSpeed : runSpeed;
       
      moveStatus = "idle";
      if(moveDirection != Vector3.zero) {
         moveStatus = isWalking ? "walking" : "running";
         if (isWalking){
            // invoke WALK animation here
         } else {
            // call RUN animation here
         }
      } else {
         // call IDLE animation here
      }
      // Jump!
      if(Input.GetButton("Jump"))
      {
         // call JUMP animation here
         moveDirection.y = jumpSpeed;
      }
    }                                                    // END "IS GROUNDED"
   
   // Allow turning at anytime. Keep the character facing in the same direction as the Camera if the right mouse button is down.
   if(Input.GetMouseButton(1)) {
      transform.rotation = Quaternion.Euler(0,Camera.main.transform.eulerAngles.y,0);
   } else {
      transform.Rotate(0,Input.GetAxis("Horizontal") * rotateSpeed * Time.deltaTime, 0);
   }
   
   // Toggle walking/running with the T key
   if(Input.GetKeyDown("t"))
      isWalking = !isWalking;
       
   //Apply gravity
   moveDirection.y -= gravity * Time.deltaTime;
   //Move controller
   var controller:CharacterController = GetComponent(CharacterController);
   var flags = controller.Move(moveDirection * Time.deltaTime);
   grounded = (flags  CollisionFlags.Below) != 0;
   };
   static function ClampAngle (angle : float, min : float, max : float) {
   if (angle < -360)
      angle += 360;
   if (angle > 360)
      angle -= 360;
   return Mathf.Clamp (angle, min, max);
}

// -------------------------------------------------------------------------------------------------------------
// ----------------------------------------------------------- END UPDATE  --------------------------------
// -------------------------------------------------------------------------------------------------------------

@script RequireComponent(CharacterController)

And heres for my camera :

var target : Transform;
var targetHeight = 100.0;
var distance = 2.8;
var maxDistance = 10;
var minDistance = 0.5;
var xSpeed = 250.0;
var ySpeed = 120.0;
var yMinLimit = -40;
var yMaxLimit = 80;
var zoomRate = 20;
var rotationDampening = 3.0;
private var x = 0.0;
private var y = 0.0;
var isTalking:boolean = false;

@script AddComponentMenu("Camera-Control/WoW Camera")

function Start () {
    var angles = transform.eulerAngles;
    x = angles.y;
    y = angles.x;

   // Make the rigid body not change rotation
      if (rigidbody)
      rigidbody.freezeRotation = true;
}

function LateUpdate () {
   if(!target)
      return;
   
   // If either mouse buttons are down, let them govern camera position
   if (Input.GetMouseButton(0) || (Input.GetMouseButton(1))){
   x += Input.GetAxis("Mouse X") * xSpeed * 0.02;
   y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02;
   
   
   // otherwise, ease behind the target if any of the directional keys are pressed
   } else if(Input.GetAxis("Vertical") || Input.GetAxis("Horizontal")) {
      var targetRotationAngle = target.eulerAngles.y;
      var currentRotationAngle = transform.eulerAngles.y;
      x = Mathf.LerpAngle(currentRotationAngle, targetRotationAngle, rotationDampening * Time.deltaTime);
    }
     

   distance -= (Input.GetAxis("Mouse ScrollWheel") * Time.deltaTime) * zoomRate * Mathf.Abs(distance);
   distance = Mathf.Clamp(distance, minDistance, maxDistance);
   
   y = ClampAngle(y, yMinLimit, yMaxLimit);
   
  // ROTATE CAMERA:
   var rotation:Quaternion = Quaternion.Euler(y, x, 0);
   transform.rotation = rotation;
   
   // POSITION CAMERA:
   var position = target.position - (rotation * Vector3.forward * distance + Vector3(0,-targetHeight,0));
   transform.position = position;
   
   // IS VIEW BLOCKED?
    var hit : RaycastHit;
    var trueTargetPosition : Vector3 = target.transform.position - Vector3(0,-targetHeight,0);
   // Cast the line to check:
    if (Physics.Linecast (trueTargetPosition, transform.position, hit)) { 
      // If so, shorten distance so camera is in front of object:
      var tempDistance = Vector3.Distance (trueTargetPosition, hit.point) - 0.28;
      // Finally, rePOSITION the CAMERA:
      position = target.position - (rotation * Vector3.forward * tempDistance + Vector3(0,-targetHeight,0));
      transform.position = position;
   }
}

static function ClampAngle (angle : float, min : float, max : float) {
   if (angle < -360)
      angle += 360;
   if (angle > 360)
      angle -= 360;
   return Mathf.Clamp (angle, min, max);
   
}

And i want the jetpack to act just like the cube but not control the cube when the jetpack is being controlled.And vis versa.