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);
}
❮
❯
Short Demo
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();
}
❮
❯
This is the caption for the video.
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.