├── Architecture_small.png ├── BackendServices ├── .gitignore ├── deploy.sh ├── functions │ ├── __init__.py │ ├── requestgamesession.py │ ├── requirements.txt │ └── scaler.py ├── template.yaml └── tests │ └── unit │ ├── __init__.py │ └── test_handler.py ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CloudFormationResources ├── cognito.yaml ├── deploy-cognito-resources.sh ├── deploy-game-server-and-update-task-definition.sh ├── deploy-vpc-ecs-resources.sh ├── ecs-resources.yaml └── game-server-task-definition.yaml ├── LICENSE ├── LinuxServerBuild └── Dockerfile ├── README.md ├── UnityProject ├── .gitignore ├── Assets │ ├── Dependencies.meta │ ├── Dependencies │ │ ├── ADD_DEPENDENCIES_HERE_CHECK_README_PRELIMINARY_SETUP │ │ └── ADD_DEPENDENCIES_HERE_CHECK_README_PRELIMINARY_SETUP.meta │ ├── Plugins.meta │ ├── PrefabsAndMaterials.meta │ ├── PrefabsAndMaterials │ │ ├── EnemyCharacter.prefab │ │ ├── EnemyCharacter.prefab.meta │ │ ├── EnemyMaterial.mat │ │ ├── EnemyMaterial.mat.meta │ │ ├── GroundMaterial.mat │ │ ├── GroundMaterial.mat.meta │ │ ├── PlayerMaterial.mat │ │ ├── PlayerMaterial.mat.meta │ │ ├── SimpleCharacter.prefab │ │ └── SimpleCharacter.prefab.meta │ ├── Scenes.meta │ ├── Scenes │ │ ├── GameWorld.unity │ │ └── GameWorld.unity.meta │ ├── Scripts.meta │ ├── Scripts │ │ ├── Client.meta │ │ ├── Client │ │ │ ├── Client.cs │ │ │ ├── Client.cs.meta │ │ │ ├── MatchmakingClient.cs │ │ │ ├── MatchmakingClient.cs.meta │ │ │ ├── MatchmakingSerializationClasses.cs │ │ │ ├── MatchmakingSerializationClasses.cs.meta │ │ │ ├── NetworkClient.cs │ │ │ ├── NetworkClient.cs.meta │ │ │ ├── SimpleController.cs │ │ │ └── SimpleController.cs.meta │ │ ├── NetworkingShared.meta │ │ ├── NetworkingShared │ │ │ ├── MessageClasses.cs │ │ │ ├── MessageClasses.cs.meta │ │ │ ├── NetworkPlayer.cs │ │ │ ├── NetworkPlayer.cs.meta │ │ │ ├── NetworkProtocol.cs │ │ │ └── NetworkProtocol.cs.meta │ │ ├── Server.meta │ │ ├── Server │ │ │ ├── Server.cs │ │ │ └── Server.cs.meta │ │ ├── UIManager.cs │ │ └── UIManager.cs.meta │ ├── link.xml │ └── link.xml.meta ├── Packages │ └── manifest.json ├── ProjectSettings │ ├── AudioManager.asset │ ├── ClusterInputManager.asset │ ├── DynamicsManager.asset │ ├── EditorBuildSettings.asset │ ├── EditorSettings.asset │ ├── GraphicsSettings.asset │ ├── InputManager.asset │ ├── NavMeshAreas.asset │ ├── Physics2DSettings.asset │ ├── PresetManager.asset │ ├── ProjectSettings.asset │ ├── ProjectVersion.txt │ ├── QualitySettings.asset │ ├── TagManager.asset │ ├── TimeManager.asset │ ├── UnityConnectSettings.asset │ ├── VFXManager.asset │ └── XRSettings.asset └── packages.config ├── cleanup_all_resources.sh └── configuration.sh /Architecture_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-gamelift-fleetiq-with-amazon-ecs/70eb2adb79cac2c441943a9f4812c73238f6d296/Architecture_small.png -------------------------------------------------------------------------------- /BackendServices/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # temporary files which can be created if a process still has a handle open of a deleted file 8 | .fuse_hidden* 9 | 10 | # KDE directory preferences 11 | .directory 12 | 13 | # Linux trash folder which might appear on any partition or disk 14 | .Trash-* 15 | 16 | # .nfs files are created when an open file is removed but is still being accessed 17 | .nfs* 18 | 19 | ### OSX ### 20 | *.DS_Store 21 | .AppleDouble 22 | .LSOverride 23 | 24 | # Icon must end with two \r 25 | Icon 26 | 27 | # Thumbnails 28 | ._* 29 | 30 | # Files that might appear in the root of a volume 31 | .DocumentRevisions-V100 32 | .fseventsd 33 | .Spotlight-V100 34 | .TemporaryItems 35 | .Trashes 36 | .VolumeIcon.icns 37 | .com.apple.timemachine.donotpresent 38 | 39 | # Directories potentially created on remote AFP share 40 | .AppleDB 41 | .AppleDesktop 42 | Network Trash Folder 43 | Temporary Items 44 | .apdisk 45 | 46 | ### PyCharm ### 47 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 48 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 49 | 50 | # User-specific stuff: 51 | .idea/**/workspace.xml 52 | .idea/**/tasks.xml 53 | .idea/dictionaries 54 | 55 | # Sensitive or high-churn files: 56 | .idea/**/dataSources/ 57 | .idea/**/dataSources.ids 58 | .idea/**/dataSources.xml 59 | .idea/**/dataSources.local.xml 60 | .idea/**/sqlDataSources.xml 61 | .idea/**/dynamic.xml 62 | .idea/**/uiDesigner.xml 63 | 64 | # Gradle: 65 | .idea/**/gradle.xml 66 | .idea/**/libraries 67 | 68 | # CMake 69 | cmake-build-debug/ 70 | 71 | # Mongo Explorer plugin: 72 | .idea/**/mongoSettings.xml 73 | 74 | ## File-based project format: 75 | *.iws 76 | 77 | ## Plugin-specific files: 78 | 79 | # IntelliJ 80 | /out/ 81 | 82 | # mpeltonen/sbt-idea plugin 83 | .idea_modules/ 84 | 85 | # JIRA plugin 86 | atlassian-ide-plugin.xml 87 | 88 | # Cursive Clojure plugin 89 | .idea/replstate.xml 90 | 91 | # Ruby plugin and RubyMine 92 | /.rakeTasks 93 | 94 | # Crashlytics plugin (for Android Studio and IntelliJ) 95 | com_crashlytics_export_strings.xml 96 | crashlytics.properties 97 | crashlytics-build.properties 98 | fabric.properties 99 | 100 | ### PyCharm Patch ### 101 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 102 | 103 | # *.iml 104 | # modules.xml 105 | # .idea/misc.xml 106 | # *.ipr 107 | 108 | # Sonarlint plugin 109 | .idea/sonarlint 110 | 111 | ### Python ### 112 | # Byte-compiled / optimized / DLL files 113 | __pycache__/ 114 | *.py[cod] 115 | *$py.class 116 | 117 | # C extensions 118 | *.so 119 | 120 | # Distribution / packaging 121 | .Python 122 | build/ 123 | develop-eggs/ 124 | dist/ 125 | downloads/ 126 | eggs/ 127 | .eggs/ 128 | lib/ 129 | lib64/ 130 | parts/ 131 | sdist/ 132 | var/ 133 | wheels/ 134 | *.egg-info/ 135 | .installed.cfg 136 | *.egg 137 | 138 | # PyInstaller 139 | # Usually these files are written by a python script from a template 140 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 141 | *.manifest 142 | *.spec 143 | 144 | # Installer logs 145 | pip-log.txt 146 | pip-delete-this-directory.txt 147 | 148 | # Unit test / coverage reports 149 | htmlcov/ 150 | .tox/ 151 | .coverage 152 | .coverage.* 153 | .cache 154 | .pytest_cache/ 155 | nosetests.xml 156 | coverage.xml 157 | *.cover 158 | .hypothesis/ 159 | 160 | # Translations 161 | *.mo 162 | *.pot 163 | 164 | # Flask stuff: 165 | instance/ 166 | .webassets-cache 167 | 168 | # Scrapy stuff: 169 | .scrapy 170 | 171 | # Sphinx documentation 172 | docs/_build/ 173 | 174 | # PyBuilder 175 | target/ 176 | 177 | # Jupyter Notebook 178 | .ipynb_checkpoints 179 | 180 | # pyenv 181 | .python-version 182 | 183 | # celery beat schedule file 184 | celerybeat-schedule.* 185 | 186 | # SageMath parsed files 187 | *.sage.py 188 | 189 | # Environments 190 | .env 191 | .venv 192 | env/ 193 | venv/ 194 | ENV/ 195 | env.bak/ 196 | venv.bak/ 197 | 198 | # Spyder project settings 199 | .spyderproject 200 | .spyproject 201 | 202 | # Rope project settings 203 | .ropeproject 204 | 205 | # mkdocs documentation 206 | /site 207 | 208 | # mypy 209 | .mypy_cache/ 210 | 211 | ### VisualStudioCode ### 212 | .vscode/* 213 | !.vscode/settings.json 214 | !.vscode/tasks.json 215 | !.vscode/launch.json 216 | !.vscode/extensions.json 217 | .history 218 | 219 | ### Windows ### 220 | # Windows thumbnail cache files 221 | Thumbs.db 222 | ehthumbs.db 223 | ehthumbs_vista.db 224 | 225 | # Folder config file 226 | Desktop.ini 227 | 228 | # Recycle Bin used on file shares 229 | $RECYCLE.BIN/ 230 | 231 | # Windows Installer files 232 | *.cab 233 | *.msi 234 | *.msm 235 | *.msp 236 | 237 | # Windows shortcuts 238 | *.lnk 239 | 240 | # Build folder 241 | 242 | */build/* 243 | 244 | template_output.yaml 245 | 246 | # End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode -------------------------------------------------------------------------------- /BackendServices/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get the configuration variables 4 | source ../configuration.sh 5 | 6 | # Create deployment bucket if it doesn't exist 7 | if [ $region == "us-east-1" ] 8 | then 9 | aws s3api create-bucket --bucket $deploymentbucketname --region $region 10 | else 11 | aws s3api create-bucket --bucket $deploymentbucketname --region $region --create-bucket-configuration LocationConstraint=$region 12 | fi 13 | 14 | # Build, package and deploy the backend 15 | sam build --use-container 16 | sam package --s3-bucket $deploymentbucketname --output-template-file template_output.yaml 17 | sam deploy --template-file template_output.yaml --region $region --capabilities CAPABILITY_IAM --stack-name fleetiq-ecs-game-servers-backend -------------------------------------------------------------------------------- /BackendServices/functions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-gamelift-fleetiq-with-amazon-ecs/70eb2adb79cac2c441943a9f4812c73238f6d296/BackendServices/functions/__init__.py -------------------------------------------------------------------------------- /BackendServices/functions/requestgamesession.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | import json 5 | import datetime 6 | import time 7 | import os 8 | import boto3 9 | from datetime import timedelta 10 | import random 11 | 12 | # Tries to find an existing or free game session and return the IP and Port to the client 13 | 14 | def lambda_handler(event, context): 15 | 16 | sqs_client = boto3.client('sqs') 17 | 18 | # 1. Check SQS Queue if there are sessions available 19 | # Try to receive message from SQS queue 20 | try: 21 | response = sqs_client.receive_message( 22 | QueueUrl=os.environ['SQS_QUEUE_URL'], 23 | MaxNumberOfMessages=1, 24 | VisibilityTimeout=15, 25 | WaitTimeSeconds=1 26 | ) 27 | message = response['Messages'][0] 28 | print(message) 29 | receipt_handle = message['ReceiptHandle'] 30 | connection_info = message['Body'] 31 | print(receipt_handle) 32 | print("got session: " + connection_info) 33 | 34 | connection_splitted = connection_info.split(":") 35 | ip = connection_splitted[0] 36 | port = connection_splitted[1] 37 | 38 | print("IP: " + ip + " PORT: " + port) 39 | 40 | # Delete received message from queue 41 | sqs_client.delete_message( 42 | QueueUrl=os.environ['SQS_QUEUE_URL'], 43 | ReceiptHandle=receipt_handle 44 | ) 45 | 46 | # Return result to client 47 | return { 48 | "statusCode": 200, 49 | "body": json.dumps({ 'publicIP': ip, 'port': port }) 50 | } 51 | except: 52 | print("Failed getting a session from the SQS queue, will try claiming a new one") 53 | 54 | # 2. If not, try to claim a new session through FleetIQ 55 | client = boto3.client('gamelift') 56 | response = client.claim_game_server( 57 | GameServerGroupName='ExampleGameServerGroup', 58 | ) 59 | print(response) 60 | connection_info = response["GameServer"]["ConnectionInfo"] 61 | try: 62 | connection_splitted = connection_info.split(":") 63 | ip = connection_splitted[0] 64 | port = connection_splitted[1] 65 | 66 | print("IP: " + ip + " PORT: " + port) 67 | 68 | # Put a ticket in to the SQS for the next player (we match 1-v-1 sessions) 69 | response = sqs_client.send_message( 70 | QueueUrl=os.environ['SQS_QUEUE_URL'], 71 | MessageBody=( 72 | connection_info 73 | ) 74 | ) 75 | print(response['MessageId']) 76 | 77 | return { 78 | "statusCode": 200, 79 | "body": json.dumps({ 'publicIP': ip, 'port': port }) 80 | } 81 | except: 82 | print("Failed getting a new session") 83 | 84 | # 3. Failed to find a server 85 | return { 86 | "statusCode": 500, 87 | "body": json.dumps({ 'failed': 'couldnt find a free server spot'}) 88 | } -------------------------------------------------------------------------------- /BackendServices/functions/requirements.txt: -------------------------------------------------------------------------------- 1 | redis -------------------------------------------------------------------------------- /BackendServices/functions/scaler.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | import json 5 | import datetime 6 | import time 7 | import os 8 | import boto3 9 | from boto3.dynamodb.conditions import Key, Attr 10 | import redis 11 | from datetime import timedelta 12 | 13 | # The amount of seconds we give servers to start up 14 | server_startup_grace_period = 60 15 | 16 | cpu_per_task = 512 17 | memory_per_task = 953 18 | 19 | """ Returns the available memory and CPU for the whole ECS cluster """ 20 | def get_available_memory_and_cpu(ecs_cluster_name): 21 | total_cpu = 0 22 | total_memory = 0 23 | 24 | ecs = boto3.client("ecs") 25 | firstround = True 26 | nextToken = None 27 | 28 | # Use pagination to get all instances beyond the first 100 29 | while firstround or nextToken != None: 30 | 31 | if nextToken == None: 32 | response = ecs.list_container_instances( 33 | cluster=ecs_cluster_name, 34 | ) 35 | else: 36 | response = ecs.list_container_instances( 37 | cluster=ecs_cluster_name, nextToken=nextToken 38 | ) 39 | print(response) 40 | if "nextToken" in response: 41 | print("found next token") 42 | nextToken = response["nextToken"] 43 | else: 44 | nextToken = None 45 | 46 | container_instances = response["containerInstanceArns"] 47 | if len(container_instances) > 0: 48 | 49 | # Get instance id, not full arn 50 | container_instances_id_only = [] 51 | for arn in container_instances: 52 | container_instances_id_only.append(arn.split("/")[2]) 53 | 54 | response = ecs.describe_container_instances( 55 | cluster=ecs_cluster_name, 56 | containerInstances=container_instances_id_only 57 | ) 58 | 59 | for instance in response["containerInstances"]: 60 | for remaining_resource in instance["remainingResources"]: 61 | if remaining_resource["name"] == "CPU": 62 | #print("Remaining CPU: " + str(remaining_resource["integerValue"])) 63 | total_cpu += int(remaining_resource["integerValue"]) 64 | elif remaining_resource["name"] == "MEMORY": 65 | #print("Remaining Memoery: " + str(remaining_resource["integerValue"])) 66 | total_memory += int(remaining_resource["integerValue"]) 67 | 68 | firstround = False 69 | 70 | print("Subtotal: " + str(total_cpu) + "," + str(total_memory)) 71 | 72 | return total_cpu, total_memory 73 | 74 | def lambda_handler(event, context): 75 | 76 | print("Running scheduled Lambda function to start new game server tasks when necessary") 77 | 78 | # Get the resources from ECS and Task CLoudFormation Stacks from environment variables 79 | ecs_cluster_name = os.environ['ECS_CLUSTER_NAME'] 80 | ecs_task_definition = "" 81 | 82 | cloudformation = boto3.client("cloudformation") 83 | # Get the Task to deploy (as this changes dynamically) 84 | stack = cloudformation.describe_stacks(StackName="fleetiq-game-servers-task-definition")["Stacks"][0] 85 | for output in stack["Outputs"]: 86 | print('%s=%s (%s)' % (output["OutputKey"], output["OutputValue"], output["Description"])) 87 | if output["OutputKey"] == "TaskDefinition": 88 | ecs_task_definition = output["OutputValue"] 89 | 90 | # Track start time 91 | start_time = time.time() 92 | 93 | ### Run the scaler up to 60 seconds (next one will be triggered after 1 minute) 94 | while (time.time() - start_time) < 59.0: 95 | 96 | try: 97 | # 1. Check Fleet CPU and Memory capacity 98 | total_cpu, total_memory = get_available_memory_and_cpu(ecs_cluster_name) 99 | print("Total CPU: " + str(total_cpu) + " Total Memory: " + str(total_memory)) 100 | 101 | # 2. Check how many game server Tasks we can start 102 | max_tasks_based_on_cpu = int(total_cpu / cpu_per_task) 103 | max_tasks_based_on_memory = int(total_memory / memory_per_task) 104 | 105 | print("Total to start cpu: " + str(max_tasks_based_on_cpu) + " total to start mem: " + str(max_tasks_based_on_memory)) 106 | 107 | # Start the lowest value of cpu and memory bound and max of 10 per round 108 | total_to_start = min(max_tasks_based_on_cpu, max_tasks_based_on_memory, 10) 109 | 110 | print("Will start: " + str(total_to_start)) 111 | 112 | # Spin up the missing servers 113 | if total_to_start > 0: 114 | # Start a game server ECS Task for each missing game serve 115 | client = boto3.client('ecs') 116 | response = client.run_task( 117 | cluster=ecs_cluster_name, 118 | launchType = 'EC2', 119 | taskDefinition=ecs_task_definition, 120 | count = total_to_start 121 | ) 122 | except Exception as e: 123 | print("Exception occured in starting Tasks") 124 | print(e) 125 | # Wait for next round unless this was the last on this minute 126 | if time.time() - start_time < 59.0: 127 | print("Wait 1 second before next round") 128 | time.sleep(1.0) -------------------------------------------------------------------------------- /BackendServices/template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | Description: > 4 | SAM App for starting ECS game server Tasks on a schedule when needed and a backend API to request game sessions 5 | 6 | # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst 7 | Globals: 8 | Function: 9 | Timeout: 300 10 | 11 | Parameters: 12 | ECSResourcesStackName: 13 | Type: String 14 | Default: "fleetiq-ecs-vpc-and-ecs-resources" 15 | Description: Name of the stack for the ECS resources to import 16 | TaskResourcesStackName: 17 | Type: String 18 | Default: "fleetiq-game-servers-task-definition" 19 | Description: Name of the stack for the Task resources to import 20 | 21 | 22 | Resources: 23 | 24 | # Log group and retention for scaling function 25 | ScalingFunctionLogGroup: 26 | Type: AWS::Logs::LogGroup 27 | DependsOn: [ ScalingFunction ] 28 | Properties: 29 | LogGroupName: !Sub /aws/lambda/${ScalingFunction} 30 | RetentionInDays: 30 31 | 32 | # Queue for available active sessions for the backend 33 | MatchMakingTicketsQueue: 34 | Type: AWS::SQS::Queue 35 | Properties: 36 | ReceiveMessageWaitTimeSeconds: 1 #Wait 1 second for messaged to arrive by default 37 | 38 | # API for Frontend functionality 39 | FrontEndAPI: 40 | Type: AWS::Serverless::Api 41 | Properties: 42 | StageName: Prod 43 | # Authenticate users with IAM (Cognito identities) 44 | Auth: 45 | DefaultAuthorizer: AWS_IAM 46 | InvokeRole: NONE #Using the Lambda role instead of caller 47 | 48 | # Scheduled Lambda function to start new game server ECS Tasks whenever there's capacity available 49 | ScalingFunction: 50 | Type: AWS::Serverless::Function 51 | Properties: 52 | CodeUri: functions/ 53 | Handler: scaler.lambda_handler 54 | Runtime: python3.7 55 | MemorySize: 1024 56 | ReservedConcurrentExecutions: 1 # We always want exactly one copy of this function running at maximum 57 | EventInvokeConfig: 58 | MaximumEventAgeInSeconds: 60 # Don't keep events in queue for long. 59 | MaximumRetryAttempts: 1 # Sometimes the execution of the previous function can overlap so one retry makes sense 60 | # Environment variables from other stacks to access the resources 61 | Environment: 62 | Variables: 63 | ECS_CLUSTER_NAME: 64 | Fn::ImportValue: 65 | !Sub "${ECSResourcesStackName}:ClusterName" 66 | Policies: 67 | - AWSCloudFormationReadOnlyAccess 68 | - AmazonECS_FullAccess 69 | - CloudWatchReadOnlyAccess 70 | Events: 71 | CheckScalingNeeds: 72 | Type: Schedule 73 | Properties: 74 | Schedule: rate(1 minute) 75 | 76 | # Function called by clients through the API to request a game session 77 | RequestGameSession: 78 | Type: AWS::Serverless::Function 79 | Properties: 80 | CodeUri: functions/ 81 | Handler: requestgamesession.lambda_handler 82 | Runtime: python3.7 83 | MemorySize: 1024 84 | Timeout: 15 85 | Policies: 86 | - AmazonSQSFullAccess 87 | - Version: 2012-10-17 88 | Statement: 89 | - Effect: Allow 90 | Action: 'gamelift:*' 91 | Resource: '*' 92 | # Environment variables from other stacks to access the resources 93 | Environment: 94 | Variables: 95 | SQS_QUEUE_URL: !Ref MatchMakingTicketsQueue 96 | Events: 97 | GetGameSession: 98 | Type: Api 99 | Properties: 100 | RestApiId: !Ref FrontEndAPI 101 | Path: /requestgamesession 102 | Method: get 103 | 104 | Outputs: 105 | ScalingFunction: 106 | Description: "Scaling Lambda Function ARN" 107 | Value: !GetAtt ScalingFunction.Arn 108 | ScalingFunctionIamRole: 109 | Description: "Implicit IAM Role created for Scaling function" 110 | Value: !GetAtt ScalingFunction.Arn 111 | FrontEndAPI: 112 | Description: "API Gateway endpoint URL for Prod stage for FrontEndAPI" 113 | Value: !Sub "https://${FrontEndAPI}.execute-api.${AWS::Region}.amazonaws.com/Prod/" 114 | FrontEndApiArn: 115 | Description: "The Execute ARN for the Cognito Role Permissions" 116 | Value: !Sub "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${FrontEndAPI}/*/*/*" 117 | Export: 118 | Name: !Sub ${AWS::StackName}:FrontEndApiArn 119 | -------------------------------------------------------------------------------- /BackendServices/tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-gamelift-fleetiq-with-amazon-ecs/70eb2adb79cac2c441943a9f4812c73238f6d296/BackendServices/tests/unit/__init__.py -------------------------------------------------------------------------------- /BackendServices/tests/unit/test_handler.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import pytest 4 | 5 | from scaler import app 6 | 7 | 8 | @pytest.fixture() 9 | def apigw_event(): 10 | """ Generates API GW Event""" 11 | 12 | return { 13 | "body": '{ "test": "body"}', 14 | "resource": "/{proxy+}", 15 | "requestContext": { 16 | "resourceId": "123456", 17 | "apiId": "1234567890", 18 | "resourcePath": "/{proxy+}", 19 | "httpMethod": "POST", 20 | "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", 21 | "accountId": "123456789012", 22 | "identity": { 23 | "apiKey": "", 24 | "userArn": "", 25 | "cognitoAuthenticationType": "", 26 | "caller": "", 27 | "userAgent": "Custom User Agent String", 28 | "user": "", 29 | "cognitoIdentityPoolId": "", 30 | "cognitoIdentityId": "", 31 | "cognitoAuthenticationProvider": "", 32 | "sourceIp": "127.0.0.1", 33 | "accountId": "", 34 | }, 35 | "stage": "prod", 36 | }, 37 | "queryStringParameters": {"foo": "bar"}, 38 | "headers": { 39 | "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", 40 | "Accept-Language": "en-US,en;q=0.8", 41 | "CloudFront-Is-Desktop-Viewer": "true", 42 | "CloudFront-Is-SmartTV-Viewer": "false", 43 | "CloudFront-Is-Mobile-Viewer": "false", 44 | "X-Forwarded-For": "127.0.0.1, 127.0.0.2", 45 | "CloudFront-Viewer-Country": "US", 46 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 47 | "Upgrade-Insecure-Requests": "1", 48 | "X-Forwarded-Port": "443", 49 | "Host": "1234567890.execute-api.us-east-1.amazonaws.com", 50 | "X-Forwarded-Proto": "https", 51 | "X-Amz-Cf-Id": "aaaaaaaaaae3VYQb9jd-nvCd-de396Uhbp027Y2JvkCPNLmGJHqlaA==", 52 | "CloudFront-Is-Tablet-Viewer": "false", 53 | "Cache-Control": "max-age=0", 54 | "User-Agent": "Custom User Agent String", 55 | "CloudFront-Forwarded-Proto": "https", 56 | "Accept-Encoding": "gzip, deflate, sdch", 57 | }, 58 | "pathParameters": {"proxy": "/examplepath"}, 59 | "httpMethod": "POST", 60 | "stageVariables": {"baz": "qux"}, 61 | "path": "/examplepath", 62 | } 63 | 64 | 65 | def test_lambda_handler(apigw_event, mocker): 66 | 67 | # NO TESTS ATM 68 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /CloudFormationResources/cognito.yaml: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | AWSTemplateFormatVersion: "2010-09-09" 5 | Description: FleetIQ ECS Example Cognito resources 6 | 7 | Parameters: 8 | 9 | # The Stack name for Backend Resources to get the API Gateway ARN 10 | BackendServicesStackName: 11 | Description: CloudFormation stack containing the Backend Services 12 | Type: String 13 | Default: fleetiq-ecs-game-servers-backend 14 | 15 | Resources: 16 | 17 | # COGNITO RESOURCES 18 | 19 | # Creates a federated Identity pool 20 | IdentityPool: 21 | Type: "AWS::Cognito::IdentityPool" 22 | Properties: 23 | IdentityPoolName: FleeIQExampleIdentityPool 24 | AllowUnauthenticatedIdentities: true 25 | 26 | # Create a role for unauthenticated access to AWS resources (used in the example) 27 | CognitoUnAuthenticatedRole: 28 | Type: "AWS::IAM::Role" 29 | Properties: 30 | AssumeRolePolicyDocument: 31 | Version: "2012-10-17" 32 | Statement: 33 | - Effect: "Allow" 34 | Principal: 35 | Federated: "cognito-identity.amazonaws.com" 36 | Action: 37 | - "sts:AssumeRoleWithWebIdentity" 38 | Condition: 39 | StringEquals: 40 | "cognito-identity.amazonaws.com:aud": !Ref IdentityPool 41 | "ForAnyValue:StringLike": 42 | "cognito-identity.amazonaws.com:amr": unauthenticated 43 | Policies: 44 | - PolicyName: "CognitoUnauthorizedPolicy" 45 | PolicyDocument: 46 | Version: "2012-10-17" 47 | Statement: 48 | - Effect: "Allow" 49 | Action: 50 | - "cognito-sync:*" 51 | - "execute-api:Invoke" 52 | Resource: 53 | Fn::ImportValue: 54 | !Sub "${BackendServicesStackName}:FrontEndApiArn" 55 | 56 | # Create a role for authenticated acces to AWS resources. Control what your user can access. This example only allows Lambda invokation 57 | # Only allows users in the previously created Identity Pool 58 | CognitoAuthenticatedRole: 59 | Type: "AWS::IAM::Role" 60 | Properties: 61 | AssumeRolePolicyDocument: 62 | Version: "2012-10-17" 63 | Statement: 64 | - Effect: "Allow" 65 | Principal: 66 | Federated: "cognito-identity.amazonaws.com" 67 | Action: 68 | - "sts:AssumeRoleWithWebIdentity" 69 | Condition: 70 | StringEquals: 71 | "cognito-identity.amazonaws.com:aud": !Ref IdentityPool 72 | "ForAnyValue:StringLike": 73 | "cognito-identity.amazonaws.com:amr": authenticated 74 | Policies: 75 | - PolicyName: "CognitoAuthorizedPolicy" 76 | PolicyDocument: 77 | Version: "2012-10-17" 78 | Statement: 79 | - Effect: "Allow" 80 | Action: 81 | - "cognito-sync:*" 82 | - "execute-api:Invoke" 83 | Resource: 84 | Fn::ImportValue: 85 | !Sub "${BackendServicesStackName}:FrontEndApiArn" 86 | 87 | # Assigns the roles to the Identity Pool 88 | IdentityPoolRoleMapping: 89 | Type: "AWS::Cognito::IdentityPoolRoleAttachment" 90 | Properties: 91 | IdentityPoolId: !Ref IdentityPool 92 | Roles: 93 | authenticated: !GetAtt CognitoAuthenticatedRole.Arn 94 | unauthenticated: !GetAtt CognitoUnAuthenticatedRole.Arn 95 | 96 | Outputs: 97 | IdentityPoolID: 98 | Description: The ID for the identity pool to be used to request identities 99 | Value: !Ref IdentityPool 100 | Export: 101 | Name: !Sub "${AWS::StackName}-IdentityPool" -------------------------------------------------------------------------------- /CloudFormationResources/deploy-cognito-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get the configuration variables 4 | source ../configuration.sh 5 | 6 | # Returns the status of a stack 7 | getstatusofstack() { 8 | aws cloudformation describe-stacks --region $region --stack-name $1 --query Stacks[].StackStatus --output text 2>/dev/null 9 | } 10 | 11 | # Deploy the Cognito Resources 12 | stackstatus=$(getstatusofstack fleetiq-ecs-game-servers-cognito) 13 | if [ -z "$stackstatus" ]; then 14 | echo "Creating Cognito Resources stack (this will take some time)..." 15 | aws cloudformation --region $region create-stack --stack-name fleetiq-ecs-game-servers-cognito \ 16 | --template-body file://cognito.yaml \ 17 | --capabilities CAPABILITY_IAM 18 | aws cloudformation --region $region wait stack-create-complete --stack-name fleetiq-ecs-game-servers-cognito 19 | echo "Done creating stack!" 20 | else 21 | echo "Updating Cognito Resources stack (this will take some time)..." 22 | aws cloudformation --region $region update-stack --stack-name fleetiq-ecs-game-servers-cognito \ 23 | --template-body file://cognito.yaml \ 24 | --capabilities CAPABILITY_IAM 25 | aws cloudformation --region $region wait stack-update-complete --stack-name fleetiq-ecs-game-servers-cognito 26 | echo "Done updating stack!" 27 | fi 28 | 29 | echo "You need this Identity pool ID in MatchmakingClient.cs:" 30 | echo $(aws cloudformation --region $region describe-stacks --stack-name fleetiq-ecs-game-servers-cognito --query "Stacks[0].Outputs[0].OutputValue") -------------------------------------------------------------------------------- /CloudFormationResources/deploy-game-server-and-update-task-definition.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get the configuration variables 4 | source ../configuration.sh 5 | 6 | # Returns the status of a stack 7 | getstatusofstack() { 8 | aws cloudformation describe-stacks --region $region --stack-name $1 --query Stacks[].StackStatus --output text 2>/dev/null 9 | } 10 | 11 | # 1. Create ECR repository if it doesn't exits 12 | aws ecr create-repository --repository-name fleetiq-game-servers --region $region 13 | 14 | # 2. Login to ECR (AWS CLI V2) 15 | aws ecr get-login-password --region $region | docker login --username AWS --password-stdin $accountid.dkr.ecr.$region.amazonaws.com/fleetiq-game-servers 16 | #eval $(aws ecr get-login --region $region --no-include-email) #This if for CLI V1 17 | 18 | # 3. Create Docker Image from latest build (expected to be already created from Unity) 19 | build_id=$(date +%Y-%m-%d.%H%M%S) 20 | docker buildx build ../LinuxServerBuild/ --platform=linux/amd64 -t $accountid.dkr.ecr.$region.amazonaws.com/fleetiq-game-servers:$build_id 21 | 22 | # 4. Push the image to ECR 23 | docker push $accountid.dkr.ecr.$region.amazonaws.com/fleetiq-game-servers:$build_id 24 | 25 | # 5. Deploy an updated task definition with the new image 26 | stackstatus=$(getstatusofstack fleetiq-game-servers-task-definition) 27 | if [ -z "$stackstatus" ]; then 28 | echo "Creating fleetiq-game-servers-task-definition stack (this will take some time)..." 29 | aws cloudformation --region $region create-stack --stack-name fleetiq-game-servers-task-definition \ 30 | --template-body file://game-server-task-definition.yaml \ 31 | --parameters ParameterKey=Image,ParameterValue=$accountid.dkr.ecr.$region.amazonaws.com/fleetiq-game-servers:$build_id \ 32 | --capabilities CAPABILITY_IAM 33 | aws cloudformation --region $region wait stack-create-complete --stack-name fleetiq-game-servers-task-definition 34 | echo "Done creating stack!" 35 | else 36 | echo "Updating fleetiq-game-servers-task-definition stack (this will take some time)..." 37 | aws cloudformation --region $region update-stack --stack-name fleetiq-game-servers-task-definition \ 38 | --template-body file://game-server-task-definition.yaml \ 39 | --parameters ParameterKey=Image,ParameterValue=$accountid.dkr.ecr.$region.amazonaws.com/fleetiq-game-servers:$build_id \ 40 | --capabilities CAPABILITY_IAM 41 | aws cloudformation --region $region wait stack-update-complete --stack-name fleetiq-game-servers-task-definition 42 | echo "Done updating stack!" 43 | fi -------------------------------------------------------------------------------- /CloudFormationResources/deploy-vpc-ecs-resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get the configuration variables 4 | source ../configuration.sh 5 | 6 | # Returns the status of a stack 7 | getstatusofstack() { 8 | aws cloudformation describe-stacks --region $region --stack-name $1 --query Stacks[].StackStatus --output text 2>/dev/null 9 | } 10 | 11 | # Deploy the VPC and ECS resources with CloudFromation 12 | stackstatus=$(getstatusofstack fleetiq-ecs-vpc-and-ecs-resources) 13 | if [ -z "$stackstatus" ]; then 14 | echo "Creating ecs-resources stack (this will take some time)..." 15 | aws cloudformation --region $region create-stack --stack-name fleetiq-ecs-vpc-and-ecs-resources \ 16 | --template-body file://ecs-resources.yaml \ 17 | --capabilities CAPABILITY_NAMED_IAM 18 | aws cloudformation --region $region wait stack-create-complete --stack-name fleetiq-ecs-vpc-and-ecs-resources 19 | echo "Done creating stack!" 20 | else 21 | echo "Updating ecs-resources stack (this will take some time)..." 22 | aws cloudformation --region $region update-stack --stack-name fleetiq-ecs-vpc-and-ecs-resources \ 23 | --template-body file://ecs-resources.yaml \ 24 | --capabilities CAPABILITY_NAMED_IAM 25 | aws cloudformation --region $region wait stack-update-complete --stack-name fleetiq-ecs-vpc-and-ecs-resources 26 | echo "Done updating stack!" 27 | fi -------------------------------------------------------------------------------- /CloudFormationResources/ecs-resources.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: A stack for deploying base resources for the ECS cluster and FleetIQ Game Server Group 3 | 4 | Parameters: 5 | GameServerPortStart: 6 | Type: Number 7 | Default: 32768 8 | Description: The inbound port range start for the ECS task game servers 9 | GameServerPortEnd: 10 | Type: Number 11 | Default: 65535 12 | Description: The inbound port range end for the ECS task game servers 13 | ECSAMI: 14 | Description: ECS-Optimized AMI ID 15 | Type: AWS::SSM::Parameter::Value 16 | Default: /aws/service/ecs/optimized-ami/amazon-linux-2/recommended/image_id 17 | InstanceType: 18 | Description: Which instance type should we use to build the ECS cluster? 19 | Type: String 20 | Default: c4.large 21 | ClusterSize: 22 | Description: How many ECS hosts do you want to initially deploy? 23 | Type: Number 24 | Default: 2 25 | Mappings: 26 | # Hard values for the subnet masks. These masks define 27 | # the range of internal IP addresses that can be assigned. 28 | # The VPC can have all IP's from 10.0.0.0 to 10.0.255.255 29 | # 30 | # If you need more IP addresses (perhaps you have so many 31 | # instances that you run out) then you can customize these 32 | # ranges to add more. Currently supports 16K game servers 33 | SubnetConfig: 34 | VPC: 35 | CIDR: '10.0.0.0/16' 36 | PublicOne: #8K IPs (used by game servers) 37 | CIDR: '10.0.0.0/24' 38 | PublicTwo: #8K IPs (used by game servers) 39 | CIDR: '10.0.32.0/24' 40 | Resources: 41 | # VPC in which containers will be networked. 42 | # It has two public subnets 43 | # We distribute the subnets across the first two available subnets 44 | # for the region, for high availability. 45 | VPC: 46 | Type: AWS::EC2::VPC 47 | Properties: 48 | EnableDnsSupport: true 49 | EnableDnsHostnames: true 50 | CidrBlock: !FindInMap ['SubnetConfig', 'VPC', 'CIDR'] 51 | 52 | # Two public subnets, where containers can have public IP addresses 53 | PublicSubnetOne: 54 | Type: AWS::EC2::Subnet 55 | Properties: 56 | AvailabilityZone: 57 | Fn::Select: 58 | - 0 59 | - Fn::GetAZs: {Ref: 'AWS::Region'} 60 | VpcId: !Ref 'VPC' 61 | CidrBlock: !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR'] 62 | MapPublicIpOnLaunch: true 63 | PublicSubnetTwo: 64 | Type: AWS::EC2::Subnet 65 | Properties: 66 | AvailabilityZone: 67 | Fn::Select: 68 | - 1 69 | - Fn::GetAZs: {Ref: 'AWS::Region'} 70 | VpcId: !Ref 'VPC' 71 | CidrBlock: !FindInMap ['SubnetConfig', 'PublicTwo', 'CIDR'] 72 | MapPublicIpOnLaunch: true 73 | 74 | # Setup networking resources for the public subnets. Containers 75 | # in the public subnets have public IP addresses and the routing table 76 | # sends network traffic via the internet gateway. 77 | InternetGateway: 78 | Type: AWS::EC2::InternetGateway 79 | GatewayAttachement: 80 | Type: AWS::EC2::VPCGatewayAttachment 81 | Properties: 82 | VpcId: !Ref 'VPC' 83 | InternetGatewayId: !Ref 'InternetGateway' 84 | PublicRouteTable: 85 | Type: AWS::EC2::RouteTable 86 | Properties: 87 | VpcId: !Ref 'VPC' 88 | PublicRoute: 89 | Type: AWS::EC2::Route 90 | DependsOn: GatewayAttachement 91 | Properties: 92 | RouteTableId: !Ref 'PublicRouteTable' 93 | DestinationCidrBlock: '0.0.0.0/0' 94 | GatewayId: !Ref 'InternetGateway' 95 | PublicSubnetOneRouteTableAssociation: 96 | Type: AWS::EC2::SubnetRouteTableAssociation 97 | Properties: 98 | SubnetId: !Ref PublicSubnetOne 99 | RouteTableId: !Ref PublicRouteTable 100 | PublicSubnetTwoRouteTableAssociation: 101 | Type: AWS::EC2::SubnetRouteTableAssociation 102 | Properties: 103 | SubnetId: !Ref PublicSubnetTwo 104 | RouteTableId: !Ref PublicRouteTable 105 | 106 | # ECS Resources 107 | ECSCluster: 108 | Type: AWS::ECS::Cluster 109 | Properties: 110 | # Enable Container Insights for detailed information on containers 111 | ClusterSettings: 112 | - Name: containerInsights 113 | Value: enabled 114 | 115 | # A security group for the ECS instances we will run in the Game Server Group. 116 | ECSInstanceSecurityGroup: 117 | Type: AWS::EC2::SecurityGroup 118 | Properties: 119 | GroupDescription: Access to the ECS containers 120 | VpcId: !Ref 'VPC' 121 | # We need to allow inbound on all dynamic ports as the bridge networking might use any of these 122 | EcsInstanceSecurityGroupIngressFromGameClients: 123 | Type: AWS::EC2::SecurityGroupIngress 124 | Properties: 125 | Description: Ingress from the game clients 126 | GroupId: !Ref 'ECSInstanceSecurityGroup' 127 | IpProtocol: tcp 128 | FromPort: !Ref GameServerPortStart 129 | ToPort: !Ref GameServerPortEnd 130 | CidrIp: "0.0.0.0/0" 131 | 132 | # This is an IAM role which authorizes ECS to manage resources on your 133 | # account on your behalf, such as updating your load balancer with the 134 | # details of where your containers are, so that traffic can reach your 135 | # containers. 136 | ECSServiceRole: 137 | Type: AWS::IAM::Role 138 | Properties: 139 | AssumeRolePolicyDocument: 140 | Statement: 141 | - Effect: Allow 142 | Principal: 143 | Service: [ecs.amazonaws.com] 144 | Action: ['sts:AssumeRole'] 145 | Path: / 146 | Policies: 147 | - PolicyName: ecs-service 148 | PolicyDocument: 149 | Statement: 150 | - Effect: Allow 151 | Action: 152 | # Rules which allow ECS to attach network interfaces to instances 153 | # on your behalf in order for awsvpc networking mode to work right 154 | - 'ec2:AttachNetworkInterface' 155 | - 'ec2:CreateNetworkInterface' 156 | - 'ec2:CreateNetworkInterfacePermission' 157 | - 'ec2:DeleteNetworkInterface' 158 | - 'ec2:DeleteNetworkInterfacePermission' 159 | - 'ec2:Describe*' 160 | - 'ec2:DetachNetworkInterface' 161 | # Rules which allow ECS to update load balancers on your behalf 162 | # with the information sabout how to send traffic to your containers 163 | - 'elasticloadbalancing:DeregisterInstancesFromLoadBalancer' 164 | - 'elasticloadbalancing:DeregisterTargets' 165 | - 'elasticloadbalancing:Describe*' 166 | - 'elasticloadbalancing:RegisterInstancesWithLoadBalancer' 167 | - 'elasticloadbalancing:RegisterTargets' 168 | Resource: '*' 169 | 170 | # This is a role which is used by the ECS tasks themselves. 171 | ECSTaskExecutionRole: 172 | Type: AWS::IAM::Role 173 | Properties: 174 | AssumeRolePolicyDocument: 175 | Statement: 176 | - Effect: Allow 177 | Principal: 178 | Service: [ecs-tasks.amazonaws.com] 179 | Action: ['sts:AssumeRole'] 180 | Path: / 181 | Policies: 182 | - PolicyName: AmazonECSTaskExecutionRolePolicy 183 | PolicyDocument: 184 | Statement: 185 | - Effect: Allow 186 | Action: 187 | # Allow the ECS Tasks to download images from ECR 188 | - 'ecr:GetAuthorizationToken' 189 | - 'ecr:BatchCheckLayerAvailability' 190 | - 'ecr:GetDownloadUrlForLayer' 191 | - 'ecr:BatchGetImage' 192 | 193 | # Allow the ECS tasks to upload logs to CloudWatch 194 | - 'logs:CreateLogStream' 195 | - 'logs:PutLogEvents' 196 | Resource: '*' 197 | 198 | ### GAMESERVERGROUP RESOURCES ### 199 | 200 | # Role for the instances 201 | ECSRole: 202 | Type: AWS::IAM::Role 203 | Properties: 204 | Path: / 205 | RoleName: !Sub FleetIQ-ECSRole-${AWS::Region} 206 | AssumeRolePolicyDocument: | 207 | { 208 | "Statement": [{ 209 | "Action": "sts:AssumeRole", 210 | "Effect": "Allow", 211 | "Principal": { 212 | "Service": "ec2.amazonaws.com" 213 | } 214 | }] 215 | } 216 | ManagedPolicyArns: 217 | - !Sub arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore 218 | - !Sub arn:${AWS::Partition}:iam::aws:policy/CloudWatchAgentServerPolicy 219 | Policies: 220 | - PolicyName: ecs-service 221 | PolicyDocument: | 222 | { 223 | "Statement": [{ 224 | "Effect": "Allow", 225 | "Action": [ 226 | "ecs:CreateCluster", 227 | "ecs:DeregisterContainerInstance", 228 | "ecs:DiscoverPollEndpoint", 229 | "ecs:Poll", 230 | "ecs:RegisterContainerInstance", 231 | "ecs:StartTelemetrySession", 232 | "ecs:Submit*", 233 | "ecr:BatchCheckLayerAvailability", 234 | "ecr:BatchGetImage", 235 | "ecr:GetDownloadUrlForLayer", 236 | "ecr:GetAuthorizationToken" 237 | ], 238 | "Resource": "*" 239 | }] 240 | } 241 | ECSInstanceProfile: 242 | Type: AWS::IAM::InstanceProfile 243 | Properties: 244 | Path: / 245 | Roles: 246 | - !Ref ECSRole 247 | 248 | # Launch Template 249 | GameServerGroupLaunchTemplate: 250 | Type: AWS::EC2::LaunchTemplate 251 | Properties: 252 | LaunchTemplateData: 253 | InstanceType: !Ref InstanceType 254 | ImageId: !Ref ECSAMI 255 | IamInstanceProfile: 256 | Arn: 257 | Fn::GetAtt: 258 | - ECSInstanceProfile 259 | - Arn 260 | SecurityGroupIds: 261 | - !Ref ECSInstanceSecurityGroup 262 | UserData: 263 | "Fn::Base64": !Sub | 264 | #!/bin/bash 265 | echo ECS_CLUSTER=${ECSCluster} >> /etc/ecs/ecs.config 266 | sudo yum update -y 267 | sudo yum install -y https://s3.${AWS::Region}.amazonaws.com/amazon-ssm-${AWS::Region}/latest/linux_amd64/amazon-ssm-agent.rpm 268 | 269 | LaunchTemplateName: GameServerGroupLaunchTemplate 270 | # Game Server Group 271 | GameServerGroupRole: 272 | Type: AWS::IAM::Role 273 | Properties: 274 | AssumeRolePolicyDocument: 275 | Statement: 276 | - Effect: Allow 277 | Principal: 278 | Service: ["gamelift.amazonaws.com", "autoscaling.amazonaws.com"] 279 | Action: ['sts:AssumeRole'] 280 | Path: / 281 | ManagedPolicyArns: 282 | - !Sub arn:${AWS::Partition}:iam::aws:policy/GameLiftGameServerGroupPolicy 283 | GameServerGroupResource: 284 | Type: AWS::GameLift::GameServerGroup 285 | Properties: 286 | GameServerGroupName: "ExampleGameServerGroup" 287 | RoleArn: !GetAtt GameServerGroupRole.Arn 288 | MinSize: 2 289 | MaxSize: 3 290 | GameServerProtectionPolicy: "FULL_PROTECTION" 291 | BalancingStrategy: "SPOT_PREFERRED" 292 | LaunchTemplate: 293 | LaunchTemplateId: !Ref GameServerGroupLaunchTemplate 294 | InstanceDefinitions: 295 | - InstanceType: "c5.xlarge" 296 | - InstanceType: "m5.xlarge" 297 | AutoScalingPolicy: 298 | TargetTrackingConfiguration: 299 | TargetValue: 75 300 | VpcSubnets: 301 | - !Ref PublicSubnetOne 302 | - !Ref PublicSubnetTwo 303 | 304 | # These are the values output by the CloudFormation template. Be careful 305 | # about changing any of them, because of them are exported with specific 306 | # names so that the other task related CF templates can use them. 307 | Outputs: 308 | ClusterName: 309 | Description: The name of the ECS cluster 310 | Value: !Ref 'ECSCluster' 311 | Export: 312 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ClusterName' ] ] 313 | ECSRole: 314 | Description: The ARN of the ECS role 315 | Value: !GetAtt 'ECSRole.Arn' 316 | Export: 317 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ECSRole' ] ] 318 | ECSTaskExecutionRole: 319 | Description: The ARN of the ECS role 320 | Value: !GetAtt 'ECSTaskExecutionRole.Arn' 321 | Export: 322 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'ECSTaskExecutionRole' ] ] 323 | VPCId: 324 | Description: The ID of the VPC that this stack is deployed in 325 | Value: !Ref 'VPC' 326 | Export: 327 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'VPCId' ] ] 328 | PublicSubnetOne: 329 | Description: Public subnet one 330 | Value: !Ref 'PublicSubnetOne' 331 | Export: 332 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetOne' ] ] 333 | PublicSubnetTwo: 334 | Description: Public subnet two 335 | Value: !Ref 'PublicSubnetTwo' 336 | Export: 337 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'PublicSubnetTwo' ] ] -------------------------------------------------------------------------------- /CloudFormationResources/game-server-task-definition.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Description: A stack for deploying the task definition to be used by the scaler to start game servers 3 | 4 | Parameters: 5 | ECSResourcesStackName: 6 | Type: String 7 | Default: "fleetiq-ecs-vpc-and-ecs-resources" 8 | Description: Name of the stack for the ECS resources to import 9 | Image: 10 | Type: String 11 | Description: The url of the image to be used in the Task definition 12 | 13 | Resources: 14 | GameServerLogGroup: 15 | Type: AWS::Logs::LogGroup 16 | Properties: 17 | RetentionInDays: 7 18 | LogGroupName: "fleetiq-game-servers" 19 | 20 | GameServerTaskRole: 21 | Type: AWS::IAM::Role 22 | Properties: 23 | AssumeRolePolicyDocument: 24 | Statement: 25 | - Effect: Allow 26 | Principal: 27 | Service: [ecs-tasks.amazonaws.com] 28 | Action: ['sts:AssumeRole'] 29 | Path: / 30 | Policies: 31 | - PolicyName: AmazonGameServerECSTaskRolePolicy 32 | PolicyDocument: 33 | Statement: 34 | - Effect: Allow 35 | Action: 36 | # Allow Lambda invocation to access backend Lambda services 37 | - 'lambda:InvokeFunction' 38 | # Allow full GameLift access for FleetIQ APIs 39 | - 'gamelift:*' 40 | Resource: '*' 41 | 42 | GameServerTaskDefinition: 43 | Type: AWS::ECS::TaskDefinition 44 | Properties: 45 | RequiresCompatibilities: 46 | - "EC2" 47 | NetworkMode: bridge 48 | Cpu: 512 49 | Memory: 953 50 | TaskRoleArn: !Ref GameServerTaskRole 51 | ExecutionRoleArn: 52 | Fn::ImportValue: 53 | !Sub "${ECSResourcesStackName}:ECSTaskExecutionRole" 54 | # 1 game server per Task 55 | ContainerDefinitions: 56 | - 57 | Name: "GameServer1" 58 | Image: !Ref Image 59 | Environment: 60 | - Name: PORT 61 | Value: 1935 62 | - Name: CONTAINERNAME 63 | Value: "container1" 64 | Cpu: 512 65 | PortMappings: 66 | - 67 | ContainerPort: 1935 68 | HostPort: 0 69 | Memory: 953 70 | Essential: true 71 | LogConfiguration: 72 | LogDriver: "awslogs" 73 | Options: 74 | "awslogs-group": "fleetiq-game-servers" 75 | "awslogs-region": !Ref AWS::Region 76 | "awslogs-stream-prefix": "ecs" 77 | 78 | Outputs: 79 | TaskDefinition: 80 | Description: The task definition resource 81 | Value: !Ref GameServerTaskDefinition 82 | Export: 83 | Name: !Join [ ':', [ !Ref 'AWS::StackName', 'TaskDefinition' ] ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /LinuxServerBuild/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | RUN apt-get update && apt-get upgrade -y 3 | RUN apt-get install ca-certificates -y 4 | COPY . /app 5 | CMD /app/FleetIQExampleServer.x86_64 6 | EXPOSE 1935 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Game Server Hosting on Amazon Elastic Container Service with Amazon GameLift FleetIQ 2 | 3 | This repository contains an example solution on how to scale a fleet of game servers on Elastic Container Service and match players to game sessions using a Serverless backend. Game Sessions are managed by Amazon GameLift FleetIQ. All resources are deployed with Infrastructure as Code using CloudFormation, Serverless Application Model, Docker and bash scripts. With Amazon GameLift FleetIQ you can leverage Spot Instances with significant cost reductions compared to On-Demand Instances. 4 | 5 | Check out the [Amazon GameLift FleetIQ documentation](https://docs.aws.amazon.com/gamelift/latest/fleetiqguide/gsg-intro.html) for more details on the service. 6 | 7 | **Note**: _“The sample code; software libraries; command line tools; proofs of concept; templates; or other related technology (including any of the foregoing that are provided by our personnel) is provided to you as AWS Content under the AWS Customer Agreement, or the relevant written agreement between you and AWS (whichever applies). You should not use this AWS Content in your production accounts, or on production or other critical data. You are responsible for testing, securing, and optimizing the AWS Content, such as sample code, as appropriate for production grade use based on your specific quality control practices and standards. Deploying AWS Content may incur AWS charges for creating or using AWS chargeable resources, such as running Amazon EC2 instances or using Amazon S3 storage.”_ 8 | 9 | # Key Features 10 | * GameLift FleetIQ used to provision a Game Server Group. EC2 instances in the group will register to an ECS Cluster as workers 11 | * Game Server Tasks deployed to the ECS Cluster by a serverless scaler service to always keep all instances fully utilized 12 | * A Serverless API used for claiming game sessions from GameLift FleetIQ. Deployed with Serverless Application Model 13 | * CloudFormation used to deploy all infrastructure resources 14 | * Cognito used for player identities. Requests against the API are signed with Cognito credentials 15 | * Unity used on both server and client side. Unity server build is deployed as a Linux Docker container 16 | 17 | The client/server Unity project is a simple "game" where 2 players join the same session and move around with their 3D characters. The server receives input from the clients, simulation is run on the server side and world state is sent to the players and the characters created and removed as players join and leave. 18 | 19 | # Contents 20 | 21 | The project contains: 22 | * **A Unity Project** that will be used for both Client and Server builds (`UnityProject`). Server build will be further built into a Docker container image and deployed to Elastic Container Registry as part of the deployment automation 23 | * **A Backend Project** created with Serverless Application Model (SAM) to create an API backend for game session requests and a continuously running scaler function for creating game server Tasks (`BackendServices`) 24 | * **Infrastructure deployment automation** leveraging AWS CloudFormation to deploy all infrastructure resources (`CloudFormationResources`) 25 | * **A build folder for the server build** which includes a Dockerfile for building the docker image (`LinuxServerBuild`) 26 | 27 | # Architecture Diagram 28 | 29 | ![Architecture Diagram](Architecture_small.png "Architecture Diagram") 30 | 31 | # Preliminary Setup 32 | 33 | 1. **Install and configure the AWS CLI** 34 | * Follow these instructions to install: [AWS CLI Installation](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html) 35 | * Configure the CLI: [AWS CLI Configuration](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html#cli-quick-configuration) 36 | 2. **Install Unity3D 2019** 37 | * Use the instructions on Unity website for installing: [Unity Hub Installation](https://docs.unity3d.com/Manual/GettingStartedInstallingHub.html) 38 | * Install support for Linux builds for your Unity installation. You will need this for the server builds. 39 | 3. **Install SAM CLI** 40 | * Follow these instructions to install the Serverless Application Model (SAM) CLI: [SAM CLI Installation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) 41 | 3. **Install Docker** 42 | * Follow the instructions on the Docker website to install it: [Get Docker](https://docs.docker.com/get-docker/) 43 | 4. **Install external dependencies** 44 | 1. Download the AWS .NET SDK [here](https://sdk-for-net.amazonwebservices.com/latest/v3/aws-sdk-netstandard2.0.zip) and copy the following files to `UnityProject/Assets/Dependencies/`: `AWSSDK.CognitoIdentity.dll`, `AWSSDK.CognitoIdentityProvider.dll`, `AWSSDK.Core.dll`, `AWSSDK.GameLift.dll`, `AWSSDK.SecurityToken.dll`, `Microsoft.Bcl.AsyncInterfaces.dll`, `System.Runtime.CompilerServices.Unsafe.dll`, `System.Threading.Tasks.Extensions.dll`. 45 | 2. Download the [Signature Calculation Example from AWS Documentation](https://docs.aws.amazon.com/AmazonS3/latest/API/samples/AmazonS3SigV4_Samples_CSharp.zip): **Copy the folders** `Signers` and `Util` to `UnityProject/Assets/Dependencies/` folder. We will use these to sign the requests against API Gateway with Cognito credentials. You could implement a similar SigV4 signing process yourself. After this you should not see any errors in your Unity console. 46 | 5. **Select deployment Region** 47 | * The solution can be deployed to any Region supporting GameLift, ECS, SQS, Lambda, API Gateway and Cognito. See [The Regional Table](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/) to validate your selected Region supports these services. You will need to configure the Region during the deployment. 48 | 49 | # Deployment With Bash Scripts 50 | 51 | 1. **Set up Configuration Variables** 52 | * Open `configuration.sh` in your favourite text editor 53 | * Set the `region` variable to your desired region 54 | * Set the `accountid` to your AWS Account ID (12 digits without dashes) 55 | * Set the `deploymentbucketname` to a **globally unique** name for the code deployment bucket 56 | 2. **Deploy VPC, ECS and FleetIQ Game Server Group Resources** 57 | * Go to `CloudFormationResources` in your terminal 58 | * Run `./deploy-vpc-ecs-resources.sh` to deploy a Virtual Private Cloud (VPC), ECS Cluster and the FleetIQ Game Server Group to host your game server containers. 59 | 3. **Set the RegionEndpoint to Server.cs** 60 | * Open `UnityProject/Assets/Scripts/Server/Server.cs` in your favourite editor 61 | * Set the `RegionEndpoint regionEndpoint` to your selected Region 62 | 4. **Build the server** 63 | * Open Unity Hub, add the UnityProject and open it (Unity 2019.2.16 or higher recommended) 64 | * In Unity go to "File -> Build Settings" 65 | * Go to "Player Settings" and find the Scripting Define Symbols ("Player settings" -> "Player" -> "Other Settings" -> "Scripting Define Symbol") 66 | * Replace the the Scripting Define Symbol with `SERVER`. Remember to press Enter after changing the value. C# scripts will use this directive to include server code and exclude client code 67 | * Close Player Settings and return to Build Settings 68 | * Switch the target platform to `Linux`. If you don't have it available, you need to install Linux platform support in Unity Hub. 69 | * Check the box `Server Build` 70 | * Build the project to the `LinuxServerBuild` folder (Click "Build" and in new window choose "LinuxServerBuild" folder, enter "FleetIQExampleServer" in "Save as" field and click "Save"). **NOTE**: It's important to give the exact name `FleetIQExampleServer` for the build as the Dockerfile uses this. 71 | 5. **Build Docker Image and Deploy Task Configuration** 72 | * Make sure you have Docker installed and running 73 | * Run `CloudFormationResources/deploy-game-server-and-update-task-definition.sh` to build the docker image, create an ECR repository and upload the image and deploy the Task Definition 74 | 6. **Deploy the Backend Services with SAM** 75 | * Make sure you have the SAM CLI installed 76 | * Run the `BackendServices/deploy.sh` script to deploy the backend Services. 77 | * You should see the Scaler function starting quickly after the Stack is deployed to run every minute. It will populate the ECS Instances with as many ECS Tasks as possible, each hosting a single game server container that registers to FleetIQ 78 | 7. **Set the API endpoint to the Unity Project** 79 | * Set the value of `static string apiEndpoint` in `UnityProject/Assets/Scripts/Client/MatchmakingClient.cs` to the endpoint created by the backend deployment 80 | * You can find this endpoint from the `fleetiq-ecs-game-servers-backend` Stack Outputs in CloudFormation or from the API Gateway console (make sure to have the `/Prod/` in the url) 81 | 8. **Deploy Cognito Resources** 82 | * Run `CloudFormationResources/deploy-cognito-resources.sh` to deploy the Amazon Cognito resources 83 | 9. **Set the Cognito Identity Pool configuration** 84 | * Set the value of `static string identityPoolID` in `UnityProject/Assets/Scripts/Client/MatchmakingClient.cs` to the identity pool created by the Cognito Resources deployment. You can find the ARN of the Identity Pool in the CloudFormation stack `fleetiq-ecs-game-servers-cognito`, in IAM console or as output of Step 8. 85 | * Set the value of `public static string regionString` and `public static Amazon.RegionEndpoint region` to the values of your selected region 86 | 10. **Build and run two clients** 87 | * Set the the Scripting Define Symbol `CLIENT` in the *Player Settings* in the Unity Project (File -> "Build Settings" -> "Player settings" → "Player" → "Other Settings" → "Scripting Define Symbol" → Replace completely with "CLIENT") 88 | * Open the scene "GameWorld" in Scenes/GameWorld 89 | * Open Build Settings (File -> Build Settings) in Unity and set target platform to `Mac OSX` (or whatever the platform you are using) and **uncheck** the box `Server Build` 90 | * Build the client to any folder (Click "Build", select your folder and click "Save") 91 | * You can run two clients by running one in the Unity Editor and one with the created build. This way the clients will get different Cognito identities. If you run multiple copies of the build, they will have the same identity. In this solution it doesn't matter but if your backend starts using the identity for player data, then it might. 92 | * You should see the clients connected to the same game session and see the movements synchronized between clients. 93 | 94 | ## Replacing the game servers 95 | 96 | The example doesn't contain a CI/CD pipeline to automate the replacement of game server Tasks. This is something you can automate to your needs using your preferred CI/CD tooling. 97 | 98 | The process for replacing game servers: 99 | 100 | 1. Modify the server code and build a new Linux server build 101 | 2. Build the container image, push it and update the Task Definiton by running `CloudFormationResources/deploy-game-server-and-update-task-definition.sh` 102 | 3. Terminate the existing game server Tasks in the ECS Console by selecting the Cluster, selecting the "Tasks" and clicking "Stop all" 103 | 4. The scaler function will now replace the Tasks with the latest Task definition and container image. 104 | 105 | **NOTE**: As the game servers terminate unexpectly, they will actually be listed within the Game Server Group for some minutes before they are cleaned up. Deregistering all existing game servers should be done separately with the AWS CLI to avoid this. 106 | 107 | # Implementation Overview 108 | 109 | ## Infrastructure 110 | 111 | The AWS Infrastructure for the solution consists of 112 | * a **VPC** with public subnets across two Availability Zones to host the ECS Instances on top of which we will deploy the ECS game server Tasks (`CloudFormationResources/ecs-resources.yaml`) 113 | * an **ECS Cluster** that is used to host the ECS Tasks (`CloudFormationResources/ecs-resources.yaml`) 114 | * a **GameLift FleetIQ Game Server Group** that maps to an Auto Scaling Group of instances that register to the ECS Cluster and use the ECS AMI. This Game Server Group will scale based on the percentage of available game servers using Target Tracking. (`CloudFormationResources/ecs-resources.yaml`) 115 | * a **Cognito Identity Pool** that is used to store the player identities (`CloudFormationResources/cognito.yaml`) 116 | * an **ECS Task Definition** that defines the Task to be run in public subnets of the VPC using the game server image uploaded to ECR. The Tasks use bridge-networking and HostPort 0 to get a dynamic port at launch. The Security Group of the instances allow access to the dynamic port range from the internet. (`CloudFormationResources/game-server-task-definition.yaml`) 117 | 118 | ### Game Server Group 119 | 120 | The Amazon GameLift FleetIQ Game Server Group is created using a Launch Template. The Launch Template uses the ECS AMI and User Data to register the instances to the ECS Cluster. This way the instances are available for Tasks once they are online. The Game Server Group is scaled based on the availability of game servers using Target Tracking. The default value is 75% utilized game servers as a target. Once new EC2 instances are launched, they will be populated by game server Tasks by the scaler Lambda function. 121 | 122 | See `CloudFormationResources/ecs-resources.yaml` to modify the minimum (default 2) and maximum (default 3) of instances in the Auto Scaling Group as well as the Target Tracking (default 75% utilized) and Instance type configuration (default c5.xlarge and m5.xlarge). We use "SPOT_PREFERRED" to prefer Spot instances and fail over to On-Demand Instances in case Spot Isntances are not viable for hosting game sessions. 123 | 124 | ## Serverless Backend Services 125 | 126 | The Serverless Backend Services are deployed with SAM (Serverless Application Model). The backend consists of 127 | * a **scaler function** (`BackendServices/functions/scaler.py`) that will run every minute using a CloudWatch Events scheduled event. The function runs around 58 seconds and checks every 1 second if new game servers should be started based on the available resources (CPU and memory) across all the Container Instances in the Game Server Group. It uses the latest Task Definition to start new ECS Tasks in the ECS Cluster. Tasks will always utilize the resources available as fully as possible. 128 | * a **function to request a games session** (`BackendServices/functions/requestgamesession.py`) that is called by the game client through API Gateway to request a new game session. The function will check if there is a game session in the SQS waiting for another player. If not, it will claim a game server through the FleetIQ API and put a message to the queue with session information for the next player. The function returns the IP and port to connect to. **NOTE:** This is just a simple example and not a full matchmaking solution. For example, if the first player drops from the session before another one gets it from SQS, the session information is still provided to the second client and the connection will fail. The client has an automatic retry to mitigate this 129 | * an **API Gateway** that uses AWS_IAM authentication to authenticate game clients with their Cognito credentials 130 | 131 | The backend service infrastructure is defined in `BackendServices/template.yaml` and is deployed with SAM using `BackendServices/deploy.sh`. 132 | 133 | ## Game Server 134 | 135 | The game server is developed with Unity in the same project (`UnityProject`) as the client. Using the Scripting Define Symbol `SERVER` will define it as a server build. The server is built as part of deploying the Task definition by creating a Docker image of the server locally (using the latest build in LinuxServerBuild) and uploading that to an ECR repository. See `LinuxServerBuild/Dockerfile` for the definition of the Docker image. It uses an Ubuntu base image but you could replace this with more lightweight options as well. 136 | 137 | Key Server functionality: 138 | * Starts a TcpListener and starts accepting clients 139 | * Accepts clients up the the maximum amount of players and keeps track of connected clients 140 | * Takes player input in and shares simulation state to clients (position updates etc.) over TCP 141 | * Terminates the process once the game session is done (players disconnected) 142 | * Registers, updates health and deregisters from the FleetIQ Game Server Group 143 | 144 | **Key code files:** 145 | * `Scripts/Server/Server.cs`: This class contains all the server logic and a NetworkServer class that manages the TCPListener and clients. It also contains all the logic to communicate with GameLift FleetIQ using the AWS .NET SDK. 146 | * `Scripts/NetworkingShared/MessageClasses.cs`: Defines the SimpleMessage class that is serialized and deserialized by the server and clients to transmit messages 147 | * `Scripts/NetworkingShared/NetworkProtocol.cs`: Defines the sending and receiving of messages with TCP Clients using BinaryFormatter and the SimpleMessage message class. Used by both server and client 148 | 149 | ## Game Client 150 | 151 | The game client is developed with Unity in the same project (`UnityProject`) as the server. Using the Scripting Define Symbol `CLIENT` will define it as a client build. 152 | 153 | The client will 154 | * Request an identity from Cognito Identity Pool by using the AWS .NET SDK (`Scripts/Client/Client.cs`) 155 | * Once the credentials are received, sign a request to API Gateway to request a game session (`Scripts/Client/MatchmakingClient.cs`) 156 | * Once the response to the game session has been received, connect to the game server with the given public IP and port 157 | * Start sending and receiving updates using a TCPClient 158 | 159 | # Cleaning Up Resources 160 | 161 | To clean up all the resources you need to make sure that the correct region is defined in `configuration.sh` and then run `cleanup_all_resources.sh`. 162 | 163 | **NOTE**: There is one manual step in the clean up where you need to stop all Tasks running in the ECS Cluster. 164 | 165 | # License 166 | 167 | This example is licensed under the MIT-0 License. See LICENSE file. 168 | -------------------------------------------------------------------------------- /UnityProject/.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Mm]emoryCaptures/ 12 | 13 | # Never ignore Asset meta data 14 | !/[Aa]ssets/**/*.meta 15 | 16 | # Uncomment this line if you wish to ignore the asset store tools plugin 17 | # /[Aa]ssets/AssetStoreTools* 18 | 19 | # Autogenerated Jetbrains Rider plugin 20 | [Aa]ssets/Plugins/Editor/JetBrains* 21 | 22 | # Visual Studio cache directory 23 | .vs/ 24 | 25 | # Gradle cache directory 26 | .gradle/ 27 | 28 | # Autogenerated VS/MD/Consulo solution and project files 29 | ExportedObj/ 30 | .consulo/ 31 | *.csproj 32 | *.unityproj 33 | *.sln 34 | *.suo 35 | *.tmp 36 | *.user 37 | *.userprefs 38 | *.pidb 39 | *.booproj 40 | *.svd 41 | *.pdb 42 | *.mdb 43 | *.opendb 44 | *.VC.db 45 | 46 | # Unity3D generated meta files 47 | *.pidb.meta 48 | *.pdb.meta 49 | *.mdb.meta 50 | 51 | # Unity3D generated file on crash reports 52 | sysinfo.txt 53 | 54 | # Builds 55 | *.apk 56 | *.unitypackage 57 | 58 | # Crashlytics generated file 59 | crashlytics-build.properties 60 | 61 | .DS_Store -------------------------------------------------------------------------------- /UnityProject/Assets/Dependencies.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 778e4cb97dfd84048985c67d8b636ae7 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /UnityProject/Assets/Dependencies/ADD_DEPENDENCIES_HERE_CHECK_README_PRELIMINARY_SETUP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/amazon-gamelift-fleetiq-with-amazon-ecs/70eb2adb79cac2c441943a9f4812c73238f6d296/UnityProject/Assets/Dependencies/ADD_DEPENDENCIES_HERE_CHECK_README_PRELIMINARY_SETUP -------------------------------------------------------------------------------- /UnityProject/Assets/Dependencies/ADD_DEPENDENCIES_HERE_CHECK_README_PRELIMINARY_SETUP.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 01e58dbfe9d9c4dd58046a52f21acd26 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /UnityProject/Assets/Plugins.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5554e9f2c183c8546a59dfa6f270159e 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /UnityProject/Assets/PrefabsAndMaterials.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 42a5e7ffdc17948faab9e590edcde2c9 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /UnityProject/Assets/PrefabsAndMaterials/EnemyCharacter.prefab: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!1 &4655882290843831597 4 | GameObject: 5 | m_ObjectHideFlags: 0 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | serializedVersion: 6 10 | m_Component: 11 | - component: {fileID: 4655882290843831594} 12 | - component: {fileID: 4655882290843831593} 13 | - component: {fileID: 4655882290843831592} 14 | - component: {fileID: 4655882290843831595} 15 | m_Layer: 0 16 | m_Name: Cylinder 17 | m_TagString: Untagged 18 | m_Icon: {fileID: 0} 19 | m_NavMeshLayer: 0 20 | m_StaticEditorFlags: 0 21 | m_IsActive: 1 22 | --- !u!4 &4655882290843831594 23 | Transform: 24 | m_ObjectHideFlags: 0 25 | m_CorrespondingSourceObject: {fileID: 0} 26 | m_PrefabInstance: {fileID: 0} 27 | m_PrefabAsset: {fileID: 0} 28 | m_GameObject: {fileID: 4655882290843831597} 29 | m_LocalRotation: {x: 0, y: 0, z: 0.7071068, w: 0.7071068} 30 | m_LocalPosition: {x: -0, y: 0.22, z: 0} 31 | m_LocalScale: {x: 0.3, y: 1, z: 0.3} 32 | m_Children: [] 33 | m_Father: {fileID: 4655882291869799818} 34 | m_RootOrder: 0 35 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 90} 36 | --- !u!33 &4655882290843831593 37 | MeshFilter: 38 | m_ObjectHideFlags: 0 39 | m_CorrespondingSourceObject: {fileID: 0} 40 | m_PrefabInstance: {fileID: 0} 41 | m_PrefabAsset: {fileID: 0} 42 | m_GameObject: {fileID: 4655882290843831597} 43 | m_Mesh: {fileID: 10206, guid: 0000000000000000e000000000000000, type: 0} 44 | --- !u!23 &4655882290843831592 45 | MeshRenderer: 46 | m_ObjectHideFlags: 0 47 | m_CorrespondingSourceObject: {fileID: 0} 48 | m_PrefabInstance: {fileID: 0} 49 | m_PrefabAsset: {fileID: 0} 50 | m_GameObject: {fileID: 4655882290843831597} 51 | m_Enabled: 1 52 | m_CastShadows: 1 53 | m_ReceiveShadows: 1 54 | m_DynamicOccludee: 1 55 | m_MotionVectors: 1 56 | m_LightProbeUsage: 1 57 | m_ReflectionProbeUsage: 1 58 | m_RenderingLayerMask: 1 59 | m_RendererPriority: 0 60 | m_Materials: 61 | - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} 62 | m_StaticBatchInfo: 63 | firstSubMesh: 0 64 | subMeshCount: 0 65 | m_StaticBatchRoot: {fileID: 0} 66 | m_ProbeAnchor: {fileID: 0} 67 | m_LightProbeVolumeOverride: {fileID: 0} 68 | m_ScaleInLightmap: 1 69 | m_ReceiveGI: 1 70 | m_PreserveUVs: 0 71 | m_IgnoreNormalsForChartDetection: 0 72 | m_ImportantGI: 0 73 | m_StitchLightmapSeams: 1 74 | m_SelectedEditorRenderState: 3 75 | m_MinimumChartSize: 4 76 | m_AutoUVMaxDistance: 0.5 77 | m_AutoUVMaxAngle: 89 78 | m_LightmapParameters: {fileID: 0} 79 | m_SortingLayerID: 0 80 | m_SortingLayer: 0 81 | m_SortingOrder: 0 82 | --- !u!136 &4655882290843831595 83 | CapsuleCollider: 84 | m_ObjectHideFlags: 0 85 | m_CorrespondingSourceObject: {fileID: 0} 86 | m_PrefabInstance: {fileID: 0} 87 | m_PrefabAsset: {fileID: 0} 88 | m_GameObject: {fileID: 4655882290843831597} 89 | m_Material: {fileID: 0} 90 | m_IsTrigger: 0 91 | m_Enabled: 1 92 | m_Radius: 0.5000001 93 | m_Height: 2 94 | m_Direction: 1 95 | m_Center: {x: 0.000000059604645, y: 0, z: -0.00000008940697} 96 | --- !u!1 &4655882291869799809 97 | GameObject: 98 | m_ObjectHideFlags: 0 99 | m_CorrespondingSourceObject: {fileID: 0} 100 | m_PrefabInstance: {fileID: 0} 101 | m_PrefabAsset: {fileID: 0} 102 | serializedVersion: 6 103 | m_Component: 104 | - component: {fileID: 4655882291869799818} 105 | - component: {fileID: 4655882291869799821} 106 | - component: {fileID: 4655882291869799820} 107 | - component: {fileID: 4655882291869799823} 108 | - component: {fileID: 4655882291869799822} 109 | m_Layer: 0 110 | m_Name: EnemyCharacter 111 | m_TagString: Untagged 112 | m_Icon: {fileID: 0} 113 | m_NavMeshLayer: 0 114 | m_StaticEditorFlags: 0 115 | m_IsActive: 1 116 | --- !u!4 &4655882291869799818 117 | Transform: 118 | m_ObjectHideFlags: 0 119 | m_CorrespondingSourceObject: {fileID: 0} 120 | m_PrefabInstance: {fileID: 0} 121 | m_PrefabAsset: {fileID: 0} 122 | m_GameObject: {fileID: 4655882291869799809} 123 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 124 | m_LocalPosition: {x: 0, y: 1.05, z: 0} 125 | m_LocalScale: {x: 1, y: 1, z: 1} 126 | m_Children: 127 | - {fileID: 4655882290843831594} 128 | m_Father: {fileID: 0} 129 | m_RootOrder: 0 130 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 131 | --- !u!33 &4655882291869799821 132 | MeshFilter: 133 | m_ObjectHideFlags: 0 134 | m_CorrespondingSourceObject: {fileID: 0} 135 | m_PrefabInstance: {fileID: 0} 136 | m_PrefabAsset: {fileID: 0} 137 | m_GameObject: {fileID: 4655882291869799809} 138 | m_Mesh: {fileID: 10208, guid: 0000000000000000e000000000000000, type: 0} 139 | --- !u!23 &4655882291869799820 140 | MeshRenderer: 141 | m_ObjectHideFlags: 0 142 | m_CorrespondingSourceObject: {fileID: 0} 143 | m_PrefabInstance: {fileID: 0} 144 | m_PrefabAsset: {fileID: 0} 145 | m_GameObject: {fileID: 4655882291869799809} 146 | m_Enabled: 1 147 | m_CastShadows: 1 148 | m_ReceiveShadows: 1 149 | m_DynamicOccludee: 1 150 | m_MotionVectors: 1 151 | m_LightProbeUsage: 1 152 | m_ReflectionProbeUsage: 1 153 | m_RenderingLayerMask: 1 154 | m_RendererPriority: 0 155 | m_Materials: 156 | - {fileID: 2100000, guid: fe0bb3c23481145ea90674d4fdf47ec3, type: 2} 157 | m_StaticBatchInfo: 158 | firstSubMesh: 0 159 | subMeshCount: 0 160 | m_StaticBatchRoot: {fileID: 0} 161 | m_ProbeAnchor: {fileID: 0} 162 | m_LightProbeVolumeOverride: {fileID: 0} 163 | m_ScaleInLightmap: 1 164 | m_ReceiveGI: 1 165 | m_PreserveUVs: 0 166 | m_IgnoreNormalsForChartDetection: 0 167 | m_ImportantGI: 0 168 | m_StitchLightmapSeams: 1 169 | m_SelectedEditorRenderState: 3 170 | m_MinimumChartSize: 4 171 | m_AutoUVMaxDistance: 0.5 172 | m_AutoUVMaxAngle: 89 173 | m_LightmapParameters: {fileID: 0} 174 | m_SortingLayerID: 0 175 | m_SortingLayer: 0 176 | m_SortingOrder: 0 177 | --- !u!136 &4655882291869799823 178 | CapsuleCollider: 179 | m_ObjectHideFlags: 0 180 | m_CorrespondingSourceObject: {fileID: 0} 181 | m_PrefabInstance: {fileID: 0} 182 | m_PrefabAsset: {fileID: 0} 183 | m_GameObject: {fileID: 4655882291869799809} 184 | m_Material: {fileID: 0} 185 | m_IsTrigger: 0 186 | m_Enabled: 1 187 | m_Radius: 0.5 188 | m_Height: 2 189 | m_Direction: 1 190 | m_Center: {x: 0, y: 0, z: 0} 191 | --- !u!143 &4655882291869799822 192 | CharacterController: 193 | m_ObjectHideFlags: 0 194 | m_CorrespondingSourceObject: {fileID: 0} 195 | m_PrefabInstance: {fileID: 0} 196 | m_PrefabAsset: {fileID: 0} 197 | m_GameObject: {fileID: 4655882291869799809} 198 | m_Material: {fileID: 0} 199 | m_IsTrigger: 0 200 | m_Enabled: 1 201 | serializedVersion: 2 202 | m_Height: 2 203 | m_Radius: 0.5 204 | m_SlopeLimit: 45 205 | m_StepOffset: 0.3 206 | m_SkinWidth: 0.08 207 | m_MinMoveDistance: 0.001 208 | m_Center: {x: 0, y: 0, z: 0} 209 | -------------------------------------------------------------------------------- /UnityProject/Assets/PrefabsAndMaterials/EnemyCharacter.prefab.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bc28c4239f24a46edb77a2d5dbc3a88d 3 | PrefabImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /UnityProject/Assets/PrefabsAndMaterials/EnemyMaterial.mat: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!21 &2100000 4 | Material: 5 | serializedVersion: 6 6 | m_ObjectHideFlags: 0 7 | m_CorrespondingSourceObject: {fileID: 0} 8 | m_PrefabInstance: {fileID: 0} 9 | m_PrefabAsset: {fileID: 0} 10 | m_Name: EnemyMaterial 11 | m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} 12 | m_ShaderKeywords: 13 | m_LightmapFlags: 4 14 | m_EnableInstancingVariants: 0 15 | m_DoubleSidedGI: 0 16 | m_CustomRenderQueue: -1 17 | stringTagMap: {} 18 | disabledShaderPasses: [] 19 | m_SavedProperties: 20 | serializedVersion: 3 21 | m_TexEnvs: 22 | - _BumpMap: 23 | m_Texture: {fileID: 0} 24 | m_Scale: {x: 1, y: 1} 25 | m_Offset: {x: 0, y: 0} 26 | - _DetailAlbedoMap: 27 | m_Texture: {fileID: 0} 28 | m_Scale: {x: 1, y: 1} 29 | m_Offset: {x: 0, y: 0} 30 | - _DetailMask: 31 | m_Texture: {fileID: 0} 32 | m_Scale: {x: 1, y: 1} 33 | m_Offset: {x: 0, y: 0} 34 | - _DetailNormalMap: 35 | m_Texture: {fileID: 0} 36 | m_Scale: {x: 1, y: 1} 37 | m_Offset: {x: 0, y: 0} 38 | - _EmissionMap: 39 | m_Texture: {fileID: 0} 40 | m_Scale: {x: 1, y: 1} 41 | m_Offset: {x: 0, y: 0} 42 | - _MainTex: 43 | m_Texture: {fileID: 0} 44 | m_Scale: {x: 1, y: 1} 45 | m_Offset: {x: 0, y: 0} 46 | - _MetallicGlossMap: 47 | m_Texture: {fileID: 0} 48 | m_Scale: {x: 1, y: 1} 49 | m_Offset: {x: 0, y: 0} 50 | - _OcclusionMap: 51 | m_Texture: {fileID: 0} 52 | m_Scale: {x: 1, y: 1} 53 | m_Offset: {x: 0, y: 0} 54 | - _ParallaxMap: 55 | m_Texture: {fileID: 0} 56 | m_Scale: {x: 1, y: 1} 57 | m_Offset: {x: 0, y: 0} 58 | m_Floats: 59 | - _BumpScale: 1 60 | - _Cutoff: 0.5 61 | - _DetailNormalMapScale: 1 62 | - _DstBlend: 0 63 | - _GlossMapScale: 1 64 | - _Glossiness: 0.5 65 | - _GlossyReflections: 1 66 | - _Metallic: 0 67 | - _Mode: 0 68 | - _OcclusionStrength: 1 69 | - _Parallax: 0.02 70 | - _SmoothnessTextureChannel: 0 71 | - _SpecularHighlights: 1 72 | - _SrcBlend: 1 73 | - _UVSec: 0 74 | - _ZWrite: 1 75 | m_Colors: 76 | - _Color: {r: 1, g: 0.06392694, b: 0, a: 1} 77 | - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} 78 | -------------------------------------------------------------------------------- /UnityProject/Assets/PrefabsAndMaterials/EnemyMaterial.mat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fe0bb3c23481145ea90674d4fdf47ec3 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 2100000 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /UnityProject/Assets/PrefabsAndMaterials/GroundMaterial.mat: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!21 &2100000 4 | Material: 5 | serializedVersion: 6 6 | m_ObjectHideFlags: 0 7 | m_CorrespondingSourceObject: {fileID: 0} 8 | m_PrefabInstance: {fileID: 0} 9 | m_PrefabAsset: {fileID: 0} 10 | m_Name: GroundMaterial 11 | m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} 12 | m_ShaderKeywords: _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A 13 | m_LightmapFlags: 4 14 | m_EnableInstancingVariants: 0 15 | m_DoubleSidedGI: 0 16 | m_CustomRenderQueue: -1 17 | stringTagMap: {} 18 | disabledShaderPasses: [] 19 | m_SavedProperties: 20 | serializedVersion: 3 21 | m_TexEnvs: 22 | - _BumpMap: 23 | m_Texture: {fileID: 0} 24 | m_Scale: {x: 1, y: 1} 25 | m_Offset: {x: 0, y: 0} 26 | - _DetailAlbedoMap: 27 | m_Texture: {fileID: 0} 28 | m_Scale: {x: 1, y: 1} 29 | m_Offset: {x: 0, y: 0} 30 | - _DetailMask: 31 | m_Texture: {fileID: 0} 32 | m_Scale: {x: 1, y: 1} 33 | m_Offset: {x: 0, y: 0} 34 | - _DetailNormalMap: 35 | m_Texture: {fileID: 0} 36 | m_Scale: {x: 1, y: 1} 37 | m_Offset: {x: 0, y: 0} 38 | - _EmissionMap: 39 | m_Texture: {fileID: 0} 40 | m_Scale: {x: 1, y: 1} 41 | m_Offset: {x: 0, y: 0} 42 | - _MainTex: 43 | m_Texture: {fileID: 0} 44 | m_Scale: {x: 1, y: 1} 45 | m_Offset: {x: 0, y: 0} 46 | - _MetallicGlossMap: 47 | m_Texture: {fileID: 0} 48 | m_Scale: {x: 1, y: 1} 49 | m_Offset: {x: 0, y: 0} 50 | - _OcclusionMap: 51 | m_Texture: {fileID: 0} 52 | m_Scale: {x: 1, y: 1} 53 | m_Offset: {x: 0, y: 0} 54 | - _ParallaxMap: 55 | m_Texture: {fileID: 0} 56 | m_Scale: {x: 1, y: 1} 57 | m_Offset: {x: 0, y: 0} 58 | m_Floats: 59 | - _BumpScale: 1 60 | - _Cutoff: 0.5 61 | - _DetailNormalMapScale: 1 62 | - _DstBlend: 0 63 | - _GlossMapScale: 0.226 64 | - _Glossiness: 0.46 65 | - _GlossyReflections: 1 66 | - _Metallic: 0.411 67 | - _Mode: 0 68 | - _OcclusionStrength: 1 69 | - _Parallax: 0.02 70 | - _SmoothnessTextureChannel: 1 71 | - _SpecularHighlights: 1 72 | - _SrcBlend: 1 73 | - _UVSec: 0 74 | - _ZWrite: 1 75 | m_Colors: 76 | - _Color: {r: 0.509434, g: 0.2929081, b: 0.16100036, a: 1} 77 | - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} 78 | -------------------------------------------------------------------------------- /UnityProject/Assets/PrefabsAndMaterials/GroundMaterial.mat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dad83f4aac05548f182cfe907cc8fc95 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 2100000 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /UnityProject/Assets/PrefabsAndMaterials/PlayerMaterial.mat: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!21 &2100000 4 | Material: 5 | serializedVersion: 6 6 | m_ObjectHideFlags: 0 7 | m_CorrespondingSourceObject: {fileID: 0} 8 | m_PrefabInstance: {fileID: 0} 9 | m_PrefabAsset: {fileID: 0} 10 | m_Name: PlayerMaterial 11 | m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} 12 | m_ShaderKeywords: 13 | m_LightmapFlags: 4 14 | m_EnableInstancingVariants: 0 15 | m_DoubleSidedGI: 0 16 | m_CustomRenderQueue: -1 17 | stringTagMap: {} 18 | disabledShaderPasses: [] 19 | m_SavedProperties: 20 | serializedVersion: 3 21 | m_TexEnvs: 22 | - _BumpMap: 23 | m_Texture: {fileID: 0} 24 | m_Scale: {x: 1, y: 1} 25 | m_Offset: {x: 0, y: 0} 26 | - _DetailAlbedoMap: 27 | m_Texture: {fileID: 0} 28 | m_Scale: {x: 1, y: 1} 29 | m_Offset: {x: 0, y: 0} 30 | - _DetailMask: 31 | m_Texture: {fileID: 0} 32 | m_Scale: {x: 1, y: 1} 33 | m_Offset: {x: 0, y: 0} 34 | - _DetailNormalMap: 35 | m_Texture: {fileID: 0} 36 | m_Scale: {x: 1, y: 1} 37 | m_Offset: {x: 0, y: 0} 38 | - _EmissionMap: 39 | m_Texture: {fileID: 0} 40 | m_Scale: {x: 1, y: 1} 41 | m_Offset: {x: 0, y: 0} 42 | - _MainTex: 43 | m_Texture: {fileID: 0} 44 | m_Scale: {x: 1, y: 1} 45 | m_Offset: {x: 0, y: 0} 46 | - _MetallicGlossMap: 47 | m_Texture: {fileID: 0} 48 | m_Scale: {x: 1, y: 1} 49 | m_Offset: {x: 0, y: 0} 50 | - _OcclusionMap: 51 | m_Texture: {fileID: 0} 52 | m_Scale: {x: 1, y: 1} 53 | m_Offset: {x: 0, y: 0} 54 | - _ParallaxMap: 55 | m_Texture: {fileID: 0} 56 | m_Scale: {x: 1, y: 1} 57 | m_Offset: {x: 0, y: 0} 58 | m_Floats: 59 | - _BumpScale: 1 60 | - _Cutoff: 0.5 61 | - _DetailNormalMapScale: 1 62 | - _DstBlend: 0 63 | - _GlossMapScale: 1 64 | - _Glossiness: 0.5 65 | - _GlossyReflections: 1 66 | - _Metallic: 0.856 67 | - _Mode: 0 68 | - _OcclusionStrength: 1 69 | - _Parallax: 0.02 70 | - _SmoothnessTextureChannel: 0 71 | - _SpecularHighlights: 1 72 | - _SrcBlend: 1 73 | - _UVSec: 0 74 | - _ZWrite: 1 75 | m_Colors: 76 | - _Color: {r: 0.1782218, g: 0.8396226, b: 0.2712677, a: 1} 77 | - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} 78 | -------------------------------------------------------------------------------- /UnityProject/Assets/PrefabsAndMaterials/PlayerMaterial.mat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6ad9e3a7b438f4329bf661e47ec1527a 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 2100000 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /UnityProject/Assets/PrefabsAndMaterials/SimpleCharacter.prefab: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!1 &4655882290843831597 4 | GameObject: 5 | m_ObjectHideFlags: 0 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | serializedVersion: 6 10 | m_Component: 11 | - component: {fileID: 4655882290843831594} 12 | - component: {fileID: 4655882290843831593} 13 | - component: {fileID: 4655882290843831592} 14 | - component: {fileID: 4655882290843831595} 15 | m_Layer: 0 16 | m_Name: Cylinder 17 | m_TagString: Untagged 18 | m_Icon: {fileID: 0} 19 | m_NavMeshLayer: 0 20 | m_StaticEditorFlags: 0 21 | m_IsActive: 1 22 | --- !u!4 &4655882290843831594 23 | Transform: 24 | m_ObjectHideFlags: 0 25 | m_CorrespondingSourceObject: {fileID: 0} 26 | m_PrefabInstance: {fileID: 0} 27 | m_PrefabAsset: {fileID: 0} 28 | m_GameObject: {fileID: 4655882290843831597} 29 | m_LocalRotation: {x: 0, y: 0, z: 0.7071068, w: 0.7071068} 30 | m_LocalPosition: {x: -0, y: 0.22, z: 0} 31 | m_LocalScale: {x: 0.3, y: 1, z: 0.3} 32 | m_Children: [] 33 | m_Father: {fileID: 4655882291869799818} 34 | m_RootOrder: 0 35 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 90} 36 | --- !u!33 &4655882290843831593 37 | MeshFilter: 38 | m_ObjectHideFlags: 0 39 | m_CorrespondingSourceObject: {fileID: 0} 40 | m_PrefabInstance: {fileID: 0} 41 | m_PrefabAsset: {fileID: 0} 42 | m_GameObject: {fileID: 4655882290843831597} 43 | m_Mesh: {fileID: 10206, guid: 0000000000000000e000000000000000, type: 0} 44 | --- !u!23 &4655882290843831592 45 | MeshRenderer: 46 | m_ObjectHideFlags: 0 47 | m_CorrespondingSourceObject: {fileID: 0} 48 | m_PrefabInstance: {fileID: 0} 49 | m_PrefabAsset: {fileID: 0} 50 | m_GameObject: {fileID: 4655882290843831597} 51 | m_Enabled: 1 52 | m_CastShadows: 1 53 | m_ReceiveShadows: 1 54 | m_DynamicOccludee: 1 55 | m_MotionVectors: 1 56 | m_LightProbeUsage: 1 57 | m_ReflectionProbeUsage: 1 58 | m_RenderingLayerMask: 1 59 | m_RendererPriority: 0 60 | m_Materials: 61 | - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} 62 | m_StaticBatchInfo: 63 | firstSubMesh: 0 64 | subMeshCount: 0 65 | m_StaticBatchRoot: {fileID: 0} 66 | m_ProbeAnchor: {fileID: 0} 67 | m_LightProbeVolumeOverride: {fileID: 0} 68 | m_ScaleInLightmap: 1 69 | m_ReceiveGI: 1 70 | m_PreserveUVs: 0 71 | m_IgnoreNormalsForChartDetection: 0 72 | m_ImportantGI: 0 73 | m_StitchLightmapSeams: 1 74 | m_SelectedEditorRenderState: 3 75 | m_MinimumChartSize: 4 76 | m_AutoUVMaxDistance: 0.5 77 | m_AutoUVMaxAngle: 89 78 | m_LightmapParameters: {fileID: 0} 79 | m_SortingLayerID: 0 80 | m_SortingLayer: 0 81 | m_SortingOrder: 0 82 | --- !u!136 &4655882290843831595 83 | CapsuleCollider: 84 | m_ObjectHideFlags: 0 85 | m_CorrespondingSourceObject: {fileID: 0} 86 | m_PrefabInstance: {fileID: 0} 87 | m_PrefabAsset: {fileID: 0} 88 | m_GameObject: {fileID: 4655882290843831597} 89 | m_Material: {fileID: 0} 90 | m_IsTrigger: 0 91 | m_Enabled: 1 92 | m_Radius: 0.5000001 93 | m_Height: 2 94 | m_Direction: 1 95 | m_Center: {x: 0.000000059604645, y: 0, z: -0.00000008940697} 96 | --- !u!1 &4655882291869799809 97 | GameObject: 98 | m_ObjectHideFlags: 0 99 | m_CorrespondingSourceObject: {fileID: 0} 100 | m_PrefabInstance: {fileID: 0} 101 | m_PrefabAsset: {fileID: 0} 102 | serializedVersion: 6 103 | m_Component: 104 | - component: {fileID: 4655882291869799818} 105 | - component: {fileID: 4655882291869799821} 106 | - component: {fileID: 4655882291869799820} 107 | - component: {fileID: 4655882291869799823} 108 | - component: {fileID: 4655882291869799822} 109 | - component: {fileID: 4657585905590680257} 110 | m_Layer: 0 111 | m_Name: SimpleCharacter 112 | m_TagString: Untagged 113 | m_Icon: {fileID: 0} 114 | m_NavMeshLayer: 0 115 | m_StaticEditorFlags: 0 116 | m_IsActive: 1 117 | --- !u!4 &4655882291869799818 118 | Transform: 119 | m_ObjectHideFlags: 0 120 | m_CorrespondingSourceObject: {fileID: 0} 121 | m_PrefabInstance: {fileID: 0} 122 | m_PrefabAsset: {fileID: 0} 123 | m_GameObject: {fileID: 4655882291869799809} 124 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 125 | m_LocalPosition: {x: 0, y: 1.05, z: 0} 126 | m_LocalScale: {x: 1, y: 1, z: 1} 127 | m_Children: 128 | - {fileID: 4655882290843831594} 129 | m_Father: {fileID: 0} 130 | m_RootOrder: 0 131 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 132 | --- !u!33 &4655882291869799821 133 | MeshFilter: 134 | m_ObjectHideFlags: 0 135 | m_CorrespondingSourceObject: {fileID: 0} 136 | m_PrefabInstance: {fileID: 0} 137 | m_PrefabAsset: {fileID: 0} 138 | m_GameObject: {fileID: 4655882291869799809} 139 | m_Mesh: {fileID: 10208, guid: 0000000000000000e000000000000000, type: 0} 140 | --- !u!23 &4655882291869799820 141 | MeshRenderer: 142 | m_ObjectHideFlags: 0 143 | m_CorrespondingSourceObject: {fileID: 0} 144 | m_PrefabInstance: {fileID: 0} 145 | m_PrefabAsset: {fileID: 0} 146 | m_GameObject: {fileID: 4655882291869799809} 147 | m_Enabled: 1 148 | m_CastShadows: 1 149 | m_ReceiveShadows: 1 150 | m_DynamicOccludee: 1 151 | m_MotionVectors: 1 152 | m_LightProbeUsage: 1 153 | m_ReflectionProbeUsage: 1 154 | m_RenderingLayerMask: 1 155 | m_RendererPriority: 0 156 | m_Materials: 157 | - {fileID: 2100000, guid: 6ad9e3a7b438f4329bf661e47ec1527a, type: 2} 158 | m_StaticBatchInfo: 159 | firstSubMesh: 0 160 | subMeshCount: 0 161 | m_StaticBatchRoot: {fileID: 0} 162 | m_ProbeAnchor: {fileID: 0} 163 | m_LightProbeVolumeOverride: {fileID: 0} 164 | m_ScaleInLightmap: 1 165 | m_ReceiveGI: 1 166 | m_PreserveUVs: 0 167 | m_IgnoreNormalsForChartDetection: 0 168 | m_ImportantGI: 0 169 | m_StitchLightmapSeams: 1 170 | m_SelectedEditorRenderState: 3 171 | m_MinimumChartSize: 4 172 | m_AutoUVMaxDistance: 0.5 173 | m_AutoUVMaxAngle: 89 174 | m_LightmapParameters: {fileID: 0} 175 | m_SortingLayerID: 0 176 | m_SortingLayer: 0 177 | m_SortingOrder: 0 178 | --- !u!136 &4655882291869799823 179 | CapsuleCollider: 180 | m_ObjectHideFlags: 0 181 | m_CorrespondingSourceObject: {fileID: 0} 182 | m_PrefabInstance: {fileID: 0} 183 | m_PrefabAsset: {fileID: 0} 184 | m_GameObject: {fileID: 4655882291869799809} 185 | m_Material: {fileID: 0} 186 | m_IsTrigger: 0 187 | m_Enabled: 1 188 | m_Radius: 0.5 189 | m_Height: 2 190 | m_Direction: 1 191 | m_Center: {x: 0, y: 0, z: 0} 192 | --- !u!143 &4655882291869799822 193 | CharacterController: 194 | m_ObjectHideFlags: 0 195 | m_CorrespondingSourceObject: {fileID: 0} 196 | m_PrefabInstance: {fileID: 0} 197 | m_PrefabAsset: {fileID: 0} 198 | m_GameObject: {fileID: 4655882291869799809} 199 | m_Material: {fileID: 0} 200 | m_IsTrigger: 0 201 | m_Enabled: 1 202 | serializedVersion: 2 203 | m_Height: 2 204 | m_Radius: 0.5 205 | m_SlopeLimit: 45 206 | m_StepOffset: 0.3 207 | m_SkinWidth: 0.08 208 | m_MinMoveDistance: 0.001 209 | m_Center: {x: 0, y: 0, z: 0} 210 | --- !u!114 &4657585905590680257 211 | MonoBehaviour: 212 | m_ObjectHideFlags: 0 213 | m_CorrespondingSourceObject: {fileID: 0} 214 | m_PrefabInstance: {fileID: 0} 215 | m_PrefabAsset: {fileID: 0} 216 | m_GameObject: {fileID: 4655882291869799809} 217 | m_Enabled: 1 218 | m_EditorHideFlags: 0 219 | m_Script: {fileID: 11500000, guid: 981a8ba6eadc64bd9b96f78320445a9d, type: 3} 220 | m_Name: 221 | m_EditorClassIdentifier: 222 | -------------------------------------------------------------------------------- /UnityProject/Assets/PrefabsAndMaterials/SimpleCharacter.prefab.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f8697ba2176fc4bfa995833f566a792d 3 | PrefabImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scenes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 87aca9e9f0378471cbbb0d1dd3e89a8f 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scenes/GameWorld.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9fc0d4010bbf28b4594072e72b8655ab 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 38fca16f674ef4c359180ac45c735bd6 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/Client.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b89beca3de8f7450988d75aef7498009 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/Client/Client.cs: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | using UnityEngine; 5 | using System.Collections.Generic; 6 | using System.Collections; 7 | using Amazon.CognitoIdentity; 8 | 9 | // *** MAIN CLIENT CLASS FOR MANAGING CLIENT CONNECTIONS AND MESSAGES *** 10 | 11 | public class Client : MonoBehaviour 12 | { 13 | // Prefabs for the player and enemy objects referenced from the scene object 14 | public GameObject characterPrefab; 15 | public GameObject enemyPrefab; 16 | 17 | #if CLIENT 18 | 19 | // Local player 20 | private NetworkClient networkClient; 21 | private NetworkPlayer localPlayer; 22 | 23 | // List of enemy players 24 | private List enemyPlayers = new List(); 25 | 26 | //We get events back from the NetworkServer through this static list 27 | public static List messagesToProcess = new List(); 28 | 29 | //Cognito credentials for sending signed requests to the API 30 | public static Amazon.Runtime.ImmutableCredentials cognitoCredentials = null; 31 | 32 | // Helper function to check if an enemy exists in the enemy list already 33 | private bool EnemyPlayerExists(int clientId) 34 | { 35 | foreach(NetworkPlayer player in enemyPlayers) 36 | { 37 | if(player.GetPlayerId() == clientId) 38 | { 39 | return true; 40 | } 41 | } 42 | return false; 43 | } 44 | 45 | // Helper function to find and enemy from the enemy list 46 | private NetworkPlayer GetEnemyPlayer(int clientId) 47 | { 48 | foreach (NetworkPlayer player in enemyPlayers) 49 | { 50 | if (player.GetPlayerId() == clientId) 51 | { 52 | return player; 53 | } 54 | } 55 | return null; 56 | } 57 | 58 | // Called by Unity when the Gameobject is created 59 | void Start() 60 | { 61 | FindObjectOfType().SetTextBox("Setting up Client.."); 62 | 63 | // Get an identity and connect to server 64 | CognitoAWSCredentials credentials = new CognitoAWSCredentials( 65 | MatchmakingClient.identityPoolID, 66 | MatchmakingClient.region); 67 | Client.cognitoCredentials = credentials.GetCredentials(); 68 | Debug.Log("Got credentials: " + Client.cognitoCredentials.AccessKey + "," + Client.cognitoCredentials.SecretKey); 69 | 70 | StartCoroutine(ConnectToServer()); 71 | } 72 | 73 | // Fixed Update is called once physics step which is configured to 30 / seconds 74 | // We use this same step for client and server to send messages and sync positions 75 | void FixedUpdate() 76 | { 77 | if (this.localPlayer != null) 78 | { 79 | // Process any messages we have received over the network 80 | this.ProcessMessages(); 81 | 82 | // Send current Move command 83 | this.SendMove(); 84 | 85 | // Receive new messages 86 | this.networkClient.Update(); 87 | } 88 | } 89 | 90 | // Find a game session and connect to the server endpoint received 91 | // This is a coroutine to simplify the logic and keep our UI updated throughout the process 92 | IEnumerator ConnectToServer() 93 | { 94 | FindObjectOfType().SetTextBox("Connecting to backend.."); 95 | 96 | yield return null; 97 | 98 | // Start network client and connect to server 99 | this.networkClient = new NetworkClient(); 100 | // We will wait for the matchmaking and connection coroutine to end before creating the player 101 | yield return StartCoroutine(this.networkClient.RequestGameSession()); 102 | 103 | if (this.networkClient.ConnectionSucceeded()) 104 | { 105 | // Create character 106 | this.localPlayer = new NetworkPlayer(0); 107 | this.localPlayer.Initialize(characterPrefab, new Vector3(UnityEngine.Random.Range(-5,5), 1, UnityEngine.Random.Range(-5, 5))); 108 | this.localPlayer.ResetTarget(); 109 | this.networkClient.SendMessage(this.localPlayer.GetSpawnMessage()); 110 | } 111 | else 112 | { 113 | // Connection failed, restart the scene to try again 114 | UnityEngine.SceneManagement.SceneManager.LoadScene(0); 115 | } 116 | 117 | yield return null; 118 | } 119 | 120 | // Process messages received from server 121 | void ProcessMessages() 122 | { 123 | List justLeftClients = new List(); 124 | List clientsMoved = new List(); 125 | 126 | // Go through any messages to process 127 | foreach (SimpleMessage msg in messagesToProcess) 128 | { 129 | // Own position 130 | if(msg.messageType == MessageType.PositionOwn) 131 | { 132 | this.localPlayer.ReceivePosition(msg, this.characterPrefab); 133 | } 134 | // players spawn and position messages 135 | else if (msg.messageType == MessageType.Spawn || msg.messageType == MessageType.Position || msg.messageType == MessageType.PlayerLeft) 136 | { 137 | if (msg.messageType == MessageType.Spawn && this.EnemyPlayerExists(msg.clientId) == false) 138 | { 139 | Debug.Log("Enemy spawned: " + msg.float1 + "," + msg.float2 + "," + msg.float3 + " ID: " + msg.clientId); 140 | NetworkPlayer enemyPlayer = new NetworkPlayer(msg.clientId); 141 | this.enemyPlayers.Add(enemyPlayer); 142 | enemyPlayer.Spawn(msg, this.enemyPrefab); 143 | } 144 | else if (msg.messageType == MessageType.Position && justLeftClients.Contains(msg.clientId) == false) 145 | { 146 | Debug.Log("Enemy pos received: " + msg.float1 + "," + msg.float2 + "," + msg.float3); 147 | //Setup enemycharacter if not done yet 148 | if (this.EnemyPlayerExists(msg.clientId) == false) 149 | { 150 | Debug.Log("Creating new with ID: " + msg.clientId); 151 | NetworkPlayer newPlayer = new NetworkPlayer(msg.clientId); 152 | this.enemyPlayers.Add(newPlayer); 153 | newPlayer.Spawn(msg, this.enemyPrefab); 154 | } 155 | // We pass the prefab with the position message as it might be the enemy is not spawned yet 156 | NetworkPlayer enemyPlayer = this.GetEnemyPlayer(msg.clientId); 157 | enemyPlayer.ReceivePosition(msg, this.enemyPrefab); 158 | 159 | clientsMoved.Add(msg.clientId); 160 | } 161 | else if(msg.messageType == MessageType.PlayerLeft) 162 | { 163 | Debug.Log("Player left " + msg.clientId); 164 | // A player left, remove from list and delete gameobject 165 | NetworkPlayer enemyPlayer = this.GetEnemyPlayer(msg.clientId); 166 | if(enemyPlayer != null) 167 | { 168 | Debug.Log("Found enemy player"); 169 | enemyPlayer.DeleteGameObject(); 170 | this.enemyPlayers.Remove(enemyPlayer); 171 | justLeftClients.Add(msg.clientId); 172 | } 173 | } 174 | } 175 | } 176 | messagesToProcess.Clear(); 177 | 178 | // Interpolate all enemy players towards their current target 179 | foreach (var enemyPlayer in this.enemyPlayers) 180 | { 181 | enemyPlayer.InterpolateToTarget(); 182 | } 183 | 184 | // Interpolate player towards his/her current target 185 | this.localPlayer.InterpolateToTarget(); 186 | } 187 | 188 | void SendMove() 189 | { 190 | // Send position if changed 191 | var newPosMessage = this.localPlayer.GetMoveMessage(); 192 | if (newPosMessage != null) 193 | this.networkClient.SendMessage(newPosMessage); 194 | } 195 | #endif 196 | } -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/Client/Client.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 14d54a9963b984bd8946300a53e112da 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/Client/MatchmakingClient.cs: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | using System; 5 | using UnityEngine; 6 | using System.Threading.Tasks; 7 | using System.Net.Http; 8 | using System.Collections.Generic; 9 | using AWSSignatureV4_S3_Sample.Signers; 10 | 11 | #if CLIENT 12 | 13 | // **** MATCMAKING API CLIENT *** 14 | // The Backend service called by this client will do simple placement of players to new or existing sessions 15 | 16 | public class MatchmakingClient 17 | { 18 | // **** SET THESE VARIABLES BASED ON YOUR OWN CONFIGURATION *** // 19 | static string apiEndpoint = "https://.amazonaws.com/Prod/"; 20 | public static string identityPoolID = ""; 21 | public static string regionString = "us-east-1"; 22 | public static Amazon.RegionEndpoint region = Amazon.RegionEndpoint.USEast1; 23 | // *********************************************************** // 24 | 25 | // Helper function to send and wait for response to a signed request to the API Gateway endpoint 26 | async Task SendSignedGetRequest(string requestUrl) 27 | { 28 | // Sign the request with cognito credentials 29 | var request = this.generateSignedRequest(requestUrl); 30 | 31 | // Execute the signed request 32 | var client = new HttpClient(); 33 | var resp = await client.SendAsync(request); 34 | 35 | // Get the response 36 | var responseStr = await resp.Content.ReadAsStringAsync(); 37 | Debug.Log(responseStr); 38 | return responseStr; 39 | } 40 | 41 | // Request a game session from the backend 42 | public GameSessionInfo RequestGameSession() 43 | { 44 | try 45 | { 46 | //Make the signed request and wait for max 10 seconds to complete 47 | var response = Task.Run(() => this.SendSignedGetRequest(apiEndpoint + "requestgamesession")); 48 | response.Wait(10000); 49 | string jsonResponse = response.Result; 50 | Debug.Log("Json response: " + jsonResponse); 51 | 52 | if (jsonResponse.Contains("failed")) 53 | return null; 54 | 55 | GameSessionInfo info = JsonUtility.FromJson(jsonResponse); 56 | return info; 57 | } 58 | catch (Exception e) 59 | { 60 | Debug.Log(e.Message); 61 | return null; 62 | } 63 | } 64 | 65 | // Generates a HTTPS requestfor API Gateway signed with the Cognito credentials from a url using the S3 signer tool example 66 | // NOTE: You need to add the floders "Signers" and "Util" to the project from the S3 signer tool example: https://docs.aws.amazon.com/AmazonS3/latest/API/samples/AmazonS3SigV4_Samples_CSharp.zip 67 | HttpRequestMessage generateSignedRequest(string url) 68 | { 69 | var endpointUri = url; 70 | 71 | var uri = new Uri(endpointUri); 72 | 73 | var headers = new Dictionary 74 | { 75 | {AWS4SignerBase.X_Amz_Content_SHA256, AWS4SignerBase.EMPTY_BODY_SHA256}, 76 | }; 77 | 78 | var signer = new AWS4SignerForAuthorizationHeader 79 | { 80 | EndpointUri = uri, 81 | HttpMethod = "GET", 82 | Service = "execute-api", 83 | Region = regionString 84 | }; 85 | 86 | //Extract the query parameters 87 | var queryParams = ""; 88 | if (url.Split('?').Length > 1) 89 | { 90 | queryParams = url.Split('?')[1]; 91 | } 92 | 93 | var authorization = signer.ComputeSignature(headers, 94 | queryParams, 95 | AWS4SignerBase.EMPTY_BODY_SHA256, 96 | Client.cognitoCredentials.AccessKey, 97 | Client.cognitoCredentials.SecretKey); 98 | 99 | headers.Add("Authorization", authorization); 100 | 101 | var request = new HttpRequestMessage 102 | { 103 | Method = HttpMethod.Get, 104 | RequestUri = new Uri(url), 105 | }; 106 | 107 | // Add the generated headers to the request 108 | foreach (var header in headers) 109 | { 110 | try 111 | { 112 | if (header.Key != null && header.Value != null) 113 | request.Headers.Add(header.Key, header.Value); 114 | } 115 | catch (Exception e) 116 | { 117 | Debug.Log("error: " + e.GetType().ToString()); 118 | } 119 | } 120 | 121 | // Add the IAM authentication token 122 | request.Headers.Add("x-amz-security-token", Client.cognitoCredentials.Token); 123 | 124 | return request; 125 | } 126 | } 127 | 128 | #endif -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/Client/MatchmakingClient.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8c107d5644b9f4e15935d1a368b8af1c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/Client/MatchmakingSerializationClasses.cs: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | // *** SERIALIZATION OBJECTS FOR MATCHMAKING API REQUESTS *** 5 | [System.Serializable] 6 | public class GameSessionInfo 7 | { 8 | public string publicIP; 9 | public int port; 10 | } -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/Client/MatchmakingSerializationClasses.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f3ce1e0050f614b2a90dcc5eec5ada8b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/Client/NetworkClient.cs: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | using System; 5 | using System.Net.Sockets; 6 | using UnityEngine; 7 | using System.Collections; 8 | 9 | #if CLIENT 10 | 11 | // *** NETWORK CLIENT FOR TCP CONNECTIONS WITH THE SERVER *** 12 | 13 | public class NetworkClient 14 | { 15 | private MatchmakingClient matchmakingClient; 16 | 17 | private TcpClient client = null; 18 | 19 | private bool connectionSucceeded = false; 20 | 21 | private GameSessionInfo gameSessionInfo = null; 22 | 23 | public bool ConnectionSucceeded() { return connectionSucceeded; } 24 | 25 | public NetworkClient() 26 | { 27 | this.matchmakingClient = new MatchmakingClient(); 28 | } 29 | 30 | public IEnumerator RequestGameSession() 31 | { 32 | Debug.Log("Request matchmaking..."); 33 | GameObject.FindObjectOfType().SetTextBox("Requesting game session..."); 34 | yield return null; 35 | 36 | bool gameSessionFound = false; 37 | int tries = 0; 38 | while (!gameSessionFound) 39 | { 40 | GameObject.FindObjectOfType().SetTextBox("Requesting game session..."); 41 | yield return null; 42 | this.gameSessionInfo = this.matchmakingClient.RequestGameSession(); 43 | if (gameSessionInfo == null) 44 | { 45 | GameObject.FindObjectOfType().SetTextBox("No game session found yet, trying again..."); 46 | yield return new WaitForSeconds(1.0f); 47 | } 48 | else 49 | { 50 | Debug.Log("Got session: " + gameSessionInfo.publicIP + " " + gameSessionInfo.port); 51 | GameObject.FindObjectOfType().SetTextBox("Found a game server, connecting..."); 52 | yield return null; 53 | 54 | gameSessionFound = true; 55 | // game session found, connect to the server 56 | Connect(); 57 | } 58 | tries++; 59 | 60 | if(tries > 20) 61 | { 62 | GameObject.FindObjectOfType().SetTextBox("Aborting game session search, no game found in 20 seconds"); 63 | Debug.Log("Aborting game session search, no game found in 20 seconds"); 64 | yield return null; 65 | break; 66 | } 67 | } 68 | } 69 | 70 | // Called by the client to receive new messages 71 | public void Update() 72 | { 73 | if (client == null) return; 74 | var messages = NetworkProtocol.Receive(client); 75 | 76 | foreach (SimpleMessage msg in messages) 77 | { 78 | HandleMessage(msg); 79 | } 80 | } 81 | 82 | private bool TryConnect() 83 | { 84 | try 85 | { 86 | //Connect with matchmaking info 87 | Debug.Log("Connect.."); 88 | Debug.Log(this.gameSessionInfo.publicIP); 89 | Debug.Log(this.gameSessionInfo.port); 90 | this.client = new TcpClient(); 91 | var result = client.BeginConnect(this.gameSessionInfo.publicIP, this.gameSessionInfo.port, null, null); 92 | 93 | var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(2)); 94 | 95 | if (!success) 96 | { 97 | throw new Exception("Failed to connect."); 98 | } 99 | client.NoDelay = true; // Use No Delay to send small messages immediately. UDP should be used for even faster messaging 100 | Debug.Log("Done"); 101 | 102 | return true; 103 | } 104 | catch (Exception e) 105 | { 106 | Debug.Log(e.Message); 107 | client = null; 108 | return false; 109 | } 110 | } 111 | 112 | public void Connect() 113 | { 114 | // try to connect to a local server 115 | if (TryConnect() == false) 116 | { 117 | Debug.Log("Failed to connect to server"); 118 | GameObject.FindObjectOfType().SetTextBox("Connection to server failed."); 119 | } 120 | else 121 | { 122 | //We're ready to play, let the server know 123 | this.Ready(); 124 | GameObject.FindObjectOfType().SetTextBox("Connected to server"); 125 | } 126 | } 127 | 128 | // Send ready to play message to server 129 | public void Ready() 130 | { 131 | if (client == null) return; 132 | this.connectionSucceeded = true; 133 | 134 | // Send READY message to let server know we are ready 135 | SimpleMessage message = new SimpleMessage(MessageType.Ready); 136 | try 137 | { 138 | NetworkProtocol.Send(client, message); 139 | } 140 | catch (SocketException e) 141 | { 142 | HandleDisconnect(); 143 | } 144 | } 145 | 146 | // Send serialized binary message to server 147 | public void SendMessage(SimpleMessage message) 148 | { 149 | if (client == null) return; 150 | try 151 | { 152 | NetworkProtocol.Send(client, message); 153 | } 154 | catch (SocketException e) 155 | { 156 | HandleDisconnect(); 157 | } 158 | } 159 | 160 | // Send disconnect message to server 161 | public void Disconnect() 162 | { 163 | if (client == null) return; 164 | SimpleMessage message = new SimpleMessage(MessageType.Disconnect); 165 | try 166 | { 167 | NetworkProtocol.Send(client, message); 168 | } 169 | 170 | finally 171 | { 172 | HandleDisconnect(); 173 | } 174 | } 175 | 176 | // Handle a message received from the server 177 | private void HandleMessage(SimpleMessage msg) 178 | { 179 | // parse message and pass json string to relevant handler for deserialization 180 | Debug.Log("Message received:" + msg.messageType + ":" + msg.message); 181 | 182 | if (msg.messageType == MessageType.Reject) 183 | HandleReject(); 184 | else if (msg.messageType == MessageType.Disconnect) 185 | HandleDisconnect(); 186 | else if (msg.messageType == MessageType.Spawn) 187 | HandleOtherPlayerSpawned(msg); 188 | else if (msg.messageType == MessageType.Position) 189 | HandleOtherPlayerPos(msg); 190 | else if (msg.messageType == MessageType.PositionOwn) 191 | HandlePlayerPos(msg); 192 | else if (msg.messageType == MessageType.PlayerLeft) 193 | HandleOtherPlayerLeft(msg); 194 | } 195 | 196 | private void HandleReject() 197 | { 198 | NetworkStream stream = client.GetStream(); 199 | stream.Close(); 200 | client.Close(); 201 | client = null; 202 | } 203 | 204 | private void HandleDisconnect() 205 | { 206 | Debug.Log("Got disconnected by server"); 207 | GameObject.FindObjectOfType().SetTextBox("Got disconnected by server"); 208 | NetworkStream stream = client.GetStream(); 209 | stream.Close(); 210 | client.Close(); 211 | client = null; 212 | } 213 | 214 | private void HandleOtherPlayerSpawned(SimpleMessage message) 215 | { 216 | Client.messagesToProcess.Add(message); 217 | } 218 | 219 | private void HandleOtherPlayerPos(SimpleMessage message) 220 | { 221 | Client.messagesToProcess.Add(message); 222 | } 223 | 224 | private void HandlePlayerPos(SimpleMessage message) 225 | { 226 | Client.messagesToProcess.Add(message); 227 | } 228 | 229 | private void HandleOtherPlayerLeft(SimpleMessage message) 230 | { 231 | Client.messagesToProcess.Add(message); 232 | } 233 | } 234 | 235 | #endif 236 | 237 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/Client/NetworkClient.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c0f3eac65fb0e44679b52a4ef5e8baca 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/Client/SimpleController.cs: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | // A simple class for getting player input and moving a character 5 | 6 | using UnityEngine; 7 | 8 | public class SimpleController : MonoBehaviour 9 | { 10 | float speed = 0.15f; 11 | 12 | float currentMoveZ = 0.0f; 13 | float currentMoveX = 0.0f; 14 | 15 | public float GetCurrentMoveX() { return currentMoveX; } 16 | public float GetCurrentMoveZ() { return currentMoveZ; } 17 | 18 | public void SetMove(float x, float z) { this.currentMoveX = x; this.currentMoveZ = z; } 19 | 20 | #if CLIENT 21 | // Manage player input on a fixed timestep 22 | void FixedUpdate() 23 | { 24 | currentMoveX = 0.0f; 25 | currentMoveZ = 0.0f; 26 | 27 | // Get the movement input 28 | if (Input.GetKey(KeyCode.UpArrow) || Input.GetKey(KeyCode.W)) 29 | { 30 | currentMoveZ = 1.0f; 31 | } 32 | else if (Input.GetKey(KeyCode.DownArrow) || Input.GetKey(KeyCode.S)) 33 | { 34 | currentMoveZ = -1.0f; 35 | } 36 | if (Input.GetKey(KeyCode.LeftArrow) || Input.GetKey(KeyCode.A)) 37 | { 38 | currentMoveX = -1.0f; 39 | } 40 | else if (Input.GetKey(KeyCode.RightArrow) || Input.GetKey(KeyCode.D)) 41 | { 42 | currentMoveX = 1.0f; 43 | } 44 | 45 | } 46 | #endif 47 | 48 | public void Move() 49 | { 50 | Vector3 move = Vector3.zero; 51 | move.x = currentMoveX; 52 | move.z = currentMoveZ; 53 | 54 | // Move the character 55 | move.Normalize(); 56 | this.transform.LookAt(this.transform.position + move); 57 | this.GetComponent().Move(move * speed); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/Client/SimpleController.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 981a8ba6eadc64bd9b96f78320445a9d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/NetworkingShared.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 130a7976091584d43bb7387ac7ba3d18 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/NetworkingShared/MessageClasses.cs: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | // Here you would define your message types for the messages between server and client 5 | // NOTE: We are using BinaryFormatter for simplicity to serialize/deserialize binary messages. 6 | // There are more optimal solutions such as Protocol buffers available. 7 | 8 | using System; 9 | 10 | [Serializable] 11 | public enum MessageType 12 | { 13 | Connect, 14 | Disconnect, 15 | Ready, 16 | Reject, 17 | Spawn, 18 | Position, 19 | PlayerLeft, 20 | PlayerInput, 21 | PositionOwn 22 | }; 23 | 24 | // We will use the same message for all requests so it will include a type and optional float values (used for position and orientation) 25 | [Serializable] 26 | public class SimpleMessage 27 | { 28 | public SimpleMessage(MessageType type, string message = "") 29 | { 30 | this.messageType = type; 31 | this.message = message; 32 | } 33 | 34 | public void SetFloats(float float1, float float2, float float3, float float4, float float5, float float6, float float7) 35 | { 36 | this.float1 = float1; this.float2 = float2; this.float3 = float3; this.float4 = float4; this.float5 = float5; this.float6 = float6; this.float7 = float7; 37 | } 38 | 39 | public void SetMoveFloats(float float1, float float2) 40 | { 41 | this.float1 = float1; this.float2 = float2; 42 | } 43 | 44 | public MessageType messageType { get; set; } 45 | public string message { get; set; } 46 | public int clientId { get; set; } 47 | 48 | // As we are using one generic message for simplicity, we always have all possible data here 49 | // You would likely want to use different classes for different message types 50 | public float float1 { get; set; } 51 | public float float2 { get; set; } 52 | public float float3 { get; set; } 53 | public float float4 { get; set; } 54 | public float float5 { get; set; } 55 | public float float6 { get; set; } 56 | public float float7 { get; set; } 57 | } 58 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/NetworkingShared/MessageClasses.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 13b05c6ab03064ecdabc807c61e05381 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/NetworkingShared/NetworkPlayer.cs: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | //Encapsulates a GameObject and manages spawning and interpolation of an enemy player 5 | 6 | using UnityEngine; 7 | 8 | public class NetworkPlayer 9 | { 10 | private GameObject character; 11 | private bool localPlayer; 12 | int playerId; 13 | 14 | private Vector3 previousPos = Vector3.zero; 15 | private Vector3 targetPos = Vector3.zero; 16 | private Quaternion targetOrientation = Quaternion.identity; 17 | 18 | public NetworkPlayer(int playerId) 19 | { 20 | this.playerId = playerId; 21 | } 22 | 23 | public void DeleteGameObject() 24 | { 25 | GameObject.Destroy(this.character); 26 | } 27 | 28 | public void SetPlayerId(int playerId) { this.playerId = playerId; } 29 | 30 | public int GetPlayerId() 31 | { 32 | return playerId; 33 | } 34 | 35 | // This is called for the local player only 36 | public void Initialize(GameObject characterPrefab, Vector3 pos) 37 | { 38 | // Create character 39 | Quaternion rotation = Quaternion.identity; 40 | this.character = GameObject.Instantiate(characterPrefab, pos, rotation); 41 | this.localPlayer = true; 42 | } 43 | 44 | // *** FOR SENDING MESSAGES (LOCAL PLAYER) *** // 45 | 46 | public SimpleMessage GetSpawnMessage() 47 | { 48 | SimpleMessage message = new SimpleMessage(MessageType.Spawn); 49 | Vector3 pos = this.character.transform.position; 50 | Quaternion rotation = this.character.transform.rotation; 51 | message.SetFloats(pos.x, pos.y, pos.z, rotation.x, rotation.y, rotation.z, rotation.w); 52 | return message; 53 | } 54 | 55 | // Resets interpolation target to current pos 56 | public void ResetTarget() 57 | { 58 | this.previousPos = this.character.transform.position; 59 | this.targetPos = this.character.transform.position; 60 | this.targetOrientation = this.character.transform.rotation; 61 | } 62 | 63 | public SimpleMessage GetPositionMessage(bool overrideChangedCheck = false) 64 | { 65 | Vector3 pos = this.character.transform.position; 66 | if(Vector3.Distance(pos, this.previousPos) > 0.01f || overrideChangedCheck) 67 | { 68 | SimpleMessage message = new SimpleMessage(MessageType.Position); 69 | this.previousPos = pos; 70 | Quaternion rotation = this.character.transform.rotation; 71 | message.SetFloats(pos.x, pos.y, pos.z, rotation.x, rotation.y, rotation.z, rotation.w); 72 | return message; 73 | } 74 | return null; 75 | } 76 | 77 | public SimpleMessage GetMoveMessage() 78 | { 79 | SimpleController controller = this.character.GetComponent(); 80 | if(controller != null) 81 | { 82 | float moveX = controller.GetCurrentMoveX(); 83 | float moveZ = controller.GetCurrentMoveZ(); 84 | SimpleMessage message = new SimpleMessage(MessageType.PlayerInput); 85 | message.SetMoveFloats(moveX, moveZ); 86 | return message; 87 | } 88 | return null; 89 | } 90 | 91 | // *** FOR RECEIVING MESSAGES (REMOTE PLAYERS) *** // 92 | 93 | // This is called for remote players only 94 | public void Spawn(SimpleMessage msg, GameObject characterPrefab) 95 | { 96 | this.localPlayer = false; 97 | 98 | //Position 99 | float x = msg.float1; 100 | float y = msg.float2; 101 | float z = msg.float3; 102 | //Orientation 103 | float qx = msg.float4; 104 | float qy = msg.float5; 105 | float qz = msg.float6; 106 | float qw = msg.float7; 107 | 108 | if (this.character == null) 109 | { 110 | Debug.Log("Enemy not spawned yet, spawn"); 111 | this.character = GameObject.Instantiate(characterPrefab, new Vector3(x, y, z), new Quaternion(qx, qy, qz, qw)); 112 | } 113 | } 114 | 115 | // Moves a single physics tick 116 | public void Move() 117 | { 118 | var controller = this.character.GetComponent(); 119 | controller.Move(); 120 | } 121 | 122 | // Set's the input on server (received from clients) 123 | public void SetInput(SimpleMessage msg) 124 | { 125 | var controller = this.character.GetComponent(); 126 | controller.SetMove(msg.float1, msg.float2); 127 | } 128 | 129 | public void ReceivePosition(SimpleMessage msg, GameObject characterPrefab) 130 | { 131 | Debug.Log("Received Pos: " + msg.float1 + "," + msg.float2 + "," + msg.float3); 132 | //Position 133 | float x = msg.float1; 134 | float y = msg.float2; 135 | float z = msg.float3; 136 | //Orientation 137 | float qx = msg.float4; 138 | float qy = msg.float5; 139 | float qz = msg.float6; 140 | float qw = msg.float7; 141 | 142 | // We spawn here too as it might be the enemy spawned before us 143 | if (this.character == null) 144 | { 145 | Debug.Log("Enemy not spawned yet, spawn"); 146 | this.character = GameObject.Instantiate(characterPrefab, new Vector3(x, y, z), new Quaternion(qx, qy, qz, qw)); 147 | } 148 | 149 | // Set the target position for interpolation which is done in InterpolateToTarget on every frame 150 | this.targetPos = new Vector3(x, y, z); 151 | this.targetOrientation = new Quaternion(qx, qy, qz, qw); 152 | } 153 | 154 | // Interpolate to target 155 | public void InterpolateToTarget() 156 | { 157 | Vector3 move = targetPos - this.character.transform.position; 158 | float distance = move.magnitude; 159 | 160 | Vector3 positionDifference = this.targetPos - this.character.transform.position; 161 | Vector3 interpolateMove = 0.5f * positionDifference; 162 | this.character.transform.SetPositionAndRotation(this.character.transform.position + interpolateMove, this.targetOrientation); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/NetworkingShared/NetworkPlayer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 19d0e0c70827b4dd4b7752cb28f8cc13 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/NetworkingShared/NetworkProtocol.cs: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | // Helper class to serialize and deserialize messages to a network stream 5 | 6 | using System; 7 | using System.Net.Sockets; 8 | using System.Collections.Generic; 9 | using System.Runtime.Serialization.Formatters.Binary; 10 | 11 | public class NetworkProtocol 12 | { 13 | public static SimpleMessage[] Receive(TcpClient client) 14 | { 15 | NetworkStream stream = client.GetStream(); 16 | var messages = new List(); 17 | while (stream.DataAvailable) 18 | { 19 | try 20 | { 21 | BinaryFormatter formatter = new BinaryFormatter(); 22 | SimpleMessage message = formatter.Deserialize(stream) as SimpleMessage; 23 | messages.Add(message); 24 | } 25 | catch(Exception e) 26 | { 27 | System.Console.WriteLine("Error receiving a message: " + e.Message); 28 | System.Console.WriteLine("Aborting the rest of the messages"); 29 | break; 30 | } 31 | } 32 | 33 | return messages.ToArray(); 34 | } 35 | 36 | public static void Send(TcpClient client, SimpleMessage message) 37 | { 38 | if (client == null) return; 39 | NetworkStream stream = client.GetStream(); 40 | BinaryFormatter formatter = new BinaryFormatter(); 41 | formatter.Serialize(stream, message); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/NetworkingShared/NetworkProtocol.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: caa4d694728b34ec3a90899b2a10bbd4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/Server.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cd373d57b6acc4ff887d331d7b6e0573 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/Server/Server.cs: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using UnityEngine; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Collections; 10 | using UnityEngine.Networking; 11 | using Amazon.GameLift; 12 | using Amazon.GameLift.Model; 13 | using Amazon; 14 | 15 | [Serializable] 16 | public class TaskData 17 | { 18 | public string TaskARN; 19 | } 20 | 21 | [Serializable] 22 | public class TaskStatusData 23 | { 24 | public string taskArn { get; set; } //arn of the task the server is running on (withut the container ID) 25 | } 26 | 27 | // *** MONOBEHAVIOUR TO MANAGE SERVER LOGIC *** // 28 | 29 | public class Server : MonoBehaviour 30 | { 31 | public GameObject playerPrefab; 32 | 33 | #if SERVER 34 | 35 | // TODO: Update this to your selected Region 36 | public RegionEndpoint regionEndpoint = RegionEndpoint.USEast1; 37 | 38 | // List of players 39 | public List players = new List(); 40 | public int rollingPlayerId = 0; //Rolling player id that is used to give new players an ID when connecting 41 | 42 | // For testing we have maximum of 2 players 43 | public static int maxPlayers = 2; 44 | 45 | public static int port = -1; 46 | public static string fleetIqGameServerGroup = null; 47 | 48 | //We get events back from the NetworkServer through this static list 49 | public static List messagesToProcess = new List(); 50 | 51 | // Game server data 52 | private string publicIP = null; 53 | private string taskDataArn = null; 54 | private string taskDataArnWithContainer = null; 55 | private string gameServerId = null; 56 | private string instanceId = null; 57 | public string GetGameServerId() { return gameServerId; } 58 | 59 | static float fleetIqUpdateInterval = 60.0f; 60 | float fleetIqUpdateCounter = 0.0f; 61 | bool registeredToFleetIQ = false; 62 | 63 | private int lastPlayerCount = 0; 64 | 65 | NetworkServer server; 66 | 67 | // Game state 68 | private bool gameStarted = false; 69 | 70 | // Helper function to check if a player exists in the enemy list already 71 | private bool PlayerExists(int clientId) 72 | { 73 | foreach (NetworkPlayer player in players) 74 | { 75 | if (player.GetPlayerId() == clientId) 76 | { 77 | return true; 78 | } 79 | } 80 | return false; 81 | } 82 | 83 | // Helper function to find a player from the enemy list 84 | private NetworkPlayer GetPlayer(int clientId) 85 | { 86 | foreach (NetworkPlayer player in players) 87 | { 88 | if (player.GetPlayerId() == clientId) 89 | { 90 | return player; 91 | } 92 | } 93 | return null; 94 | } 95 | 96 | public void RemovePlayer(int clientId) 97 | { 98 | foreach (NetworkPlayer player in players) 99 | { 100 | if (player.GetPlayerId() == clientId) 101 | { 102 | player.DeleteGameObject(); 103 | players.Remove(player); 104 | return; 105 | } 106 | } 107 | } 108 | 109 | public void StartGame() 110 | { 111 | System.Console.WriteLine("Starting game"); 112 | this.gameStarted = true; 113 | } 114 | 115 | public bool GameStarted() 116 | { 117 | return this.gameStarted; 118 | } 119 | 120 | // Start is called before the first frame update 121 | void Start() 122 | { 123 | // Get the game server group for FleetIQ access 124 | Server.fleetIqGameServerGroup = "ExampleGameServerGroup"; 125 | Console.WriteLine("My game server group: " + Server.fleetIqGameServerGroup); 126 | 127 | // Get my IP information through ipify 128 | var requestIPAddressPath = "https://api.ipify.org"; 129 | StartCoroutine(GetMyIP(requestIPAddressPath)); 130 | 131 | // Get my Task information to generate an identifier for this game server 132 | var ecsMetadataPath = Environment.GetEnvironmentVariable("ECS_CONTAINER_METADATA_URI"); 133 | Console.WriteLine("ECS metadata path: " + ecsMetadataPath); 134 | // Request the metadata 135 | StartCoroutine(GetTaskMetadata(ecsMetadataPath + "/task")); 136 | 137 | // Request instance-id for FleetIQ calls 138 | StartCoroutine(GetInstanceId("http://169.254.169.254/latest/meta-data/instance-id")); 139 | 140 | // Set FleetIQ update counter to trigger in a few seconds to inform that we're ready as soon as possible 141 | this.fleetIqUpdateCounter = Server.fleetIqUpdateInterval - 2.0f; 142 | } 143 | 144 | IEnumerator GetMyIP(string uri) 145 | { 146 | using (UnityWebRequest webRequest = UnityWebRequest.Get(uri)) 147 | { 148 | // Request and wait for the desired page. 149 | yield return webRequest.SendWebRequest(); 150 | 151 | if (webRequest.isNetworkError) 152 | { 153 | Debug.Log("Web request Error: " + webRequest.error); 154 | } 155 | else 156 | { 157 | Debug.Log("Web Request Received: " + webRequest.downloadHandler.text); 158 | // Set the public IP 159 | this.publicIP = webRequest.downloadHandler.text; 160 | } 161 | } 162 | } 163 | 164 | IEnumerator GetTaskMetadata(string uri) 165 | { 166 | using (UnityWebRequest webRequest = UnityWebRequest.Get(uri)) 167 | { 168 | // Request and wait for the desired page. 169 | yield return webRequest.SendWebRequest(); 170 | 171 | if (webRequest.isNetworkError) 172 | { 173 | Debug.Log("Task metadata Error: " + webRequest.error); 174 | } 175 | else 176 | { 177 | Debug.Log("Task metadata Received: " + webRequest.downloadHandler.text); 178 | var taskData = JsonUtility.FromJson(webRequest.downloadHandler.text); 179 | this.taskDataArn = taskData.TaskARN; 180 | // Search for the host port, a bit hacky but it's always 5 numbers after "HostPort": 181 | string afterHostport = webRequest.downloadHandler.text.Split(new string[] { "\"HostPort\":" }, StringSplitOptions.None)[1]; 182 | Server.port = int.Parse(afterHostport.Substring(0, 5)); 183 | Console.WriteLine("port: " + Server.port); 184 | this.taskDataArnWithContainer = taskData.TaskARN + "-" + Environment.GetEnvironmentVariable("CONTAINERNAME"); //Including the container name as we run multiple containers in a Task 185 | Debug.Log("TaskARN: " + this.taskDataArn); 186 | Debug.Log("TaskARN with container: " + this.taskDataArnWithContainer); 187 | // We will generate the game server name with the Task Arn id only to match the constraints 188 | var splittedArn = this.taskDataArnWithContainer.Split('/'); 189 | this.gameServerId = splittedArn[splittedArn.Length - 1]; 190 | Debug.Log("Game Server ID: " + this.gameServerId); 191 | } 192 | } 193 | } 194 | 195 | IEnumerator GetInstanceId(string uri) 196 | { 197 | using (UnityWebRequest webRequest = UnityWebRequest.Get(uri)) 198 | { 199 | // Request and wait for the desired page. 200 | yield return webRequest.SendWebRequest(); 201 | 202 | if (webRequest.isNetworkError) 203 | { 204 | Debug.Log("Web request Error: " + webRequest.error); 205 | } 206 | else 207 | { 208 | Debug.Log("Instance metadata Received: " + webRequest.downloadHandler.text); 209 | // Set the public IP 210 | this.instanceId = webRequest.downloadHandler.text; 211 | } 212 | } 213 | } 214 | 215 | private void Update() 216 | { 217 | // Create TCP server if we received port already 218 | if(Server.port > 0 && this.server == null) 219 | { 220 | Console.WriteLine("Port received, create network server"); 221 | this.server = new NetworkServer(this); 222 | } 223 | 224 | // Register to FleetIQ if we have all the needed info and not registered yet 225 | if (!registeredToFleetIQ) 226 | { 227 | if (this.taskDataArnWithContainer != null && this.publicIP != null && this.instanceId != null) 228 | { 229 | Console.WriteLine("Registering to FleetIQ"); 230 | var gameLiftConfig = new AmazonGameLiftConfig { RegionEndpoint = this.regionEndpoint }; 231 | var gameLiftClient = new AmazonGameLiftClient(gameLiftConfig); 232 | var registerGameServerRequest = new RegisterGameServerRequest(); 233 | registerGameServerRequest.GameServerGroupName = Server.fleetIqGameServerGroup; 234 | registerGameServerRequest.InstanceId = this.instanceId; 235 | registerGameServerRequest.ConnectionInfo = this.publicIP + ":" + Server.port; 236 | registerGameServerRequest.GameServerId = this.gameServerId; 237 | registerGameServerRequest.GameServerData = "{\"gametype\": \"normal\"}"; 238 | var response = gameLiftClient.RegisterGameServerAsync(registerGameServerRequest); 239 | response.Wait(); 240 | Console.WriteLine("Response HTTP code: " + response.Result.HttpStatusCode.ToString()); 241 | Console.WriteLine("Response game server: " + response.Result.GameServer.GameServerData.ToString()); 242 | Console.WriteLine("RegistrationRequest Sent!"); 243 | 244 | this.registeredToFleetIQ = true; 245 | } 246 | } 247 | } 248 | 249 | // FixedUpdate is called 30 times per second (configured in Project Settings -> Time -> Fixed TimeStep). 250 | // This is the interval we're running the simulation and processing messages on the server 251 | void FixedUpdate() 252 | { 253 | // Don't run before Network server is created 254 | if (this.server == null) 255 | return; 256 | 257 | // Update the Network server to check client status and get messages 258 | server.Update(); 259 | 260 | // Process any messages we received 261 | this.ProcessMessages(); 262 | 263 | // Move players based on latest input and update player states to clients 264 | for (int i = 0; i < this.players.Count; i++) 265 | { 266 | var player = this.players[i]; 267 | // Move 268 | player.Move(); 269 | 270 | // Send state if changed 271 | var positionMessage = player.GetPositionMessage(); 272 | if (positionMessage != null) 273 | { 274 | positionMessage.clientId = player.GetPlayerId(); 275 | this.server.TransmitMessage(positionMessage, player.GetPlayerId()); 276 | //Send to the player him/herself 277 | positionMessage.messageType = MessageType.PositionOwn; 278 | this.server.SendMessage(player.GetPlayerId(), positionMessage); 279 | } 280 | } 281 | 282 | // Update the server state to FleetIQ every 60. This will also get done when new clients connect 283 | this.fleetIqUpdateCounter += Time.fixedDeltaTime; 284 | if(this.fleetIqUpdateCounter > Server.fleetIqUpdateInterval && this.taskDataArnWithContainer != null) 285 | { 286 | this.UpdateFleetIQ(); 287 | this.fleetIqUpdateCounter = 0.0f; 288 | } 289 | 290 | // If a new player joined, update FleetIQ as well 291 | if(this.server.GetPlayerCount() > this.lastPlayerCount) 292 | { 293 | Debug.Log("New player joined, update FleetIQ"); 294 | this.UpdateFleetIQ(); 295 | this.lastPlayerCount = this.server.GetPlayerCount(); 296 | } 297 | } 298 | 299 | private void ProcessMessages() 300 | { 301 | // Go through any messages we received to process 302 | foreach (SimpleMessage msg in messagesToProcess) 303 | { 304 | // Spawn player 305 | if (msg.messageType == MessageType.Spawn) 306 | { 307 | Debug.Log("Player spawned: " + msg.float1 + "," + msg.float2 + "," + msg.float3); 308 | NetworkPlayer player = new NetworkPlayer(msg.clientId); 309 | this.players.Add(player); 310 | player.Spawn(msg, this.playerPrefab); 311 | player.SetPlayerId(msg.clientId); 312 | 313 | // Send all existing player positions to the newly joined 314 | for (int i = 0; i < this.players.Count-1; i++) 315 | { 316 | var otherPlayer = this.players[i]; 317 | // Send state 318 | var positionMessage = otherPlayer.GetPositionMessage(overrideChangedCheck: true); 319 | if (positionMessage != null) 320 | { 321 | positionMessage.clientId = otherPlayer.GetPlayerId(); 322 | this.server.SendMessage(player.GetPlayerId(), positionMessage); 323 | } 324 | } 325 | } 326 | 327 | // Set player input 328 | if (msg.messageType == MessageType.PlayerInput) 329 | { 330 | // Only handle input if the player exists 331 | if (this.PlayerExists(msg.clientId)) 332 | { 333 | Debug.Log("Player moved: " + msg.float1 + "," + msg.float2 + " ID: " + msg.clientId); 334 | 335 | if (this.PlayerExists(msg.clientId)) 336 | { 337 | var player = this.GetPlayer(msg.clientId); 338 | player.SetInput(msg); 339 | } 340 | else 341 | { 342 | Debug.Log("PLAYER MOVED BUT IS NOT SPAWNED! SPAWN TO RANDOM POS"); 343 | Vector3 spawnPos = new Vector3(UnityEngine.Random.Range(-5, 5), 1, UnityEngine.Random.Range(-5, 5)); 344 | var quat = Quaternion.identity; 345 | SimpleMessage tmpMsg = new SimpleMessage(MessageType.Spawn); 346 | tmpMsg.SetFloats(spawnPos.x, spawnPos.y, spawnPos.z, quat.x, quat.y, quat.z, quat.w); 347 | tmpMsg.clientId = msg.clientId; 348 | 349 | NetworkPlayer player = new NetworkPlayer(msg.clientId); 350 | this.players.Add(player); 351 | player.Spawn(tmpMsg, this.playerPrefab); 352 | player.SetPlayerId(msg.clientId); 353 | } 354 | } 355 | else 356 | { 357 | Debug.Log("Player doesn't exists anymore, don't take in input: " + msg.clientId); 358 | } 359 | } 360 | } 361 | messagesToProcess.Clear(); 362 | } 363 | 364 | public void DisconnectAll() 365 | { 366 | this.server.DisconnectAll(); 367 | } 368 | 369 | public void UpdateFleetIQ(bool serverTerminated = false) 370 | { 371 | // Only update if we're ready 372 | if(this.server.IsReady()) 373 | { 374 | Console.WriteLine("Server Ready, updating to FleetIQ"); 375 | 376 | var utilizationStatus = GameServerUtilizationStatus.AVAILABLE; 377 | 378 | // If we have a player connected, we're utilized (backend has claimed this game server for 2 players) 379 | if (this.server.GetPlayerCount() > 0) 380 | { 381 | utilizationStatus = GameServerUtilizationStatus.UTILIZED; 382 | } 383 | 384 | var gameLiftConfig = new AmazonGameLiftConfig { RegionEndpoint = this.regionEndpoint }; 385 | var gameLiftClient = new AmazonGameLiftClient(gameLiftConfig); 386 | var updateGameServerRequest = new UpdateGameServerRequest(); 387 | updateGameServerRequest.GameServerGroupName = Server.fleetIqGameServerGroup; 388 | updateGameServerRequest.GameServerId = this.gameServerId; 389 | updateGameServerRequest.HealthCheck = GameServerHealthCheck.HEALTHY; 390 | updateGameServerRequest.UtilizationStatus = utilizationStatus; 391 | 392 | gameLiftClient.UpdateGameServerAsync(updateGameServerRequest); 393 | 394 | Console.WriteLine("FleetIQ Updat sent!"); 395 | } 396 | } 397 | 398 | } 399 | 400 | // *** SERVER NETWORK LOGIC *** // 401 | 402 | public class NetworkServer 403 | { 404 | private TcpListener listener; 405 | // Clients are stored as a dictionary of the TCPCLient and the ClientID 406 | private Dictionary clients = new Dictionary(); 407 | private List readyClients = new List(); 408 | private List clientsToRemove = new List(); 409 | 410 | private Server server = null; 411 | 412 | private bool ready = false; 413 | 414 | public int GetPlayerCount() { return clients.Count; } 415 | public bool IsReady() { return this.ready; } 416 | 417 | // Ends the game session for all and disconnects the players 418 | public void TerminateGameSession() 419 | { 420 | Console.WriteLine("Terminating session and Task"); 421 | // Let FleetIQ know we are done 422 | var gameLiftConfig = new AmazonGameLiftConfig { RegionEndpoint = this.server.regionEndpoint }; 423 | var gameLiftClient = new AmazonGameLiftClient(gameLiftConfig); 424 | var deregisterGameServerRequest = new DeregisterGameServerRequest(); 425 | deregisterGameServerRequest.GameServerGroupName = Server.fleetIqGameServerGroup; 426 | deregisterGameServerRequest.GameServerId = this.server.GetGameServerId(); 427 | var response = gameLiftClient.DeregisterGameServerAsync(deregisterGameServerRequest); 428 | response.Wait(); 429 | Console.WriteLine("Deregistered from FleetIQ, terminate Task..."); 430 | Application.Quit(); 431 | } 432 | 433 | public NetworkServer(Server server) 434 | { 435 | this.server = server; 436 | 437 | //Start the TCP server 438 | int port = Server.port; 439 | Debug.Log("Starting server on port " + port); 440 | listener = new TcpListener(IPAddress.Any, 1935); //We listen to 1935 on all servers, Bridge networking will map this to a dynamic port 441 | Debug.Log("Listening at: " + listener.LocalEndpoint.ToString()); 442 | listener.Start(); 443 | this.ready = true; 444 | } 445 | 446 | // Checks if socket is still connected 447 | private bool IsSocketConnected(TcpClient client) 448 | { 449 | var bClosed = false; 450 | 451 | // Detect if client disconnected 452 | if (client.Client.Poll(0, SelectMode.SelectRead)) 453 | { 454 | byte[] buff = new byte[1]; 455 | if (client.Client.Receive(buff, SocketFlags.Peek) == 0) 456 | { 457 | // Client disconnected 458 | bClosed = true; 459 | } 460 | } 461 | 462 | return !bClosed; 463 | } 464 | 465 | public void Update() 466 | { 467 | // Are there any new connections pending? 468 | if (listener.Pending()) 469 | { 470 | System.Console.WriteLine("Client pending.."); 471 | TcpClient client = listener.AcceptTcpClient(); 472 | client.NoDelay = true; // Use No Delay to send small messages immediately. UDP should be used for even faster messaging 473 | System.Console.WriteLine("Client accepted."); 474 | 475 | // We have a maximum of 2 clients per game 476 | if(this.clients.Count < Server.maxPlayers) 477 | { 478 | // Add client and give it the Id of the value of rollingPlayerId 479 | this.clients.Add(client, this.server.rollingPlayerId); 480 | this.server.rollingPlayerId++; 481 | return; 482 | } 483 | else 484 | { 485 | // game already full, reject the connection 486 | try 487 | { 488 | SimpleMessage message = new SimpleMessage(MessageType.Reject, "game already full"); 489 | NetworkProtocol.Send(client, message); 490 | } 491 | catch (SocketException) { } 492 | } 493 | 494 | } 495 | 496 | // Iterate through clients and check if they have new messages or are disconnected 497 | int playerIdx = 0; 498 | foreach (var client in this.clients) 499 | { 500 | var tcpClient = client.Key; 501 | try 502 | { 503 | if (tcpClient == null) continue; 504 | if (this.IsSocketConnected(tcpClient) == false) 505 | { 506 | System.Console.WriteLine("Client not connected anymore"); 507 | this.clientsToRemove.Add(tcpClient); 508 | } 509 | var messages = NetworkProtocol.Receive(tcpClient); 510 | foreach(SimpleMessage message in messages) 511 | { 512 | System.Console.WriteLine("Received message: " + message.message + " type: " + message.messageType); 513 | bool disconnect = HandleMessage(playerIdx, tcpClient, message); 514 | if (disconnect) 515 | this.clientsToRemove.Add(tcpClient); 516 | } 517 | } 518 | catch (Exception e) 519 | { 520 | System.Console.WriteLine("Error receiving from a client: " + e.Message); 521 | this.clientsToRemove.Add(tcpClient); 522 | } 523 | playerIdx++; 524 | } 525 | 526 | //Remove dead clients 527 | foreach (var clientToRemove in this.clientsToRemove) 528 | { 529 | try 530 | { 531 | this.RemoveClient(clientToRemove); 532 | } 533 | catch (Exception e) 534 | { 535 | System.Console.WriteLine("Couldn't remove client: " + e.Message); 536 | } 537 | } 538 | this.clientsToRemove.Clear(); 539 | 540 | //End game if no clients 541 | if(this.server.GameStarted()) 542 | { 543 | if(this.clients.Count <= 0) 544 | { 545 | System.Console.WriteLine("Clients gone, stop session"); 546 | this.TerminateGameSession(); 547 | } 548 | } 549 | } 550 | 551 | public void DisconnectAll() 552 | { 553 | // warn clients 554 | SimpleMessage message = new SimpleMessage(MessageType.Disconnect); 555 | TransmitMessage(message); 556 | // disconnect connections 557 | foreach (var client in this.clients) 558 | { 559 | this.clientsToRemove.Add(client.Key); 560 | } 561 | 562 | //Reset the client lists 563 | this.clients = new Dictionary(); 564 | this.readyClients = new List(); 565 | this.server.players = new List(); 566 | } 567 | 568 | public void TransmitMessage(SimpleMessage msg, int excludeClient) 569 | { 570 | // send the same message to all players 571 | foreach (var client in this.clients) 572 | { 573 | //Skip if this is the excluded client 574 | if (client.Value == excludeClient) 575 | { 576 | continue; 577 | } 578 | 579 | try 580 | { 581 | NetworkProtocol.Send(client.Key, msg); 582 | } 583 | catch (Exception e) 584 | { 585 | this.clientsToRemove.Add(client.Key); 586 | } 587 | } 588 | } 589 | 590 | //Transmit message to multiple clients 591 | public void TransmitMessage(SimpleMessage msg, TcpClient excludeClient = null) 592 | { 593 | // send the same message to all players 594 | foreach (var client in this.clients) 595 | { 596 | //Skip if this is the excluded client 597 | if(excludeClient != null && excludeClient == client.Key) 598 | { 599 | continue; 600 | } 601 | 602 | try 603 | { 604 | NetworkProtocol.Send(client.Key, msg); 605 | } 606 | catch (Exception e) 607 | { 608 | this.clientsToRemove.Add(client.Key); 609 | } 610 | } 611 | } 612 | 613 | private TcpClient SearchClient(int clientId) 614 | { 615 | foreach(var client in this.clients) 616 | { 617 | if(client.Value == clientId) 618 | { 619 | return client.Key; 620 | } 621 | } 622 | return null; 623 | } 624 | 625 | public void SendMessage(int clientId, SimpleMessage msg) 626 | { 627 | try 628 | { 629 | TcpClient client = this.SearchClient(clientId); 630 | SendMessage(client, msg); 631 | } 632 | catch (Exception e) 633 | { 634 | Console.WriteLine("Failed to send message to client: " + clientId); 635 | } 636 | } 637 | //Send message to single client 638 | private void SendMessage(TcpClient client, SimpleMessage msg) 639 | { 640 | try 641 | { 642 | NetworkProtocol.Send(client, msg); 643 | } 644 | catch (Exception e) 645 | { 646 | this.clientsToRemove.Add(client); 647 | } 648 | } 649 | 650 | private bool HandleMessage(int playerIdx, TcpClient client, SimpleMessage msg) 651 | { 652 | if (msg.messageType == MessageType.Disconnect) 653 | { 654 | this.clientsToRemove.Add(client); 655 | return true; 656 | } 657 | else if (msg.messageType == MessageType.Ready) 658 | HandleReady(client); 659 | else if (msg.messageType == MessageType.Spawn) 660 | HandleSpawn(client, msg); 661 | else if (msg.messageType == MessageType.PlayerInput) 662 | HandleMove(client, msg); 663 | 664 | return false; 665 | } 666 | 667 | private void HandleReady(TcpClient client) 668 | { 669 | // start the game once we have at least one client online 670 | this.readyClients.Add(client); 671 | 672 | if (readyClients.Count >= 1) 673 | { 674 | System.Console.WriteLine("Enough clients, let's start the game!"); 675 | this.server.StartGame(); 676 | } 677 | } 678 | 679 | private void HandleSpawn(TcpClient client, SimpleMessage message) 680 | { 681 | // Get client id (this is the value in the dictionary where the TCPClient is the key) 682 | int clientId = this.clients[client]; 683 | 684 | System.Console.WriteLine("Player " + clientId + " spawned with coordinates: " + message.float1 + "," + message.float2 + "," + message.float3); 685 | 686 | // Add client ID 687 | message.clientId = clientId; 688 | 689 | // Add to list to create the gameobject instance on the server 690 | Server.messagesToProcess.Add(message); 691 | } 692 | 693 | private void HandleMove(TcpClient client, SimpleMessage message) 694 | { 695 | // Get client id (this is the value in the dictionary where the TCPClient is the key) 696 | int clientId = this.clients[client]; 697 | 698 | System.Console.WriteLine("Got move from client: " + clientId + " with input: " + message.float1 + "," + message.float2); 699 | 700 | // Add client ID 701 | message.clientId = clientId; 702 | 703 | // Add to list to create the gameobject instance on the server 704 | Server.messagesToProcess.Add(message); 705 | } 706 | 707 | private void RemoveClient(TcpClient client) 708 | { 709 | //Let the other clients know the player was removed 710 | int clientId = this.clients[client]; 711 | 712 | SimpleMessage message = new SimpleMessage(MessageType.PlayerLeft); 713 | message.clientId = clientId; 714 | TransmitMessage(message, client); 715 | 716 | // Disconnect and remove 717 | this.DisconnectPlayer(client); 718 | this.clients.Remove(client); 719 | this.readyClients.Remove(client); 720 | this.server.RemovePlayer(clientId); 721 | } 722 | 723 | private void DisconnectPlayer(TcpClient client) 724 | { 725 | try 726 | { 727 | // remove the client and close the connection 728 | if (client != null) 729 | { 730 | NetworkStream stream = client.GetStream(); 731 | stream.Close(); 732 | client.Close(); 733 | } 734 | } 735 | catch (Exception e) 736 | { 737 | System.Console.WriteLine("Failed to disconnect player: " + e.Message); 738 | } 739 | } 740 | #endif 741 | } 742 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/Server/Server.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c7e6931ff73724c35986212b1c064457 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/UIManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | using UnityEngine; 5 | 6 | public class UIManager : MonoBehaviour 7 | { 8 | public UnityEngine.UI.Text textBox; 9 | 10 | public void SetTextBox(string text) 11 | { 12 | this.textBox.text = text; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /UnityProject/Assets/Scripts/UIManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5557e1e5c65b14db5b0569c1f14d61f7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /UnityProject/Assets/link.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /UnityProject/Assets/link.xml.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 37e692dc9cc93457e9740d795fa72e2b 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /UnityProject/Packages/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "com.unity.collab-proxy": "1.2.16", 4 | "com.unity.ext.nunit": "1.0.0", 5 | "com.unity.ide.rider": "1.1.4", 6 | "com.unity.ide.vscode": "1.2.0", 7 | "com.unity.test-framework": "1.1.14", 8 | "com.unity.textmeshpro": "2.0.1", 9 | "com.unity.timeline": "1.2.6", 10 | "com.unity.ugui": "1.0.0", 11 | "com.unity.modules.ai": "1.0.0", 12 | "com.unity.modules.androidjni": "1.0.0", 13 | "com.unity.modules.animation": "1.0.0", 14 | "com.unity.modules.assetbundle": "1.0.0", 15 | "com.unity.modules.audio": "1.0.0", 16 | "com.unity.modules.cloth": "1.0.0", 17 | "com.unity.modules.director": "1.0.0", 18 | "com.unity.modules.imageconversion": "1.0.0", 19 | "com.unity.modules.imgui": "1.0.0", 20 | "com.unity.modules.jsonserialize": "1.0.0", 21 | "com.unity.modules.particlesystem": "1.0.0", 22 | "com.unity.modules.physics": "1.0.0", 23 | "com.unity.modules.physics2d": "1.0.0", 24 | "com.unity.modules.screencapture": "1.0.0", 25 | "com.unity.modules.terrain": "1.0.0", 26 | "com.unity.modules.terrainphysics": "1.0.0", 27 | "com.unity.modules.tilemap": "1.0.0", 28 | "com.unity.modules.ui": "1.0.0", 29 | "com.unity.modules.uielements": "1.0.0", 30 | "com.unity.modules.umbra": "1.0.0", 31 | "com.unity.modules.unityanalytics": "1.0.0", 32 | "com.unity.modules.unitywebrequest": "1.0.0", 33 | "com.unity.modules.unitywebrequestassetbundle": "1.0.0", 34 | "com.unity.modules.unitywebrequestaudio": "1.0.0", 35 | "com.unity.modules.unitywebrequesttexture": "1.0.0", 36 | "com.unity.modules.unitywebrequestwww": "1.0.0", 37 | "com.unity.modules.vehicles": "1.0.0", 38 | "com.unity.modules.video": "1.0.0", 39 | "com.unity.modules.vr": "1.0.0", 40 | "com.unity.modules.wind": "1.0.0", 41 | "com.unity.modules.xr": "1.0.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /UnityProject/ProjectSettings/AudioManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!11 &1 4 | AudioManager: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_Volume: 1 8 | Rolloff Scale: 1 9 | Doppler Factor: 1 10 | Default Speaker Mode: 2 11 | m_SampleRate: 0 12 | m_DSPBufferSize: 1024 13 | m_VirtualVoiceCount: 512 14 | m_RealVoiceCount: 32 15 | m_SpatializerPlugin: 16 | m_AmbisonicDecoderPlugin: 17 | m_DisableAudio: 0 18 | m_VirtualizeEffects: 1 19 | m_RequestedDSPBufferSize: 1024 20 | -------------------------------------------------------------------------------- /UnityProject/ProjectSettings/ClusterInputManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!236 &1 4 | ClusterInputManager: 5 | m_ObjectHideFlags: 0 6 | m_Inputs: [] 7 | -------------------------------------------------------------------------------- /UnityProject/ProjectSettings/DynamicsManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!55 &1 4 | PhysicsManager: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 11 7 | m_Gravity: {x: 0, y: -9.81, z: 0} 8 | m_DefaultMaterial: {fileID: 0} 9 | m_BounceThreshold: 2 10 | m_SleepThreshold: 0.005 11 | m_DefaultContactOffset: 0.01 12 | m_DefaultSolverIterations: 6 13 | m_DefaultSolverVelocityIterations: 1 14 | m_QueriesHitBackfaces: 0 15 | m_QueriesHitTriggers: 1 16 | m_EnableAdaptiveForce: 0 17 | m_ClothInterCollisionDistance: 0 18 | m_ClothInterCollisionStiffness: 0 19 | m_ContactsGeneration: 1 20 | m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 21 | m_AutoSimulation: 1 22 | m_AutoSyncTransforms: 0 23 | m_ReuseCollisionCallbacks: 1 24 | m_ClothInterCollisionSettingsToggle: 0 25 | m_ContactPairsMode: 0 26 | m_BroadphaseType: 0 27 | m_WorldBounds: 28 | m_Center: {x: 0, y: 0, z: 0} 29 | m_Extent: {x: 250, y: 250, z: 250} 30 | m_WorldSubdivisions: 8 31 | m_FrictionType: 0 32 | m_EnableEnhancedDeterminism: 0 33 | m_EnableUnifiedHeightmaps: 1 34 | m_DefaultMaxAngluarSpeed: 7 35 | -------------------------------------------------------------------------------- /UnityProject/ProjectSettings/EditorBuildSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!1045 &1 4 | EditorBuildSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_Scenes: 8 | - enabled: 1 9 | path: Assets/Scenes/GameWorld.unity 10 | guid: 9fc0d4010bbf28b4594072e72b8655ab 11 | m_configObjects: {} 12 | -------------------------------------------------------------------------------- /UnityProject/ProjectSettings/EditorSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!159 &1 4 | EditorSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 9 7 | m_ExternalVersionControlSupport: Hidden Meta Files 8 | m_SerializationMode: 2 9 | m_LineEndingsForNewScripts: 2 10 | m_DefaultBehaviorMode: 0 11 | m_PrefabRegularEnvironment: {fileID: 0} 12 | m_PrefabUIEnvironment: {fileID: 0} 13 | m_SpritePackerMode: 0 14 | m_SpritePackerPaddingPower: 1 15 | m_EtcTextureCompressorBehavior: 1 16 | m_EtcTextureFastCompressor: 1 17 | m_EtcTextureNormalCompressor: 2 18 | m_EtcTextureBestCompressor: 4 19 | m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef;rsp;asmref 20 | m_ProjectGenerationRootNamespace: 21 | m_CollabEditorSettings: 22 | inProgressEnabled: 1 23 | m_EnableTextureStreamingInEditMode: 1 24 | m_EnableTextureStreamingInPlayMode: 1 25 | m_AsyncShaderCompilation: 1 26 | m_EnterPlayModeOptionsEnabled: 0 27 | m_EnterPlayModeOptions: 3 28 | m_ShowLightmapResolutionOverlay: 1 29 | m_UseLegacyProbeSampleCount: 1 30 | m_AssetPipelineMode: 1 31 | m_CacheServerMode: 0 32 | m_CacheServerEndpoint: 33 | m_CacheServerNamespacePrefix: default 34 | m_CacheServerEnableDownload: 1 35 | m_CacheServerEnableUpload: 1 36 | -------------------------------------------------------------------------------- /UnityProject/ProjectSettings/GraphicsSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!30 &1 4 | GraphicsSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 13 7 | m_Deferred: 8 | m_Mode: 1 9 | m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0} 10 | m_DeferredReflections: 11 | m_Mode: 1 12 | m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0} 13 | m_ScreenSpaceShadows: 14 | m_Mode: 1 15 | m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0} 16 | m_LegacyDeferred: 17 | m_Mode: 1 18 | m_Shader: {fileID: 63, guid: 0000000000000000f000000000000000, type: 0} 19 | m_DepthNormals: 20 | m_Mode: 1 21 | m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0} 22 | m_MotionVectors: 23 | m_Mode: 1 24 | m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0} 25 | m_LightHalo: 26 | m_Mode: 1 27 | m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0} 28 | m_LensFlare: 29 | m_Mode: 1 30 | m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0} 31 | m_AlwaysIncludedShaders: 32 | - {fileID: 7, guid: 0000000000000000f000000000000000, type: 0} 33 | - {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0} 34 | - {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0} 35 | - {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0} 36 | - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0} 37 | - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0} 38 | - {fileID: 16000, guid: 0000000000000000f000000000000000, type: 0} 39 | - {fileID: 16001, guid: 0000000000000000f000000000000000, type: 0} 40 | - {fileID: 17000, guid: 0000000000000000f000000000000000, type: 0} 41 | m_PreloadedShaders: [] 42 | m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, 43 | type: 0} 44 | m_CustomRenderPipeline: {fileID: 0} 45 | m_TransparencySortMode: 0 46 | m_TransparencySortAxis: {x: 0, y: 0, z: 1} 47 | m_DefaultRenderingPath: 1 48 | m_DefaultMobileRenderingPath: 1 49 | m_TierSettings: [] 50 | m_LightmapStripping: 0 51 | m_FogStripping: 0 52 | m_InstancingStripping: 0 53 | m_LightmapKeepPlain: 1 54 | m_LightmapKeepDirCombined: 1 55 | m_LightmapKeepDynamicPlain: 1 56 | m_LightmapKeepDynamicDirCombined: 1 57 | m_LightmapKeepShadowMask: 1 58 | m_LightmapKeepSubtractive: 1 59 | m_FogKeepLinear: 1 60 | m_FogKeepExp: 1 61 | m_FogKeepExp2: 1 62 | m_AlbedoSwatchInfos: [] 63 | m_LightsUseLinearIntensity: 0 64 | m_LightsUseColorTemperature: 0 65 | m_LogWhenShaderIsCompiled: 0 66 | m_AllowEnlightenSupportForUpgradedProject: 1 67 | -------------------------------------------------------------------------------- /UnityProject/ProjectSettings/InputManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!13 &1 4 | InputManager: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_Axes: 8 | - serializedVersion: 3 9 | m_Name: Horizontal 10 | descriptiveName: 11 | descriptiveNegativeName: 12 | negativeButton: left 13 | positiveButton: right 14 | altNegativeButton: a 15 | altPositiveButton: d 16 | gravity: 3 17 | dead: 0.001 18 | sensitivity: 3 19 | snap: 1 20 | invert: 0 21 | type: 0 22 | axis: 0 23 | joyNum: 0 24 | - serializedVersion: 3 25 | m_Name: Vertical 26 | descriptiveName: 27 | descriptiveNegativeName: 28 | negativeButton: down 29 | positiveButton: up 30 | altNegativeButton: s 31 | altPositiveButton: w 32 | gravity: 3 33 | dead: 0.001 34 | sensitivity: 3 35 | snap: 1 36 | invert: 0 37 | type: 0 38 | axis: 0 39 | joyNum: 0 40 | - serializedVersion: 3 41 | m_Name: Fire1 42 | descriptiveName: 43 | descriptiveNegativeName: 44 | negativeButton: 45 | positiveButton: left ctrl 46 | altNegativeButton: 47 | altPositiveButton: mouse 0 48 | gravity: 1000 49 | dead: 0.001 50 | sensitivity: 1000 51 | snap: 0 52 | invert: 0 53 | type: 0 54 | axis: 0 55 | joyNum: 0 56 | - serializedVersion: 3 57 | m_Name: Fire2 58 | descriptiveName: 59 | descriptiveNegativeName: 60 | negativeButton: 61 | positiveButton: left alt 62 | altNegativeButton: 63 | altPositiveButton: mouse 1 64 | gravity: 1000 65 | dead: 0.001 66 | sensitivity: 1000 67 | snap: 0 68 | invert: 0 69 | type: 0 70 | axis: 0 71 | joyNum: 0 72 | - serializedVersion: 3 73 | m_Name: Fire3 74 | descriptiveName: 75 | descriptiveNegativeName: 76 | negativeButton: 77 | positiveButton: left shift 78 | altNegativeButton: 79 | altPositiveButton: mouse 2 80 | gravity: 1000 81 | dead: 0.001 82 | sensitivity: 1000 83 | snap: 0 84 | invert: 0 85 | type: 0 86 | axis: 0 87 | joyNum: 0 88 | - serializedVersion: 3 89 | m_Name: Jump 90 | descriptiveName: 91 | descriptiveNegativeName: 92 | negativeButton: 93 | positiveButton: space 94 | altNegativeButton: 95 | altPositiveButton: 96 | gravity: 1000 97 | dead: 0.001 98 | sensitivity: 1000 99 | snap: 0 100 | invert: 0 101 | type: 0 102 | axis: 0 103 | joyNum: 0 104 | - serializedVersion: 3 105 | m_Name: Mouse X 106 | descriptiveName: 107 | descriptiveNegativeName: 108 | negativeButton: 109 | positiveButton: 110 | altNegativeButton: 111 | altPositiveButton: 112 | gravity: 0 113 | dead: 0 114 | sensitivity: 0.1 115 | snap: 0 116 | invert: 0 117 | type: 1 118 | axis: 0 119 | joyNum: 0 120 | - serializedVersion: 3 121 | m_Name: Mouse Y 122 | descriptiveName: 123 | descriptiveNegativeName: 124 | negativeButton: 125 | positiveButton: 126 | altNegativeButton: 127 | altPositiveButton: 128 | gravity: 0 129 | dead: 0 130 | sensitivity: 0.1 131 | snap: 0 132 | invert: 0 133 | type: 1 134 | axis: 1 135 | joyNum: 0 136 | - serializedVersion: 3 137 | m_Name: Mouse ScrollWheel 138 | descriptiveName: 139 | descriptiveNegativeName: 140 | negativeButton: 141 | positiveButton: 142 | altNegativeButton: 143 | altPositiveButton: 144 | gravity: 0 145 | dead: 0 146 | sensitivity: 0.1 147 | snap: 0 148 | invert: 0 149 | type: 1 150 | axis: 2 151 | joyNum: 0 152 | - serializedVersion: 3 153 | m_Name: Horizontal 154 | descriptiveName: 155 | descriptiveNegativeName: 156 | negativeButton: 157 | positiveButton: 158 | altNegativeButton: 159 | altPositiveButton: 160 | gravity: 0 161 | dead: 0.19 162 | sensitivity: 1 163 | snap: 0 164 | invert: 0 165 | type: 2 166 | axis: 0 167 | joyNum: 0 168 | - serializedVersion: 3 169 | m_Name: Vertical 170 | descriptiveName: 171 | descriptiveNegativeName: 172 | negativeButton: 173 | positiveButton: 174 | altNegativeButton: 175 | altPositiveButton: 176 | gravity: 0 177 | dead: 0.19 178 | sensitivity: 1 179 | snap: 0 180 | invert: 1 181 | type: 2 182 | axis: 1 183 | joyNum: 0 184 | - serializedVersion: 3 185 | m_Name: Fire1 186 | descriptiveName: 187 | descriptiveNegativeName: 188 | negativeButton: 189 | positiveButton: joystick button 0 190 | altNegativeButton: 191 | altPositiveButton: 192 | gravity: 1000 193 | dead: 0.001 194 | sensitivity: 1000 195 | snap: 0 196 | invert: 0 197 | type: 0 198 | axis: 0 199 | joyNum: 0 200 | - serializedVersion: 3 201 | m_Name: Fire2 202 | descriptiveName: 203 | descriptiveNegativeName: 204 | negativeButton: 205 | positiveButton: joystick button 1 206 | altNegativeButton: 207 | altPositiveButton: 208 | gravity: 1000 209 | dead: 0.001 210 | sensitivity: 1000 211 | snap: 0 212 | invert: 0 213 | type: 0 214 | axis: 0 215 | joyNum: 0 216 | - serializedVersion: 3 217 | m_Name: Fire3 218 | descriptiveName: 219 | descriptiveNegativeName: 220 | negativeButton: 221 | positiveButton: joystick button 2 222 | altNegativeButton: 223 | altPositiveButton: 224 | gravity: 1000 225 | dead: 0.001 226 | sensitivity: 1000 227 | snap: 0 228 | invert: 0 229 | type: 0 230 | axis: 0 231 | joyNum: 0 232 | - serializedVersion: 3 233 | m_Name: Jump 234 | descriptiveName: 235 | descriptiveNegativeName: 236 | negativeButton: 237 | positiveButton: joystick button 3 238 | altNegativeButton: 239 | altPositiveButton: 240 | gravity: 1000 241 | dead: 0.001 242 | sensitivity: 1000 243 | snap: 0 244 | invert: 0 245 | type: 0 246 | axis: 0 247 | joyNum: 0 248 | - serializedVersion: 3 249 | m_Name: Submit 250 | descriptiveName: 251 | descriptiveNegativeName: 252 | negativeButton: 253 | positiveButton: return 254 | altNegativeButton: 255 | altPositiveButton: joystick button 0 256 | gravity: 1000 257 | dead: 0.001 258 | sensitivity: 1000 259 | snap: 0 260 | invert: 0 261 | type: 0 262 | axis: 0 263 | joyNum: 0 264 | - serializedVersion: 3 265 | m_Name: Submit 266 | descriptiveName: 267 | descriptiveNegativeName: 268 | negativeButton: 269 | positiveButton: enter 270 | altNegativeButton: 271 | altPositiveButton: space 272 | gravity: 1000 273 | dead: 0.001 274 | sensitivity: 1000 275 | snap: 0 276 | invert: 0 277 | type: 0 278 | axis: 0 279 | joyNum: 0 280 | - serializedVersion: 3 281 | m_Name: Cancel 282 | descriptiveName: 283 | descriptiveNegativeName: 284 | negativeButton: 285 | positiveButton: escape 286 | altNegativeButton: 287 | altPositiveButton: joystick button 1 288 | gravity: 1000 289 | dead: 0.001 290 | sensitivity: 1000 291 | snap: 0 292 | invert: 0 293 | type: 0 294 | axis: 0 295 | joyNum: 0 296 | -------------------------------------------------------------------------------- /UnityProject/ProjectSettings/NavMeshAreas.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!126 &1 4 | NavMeshProjectSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | areas: 8 | - name: Walkable 9 | cost: 1 10 | - name: Not Walkable 11 | cost: 1 12 | - name: Jump 13 | cost: 2 14 | - name: 15 | cost: 1 16 | - name: 17 | cost: 1 18 | - name: 19 | cost: 1 20 | - name: 21 | cost: 1 22 | - name: 23 | cost: 1 24 | - name: 25 | cost: 1 26 | - name: 27 | cost: 1 28 | - name: 29 | cost: 1 30 | - name: 31 | cost: 1 32 | - name: 33 | cost: 1 34 | - name: 35 | cost: 1 36 | - name: 37 | cost: 1 38 | - name: 39 | cost: 1 40 | - name: 41 | cost: 1 42 | - name: 43 | cost: 1 44 | - name: 45 | cost: 1 46 | - name: 47 | cost: 1 48 | - name: 49 | cost: 1 50 | - name: 51 | cost: 1 52 | - name: 53 | cost: 1 54 | - name: 55 | cost: 1 56 | - name: 57 | cost: 1 58 | - name: 59 | cost: 1 60 | - name: 61 | cost: 1 62 | - name: 63 | cost: 1 64 | - name: 65 | cost: 1 66 | - name: 67 | cost: 1 68 | - name: 69 | cost: 1 70 | - name: 71 | cost: 1 72 | m_LastAgentTypeID: -887442657 73 | m_Settings: 74 | - serializedVersion: 2 75 | agentTypeID: 0 76 | agentRadius: 0.5 77 | agentHeight: 2 78 | agentSlope: 45 79 | agentClimb: 0.75 80 | ledgeDropHeight: 0 81 | maxJumpAcrossDistance: 0 82 | minRegionArea: 2 83 | manualCellSize: 0 84 | cellSize: 0.16666667 85 | manualTileSize: 0 86 | tileSize: 256 87 | accuratePlacement: 0 88 | debug: 89 | m_Flags: 0 90 | m_SettingNames: 91 | - Humanoid 92 | -------------------------------------------------------------------------------- /UnityProject/ProjectSettings/Physics2DSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!19 &1 4 | Physics2DSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 4 7 | m_Gravity: {x: 0, y: -9.81} 8 | m_DefaultMaterial: {fileID: 0} 9 | m_VelocityIterations: 8 10 | m_PositionIterations: 3 11 | m_VelocityThreshold: 1 12 | m_MaxLinearCorrection: 0.2 13 | m_MaxAngularCorrection: 8 14 | m_MaxTranslationSpeed: 100 15 | m_MaxRotationSpeed: 360 16 | m_BaumgarteScale: 0.2 17 | m_BaumgarteTimeOfImpactScale: 0.75 18 | m_TimeToSleep: 0.5 19 | m_LinearSleepTolerance: 0.01 20 | m_AngularSleepTolerance: 2 21 | m_DefaultContactOffset: 0.01 22 | m_JobOptions: 23 | serializedVersion: 2 24 | useMultithreading: 0 25 | useConsistencySorting: 0 26 | m_InterpolationPosesPerJob: 100 27 | m_NewContactsPerJob: 30 28 | m_CollideContactsPerJob: 100 29 | m_ClearFlagsPerJob: 200 30 | m_ClearBodyForcesPerJob: 200 31 | m_SyncDiscreteFixturesPerJob: 50 32 | m_SyncContinuousFixturesPerJob: 50 33 | m_FindNearestContactsPerJob: 100 34 | m_UpdateTriggerContactsPerJob: 100 35 | m_IslandSolverCostThreshold: 100 36 | m_IslandSolverBodyCostScale: 1 37 | m_IslandSolverContactCostScale: 10 38 | m_IslandSolverJointCostScale: 10 39 | m_IslandSolverBodiesPerJob: 50 40 | m_IslandSolverContactsPerJob: 50 41 | m_AutoSimulation: 1 42 | m_QueriesHitTriggers: 1 43 | m_QueriesStartInColliders: 1 44 | m_CallbacksOnDisable: 1 45 | m_ReuseCollisionCallbacks: 1 46 | m_AutoSyncTransforms: 0 47 | m_AlwaysShowColliders: 0 48 | m_ShowColliderSleep: 1 49 | m_ShowColliderContacts: 0 50 | m_ShowColliderAABB: 0 51 | m_ContactArrowScale: 0.2 52 | m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412} 53 | m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432} 54 | m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745} 55 | m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804} 56 | m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 57 | -------------------------------------------------------------------------------- /UnityProject/ProjectSettings/PresetManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!1386491679 &1 4 | PresetManager: 5 | m_ObjectHideFlags: 0 6 | m_DefaultList: [] 7 | -------------------------------------------------------------------------------- /UnityProject/ProjectSettings/ProjectSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!129 &1 4 | PlayerSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 20 7 | productGUID: 9104dbce35b504299b3cca29249ac11f 8 | AndroidProfiler: 0 9 | AndroidFilterTouchesWhenObscured: 0 10 | AndroidEnableSustainedPerformanceMode: 0 11 | defaultScreenOrientation: 4 12 | targetDevice: 2 13 | useOnDemandResources: 0 14 | accelerometerFrequency: 60 15 | companyName: DefaultCompany 16 | productName: FleetIQGameServerExample 17 | defaultCursor: {fileID: 0} 18 | cursorHotspot: {x: 0, y: 0} 19 | m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} 20 | m_ShowUnitySplashScreen: 1 21 | m_ShowUnitySplashLogo: 1 22 | m_SplashScreenOverlayOpacity: 1 23 | m_SplashScreenAnimation: 1 24 | m_SplashScreenLogoStyle: 1 25 | m_SplashScreenDrawMode: 0 26 | m_SplashScreenBackgroundAnimationZoom: 1 27 | m_SplashScreenLogoAnimationZoom: 1 28 | m_SplashScreenBackgroundLandscapeAspect: 1 29 | m_SplashScreenBackgroundPortraitAspect: 1 30 | m_SplashScreenBackgroundLandscapeUvs: 31 | serializedVersion: 2 32 | x: 0 33 | y: 0 34 | width: 1 35 | height: 1 36 | m_SplashScreenBackgroundPortraitUvs: 37 | serializedVersion: 2 38 | x: 0 39 | y: 0 40 | width: 1 41 | height: 1 42 | m_SplashScreenLogos: [] 43 | m_VirtualRealitySplashScreen: {fileID: 0} 44 | m_HolographicTrackingLossScreen: {fileID: 0} 45 | defaultScreenWidth: 1024 46 | defaultScreenHeight: 768 47 | defaultScreenWidthWeb: 960 48 | defaultScreenHeightWeb: 600 49 | m_StereoRenderingPath: 0 50 | m_ActiveColorSpace: 0 51 | m_MTRendering: 1 52 | m_StackTraceTypes: 010000000100000001000000010000000100000001000000 53 | iosShowActivityIndicatorOnLoading: -1 54 | androidShowActivityIndicatorOnLoading: -1 55 | iosUseCustomAppBackgroundBehavior: 0 56 | iosAllowHTTPDownload: 1 57 | allowedAutorotateToPortrait: 1 58 | allowedAutorotateToPortraitUpsideDown: 1 59 | allowedAutorotateToLandscapeRight: 1 60 | allowedAutorotateToLandscapeLeft: 1 61 | useOSAutorotation: 1 62 | use32BitDisplayBuffer: 1 63 | preserveFramebufferAlpha: 0 64 | disableDepthAndStencilBuffers: 0 65 | androidStartInFullscreen: 1 66 | androidRenderOutsideSafeArea: 1 67 | androidUseSwappy: 0 68 | androidBlitType: 0 69 | defaultIsNativeResolution: 1 70 | macRetinaSupport: 1 71 | runInBackground: 1 72 | captureSingleScreen: 0 73 | muteOtherAudioSources: 0 74 | Prepare IOS For Recording: 0 75 | Force IOS Speakers When Recording: 0 76 | deferSystemGesturesMode: 0 77 | hideHomeButton: 0 78 | submitAnalytics: 0 79 | usePlayerLog: 1 80 | bakeCollisionMeshes: 0 81 | forceSingleInstance: 0 82 | useFlipModelSwapchain: 1 83 | resizableWindow: 1 84 | useMacAppStoreValidation: 0 85 | macAppStoreCategory: public.app-category.games 86 | gpuSkinning: 1 87 | xboxPIXTextureCapture: 0 88 | xboxEnableAvatar: 0 89 | xboxEnableKinect: 0 90 | xboxEnableKinectAutoTracking: 0 91 | xboxEnableFitness: 0 92 | visibleInBackground: 1 93 | allowFullscreenSwitch: 1 94 | fullscreenMode: 3 95 | xboxSpeechDB: 0 96 | xboxEnableHeadOrientation: 0 97 | xboxEnableGuest: 0 98 | xboxEnablePIXSampling: 0 99 | metalFramebufferOnly: 0 100 | xboxOneResolution: 0 101 | xboxOneSResolution: 0 102 | xboxOneXResolution: 3 103 | xboxOneMonoLoggingLevel: 0 104 | xboxOneLoggingLevel: 1 105 | xboxOneDisableEsram: 0 106 | xboxOneEnableTypeOptimization: 0 107 | xboxOnePresentImmediateThreshold: 0 108 | switchQueueCommandMemory: 0 109 | switchQueueControlMemory: 16384 110 | switchQueueComputeMemory: 262144 111 | switchNVNShaderPoolsGranularity: 33554432 112 | switchNVNDefaultPoolsGranularity: 16777216 113 | switchNVNOtherPoolsGranularity: 16777216 114 | stadiaPresentMode: 0 115 | stadiaTargetFramerate: 0 116 | vulkanNumSwapchainBuffers: 3 117 | vulkanEnableSetSRGBWrite: 1 118 | m_SupportedAspectRatios: 119 | 4:3: 1 120 | 5:4: 1 121 | 16:10: 1 122 | 16:9: 1 123 | Others: 1 124 | bundleVersion: 0.1 125 | preloadedAssets: [] 126 | metroInputSource: 0 127 | wsaTransparentSwapchain: 0 128 | m_HolographicPauseOnTrackingLoss: 1 129 | xboxOneDisableKinectGpuReservation: 1 130 | xboxOneEnable7thCore: 1 131 | vrSettings: 132 | cardboard: 133 | depthFormat: 0 134 | enableTransitionView: 0 135 | daydream: 136 | depthFormat: 0 137 | useSustainedPerformanceMode: 0 138 | enableVideoLayer: 0 139 | useProtectedVideoMemory: 0 140 | minimumSupportedHeadTracking: 0 141 | maximumSupportedHeadTracking: 1 142 | hololens: 143 | depthFormat: 1 144 | depthBufferSharingEnabled: 1 145 | lumin: 146 | depthFormat: 0 147 | frameTiming: 2 148 | enableGLCache: 0 149 | glCacheMaxBlobSize: 524288 150 | glCacheMaxFileSize: 8388608 151 | oculus: 152 | sharedDepthBuffer: 1 153 | dashSupport: 1 154 | lowOverheadMode: 0 155 | protectedContext: 0 156 | v2Signing: 0 157 | enable360StereoCapture: 0 158 | isWsaHolographicRemotingEnabled: 0 159 | enableFrameTimingStats: 0 160 | useHDRDisplay: 0 161 | D3DHDRBitDepth: 0 162 | m_ColorGamuts: 00000000 163 | targetPixelDensity: 30 164 | resolutionScalingMode: 0 165 | androidSupportedAspectRatio: 1 166 | androidMaxAspectRatio: 2.1 167 | applicationIdentifier: {} 168 | buildNumber: {} 169 | AndroidBundleVersionCode: 1 170 | AndroidMinSdkVersion: 19 171 | AndroidTargetSdkVersion: 0 172 | AndroidPreferredInstallLocation: 1 173 | aotOptions: 174 | stripEngineCode: 1 175 | iPhoneStrippingLevel: 0 176 | iPhoneScriptCallOptimization: 0 177 | ForceInternetPermission: 0 178 | ForceSDCardPermission: 0 179 | CreateWallpaper: 0 180 | APKExpansionFiles: 0 181 | keepLoadedShadersAlive: 0 182 | StripUnusedMeshComponents: 1 183 | VertexChannelCompressionMask: 4054 184 | iPhoneSdkVersion: 988 185 | iOSTargetOSVersionString: 10.0 186 | tvOSSdkVersion: 0 187 | tvOSRequireExtendedGameController: 0 188 | tvOSTargetOSVersionString: 10.0 189 | uIPrerenderedIcon: 0 190 | uIRequiresPersistentWiFi: 0 191 | uIRequiresFullScreen: 1 192 | uIStatusBarHidden: 1 193 | uIExitOnSuspend: 0 194 | uIStatusBarStyle: 0 195 | appleTVSplashScreen: {fileID: 0} 196 | appleTVSplashScreen2x: {fileID: 0} 197 | tvOSSmallIconLayers: [] 198 | tvOSSmallIconLayers2x: [] 199 | tvOSLargeIconLayers: [] 200 | tvOSLargeIconLayers2x: [] 201 | tvOSTopShelfImageLayers: [] 202 | tvOSTopShelfImageLayers2x: [] 203 | tvOSTopShelfImageWideLayers: [] 204 | tvOSTopShelfImageWideLayers2x: [] 205 | iOSLaunchScreenType: 0 206 | iOSLaunchScreenPortrait: {fileID: 0} 207 | iOSLaunchScreenLandscape: {fileID: 0} 208 | iOSLaunchScreenBackgroundColor: 209 | serializedVersion: 2 210 | rgba: 0 211 | iOSLaunchScreenFillPct: 100 212 | iOSLaunchScreenSize: 100 213 | iOSLaunchScreenCustomXibPath: 214 | iOSLaunchScreeniPadType: 0 215 | iOSLaunchScreeniPadImage: {fileID: 0} 216 | iOSLaunchScreeniPadBackgroundColor: 217 | serializedVersion: 2 218 | rgba: 0 219 | iOSLaunchScreeniPadFillPct: 100 220 | iOSLaunchScreeniPadSize: 100 221 | iOSLaunchScreeniPadCustomXibPath: 222 | iOSUseLaunchScreenStoryboard: 0 223 | iOSLaunchScreenCustomStoryboardPath: 224 | iOSDeviceRequirements: [] 225 | iOSURLSchemes: [] 226 | iOSBackgroundModes: 0 227 | iOSMetalForceHardShadows: 0 228 | metalEditorSupport: 1 229 | metalAPIValidation: 1 230 | iOSRenderExtraFrameOnPause: 0 231 | appleDeveloperTeamID: 232 | iOSManualSigningProvisioningProfileID: 233 | tvOSManualSigningProvisioningProfileID: 234 | iOSManualSigningProvisioningProfileType: 0 235 | tvOSManualSigningProvisioningProfileType: 0 236 | appleEnableAutomaticSigning: 0 237 | iOSRequireARKit: 0 238 | iOSAutomaticallyDetectAndAddCapabilities: 1 239 | appleEnableProMotion: 0 240 | clonedFromGUID: c0afd0d1d80e3634a9dac47e8a0426ea 241 | templatePackageId: com.unity.template.3d@3.1.2 242 | templateDefaultScene: Assets/Scenes/SampleScene.unity 243 | AndroidTargetArchitectures: 1 244 | AndroidSplashScreenScale: 0 245 | androidSplashScreen: {fileID: 0} 246 | AndroidKeystoreName: '{inproject}: ' 247 | AndroidKeyaliasName: 248 | AndroidBuildApkPerCpuArchitecture: 0 249 | AndroidTVCompatibility: 0 250 | AndroidIsGame: 1 251 | AndroidEnableTango: 0 252 | androidEnableBanner: 1 253 | androidUseLowAccuracyLocation: 0 254 | androidUseCustomKeystore: 0 255 | m_AndroidBanners: 256 | - width: 320 257 | height: 180 258 | banner: {fileID: 0} 259 | androidGamepadSupportLevel: 0 260 | AndroidValidateAppBundleSize: 1 261 | AndroidAppBundleSizeToValidate: 150 262 | m_BuildTargetIcons: [] 263 | m_BuildTargetPlatformIcons: [] 264 | m_BuildTargetBatching: 265 | - m_BuildTarget: Standalone 266 | m_StaticBatching: 1 267 | m_DynamicBatching: 0 268 | - m_BuildTarget: tvOS 269 | m_StaticBatching: 1 270 | m_DynamicBatching: 0 271 | - m_BuildTarget: Android 272 | m_StaticBatching: 1 273 | m_DynamicBatching: 0 274 | - m_BuildTarget: iPhone 275 | m_StaticBatching: 1 276 | m_DynamicBatching: 0 277 | - m_BuildTarget: WebGL 278 | m_StaticBatching: 0 279 | m_DynamicBatching: 0 280 | m_BuildTargetGraphicsJobs: 281 | - m_BuildTarget: MacStandaloneSupport 282 | m_GraphicsJobs: 0 283 | - m_BuildTarget: Switch 284 | m_GraphicsJobs: 0 285 | - m_BuildTarget: MetroSupport 286 | m_GraphicsJobs: 0 287 | - m_BuildTarget: AppleTVSupport 288 | m_GraphicsJobs: 0 289 | - m_BuildTarget: BJMSupport 290 | m_GraphicsJobs: 0 291 | - m_BuildTarget: LinuxStandaloneSupport 292 | m_GraphicsJobs: 0 293 | - m_BuildTarget: PS4Player 294 | m_GraphicsJobs: 0 295 | - m_BuildTarget: iOSSupport 296 | m_GraphicsJobs: 0 297 | - m_BuildTarget: WindowsStandaloneSupport 298 | m_GraphicsJobs: 0 299 | - m_BuildTarget: XboxOnePlayer 300 | m_GraphicsJobs: 0 301 | - m_BuildTarget: LuminSupport 302 | m_GraphicsJobs: 0 303 | - m_BuildTarget: AndroidPlayer 304 | m_GraphicsJobs: 0 305 | - m_BuildTarget: WebGLSupport 306 | m_GraphicsJobs: 0 307 | m_BuildTargetGraphicsJobMode: 308 | - m_BuildTarget: PS4Player 309 | m_GraphicsJobMode: 0 310 | - m_BuildTarget: XboxOnePlayer 311 | m_GraphicsJobMode: 0 312 | m_BuildTargetGraphicsAPIs: 313 | - m_BuildTarget: AndroidPlayer 314 | m_APIs: 150000000b000000 315 | m_Automatic: 0 316 | - m_BuildTarget: iOSSupport 317 | m_APIs: 10000000 318 | m_Automatic: 1 319 | - m_BuildTarget: AppleTVSupport 320 | m_APIs: 10000000 321 | m_Automatic: 0 322 | - m_BuildTarget: WebGLSupport 323 | m_APIs: 0b000000 324 | m_Automatic: 1 325 | m_BuildTargetVRSettings: 326 | - m_BuildTarget: Standalone 327 | m_Enabled: 0 328 | m_Devices: 329 | - Oculus 330 | - OpenVR 331 | openGLRequireES31: 0 332 | openGLRequireES31AEP: 0 333 | openGLRequireES32: 0 334 | m_TemplateCustomTags: {} 335 | mobileMTRendering: 336 | Android: 1 337 | iPhone: 1 338 | tvOS: 1 339 | m_BuildTargetGroupLightmapEncodingQuality: [] 340 | m_BuildTargetGroupLightmapSettings: [] 341 | playModeTestRunnerEnabled: 0 342 | runPlayModeTestAsEditModeTest: 0 343 | actionOnDotNetUnhandledException: 1 344 | enableInternalProfiler: 0 345 | logObjCUncaughtExceptions: 1 346 | enableCrashReportAPI: 0 347 | cameraUsageDescription: 348 | locationUsageDescription: 349 | microphoneUsageDescription: 350 | switchNetLibKey: 351 | switchSocketMemoryPoolSize: 6144 352 | switchSocketAllocatorPoolSize: 128 353 | switchSocketConcurrencyLimit: 14 354 | switchScreenResolutionBehavior: 2 355 | switchUseCPUProfiler: 0 356 | switchApplicationID: 0x01004b9000490000 357 | switchNSODependencies: 358 | switchTitleNames_0: 359 | switchTitleNames_1: 360 | switchTitleNames_2: 361 | switchTitleNames_3: 362 | switchTitleNames_4: 363 | switchTitleNames_5: 364 | switchTitleNames_6: 365 | switchTitleNames_7: 366 | switchTitleNames_8: 367 | switchTitleNames_9: 368 | switchTitleNames_10: 369 | switchTitleNames_11: 370 | switchTitleNames_12: 371 | switchTitleNames_13: 372 | switchTitleNames_14: 373 | switchPublisherNames_0: 374 | switchPublisherNames_1: 375 | switchPublisherNames_2: 376 | switchPublisherNames_3: 377 | switchPublisherNames_4: 378 | switchPublisherNames_5: 379 | switchPublisherNames_6: 380 | switchPublisherNames_7: 381 | switchPublisherNames_8: 382 | switchPublisherNames_9: 383 | switchPublisherNames_10: 384 | switchPublisherNames_11: 385 | switchPublisherNames_12: 386 | switchPublisherNames_13: 387 | switchPublisherNames_14: 388 | switchIcons_0: {fileID: 0} 389 | switchIcons_1: {fileID: 0} 390 | switchIcons_2: {fileID: 0} 391 | switchIcons_3: {fileID: 0} 392 | switchIcons_4: {fileID: 0} 393 | switchIcons_5: {fileID: 0} 394 | switchIcons_6: {fileID: 0} 395 | switchIcons_7: {fileID: 0} 396 | switchIcons_8: {fileID: 0} 397 | switchIcons_9: {fileID: 0} 398 | switchIcons_10: {fileID: 0} 399 | switchIcons_11: {fileID: 0} 400 | switchIcons_12: {fileID: 0} 401 | switchIcons_13: {fileID: 0} 402 | switchIcons_14: {fileID: 0} 403 | switchSmallIcons_0: {fileID: 0} 404 | switchSmallIcons_1: {fileID: 0} 405 | switchSmallIcons_2: {fileID: 0} 406 | switchSmallIcons_3: {fileID: 0} 407 | switchSmallIcons_4: {fileID: 0} 408 | switchSmallIcons_5: {fileID: 0} 409 | switchSmallIcons_6: {fileID: 0} 410 | switchSmallIcons_7: {fileID: 0} 411 | switchSmallIcons_8: {fileID: 0} 412 | switchSmallIcons_9: {fileID: 0} 413 | switchSmallIcons_10: {fileID: 0} 414 | switchSmallIcons_11: {fileID: 0} 415 | switchSmallIcons_12: {fileID: 0} 416 | switchSmallIcons_13: {fileID: 0} 417 | switchSmallIcons_14: {fileID: 0} 418 | switchManualHTML: 419 | switchAccessibleURLs: 420 | switchLegalInformation: 421 | switchMainThreadStackSize: 1048576 422 | switchPresenceGroupId: 423 | switchLogoHandling: 0 424 | switchReleaseVersion: 0 425 | switchDisplayVersion: 1.0.0 426 | switchStartupUserAccount: 0 427 | switchTouchScreenUsage: 0 428 | switchSupportedLanguagesMask: 0 429 | switchLogoType: 0 430 | switchApplicationErrorCodeCategory: 431 | switchUserAccountSaveDataSize: 0 432 | switchUserAccountSaveDataJournalSize: 0 433 | switchApplicationAttribute: 0 434 | switchCardSpecSize: -1 435 | switchCardSpecClock: -1 436 | switchRatingsMask: 0 437 | switchRatingsInt_0: 0 438 | switchRatingsInt_1: 0 439 | switchRatingsInt_2: 0 440 | switchRatingsInt_3: 0 441 | switchRatingsInt_4: 0 442 | switchRatingsInt_5: 0 443 | switchRatingsInt_6: 0 444 | switchRatingsInt_7: 0 445 | switchRatingsInt_8: 0 446 | switchRatingsInt_9: 0 447 | switchRatingsInt_10: 0 448 | switchRatingsInt_11: 0 449 | switchRatingsInt_12: 0 450 | switchLocalCommunicationIds_0: 451 | switchLocalCommunicationIds_1: 452 | switchLocalCommunicationIds_2: 453 | switchLocalCommunicationIds_3: 454 | switchLocalCommunicationIds_4: 455 | switchLocalCommunicationIds_5: 456 | switchLocalCommunicationIds_6: 457 | switchLocalCommunicationIds_7: 458 | switchParentalControl: 0 459 | switchAllowsScreenshot: 1 460 | switchAllowsVideoCapturing: 1 461 | switchAllowsRuntimeAddOnContentInstall: 0 462 | switchDataLossConfirmation: 0 463 | switchUserAccountLockEnabled: 0 464 | switchSystemResourceMemory: 16777216 465 | switchSupportedNpadStyles: 22 466 | switchNativeFsCacheSize: 32 467 | switchIsHoldTypeHorizontal: 0 468 | switchSupportedNpadCount: 8 469 | switchSocketConfigEnabled: 0 470 | switchTcpInitialSendBufferSize: 32 471 | switchTcpInitialReceiveBufferSize: 64 472 | switchTcpAutoSendBufferSizeMax: 256 473 | switchTcpAutoReceiveBufferSizeMax: 256 474 | switchUdpSendBufferSize: 9 475 | switchUdpReceiveBufferSize: 42 476 | switchSocketBufferEfficiency: 4 477 | switchSocketInitializeEnabled: 1 478 | switchNetworkInterfaceManagerInitializeEnabled: 1 479 | switchPlayerConnectionEnabled: 1 480 | ps4NPAgeRating: 12 481 | ps4NPTitleSecret: 482 | ps4NPTrophyPackPath: 483 | ps4ParentalLevel: 11 484 | ps4ContentID: ED1633-NPXX51362_00-0000000000000000 485 | ps4Category: 0 486 | ps4MasterVersion: 01.00 487 | ps4AppVersion: 01.00 488 | ps4AppType: 0 489 | ps4ParamSfxPath: 490 | ps4VideoOutPixelFormat: 0 491 | ps4VideoOutInitialWidth: 1920 492 | ps4VideoOutBaseModeInitialWidth: 1920 493 | ps4VideoOutReprojectionRate: 60 494 | ps4PronunciationXMLPath: 495 | ps4PronunciationSIGPath: 496 | ps4BackgroundImagePath: 497 | ps4StartupImagePath: 498 | ps4StartupImagesFolder: 499 | ps4IconImagesFolder: 500 | ps4SaveDataImagePath: 501 | ps4SdkOverride: 502 | ps4BGMPath: 503 | ps4ShareFilePath: 504 | ps4ShareOverlayImagePath: 505 | ps4PrivacyGuardImagePath: 506 | ps4NPtitleDatPath: 507 | ps4RemotePlayKeyAssignment: -1 508 | ps4RemotePlayKeyMappingDir: 509 | ps4PlayTogetherPlayerCount: 0 510 | ps4EnterButtonAssignment: 1 511 | ps4ApplicationParam1: 0 512 | ps4ApplicationParam2: 0 513 | ps4ApplicationParam3: 0 514 | ps4ApplicationParam4: 0 515 | ps4DownloadDataSize: 0 516 | ps4GarlicHeapSize: 2048 517 | ps4ProGarlicHeapSize: 2560 518 | playerPrefsMaxSize: 32768 519 | ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ 520 | ps4pnSessions: 1 521 | ps4pnPresence: 1 522 | ps4pnFriends: 1 523 | ps4pnGameCustomData: 1 524 | playerPrefsSupport: 0 525 | enableApplicationExit: 0 526 | resetTempFolder: 1 527 | restrictedAudioUsageRights: 0 528 | ps4UseResolutionFallback: 0 529 | ps4ReprojectionSupport: 0 530 | ps4UseAudio3dBackend: 0 531 | ps4UseLowGarlicFragmentationMode: 1 532 | ps4SocialScreenEnabled: 0 533 | ps4ScriptOptimizationLevel: 0 534 | ps4Audio3dVirtualSpeakerCount: 14 535 | ps4attribCpuUsage: 0 536 | ps4PatchPkgPath: 537 | ps4PatchLatestPkgPath: 538 | ps4PatchChangeinfoPath: 539 | ps4PatchDayOne: 0 540 | ps4attribUserManagement: 0 541 | ps4attribMoveSupport: 0 542 | ps4attrib3DSupport: 0 543 | ps4attribShareSupport: 0 544 | ps4attribExclusiveVR: 0 545 | ps4disableAutoHideSplash: 0 546 | ps4videoRecordingFeaturesUsed: 0 547 | ps4contentSearchFeaturesUsed: 0 548 | ps4attribEyeToEyeDistanceSettingVR: 0 549 | ps4IncludedModules: [] 550 | ps4attribVROutputEnabled: 0 551 | monoEnv: 552 | splashScreenBackgroundSourceLandscape: {fileID: 0} 553 | splashScreenBackgroundSourcePortrait: {fileID: 0} 554 | blurSplashScreenBackground: 1 555 | spritePackerPolicy: 556 | webGLMemorySize: 16 557 | webGLExceptionSupport: 1 558 | webGLNameFilesAsHashes: 0 559 | webGLDataCaching: 1 560 | webGLDebugSymbols: 0 561 | webGLEmscriptenArgs: 562 | webGLModulesDirectory: 563 | webGLTemplate: APPLICATION:Default 564 | webGLAnalyzeBuildSize: 0 565 | webGLUseEmbeddedResources: 0 566 | webGLCompressionFormat: 1 567 | webGLLinkerTarget: 1 568 | webGLThreadsSupport: 0 569 | webGLWasmStreaming: 0 570 | scriptingDefineSymbols: 571 | 1: SERVER 572 | 4: SERVER 573 | platformArchitecture: {} 574 | scriptingBackend: 575 | Standalone: 0 576 | il2cppCompilerConfiguration: {} 577 | managedStrippingLevel: {} 578 | incrementalIl2cppBuild: {} 579 | allowUnsafeCode: 0 580 | additionalIl2CppArgs: 581 | scriptingRuntimeVersion: 1 582 | gcIncremental: 0 583 | gcWBarrierValidation: 0 584 | apiCompatibilityLevelPerPlatform: 585 | Standalone: 6 586 | iPhone: 3 587 | m_RenderingPath: 1 588 | m_MobileRenderingPath: 1 589 | metroPackageName: Template_3D 590 | metroPackageVersion: 591 | metroCertificatePath: 592 | metroCertificatePassword: 593 | metroCertificateSubject: 594 | metroCertificateIssuer: 595 | metroCertificateNotAfter: 0000000000000000 596 | metroApplicationDescription: Template_3D 597 | wsaImages: {} 598 | metroTileShortName: 599 | metroTileShowName: 0 600 | metroMediumTileShowName: 0 601 | metroLargeTileShowName: 0 602 | metroWideTileShowName: 0 603 | metroSupportStreamingInstall: 0 604 | metroLastRequiredScene: 0 605 | metroDefaultTileSize: 1 606 | metroTileForegroundText: 2 607 | metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} 608 | metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, 609 | a: 1} 610 | metroSplashScreenUseBackgroundColor: 0 611 | platformCapabilities: {} 612 | metroTargetDeviceFamilies: {} 613 | metroFTAName: 614 | metroFTAFileTypes: [] 615 | metroProtocolName: 616 | XboxOneProductId: 617 | XboxOneUpdateKey: 618 | XboxOneSandboxId: 619 | XboxOneContentId: 620 | XboxOneTitleId: 621 | XboxOneSCId: 622 | XboxOneGameOsOverridePath: 623 | XboxOnePackagingOverridePath: 624 | XboxOneAppManifestOverridePath: 625 | XboxOneVersion: 1.0.0.0 626 | XboxOnePackageEncryption: 0 627 | XboxOnePackageUpdateGranularity: 2 628 | XboxOneDescription: 629 | XboxOneLanguage: 630 | - enus 631 | XboxOneCapability: [] 632 | XboxOneGameRating: {} 633 | XboxOneIsContentPackage: 0 634 | XboxOneEnableGPUVariability: 1 635 | XboxOneSockets: {} 636 | XboxOneSplashScreen: {fileID: 0} 637 | XboxOneAllowedProductIds: [] 638 | XboxOnePersistentLocalStorageSize: 0 639 | XboxOneXTitleMemory: 8 640 | XboxOneOverrideIdentityName: 641 | XboxOneOverrideIdentityPublisher: 642 | vrEditorSettings: 643 | daydream: 644 | daydreamIconForeground: {fileID: 0} 645 | daydreamIconBackground: {fileID: 0} 646 | cloudServicesEnabled: 647 | UNet: 1 648 | luminIcon: 649 | m_Name: 650 | m_ModelFolderPath: 651 | m_PortalFolderPath: 652 | luminCert: 653 | m_CertPath: 654 | m_SignPackage: 1 655 | luminIsChannelApp: 0 656 | luminVersion: 657 | m_VersionCode: 1 658 | m_VersionName: 659 | apiCompatibilityLevel: 6 660 | cloudProjectId: 661 | framebufferDepthMemorylessMode: 0 662 | projectName: 663 | organizationId: 664 | cloudEnabled: 0 665 | enableNativePlatformBackendsForNewInputSystem: 0 666 | disableOldInputManagerSupport: 0 667 | legacyClampBlendShapeWeights: 1 668 | -------------------------------------------------------------------------------- /UnityProject/ProjectSettings/ProjectVersion.txt: -------------------------------------------------------------------------------- 1 | m_EditorVersion: 2019.4.0f1 2 | m_EditorVersionWithRevision: 2019.4.0f1 (0af376155913) 3 | -------------------------------------------------------------------------------- /UnityProject/ProjectSettings/QualitySettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!47 &1 4 | QualitySettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 5 7 | m_CurrentQuality: 5 8 | m_QualitySettings: 9 | - serializedVersion: 2 10 | name: Very Low 11 | pixelLightCount: 0 12 | shadows: 0 13 | shadowResolution: 0 14 | shadowProjection: 1 15 | shadowCascades: 1 16 | shadowDistance: 15 17 | shadowNearPlaneOffset: 3 18 | shadowCascade2Split: 0.33333334 19 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} 20 | shadowmaskMode: 0 21 | blendWeights: 1 22 | textureQuality: 1 23 | anisotropicTextures: 0 24 | antiAliasing: 0 25 | softParticles: 0 26 | softVegetation: 0 27 | realtimeReflectionProbes: 0 28 | billboardsFaceCameraPosition: 0 29 | vSyncCount: 0 30 | lodBias: 0.3 31 | maximumLODLevel: 0 32 | streamingMipmapsActive: 0 33 | streamingMipmapsAddAllCameras: 1 34 | streamingMipmapsMemoryBudget: 512 35 | streamingMipmapsRenderersPerFrame: 512 36 | streamingMipmapsMaxLevelReduction: 2 37 | streamingMipmapsMaxFileIORequests: 1024 38 | particleRaycastBudget: 4 39 | asyncUploadTimeSlice: 2 40 | asyncUploadBufferSize: 16 41 | asyncUploadPersistentBuffer: 1 42 | resolutionScalingFixedDPIFactor: 1 43 | excludedTargetPlatforms: [] 44 | - serializedVersion: 2 45 | name: Low 46 | pixelLightCount: 0 47 | shadows: 0 48 | shadowResolution: 0 49 | shadowProjection: 1 50 | shadowCascades: 1 51 | shadowDistance: 20 52 | shadowNearPlaneOffset: 3 53 | shadowCascade2Split: 0.33333334 54 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} 55 | shadowmaskMode: 0 56 | blendWeights: 2 57 | textureQuality: 0 58 | anisotropicTextures: 0 59 | antiAliasing: 0 60 | softParticles: 0 61 | softVegetation: 0 62 | realtimeReflectionProbes: 0 63 | billboardsFaceCameraPosition: 0 64 | vSyncCount: 0 65 | lodBias: 0.4 66 | maximumLODLevel: 0 67 | streamingMipmapsActive: 0 68 | streamingMipmapsAddAllCameras: 1 69 | streamingMipmapsMemoryBudget: 512 70 | streamingMipmapsRenderersPerFrame: 512 71 | streamingMipmapsMaxLevelReduction: 2 72 | streamingMipmapsMaxFileIORequests: 1024 73 | particleRaycastBudget: 16 74 | asyncUploadTimeSlice: 2 75 | asyncUploadBufferSize: 16 76 | asyncUploadPersistentBuffer: 1 77 | resolutionScalingFixedDPIFactor: 1 78 | excludedTargetPlatforms: [] 79 | - serializedVersion: 2 80 | name: Medium 81 | pixelLightCount: 1 82 | shadows: 1 83 | shadowResolution: 0 84 | shadowProjection: 1 85 | shadowCascades: 1 86 | shadowDistance: 20 87 | shadowNearPlaneOffset: 3 88 | shadowCascade2Split: 0.33333334 89 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} 90 | shadowmaskMode: 0 91 | blendWeights: 2 92 | textureQuality: 0 93 | anisotropicTextures: 1 94 | antiAliasing: 0 95 | softParticles: 0 96 | softVegetation: 0 97 | realtimeReflectionProbes: 0 98 | billboardsFaceCameraPosition: 0 99 | vSyncCount: 1 100 | lodBias: 0.7 101 | maximumLODLevel: 0 102 | streamingMipmapsActive: 0 103 | streamingMipmapsAddAllCameras: 1 104 | streamingMipmapsMemoryBudget: 512 105 | streamingMipmapsRenderersPerFrame: 512 106 | streamingMipmapsMaxLevelReduction: 2 107 | streamingMipmapsMaxFileIORequests: 1024 108 | particleRaycastBudget: 64 109 | asyncUploadTimeSlice: 2 110 | asyncUploadBufferSize: 16 111 | asyncUploadPersistentBuffer: 1 112 | resolutionScalingFixedDPIFactor: 1 113 | excludedTargetPlatforms: [] 114 | - serializedVersion: 2 115 | name: High 116 | pixelLightCount: 2 117 | shadows: 2 118 | shadowResolution: 1 119 | shadowProjection: 1 120 | shadowCascades: 2 121 | shadowDistance: 40 122 | shadowNearPlaneOffset: 3 123 | shadowCascade2Split: 0.33333334 124 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} 125 | shadowmaskMode: 1 126 | blendWeights: 2 127 | textureQuality: 0 128 | anisotropicTextures: 1 129 | antiAliasing: 0 130 | softParticles: 0 131 | softVegetation: 1 132 | realtimeReflectionProbes: 1 133 | billboardsFaceCameraPosition: 1 134 | vSyncCount: 1 135 | lodBias: 1 136 | maximumLODLevel: 0 137 | streamingMipmapsActive: 0 138 | streamingMipmapsAddAllCameras: 1 139 | streamingMipmapsMemoryBudget: 512 140 | streamingMipmapsRenderersPerFrame: 512 141 | streamingMipmapsMaxLevelReduction: 2 142 | streamingMipmapsMaxFileIORequests: 1024 143 | particleRaycastBudget: 256 144 | asyncUploadTimeSlice: 2 145 | asyncUploadBufferSize: 16 146 | asyncUploadPersistentBuffer: 1 147 | resolutionScalingFixedDPIFactor: 1 148 | excludedTargetPlatforms: [] 149 | - serializedVersion: 2 150 | name: Very High 151 | pixelLightCount: 3 152 | shadows: 2 153 | shadowResolution: 2 154 | shadowProjection: 1 155 | shadowCascades: 2 156 | shadowDistance: 70 157 | shadowNearPlaneOffset: 3 158 | shadowCascade2Split: 0.33333334 159 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} 160 | shadowmaskMode: 1 161 | blendWeights: 4 162 | textureQuality: 0 163 | anisotropicTextures: 2 164 | antiAliasing: 2 165 | softParticles: 1 166 | softVegetation: 1 167 | realtimeReflectionProbes: 1 168 | billboardsFaceCameraPosition: 1 169 | vSyncCount: 1 170 | lodBias: 1.5 171 | maximumLODLevel: 0 172 | streamingMipmapsActive: 0 173 | streamingMipmapsAddAllCameras: 1 174 | streamingMipmapsMemoryBudget: 512 175 | streamingMipmapsRenderersPerFrame: 512 176 | streamingMipmapsMaxLevelReduction: 2 177 | streamingMipmapsMaxFileIORequests: 1024 178 | particleRaycastBudget: 1024 179 | asyncUploadTimeSlice: 2 180 | asyncUploadBufferSize: 16 181 | asyncUploadPersistentBuffer: 1 182 | resolutionScalingFixedDPIFactor: 1 183 | excludedTargetPlatforms: [] 184 | - serializedVersion: 2 185 | name: Ultra 186 | pixelLightCount: 4 187 | shadows: 2 188 | shadowResolution: 2 189 | shadowProjection: 1 190 | shadowCascades: 4 191 | shadowDistance: 150 192 | shadowNearPlaneOffset: 3 193 | shadowCascade2Split: 0.33333334 194 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} 195 | shadowmaskMode: 1 196 | blendWeights: 4 197 | textureQuality: 0 198 | anisotropicTextures: 2 199 | antiAliasing: 2 200 | softParticles: 1 201 | softVegetation: 1 202 | realtimeReflectionProbes: 1 203 | billboardsFaceCameraPosition: 1 204 | vSyncCount: 1 205 | lodBias: 2 206 | maximumLODLevel: 0 207 | streamingMipmapsActive: 0 208 | streamingMipmapsAddAllCameras: 1 209 | streamingMipmapsMemoryBudget: 512 210 | streamingMipmapsRenderersPerFrame: 512 211 | streamingMipmapsMaxLevelReduction: 2 212 | streamingMipmapsMaxFileIORequests: 1024 213 | particleRaycastBudget: 4096 214 | asyncUploadTimeSlice: 2 215 | asyncUploadBufferSize: 16 216 | asyncUploadPersistentBuffer: 1 217 | resolutionScalingFixedDPIFactor: 1 218 | excludedTargetPlatforms: [] 219 | m_PerPlatformDefaultQuality: 220 | Android: 2 221 | Lumin: 5 222 | Nintendo 3DS: 5 223 | Nintendo Switch: 5 224 | PS4: 5 225 | PSP2: 2 226 | Standalone: 5 227 | WebGL: 3 228 | Windows Store Apps: 5 229 | XboxOne: 5 230 | iPhone: 2 231 | tvOS: 2 232 | -------------------------------------------------------------------------------- /UnityProject/ProjectSettings/TagManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!78 &1 4 | TagManager: 5 | serializedVersion: 2 6 | tags: [] 7 | layers: 8 | - Default 9 | - TransparentFX 10 | - Ignore Raycast 11 | - 12 | - Water 13 | - UI 14 | - 15 | - 16 | - 17 | - 18 | - 19 | - 20 | - 21 | - 22 | - 23 | - 24 | - 25 | - 26 | - 27 | - 28 | - 29 | - 30 | - 31 | - 32 | - 33 | - 34 | - 35 | - 36 | - 37 | - 38 | - 39 | - 40 | m_SortingLayers: 41 | - name: Default 42 | uniqueID: 0 43 | locked: 0 44 | -------------------------------------------------------------------------------- /UnityProject/ProjectSettings/TimeManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!5 &1 4 | TimeManager: 5 | m_ObjectHideFlags: 0 6 | Fixed Timestep: 0.03333333 7 | Maximum Allowed Timestep: 0.33333334 8 | m_TimeScale: 1 9 | Maximum Particle Timestep: 0.03333333 10 | -------------------------------------------------------------------------------- /UnityProject/ProjectSettings/UnityConnectSettings.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!310 &1 4 | UnityConnectSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 1 7 | m_Enabled: 0 8 | m_TestMode: 0 9 | m_EventOldUrl: 10 | m_EventUrl: 11 | m_ConfigUrl: 12 | m_TestInitMode: 0 13 | CrashReportingSettings: 14 | m_EventUrl: 15 | m_Enabled: 0 16 | m_LogBufferSize: 10 17 | m_CaptureEditorExceptions: 1 18 | UnityPurchasingSettings: 19 | m_Enabled: 0 20 | m_TestMode: 0 21 | UnityAnalyticsSettings: 22 | m_Enabled: 0 23 | m_TestMode: 0 24 | m_InitializeOnStartup: 1 25 | UnityAdsSettings: 26 | m_Enabled: 0 27 | m_InitializeOnStartup: 1 28 | m_TestMode: 0 29 | m_IosGameId: 30 | m_AndroidGameId: 31 | m_GameIds: {} 32 | m_GameId: 33 | PerformanceReportingSettings: 34 | m_Enabled: 0 35 | -------------------------------------------------------------------------------- /UnityProject/ProjectSettings/VFXManager.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!937362698 &1 4 | VFXManager: 5 | m_ObjectHideFlags: 0 6 | m_IndirectShader: {fileID: 0} 7 | m_CopyBufferShader: {fileID: 0} 8 | m_SortShader: {fileID: 0} 9 | m_RenderPipeSettingsPath: 10 | m_FixedTimeStep: 0.016666668 11 | m_MaxDeltaTime: 0.05 12 | -------------------------------------------------------------------------------- /UnityProject/ProjectSettings/XRSettings.asset: -------------------------------------------------------------------------------- 1 | { 2 | "m_SettingKeys": [ 3 | "VR Device Disabled", 4 | "VR Device User Alert" 5 | ], 6 | "m_SettingValues": [ 7 | "False", 8 | "False" 9 | ] 10 | } -------------------------------------------------------------------------------- /UnityProject/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /cleanup_all_resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Get the configuration variables 4 | source configuration.sh 5 | 6 | read -p "Are you sure you want to delete all resources? " -n 1 -r 7 | echo # (optional) move to a new line 8 | if [[ ! $REPLY =~ ^[Yy]$ ]] 9 | then 10 | exit 1 11 | fi 12 | 13 | echo "Delete Cognito Stack.." 14 | aws cloudformation --region $region delete-stack --stack-name fleetiq-ecs-game-servers-cognito 15 | aws cloudformation --region $region wait stack-delete-complete --stack-name fleetiq-ecs-game-servers-cognito 16 | echo "Done deleting stack!" 17 | 18 | echo "Delete Backend Services Stack.." 19 | aws cloudformation --region $region delete-stack --stack-name fleetiq-ecs-game-servers-backend 20 | aws cloudformation --region $region wait stack-delete-complete --stack-name fleetiq-ecs-game-servers-backend 21 | echo "Done deleting stack!" 22 | 23 | echo "Delete Task Definition Stack.." 24 | aws cloudformation --region $region delete-stack --stack-name fleetiq-game-servers-task-definition 25 | aws cloudformation --region $region wait stack-delete-complete --stack-name fleetiq-game-servers-task-definition 26 | echo "Done deleting stack!" 27 | 28 | read -p "ACTION REQUIRED: Go to the ECS Cluster Tasks in AWS Management Console and STOP ALL TASKS. Once ready, press any key. " -n 1 -r 29 | echo # (optional) move to a new line 30 | 31 | echo "Delete VPC and ECS Stack.." 32 | aws cloudformation --region $region delete-stack --stack-name fleetiq-ecs-vpc-and-ecs-resources 33 | aws cloudformation --region $region wait stack-delete-complete --stack-name fleetiq-ecs-vpc-and-ecs-resources 34 | echo "Done deleting stack!" 35 | 36 | echo "All Resources cleaned up!" -------------------------------------------------------------------------------- /configuration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # The Account and Region where we will deploy 4 | region="us-east-1" 5 | accountid="" 6 | 7 | # A Unique name for the bucket used for backend deployments 8 | deploymentbucketname="" 9 | --------------------------------------------------------------------------------