Skip to content

CI CD Integration

Daisuke Tsukada edited this page Aug 26, 2025 · 1 revision

CI/CD Integration

Integrate Unity Remote Server into your continuous integration and deployment pipelines.

GitHub Actions

Basic Unity Build

.github/workflows/unity-build.yml:

name: Unity Build

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Start Unity with Remote Server
        run: |
          # Start Unity in batch mode with your project
          $UNITY_PATH -batchmode \
            -projectPath . \
            -logFile - &
          
          # Wait for Remote Server to be ready
          while ! curl -s http://127.0.0.1:8787/health; do
            echo "Waiting for Unity Remote Server..."
            sleep 5
          done
      
      - name: Build Android
        run: |
          curl -X POST http://127.0.0.1:8787/build \
            -H "Content-Type: application/json" \
            -d '{
              "platform": "android",
              "outputPath": "Builds/game.apk"
            }'
      
      - name: Upload Build Artifacts
        uses: actions/upload-artifact@v3
        with:
          name: android-build
          path: Builds/game.apk

Multi-Platform Build

name: Multi-Platform Build

on:
  release:
    types: [created]

jobs:
  build:
    strategy:
      matrix:
        platform: [android, ios, windows, mac, linux]
        include:
          - platform: android
            output: game.apk
          - platform: ios
            output: ios-build
          - platform: windows
            output: game.exe
          - platform: mac
            output: game.app
          - platform: linux
            output: game.x86_64
    
    runs-on: ${{ matrix.platform == 'mac' && 'macos-latest' || 'ubuntu-latest' }}
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Unity
        uses: game-ci/unity-builder@v2
        with:
          unityVersion: 2022.3.10f1
      
      - name: Install Remote Server Package
        run: |
          # Add package via manifest
          jq '.dependencies += {"com.dsgarage.unity-remote-server": "https://github.com/dsgarage/UnityRemoteServer.git"}' \
            Packages/manifest.json > tmp.json && mv tmp.json Packages/manifest.json
      
      - name: Build ${{ matrix.platform }}
        run: |
          # Use the provided build script
          ./Scripts/unity-build.sh ${{ matrix.platform }} Builds/${{ matrix.output }}
      
      - name: Upload ${{ matrix.platform }} Build
        uses: actions/upload-artifact@v3
        with:
          name: ${{ matrix.platform }}-build
          path: Builds/${{ matrix.output }}

Jenkins Pipeline

Jenkinsfile:

pipeline {
    agent any
    
    environment {
        UNITY_PATH = '/Applications/Unity/Hub/Editor/2022.3.10f1/Unity.app/Contents/MacOS/Unity'
        PROJECT_PATH = "${WORKSPACE}"
        REMOTE_SERVER = 'http://127.0.0.1:8787'
    }
    
    stages {
        stage('Setup') {
            steps {
                sh 'git clean -fdx'
                sh 'git checkout main'
                sh 'git pull origin main'
            }
        }
        
        stage('Start Unity') {
            steps {
                sh """
                    ${UNITY_PATH} -batchmode \
                        -projectPath ${PROJECT_PATH} \
                        -logFile unity.log &
                    
                    # Wait for Remote Server
                    for i in {1..60}; do
                        if curl -s ${REMOTE_SERVER}/health; then
                            echo "Unity Remote Server is ready"
                            break
                        fi
                        echo "Waiting for Unity... (\$i/60)"
                        sleep 5
                    done
                """
            }
        }
        
        stage('Run Tests') {
            steps {
                sh """
                    curl -X POST ${REMOTE_SERVER}/test/run \
                        -H "Content-Type: application/json" \
                        -d '{"runAll": true}'
                    
                    # Wait for tests to complete
                    sleep 30
                    
                    # Get test results
                    curl ${REMOTE_SERVER}/test/results > test-results.json
                """
                
                junit 'test-results.xml'
            }
        }
        
        stage('Build') {
            parallel {
                stage('Android') {
                    steps {
                        sh './Scripts/unity-build.sh android Builds/game.apk'
                        archiveArtifacts 'Builds/game.apk'
                    }
                }
                stage('iOS') {
                    when {
                        expression { env.NODE_NAME == 'mac-agent' }
                    }
                    steps {
                        sh './Scripts/unity-build.sh ios Builds/ios'
                        archiveArtifacts 'Builds/ios/**/*'
                    }
                }
            }
        }
        
        stage('Deploy') {
            when {
                branch 'main'
            }
            steps {
                // Deploy to app stores
                sh 'fastlane deploy'
            }
        }
    }
    
    post {
        always {
            // Get Unity logs
            sh 'curl ${REMOTE_SERVER}/errors > unity-errors.log'
            archiveArtifacts 'unity-errors.log'
            
            // Stop Unity
            sh 'pkill -f Unity || true'
        }
    }
}

