• Home  / 
  • Author's archive:
About the author

Jason Weimann

Automatic Deployment, Registration, and Deregistration with Octopus Deploy on AWS EC2

By Jason Weimann / February 23, 2016

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.

What we’ll cover

  • Automatic Installation of Tentacles on new EC2 instances
  • Automatic Subscribing to projects & roles when an EC2 instance first starts up
  • Automatic Deployment of the correct release to the new EC2 instances
  • Automatic Deregistration when EC2 instances come down (terminated by an ASG or manually)

Bootstrapping our Setup

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.

What are the Script Variables???

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.

 

Bootstrapper.ps1 – The Bootstrapper Script

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.

Script Variables

$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.

 

 


 

 

ServerSetup.ps1 – The Server Setup Script

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.

 

Script Variables

$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-Transcript
Note: 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).

ServerSetup.ps1 –Script Explanation

Let’s break this script down and see what’s going on.

Starting the Transcript

# 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.

Get variables we need

### 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.

 

Running our scripts that do work

# 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.

Uploading the logs

# 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.


 

The Deployment Scripts

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).

 

SetVariables.ps1 – Setting the variables we’ll use in other scripts.

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.

Script Variables

$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;
}

 

SetInstanceNameTag.ps1 – Tagging your instance with a good name

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.


 

InstallCertificates.ps1 – Optional – Installing your SSL Certs

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.

Import-PfxCertificate

This handles importing the certificate file once it’s been downloaded from your S3 bucket.

Download-Certificates

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.

Certificate Subfolders By Role

Certificate Subfolders By Role

 


 

InstallTentacle.ps1 – Doing the actual installation and registration with Octopus

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.

  1. Download the tentacle installer from S3
  2. Install the tentacle service
  3. Register the tentacle with the Octopus server

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 inSetVariables.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 $originalWorkingPath
I 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 $registerExp
This 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.


 

AddOctopousMachineIdTag.ps1 – Tagging the instance so it can be removed later

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"

 


 

 

Auto Deploy

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
}

 

The S3 Bucket

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.

S3 Bucket   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)

 

Create Your Folders

Let’s create the folders from the screenshot in your own bucket now.

  1. Upload the “serverSetup.ps1script to the root of your S3 Bucket.
  2. Upload the Scripts folder contents, including your selected Octopus Tentacle installer and the AWSSDK.dll file.
  3. Create a Certificates folder in the root of your S3 Bucket.
    1. Create a folder for each role you have.  ex. www.jasonweimann.com
    2. Place all certificates you want installed on instances tagged with that role into the sub-folder for their role. (ex. if I have certificates for jasonweimann.com, they’d be placed in “Certificates\www.jasonweimann.com\mycertificatefile.pfx“)
  4. Create an empty folder named “ServerSetupLogs

 

You can download the scripts and README files as a zip below

Download “Octopus Deploy Automated Registration” octopus-deploy-automated-registration.zip – Downloaded 915 times – 4 MB

.

Be sure to replace the required strings in each script with your own Octopus Server IP, S3 Bucket Name, API key.

 

Octopus Setup

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

  • Production – api.jasonweimann.com
  • Production – www.jasonweimann.com

If you ran both sites on the same EC2 instance in Development, it would be named

  • Development – www.jasonweimann.com|api.jasonweimann.com

 

 

Starting your EC2 instance

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.

  1. An IAM role with access to call into AWS services – In the screenshots below, mine is named “PowerUser

  2. A security group with the Octopus port 10933 open from your Octopus Server IP – In the screenshots below, mine is named “Octopus Deploy

 

The Launch Configuration

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”

 

 

Creating the Auto Scaling Group

In the first step, you don’t need to do anything special.

Select a name, subnet, and group size.

ClickConfigure Notifications

 

Tags – The really important part

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

Environment and Roles set

Environment and Roles set

 

Multiple roles selected are split by a pipe |

Multiple roles selected

 

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)

 

Handling Deregistration

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.

2016-02-08 17_40_45-AWS Lambda

On the “Select blueprint” page, just click “Skip”

2016-02-08 17_41_14-AWS Lambda

Now, give your lambda a name and description.

Select Node.js for the Runtime

Select Edit code inline for Code entry type

2016-02-08 17_42_33-AWS Lambda

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();
        }
    });
};

 

2016-02-08 17_43_59-AWS Lambda

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”

 

Cloudwatch Events

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.

2016-02-08 17_45_14-AWS Management Console

Under Events, select Rules and clickCreate rule

2016-02-08 17_45_43-CloudWatch Management Console

 

