Type to Search

Project GAL DevLog 2

Posted by : on

Category : game-projects


Arm'n Aimer

Aiming Mechanic Development in Project GAL


See the ongoing project page here!

Introduction

This Project Gal Dev Log provides a brief overview of the first-person aim-control I implemented in a recent development sprint. I will go over my initial goal for this sprint, the challenges that I ran into, and a brief discussion of my thought processes leading to accomplishing this sprint goal.

Initial Goal

Since Gal is inspired by the classic carnival gallery shooting games, the initial goal was to allow the player an angle-restricted first-person camera control, using the mouse/gamepad. A sub-challenge emerged after accomplishing this goal, where the player-model's weapon-hand did not follow the player's view as it should in a first-person shooter-type game. A bulk of this sprint was spent solving this aspect.

pop.gif
Above:Just like its carnival counterpart, spring-loaded targets appear from behind various stage props. In Gal, the player traverses the level on a dolly-track. The dolly track aspect is inspired by my love of the Disneyland attraction: Toy Story Midway Mania.

First-Person Aim Controls

Challenge:

Since Gal is a sort-of fps game, the primary task was to implement fps-style aiming controls.

I came to an initial working-solution for the fps control implementation; however, I never felt satisfied with the overly complicated nature of the technique I was adapting. This solution required the use of a custom Cinemachine Virtual Camera Extension. The extension script constantly threw warning messages in my console.

In an attempt to simplify things, I imported the official Unity Starter Package First-Person Controller. After importing the package, my editor started to crash occasionally.


Approach/Solution

To eventually overcome the instability caused by the First-Person Controller package, I created a new empty Unity project. I imported the asset package to this project and deleted anything I didn't need. I then exported it as a new package with a trimmed set of assets. The editor crash issue has since ceased.

From then on, player-controlled Camera rotation was made possible using the Unity Input System Package. A Player Input Component on my Input Manager game object is in charge of triggering a Unity Event for each of the registered input action the user performs.

In my case, the OnLook input event trigger was set to call the FirstPersonController.onLook(CallbackContext) function every time the player moves the mouse / joystick.


OnLook.png

The player camera's updated pitch and yaw are calculated and applied to the camera's transform each time the OnLook(CallbackContext) function is called. Inside this OnLook(CallbackContext) function is a simple call to a pre-existing function from the Starter Asset First-Person Controller Script I'm modifying:

// Public function for the Unity Event to call when a "Look" occurs
        public void OnLook(CallbackConteblockxt context) {
        
            CameraRotation(context.ReadValue<Vector2>());
        }

Player View Range Too Wide/Tall

Challenge

Initially, players had the ability to view in all directions with no angular-limit, which turned out to be the source of a few distinct issues:

  • Can see parts of player's model that should not be seen (bad for immersion)
  • Can shoot back of horse head (undesirable behavior)
  • Arm passes through player-model body (unrealistic / bad for immersion)
  • Gives more time to hit targets already passed-by (reduces prioritization challenge)

Approach/Solution

As can be seen in the following snippet, camera rotation is being clamped in 4 distinct directions. The original file I am modifying only clamped the two-axes symmetrically (one clamp for horizontal, one clamp for vertical).

