Upcoming Presentation – Intro to Unity – March 8th – Ontario, CA
If you’re in the area, come check out my talk on March 8th!
http://www.meetup.com/NerdsLikeYou/events/227552230/
If you’re in the area, come check out my talk on March 8th!
http://www.meetup.com/NerdsLikeYou/events/227552230/
Do you want to use Octopus Deploy to manage deployments on AWS EC2 instances in an Auto Scaling Group? We did, and ran into some issues, so here’s how we solved them.
Octopus Deploy is a great tool for handling deployment across all your environments, but setting it up with AWS EC2 requires a bit of work. This post assumes you have at least a brief understanding of how Octopus works and a bit of familiarity with Amazon Web Services.
The first thing we need to do is trigger installation of the Octopus tentacle when a new server is brought up.
To do this, we use the EC2 config service along with a powershell script in the userdata for the instance.
Some of the scripts have Script Variables before them.
Pay close attention to those and be sure to set the proper values for any Mandatory variables.
The first script you’ll need is the bootstrapper. It’s designed to be small and unchanging, so you can assign it once in your launch configuration and not need to change it later.
The bootstrapper only has 2 tasks. Download your primary setup script “serverSetup.ps1” and execute it.
Here’s what the script looks like.
$sourceBucketName | Mandatory | Set this to your S3 bucket name |
<powershell> Write-Output "Downloading Server Script from S3" $sourceBucketName = "YOUR_S3_BUCKET_HERE" $serverSetupFileName = "serverSetup.ps1" $localSetupFileName = "c:\temp\serverSetup.ps1" $localPath = "C:\temp\" Read-s3object -bucketname $sourceBucketName -Key $serverSetupFileName -File $localSetupFileName invoke-expression -Command $localPath\ServerSetup.ps1 </powershell>
Something to note here is the set of <powershell> tags around the code. Those exist because we’ll be placing this script in the EC2 userdata, which needs them to understand which scripting language we’re using. If you execute the script manually from a powershell command line, you’ll see errors for those lines, but you can ignore them.
Much like the bootstrap script, the ServerSetup.ps1 script doesn’t contain much logic. It’s intended to download and execute other scripts that perform specific actions.
$sourceBucketName | Mandatory | Set this to your S3 bucket name |
$scriptFolderName | Optional | Set this to the subfolder holding your scripts |
# If for whatever reason this doesn't work, check this file: Start-Transcript -path "D:\ServerSetupLog.txt" -append Write-Output "####################################" Write-Output "Starting ServerSetup.ps1" ### Custom Variables ### $scriptFolderName = "Scripts" # Variables that must be set here AND in SetVariables.ps1 $sourceBucketName = "YOUR_S3_BUCKET_HERE" $localPath = "C:\temp\" $instanceId = Invoke-RestMethod -Method Get -Uri http://169.254.169.254/latest/meta-data/instance-id ### Custom Variables ### # Download All files from S3 Set-Location $localPath Write-Output "Downloading scripts from $sourceBucketName\$scriptFolderName to local path $localPath" $objects = get-s3object -bucketname $sourceBucketName -KeyPrefix $scriptFolderName foreach($object in $objects) { $localFileName = $object.Key -replace $scriptFolderName, '' if ($localFileName -ne '' -and $localFileName -ne '/') { $localFilePath = Join-Path $localPath $localFileName Write-Output "Copying File " $localFileName " to " $localFilePath Copy-S3Object -BucketName $sourceBucketName -Key $object.Key -LocalFile $localFilePath } } # Import any needed modules here Import-Module AWSPowerShell & .\setInstanceNameTag.ps1 & .\installCertificates.ps1 Set-Location $localPath & .\installTentacle.ps1 & .\addOctopusMachineIdTag.ps1 & .\autoDeploy.ps1 Write-Output "Deployment complete." # Write the tentacle installation log to S3 Write-S3Object -bucketname $sourceBucketName -File D:\ServerSetupLog.txt -key ServerSetupLogs/$instanceId/ServerSetupLog.txt Stop-TranscriptNote: If you decide to just copy/paste this script, make sure to replace the instances of "yourS3BucketName" with your actual bucket name, and user a log path that exists for your EC2 instances (this one uses the D: drive).
Let’s break this script down and see what’s going on.
# If for whatever reason this doesn't work, check this file: Start-Transcript -path "D:\ServerSetupLog.txt" -append Write-Output "####################################" Write-Output "Starting ServerSetup.ps1"
Here, we’re just starting a transcript of everything that executes so we can upload it to S3 later. This helps when you have issues with a server or your scripts and want to find out exactly what happened. The example here uses the D: drive, so if you don’t have a D: drive on your EC2 images, switch this to another location.
### Custom Variables ### $scriptFolderName = "Scripts" # Variables that must be set here AND in SetVariables.ps1 $sourceBucketName = "YOUR_S3_BUCKET_HERE" $localPath = "C:\temp\" ### Custom Variables ### # Download All files from S3 Set-Location $localPath Write-Output "Downloading scripts from $sourceBucketName\$scriptFolderName to local path $localPath" $objects = get-s3object -bucketname $sourceBucketName -KeyPrefix $scriptFolderName foreach($object in $objects) { $localFileName = $object.Key -replace $scriptFolderName, '' if ($localFileName -ne '' -and $localFileName -ne '/') { $localFilePath = Join-Path $localPath $localFileName Write-Output "Copying File " $localFileName " to " $localFilePath Copy-S3Object -BucketName $sourceBucketName -Key $object.Key -LocalFile $localFilePath } }
Here, we’re downloading all of the files in the Scripts subfolder of our bucket (unless you changed the $scriptFolderName). If you look at the screenshot of S3 below, you’ll see that one of the folders in the root is named “Scripts“. The scripts are downloaded to the C:\temp folder (from $localPath), and will be executed in the next step.
# Import any needed modules here Import-Module AWSPowerShell & .\setInstanceNameTag.ps1 & .\installCertificates.ps1 Set-Location $localPath & .\installTentacle.ps1 & .\addOctopusMachineIdTag.ps1 & .\autoDeploy.ps1 Write-Output "Deployment complete."
The first thing happening in this chunk is we import the AWS powershell module.
Next, we execute our set of powershell scripts that complete different tasks. Again, all of these powershell scripts are hosted in the “Scripts” subfolder of the S3 bucket.
If you decide to add another step, just create a new script and add the call to it here.
# Write the tentacle installation log to S3 Write-S3Object -bucketname $sourceBucketName -File D:\ServerSetupLog.txt -key ServerSetupLogs/$instanceId/ServerSetupLog.txt Stop-Transcript
The final step is to upload the transcript to our “ServerSetupLogs” subfolder of the S3 bucket. Again, if you rename anything, just make sure to duplicate the renaming here.
So far, we’ve only seen scripts intended to download and execute other scripts. These scripts are where we keep the logic for registering new tentacles, installing certificates, and tagging our instances. I’m going to cover the scripts in order of execution. You may not be interested in them all, but I recommend you at least take quick look at each to see what it’s doing. All of the scripts other than “InstallCertificates.ps1” are mandatory for the entire system to work properly (as defined in this post).
This script is where you set all of your custom variables (other than the 2 that were mandatory above).
It also calculates many variables the other scripts need, like the current AWS region of the EC2 instance.
$sourceBucketName | Mandatory | Set this to your S3 bucket name |
$octopusApiKey | Mandatory | Set your Octopus Server API Key here |
$octopusServerUrl | Mandatory | The IP & Port of your Octopus Server |
$octopusServerThumbprint | Mandatory | Set your Octopus Server thumbprint here |
$octopusInstallerName | Mandatory | Set this to the Tentacle installer filename that you stored in your S3 bucket |
#### Customizable Variables #### $sourceBucketName = "YOUR_S3_BUCKET_HERE" $localPath = "C:\temp\" $octopusApiKey = "YOUR_OCTOPUS_API_KEY" #API-XXXXXXXXXXXXXXXXXXXXXXXXXXX $octopusServerUrl = "http://YOUR_OCTOPUS_SERVER_IP_AND_PORT/" #192.168.1.1:81 $octopusServerThumbprint = "YOUR_OCTOPUS_SERVER_THUMBPRINT" $tentacleListenPort = 10933 $tentacleHomeDirectory = "D:\Octopus" $tentacleAppDirectory = "D:\Octopus\Applications" $tentacleConfigFile = "D:\Octopus\Tentacle\Tentacle.config" $octopusInstallerName = "Octopus.Tentacle.3.2.13-x64.msi" #### Customizable Variables #### ## Get Variables we need ## $availabilityZone = Invoke-WebRequest http://169.254.169.254/latest/meta-data/placement/availability-zone -UseBasicParsing $region = $availabilityZone.Content.Trim("a","b","c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m") # Get our public IP address. # This is used for registration with the Octopus server, to give the server an endpoint to contact this tentacle on. $ipAddress = (Invoke-RestMethod -Method Get -Uri http://169.254.169.254/latest/meta-data/public-ipv4) $ipAddress = $ipAddress.Trim() ## Get Variables we need ## ### Get-RolesAndEnvironment ### $instanceId = (Invoke-RestMethod -Method Get -Uri http://169.254.169.254/latest/meta-data/instance-id) $instance = ((Get-EC2Instance -region $region -Instance $instanceId).RunningInstance) $myInstance = $instance | Where-Object { $_.InstanceId -eq $instanceId } $roles = ($myInstance.Tags | Where-Object { $_.Key -eq "Roles" }).Value $environment = ($myInstance.Tags | Where-Object { $_.Key -eq "Environment" }).Value ### Get-RolesAndEnvironment ### if (!$variablesShown) { Write-Output "Variables: Used" Write-Output "Source Bucket - $sourceBucketName" Write-Output "Source Bucket Key (folder) - $keyPrefix" Write-Output "Local Script Path - $localPath" Write-Output "Octopus Settings" Write-Output "================" Write-Output "API Key - $octopusApiKey" Write-Output "Octopus Endpoint - $octopusServerUrl" Write-Output "Octopus Thumbprint - $octopusServerThumbprint" Write-Output "Tentacle ListenPort - $tentacleListenPort" Write-Output "Tentacle HomeDirectory - $tentacleHomeDirectory" Write-Output "Tentacle App Directory - $tentacleAppDirectory" Write-Output "Tentacle ConfigFile - $tentacleConfigFile" Write-Output "Tentacle Installer - $octopusInstallerName" Write-Output "EC2 Settings" Write-Output "============" Write-Output "Region - $region" Write-Output "Ip Address - $ipAddress" Write-Output "InstanceId - $instanceId" Write-Output "Roles - $roles" Write-Output "Environment - $environment" $global:variablesShown = 1; }
The purpose of this script is to set any tags you want on the instance. The script as it stands only sets the “Name” tag so that it shows our Octopus Environment and Octopus Role.
The end result will be something like “Development – www.jasonweimann.com” or “Staging – api.jasonweimann.com|www.jasonweimann.com” (the pipe is used when an instance is in multiple Octopus roles).
# This script is used to set custom tags on the EC2 instance # It currently only sets the name tag to a combination of the Environment & Roles tags # ex. "Production www.yoursite.com" # "Development www.yoursite.com" Write-Output "####################################" Write-Output "Starting SetInstanceNameTag.ps1" # Get our variables . .\SetVariables.ps1 # Name formatting happens here. If you want to change the format, modify this. # This needs to be after SetVariables so we have the environment & roles $instanceName = $environment + " " + $roles Import-Module AWSPowerShell # Sets the name tag to $instanceName function Set-InstanceNameTag() { $instanceId = (Invoke-RestMethod -Method Get -Uri http://169.254.169.254/latest/meta-data/instance-id) #Get the current value of the name tag Remove-EC2Tag ` -Resource $instanceId ` -Tag @{ Key="Name" } ` -Region $Region ` -ProfileName $ProfileName ` -Force Write-Output "Setting Name to $instanceName" New-EC2Tag ` -Resource $instanceId ` -Tag @{ Key="Name"; Value=$instanceName } ` -Region $Region ` } Set-InstanceNameTag
You may notice that we make some calls that were made previously in here like Import-Module. This is done so that the scripts can be run independently as well. We try to keep each script completely contained so that it doesn’t break if another script is modified.
The “InstallCertificates.ps1” script is Optional if you don’t have any certificates. There is no harm in leaving it in though so you can add certificates at a later date. If no certificates exist for the Octopus role the EC2 instance is a member of, nothing will happen.
# This script is used to install required HTTPS certificates on the EC2 instance # It looks at the roles the instance is in, then downloads and installs any required certificates from S3 # ex. Production www.yourwebsite.com # Development www.jasonweimann.com Write-Output "####################################" Write-Output "Starting InstallCertificates.ps1" # Get our variables . .\SetVariables.ps1 # Import any custom modules here Import-Module AWSPowerShell set-location cert:\localMachine\my function Import-PfxCertificate { param([String]$certPath,[String]$certRootStore = "LocalMachine",[String]$certStore = "My") $pfxPass = "cc540540!" | ConvertTo-SecureString -AsPlainText -Force $pfx = new-object System.Security.Cryptography.X509Certificates.X509Certificate2 $pfx.import($certPath,$pfxPass,"PersistKeySet") $store = new-object System.Security.Cryptography.X509Certificates.X509Store($certStore,$certRootStore) $store.open("MaxAllowed") $store.add($pfx) Write-Output "Store $certStore RootStore $certRootStore" $store.close() } function Download-Certificates() { param ( [Parameter(Mandatory=$True)] [string]$role ) $keyPrefix = "Certificates/$role" $objects = get-s3object -bucketname $sourceBucketName -KeyPrefix $keyPrefix foreach($object in $objects) { $localFileName = $object.Key -replace $keyPrefix, '' Write-Output "Copying File to $localFileName" if ($localFileName -ne '' -and $localFileName -ne '/') { $localFilePath = Join-Path $localPath $localFileName Write-Output "Copying File " $localFileName " to " $localFilePath Copy-S3Object -BucketName $sourceBucketName -Key $object.Key -LocalFile $localFilePath Write-Output "Installing Certificate $localFilePath" Import-PfxCertificate $localFilePath Write-Output "Certificate Install Complete" } } } foreach($roleName in $roles.Split("{|}")) { Download-Certificates $roleName }This script has two main functions.
If you don’t have any certificates, there’s still no harm in leaving the script there. If you decide to add one later, it’s as easy as dropping it into your S3 bucket.
This handles importing the certificate file once it’s been downloaded from your S3 bucket.
This function will download each certificate that the EC2 instance requires. The certificates are placed in a folder per role. In the example below, I have two certificate folders. These both match Octopus role names in my octopus deploy server. If I ever need to add new certificates to my setup, I just place them in the folder for the corrisponding role and they’ll be auto installed when new instances come up.
We could also run this script manually to update all of the certificates on EC2 instances that are already running if needed.
The installTentacle.ps1 script is actually a modified version of one I found online (I can’t find the original anywhere or I’d link it here).
Its does 3 things.
The reason I download the installer from S3 is that we had an issue recently where a newer tentacle installer was released and was incompatible with our server. To avoid an issue like that in the future, we just store the tentacle version we’re happy with on S3 and let the EC2 instances grab it from there.
You will need to check the version # and make sure you change $octopusInstallerName in “SetVariables.ps1“ whichever tentacle version you’re using. The one in the script that you’d replace is “Octopus.Tentacle.3.2.13-x64.msi“.
The tentacle installer MUST be in the “Scripts” subfolder of your S3 bucket.
# This script runs installation of the octopus deploy tentacle # and registration with the Octopus server located at octopusServerUrl Write-Output "####################################" Write-Output "Starting InstallTentacle.ps1" # Get our variables . .\SetVariables.ps1 # Store original working path so we can change back to it at the end of the script $originalWorkingPath = (Get-Item -Path ".\" -Verbose).FullName # Installation Function function Install-Tentacle { param ( [Parameter(Mandatory=$True)] [string]$apiKey, [Parameter(Mandatory=$True)] [System.Uri]$octopusServerUrl, [Parameter(Mandatory=$True)] [string]$environment, [Parameter(Mandatory=$True)] [string]$role ) Write-Output "Beginning Tentacle installation" # The tentacle.msi file m ust be in the current working directory before this script launches # ServerSetup.ps1 downloads the version of the tentacle we use to c:\temp\tentacle.msi $tentaclePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath(".\Tentacle.msi") # Remove any existing instance of the tentacle - This is only needed when re-running the script after the initial install # to prevent issues with certificates changing during installation. It should fail on a new EC2 instance. Write-Output "Removing Previous Installation if it exists" $msiExitCode = (Start-Process -FilePath "msiexec.exe" -ArgumentList "/x $octopusInstallerName /quiet" -Wait -Passthru).ExitCode # Start the actual installation of the tentacle Write-Output "Installing MSI" $msiExitCode = (Start-Process -FilePath "msiexec.exe" -ArgumentList "/i $octopusInstallerName /quiet" -Wait -Passthru).ExitCode Write-Output "Tentacle MSI installer returned exit code $msiExitCode" if ($msiExitCode -ne 0) { throw "Installation aborted" } # Open the firewall port Write-Output "Open port $tentacleListenPort on Windows Firewall" & netsh.exe firewall add portopening TCP $tentacleListenPort "Octopus Tentacle" if ($lastExitCode -ne 0) { throw "Installation failed when modifying firewall rules" } Write-Output "Configuring and registering Tentacle" # Change directory to where tentacle.exe is located # tentacle.exe is a tool provided with Octopus deploy to handle registration and other tasks on a tentacle cd "${env:ProgramFiles}\Octopus Deploy\Tentacle" # Run the required tentacle.exe commands to register with the Octopus server & .\tentacle.exe create-instance --instance "Tentacle" --config $tentacleConfigFile --console | Write-Host if ($lastExitCode -ne 0) { throw "Installation failed on create-instance" } & .\tentacle.exe configure --instance "Tentacle" --home $tentacleHomeDirectory --console | Write-Host if ($lastExitCode -ne 0) { throw "Installation failed on configure" } & .\tentacle.exe configure --instance "Tentacle" --app $tentacleAppDirectory --console | Write-Host if ($lastExitCode -ne 0) { throw "Installation failed on configure" } & .\tentacle.exe configure --instance "Tentacle" --port $tentacleListenPort --console | Write-Host if ($lastExitCode -ne 0) { throw "Installation failed on configure" } & .\tentacle.exe new-certificate --instance "Tentacle" --console | Write-Host if ($lastExitCode -ne 0) { throw "Installation failed on creating new certificate" } & .\tentacle.exe configure --instance "Tentacle" --trust $octopusServerThumbprint --console | Write-Host if ($lastExitCode -ne 0) { throw "Installation failed on configure" } # We may need to register with multiple roles. To accomplish that, we need to assign a variable with a --role for each one. # Concatanating this in the Invoke-Expression call does not work. foreach($roleName in $roles.Split("{|}")) { $roleExp += " --role '$roleName' " } # Create the register expression (needed because of multiple roles) $registerExp = ".\tentacle.exe register-with --instance ""Tentacle"" --server $octopusServerUrl --environment $environment $roleExp --name $instanceId --publicHostName $ipAddress --apiKey $apiKey --comms-style TentaclePassive --force --console | Write-Host" Write-Output $registerExp # Log the expression for debugging Invoke-Expression $registerExp if ($lastExitCode -ne 0) { Write-Output "Environment: $environment Role: $role Name: $instanceId PublicHostName: ipAddress" throw "Installation failed on register-with" } & .\tentacle.exe service --instance "Tentacle" --install --start --console | Write-Host if ($lastExitCode -ne 0) { throw "Installation failed on service install" } Write-Output "Tentacle commands complete" } # Call the installation function Install-Tentacle -apikey $octopusApiKey -octopusServerUrl $octopusServerUrl -environment $environment -role $roles # Change back to original working directory cd $originalWorkingPathI won’t cover everything this script is doing, but if you scan through, you’ll see that most of it is just setup of the Octopus Deploy Tentacle. The part I want to point out though is this
# We may need to register with multiple roles. To accomplish that, we need to assign a variable with a --role for each one. # Concatanating this in the Invoke-Expression call does not work. foreach($roleName in $registerInRoles.Split("{|}")) { $roleExp += " --role '$roleName' " } # Create the register expression (needed because of multiple roles) $registerExp = ".\tentacle.exe register-with --instance ""Tentacle"" --server $octopusServerUrl --environment $environment $roleExp --name $instanceId --publicHostName $ipAddress --apiKey $apiKey --comms-style TentaclePassive --force --console | Write-Host" Write-Output $registerExp # Log the expression for debugging Invoke-Expression $registerExpThis is the section that’s registering our new tentacle with the Octopus Deploy server. Most examples you’ll see only show how to register with a single role. In our case, we want to be able to host multiple roles on a single instance for the Development environment. When we setup roles later, you’ll see that we split them with a pipe, and in this part of the script, we Split them based on the pipe, then add a –role entry for each role we want to be a member of.
It may seem like we’ve already gone through a bunch of scripts, and we have, but that’s just because we want everything to be very modular. You may also be wondering why we’re doing another Tag change in a separate file… It’s only because this must be done after the tentacle registration is complete and we wanted the Name set before anything else runs. The “AddOctopusMachineIdTag.ps1” script works by querying the Octopus Deploy server for a machine by Name/ AWS instanceId (our octopus target names are the instanceId). When it finds the correct machine, it places a tag on the EC2 instance named “OctopusMachineId“. This tag is used later for automatic deregistration. The Octopus machine Id is what you see in the address bar when you look at one of your deployment. Ex. 192.168.1.1/app#/machines/Machines-167 Before you use this script, be sure to add your Octopus IP & API keys
# This script is used to set the OctopusMachineId tags on the EC2 instance # This tag is used by cloudwatch to auto deregister the tentacle when the instance is unavailable Write-Output "####################################" Write-Output "Starting AddOctopusMachineIdTag.ps1" # Get our variables . .\SetVariables.ps1 Add-Type -Path "C:\Program Files\Octopus Deploy\Tentacle\Newtonsoft.Json.dll" # Path to Newtonsoft.Json.dll Add-Type -Path "C:\Program Files\Octopus Deploy\Tentacle\Octopus.Client.dll" # Path to Octopus.Client.dll $endpoint = new-object Octopus.Client.OctopusServerEndpoint $octopusServerUrl,$octopusApiKey $repository = new-object Octopus.Client.OctopusRepository $endpoint $findmachine = $repository.Machines.FindByName("$instanceId") $octopusMachineid = $findmachine.id # Set the OctopusMachineId tag New-EC2Tag ` -Resource $instanceId ` -Tag @{ Key="OctopusMachineId"; Value=$octopusMachineid } ` -Region $region ` Write-Output "Set OctopusMachineId to $octopusMachineid"
This is the final script of the boostrapping process. This script is where our new EC2 instance requests the latest release from your Octopus Deploy server. It works by using the Octopus servers REST api to determine which release(s) should be on the instance based on it’s Environment and Roles. It then requests a deployment to itself of those releases.
# This script tells the Octopus server to deploy any releases this # instance should be running. It looks at the Environment and roles # to determine what release # should be deployed, then sends commands # to the server to begin that deployment. # This is run only on the initial startup, and is launched by ServerSetup.ps1 Write-Output "####################################" Write-Output "Starting AutoDeploy.ps1" Write-Output "Starting AutoDeploy" # Get our variables . .\SetVariables.ps1 $Header = @{ "X-Octopus-ApiKey" = $octopusApiKey } # This header is used in all the octopus rest api calls # Get the project names and replace any periods with - for the rest calls (periods in a URI are of course problematic) $projectNames = $roles.replace(".", "-") # Function to request the release for a project # This is called once for each project/role this EC2 instance is a member of function GetReleaseForProject { param ( [Parameter(Mandatory=$True)] [string]$projectName ) Write-Output "Getting Build for $projectName" # Get our Octopus Machine Id # This is needed for our rest calls and is the internal machine ID Octopus uses # We get all machines here, then find the one who's name matches our instanceId, then select the Id to use later $instanceId = (Invoke-RestMethod -Method Get -Uri http://169.254.169.254/latest/meta-data/instance-id) $global:allMachines = Invoke-WebRequest -UseBasicParsing $octopusServerUrl/api/machines -header $Header | ConvertFrom-Json Write-Output "Getting MachineID for InstanceID: $instanceId" Write-Output "allMachines: $allMachines.Count" $OctopusMachineId = $allMachines.Items.where({$_.Name -eq "$instanceId"}).Id Write-Output "OctopusMachineId: $OctopusMachineId" # Getting Environment and Project By Name - NOTE: This is not the same as the Environment Tag $fullUri = "$octopusServerUrl/api/projects/$ProjectName" $Project = Invoke-WebRequest -UseBasicParsing -Uri $fullUri -Headers $Header| ConvertFrom-Json $Environments = Invoke-WebRequest -UseBasicParsing -Uri $octopusServerUrl/api/Environments/all -Headers $Header| ConvertFrom-Json Write-Output "Environments: $Environments" $OctopusEnvironment = $Environments | ?{$_.name -eq $environment} # Finally set the environment and project id strings $environmentId = $OctopusEnvironment.Id $projectId = $Project.Id # Get the most recent release that matches our environmentId & projectId $fullUri = "$octopusServerUrl/api/deployments?Environments=$environmentId&Projects=$projectId&SpecificMachineIds=instanceId&Take=1" $currentRelease = Invoke-WebRequest -UseBasicParsing -Uri "$fullUri" -Headers $Header | ConvertFrom-Json # Set our machine name in an array of MachineNames to be converted into a JSON array for the rest call [string[]] $MachineNames = $OctopusMachineId # Generate the JSON for our rest call by creating an object in powershell and using ConvertTo-Json $DeploymentBody = @{ ReleaseID = $currentRelease.Items[0].ReleaseID EnvironmentID = $OctopusEnvironment.id SpecificMachineIds = $MachineNames } | ConvertTo-Json $fullUri = "$octopusServerUrl/api/deployments" Write-Output "Full Uri: $fullUri" Write-Output "DeploymentBody: $DeploymentBody" Write-Output "Headers: $Header" # Make the rest call to start a deployment $deploymentCall = Invoke-WebRequest -UseBasicParsing -Uri $fullUri -Method Post -Headers $Header -Body $DeploymentBody } # Split all of our project names into an array to loop over Write-Output "Project Names: $projectNames" $projectsSplit = $projectNames.split("|") Write-Output "Split Projects $projectsSplit" # Call GetReleaseForProject for each role/project this instance is a member of foreach($projectName in $projectsSplit) { GetReleaseForProject $projectName }
Next, we need to setup your S3 bucket. If you don’t feel comfortable with S3 via command line, I’d recommend you use a tool like S3 Browser for the next part.
Your S3 bucket should look like this.
In the screenshot, the “serverSetup.ps1” script is placed in the root of the bucket “deploy“.
There are 3 subfolders that each serve a unique purpose. If you read the scripts above, you probably already know what two of them are used for. But just in-case it’s not clear, here’s a quick description.
Certificates | Holds SSL certificates you want installed on your machines (this assumes you have certificates you want installed) |
Scripts | All of the scripts from above that do the real work are placed here. (In the “Scripts” folder of the download) The Tentacle Installer and AWSSDK.DLL files are also placed here. |
ServerSetupLogs | Inside, there are subfolders for each EC2 instance that contain any logs you upload from it. (The serverSetup.ps1 script will upload the full log here automatically at the end) |
Let’s create the folders from the screenshot in your own bucket now.
You can download the scripts and README files as a zip below
.Be sure to replace the required strings in each script with your own Octopus Server IP, S3 Bucket Name, API key.
This part is just meant to show how our environments are setup and how to use the roles in the scripts above. You don’t need to match our naming scheme, but you do need to understand how the roles are defined.
What are these roles???
Roles are just projects in Octopus. Each EC2 instance can have one or more roles (host one or more websites/projects).
With the default naming scheme in the scripts, your servers Name tag will be set to {EnvironmentName} – {RoleName}
For the example projects below, if you ran each role on it’s own EC2 instance in the Production environment their names would be
If you ran both sites on the same EC2 instance in Development, it would be named
Because we’ll be using an Auto Scaling Group, the first thing we need to do is create a Launch Configuration. If you already know how to do this, then just pay attention for the parts in red. For everyone else, just follow step by step.
Before we can create the instance, you need to have 2 things setup.
Open the Launch Configurations page from the EC2 menu
Select Create launch configuration
Select your AMI
Click Next: Configure Details
Choose a name for the Launch Configuration
Select the IAM role you created above (it needs access to call AWS services)
For “User data“, select “As file”.
Check the “Monitoring” checkbox
Choose the “bootstrap.ps1” file
Click next.
In Storage, you’ll want to Create a D: drive for your deployments
Don’t forget to check Delete on Termination unless you want your HDD images staying around even after a server has been terminated.
Now, select your Security Group that gives the Octopus server access to connect to the Tentacle (created above)
Double check your settings, then click “Create launch configuration”
In the first step, you don’t need to do anything special.
Select a name, subnet, and group size.
Click “Configure Notifications”
This is how you will determine what environment and roles the instance(s) in your Auto Scaling Group will be in.
Do this by setting 2 tags. Environment & Roles.
You can have other tags if you need them, but Environment & Roles must be there.
Environment | This must match the name of your Octopus Environment. If you name it Development, use Development here. If your Octopus environment name doesn’t match what’s in here, the deployment won’t work. |
Role | This must match your Octopus Project Name. If you want multiple roles, split them with a pipe | Do not add extra spaces. (ex. www.jasonweimann.com|api.jasonweimann.com) |
Environment must match the name of your Octopus Environment. If you name it Development, use Development here. If your Octopus environment name doesn’t match what’s in here, the deployment won’t work.
Role must match your Octopus Project name
Click “Create Auto Scaling group”
Looking at Errors / Issues
After a few minutes, your EC2 instance should start up. If it doesn’t automatically register, get renamed, and take a deployment from your server, don’t worry.
Remember above when we added the scripts, one of them copies logs to your S3 Bucket.
Inspect the S3 Buckets subfolder “ServerSetupLogs“.
You’ll see a sub-folder for each EC2 instance that’s come up with the deployment scripts running.
Look into those logs and search for the error/issue…
If you can’t find your log there, it should also be available on the root of the D: drive. Just remote connect to the instance and look at the log there.
If you’re unsure what happened and need help, feel free to comment below.
If your server never got renamed, and the scripts didn’t even get executed, it may be from using a custom AMI.
If you want to use a custom AMI, make sure you have checked the Execute User Data option in the EC2 Config Services application before creating the AMI. (you may need to re-save your AMI with this option checked)
The last thing you need to do is setup deregistration.
The deregistration process works by monitoring CloudWatch events and triggering a Lambda function.
First, we need to create the Lambda
Open the Lambda page and hit Create a Lambda Function.
On the “Select blueprint” page, just click “Skip”
Now, give your lambda a name and description.
Select Node.js for the Runtime
Select Edit code inline for Code entry type
Paste the code below into the Code area
IMPORTANT: You need to put in your Octopus Server IP & API Key in the script before pasting it.
var aws = require("aws-sdk"); exports.handler = function(event, context) { if (event.detail.state != "terminated") context.succeed(event); var http = require('http'); var instanceId = event.detail["instance-id"]; // [""] required because of the hyphen var currentRegion = event.region; console.log('EC2InstanceId =', instanceId); var ec2 = new aws.EC2({region: currentRegion}); //event.ResourceProperties.Region}); var params = { DryRun: false, Filters: [ { Name: 'resource-id', Values: [ instanceId, ] }, { Name: 'key', Values: [ 'OctopusMachineId', / more items / ] }, ], MaxResults: 5, NextToken: 'STRING_VALUE' }; console.log("Getting MachineName for InstanceID: " + instanceId); ec2.describeTags(params, function(err, data) { if (err) { console.log(err, err.stack); // an error occurred context.succeed(err); } else { console.log(data); // successful response var octopusMachineId = data.Tags[0].Value; var fullPath = '/api/machines/' + octopusMachineId + '?apiKey=YOUR_OCTOPUS_API_KEY_HERE'; // API-XXXXXXXXXXXXXXXXXXXXXXXXXX var options = { host: 'YOUR_OCTOPUS_SERVER_IP_HERE', port: 81, path: fullPath, method: 'Delete' }; callback = function(response) { var str = ''; response.on('data', function (chunk) { str += chunk; }); response.on('end', function () { console.log(str); context.succeed(str); }); } http.request(options, callback).end(); } }); };
Leave the Handler with the default value.
Set the role to one that has access to S3 (if you don’t have one, you’ll need to make one now)
You can leave the memory and timeout values at the defaults.
Continue to the Review page
Review your lambda then click “Create function”
The last thing we need to do is setup a Cloudwatch event for EC2 termination.
This event will trigger the lambda that does deregistration of the tentacle.
Open the Cloudwatch page.
Under Events, select Rules and click “Create rule”
Select “EC2 instance state change notification” for the event source.
Select “Specific state(s)” and choose “Terminated“.
Add a target and set it to the Lambda you just created.
It should look like this when you’re done.
Continue to the next page.
Give the Rule a name and make sure Enabled is checked.
Click “Create Rule” and you’re done.
With this rule created, any EC2 instance that terminates should automatically de-register from your octopus server.
This is a big subject, with many parts. There’s a lot to learn, but if you download the scripts and make the few configuration changes, you should be able to get it working.
If you have Questions about anything here, please post them in the comments and I’ll do my best to assist.
You can download the scripts and README files as a zip below
This post will be the first in what will likely be a long series. Dependency Injection and Unit Testing are generally considered staples in modern software development. Game development for a variety of reasons is one of the few areas where this isn’t true. While it’s starting to become more popular, there are quite a few things holding back game programmers from embracing these valuable paradigms.
I’d like to open by giving a quick description of the benefits you’ll gain by embracing dependency injection. Before you jump away thinking “I don’t need this” or “this will be too complicated”, just take a look at what you have to gain. And let me try to quell any fears, this is something you can definitely take advantage of, it won’t be hard, it’ll save you time, and your code will be improved.
Dependency Injection by it’s nature encourages very loose coupling. This means your objects and classes don’t have tight dependencies on other classes. Loose coupling leads to having code that is much less rigid and brittle.
Loose coupling also makes your classes much easier to use across projects. When your code has dependencies on many other classes or static global variables, it’s much harder to re-use that code in other projects. Once you get into the habit of good separation and dependency injection, you’ll find that reuse becomes a near trivial task.
While you can certainly code to interfaces without Dependency Injection, using it will naturally encourage this behavior. The benefits of this will become a bit more obvious in the example below, but it essentially allows you to swap behavior and systems in a clean way.
When you start injecting your dependencies, you quickly end up with less “spaghetti-code”. It becomes much clearer what a classes job is, and how the class interacts with the rest of your project. You’ll find that you no-longer have to worry about a minor change in one piece of code having an unexpected consequence in something that you thought would be completely unrelated.
As an example, once while working on a major AAA MMO game, I saw a bug fix to a specific class ability completely break the entire crafting system. This exactly the kind of thing we want to avoid.
This is one of the most commonly stated benefits to Dependency Injection. While it’s a huge benefit, you can see above that it’s not the only one. Even if you don’t plan to unit test initially (though you should), don’t rule out dependency injection.
If you haven’t experienced a well unit tested project before, let me just say that it’s career changing. A well tested project is less likely to slip on deadlines, ship with bugs, or fail completely. When your project is under test, there’s no fear when you want to make a change, because you know immediately when something is broken. You no-longer need to run through your entire game loop to verify that your changes work, and more importantly that you haven’t broken other functionality.
Now, I’d like to cover a few of the reasons the game industry has been slow to adopt Dependency Injection & Unit Testing.
While this doesn’t apply to Unity specifically, the game industry as a whole has primarily relied on C++. There were of course studios that developed in other languages, but to this date, outside of Unity, the major engines are all C++ based. C++ has not had nearly the same movement towards Dependency Injection or Unit Testing as other common enterprise languages (Java, C#, JS, Ruby, etc). This is changing though, and with the proliferation of unit testing and dependency injection in C#, it’s the perfect time to jump in with your games.
Dependency Injection adds overhead to your game. In the past, that overhead could be too much for the hardware to handle. Given the option between 60fps and dependency injection, 60fps is almost always the correct answer. Now though, hardware is really fast, and 99% of games can easily support Injection without giving up any performance.
While there are countless other “reasons” you could come across from game programmers, the key one is just an issue of mindset. Too many people have been programming without Injection and Unit testing and just haven’t been exposed to the benefits. They think “that’s for enterprise software”, “that’s something web developers do”, or “that doesn’t work for games”. My goal here is to convince you that it’s worth trying. I promise if you dig in and try dependency injection and unit testing, you’ll quickly start to see the benefits, and you’ll want to spread the word as well.
When you’re searching, you may also see the DI frameworks referred to as IOC containers.
You may be wondering how you get started with Dependency Injection in Unity. It’s not something built into the Unity engine, but there are a variety of options to choose from on GitHub and in the Asset Store.
Personally, I’ve been using Zenject, and my samples will be done using it. But that doesn’t mean you shouldn’t look into the other options available.
I think the description provided on the Zenject asset page does a better job describing it than I could, so here it is:
Zenject is a lightweight dependency injection framework built specifically to target Unity. It can be used to turn the code base of your Unity application into a collection of loosely-coupled parts with highly segmented responsibilities. Zenject can then glue the parts together in many different configurations to allow you to easily write, re-use, refactor and test your code in a scalable and extremely flexible way.
While I hope that after reading this series you have a good idea why you should use a dependency injection framework, and how to get started, I must highly recommend you take a look at the documentation provided on the Zenject GitHub page.
Most Dependency Injection is done via what’s called Constructor Injection. This means that anything your class relies on outside itself is passed in via the constructor.
I want to give an example of how you’d use Constructor Injection in a more general sense before diving too deep into the differences with Unity. What I’m presenting here is a simplified version of a game state system I’m using in a Unity project currently.
In my project, I have a variety of game state classes. The different “GameStates” handle how the game functions at different stages throughout the game cycle. There are game states for things like generating terrain, lost/gameover, building, attacking, and in this example, ready to start.
In the game state “ready to start“, all we want to do is wait for the user to start the game. The game state doesn’t care how the user starts the game, only that they do. The simplest way to implement this would be to check on every update and see if the user pressed the “Fire1” button.
It may look something like this:
using UnityEngine; public class GameStateReadyToStart : MonoBehaviour { void Update() { if (Input.GetButton("Fire1")) SwitchToNextGameState(); } private void SwitchToNextGameState() { // Logic to go to next gamestate here } }
This will work, it’s simple, and quick to implement, but there are some issues with it.
You may be thinking that’s a long list, but I guarantee there are more problems than just those.
The first answer you may come across to some of these problems is the Singleton pattern.
While it’s very popular, simple, and resolves half of our issues, it doesn’t fix the rest.
Because of that, outside the game development world, the singleton pattern is generally considered bad practice and is often referred to as an anti-pattern.
Now, let me show you an easy way to resolve all of the problems above.
public class GameStateReadyToStart { public GameStateReadyToStart(IHandleInput inputHandler) { inputHandler.OnContinue += () => { SwitchToNextGameState(); }; } private void SwitchToNextGameState() { // Logic to go to next gamestate here } }
Here, you can see we’ve moved input handling out of the “gamestate” object into it’s own “inputHandler” class. Instead of reading input in an update loop, we simply wait for the InputHandler to tell us when the user is ready to continue. The gamestate doesn’t care how the user tells us to continue. All the gamestate cares about is that the user told it to switch to the next state. It’s now properly separated and doing only what it should do, nothing more.
The “IHandleInput” interface for this example is very simple:
using System; public interface IHandleInput { Action OnContinue { get; set; } }
Now, if we want to switch input types across devices, we simply write different implementations of the “IHandleInput“ interface.
We could for example have implementations like:
Now without going too much deeper into my example, you may be thinking “that’s nice, but now I have to pass in an input handler and manage that”.
This is where dependency injection comes into play. Instead of creating your handler and passing it into the constructor, what we’ll do is Register the handler(s) we want with our Dependency Injection Container.
To do that, we need to create a new class that derives from the Zenject class “MonoInstaller”
using System; using UnityEngine; using Zenject; using Zenject.Commands; public class TouchGameInstaller : MonoInstaller { public override void InstallBindings() { Container.Bind<IHandleInput>().ToTransient<TouchInputHandler>(); Container.Bind<GameStateReadyToStart>().ToTransient<GameStateReadyToStart>(); Container.Resolve<GameStateReadyToStart>(); } }
In the TouchGameInstaller class, we override InstallBindings and register our 2 classes.
Line 13 simply asks the container for an instance of the game state.
This is a very simplified version of the Installer with a single game state, later parts of this series will show how we handle multiple game states.
What we’ve done here though is avoid having to manage the life and dependencies of our “gamestate” class.
The Dependency Injection Container will inspect our classes and realize that the “GameStateReadyToStart” class has a dependency on an “IHandleInput“, because the constructor has it as a parameter.
It will then look at it’s bindings and find that “IHandleInput” is bound to “TouchInputHandler“, so it will instantiate a “TouchInputHandler” and pass it into our “gamestate” automatically.
Now, if we want to switch our implementations on different devices, we simply switch our our “TouchGameInstaller” with a new installer for the device and make no changes to our GameState classes or any existing InputHandler classes. We no longer risk breaking anything existing when we want to add a new platform. And we can now hook up our GameState to unit tests by using an Installer that registers a TestInputHandler.
You may realize that I haven’t injected any gameobjects yet, and could be wondering how this works with monobehaviors that can’t constructors.
In the next part of this series, I’ll explain how to hook up your gameobjects and monobehaviors with the dependency injection framework and continue the example showing how the entire thing interacts.
If you’ve read many Unity tutorials, you may already know that it’s very easy to edit your script fields in the Unity inspector.
Most Unity tutorials (including on the official page) tell you that if you have a MonoBehaviour attached to a GameObject, any public field can be edited.
While that does technically work, I want to explain why it’s not the best way to setup your scripts, and offer an alternative that I think will help you in the future.
In this article, you’ll learn how to use proper Encapsulation while still taking full advantage of the Unity Inspector.
Take a look at this “Car.cs” script.
using UnityEngine; public class Car : MonoBehaviour { public Tire FrontTires; public Tire RearTires; public Tire FrontRightTire; public Tire FrontLeftTire; public Tire RearRightTire; public Tire RearLeftTire; private void Start() { // Instantiate Tires FrontRightTire = Instantiate(FrontTires); FrontLeftTire = Instantiate(FrontTires); RearRightTire = Instantiate(RearTires); RearLeftTire = Instantiate(RearTires); } }
If you look at the Start method, you can tell that the fields “FrontTires” & “RearTires” are referring to prefabs that will be be used to instantiate the 4 tires of the car.
Once we’ve assigned some Tire prefabs, it looks like this in the Inspector.
In play mode, the Start method will instantiate the 4 actual tires on our car and it’ll look like this.
The first thing you might realize is that there could be some confusion about which fields to assign the prefab to.
You’ve just seen the code, or in your own projects, perhaps you’ve just written it, and it may seem like a non-issue.
But if your project ever grows, it’s likely others will need to figure out the difference, and to do so, they’ll need to look at the code too.
If your project lasts more than a few days/weeks, you also may forget and have to look back through the code.
Now you could solve this with special naming. I’ve seen plenty projects where the “Prefab” fields had a prefix or suffix like “Front Tires Prefab”.
That can also work, but then you still have 4 extra fields in there that you have to read every time. And remember, this is a simple example, your real classes could have dozens of these fields.
To resolve this, let’s change the entries we don’t want to be editable into Properties.
Now let’s change the “Car.cs” script to match this.Microsoft recommends you make your fields all private and use properties for anything that is public. There are plenty of benefits not described in this article, so feel free to read in detail from Microsofts article Fields(C# Programming Guide)
using UnityEngine; public class Car : MonoBehaviour { public Tire FrontTires; public Tire RearTires; public Tire FrontRightTire { get; set; } public Tire FrontLeftTire { get; set; } public Tire RearRightTire { get; set; } public Tire RearLeftTire { get; set; } private void Start() { // Instantiate Tires FrontRightTire = Instantiate(FrontTires); FrontLeftTire = Instantiate(FrontTires); RearRightTire = Instantiate(RearTires); RearLeftTire = Instantiate(RearTires); } }
Here’s what it looks like in the Inspector
With that change, you may be thinking we’ve resolved the issue and everything is good now.
While it’s true that confusion in the editor is all cleared up, we still have one more problem to address.
That problem is lack of Encapsulation.
“In general, encapsulation is one of the four fundamentals of OOP (object-oriented programming). Encapsulation refers to the bundling of data with the methods that operate on that data.”
There are countless articles and books available describing the benefits of encapsulation.
The key thing to know is that properly encapsulated classes only expose what’s needed to make them operate properly.
That means we don’t expose every property, field, or method as public.
Instead, we only expose the specific ones we want to be accessed by other classes, and we try to keep them to the bare minimum required.
Why?
We do this so that our classes/objects are easy to interact with. We want to minimize confusion and eliminate the ability to use the classes in an improper way.
You may be wondering why you should care if things are public. Afterall, public things are easy to get to, and you know what you want to get to and will ignore the rest.
But remember, current you will not be the only one working on your classes.
If your project lasts beyond a weekend, you need to think about:
Unless you have a perfect memory, good coding practices will help you in the future when you’re interacting with classes you wrote weeks or months ago.
Let’s look at this “Wall” script now to get an idea of why proper encapsulation is so important.
using UnityEngine; public class Wall : MonoBehaviour { public void Update() { if (Input.GetButtonDown("Fire1")) DamageCar(FindObjectOfType<Car>()); } public void DamageCar(Car car) { car.FrontTires.Tread -= 1; car.RearTires.Tread -= 1; } }
The “DamageCar” method is supposed to damage all of the wheels on the car by reducing their Tread value by 1.
If we look back to the “Car.cs” script, “FrontTires” & “RearTires” are actually the prefabs, not the instantiated tires the car should be using.
In this case, if we execute the method, we’re not only failing to properly damage our tires, we’re actually modifying the prefab values.
This is an easy mistake to make, because our prefab fields that we we shouldn’t be interacting with aren’t properly encapsulated.
If we make the “FrontTires” & “RearTires” private, we won’t be able to edit them in the inspector… and we want to edit them in the inspector.
Luckily, Unity developers knew this would be a need and gave us the ability to flag our private fields as editable in the inspector.
Adding the [SerializeField] attribute before private fields makes them appear in the Inspector the same way a public field does, but allows us to keep the fields properly encapsulated.
Take a look at the updated car script
using UnityEngine; public class Car : MonoBehaviour { [SerializeField] private Tire _frontTires; [SerializeField] private Tire _rearTires; public Tire FrontRightTire { get; set; } public Tire FrontLeftTire { get; set; } public Tire RearRightTire { get; set; } public Tire RearLeftTire { get; set; } private void Start() { // Instantiate Tires FrontRightTire = Instantiate(_frontTires); FrontLeftTire = Instantiate(_frontTires); RearRightTire = Instantiate(_rearTires); RearLeftTire = Instantiate(_rearTires); } }
Here you see we no-longer expose the “FrontTires” and “RearTires” fields outside of our class (by marking them private).
In the inspector, we still see them available to be assigned to.
Now our problems are solved and our class is properly encapsulated!
You may also notice that the casing on them has been changed. While this is not required to properly encapsulate your objects, it is very common practice in the C# community to denote private fields with camel case prefixed by an underscore. If you don’t like the underscore, consider at least using camel casing for your private fields and reserve pascal casing for public properties.
Want the source for this project to try it out yourself? Here it is: https://unity3dcollege.blob.core.windows.net/site/Downloads/Encapsulation%20SerializeField.zip
It’s time to do a Unity Android build. Once you complete this tutorial, you’ll be ready to build any of your projects to Android.
It’s time to put your game onto a phone (or tablet) so you can start showing it off to friends (or strangers if you don’t have friends).
Building your game out to an android device is actually pretty easy to do, once you have the initial setup out of the way.
The first thing you need to do is enable the android Developer Options
This is found in the settings of your android device, but as of android 4.2 it’s hidden by default.
To make the developer options available, go to “About device” in your android settings.
Find the “Build number“, tap on it 7 times.
If you tapped correctly…. developer mode should now be available.
Go back to Settings and look for “Developer options” (for me, it’s right above About device).
Enable the Developer options.
Check USB debugging
Optional: Check Stay awake – You don’t have to do this, but if your phone locks it will stop your builds.
We’re done with phone setup, now it’s time to deal with the Unity setup.
Go ahead and plug in your phone or tablet.
If you’re on windows, you probably need to install Googles USB drivers. You may already have them, in which case you can skip ahead.
But if you end up with your device not being detected, make sure to follow the steps in this section.
First, download the USB driver from google here
http://developer.android.com/sdk/win-usb.html
You should get a file that looks like this
Right click on it and select “Extract All”
Open the folder that’s created and find android_winusb.inf
Right click on it and select “Install”
If you’re presented with this security warning, click “Open”
You may be greeted with a User Account Control dialog after that, if so, just click “Yes”
You should see a message saying the installation was completed successfully.
Go to your Unity editor and select File->Build Settings
Select Android from the list of platforms and click “Switch Platform”
Now, make sure you have a scene selected in the Scenes In Build area.
If you don’t, you need to add one now.
To add a scene you can open it, then click “Add Open Scenes”
Leave the options at their default values and click “Build and Run“
Create a folder to save the output to, then name your file.
I recommend you create a separate folder for each build platform (Android, WebGL, Standalone, etc)
Do not make it a sub-folder of your Unity project, a build is not part of your project and belongs in a separate place.
Below, you can see I have a folder at c:\builds\SwappyFish\Android and name my output file SwappyFish.apk
Click Save
There are a few possibilities for what happens now.
You may have received an error saying no suitable android device was found.
If so, we need to do a bit more setup, if not, skip past this section.
Let’s Fix it
When you installed Unity, there were options for different build platforms.
If you didn’t select “Android Build Support“, you’re probably missing the required android files.
You have 2 options here, you can re-install Unity with the Android Build Support checked (in-fact, I’d select all of the platforms there to make it easier next time you try another target).
This could be a good opportunity to update to the latest Unity version as well. If you want the easy way, or an update sounds good, go for that option.
This is a bit more involved than the easy way, but if you really don’t want to re-run the installer, this should fix the error for you.
One of the requirements for an Android build is the Java SDK.
If you haven’t already installed it (and didn’t select android build support so Unity would install it for you), you’ll need to download and install it now.
The current link to the Java SDK is here
http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
If that stops working, just search for Java SDK on google and pick the one that looks like what’s below
The Java SDK page is not the most friendly to navigate, so just look for these 2 highlighted areas
Accept the License Agreement
Click on the download link for Windows x64
If you have a 32bit version of windows, download the Windows x86 version instead.
When your download completes, open it and you’ll be presented with the Java SDK Installer
Click Next
Keep the default settings and click Next
Now you’ll get another popup, just click Next again
While the installer will allow you to change the destination folder, I recommend against it.
The SDK should be done installing, click Close.
Build Again
If you got past the first part, you were probably presented with this error message.
This is telling you that you need to set the Bundle Identifier.
To set your bundle identifier, open the Player Settings under Edit->Project Settings->Player
When you click this, you’ll see PlayerSettings in the Inspector tab
If the Android option isn’t already selected, choose it by clicking on the icon that now
Your settings should look like this now, though your Product Name may be different.
Set a Company and Product name of your choice.
Expand the “Other Settings” area
You’ll see the area “Bundle Identifier” here.
Set your Bundle Identifier to something that matches the pattern. It must start with com, then have your company name, and product name.
These’s don’t need to match exactly with your product and company, but should if possible. Special characters are not allowed here.
My setting for this project is com.jasonweimann.SwappyFish
Once you’ve set your Bundle Identifier, save your project.
This time, your build should actually work!
Reminder: Your phone/tablet must be UNLOCKED and plugged in for a build to deploy to it.
If you followed the steps above, you should now have a working android build that you can show off.
Put it on a few devices and show everyone what you’ve done!
Alright, you’ve built a game, and it’d been great so far.
Now you might want to share your game with someone. There are quite a few options for sharing your game, we’ll cover most of the common ones, but we’re going to start with the “Standalone” build.
Before we build though, Save your Scene!
In your editor, open the File menu and select “Build Settings…”
You will be presented with a new window that looks like this
Click the “Add Open Scenes” button
Your scene may have a different name than mine, that’s okay, as long as it’s the currently open scene you’re working in.
Now that the scene is added, click the “Build and Run” button.
You’ll be presented with a file dialog that looks like this
Do not save here.
Select a new folder for your builds, outside of your project folder.
Here, I’ve created a folder named Builds on the C: drive. You can place your folder wherever you like, just don’t put it in with your Unity project.
Now give it a name (mine is “SwappyFish”) and click Save
If you don’t get any errors, you should be presented with the default Unity game options screen.
Click Play and give your game a try in full screen mode. (Remember Alt-F4 to get out).
You’ve just created your first real build of your game!
If you open Explorer (or Finder on a mac), you can see your games files in the folder you previously selected.
It should look like this on windows (and very similar on mac)
To distribute your standalone build, you simply need to compress the contents of the folder into a file you can send to your friends and family.
If you already know how to zip a folder, just select the entire contents and compress them with your favorite compression tool (7-zip if my preferred one).
If you don’t know what I’m talking about… no problem, just select all of the files in the folder and right click.
Select “Send to” -> “Compressed (zipped) folder”
Now you have a .zip file you can share. Go share it now, let someone see what you’ve learned!
In this section, we’ll do a little cleanup, and finally cover the steps to build to different devices.
The first thing we need to do is randomize our bubble heights. While we can pop them easily, we really want some variation to make it feel more polished.
To accomplish this, we’ll use a familiar technique. UnityEngine.Random
Open the Bubble.cs script.
We need to make two changes here. First, Change the Reset() method to match this
void Reset() { float randomHeight = Random.Range(-8f, -18f); transform.position = new Vector3(transform.position.x, randomHeight, transform.position.z); }
This will give us a randomized starting height for the bubble.
If you play now, you’ll notice the bubbles are still coming at the same time. The reason this happens is we aren’t randomizing until after the bubble has been popped and reset. To fix this, we’ll just call reset when the game starts.
In the Bubble.cs file, add a new method named Start() that will call Reset()
Start is another built in method that we’re going to hook into. It’s called when the scene starts or when a new GameObject is first added to the scene.
Your final Bubble.cs script should look like this
using UnityEngine; public class Bubble : MonoBehaviour { [SerializeField] private float _moveSpeed = 1f; void Start() { Reset(); } // Update is called once per frame void Update() { transform.Translate(Vector3.up * Time.deltaTime * _moveSpeed); if (transform.position.y > 10) Reset(); } void Reset() { float randomHeight = Random.Range(-8f, -18f); transform.position = new Vector3(transform.position.x, randomHeight, transform.position.z); } void OnTriggerEnter2D(Collider2D other) { if (OtherIsTheFish(other)) { ScoreKeeper scoreKeeper = GameObject.FindObjectOfType<ScoreKeeper>(); scoreKeeper.IncrementScore(); Reset(); } } bool OtherIsTheFish(Collider2D other) { return (other.GetComponent<Fish>() != null); } }
Now try playing again.
Because we call Reset when the game starts, all of the bubbles have a random initial height.
Our current bubble is pretty grey, it’s time to lighten it up a bit.
If you have access to Photoshop (there’s a free trial available), open up the bubble.png file and we’ll make an adjustment to it.
If you don’t have Photoshop, or just don’t want to edit the image, you can skip this section and download the modified version in the next section.
With bubble.png open, select Image->Adjustments->Levels
Move the left slider or type in the value 77
Save your file and notice how much brighter your bubble is.
The modified bubble is available for download here https://drive.google.com/file/d/0B-lckWH-cZaianllZHlURFZRcVk/view?usp=sharing
Just download it to your Art folder inside your Unity project and replace your existing bubble.
Now we’re going to adjust the alpha channel on our bubble material.
The alpha channel controls transparency. We’ll be increasing the transparency (actually reducing opacity) to make our bubble look a bit better.
In your scene, select one of the bubbles, then look to the inspector.
Find the Color section and click on it
Adjust the Alpha channel to 170 and watch the bubble get a little transparent.
Right now, our background is pretty dull. It’s hardly a background at all, it’s really just a boring solid blue.
Let’s fix it!
Download this gradient color image https://drive.google.com/file/d/0B-lckWH-cZaiQVd3SnFDbmc0bXc/view?usp=sharing
Place the GradientBackground.png file in your Art folder.
Create a new Sprite
Name your sprite “Background”
Using the sprite picker, select the GradientBackground that you just downloaded
Adjust the transform values to match these
You may be wondering where your fish and bubbles have gone…
Does your screen look something like this?
If so, don’t worry!
We just need to adjust the “Order in Layer” value on the Sprite Renderer
Order in Layer determines what order the sprites are drawn in. Sprites that are drawn later will always cover sprites drawn before them (unless they’re transparent like our bubble).
Set “Order in Layer” to -1
Your scene view should now look like this
Good work making your game look better.
Now, I want you to add a bit of your own touch to the game.
Find a new background that you really like and put it in place of the gradient.
I’d recommend you find an image on google using a search that resembles this
When you put in your own custom image, you’ll need to adjust the size of the transform to make it fit right and not be stretched out.
Here’s what my final version looks like.
Now that you’ve finished yours, post a screenshot in the comments and show everyone what you’ve done!
Up Next: Building Unity Projects – We’ll cover how to get your game built and shared with your friends on PC, Mac, Android, and in a web browser.
Welcome back! In this section, we’ll add a scoring system.
The plan…
For our scoring system, we’ll use the bubble image we have in the art folder.
We’ll setup our game so that the player needs to pop bubbles for points.
To display the score, you’ll need UI components. We’ll use the Unity3D UI system to accomplish this.
The Unity GUI system will allow us to easily draw text and graphics over our game.
Getting started with the GUI system is pretty simple, we just need to add a GUI game object.
Add a Text object by selecting GameObject->UI->Text
In your Hierarchy, you should see TWO new GameObjects. There’s the Text that you added and a Canvas that is it’s parent.
The Canvas is the root of your UI, and all UI elements must be children of such a Canvas.
Because we didn’t already have a Canvas in our Scene, the editor automatically created one for us and added our new Text GameObject as a child.
In your game view, you should see our new Text object in the bottom left.
Let’s edit our Text object to say something more meaningful than “New Text”
With the Text GameObject selected, look at the Text component in the Inspector and notice the “Text” field.
Change it to say “Score: 0”
Right now, our UI component isn’t positioned correctly. We could drag it around until it looks close, but right now it’s Anchored from the middle, so it would be at different areas depending on the screen size. What we want for this game is to anchor the score text to the top left of the screen. To accomplish this, we’ll use the “Anchor Presets”.
On the transform for the Text, click the Anchor tool (the red cross with a white box in the middle, the mouse is over it in the screenshot)
You’ll be presented with this dialog.
We want to select the top left point, but before you click it, notice the text at the top of the dialog.
For this UI component, we want to set the position along with the anchor, so before you click the pivot, hold the Alt key.
You should now notice your Score Text has moved to the top left corner of the screen.
Before we go too much further, let’s re-name the Text to something useful.
Change the name to “ScoreText”
Try playing with the text component for a few minutes.
Adjust things like the font size, style, and color to get familiar with some of your options.
If you increase the font over 26, you’ll notice that it’s no-longer visible in your game view.
This is because the text is too large to fit into the component. You can fix this by increasing the size of the text component.
It’s time to hookup some code to make our score counter work.
The first thing we want to do is create a script to handle keeping score.
Create a new script named “ScoreKeeper”
Change the ScoreKeeper.cs file to look like this
using UnityEngine; using UnityEngine.UI; public class ScoreKeeper : MonoBehaviour { private int _currentScore = 0; public void IncrementScore() { _currentScore++; Text scoreText = GetComponent<Text>(); scoreText.text = "Score: " + _currentScore.ToString(); } void Update() { IncrementScore(); } }
Attach the “ScoreKeeper” script to the ScoreText GameObject
Before you hit play, look at the script and see if you can guess what it’s going to do.
…
…
…
…
…
…
Let’s inspect the different parts of this script to see what’s going on.
using UnityEngine.UI;
You’ve seen the “using” statements before. What they do is tell the engine that we want to “use” components from that namespace.
In computing, a namespace is a set of symbols that are used to organize objects of various kinds, so that these objects may be referred to by name.
In this instance, we’re just telling Unity that we’ll be “using” UI components in our script.
private int _currentScore = 0;
Here, we’re defining a variable to hold our current score and setting it’s value to 0.
public void IncrementScore() { _currentScore++; Text scoreText = GetComponent&amp;amp;lt;Text&amp;amp;gt;(); scoreText.text = "Score: " + _currentScore.ToString(); }
The IncrementScore method does 3 things.
_currentScore = _currentScore + 1;
ToString() gives us a text representation of a a non-text object
The last bit of code we have is the Update method. All it’s doing is calling IncrementScore. Because Update is called every frame, our IncrementScore method is called every frame, which in turn makes our score increase. In this instance, the faster our game is running, the faster our score will increase.
void Update() { IncrementScore(); }
The Update method is really just implemented so we can see something working. For our game, we’ll have a more complicated scoring system, so let’s delete the Update method.
Change your ScoreKeeper script to look like this
using UnityEngine; using UnityEngine.UI; public class ScoreKeeper : MonoBehaviour { private int _currentScore = 0; public void IncrementScore() { _currentScore++; Text scoreText = GetComponent<Text>(); scoreText.text = "Score: " + _currentScore.ToString(); } }
Now that our code has changed, we need another way to call the IncrementScore method….
Look to your art folder in the Project View.
Drag a bubble from the Art folder into your scene.
Now, add a CircleCollider2D component to the newly placed bubble.
Check the IsTrigger box.
We’ve got our bubble placed, but we really need to get some code in to make things work.
using UnityEngine; public class Bubble : MonoBehaviour { [SerializeField] private float _moveSpeed = 1f; // Update is called once per frame void Update() { transform.Translate(Vector3.up * Time.deltaTime * _moveSpeed); if (transform.position.y > 10) { Reset(); } } void Reset() { transform.position = new Vector3(transform.position.x, -10, transform.position.z); } void OnTriggerEnter2D(Collider2D other) { if (OtherIsTheFish(other)) { ScoreKeeper scoreKeeper = GameObject.FindObjectOfType<ScoreKeeper>(); scoreKeeper.IncrementScore(); Reset(); } } bool OtherIsTheFish(Collider2D other) { return (other.GetComponent<Fish>() != null); } }
Attach the bubble script to your bubble in the Hierarchy.
Now try playing and see if you can catch the bubble.
If you placed your bubble like I placed mine, it can’t be caught.
The reason for this is that we only ever change the Y position of the bubble, so it never moves past us.
Instead of adding more code to the bubble, we’ve got another trick we’ll be using.
Set the bubbles position to [2.5, -4, 0]
Now, make the bubble a child of the seaweed parent.
In the Inspector, hit the Apply button so that all our seaweed parents get a bubble.
Now give your game another play and enjoy your great work!
If all went well, it should look a bit like this
In this post, we’ll add some props to make our game feel a little more polished.
Let’s get right to it!
To get started, take the crab from the art folder and drag it to your Hierarchy.
Change the position and scale of the crab to match the image.
Take special note of the Z value “-1”. This is needed to make the crab appear in front of the ground.
Once you have the settings copied, try playing with the Z value and see how it disappears when the Z is not a negative value.
Your crab should look like this. If your crab is above or below the ground, try adjusting the Y position until it looks right.
Hit Play and watch the crab not move.
Easy! We re-use the “MoveLeft” script from before.
Attach the “MoveLeft” script to the crab
Set the speed to 2.5
Click play again and watch the crab disappear off our screen only to reappear later.
Let’s add another prop using the same technique as before.
Grab the starfish and drag it to the Hierarchy.
Set it’s position and scale to match these
Add the “MoveLeft” script to the starfish.
Your starfish should look something like this.
If your starfish is above or below the ground, try adjusting the Y position until it looks right.
If you were looking around in the art folder, you may have guessed, we still need to add the clam.
Drag the clam to your Hierarchy.
Set it’s position and scale to match these
Add the “MoveLeft” script to the CLAM.
Now it’s time to play! Give your game a test run and make sure the Crab, Clam, and Starfish are re-appearing properly.
If you played, you may have noticed a bug in our game.
When the props re-appear on the right side of the screen, they’re going to a random height.
To fix this, we need to add an option to our “MoveLeft” script.
Change your “MoveLeft” script to match this
using UnityEngine; public class MoveLeft : MonoBehaviour { [SerializeField] private float _speed = 5f; [SerializeField] private bool _randomizeHeight = true; // Update is called once per frame void Update() { transform.Translate(Vector3.left * Time.deltaTime * _speed); if (transform.position.x < -15) { if (_randomizeHeight) { float randomYPosition = UnityEngine.Random.Range(-3, 3); transform.position = new Vector3(15, randomYPosition, 0); } else { transform.position = new Vector3(15, transform.position.y, 0); } } } }
Now, on your Clam, Starfish, and Crab, uncheck the Randomize Height option in the Inspector.
Play again and you should see your props preserving their Y position.
It’s time to save our scene. Select File->Save Scene As
Browse to the Scenes folder
Give your scene a unique name, or copy mine.
You can verify that your scene is saved by looking in the Scenes folder.
Now repeat this process to save our experimental scene.
Your scenes folder should now look like this
The top left area of the unity application bar shows the name of the currently loaded scene.
Great work so far, now it’s time to have a little fun and get creative!
ctrl/cmd-s will save the currently opened scene
For this part, I want you to add some more props and place them where ever you like.
Spend 5 minutes trying things out.
Play with their scale, position, and rotation until you get something YOU LIKE.
Come back once you’re happy with your changes.
Now SAVE your scene so you don’t lose that customization.
If you like, you can even find some external art.
One of the easiest places to use is google images.
Just make sure that you select “Transparent” for the “Color” when searching.
If you don’t see Color, click the “Search tools”
To download an image from google images, just click on it to get the full art view, then right click and hit “Save image as…”
In the next post, we’ll add an interesting scoring system.
You’ll get a nice introduction to the Unity3D GUI system and add a goal for your players.
Right now, our game is a bit too easy. It goes on forever, but it’s always exactly the same.
What we want to do next is add some variation to the seaweed. To accomplish this, we’ll have our game pick a randomized Y value for the position.
Since we already move our seaweed when it gets to -15 on the X axis, we can make do the randomization at that time.
To do the randomization, we’ll just call into the Random function Unity provides us.
float randomYPosition = UnityEngine.Random.Range(-3, 3);
UnityEngine.Random.Range will return a value between the first and second numbers passed in. For this example, we’re passing negative 3 and positive 3, so we’ll get a number somewhere between there.
Change your “MoveLeft” script to match this
using UnityEngine; using System.Collections; public class MoveLeft : MonoBehaviour { [SerializeField] private float _speed = 5f; // Update is called once per frame void Update () { transform.Translate(Vector3.left * Time.deltaTime * _speed); if (transform.position.x < -15) { float randomYPosition = UnityEngine.Random.Range(-3, 3); transform.position = new Vector3(15, randomYPosition, 0); } } }
Give the game a play now.
Notice how the seaweed Y position is changing just enough to add some difficulty to our game.
If you’ve been playing all these times I said to Play, you’ve probably noticed a few issues.
For example, if you fall down without hitting a seaweed, you just fall, there’s no ground. The same goes for flying too high, you can go above the seaweed and just hang out there safely.
Open the “Fish” script and modify the Update() method to match this
// Update is called once per frame void Update () { bool pressedFireButton = Input.GetButton("Fire1"); if (pressedFireButton) { Rigidbody2D rigidbody = GetComponent<Rigidbody2D>(); rigidbody.velocity = Vector3.zero; rigidbody.AddForce(Vector3.up * _upwardForceMultiplier); } if (transform.position.y > 6f || transform.position.y < -6f) { Application.LoadLevel(0); } }
If you play again, you’ll see that when the fish drops below -6 on the Y axis, the fish dies and the level re-loads.
The same happens if you click fast enough and bring your above positive 6 on the Y axis.
Let’s add some real ground now. In Flappy Bird, we have a simple striped ground (mario ground). For our game, we have some dirt.
To do this, we’re actually going to add a quad to our scene. The quad is located under 3D assets, but it does exactly what we need for our 2D game.
Remember you can mix and match 2D/3D in your games.
Rename the new quad to “Ground”
Adjust the position to [0, -4.8, 0]. Y is -4.8
Set the X value of Scale to 20.
Your Game View should now look like this
A quad is not a sprite, so we don’t have a Sprite Renderer.
What we have instead is a Mesh Renderer.
What we need to do is change the Material of our renderer. The art package you downloaded in part 1 has a materials folder with a material named “Ground”.
Drag that “Ground” material and drop it onto the “Ground” Game Object in the Hierarchy.
You could also drag the material onto the area that says “Element 0” in the screenshot above.
Since the quad is a 3D object, when we added it, there was a 3D collider attached to it. That collider is a new type that we haven’t used before called a MeshCollider.
We’re building a 2D game though, so we need to remove that 3D collider.
Then add a BoxCollider2D to our “ground”.
Your BoxCollider2D should have a nice rectangular green outline.
When we hit the ground, we want it to do the same thing the seaweed does, so let’s reuse one of our old scripts.
Add the “SeaweedCollisionHandler” script to the “ground”
Now think for a second.
What do you expect to happen?
..
..
Go ahead and hit play to see if you were right.
Right now, it probably seems think things have gotten worse.
Your fish is sliding along the ground. The seaweed is sliding along the ground. And your fish isn’t dying until he slides in.
If you remember from part 2, we forgot to check the IsTrigger checkbox.
Go ahead and check it now, then give it another try.
Your fish should now be dying the moment it touches the ground.
The last thing we need to do is get our ground animating. Previously, when we wanted things to move, we applied a Translate on their rigidbody.
For the ground though, we’re doing something different. We created the ground as a Quad for a specific reason. We want to animate the texture on it, without actually moving the transform.
To do that, we’ll need to create a new script. Create one now named “GroundScroller”.
Open the “GroundScroller” script and edit it to match this
using UnityEngine; public class GroundScroller : MonoBehaviour { [SerializeField] private float _scrollSpeed = 5f; // Update is called once per frame void Update() { // Get the current offset Vector2 currentTextureOffset = this.GetComponent<Renderer>().material.GetTextureOffset("_MainTex"); // Determine the amount to scroll this frame float distanceToScrollLeft = Time.deltaTime * _scrollSpeed; // Calculate the new offset (Add current + distance) float newTextureOffset_X = currentTextureOffset.x + distanceToScrollLeft; // Create a new Vector2 with the updated offset currentTextureOffset = new Vector2(newTextureOffset_X, currentTextureOffset.y); // Set the offset to our new value this.GetComponent<Renderer>().material.SetTextureOffset("_MainTex", currentTextureOffset); } }
Attach the “GroundScroller” script to the Ground GameObject
Try playing again and watch the ground scroll along with the seaweed!
Well, maybe it’s not quote scrolling “along” with the seaweed. It’s going a bit too fast and looks pretty strange.
Luckily, if you were paying attention to the code, we’ve added a variable to adjust the scroll speed.
If you try to adjust the speed while playing, it’s going to keep resetting. This is because of how we’re handling death. When we do a LoadLevel to reload the scene, all of those gameobjects are being re-loaded and re-created.
We have a couple of options for finding the correct speed.
Personally, I prefer the easy way, so let’s go with option c.
Now play one more time and enjoy what you’ve made. See how far you can get.
Great work so far. Our game has come together and is functional and fun.
In the next part, we’ll make our game a little fancier with some props and add a unique scoring system.