Yousaf Zahir
Computer Scientist 🕹️ The University of Calgary
Welcome to my website, here are links to some recordings of my personal projects.
These links are intended to be used in conjunction with my resume.

THE PROJECTS ARE ORDERED CHRONOLOGICALLY FROM MOST RECENT TO OLDEST.

Gameplay Programming — Axe

         Inspired by Kratos’ Leviathan Axe from God of War, I decided to create my own version using Unreal Engine 5.4. My goal was to gain hands-on experience implementing gameplay features in Unreal, complete with VFX and other elements.

         I learned a lot throughout the process of building this project. In the second video, you can see my initial version, where I attempted to make the axe function using physics impulses. At the time, I wasn’t familiar with animation notifications, so I created an invisible trigger within the player that would activate on collision—timed to occur at a specific point in the character’s animation.

        The second iteration, which represents the current state of the demo, is much more refined. It uses splines to map the axe’s path, replacing the simple chaos destruction of the original version with procedural meshes. I also incorporated features like custom animation notifies to enhance functionality.

While I did use tutorials to learn new Unreal Engine features, the implementation is entirely my own.



// Called when the game starts
void UPlayerFunctionality::BeginPlay() {
	Super::BeginPlay();
	Initialize();

	BIND_INPUT_KEY(LeftShift, Pressed, HandleSprintBegin);
	BIND_INPUT_KEY(LeftShift, Released, DefaultSettings);

	BIND_INPUT_KEY(RightMouseButton, Pressed, HandlePreThrow);
	BIND_INPUT_KEY(RightMouseButton, Released, DefaultSettings);

	BIND_INPUT_KEY(LeftMouseButton, Pressed, PrepThrow);
	BIND_INPUT_KEY(LeftMouseButton, Released, ThrowAxe);

	BIND_INPUT_KEY(R, Pressed, RetrieveAxe);

	ResetLerpValues();
}