I introduced the 4-direction clamp to remedy a couple of issues:

  • Upward clamp
    • Keeps the player from looking too far into the sky (there's no targets up there currently)
    • Promotes target prioritization.
  • Downward clamp
    • Restricts view of untidy off-camera aspects
    • Removes temptation to shoot your horse
  • Horizontal clamp
    • Remains symmetrical
    • Distinct left/right clamps provided for future potential and to maintain consistency with up/down clamp pattern.

// CameraRotation provided with StarterAsset Package
// I modified it by adding distinct 4-way clamp on camera pitch/yaw 
private void CameraRotation(Vector2 lookVec) {
    
    /*
        Other Code
    */

    // Clamp pitch 
    // ("Upward" pitch has a separate purpose than "Downward pitch)"
    _cinemachineTargetPitch = 
        ClampAngle(_cinemachineTargetPitch, BottomClamp, TopClamp);
    
        // Clamp yaw 
    // (Remains symmetrical, but given optional "Leftward"/"Rightward")
    _cinemachineTargetYaw = 
        ClampAngle(_cinemachineTargetYaw, LeftClamp, RightClamp);
    
        // Update Cinemachine camera target pitch and yaw
    CinemachineCameraTarget.transform.localRotation =
        Quaternion.Euler(_cinemachineTargetPitch, _cinemachineTargetYaw, 0.0f);
}

// Pre-existing code that is called within the CameraRotation function
private static float ClampAngle(float lfAngle, float lfMin, float lfMax) {

    if (lfAngle < -360f) lfAngle += 360f;
    if (lfAngle > 360f) lfAngle -= 360f;
    return Mathf.Clamp(lfAngle, lfMin, lfMax);
}

I was able to determine the optimal clamp values by exposing a float value for each clamp to the editor. This allowed me to adjust during play-mode testing.


// How far in degrees can you move the camera upward
[SerializeField] private float TopClamp = 90.0f;

// How far in degrees can you move the camera downward
[SerializeField] private float BottomClamp = -90.0f;

// How far in degrees can you move the camera leftward
[SerializeField] private float LeftClamp = 90.0f;

// How far in degrees can you move the camera rightward
[SerializeField] private float RightClamp = -90.0f;

Model Artifacts Permeating View

Challenge:

Additionally, with this solution in place, I began to see player-model artifacts permeating the camera frustum. These were small cross-sections of the player-model's face. The solution itself didn't cause these artifacts, but since the camera was not in the player's control, new issues such as this began to surface.


Approach/Solution

I solved the issue with model artifacts bleeding into view quickly by simply translating the neck bone from the player character armature toward the inside of the player character's model.


peekaboo.gif
Above: The model's neck bone being returned to its original position.

Player-Model Arm Orientation Issues

Challenge

Haphazard importation of 3rd-party models and assets throughout the early development stages left some prefab's transforms in awkward states of disorientation. This made it especially difficult to get a consistent set of vectors to use when trying to manipulate the player-model's arm toward the camera/aim.


Approach/Solution

Armature Reorientation:

To address the orientation issues, I aligned the left-arm into a T-pose, ensuring that all arm bones pointed uniformly forward. I then carefully aligned the revolver pistol into the arm's hand, aligning it with the arm's forward vector direction.


armature.PNG
Above: The model's left-hand armature in alignment.

This was a key step in attaining the expected arm behavior. I could then easily place an Animation Rigging package Multi-Aim Constraint component in the armature hierarchy to automatically do the work of rotating the shoulder of the arm/hand holding the gun, effectively aiming the revolver!

Check out the Unity docs for more on Multi-Aim Constraint


aim-sphere.png
Above: The MultiAimConstraint Component

aim-around.gif
Above: The arm aiming as intended.

Aim Point Determination:

A raycast was employed to determine the player's aim point. The ray was cast from the player toward the world mouse position. The world mouse position was easily attained via the CallbackContext object passed to the OnLook function when the input asset system invoked the OnLook function.


// transformPos is the transform component of a sphere GameObject
// that is passed in through the editor
// It acts as a visual debugging tool
transformPos = null;

// Calculate screen center point with simple arithmetic
Vector2 screenCenterPoint = new Vector2(Screen.width / 2f, Screen.height / 2f);

// Cast a ray to this calculated point
Ray ray = Camera.main.ScreenPointToRay(screenCenterPoint);

// Check for a ray-hit
// The collider mask is a LayerMask set in editor that helps determine valid objects
if (Physics.Raycast(ray, out RaycastHit raycastHit, 999f, aimColliderMask))
{
    // Set our world anchor/debug sphere transform
    transformPos = raycastHit.transform;

    // Perform Shoot (covered in a later sprint log)
    Shoot(raycastHit);

}

sphere.gif
Above: The orange sphere is at the same transform as the raycastHit.point

Invisible Walls

Additionally, an invisible bounding box was placed around the level to give the casted ray a place to hit no matter where the player aims. This was to ensure that there was always a new point to update the debug sphere in the previously seen gif, as well as the arm's aim vector. Otherwise, when aiming in the sky, the sphere/arm would stop moving until the ray cast logic identified another collision.


Player Feedback

Initial player feedback indicates the need for a smoother camera control. Players indicated that the camera can move too quickly, making it harder to control and thus potentially less enjoyable.


Gal_FPS_Demo.gif

To Be Addressed

As of this log, a few issues remain and need attention:

  • Arm clipping issues
    • I believe this happens when the arm is ~parallel with the view-frustum
  • Camera movement jitter
    • As mentioned in the feedback section, the aim movement needs to be interpolated/smoothed a bit to provide an easier to control aim mechanic.

Next Time:

In the next Project Gal Dev Log, I plan to discuss the shoot input control and its callback listener OnShoot(CallbackContext). You can see a preview of my progress in the following gif!


preview.gif
Above: Wait... your guns can shoot?

Conclusion

In conclusion, the development of the aiming mechanic in Project GAL involved overcoming challenges related to package importation, world/local vector orientation issues, camera clipping artifacts, inverse kinematic rigging and its MultiAimConstraint component.

Re-orienting the character model's armature to a consistent forward vector orientation, debugging with raycastHit for world mouse position, and the MultiAimConstraint component technique, are what eventually led me to successfully implementing the view mechanic, and subsequent in solving the challenge of syncing the player-model's arm with that view mechanic!

I could go on for some time more about this sprint, but I'm eager to keep developing!


Until next time!

- Josh ✌️


About Joshua Lollis
Joshua Lollis

Student/Game Dev/Programmer/Artist/Musician based in Fullerton CA, USA

Email : lollisjosh@csu.fullerton.edu

Website : https://telloviz.netlify.app

About Josh Lollis

Hi, my name is Josh! I love creating! I hope you find something that inspires you here. :D