Select “EC2 instance state change notification” for the event source.

2016-02-08 17_46_00-CloudWatch Management Console

 

SelectSpecific 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.

2016-02-09 08_29_36-CloudWatch Management Console

 

Continue to the next page.

Give the Rule a name and make sure Enabled is checked.

2016-02-09 08_30_01-CloudWatch Management Console

Click “Create Rule” and you’re done.

You’re Done!

With this rule created, any EC2 instance that terminates should automatically de-register from your octopus server.

 

Questions?

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

Download “Octopus Deploy Automated Registration” octopus-deploy-automated-registration.zip – Downloaded 915 times – 4 MB

 

 

Continue reading >
Share

Dependency Injection and Unit Testing Unity

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.

 

Benefits – What do I have to gain?

Loose Coupling

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.

Re-usbility Across Projects

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.

Encourages Coding to Interfaces

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.

Cleaner Code

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.

Unit Testing

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.

 

 

If it’s so good, why isn’t this common place?

Now, I’d like to cover a few of the reasons the game industry has been slow to adopt Dependency Injection & Unit Testing.

C++

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.

Performance

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.

Mindset

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.

 

Dependency Injection Frameworks

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.

Zenject

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.

Constructor Injection

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.

Example

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.

 

Problems

  • Our gamestate is a monobehaviour so we can read the input during the update.
  • The Input logic is inside a class who’s job isn’t Input.  The gamestate should handle game state/flow, not input.
  • Changing our input logic requires us to touch the gamestate class.
  • We’ll have to add input logic to every other gamestate.
  • Input can’t be easily varied across different devices.  If we want a touch button on iPad and the A button on an xbox, we have to make bigger changes to our gamestate class.
  • We can’t write unit tests against our gamestate because we can’t trigger input button presses.

You may be thinking that’s a long list, but I guarantee there are more problems than just those.

Why not just use a Singleton?

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.

 

Let’s try some separation

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:

  • TouchInputHandler – Continues when the user presses anything bound to “Fire1
  • GUIBasedInputHandler – Continues when the user clicks a GUI button
  • VoiceInputHandler – Continues when the user says a phrase
  • NetworkInputHandler – Continues when the user presses something on another device (think controlling a PC game with a phone)
  • TestInputHandler – Continues via a unit test designed to verify state switching doesn’t break

 

Time to Inject!

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 “gamestateautomatically.

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.

 

 

Continue reading >
Share

Editing Unity variables – Encapsulation & [SerializeField]

Editing Unity script variables in the Inspector – The case for Encapsulation & [SerializeField]

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.


 

Problem #1 – Confusion

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.

Fix #1 – Let’s Use Properties for anything public

To resolve this, let’s change the entries we don’t want to be editable into Properties.

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)

Now let’s change the “Car.cs” script to match this.
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.

 


Problem #2 – No 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:

  • other people – make it hard for them to misuse your classes.
  • and just as important, there’s future you.

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.

 


Problem #2 – The Example

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.

Do you see what’s wrong here?

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.

Problem #2 – How do we fix it?

If we make the “FrontTires” & “RearTiresprivate, 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.

[SerializeField]

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.

Video Version

Project Download

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

Continue reading >
Share

Unity Android Deployment

By Jason Weimann / February 1, 2016

Unity Android Builds

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.

 

Grab your phone! (or tablet)

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.

Enabling Developer Options

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 toAbout device” in your android settings.

Find the “Build number“, tap on it 7 times.

Build Number in About Device

Build Number in About Device

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).

Developer Options

Developer Options

Picking our options

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.

What to check in Developer Options

What to check in Developer Options

Plug it in

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.

Google USB Drivers

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.

Let’s try a build

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

An Error??

Getting list of attached devices

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

 

The Easy Way

Unity Select Components

Unity Select Components

When you installed Unity, there were options for different build platforms.

If you didn’t selectAndroid 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.

The Alternative

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

 

Another Error? (bundle identifier)

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.

Build Again

This time, your build should actually work!

Reminder: Your phone/tablet must be UNLOCKED and plugged in for a build to deploy to it.

Deploying to Device

Deploying to Device

 

Well Done

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!

Continue reading >
Share

How to make Unity builds – Standalone

By Jason Weimann / January 28, 2016

Standalone Builds

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

Adding the currently open scene to the list of scenes to build

Adding the currently open scene to the list of scenes to build

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.

IMPORTANT NOTE: We haven’t yet added a way to close the game, to get out once you hit play hold Alt and hit F4.

Click Play and give your game a try in full screen mode.  (Remember Alt-F4 to get out).

