Build a Warrior Game on AWS: A Serverless Action-Adventure Game

2026-11-04来源 dev.to AWSEnglish
Build a Warrior Game on AWS: A Serverless Action-Adventure Game

What if you could build your own action-adventure game and host it on AWS without managing traditional game servers?

You don't need a large game-development team or complicated infrastructure to get started. With HTML5 Canvas, JavaScript, and AWS serverless services, you can create a browser-based game where players control a warrior, fight enemies, earn points, and compete on a leaderboard.

In this tutorial, we'll build the foundation for a project called Warrior Quest.

We'll combine frontend game development with AWS services for hosting, backend processing, database storage, and monitoring.

🎮 1. What Are We Building?

Warrior Quest is a simple 2D browser-based action game.

The player controls a warrior who must survive enemy attacks and earn points.

Our initial version will include:

  • Warrior movement using keyboard controls.
  • Basic enemy movement.
  • Player health and score tracking.
  • Enemy encounters and attacks.
  • Game-over and restart functionality.
  • Backend score submission.
  • A leaderboard for high scores.

We'll use HTML5 Canvas and JavaScript for the frontend and AWS for the cloud infrastructure.

This is a learning prototype. The initial game logic runs in the browser, while AWS handles backend operations such as score submission and leaderboard retrieval.

☁️ 2. AWS Architecture

Here's the proposed architecture:

 PLAYER | v AMAZON CLOUDFRONT | v AMAZON S3 (Game Frontend Files) | v PLAYER BROWSER | | HTTPS API Requests v AMAZON API GATEWAY | v AWS LAMBDA (Backend Logic) | v AMAZON DYNAMODB (Player Scores) AMAZON CLOUDWATCH (Logs and Monitoring) AMAZON COGNITO (Optional Authentication) 

AWS services and their responsibilities

AWS Service Responsibility
Amazon S3 Stores HTML, CSS, JavaScript, and game assets
Amazon CloudFront Delivers game files through a CDN
Amazon API Gateway Exposes backend API endpoints
AWS Lambda Processes score submissions and leaderboard requests
Amazon DynamoDB Stores player scores
Amazon Cognito Provides optional player authentication
Amazon CloudWatch Monitors backend activity and logs

Let's implement the main components.

🛠️ 3. Build the Game Frontend

First, create a project directory:

warrior-game/ │ ├── index.html ├── style.css └── game.js 

Step 1: Create index.html

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Warrior Quest</title> <link rel="stylesheet" href="style.css"> </head> <body> <main class="game-container"> <h1>Warrior Quest</h1> <div class="game-info"> <span id="health">Health: 100</span> <span id="score">Score: 0</span> </div> <canvas id="gameCanvas" width="800" height="450"> </canvas> <div class="controls"> <p>Move: Arrow Keys or WASD</p> <p>Attack: Spacebar</p> </div> <button id="restartButton">Restart Game</button> <section> <h2>Leaderboard</h2> <ol id="leaderboard"></ol> </section> </main> <script src="game.js"></script> </body> </html> 

This creates the game canvas, health and score displays, restart button, and leaderboard section.

Step 2: Add CSS styling

Create style.css:

body { margin: 0; font-family: Arial, sans-serif; background: #111827; color: white; text-align: center; } .game-container { max-width: 900px; margin: 30px auto; padding: 20px; } .game-info { display: flex; justify-content: space-around; margin: 15px; font-size: 20px; } canvas { background: #263445; border: 3px solid #f59e0b; max-width: 100%; } .controls { color: #d1d5db; } button { background: #f59e0b; border: none; padding: 12px 24px; font-size: 16px; cursor: pointer; border-radius: 6px; } button:hover { background: #d97706; } 

Step 3: Implement basic game mechanics

Create game.js:

const canvas = document.getElementById("gameCanvas"); const ctx = canvas.getContext("2d"); const healthElement = document.getElementById("health"); const scoreElement = document.getElementById("score"); const restartButton = document.getElementById("restartButton"); let keys = {}; let animationId; let lastTime = 0; let enemyTimer = 0; let score = 0; let gameOver = false; const player = { x: 100, y: 300, width: 35, height: 50, speed: 220, health: 100, attacking: false }; let enemies = []; document.addEventListener("keydown", event => { keys[event.key.toLowerCase()] = true; if (event.code === "Space") { player.attacking = true; } }); document.addEventListener("keyup", event => { keys[event.key.toLowerCase()] = false; if (event.code === "Space") { player.attacking = false; } }); function spawnEnemy() { enemies.push({ x: canvas.width - 40, y: 300, width: 35, height: 50, speed: 70 }); } function update(deltaTime) { if (gameOver) return; const movement = player.speed * deltaTime; if (keys["arrowleft"] || keys["a"]) { player.x -= movement; } if (keys["arrowright"] || keys["d"]) { player.x += movement; } if (keys["arrowup"] || keys["w"]) { player.y -= movement; } if (keys["arrowdown"] || keys["s"]) { player.y += movement; } player.x = Math.max( 0, Math.min(canvas.width - player.width, player.x) ); player.y = Math.max( 0, Math.min(canvas.height - player.height, player.y) ); enemyTimer += deltaTime; if (enemyTimer >= 2) { spawnEnemy(); enemyTimer = 0; } enemies.forEach(enemy => { enemy.x -= enemy.speed * deltaTime; const touching = player.x < enemy.x + enemy.width && player.x + player.width > enemy.x && player.y < enemy.y + enemy.height && player.y + player.height > enemy.y; if (touching && player.attacking) { enemy.defeated = true; score += 10; } else if (touching) { player.health -= 0.2; } }); enemies = enemies.filter(enemy => !enemy.defeated && enemy.x > -enemy.width ); if (player.health <= 0) { gameOver = true; submitScore(score); } healthElement.textContent = `Health: ${Math.max(0, Math.floor(player.health))}`; scoreElement.textContent = `Score: ${score}`; } function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = "#166534"; ctx.fillRect(0, 370, canvas.width, 80); // Draw player ctx.fillStyle = "#f59e0b"; ctx.fillRect( player.x, player.y, player.width, player.height ); // Draw enemies ctx.fillStyle = "#ef4444"; enemies.forEach(enemy => { ctx.fillRect( enemy.x, enemy.y, enemy.width, enemy.height ); }); if (gameOver) { ctx.fillStyle = "white"; ctx.font = "36px Arial"; ctx.fillText("GAME OVER", 300, 200); } } function gameLoop(timestamp) { if (gameOver) { draw(); return; } const deltaTime = Math.min( (timestamp - lastTime) / 1000, 0.05 ); lastTime = timestamp; update(deltaTime); draw(); animationId = requestAnimationFrame(gameLoop); } function startGame() { cancelAnimationFrame(animationId); player.x = 100; player.y = 300; player.health = 100; player.attacking = false; enemies = []; score = 0; enemyTimer = 0; lastTime = 0; gameOver = false; gameLoop(performance.now()); } restartButton.addEventListener("click", startGame); startGame(); 

This prototype uses simple colored rectangles instead of character sprites. It demonstrates the game loop, player movement, enemy spawning, collision detection, health, and scoring.

You can later replace the rectangles with animated warrior and enemy sprites.

🗄️ 4. Create the AWS Backend

Now let's connect the game to AWS.

We'll create two API endpoints:

POST /scores GET /leaderboard 

The first endpoint submits a player's score. The second retrieves the highest scores.

Step 1: Create a DynamoDB table

Open the DynamoDB console and create a table:

Table name: WarriorLeaderboard Partition key: seasonId (String) Sort key: scoreKey (String) 

We'll use an inverted score value in the sort key so that the highest scores can be retrieved first using ascending sort order.

For example, with a maximum score of 999999999999:

scoreKey = SCORE#000000997499#timestamp#playerId 

The actual inverse value is calculated as:

inverseScore = MAX_SCORE - score 

Step 2: Create the score submission Lambda

Create a Lambda function using a supported Python runtime.

import json import os import time import uuid import boto3 dynamodb = boto3.resource("dynamodb") table = dynamodb.Table(os.environ["TABLE_NAME"]) MAX_SCORE = 999999999999 def response(status_code, body): return { "statusCode": status_code, "headers": { "Content-Type": "application/json" }, "body": json.dumps(body) } def lambda_handler(event, context): try: body = json.loads(event.get("body") or "{}") player_id = body.get("playerId") score = body.get("score") if not player_id: return response( 400, {"message": "playerId is required"} ) if ( not isinstance(score, int) or isinstance(score, bool) or score < 0 or score > MAX_SCORE ): return response( 400, {"message": "Invalid score"} ) season_id = "SEASON#2026" inverse_score = MAX_SCORE - score score_key = ( f"SCORE#{inverse_score:012d}" f"#{int(time.time()):010d}" f"#{uuid.uuid4().hex}" ) table.put_item( Item={ "seasonId": season_id, "scoreKey": score_key, "playerId": str(player_id)[:100], "score": score, "createdAt": int(time.time()) } ) return response( 200, { "message": "Score submitted", "score": score } ) except Exception: return response( 500, {"message": "Unable to submit score"} ) 

Set the Lambda environment variable:

TABLE_NAME = WarriorLeaderboard 

Give the function permission to write to the DynamoDB table.

Step 3: Create the leaderboard Lambda

Create another Lambda function:

import json import os import boto3 dynamodb = boto3.resource("dynamodb") table = dynamodb.Table(os.environ["TABLE_NAME"]) def lambda_handler(event, context): try: result = table.query( KeyConditionExpression="seasonId = :season", ExpressionAttributeValues={ ":season": "SEASON#2026" }, ScanIndexForward=True, Limit=10 ) leaderboard = [ { "playerId": item["playerId"], "score": item["score"] } for item in result.get("Items", []) ] return { "statusCode": 200, "headers": { "Content-Type": "application/json" }, "body": json.dumps({ "leaderboard": leaderboard }) } except Exception: return { "statusCode": 500, "headers": { "Content-Type": "application/json" }, "body": json.dumps({ "message": "Unable to retrieve leaderboard" }) } 

Set the same TABLE_NAME environment variable and give this Lambda function permission to query the table.

🌐 5. Configure Amazon API Gateway

Create an HTTP API in API Gateway.

Configure these routes:

Method Route Lambda integration
POST /scores Score submission Lambda
GET /leaderboard Lead...

原文:https://www.a8q9.cn/en/article/build-a-warrior-game-on-aws-a-serverless-action-adventure-game-0d3e92.html

相关文章