About

Hi! I'm Vilhelm and I am a game programmer.

I do most parts of game dev, but I am especially interested in Gameplay and Engines, which often includes some AI work. I like having structure and making structured code. The creative aspects of programming really speak to me, one person's way to solve a problem will never be the same as another. I value teamwork as one of the most important ingredients to create great things but I won't shy away from bashing my head into a problem to solve it (mostly that mean's i enjoy learning new things and will dig in to whatever topic i am working on). I am experienced enough to be aware of my own limitations and ask for help when i need it.

Before studying programming, I was a freelance documentary photographer / visual storyteller. Other than covering general news in Sweden I was reporting from countries such as Syria, Myanmar, Indonesia, Iraq. If you are interested, some of my photographic work can be seen here.

Here is my CV.

Interests and skills

  • Gameplay
  • AI & Behaviour
  • Engines
  • Systems
Vilhelm Stokstad

Projects

Vulkan engine

Vulkan engine

I'm building a vulkan based game engine to learn vulkan, graphics, shaders and ECS. The 'ball' above is a 250mb .obj file where each 'hair' is separate, it renders without delay even on a laptop mac.

Team size: Solo project
Project time: Ongoing
Engine: Vulkan, C++, cross-platform
Role: Programmer
My Work:In this project I'm trying to learn as much as possible. From setting up and managing a vulkan backend to basic gameplay systems, physics and ECS. I'm using some libraries for some parts, and doing others from scratch. I've got some working point lights, models and textures, and I am right now working on writing my own ECS.
Lessons Learned:Vulkan is a lot of boilerplate, but that also teaches me a lot about how to manage the gpu, memory allocation and timings. It's really fun and not very forgiving. As a whole the project is a pretty good exercise in how to structure projects, abstraction and maintaining an ever-growing codebase.
// global ubo global_ubo ubo{}; ubo.projection = camera.getProjection(); ubo.view = camera.getView(); ubo.inv_view_mat = frame.camera.getInverseView(); ubo.ambient_light_color = {0.2f, .2f, .2f, 0.2f}; point_light_render_system.update(frame, ubo); // physics // physics_system.update(frame); // write updates to buffer ubo_buffers[frame_index]->writeToIndex(&ubo, frame_index); ubo_buffers[frame_index]->flush(); // Render renderer_.beginSwapChainRenderPass(command_buffer); // rendering point_light_render_system.render(frame); simple_render_system.renderGameObjects(frame); // END renderer_.endSwapChainRenderPass(command_buffer); renderer_.endFrame(); } } vkDeviceWaitIdle(device_.device());

BEPPUS

In BEPPUS, a 'Oddworld meets Lemmings' puzzle platformer, you lead a group of Beppus with strange powers. Their world is harsh, and sacrificing some of them will be needed for the others’ salvation.

Team size: 6
Project time: 4 weeks
Engine: Unreal Engine 4 / C++
Role: Gameplay, Systems, AI.
My Work:I did a lot of the general gameplay, including AI and interactions/commands as well as designed the systems behind the different commands for the controllable characters. I did the system with designers in mind, resulting in editable blueprints handling the visual ques connected to them.
Lessons Learned:Do level streaming from the start, don't rework the level structure two days before deadline for 'performance'. Buddy coding and closely integrated work between us programmers resulted in a fun AND quite effective workload.
void AGP_PatrollingAIController::HandleMove(){ CurrentDirection.X = 0.f; if ( !bIsPausing ){ Minion->HandleRightInput(CurrentDirection.Y); FVector TraceStart = GetPawn()->GetPawnViewLocation() + CurrentDirection * 20.f; FVector TraceEnd = TraceStart + CurrentDirection * TraceLength; FHitResult Hit; if ( GetWorld()->LineTraceSingleByObjectType(Hit, TraceStart, TraceEnd, COQP) ){ if ( Hit.bBlockingHit ){ AGP_PuzzlePiece_Portal* Portal = Cast<AGP_PuzzlePiece_Portal>(Hit.GetActor()); if ( Portal || Minion->IsTeleporting == true ) return; TurnAround(); return; } } else{ TraceStart.Z -= SecondTraceHeightOffset; TraceEnd = TraceStart + CurrentDirection * (TraceLength * 0.4f); if ( GetWorld()->LineTraceSingleByObjectType(Hit, TraceStart, TraceEnd, COQP) ){ if ( Hit.bBlockingHit ){ AGP_PuzzlePiece_Portal* Portal = Cast<AGP_PuzzlePiece_Portal>(Hit.GetActor()); if ( Portal ) return; TurnAround(); return; } } } } if ( !GetPawn<AGP_Minion>()->GetIsGrounded() && !bIsPausing ){ PauseMove(CurrentDirection); return; } if ( GetPawn<AGP_Minion>()->GetIsGrounded() && bIsPausing ){ ResumeMove(CurrentDirection); } }

The Unseen

The Unseen is a first person horror/survival game. You enter an underground water treatment facility as a mechanic. Equipped with only the standard issue multi-tool you set out to fix the issues all over the plant. But something lurks in the shadows.


Team size: 9
Project time: 6 weeks
Engine: Unreal Engine 4 / C++
Role: Lead Programmer, Systems, AI, scan shader.
My Work:I constructed the AI system for the 'Entity' as well as the scanning system including the scan shader and scanned objects custom render depth. I also did a 'pixel sort shader' for a 'distortion-of-reality'-effect when the player is close to the 'Entity'.As Lead programmer and Perforce/VCS responsible i also worked on several smaller gameplay things and oversaw the whole version control as well as builds and deployment of the final product.
Lessons Learned:A tighter control on 'interactable Objects' that are supposed to show up on the scanner would have helped a lot. As most of the puzzles and interactions where almost entirely handled by blueprints in UE4, it would have made it a lot easier to be a bit more specific in code about which objects should be highlighted and when. Also, the AI turned out more of a trial-and-error test which would have needed a lot more in-game control to be effective in the game.
//This should only happen once per move; if (Enemy->bHasRequestedLocation) { if (FVector::Dist(Enemy->GetActorLocation(), Enemy->RequestedLocation) < 5.f) { OnArrivedAtTargetDestination(); } } //SCREEN DISTORTION EFFECT IF PLAYER IS CLOSE// float DistanceToPlayer = Enemy->GetDistanceToPlayer(); Enemy->AggressionLevel = FMath::Clamp(Enemy->AggressionLevel, 0.001f, 1.f); if (DistanceToPlayer <= AISightRadius) { ScreenPixelEffectAmount += FMath::Lerp(0.f, 1.f, Enemy->AggressionLevel); } else { ScreenPixelEffectAmount -= FMath::Lerp(0.f, 1.0f, Enemy->AggressionLevel); } ScreenPixelEffectAmount = FMath::Sin(ScreenPixelEffectAmount * DeltaSeconds); UKismetMaterialLibrary::SetScalarParameterValue(this, PixelSortParameterCollection, FName("AlphaClamp"), ScreenPixelEffectAmount); //CHECK IF IS IDLE// if (StateMachine->GetState() == EEnemyState::IDLE) { IdleTimer += DeltaSeconds; } if (IdleTimer >= MaxIdleTime) { SetAILogicActive(true); PlayerLastKnownPosition = GetPoint(true)->GetActorLocation(); StateMachine->SetState(EEnemyState::SEARCHING); IdleTimer = 0.f; }

SubOptimal

SubOptimal is a point and click adventure taking place on a stranded submarine. A mysterious asteroid have crashed close by and weird energies have transformed the crew into strange animal like beings.


Team size: 9
Project time: 4 weeks
Engine: Unity / C#
Role: Gameplay & Systems.
My Work:In SubOptimal I was in charge of the whole structure for 'Interactable Objects' and their integration into the game. We both needed a way to highlight those objects in the world, on hovering, and have customizable yet reusable interactions with them. I also did a lot of trial and error with the camera system, first setting out to do an automated system where cameras became active based on player visibility, but as the project progressed we scrapped that for a more robust 'trigger' set-up.
Lessons Learned:While automation is nice, it's sometimes just faster to do it manually from the get go (regarding the camera system). Instead of a heavy inheritance based structure for interactables in the world, it would probably have been both easier and more performant with an interface or component based set-up.

ShmuperHot

ShmuperHot

Space Shoot'Em Up mini-game with heavy inspiration from Super Hot in regard to time dilation and slow-motion. Developed as an assignment at Future Games, then ported to Mobile (iOS) through Unity as a side project.

Team size: solo project
Project time: 2 weeks
Engine: Unity / C# and XCode / swift
Role: Everything
My Work:Tasked with making a simple space shooter I experimented with time controlled by gameplay, time only moves forward as the player vessel moves around. When porting to iOS I reworked the whole structure of the codebase to make it more performant, implementing a semi data oriented design, where I collectively call update and fixed update on groups of objects instead of running it individually (as is the base implementation in Unity).
Lessons Learned:Mobile UI is hard to get right. Porting to mobile is less about getting it to run on a small device and more about getting it to feel good on a mobile device.
public class BatchUpdateManager : MonoBehaviour { [SerializeField] private EnemySpawnManager _enemySpawnManager; [SerializeField] private SpaceJunkManager _spaceJunkManager; [SerializeField] private TimeManager _timeManager; [SerializeField] private PlayerBoundaries _playerBoundaries; [SerializeField] private PlayerInput _playerInput; [SerializeField] private PlayerController _playerController; [SerializeField] private PlayerMovement _playerMovement; [SerializeField] private ShieldControl _shieldControl; [SerializeField] private BackgroundController _backgroundController; private void Update(){ _enemySpawnManager.BatchUpdate(); _playerInput.BatchUpdate(); _playerController.BatchUpdate(); _shieldControl.BatchUpdate(); _backgroundController.BatchUpdate(); } private void FixedUpdate(){ _spaceJunkManager.BatchFixedUpdate(); _timeManager.BatchFixedUpdate(); _playerBoundaries.BatchFixedUpdate(); } }

Contact Vilhelm

Contact

Vilhelm Stokstad

© All rights are reserved | 2022 |