void UPlayerFunctionality::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) {
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	// ---- Variables
	float *CameraFOV = &OwnerCamera->FieldOfView;
	float *PlayerMaxSpeed = &OwnerActor->GetCharacterMovement()->MaxWalkSpeed;
	FVector CameraOffset = OwnerCamera->GetRelativeLocation();

	// ---- Implementation
	lerpAlpha += DeltaTime / lerpDuration;
	lerpAlpha = FMath::Clamp(lerpAlpha, 0.f, 1.f);

	// Run Camera
	if (PlayerHelper->DistanceToTarget(&desiredCameraFov, CameraFOV) > 0.1f) {
		*CameraFOV = FMath::Lerp(*CameraFOV, desiredCameraFov, lerpAlpha);
	}

	// Player Movement
	if (PlayerHelper->DistanceToTarget(&desiredMaxSpeed, PlayerMaxSpeed) > 0.1f) {
		*PlayerMaxSpeed = FMath::Lerp(*PlayerMaxSpeed, desiredMaxSpeed, lerpAlpha);
	}

	// Zoom Camera
	if (PlayerHelper->DistanceToTarget(&CameraOffset, &desiredCameraOffset) > 0.1f) {
		const FVector NewCameraOffset = FMath::Lerp(CameraOffset, desiredCameraOffset, lerpAlpha);
		OwnerCamera->SetRelativeLocation(NewCameraOffset);
	}

	// Zoom -- Player turn with camera
	// Notice: the last else-if only executes when b_rotationJitter doesn't change.
	// It's ugly but don't change.
	if (b_matchCamRotation) {
		float ActorYaw = OwnerActor->GetActorRotation().Yaw;
		float CameraYaw = OwnerCamera->GetComponentRotation().Yaw;

		if (CameraYaw < ActorYaw && !b_rotationJitter) {
			b_rotationJitter = true;

		} else if (CameraYaw > ActorYaw && b_rotationJitter) {
			b_rotationJitter = false;

		} else if (FMath::Abs(ActorYaw - CameraYaw) > 0.1f) {
			float NewActorYaw = FMath::Lerp(ActorYaw, CameraYaw, lerpAlpha);

			FRotator NewActorRotation = OwnerActor->GetActorRotation();
			NewActorRotation.Yaw = NewActorYaw;
			OwnerActor->SetActorRotation(NewActorRotation);
		}
	}


	if (b_axeBeingRetrieved) {
		float DistanceToActor = FMath::Abs(FVector::Dist(Axe->GetComponentLocation(), OwnerActor->GetActorLocation()));
		float SplinePoint2 = FVector::Dist(AxePath->GetLocationAtSplinePoint(1, ESplineCoordinateSpace::World), AxePath->GetLocationAtSplinePoint(0, ESplineCoordinateSpace::World));
		
		// plays catching animation
		if (DistanceToActor <= SplinePoint2 && !Axe->GetAttachParent()) {
			bCatchingAxe = true;
		} 
		
		if (DistanceToActor < 150.f) {
			b_axeBeingRetrieved = false;

			Axe->AttachToComponent(OwnerActor->GetMesh(), FAttachmentTransformRules::SnapToTargetNotIncludingScale, WeaponSocket->SocketName);	
			Axe->SetRelativeTransform(heldAxeTransform);
			bCatchingAxe = false;
			
		} else {
			Axe->AddLocalRotation(FRotator(10.f, 0.f, 0));
		}

	}


	// controlling axe trail visibility
	if (Axe->GetAttachParent() || (!Axe->IsSimulatingPhysics() && !b_axeBeingRetrieved)) {
		AxeSpinVFX->Deactivate();
		
	} else {
		AxeSpinVFX->Activate();

	}

	const FVector OwnerLocation = OwnerActor->GetActorLocation();
	constexpr float PlayerHeight = 200.f;
		
	// Axe is in hand
	if (Axe->GetAttachParent()) {
		Spin = 0.f;
		
		FVector Start = OwnerCamera->GetComponentLocation();
		FVector End = Start + OwnerCamera->GetForwardVector() * 6000.f;
		
		FHitResult Hit;
		FCollisionQueryParams CollisionParams;
		CollisionParams.AddIgnoredActor(OwnerActor);
		
		GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_WorldStatic, CollisionParams);
		
		if (Hit.GetActor()) {
			// raycast has been hit
			FVector Point2Dir = OwnerCamera->GetForwardVector();
			Point2Dir.Normalize();

			FVector Point2Location = Hit.Location + (Point2Dir * 3000.f);
			Point2Location.Z = Hit.Location.Z;
			
			FVector Point3Location = Point2Location + (Point2Dir * 1800.f);
			Point3Location.Z = 0.f;

			AnchoredPoint1Pos = Hit.Location;
			AnchoredPoint2Pos = Point2Location + FVector(0.f, 0.f, PlayerHeight);
			AnchoredPoint3Pos = Point3Location;
			
			
		} else {
			// no raycast hit
			AnchoredPoint1Pos = End;
			
			AnchoredPoint3Pos = End;
			AnchoredPoint3Pos.Z = 0.f;

			AnchoredPoint2Pos = FMath::Lerp(AnchoredPoint1Pos, AnchoredPoint3Pos, 0.5f);
		}
		
	} else {
		if (!Axe->IsSimulatingPhysics() && !b_axeBeingRetrieved){
			//  Axe is on ground
			/// -- Adjusting the up direction, so that on axe retrieve the axe will go in direction of the catching hand.
			FVector CharToAxe = (AnchoredPoint3Pos - OwnerLocation).GetSafeNormal();
			UpDirection = FVector::DotProduct(OwnerActor->GetActorForwardVector(), CharToAxe);
			
		} else {
			// Axe in Air

			if (bAxeMovingAway) {
				// Axe is being thrown

				/// -- Keep axe laterally tangential to spline. 
				float curDistanceAlongSpline = AxePath->GetDistanceAlongSplineAtLocation(Axe->GetComponentLocation(), ESplineCoordinateSpace::World);
				FVector curTangent = AxePath->GetTangentAtDistanceAlongSpline(curDistanceAlongSpline, ESplineCoordinateSpace::World);
				FRotator DesiredRotation = FRotationMatrix::MakeFromXZ(curTangent, FVector::UpVector).Rotator();

				// -- Roll axe is it goes over its throw path.
				float totalRollingDistance = AxePath->GetDistanceAlongSplineAtSplinePoint(1);
				float startRollingDistance = totalRollingDistance * 0.1f;
				float endRollingDistance = totalRollingDistance * 0.9f;

				if (curDistanceAlongSpline > startRollingDistance && curDistanceAlongSpline < endRollingDistance) {
					float distanceRatio = (curDistanceAlongSpline - startRollingDistance) / (endRollingDistance - startRollingDistance);
					float targetRoll = FMath::InterpEaseOut(110.0f, 470.0f, distanceRatio, 2.0f);

					SpeedMag = FMath::InterpEaseOut(4.f, 1.f, distanceRatio, 2.0f); // ramp down axe speed from 4x to 1x speed
				
					DesiredRotation.Roll = targetRoll;  

				} else {
					DesiredRotation.Add(0.f, 0.f, 110.f);
					SpeedMag = 1.0f;
				}

				
				Axe->SetWorldRotation(DesiredRotation);

				/// -- Spin Axe as it goes (Frisbee)
				Spin += 1000.f * DeltaTime;
				Axe->AddLocalRotation(FRotator(Spin, 0.f, 0.f));

			} else {
				// Axe is being retrieved

				/// -- Adjusted the positions of the middle points.
				AnchoredPoint1Pos = FMath::Lerp(OwnerLocation, AnchoredPoint3Pos, 0.25);
				AnchoredPoint1Pos.Z = PlayerHeight;
				
				AnchoredPoint2Pos = FMath::Lerp(OwnerLocation, AnchoredPoint3Pos, 0.75);
				AnchoredPoint2Pos.Z = PlayerHeight;
				
				/// -- Adjusting the lateral offset. 
				FVector CharToAxe = (AnchoredPoint3Pos - OwnerLocation).GetSafeNormal();
				
				CharToAxe = FVector::CrossProduct(FVector(0.f, 0.f, UpDirection), CharToAxe).GetSafeNormal();
				
				FVector XOffSet = CharToAxe * 500.f;
				AnchoredPoint1Pos += XOffSet;

				AnchoredPoint2Pos += (XOffSet / 2);
			}
		}	
	}
	
	AxePath->SetLocationAtSplinePoint(1, AnchoredPoint1Pos,ESplineCoordinateSpace::World, true);
	AxePath->SetLocationAtSplinePoint(2, AnchoredPoint2Pos, ESplineCoordinateSpace::World, true);
	AxePath->SetLocationAtSplinePoint(3, AnchoredPoint3Pos, ESplineCoordinateSpace::World, true);

}
                         




