Search Unity

Constraining physics to cylindrical surface

Discussion in 'Physics' started by Franolich, May 3, 2015.

  1. Franolich

    Franolich

    Joined:
    Feb 12, 2013
    Posts:
    6
    EDIT - Solution found! See reply below

    To help me learn Unity 5 (on Windows 7 64-bit) I am creating a clone of a platform game. The platform blocks are organised in a grid wrapped around a cylindrical tower. The player character, enemies and other level elements are constrained to a fixed radius around the tower axis. The camera is always on a horizontal line from the tower axis to the centre of the player and looks towards the axis - ie the camera follows the player around the tower with the player always at the centre of the viewport. The original game used orthographic projection - everything rendered as bitmaps - while I am working with perspective projection - tower, platforms, etc all 3D meshes.

    My difficulty at the moment is in deciding how best to use Unity's physics engine to apply the cylindrical constraint and the following gameplay rules:

    -- The player is always side-on to the camera ie is pointing along a tangent of the tower.
    -- When the player reaches an obstacle - a platform at a different height to the one he is walking on - the player automatically hops vertically. If the obstacle is not too high, the player is then moved horizontally onto the obstacle.
    -- The player is affected by gravity, ie falls when jumping or walking off a platform edge.
    -- In addition to fixed platform blocks, there are disappearing blocks (disappear when the player lands on top of them but not when hit from the sides or underneath) shootable blocks (disappear when shot by the player) and sliding blocks (cause the player to move in a particular direction when there is no player directional input).
    -- Horizontal enemies travel in a horizontal plane around the tower, only changing direction when they collide with a platform or another enemy. Similarly for vertical enemies.
    -- Rolling ball enemies travel along adjacent horizontal platforms and change direction when they collide with the side of another platform at a different height or have reached the edge of the last adjacent platform at the same height.
    -- Bouncing ball enemies move horizontally and are affected by gravity but always bounce to same height.
    -- When the player collides with an enemy, the player is sent tumbling on a vertical trajectory and eventually lands on a lower platform (if there is one). But the enemy trajectory is unaltered.

    Below is what I've tried so far and the problems I've had. Any ideas on how I could do things better would be most welcome.

    It seems obvious that the permanently fixed platform blocks should be static colliders (non-trigger collider, no rigid body component). This has not varied in any of my experiments. There is the issue as to what shape collider I should use (currently using a MeshCollider as the platforms are not quite close enough to being rectangular) but I won't go into that here - this post is already too long!

    Before attempting to apply the above rules, my first experiment was with rolling balls (rigid bodies) that could bounce and fall onto lower platforms. In the FixedUpdate() method I:

    -- scaled the XZ components of the position to a fixed distance from the tower axis
    -- rotated the XZ component of the velocity so that it was tangential to the tower
    -- set the angular velocity axis to a horizontal line from the tower axis to the ball centre

    The result of this looked great and collision detection seemed to be unaffected. However I suspect using such a method might significantly affect performance if used on many rigid bodies. Further it does not really help in applying many of the rules above. And finally I get the impression that one should be setting things up to make the most of the physics engine, ie get it to do most of the work and not fight the results it generates too much.

    I then tried implementing player control. Note that I am using the State design pattern with state sub-classes (Stationary, Walking, Hopping, Falling, etc) all implementing an Update() method. The current state's Update() method is called from the script's MonoBehaviour.Update() method.

    I began with adding a RigidBody component to the player object and tried using AddForce(vel,ForceMode.VelocityChange). But it was not obvious to me what velocity change would maintain a constant distance from the tower axis, a constant walking speed, and keep the player pointing in a direction tangential to the tower. Setting the absolute velocity in FixedUpdate() might be easier to manage but, as above, this seems undesirable and anyway I believe Input is not updated until just before calls to Update(). I also found that most of my attempts at using AddForce() led to the player rolling - though now I relaise I could have used RigidBody.freezeRotation.

    I thought of making the player a kinematic rigid body but this meant having to implement collision detection with platforms myself - otherwise the player passed through platforms

    So that left the CharacterCollider option which has its own set of problems:

    -- The vertical capsule collider is not a great fit for my squat character mesh but is just about acceptable. However the rounded ends of the collider can cause the player to get stuck on the corners of platforms. In OnControllerColliderHit(hit) I had to check if hit.normal was pointing upwards and only then consider the player to be properly grounded.

    -- I had to set stepOffset to 0 because CharacterCollider automatic stepping is instantaneous while I need hopping onto a platform to take a non-zero time.

    -- In order to ensure the player does not alter enemy trajectories on collision, I had to make the enemies kinematic rigid body trigger colliders. I also had to implement OnTriggerEnter() in the player script to make the player fall. Actually making enemies kinematic makes sense, particularly for those that move horizontally and have curved trajectories around the tower.

    -- Obviously disappearing blocks and shootable blocks should not be static colliders. As non-kinematic rigid bodies, they properly interact with a CharacterCollider in most respects. However they do not receive trigger/collision messages and instead I had to use OnControllerColliderHit() in the player control script. (I assume this is because collision handlers are for use in the FixedUpdate()/internal physics update loop while OnControllerColliderHit deals with collisions resulting from calls to CharacterCollider.Move().)

    But I don't want the logic for interacting with these platform types to be in the player script - this should happen in the platform scripts. So what is the best way of notifying platforms of a collision with the player?

    I already have all platform types implementing an ILevelElement interface (used for initialisation when loading the level or resetting the level after a life is lost). I suppose I could add a ReactToPlayer() method to this interface and use it from the player script.

    Code (CSharp):
    1. hit.gameObject.GetComponent<ILevelElement>().ReactToPlayer()
    But this seems an unnecessary performance hit considering most platforms are fixed and do not have any special behaviour on player contact. It would be so much better if CharacterCollider sent messages to objects it collides with.

    -- I need to be able to detect when the player is on the edge of platform with no adjacent platforms since in this teetering state any further directional input should cause the player to fall backwards off the platform. (This is also relevant to making rolling balls change their direction upon reaching a platform edge.)

    -- A severe limitation of the CharacterCollider component is the restriction to a vertical capsule collider. What if, for example, I wanted to make the player able to fight ie kick and punch etc? In that case I would have thought a compound collider would be appropriate - colliders on child game objects for fists and feet etc. But this would need a RigidBody component and this is not allowed in combination with a CharacterCollider.

    All my attempts to keep the player facing a direction tangential to the tower have met with problems. For example calling my MoveFace() method (below) after calls to CharacterCollider.Move() causes some very strange behaviour. Say I'm walking to the left (angle to x-axis drops from 360 degrees) everything seems fine until at 300 degrees the player lurches by a much bigger angle change than expected. If I walk a bit further, the player is then rapidly spun around the tower to around 12 degrees.

    My suspicion is that the rotation messes up collision detection somehow causing calls to Move() to behave unpredictably. But this leaves my wondering what to do to keep the player facing the correct direction...

    Code (CSharp):
    1. public void MoveFace ()
    2. {
    3.     Vector3 angles = transform.localEulerAngles;
    4.     angles.y = 360f - XAxisAngleDegrees(transform.localPosition);
    5.     transform.localEulerAngles = angles;
    6. }
    7.  
    8. public float XAxisAngleDegrees (Vector3 v)
    9. {
    10.     Vector2 vXZ = new Vector2(v.x, v.z);
    11.     float angle = Mathf.Acos(Mathf.Clamp(vXZ.normalized.x, -1.0f, 1.0f));
    12.     angle *= (180.0f / Mathf.PI);
    13.     if (vXZ.y < 0.0f)
    14.     {
    15.         angle = 360.0f - angle;
    16.     }
    17.     return angle;
    18. }
    19.  
    20. /*Note that the player and camera game objects are children of
    21. an empty game object located at the world origin. This is merely to
    22. make organisation simpler for the split-screen multiplayer mode and
    23. is why I refer to localPosition and localEulerAngles in the above.*/
     
    Last edited: Sep 6, 2015
  2. Franolich

    Franolich

    Joined:
    Feb 12, 2013
    Posts:
    6
    The answer is to use a joint to apply the constraint. I got the idea from aldonaletto's reply to the thread Constrain rigid body movement to a sphere.

    I now have a component that applies the constraint by:
    • Creating an axis rigid body located on the tower axis.
    • Setting the axis rigid body constraints to only allow translation along and rotation around the y-axis.
    • Setting both the constrained rigid body and axis rigid body mass to half the constrained rigid body's original mass.
    • Creating a hinge joint and attaching it to the constrained rigid body.
    • Setting the hinge joint's connected body to be the axis rigid body.
    Then one can apply forces to the constrained rigid body as normal with the component ensuring the body is always:
    • a fixed distance from the tower axis
    • pointing along along a tangent to the tower (provided the hinge axis angle limits are both set to 0)
    A fixed joint would achieve the same result but I use a hinge joint to add flexibility. For example a constrained sphere can be allowed to roll by using a hinge axis parallel to a radial vector (from the tower axis to the constrained body's
    position) and turning off the axis limits.

    The image below shows constrained rolling spheres with
    • red lines = horizontal radial vectors
    • yellow lines = hinge joint axes
    • white lines = sphere forward vectors

     
    Last edited: Sep 6, 2015