Search Unity

Click to move a character

Discussion in 'Scripting' started by Screenhog, Aug 25, 2011.

  1. Screenhog

    Screenhog

    Joined:
    Jul 2, 2009
    Posts:
    498
    All right, I'm sure this has been done before, but my searching on the Unity forums hasn't been particularly fruitful.

    I want to make a character move toward a location with a single mouse click. Pathfinding is not necessary, but it needs to be able to react properly to terrain (if the clicked point is on the other side of a hill, it would walk up the hill, and then react to gravity on the way back down). When the character reaches the destination, it stops. The character can use either a CharacterController or a Rigidbody, it doesn't really matter to me which (although there will hopefully be many characters onscreen, so if one is better on performance than the other, that would be good to know)

    Has this already been built, and if so, can someone provide a link for it?
     
  2. handsomePATT

    handsomePATT

    Joined:
    Nov 30, 2010
    Posts:
    574
    look at the itween examples. this exact thing is in there
     
  3. bigmisterb

    bigmisterb

    Joined:
    Nov 6, 2010
    Posts:
    4,221
    Two basic things here.. facing your character towards a mouse position, and moving to that position

    The first subject was covered by another guys thread

    http://forum.unity3d.com/threads/101900-rotate-object-towards-mouse-position

    The second would be covered in the CharacterController.Move documentation

    http://unity3d.com/support/documentation/ScriptReference/CharacterController.Move.html

    you would need to write a Coroutine, that moves the character until he is within a certain distance of the mouse position you clicked. (say 0.2 units)

    The key would be to check his position in the X and Z directions, not the Y.
     
  4. Screenhog

    Screenhog

    Joined:
    Jul 2, 2009
    Posts:
    498
    I had started with CharacterController.Move, but since that was built with keyboard input in mind, I didn't know what new value to give moveDirection in the example script.

    I'll have to check out iTween.
     
  5. sawfish

    sawfish

    Joined:
    Feb 12, 2011
    Posts:
    314
    Look at the Penelope tutorial. Yes, it is for mobile, BUT, it has this exact feature, only for where you touch it does a ScreenToWorld raycast, etc.
     
  6. Screenhog

    Screenhog

    Joined:
    Jul 2, 2009
    Posts:
    498
    Well... it sort of works... but it's really choppy.

    Code (csharp):
    1.  
    2. var speed : float = 6.0;
    3. var gravity : float = 20.0;
    4. private var hit: RaycastHit;
    5. private var moveDirection : Vector3 = Vector3.zero;
    6. private var destination : Vector3;
    7.  
    8. function Start() {
    9.     destination = transform.position;
    10. }
    11.  
    12. function Update() {
    13.     if (Input.GetMouseButtonDown(0)){ // if the left mouse button is clicked
    14.         var ray = Camera.main.ScreenPointToRay (Input.mousePosition); //cast a ray through the current mouse position from the camera
    15.         if (Physics.Raycast (ray, hit, 100)  hit.collider.gameObject.tag=="Ground") { //if the ray hits the ground
    16.             Debug.Log (hit.point);
    17.             destination = hit.point;
    18.         }
    19.     }
    20.    
    21.     var controller : CharacterController = GetComponent(CharacterController);
    22.    
    23.     if (destination != transform.position) {
    24.             if ((destination-transform.position).sqrMagnitude > 1.0) { //yes, this is a choppy way of getting to the endpoint... I'll fix this later
    25.             var relativePos = destination - transform.position;
    26.             relativePos.y = transform.position.y;
    27.             var rotation = Quaternion.LookRotation(relativePos);
    28.             transform.rotation = rotation;
    29.  
    30.             if (controller.isGrounded) {
    31.                     moveDirection = Vector3.forward;
    32.                     moveDirection = transform.TransformDirection(moveDirection);
    33.                     moveDirection *= speed;
    34.             }
    35.        
    36.             // Apply gravity
    37.                 moveDirection.y -= gravity * Time.deltaTime;
    38.            
    39.                 // Move the controller
    40.                 controller.Move(moveDirection * Time.deltaTime);
    41.             } else {
    42.                 transform.position = destination;
    43.             }
    44.     }
    45. }
    46.  
    Ugh... sorry about the tabs being so huge. Copy and paste from MonoDevelop hasn't been my friend.
     
  7. bigmisterb

    bigmisterb

    Joined:
    Nov 6, 2010
    Posts:
    4,221
    Tabs are fine. :)

    A little more elegant soloution... (coroutine)

    // make sure to debug it first.
    Code (csharp):
    1.  
    2. var speed : float = 6.0;
    3. var gravity : float = 20.0;
    4. private var destination : Vector3;
    5. private var iClicked=false;
    6.  
    7. function Start() {
    8.     destination = transform.position;
    9.     StartCoroutine(myCoroutine());
    10. }
    11.  
    12. function Update() {
    13.     if (Input.GetMouseButtonDown(0)){ // if the left mouse button is clicked
    14.         var hit: RaycastHit;
    15.         var ray = Camera.main.ScreenPointToRay (Input.mousePosition); //cast a ray through the current mouse position from the camera
    16.         if (Physics.Raycast (ray, hit, 100)) { //if the ray hits the ground
    17.             if(hit.collider.gameObject.tag=="Ground"){
    18.                 destination = hit.point;
    19.                 iClicked=true;
    20.             }
    21.         }
    22.     }
    23. }
    24.  
    25. function myCoroutine(){
    26.     var controller : CharacterController = GetComponent(CharacterController);
    27.    
    28.     while(true){
    29.         if(iClicked){ // if we click a mouse, look at the point we clicked
    30.             transform.LookAt(destination);
    31.             iClicked=false;
    32.         }
    33.        
    34.         var moveDirection=Vector3.zero;
    35.         // see if our destination is in front of us
    36.         var z=transform.InverseTransformPoint(destination).z;
    37.         if(z>0.2){
    38.             // if it is, see if we are on the ground
    39.             if (controller.isGrounded) {
    40.                 // if we are, move
    41.                 moveDirection = transform.forward;
    42.             }
    43.             // Apply gravity
    44.             moveDirection.y -= gravity * Time.deltaTime;
    45.             controller.Move(moveDirection * speed * Time.deltaTime);
    46.         }
    47.         yield;
    48.     }
    49. }
    50.  
     
    Last edited: Aug 25, 2011
  8. Screenhog

    Screenhog

    Joined:
    Jul 2, 2009
    Posts:
    498
    I'm pretty unfamiliar with Coroutines... how might this error be fixed? It says "No appropriate version of 'UnityEngine.MonoBehaviour.StartCoroutine' for the argument list '(void)' was found."
     
  9. bigmisterb

    bigmisterb

    Joined:
    Nov 6, 2010
    Posts:
    4,221
    woops, forgot the yield. In Javascript a function will be defined as an IEnumerator, if you use a yield or yielding method within it's bounds. Since I forgot one, it wasn't thinking it was one.

    I corrected the code above
     
  10. Screenhog

    Screenhog

    Joined:
    Jul 2, 2009
    Posts:
    498
    Well, it fixed the bug, and I greatly appreciate the help... but I still don't know why the script is producing such a choppy result.

    EDIT: Hmm... that's odd. When I put it in a new scene, it worked quite well. Perhaps something else I'd done in my old scene was causing it grief. I'll keep you posted.
     
    Last edited: Aug 26, 2011
  11. terminal205

    terminal205

    Joined:
    Sep 4, 2011
    Posts:
    259
    Hi Screenhog,

    Any luck identifying the cause of the choppy issue? I'm always on the look out for practices to avoid.