Kingdom of Predators

    The paper can be requested via email.


          An agent-based emergence simulation of a predator-prey ecosystem made in Unreal Engine 5 with C++ as my final project for CPSC 565: Emergent Computing class. This project was completely planned, designed, and implemented by me. Notably, the creatures' decisions are solely composed of information they can collect. In other words, there is no external data being transferred into the creatures; rather, the creatures are looking and listening to collect their own data. Here’s where the emergence comes in: all the creatures are relatively simple, but as a consequence of many of them acting together, more complex behaviors emerge.

         This project aimed to see whether agent-based simulation could be used to create believable actors and assess the viability of using such a system in games and other simulations. The hope was that emergence-based behavior would prove to be a viable solution for making virtual worlds feel alive and not mechanical. The project was largely a success; however, further exploration needs to be done to assess the viability of such agents in virtual worlds beyond simulation.

         The project was made using Unreal Engine 5 and C++, and no blueprints were used. The StateTrees plugin was used to implement the creature AI.



// This is sample code from a animal/creature actor in a simulation made in Unreal Engine 5. Comments have been added to provide additional context regarding some objects.
// This code was entirely written by me, without the help of AI. It was written under significant time constraints as it was a small piece of a final project I worked on, I have decided to leave
// many parts of the code unchanged (except for the addition of comments), so that it is a more realisitic depiction of how I write code under tight time constraints. That having been said,
// looking back I would quite a few changes, which I would be happy to discuss. 

