(Dis)continuous (Dis)orientation: A Unity VR Game

Final Presentation:

Class: Coding for Design II

This class is focused on teaching C# and Unity to the capacity of designing and creating meaningful spatial interactions. For our final project, we were tasked with creating a spatial interaction of our choosing that abstracts from the way we conventionally understand or move throughout a space.

Group Members:

Leon Carranza (Lead Programmer)

Prarthana Jathar

Nandini Mohan

Final Implementation and Gameplay:

The way the player moves in our game subverts the way we conventionally understand movement in architecture. Rather than relying on our abstraction of flat space on earth and a singular direction of down, the player must navigate across discontinuous meshes where their curvature is apparent. Constraining the player to moving along mesh normals, the player must move objects throughout space in order to move continuously towards a goal.



Process:

Moving a player-controlled object along the normals of a mesh

My first milestone in programming was getting a player-controlled object to move along a mesh's normal. I found that the mesh itself must be concave, but also that the angle changes were often too sharp and sudden. My next goals were both to smoothen sudden angle changes through quaternion lerping and to allow for continuous movement along multiple meshes as long as they intersected.


Moving continually across intersecting meshes

I next made movement continuous as the player moved themselves between intersecting meshes. This involved constantly checking if the player was about to approach a mesh they weren't already stuck to using a spherecast.


Smooth movement and controls

The addition of quaternion lerps meant that players would now smoothly rotate from one rotation position to another. Additionally, input from the VR headset and controllers could now be collected to allow players to rotate and move objects throughout space. This allows for player-made intersections and paths.


More Final Gameplay Footage

It was now time to place the player in the intended mobius strip city. The overall shape of the city, the way the player must navigate it, and the materials all work to contribute to the defamiliarization of a space that is, at a glance, familiar to the player.



Code Snippets

Custom Movement:

    void CustomMovement()
    {
        var stuck = false;

        Vector3 new_p = transform.position;



        if (_inputData._rightController.TryGetFeatureValue(CommonUsages.primary2DAxis, out Vector2 _rightAxis))
        {
            _rightAxis = new Vector2(_rightAxis.x, _rightAxis.y);
            transform.Rotate(transform.forward, _rightAxis.x * 5.0f, Space.World);
        }

        if (_inputData._leftController.TryGetFeatureValue(CommonUsages.trigger, out float _triggerQuant))
        {
            _trigger = _triggerQuant;
        }

        if (_inputData._leftController.TryGetFeatureValue(CommonUsages.primary2DAxis, out Vector2 _leftAxis) && _trigger == 0)
        {
            _leftAxis = new Vector2(_leftAxis.x, _leftAxis.y);
            transform.position += transform.up * _leftAxis.y * speed * Time.deltaTime;
        }

        sticky_collider = stickTo();
        
        Vector3 cp = sticky_collider.ClosestPoint(new_p);

        Ray r = new Ray(new_p, cp - new_p);

        if (sticky_collider.Raycast(r, out var hit, Mathf.Infinity))
        {
            if (stuck == false)
            {
                transform.position = cp;
                Quaternion OriginalRot = transform.rotation;
                rotationGoal = Vector3.Lerp(new_p, cp + hit.normal, 10f * Time.deltaTime);
                transform.LookAt(rotationGoal, transform.up);
                Quaternion NewRot = transform.rotation;
                transform.rotation = OriginalRot;
                transform.rotation = Quaternion.Lerp(transform.rotation, NewRot, speed * Time.deltaTime);
                stuck = true;
            }

            else if (stuck == true)
            {
                transform.position = cp;
                Quaternion OriginalRot = transform.rotation;
                rotationGoal = Vector3.Lerp(new_p, cp + hit.normal, 10f * Time.deltaTime);
                transform.LookAt(rotationGoal, transform.up);
                Quaternion NewRot = transform.rotation;
                transform.rotation = OriginalRot;
                transform.rotation = Quaternion.Lerp(transform.rotation, NewRot, speed * Time.deltaTime);
                stuck = false;
            }
        }
    }
              

Sticking to Mesh Collider:

                private Collider stickTo()
                {
                    origin = transform.position;
                    direction = transform.up;
                    RaycastHit spherehit;
                    if (Physics.SphereCast(origin, sphereRadius, direction, out spherehit, maxDistance, layerMask, QueryTriggerInteraction.UseGlobal))
                    {
                        currentHitObject = spherehit.transform.gameObject;
                        currentHitDistance = spherehit.distance;
                        sticky_collider = spherehit.collider;
                    }
                    else
                    {
                        currentHitDistance = maxDistance;
                        currentHitObject = null;
                    }
                    return sticky_collider;
                }