Well Done

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)

Distributing your game

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”

Compressing Standalone Build

Compressing Standalone Build

Now you have a .zip file you can share.  Go share it now, let someone see what you’ve learned!

Continue reading >
Share

Building Flappy Bird #9 – Visuals and Beautification

By Jason Weimann / January 25, 2016

Building Flappy Bird #9 – Visuals and Beautification

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);
  }
}

Play Time

Now try playing again.

Because we call Reset when the game starts, all of the bubbles have a random initial height.

Bubbles starting at random heights

Bubbles starting at random heights

 

Bubble Colors

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.

 

Optional – Photoshop

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.

Alternative – Download the Bubble

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.

 

Material Changes

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

Adjusting Transparency

Adjusting Transparency

Adjust the Alpha channel to 170 and watch the bubble get a little transparent.

 

Background Time

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

Position 0, 1.25, 0 Rotation 0, 0, 0 Scale 15, 0.4, 0

Background Transform values

 

Where’s my stuff??

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

Horray!

Your scene view should now look like this

Good work making your game look better.

Personal Touch

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

https://www.google.com/search?q=undersea+background&biw=1536&bih=748&source=lnms&tbm=isch&sa=X&ved=0ahUKEwjrgM-HlMTKAhUClYMKHYimC8MQ_AUIBigB

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!

Sharing

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.

Continue reading >
Share

Building Flappy Bird #8 – UGUI – Building the UI

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.

UGUI – The Unity3D GUI system

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”

Anchoring

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”

Experiment

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.

Let’s Code

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.

 

Score Changing

Score Changing

 

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;amp;lt;Text&amp;amp;amp;gt;();
   scoreText.text = "Score: " + _currentScore.ToString();
}

The IncrementScore method does 3 things.

  • Adds one to the score using the ++ operator.  This could also be written as
    _currentScore = _currentScore + 1;
  • Gets the Text Component and assigns it to a local variable named “scoreText”
  • Sets the .text property of the “scoreText” object to have our new current score.

    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….

In comes the bubble

Look to your art folder in the Project View.

Drag a bubble from the Art folder into your scene.

Dragging Bubble to Scene

Dragging Bubble to Scene

Now, add a CircleCollider2D component to the newly placed bubble.

Check the IsTrigger box.

 

Let’s Code

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.

Cant catch the bubble

Cant 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.

Bubble becoming a child of seaweed parent

Bubble becoming a child of 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

Popping Bubbles

Popping Bubbles

 

Next Up: We’ll randomize our bubbles, fix more bugs, and polish up our graphics (and maybe build out to a phone).

Continue reading >
Share

Building Flappy Bird #7 – Props &Experimentation

Building Flappy Bird #7 – Props and Experimentation

In this post, we’ll add some props to make our game feel a little more polished.

Let’s get right to it!

Props

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.

How do we move the crab???

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 do it again!

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.

 

One Last time…

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.

Save your Scene

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!

Experiment

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.

Optional

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…”

 

Next Up: Scoring

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.

 

Continue reading >
Share

Building Flappy Bird #6 – Randomization & Ground

Building Flappy Bird #6 – Randomization & Ground

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.

Seaweed Random Height

 

Notice how the seaweed Y position is changing just enough to add some difficulty to our game.

Cheats (Bugs we should fix)

There's No Floor

 

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);
  }
}

Fish Floor

Fish Floor

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.

 

Real Ground

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”

Quad renamed 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

 

Materials

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.

 

Adding material to ground

Adding material to ground

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.

Removing the MeshCollider

Removing the MeshCollider

 

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”

Get ready to play

Now think for a second.

What do you expect to happen?

..

..

Go ahead and hit play to see if you were right.

Strange sliding fish

Strange sliding fish

What’s going on?

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.

Animating 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!

 

Ground scrolling too fast

Ground scrolling too fast

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.

  1. Change our death system to not use LoadLevel
  2. Stop the game, adjust the value, hit play, rinse, repeat (until we get the right value).
  3. Disable the fish and adjust while the game plays.

Personally, I prefer the easy way, so let’s go with option c.

  1. Stop playing.
  2. Now disable the Fish.
  3. Start Playing again
  4. Adjust the speed until find a reasonable value. (I ended up with 1.5)
  5. Memorize that number
  6. Stop playing
  7. Re-enter that number.

Now play one more time and enjoy what you’ve made.  See how far you can get.

 

Next Up

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.

Continue to: 

Building Flappy Bird #7 – Props &Experimentation

Continue reading >
Share
Page 20 of 21