void UData::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) {
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

    FVector OwnerLocation = Owner->GetActorLocation();
    FRotator OwnerRotation = Owner->GetActorRotation().Clamp();

    // directionArr is a small array corresponding to the direction that character is facing, whereas memory is a 360 sized array corresponding to all the directions the creature can face.
    int arrOffSet = OwnerRotation.Yaw;
    directionalArr = &memory[arrOffSet];

    // below code decays lastHit and energy variables over time.
    if(lastHit > 0) lastHit -= DeltaTime;

    if (energy > 0) {
        energy -= DeltaTime;
        if (energy <= 0) ProcessHit(attr_health);
    }

    UpdateHealth();


    // Raycast parameters
    FCollisionQueryParams TraceParams;
    TraceParams.AddIgnoredActor(Owner);
    FHitResult HitResult;


    // the creature shoots raycasts in a conal shape in the direction it is facing. Each i corresponds to a degree.
    for (int32 i = 0; i < directionalArrSize; i++) {
        TArray Hits;
        // this is used to know the direction of the raycast relative to the owner
        FVector Direction = OwnerRotation.RotateVector(std::get<1>(directionTuple[i]));

        // Perform the raycast
        if (GetWorld()->LineTraceMultiByObjectType(Hits, OwnerLocation, OwnerLocation + Direction * 1000.0f, PawnsAndCorpses, TraceParams)) {
            int sumOfHits = 0;
            for (auto Hit : Hits) {
                // check to see if a character was hit, if it was then we check cached information to see if we've computed owner creatures attraction to hit creature. If it's the first time hitting the creature, then we compute
                // attraction and store the value.
                if (Hit.GetActor()->GetName().Mid(0, 12) == "BP_Character") {
                    if (!IDMap.Contains(Hit.GetActor()->FindComponentByClass()->ID)) {
                        IDMap.Add(Hit.GetActor()->FindComponentByClass()->ID, CalcAttraction(Hit.GetActor()));

                    }

                    if (!(Hit.GetActor()->FindComponentByClass()->IDMap.Contains(ID))) {
                        Hit.GetActor()->FindComponentByClass()->IDMap.Add(ID, CalcAttraction(Owner));
                    }

                }

                sumOfHits += gameObjectToAffinity(Hit.GetActor());
            }

            // this assigns an attraction value to a direction that the creature can move. The creatue will move in the direction of the highest attraction value.
            memory[(arrOffSet + i) % 360] = sumOfHits;

            // update highest if necessary
            if (sumOfHits > highestHit) {
                highestHit = sumOfHits;
                highestHitI = (arrOffSet + i) % 360;
            }

            // if we are debugging, if we hit something draw the raycast red, otherwise green
            if (seeDebugLines)DrawDebugLine(GetWorld(), OwnerLocation, OwnerLocation + Direction * 1000.0f, FColor::Red, false, 0.1f, 0, 1.0f);

        } else {
            if (seeDebugLines)DrawDebugLine(GetWorld(), OwnerLocation, OwnerLocation + Direction * 1000.0f, FColor::Green, false, 0.1f, 0, 1.0f);
            
        }
    }

    DecayMemory();

}
                        




Pathfinder

         This is Pathfinder, a project I made over the summer of 2022 to learn and implement pathfinding algorithms, specifically BFS and DFS. I had learned them for the first time a a year ago or so and had forgotten them over time. So, I came up with a project to work on which would help me practice and understand the algorithms. The project was made using Unity 3D and C#, and I relearned the algorithms using MIT OCW.





KorEscape

The GDD can be requested via email.


         This is KorEscape, a game my group and I made for Art 503 during the Winter semester of 2022. It was made using the Unity Engine and C#. To get a proper breakdown of all the features in the game you can download the game design document from above. All of the programming and implementation (setting up animations, setting up the map layout, etc.) were exclusively my responsibility.





Untitled Unity 3D Game

         This is a game made for the GDN 2021 Game Jam over the course of 4-days that was actively worked on by myself and one other person in Europe who I met online for the purpose of the game jam. We both worked on it remotely using Unity 3D, C# and GitHub. This was my first time working with another person on a game jam as well as working with Unity 3D.




Fallball

         This is a game I made for the Innovators' Jam 2021, a solo game jam with the theme of "can't stop moving". I designed and developed this game over the course of 48 hours using Unity 2D and C#. This was also my first ever game jam.






You can contact me using my E-MAIL: yzahir.cs@outlook.com