Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Rotating a FPS Character With Third Person Mechanics

Discussion in 'Scripting' started by Metal Head, Oct 19, 2011.

  1. Metal Head

    Metal Head

    Joined:
    Aug 14, 2010
    Posts:
    411
    Sooo what I'm doing here is I have a character, rigged and animated. Most animated characters are used for players in third person games or not used for players at all (enemies, friendly NPCs etc.). In my game however I'm trying to use an animated character model and make it into a FPS system which I then hopefully will be able to release for download and use in my game.

    Now what am I trying to achieve:
    Rotation around the y axis of the character. That's easy - transform.Rotate :D however I wanted to add some more realism to that so I decided that I'll make a system which not only rotates the whole character, but also rotates several bones like the chest, neck and the head. This was also easy to make but what's not easy to make is make the player even out this rotation. See, I don't want to rotate the player and in the meantime rotate the body bones, because the body rotation has constraints, it's limited to some value, because when you turn around in real life you don't do a 180 with your body, but you gently rotate it. So I thought of this:
    When you move the mouse, the whole player would get rotated first, then the system would calculate the difference between player's last rotation value and the current rotation, divide that difference by the number of all the bones that get affected and that way rotate the body. Now that's cool, nothing hard, but the hard part is to make the player "even this out". After I have stopped moving the mouse, I want the body bones to slowly start rotating towards zero and in the mean time, the body to start rotating in the opposite direction so I wouldn't get any change in the camera rotation (which is attached to the head of the character)

    Most of the things I've already done, but that last part I highlighted in bold...I've been struggling with it for a solid amount of time. Here's my code:

    Code (csharp):
    1. // I've only copied the variables needed for this effect
    2.  
    3. var rotationJoints : Transform[]; // An array of all the body joints that we want to rotate
    4. var maxBodyRotation : float = 20; // This is the maximum rotation of all the body joints that we can achieve
    5. var rotationReturnSpeed : float = 5; // This is how fast the bones Lerp to 0 when there's no or low mouse input.
    6.  
    7. private var lastCharacterRotation : float = 0; //The last transform.rotation.y of the character
    8. private var currentBodyRotation : float = 5; // The sum of all the rotations of the body joints
    9. private var lastBodyRotation : float = 0; // The last sum of all the rotations of the body joints
    10. private var initialBodyRotation : float; //The initial rotation of a bone (the same for all the bones that are affected)
    11.  
    12. var publicMouseY : float; //Mouse input Y
    13. var publicMouseX : float; // Mouse input X
    Start function looks like this:
    Code (csharp):
    1. function Start(){
    2. lastCharacterRotation = transform.rotation.y; // Get the current rotation of the character
    3. maxBodyRotation *= Mathf.Deg2Rad; // Turn the max body rotation into Radians since I've made it so I can set Degrees in the editor
    4. initialMaxBodyRotation += Mathf.Deg2Rad; // Same here
    5. }
    Now in FixedUpdate I only rotate the character with a multiplier the number of the affected joints:

    Code (csharp):
    1. transform.Rotate((Vector3.up*publicMouseX*rotationJoints.length));
    And now in Update I have this "masterpiece":

    Code (csharp):
    1.  
    2. currentBodyRotation += transform.rotation.y - lastCharacterRotation; //Add the difference between the last rotation and the current rotation of the player to the body joints.
    3. currentBodyRotation = Mathf.Clamp(currentBodyRotation, -maxBodyRotation, maxBodyRotation); //Clamp the rotation so you don't "oversteer"
    4. lastCharacterRotation = transform.rotation.y; // Set lastCharacterRotation as we don't need the previous data anymore
    5. initialBodyRotation = currentBodyRotation/rotationJoints.length; // Calculate the initial rotation of the bones that needs to be applied
    6. currentBodyRotation = Mathf.Lerp(currentBodyRotation, 0, Time.deltaTime*rotationReturnSpeed); // Lerp the body rotation to 0
    7. transform.rotation.y -= currentBodyRotation - lastBodyRotation; //Subtract the rotation of the body from the world rotation
    8. lastBodyRotation = currentBodyRotation;
    Everything I think works fine except for the last two lines here... I don't know how to make it so the world rotation would properly even out with the body rotation...


    And just so you can see, I rotate the body joints like this:
    Code (csharp):
    1. for(var joint in rotationJoints){
    2. joint.localRotation.z = rotationY*Mathf.Deg2Rad;
    3. joint.localRotation.x = -initialBodyRotation;
    4. }
    Note I'm using the X axis rather than the Y...that's because the coordinates of the bones are kinda f*cked up, but it works great like that.
     
  2. Metal Head

    Metal Head

    Joined:
    Aug 14, 2010
    Posts:
    411
    Still nobody interested in my issue?
     
  3. Metal Head

    Metal Head

    Joined:
    Aug 14, 2010
    Posts:
    411
    Okay so I found one of my major problems. It was that I was executing transform.Rotate in FixedUpdate. I thought that I should execute it there because I'm using a rigidbody, but I don't know what have I been thinking. Sooo everything now is SLIGHTLY better, but the whole thing behaves really weird. The rotation twitches as that system of mine cannot do that rotation alignment properly. But it's not twitchy all the time. For example if I rotate 180 degrees, the rotation would start behaving pretty weird, it won't twitch, but it will continue to rotate and slowly stop as if it's sliding...

    EDIT:
    So I modified the above code by switching two line's places:

    currentBodyRotation += transform.rotation.y - lastCharacterRotation;
    currentBodyRotation = Mathf.Clamp(currentBodyRotation, -maxBodyRotation, maxBodyRotation);
    lastCharacterRotation = transform.rotation.y;
    currentBodyRotation = Mathf.LerpAngle(currentBodyRotation, 0, Time.deltaTime*rotationReturnSpeed);
    initialBodyRotation = currentBodyRotation/rotationJoints.length;
    transform.rotation.y -= currentBodyRotation - lastBodyRotation;
    lastBodyRotation = currentBodyRotation;


    There's no more twitching, but that sliding I'm talking about still occurs.
     
    Last edited: Oct 21, 2011
  4. Metal Head

    Metal Head

    Joined:
    Aug 14, 2010
    Posts:
    411
    Still no answers? Ok. So more info on the problem:

    transform.rotation.y -= currentBodyRotation - lastBodyRotation;
    lastBodyRotation = currentBodyRotation;


    These are the lines that I use to even out the rotation. Without them the code would work fine, but if I rotate my player, after stopping the rotation, the camera would start rotating several degrees backwards because I no longer have those two lines which basically will rotate the whole player so that his rotation compensates the body rotation.

    So I need some help on how to modify those two lines. Just that is all I need. I tried various methods - none worked properly. If you want, I can upload a demo.
     
  5. Metal Head

    Metal Head

    Joined:
    Aug 14, 2010
    Posts:
    411
    No ideas ? I could go without that feature, but that way everything looks much more static. It has been a week now and I still cannot resolve this.
     
  6. bigmisterb

    bigmisterb

    Joined:
    Nov 6, 2010
    Posts:
    4,221
    Your probably not getting an answer because of two reasons. 1) its a spaghetti mess. many parts are hard to put back together. 2) The code is incomplete. You want someone to diagnose the code, but you then want them to figure out why you did everything you did and rewrite everything you have done.

    To answer what I think part of your problem is..... You are altering the rotation's from the Quaternion directly. You should never do this. You should be using the localEulerAngles instead.

    That being said, post all of the code some someone can dissect it better. Only then will you get the answer you are looking for.
     
  7. Metal Head

    Metal Head

    Joined:
    Aug 14, 2010
    Posts:
    411
    I'm sorry! I didn't realize that. I tried to post just some of the code so I don't make everyone read a lot.

    I tried your suggestion and I couldn't make it work. The player stops rotating and only the body rotates.

    So here's the less trimmed version of the code with comments:

    Code (csharp):
    1.  
    2. var rotationJoints : Transform[]; // an array with the joints that have to be rotated
    3. var maxBodyRotation : float = 20;
    4. var rotationReturnSpeed : float = 5;
    5.  
    6.  
    7. private var lastCharacterRotation : float = 0;
    8. private var currentBodyRotation : float = 0;
    9. private var lastBodyRotation : float = 0;
    10. private var initialBodyRotation : float; // initial body rotation has a wrong name. That's the rotation of a single joint
    11.  
    12. var playerCamera : Transform;
    13.  
    14.  
    15. public var publicMouseY : float;
    16. public var publicMouseX : float;
    17. public var control : boolean = true;
    18. private var moveDirection : Vector3; //Direction Of Movement
    19. private var rotationY : float = 0.0; //Vertical Player Rotation
    20. private var rotationX : float = 0.0; //Horizontal Player Rotation
    21. private var localVelocity : Vector3;
    22.  
    23. function Start(){
    24. lastCharacterRotation = transform.rotation.y;
    25. initialMaxBodyRotation = maxBodyRotation/rotationJoints.length;
    26. maxBodyRotation *= Mathf.Deg2Rad;
    27. initialMaxBodyRotation += Mathf.Deg2Rad;
    28. //animation.Play("idle");
    29. }
    30.  
    31. function Update(){
    32. if(control  !LogicGate.inDialog){
    33. publicMouseY = Input.GetAxis("Mouse Y");
    34. publicMouseX = Input.GetAxis("Mouse X");
    35. } else if(LogicGate.inDialog){
    36. publicMouseX = publicMouseY = 0;
    37. }
    38. PlayerControl(Input.GetAxis("Vertical"), Input.GetAxis("Horizontal"), Input.GetButton("Walk"), Input.GetButton("Sprint"), Input.GetButton("Duck"));
    39. }
    40.  
    41.  
    42. function LateUpdate () {
    43. //Rotate the joints from the rotationJoints array
    44. for(var joint in rotationJoints){
    45. joint.localRotation.z = rotationY*Mathf.Deg2Rad; //makes the player look up/down (messed up axes)
    46. joint.localRotation.x = -initialBodyRotation; // makes the body rotate horizontally
    47. }
    48. }
    49.  
    50.  
    51. function PlayerControl(forward : float, sideways : float, walk : boolean, sprint : boolean, duck : boolean){
    52.  
    53. //Handle Body and Character Rotation
    54. transform.Rotate((Vector3.up*publicMouseX*rotationJoints.length));
    55.  
    56. rotationY += publicMouseY;
    57. rotationY = Mathf.Clamp(rotationY, -10, 10);
    58.  
    59. currentBodyRotation += transform.rotation.y - lastCharacterRotation;
    60. currentBodyRotation = Mathf.Clamp(currentBodyRotation, -maxBodyRotation, maxBodyRotation);
    61. lastCharacterRotation = transform.rotation.y;
    62. currentBodyRotation = Mathf.LerpAngle(currentBodyRotation, 0, Time.deltaTime*rotationReturnSpeed);
    63. initialBodyRotation = currentBodyRotation/rotationJoints.length;
    64. transform.rotation.y -= currentBodyRotation - lastBodyRotation;
    65. lastBodyRotation = currentBodyRotation;
    66.  
    67.  
    68. var moveSpeed : float;
    69. if(sprint) moveSpeed = 18;
    70. else if(walk) moveSpeed = 4.5;
    71. else moveSpeed = 18;
    72.  
    73.  
    74. var moveVector : Vector3 = transform.TransformDirection(Vector3(sideways*moveSpeed, -1, forward*moveSpeed));
    75. rigidbody.velocity.x = moveVector.x;
    76. rigidbody.velocity.z = moveVector.z;
    77.  
    78. }
    79.  
    Upon request, I can post a demo for you to see how the character behaves with this code. Otherwise, you can just rename the inputs from the script and put it on a rigged character in Unity to see what's wrong (attach a camera to the head of the character)
     
  8. Metal Head

    Metal Head

    Joined:
    Aug 14, 2010
    Posts:
    411
    OK, here's an archive with the current result I have:
    http://www.mediafire.com/?ugi8o0w4d2vbxyb
    I just cannot fix this thing...no matter what I do. I hope that when you see it in practice and see how it behaves, maybe somebody will be able to help me :)
     
  9. BlackMantis

    BlackMantis

    Joined:
    Feb 7, 2010
    Posts:
    1,475
    I only read your topic title so i don't know for sure what your looking for.

    If you search for a topic called wow cam you will find a few examples of scripts that will let you rotate the character and not the camera in third person.
     
  10. Metal Head

    Metal Head

    Joined:
    Aug 14, 2010
    Posts:
    411
    You should have read the topic. It's not about that. I'm doing a first person character, but with third person mechanics. So what I need to to is rotate the character and rotate a few parts of the body as well and make them work in conjunction.
     
  11. BlackMantis

    BlackMantis

    Joined:
    Feb 7, 2010
    Posts:
    1,475
    So you will be able to see the body(lower parts of the body)?

    After reading your post it seems like you can use animations for this task. What you could do is adjust the speed of the animation for each bone. You can then smooth your rotations back by reversing your animations. Or using Quaternion.Slerp.
     
  12. Metal Head

    Metal Head

    Joined:
    Aug 14, 2010
    Posts:
    411
    No, that's still not the case. Have you checked the demo I've uploaded?
    There's several bones in the body that can be rotated. When I rotate the whole character, those bones rotate too and then from the rotation of the whole character, I subtract the rotation of the bones. Animations are not going to work here.
     
  13. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    You haven't included a Mac version in your demo, so I couldn't get much from it. (Not to be rude)

    It's not exactly clear, but are you going for rotating the legs if the upper body rotates to the side too much?

    Using what Black Mantis said, I think you should go for Quaternion.Slerp, and

    Code (csharp):
    1.  
    2. if (upperbodybone.rotation.y > whatevernumber || upperbodybone.rotation.y < whatevernumber)
    3. transform.rotation.y = Quaternion.Slerp(transform.rotation, upperbodybone.rotation, speed * Time.deltaTime).y;
    4.  
    etc.

    And separate the bones of the upper and lower body, so you can animate both separately.

    It sounds like you have something in the wrong order or un-parented if your bones are rotating weirdly.

    Sorry I can't be more help, but maybe you could explain your point a little clearer?
     
  14. Metal Head

    Metal Head

    Joined:
    Aug 14, 2010
    Posts:
    411
    Thanks for the replies! (Finally somebody saw my topic, yey!)
    So it's a little complicated. I don't know how to explain what I'm doing even in my own language.
    I have a rigged character (using a Biped Skeleton) imported from Max. I am rotating the character with the transform.Rotate function.
    Based on how much I rotate the character, I add rotation to a variable, called "currentBodyRotation". I have an array of bones: Spine1, spine2, spine3, neck, head etc. So that currentBody rotation variable gets divided by the number of the array elements. That's how I get the rotation I need to apply to a single bone from the bones in the array. I then rotate all the bones from the array with that rotation in the same direction I rotate the whole character. Then from the CHARACTER's rotation I subtract the difference between the rotation of all the bones in the array and their rotation from the previous frame:
    transform.rotation.y -= currentBodyRotation - lastBodyRotation;

    I just don't want my player to look so static when I rotate him. For example in the f.e.a.r. game or Duke Nukem you can see your legs, but when you rotate horizontally, the whole body is static to the camera and it doesn't look real. That's why I just want to add some movement in the upper part of the body.

    Here's a MAC version:
    http://www.mediafire.com/?n7ioerrdlgsk4as

    This is as clear as I can explain it.

    I never Lerp the rotation actually. I have that currentBodyRotation variable that holds the amount that the character's body should be rotated with. Maybe that's the root of my problem... I have no other idea on how to do this whole thing.
     
  15. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    "Damaged or incomplete data in files"

    but it's okay, I know what you're trying to get at! :p

    What I have to say is--if you're dividing the rotation throughout the spine bones in the Update function, (which you should) make sure you're multiplying by Time.deltaTime which is the time it takes for 1 frame to render... so it ends up with dividing and subtracting once per second--smooth movement. And then you can multiply with a number like 4 to get faster movement, or by .4 to get slower movement.

    http://unity3d.com/support/documentation/ScriptReference/Time-deltaTime.html
     
  16. Metal Head

    Metal Head

    Joined:
    Aug 14, 2010
    Posts:
    411
    Yeah, you got me right, but I'm pretty much doing this already:
    currentBodyRotation = Mathf.LerpAngle(currentBodyRotation, 0, Time.deltaTime*rotationReturnSpeed);
    See, it Lerps with amount Time.deltaTime, so the rotation is pretty much real-time. And all the other actions are based on this thing actually, because I only have subtractions from the current body rotation.

    I am making the build for Mac OS X Universal. I just archived the copy of the project with WinRar. How should I build it if that does not work. Sorry, I'm just not familiar with Mac at all :D