GitLab CI

.gitlab-ci.yml:

variables:
  UNITY_VERSION: "2022.3.10f1"
  UNITY_PATH: "/opt/Unity/Editor/Unity"
  BUILD_NAME: "game"

stages:
  - test
  - build
  - deploy

before_script:
  - apt-get update && apt-get install -y curl jq
  - |
    # Start Unity with Remote Server
    $UNITY_PATH -batchmode \
      -projectPath . \
      -logFile - &
    UNITY_PID=$!
    
    # Wait for server
    for i in $(seq 1 60); do
      if curl -s http://127.0.0.1:8787/health; then
        echo "Remote Server ready"
        break
      fi
      sleep 5
    done

test:
  stage: test
  script:
    - |
      # Run unit tests
      curl -X POST http://127.0.0.1:8787/test/run \
        -H "Content-Type: application/json" \
        -d '{"runAll": true}'
      
      # Check for compilation errors
      ERRORS=$(curl -s http://127.0.0.1:8787/errors?level=error)
      if [ $(echo "$ERRORS" | jq '. | length') -gt 0 ]; then
        echo "Compilation errors found:"
        echo "$ERRORS" | jq '.'
        exit 1
      fi
  artifacts:
    reports:
      junit: test-results.xml

build:android:
  stage: build
  script:
    - ./Scripts/unity-build.sh android Builds/${BUILD_NAME}.apk
  artifacts:
    paths:
      - Builds/${BUILD_NAME}.apk
    expire_in: 1 week

build:ios:
  stage: build
  tags:
    - mac
  script:
    - ./Scripts/unity-build.sh ios Builds/ios
  artifacts:
    paths:
      - Builds/ios/
    expire_in: 1 week

deploy:production:
  stage: deploy
  only:
    - main
  script:
    - echo "Deploying to production..."
    # Add deployment scripts here

Docker Integration

Dockerfile:

FROM unityci/editor:ubuntu-2022.3.10f1-android-1.0.1

# Install dependencies
RUN apt-get update && \
    apt-get install -y \
    curl \
    jq \
    git

# Copy project
COPY . /project
WORKDIR /project

# Install Unity Remote Server
RUN echo '{"dependencies":{"com.dsgarage.unity-remote-server":"https://github.com/dsgarage/UnityRemoteServer.git"}}' \
    | jq -s '.[0] * .[1]' Packages/manifest.json - > tmp.json && \
    mv tmp.json Packages/manifest.json

# Copy build scripts
COPY Scripts/ /scripts/
RUN chmod +x /scripts/*.sh

# Start script
COPY docker-entrypoint.sh /
RUN chmod +x /docker-entrypoint.sh

ENTRYPOINT ["/docker-entrypoint.sh"]

docker-entrypoint.sh:

#!/bin/bash
set -e

# Start Unity with Remote Server
echo "Starting Unity..."
xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' \
  unity-editor \
  -projectPath /project \
  -batchmode \
  -nographics \
  -logFile - &

# Wait for Remote Server
echo "Waiting for Remote Server..."
for i in $(seq 1 60); do
  if curl -s http://127.0.0.1:8787/health; then
    echo "Remote Server is ready!"
    break
  fi
  sleep 5
done

# Execute command
exec "$@"

Usage:

# Build Docker image
docker build -t unity-builder .

# Run build
docker run --rm \
  -v $(pwd)/Builds:/project/Builds \
  unity-builder \
  /scripts/unity-build.sh android /project/Builds/game.apk

CircleCI

.circleci/config.yml:

version: 2.1

orbs:
  unity: game-ci/unity@1.0.0

jobs:
  test-and-build:
    machine:
      image: ubuntu-2204:current
    steps:
      - checkout
      
      - run:
          name: Install Unity
          command: |
            # Install Unity Hub
            wget -q -O- https://hub.unity3d.com/linux/keys/public | sudo apt-key add -
            sudo sh -c 'echo "deb https://hub.unity3d.com/linux/repos/deb stable main" > /etc/apt/sources.list.d/unityhub.list'
            sudo apt update && sudo apt install unityhub
            
            # Install Unity version
            unityhub install --version 2022.3.10f1 --module android
      
      - run:
          name: Start Unity with Remote Server
          background: true
          command: |
            Unity -batchmode \
              -projectPath . \
              -logFile unity.log
      
      - run:
          name: Wait for Remote Server
          command: |
            for i in $(seq 1 60); do
              if curl -s http://127.0.0.1:8787/health; then
                echo "Server ready"
                exit 0
              fi
              sleep 5
            done
            exit 1
      
      - run:
          name: Run Tests
          command: |
            python3 Scripts/unity-control.py compile --timeout 120
            python3 Scripts/unity-control.py errors --level error
      
      - run:
          name: Build Android
          command: |
            ./Scripts/unity-build.sh android Builds/game.apk
      
      - store_artifacts:
          path: Builds/game.apk
          destination: android-build

workflows:
  build-and-test:
    jobs:
      - test-and-build:
          filters:
            branches:
              only:
                - main
                - develop

Azure DevOps

azure-pipelines.yml:

trigger:
  branches:
    include:
      - main
      - develop

pool:
  vmImage: 'ubuntu-latest'

variables:
  UNITY_VERSION: '2022.3.10f1'
  UNITY_REMOTE_SERVER: 'http://127.0.0.1:8787'

stages:
  - stage: Test
    jobs:
      - job: RunTests
        steps:
          - task: UnityBuildTask@3
            inputs:
              buildTarget: 'StandaloneLinux64'
              unityVersion: $(UNITY_VERSION)
              cmdArgs: '-batchmode -quit -runTests'
          
          - script: |
              # Check compilation errors
              curl $(UNITY_REMOTE_SERVER)/errors?level=error > errors.json
              if [ $(jq '. | length' errors.json) -gt 0 ]; then
                echo "##vso[task.logissue type=error]Compilation errors found"
                cat errors.json | jq '.'
                exit 1
              fi
            displayName: 'Check Compilation Errors'
  
  - stage: Build
    dependsOn: Test
    jobs:
      - job: BuildAndroid
        steps:
          - script: |
              chmod +x Scripts/unity-build.sh
              ./Scripts/unity-build.sh android $(Build.ArtifactStagingDirectory)/game.apk
            displayName: 'Build Android'
          
          - task: PublishBuildArtifacts@1
            inputs:
              pathtoPublish: '$(Build.ArtifactStagingDirectory)'
              artifactName: 'android-build'

  - stage: Deploy
    dependsOn: Build
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
    jobs:
      - deployment: DeployToStore
        environment: 'production'
        strategy:
          runOnce:
            deploy:
              steps:
                - script: echo "Deploy to store"

Best Practices

1. Health Checks

Always verify server is ready:

wait_for_server() {
  local max_attempts=60
  local attempt=0
  
  while [ $attempt -lt $max_attempts ]; do
    if curl -s http://127.0.0.1:8787/health > /dev/null; then
      echo "Server is ready!"
      return 0
    fi
    
    echo "Waiting for server... ($attempt/$max_attempts)"
    sleep 5
    ((attempt++))
  done
  
  echo "Server failed to start"
  return 1
}

2. Error Handling

Check for compilation errors before building:

def check_compilation():
    client = UnityRemoteClient()
    
    # Wait for compilation
    result = client.await_compile(timeout=300)
    if not result.get('ok'):
        raise Exception(f"Compilation failed: {result.get('error')}")
    
    # Check for errors
    errors = client.get_errors(level='error')
    if errors:
        print("Compilation errors found:")
        for error in errors:
            print(f"  - {error['message']}")
        raise Exception("Build aborted due to compilation errors")

3. Parallel Builds

Build multiple platforms concurrently:

jobs:
  build:
    strategy:
      matrix:
        platform: [android, ios, windows]
    runs-on: ${{ matrix.runner }}
    steps:
      - run: ./Scripts/unity-build.sh ${{ matrix.platform }} output.${{ matrix.ext }}

4. Caching

Cache Unity Library folder:

- uses: actions/cache@v3
  with:
    path: Library
    key: Library-${{ matrix.platform }}-${{ hashFiles('Assets/**', 'Packages/**', 'ProjectSettings/**') }}
    restore-keys: |
      Library-${{ matrix.platform }}-
      Library-

5. Notifications

Send build status notifications:

notify_slack() {
  local status=$1
  local platform=$2
  
  curl -X POST $SLACK_WEBHOOK \
    -H 'Content-Type: application/json' \
    -d "{
      \"text\": \"Build $status for $platform\",
      \"color\": \"$([[ $status == 'success' ]] && echo 'good' || echo 'danger')\"
    }"
}

Troubleshooting

Unity Not Starting

  • Check Unity license activation
  • Verify Unity installation path
  • Check available system resources

Server Not Responding

  • Ensure Remote Server package is installed
  • Check if port 8787 is available
  • Review Unity logs for errors

Build Failures

  • Check disk space
  • Verify platform modules installed
  • Review compilation errors

Timeout Issues

  • Increase timeout values
  • Optimize project size
  • Use incremental builds