Managing build pipeline and Steam integration for a 15-person BUAS capstone team. Built automated Jenkins CI/CD, Steam deployment systems, and developer tools that enabled our team to successfully ship a commercial game demo and final release.
A 15-person capstone team (3 programmers, 6 artists, 5 designers, 1 producer) needed efficient collaboration on a year-long commercial game project. Manual builds, inconsistent testing, and complex Steam deployment were blocking progress and creating integration headaches.
Built comprehensive DevOps infrastructure with automated Jenkins pipelines, daily testing suites, Steam deployment automation, and custom Discord integration. Managed both our team's pipeline and served as Jenkins administrator for the entire BUAS program.
Built automated Jenkins pipelines for daily testing and weekly deployment builds. Created custom Groovy libraries for Unreal Engine integration and Discord notifications.
Managed SteamWorks page, implemented Steam Input API, and built automated Steam deployment pipeline with branch management and SteamGuard authentication handling.
Implemented daily automated testing with Unreal's automation framework, custom test result parsing, and detailed Discord reporting for immediate team feedback.
Served as Jenkins administrator for the entire BUAS program, managing infrastructure for multiple teams and maintaining shared Jenkins libraries for Unreal projects.
Created comprehensive Discord notification system with rich embed formatting, automated test reports, and Steam deployment status updates for seamless team communication.
Set up cross-platform builds with Perforce integration, automated Google Drive backup, and flexible deployment targeting for Steam, Itch.io, and internal testing.
Two-pipeline approach: daily automated testing for continuous quality assurance and weekly deployment builds for release management.
Latest changelist from depot
Run project automation tests
Rich embed test results
// Custom Discord tools library for rich notifications
def JenkinsTools = library('DiscordTools@master').com.github.cynic1254
library identifier: 'JenkinsLib@master',
retriever: modernSCM([
$class: 'GitSCMSource',
credentialsId: '',
remote: 'redacted'
])
pipeline {
agent {
node {
label "Win64"
customWorkspace "C:\\Jenkins\\${env.JOB_NAME}"
}
}
environment {
// Redacted
}
stages {
stage ("Init") {
steps {
script {
JenkinsTools.Library.Init(this)
}
}
}
stage ("P4 Setup") {
steps {
script {
def newestChangeList = p4v.initGetLatestCL(env.P4USER, env.P4HOST)
echo("NEWEST CHANGELIST: ${newestChangeList}")
p4v.init(env.P4USER, env.P4HOST, env.P4WORKSPACE, env.P4MAPPING, newestChangeList, !true)
echo("P4 synced to: ${env.P4_CHANGELIST}")
}
}
}
stage ("Testing") {
steps {
script {
// Run Unreal automation tests and parse results
def testResult = JenkinsTools.Unreal.RunAutomationCommand("RunTests Project")
// Send rich Discord embed with test results
JenkinsTools.DiscordMessage.FromTestResults(testResult).Send(env.DiscordWebhook)
// Generate JUnit XML for Jenkins integration
testResult.WriteXMLToFile("${env.WORKSPACE}/Logs/UnitTestsReport/Report.xml")
junit allowEmptyResults: true,
skipMarkingBuildUnstable: true,
testResults: 'Logs/UnitTestsReport/Report.xml'
}
}
}
}
post {
success {
script {
JenkinsTools.DiscordMessage.Succeeded().Send(env.DiscordWebhook)
}
}
failure {
script {
JenkinsTools.DiscordMessage.Failed().Send(env.DiscordWebhook)
}
}
cleanup {
script {
if(env.CLEANWORKSPACE.toBoolean()) {
cleanWs()
}
}
}
}
}
Built a comprehensive Discord notification system with rich embeds for test results, build status, and Steam deployment updates.
static DiscordMessage FromTestResults(UnrealTestResult testResults) {
def embed = new Embed()
if (testResults.failed > 0) {
embed.title = "Some tests failed!"
embed.color = 16776960 // Yellow
embed.description = "(${testResults.failed}/${testResults.failed + testResults.succeeded}) tests failed"
embed.fields = ParseTestResultFields(testResults)
} else {
embed.title = "All tests succeeded!"
embed.color = 65280 // Green
embed.description = "Successfully ran all tests"
}
embed.footer = new Embed.Footer(
text: "Ran ${testResults.succeeded + testResults.failed} tests in ${String.format("%.4f", testResults.totalDuration)} seconds. Full results: ${Library.steps.env.BUILD_URL}"
)
return new DiscordMessage(
avatar_url: "https://preview.redd.it/is-it-normal-for-games-to-have-a-unreal-engine-logo-as-its-v0-ekvife6ql3xc1.jpeg",
username: "Unreal Test Result",
embeds: [embed]
)
}
static DiscordMessage SteamPushStarted(String AuthorizerID) {
return new DiscordMessage(
embeds: [
new Embed(
title: "Started push to steam",
color: 16777215, // White
fields: [
new Embed.Field(
name: "Push to Steam has been initiated, SteamGuard Authorization might be required",
value: "${Library.steps.env.BUILD_URL}"
),
new Embed.Field(
name: "Authorizer:",
value: "<@${AuthorizerID}>"
)
]
)
],
allowed_mentions: new Mentions(users: ["${AuthorizerID}"]),
username: "Steam Auth",
avatar_url: "https://e1.pngegg.com/pngimages/855/514/png-clipart-clay-os-6-a-macos-icon-steam-steam-logo-thumbnail.png"
)
}
Year-long capstone project from concept to commercial Steam release:
Team formation, initial concept development, and Jenkins infrastructure setup. Established basic build pipeline and Steam development environment.
Built daily testing pipeline, implemented Discord integration, and established automated Perforce sync. Created custom Groovy libraries for team workflow.
Implemented Steam Input API, built deployment automation with SteamGuard handling, and refined testing framework based on development needs.
Deployed playable Steam demo using automated pipeline. Zero critical deployment issues thanks to comprehensive testing and deployment automation.
Successfully launched complete game on Steam. Pipeline handled final release deployment smoothly, enabling team to focus on last-minute polish and marketing.
Built sophisticated Steam deployment with authentication handling and branch management:
stage("Primary-DDS-Deployment") {
when {
expression { env.DEPLOYTODDS.toBoolean() }
}
steps {
script {
if(env.DDS == "Steam") {
if(currentBuild.result == "UNSTABLE") {
log.warning("====!!==== UNSTABLE BUILD - PUSH TO STEAM ABORTED ====!!====")
} else if(env.PLATFORM == "PS5") {
log.warning("====!!==== PS5 BUILD - NOT ALLOWED TO UPLOAD TO STEAM ====!!====")
} else {
// Notify team of Steam deployment start
discord.sendMessage(discord.createMessage(
"Started push to steam",
"white",
[[name:"Push to Steam has been initiated, SteamGuard Authorization might be required",
value:"${env.BUILD_URL}"],
[name:"Authorizer:",
value:"<@354316115704545281>"]],
[text:"${env.JOB_BASE_NAME}"])
, env.WebHook_BUILD)
steam.init(env.STEAMUSR, env.STEAMCMD)
// Create appropriate depot manifest based on platform
def appManifest
if (env.PLATFORM == "Linux"){
appManifest = steam.createAppManifest("3365850", "3365852", "",
"${env.JOB_BASE_NAME}", false, "", "${env.STEAMBRANCH}", env.OUTPUTDIR)
steam.createDepotManifest("3365852", "${env.OUTPUTDIR}\\${platformDir}")
} else {
appManifest = steam.createAppManifest("3365850", "3365851", "",
"${env.JOB_BASE_NAME}", false, "", "${env.STEAMBRANCH}", env.OUTPUTDIR)
steam.createDepotManifest("3365851", "${env.OUTPUTDIR}\\${platformDir}")
}
try {
steam.tryDeploy("${env.WORKSPACE}\\${appManifest}")
} catch (Exception e) {
dds_auth_timeout = true
// Handle SteamGuard timeout gracefully
discord.sendMessage(discord.createMessage(
"Steam Authorization Timed Out",
"white",
[[name:"Steam Authorization was not provided in time, Build has not been uploaded to steam, will be uploaded to GDrive instead",
value:"${env.BUILD_URL}"]],
[text:"${env.JOB_BASE_NAME}"])
, env.WebHook_BUILD)
catchError(stageResult: 'UNSTABLE', buildResult: currentBuild.result) {
error("Mark Stage as Skipped")
}
}
}
}
}
}
}
All this infrastructure work enabled our team to successfully ship a commercial game. The DevOps foundation allowed creators to focus on crafting an engaging puzzle experience.