<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Joseph B. Manley</title>
    <description>The latest articles on Forem by Joseph B. Manley (@josephbmanley).</description>
    <link>https://forem.com/josephbmanley</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F471569%2F46b34621-ec67-412e-a1ba-72c4e0be3276.jpeg</url>
      <title>Forem: Joseph B. Manley</title>
      <link>https://forem.com/josephbmanley</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/josephbmanley"/>
    <language>en</language>
    <item>
      <title>Using AWS S3 to host HTML5 Godot projects</title>
      <dc:creator>Joseph B. Manley</dc:creator>
      <pubDate>Fri, 19 Feb 2021 00:36:11 +0000</pubDate>
      <link>https://forem.com/josephbmanley/using-aws-s3-to-host-html5-godot-projects-2nc9</link>
      <guid>https://forem.com/josephbmanley/using-aws-s3-to-host-html5-godot-projects-2nc9</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9xB4UFu0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AaK6zY6u4C8BueLPta1fFsw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9xB4UFu0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AaK6zY6u4C8BueLPta1fFsw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When working on a new project, you may want a simple website to share your game with friends and something that can, later on, support many thousands of active users. AWS offers a great storage solution called “Simple Storage Service” or “S3” that can do just that! For those unaware, AWS is Amazon’s cloud hosting platform. S3 is one of AWS’s core services that provides a simple key file store that users can access over the web or AWS’s easy-to-use API.&lt;/p&gt;

&lt;p&gt;Below is a guide on how to get started with S3 with Godot HTML5. I also added a section on how to automate HTML5 builds with GitHub Actions.&lt;/p&gt;

&lt;p&gt;Start by logging in to your AWS, if you need to create an AWS account, you can see the latest what to there that &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/"&gt;here&lt;/a&gt;. Once you are logged in to your AWS account, click the “Services” drop down and select “S3".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7j075G1L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/779/1%2AePD5tm6IrSPCgdMBiL2ipA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7j075G1L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/779/1%2AePD5tm6IrSPCgdMBiL2ipA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From here, you can create a new bucket. A bucket is a file store that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--J1SU2D2u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/777/1%2AZpZ42ppIr20vAOmNEvxcXA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--J1SU2D2u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/777/1%2AZpZ42ppIr20vAOmNEvxcXA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Set a bucket name, region, and be sure that “Block all public access” is unchecked. If it’s checked you will not be able to access it from the internet in later steps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TwvJUXvb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/837/1%2Af3x7C2Ss2jSos_3dof2CWA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TwvJUXvb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/837/1%2Af3x7C2Ss2jSos_3dof2CWA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you’ve finished creating your bucket, click on it’s name to open it up and go to the “Properties” tab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QHk7T9WU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/976/1%2AQp8wRQujBJIMmOW4LhmY-w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QHk7T9WU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/976/1%2AQp8wRQujBJIMmOW4LhmY-w.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Scroll all the way until you see the section titled “Static website hosting” and click “Edit”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--N9KiyxsT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A5yof1DL5hsLgCAwh-MCp_A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N9KiyxsT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2A5yof1DL5hsLgCAwh-MCp_A.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enable static website hosting and configure your paths. Later, I will export my game project as index.html , so I am going to use that for my index and error document.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HGSaJbSS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/850/1%2AduYERAplb85bpenCr_3hZA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HGSaJbSS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/850/1%2AduYERAplb85bpenCr_3hZA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After saving, if you scroll back to the “Static website hosting” portion of “Properties”, you should see your bucket’s new web address!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4oZWiZEF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/834/1%2Acv35U9fq_L9SpvosGDfT1w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4oZWiZEF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/834/1%2Acv35U9fq_L9SpvosGDfT1w.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But, if you open it up right now, you will get a 403 error. This is because you need to give the pubic read access to the bucket.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2VaqpStz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AU9zard1LCZX1XEM7nYD5YQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2VaqpStz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AU9zard1LCZX1XEM7nYD5YQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To do this, go to “Permissions”. Then, scroll down to “Bucket policy” and click “Edit.”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0rjOm_fo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/928/1%2AGQn46DEKqJhGFB_1xAmikw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0rjOm_fo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/928/1%2AGQn46DEKqJhGFB_1xAmikw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VPh2IjXd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/995/1%2AO_vtM0XW-8lzCJbGChUibg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VPh2IjXd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/995/1%2AO_vtM0XW-8lzCJbGChUibg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For your policy, you can use this template where [YOUR-BUCKET-NAME-HERE] is the name of your bucket.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::[YOUR-BUCKET-NAME-HERE]/*"
            ]
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For me, it looks something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9XPuSJn4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/888/1%2Afe_djB6GD1teKsPeA_MLLw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9XPuSJn4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/888/1%2Afe_djB6GD1teKsPeA_MLLw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After saving, if you refresh your web endpoint and tada, you have a static website! All that’s left is to export and upload your project!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qzrL0ItT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/784/1%2AkDx3-uFVpB0va1ApyYRMdw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qzrL0ItT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/784/1%2AkDx3-uFVpB0va1ApyYRMdw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Manually Exporting and Uploading
&lt;/h3&gt;

&lt;p&gt;Open up your Godot project and export your project for HTML5. Be sure, your export name is the same as your index document name in your S3 configuration. If you have any issues &lt;a href="https://docs.godotengine.org/en/stable/getting_started/workflow/export/exporting_for_web.html"&gt;see the Godot documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mtis4nbB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/971/1%2Abve1LCRbZZYbJ9h4ZBuKoA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mtis4nbB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/971/1%2Abve1LCRbZZYbJ9h4ZBuKoA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After exporting, here is my directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── favicon.png
├── favicon.png.import
├── index.html
├── index.js
├── index.pck
├── index.png
├── index.png.import
└── index.wasm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now back in the S3 console, you need to go to “Objects” then click “Upload”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vM1h1_SL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/941/1%2Agl4dQNx-a0Qj91O6usm_Dg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vM1h1_SL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/941/1%2Agl4dQNx-a0Qj91O6usm_Dg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then select “Add Files” and select all the files in your build directory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZtH8cmXX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/831/1%2AHQ2dRpRL-tjejpyrOJIIaA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZtH8cmXX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/831/1%2AHQ2dRpRL-tjejpyrOJIIaA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is what I have:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iKvWVAOu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/826/1%2AE_BpnoFLNvtmu7uXpOr2JQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iKvWVAOu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/826/1%2AE_BpnoFLNvtmu7uXpOr2JQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After you click “Upload”, you can refresh your site and there is your game!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0Jq5rl3u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AY7iuT0e5h7vYrVx04CXtPg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0Jq5rl3u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AY7iuT0e5h7vYrVx04CXtPg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Automating with GitHub Actions
&lt;/h3&gt;

&lt;p&gt;First, your game project code is required to be hosted on GitHub. If you don’t know how to do that, here is a guide on getting started with GitHub: &lt;a href="https://guides.github.com/activities/hello-world/"&gt;https://guides.github.com/activities/hello-world/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a new file in your repo .github/workflows/build.yaml from &lt;a href="https://gist.github.com/josephbmanley/36c2d47cc94e7a83a430285f301963e5"&gt;this snippet&lt;/a&gt; (&lt;a href="https://gist.github.com/josephbmanley/36c2d47cc94e7a83a430285f301963e5"&gt;https://gist.github.com/josephbmanley/36c2d47cc94e7a83a430285f301963e5&lt;/a&gt;) and update the AWS_S3_BUCKET and AWS_REGION fields.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pRCkL2cB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/748/1%2AGHe079vGyD-uxCKaxTYIbQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pRCkL2cB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/748/1%2AGHe079vGyD-uxCKaxTYIbQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This file represents your workflow. Your workflow gives a list of steps for GitHub to do when there is a new event. In this case, on push (upload to GitHub), it will download your project, build it with the latest version of Godot, and upload the result to S3.&lt;/p&gt;

&lt;p&gt;Though, if you notice it has secrets.AWS_ACCESS_KEY_ID and secrets.AWS_SECRET_ACCESS_KEY . Both of these, you need to feed to GitHub, so it have access to your S3 bucket.&lt;/p&gt;

&lt;p&gt;To do this, go to the AWS console, then “Services”, and select “IAM”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lncvfYP_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AhxUq4l8NC-m0Vl1Byjr8jQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lncvfYP_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AhxUq4l8NC-m0Vl1Byjr8jQ.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then go to “Users” and “Add user”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zGs54Slt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/585/1%2AbVT-eToIAPLnmSQJaW17Zw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zGs54Slt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/585/1%2AbVT-eToIAPLnmSQJaW17Zw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use a user name of your choice and make sure that “Programmatic access” is checked.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PO-1nxex--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ARUfkEfgWpDv6n5SpzqfQmg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PO-1nxex--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2ARUfkEfgWpDv6n5SpzqfQmg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the next page, click the “Attach existing policies directly” tab and search for S3. Then, select the “AmazonS3FullAccess” policy. If you are more familiar with AWS and IAM, I would instead suggest creating your own policy for minimal access.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gqmskiwe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AzvHS-JX-68WaWe1U1_Vlow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gqmskiwe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AzvHS-JX-68WaWe1U1_Vlow.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, click through the next few prompts until your user is created. Here you’ll see your new access key ID and secret access key. Save those values because these are the values that will be put into GitHub.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XPyrPuh7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AndmreioRgqkCDIIzAHl8zw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XPyrPuh7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AndmreioRgqkCDIIzAHl8zw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In your GitHub repo, go to “Settings”, “Secrets”, and then “New repository secret”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SPX3OUAR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2As6ioZfnxt8FO3YXqCv1m_g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SPX3OUAR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2As6ioZfnxt8FO3YXqCv1m_g.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In here put AWS_ACCESS_KEY_ID with the value being the access key id you saved earlier. Then, repeat the process for AWS_SECRET_ACCESS_KEY with your secret access key.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wf3paRCw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AUJaqReHo0HnVaTDWfVY0Nw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wf3paRCw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AUJaqReHo0HnVaTDWfVY0Nw.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, the next time you push to GitHub. Your S3 bucket should be updated and your game accessible!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0Jq5rl3u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AY7iuT0e5h7vYrVx04CXtPg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0Jq5rl3u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AY7iuT0e5h7vYrVx04CXtPg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Where to go from here?
&lt;/h3&gt;

&lt;p&gt;Congratulations, you now have a simple website that you can use to share your project anywhere! If you want to improve your website, I suggest looking into to use CloudFront to enable caching and HTTPS for your website: &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/cloudfront-https-requests-s3/"&gt;https://aws.amazon.com/premiumsupport/knowledge-center/cloudfront-https-requests-s3/&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Related Links
&lt;/h4&gt;

&lt;p&gt;GitHub Workflow Gist: &lt;a href="https://gist.github.com/josephbmanley/36c2d47cc94e7a83a430285f301963e5"&gt;https://gist.github.com/josephbmanley/36c2d47cc94e7a83a430285f301963e5&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub Build Godot Action: &lt;a href="https://github.com/josephbmanley/build-godot-action"&gt;https://github.com/josephbmanley/build-godot-action&lt;/a&gt;&lt;/p&gt;

</description>
      <category>godot</category>
      <category>aws</category>
      <category>s3</category>
      <category>awsgametech</category>
    </item>
    <item>
      <title>Creating a AWS Lambda function with Haxe</title>
      <dc:creator>Joseph B. Manley</dc:creator>
      <pubDate>Tue, 16 Feb 2021 23:38:13 +0000</pubDate>
      <link>https://forem.com/aws-builders/creating-a-aws-lambda-function-with-haxe-1ei2</link>
      <guid>https://forem.com/aws-builders/creating-a-aws-lambda-function-with-haxe-1ei2</guid>
      <description>&lt;p&gt;Over the weekend, I had some fun and experimented with Haxe. For those unaware, Haxe is a typed multi-platform high-level programming language mostly used for game development, such as the indie hit &lt;a href="https://dead-cells.com/"&gt;Deadcells&lt;/a&gt;. While exporting to JavaScript, I thought, “Hey, would this work with Lambda?” Sure, enough, it can.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dXf7yU-Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/362/0%2AgXb79Qxrgx4FYW7M.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dXf7yU-Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/362/0%2AgXb79Qxrgx4FYW7M.png" alt=""&gt;&lt;/a&gt;Image Source: Haxe Foundation&lt;/p&gt;

&lt;p&gt;Is it very practical? I’m not so sure. Haxe isn’t as popular as Python or JavaScript, therefore not having as much third-party support, and Haxe isn’t run directly but rather as an exported language of your choice. But if you’re a Haxe shop or want a strongly typed language that can run well and easily on AWS, it’s worth a quick look.&lt;/p&gt;

&lt;p&gt;Bellow, I put together a quick guide on how to get started with Haxe on Lambda.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building a simple Haxe Lambda function
&lt;/h3&gt;

&lt;p&gt;First you need to setup your Haxe project. For this guide, I am going to use Handler as my main class and will export to JavaScript to export/handler.js&lt;/p&gt;

&lt;p&gt;Here is my structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── compile.hxml
├── export // Note: Directory won't exist until compile
│ └── handler.js
└── src
    └── Handler.hx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is my compile.hxml :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-cp src
--js export/handler.js
--main Handler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let’s jump into the Haxe code. Here is the start of my src/Handler.hx :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Handler {
    static function main() {}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;As a note here, while Handler.main is not executed by the Lambda function, it is still required to property export your Haxe project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s actually really simple, first we just have to expose the Handler class, you that it can be accessed by JavaScript files by adding @:expose&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@:expose
class Handler {
    static function main() {}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can add our lambda handler function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Handler {
    static function main() {}
    static function handler() : String {
        return "Hello from Haxe!"
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now go and compile your code, to do so call haxe compile.hxml from your project directory.&lt;/p&gt;

&lt;p&gt;Now the big secret to getting your function called is by passing your function through to the lambda function. This can be achieved importing your exported Javascript and calling the function from an async method. This file will become the entrypoint for your Lambda function. In my case it’s export/index.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const handler = require("handler");

exports.handler = async function(event, context, callback) {
    return handler.Handler.handler(event,context,callback);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if we zip up our export directory and upload it to our lambda function…&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QURgF3F1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Ak62gCl9q37M7BSUW-WMXTg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QURgF3F1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Ak62gCl9q37M7BSUW-WMXTg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Reading inputs
&lt;/h3&gt;

&lt;p&gt;If you noticed in the previous part, we pass the event, context, and callback objects all to our function in index.js . This is because we can actually reference those objects as an AnonType and referencing the properties we would like to access. Here’s an example using the event object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@:expose
class Handler {
    static function main() {}

    static function handler(event : {source : String}) : String {

        // Run your Haxe code
        var eventSource: String = event.source;

        // Return response
        return 'Hello to $eventSource from Haxe!';
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Vlr47Tz6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AU4ibbqYMRNyCy0odHJ-2_g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Vlr47Tz6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AU4ibbqYMRNyCy0odHJ-2_g.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now with the context object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@:expose
class Handler {
    static function main() {}

    static function handler(event : {source : String}, context : {functionName : String}) : String {

        // Run your Haxe code
        var eventSource : String = event.source;
        var lambdaName : String = context.functionName;

        // Return response
        return 'Hello to $eventSource from $lambdaName!';
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YHOhm6bH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AZJKvvGA5CzTull-eYIiuRg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YHOhm6bH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AZJKvvGA5CzTull-eYIiuRg.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Related Links
&lt;/h4&gt;

&lt;p&gt;Source on GitHub: &lt;a href="https://github.com/josephbmanley/haxe-lambda-example"&gt;https://github.com/josephbmanley/haxe-lambda-example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Official Haxe Website: &lt;a href="https://haxe.org/"&gt;https://haxe.org/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>haxe</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>How TetraForce runs Godot on AWS</title>
      <dc:creator>Joseph B. Manley</dc:creator>
      <pubDate>Fri, 06 Nov 2020 07:27:40 +0000</pubDate>
      <link>https://forem.com/josephbmanley/how-tetraforce-runs-godot-on-aws-1gh6</link>
      <guid>https://forem.com/josephbmanley/how-tetraforce-runs-godot-on-aws-1gh6</guid>
      <description>&lt;p&gt;TetraForce is an open-source multiplayer action-adventure RPG inspired by the popular Zelda game, Link's Awakening. It uses Godot’s built-in UDP networking library and scripting language for most of the game’s logic.&lt;/p&gt;

&lt;p&gt;The main developers managing the project are fornclake and TheRetroDragon. Back in July, I got in contact with them to discuss putting TetraForce into the cloud! Within a week, TetraForce was running on AWS. Since then, there have been many gradual improvements to get the project where it is today.&lt;/p&gt;

&lt;p&gt;For a summary of this project, the Amazon Elastic Container Service cluster manages and runs TetraForce’s containers using a serverless API. Initially, our cluster was hosted in an auto-scaling group of EC2 instances (Virtual Machines). Later, it was moved to AWS Fargate (Serverless Container Platform) to simplify scaling and reduce costs during periods of low utilization.&lt;/p&gt;

&lt;p&gt;There is a Serverless REST API using Lambda for creating and joining rooms, which sends and sets server information, such as name and ECS task ID, in a DynamoDB table. When a server closes, an additional lambda removes it from the DynamoDB table.&lt;/p&gt;

&lt;p&gt;Now let’s jump into some of the details.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpujg9a9dp1hj0njyf34n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpujg9a9dp1hj0njyf34n.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Dockerization
&lt;/h1&gt;

&lt;p&gt;Godot being lightweight, is easy to Dockerize. For the TetraForce Docker image, it just installs dependencies, the Godot server runtime, and the TetraForce’s &lt;code&gt;pck&lt;/code&gt; file. It can easily be extended to add additional &lt;code&gt;pck&lt;/code&gt; files in the future for mods or expansions.&lt;/p&gt;

&lt;p&gt;As of when this post was written, this is our working Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM centos:centos8

RUN yum install -y wget unzip libXcursor openssl openssl-libs libXinerama libXrandr-devel libXi alsa-lib pulseaudio-libs mesa-libGL

ENV GODOT_VERSION "3.2.2"

# Install Godot Server
RUN wget -q https://downloads.tuxfamily.org/godotengine/${GODOT_VERSION}/Godot_v${GODOT_VERSION}-stable_linux_headless.64.zip \
    &amp;amp;&amp;amp; unzip Godot_v${GODOT_VERSION}-stable_linux_headless.64.zip \
    &amp;amp;&amp;amp; mv Godot_v${GODOT_VERSION}-stable_linux_headless.64 /usr/local/bin/godot \
    &amp;amp;&amp;amp; chmod +x /usr/local/bin/godot

# Create Runtime User
RUN useradd -d /tetra tetra


# Add pck file
ADD build/TetraForce.pck /tetra/TetraForce.pck

CMD /usr/local/bin/godot --main-pack /tetra/TetraForce.pck --empty-server-timeout=300
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CMD for the image is to run the Godot server runtime using the TetraForce &lt;code&gt;pck&lt;/code&gt; file. You can notice in the run command we also pass a &lt;code&gt;--empty-server-timeout=300&lt;/code&gt;; this is for our containers to close servers that have no active players automatically.&lt;/p&gt;

&lt;h1&gt;
  
  
  Server Management
&lt;/h1&gt;

&lt;p&gt;Each game room runs as a task in ECS. Amazon assigns those tasks a public IP. The client receives those IPs by interacting with TetraForce’s API.&lt;/p&gt;

&lt;h2&gt;
  
  
  The REST API
&lt;/h2&gt;

&lt;p&gt;The client can get room information and create new rooms by making HTTPS requests to the REST API. The REST API uses API Gateway to route requests to a Lambda functions.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/create_server&lt;/code&gt; endpoint takes in an optional parameter of a name. If it does not have a name, it will randomly generate one. From there, it will spin up a new ECS task tagged with the room’s name and add a new entry into the DynamoDB table. The Lambda function will return whether it was a success and the name of the room created.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/get_servers&lt;/code&gt; endpoint takes in an optional parameter of a room name and page. If it’s passed a name, it will only do a lookup for the given room. Otherwise, it will return a list of room information. The room information includes the room name, public IP, and port. It’s also modular to all for additional values in the future, such as version.&lt;/p&gt;

&lt;h2&gt;
  
  
  CloudWatch Events
&lt;/h2&gt;

&lt;p&gt;Server cleanup is managed by an additional Lambda function that is triggered by a CloudWatch Event Rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "source": ["aws.ecs"],
  "detail-type": [ "ECS Task State Change" ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This event will trigger the Lambda anytime an ECS Task in the cluster enters a new state. That leaves the responsibility of verifying the task’s state and then removing the room from the DynamoDB table if it’s no longer running to the Lambda function.&lt;/p&gt;

&lt;h1&gt;
  
  
  Client Integration
&lt;/h1&gt;

&lt;p&gt;Using Godot’s &lt;code&gt;HTTPRequest&lt;/code&gt; Node, the client can hit the REST API hosted on AWS to request server information or create a new lobby.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export(String) var api_endpoint = "api.tetraforce.io"

var _http_client : HTTPRequest# Asynchronous coroutine.
# Requests API for data from a specific server
# Returns: {"message": [MESSAGE], "data" : [DATA] }
func get_server(lobby : String) -&amp;gt; Dictionary:
    _http_client.request("https://" + api_endpoint + "/get_servers?server=" + str(lobby), [], true, HTTPClient.METHOD_GET)
    var result = yield(_http_client, "request_completed")
    if len(result) &amp;gt; 3 and result[1] == 200:
        var json : JSONParseResult = JSON.parse(result[3].get_string_from_utf8())
        if json.error:
            return _build_error_message(json.error_string)
        return json.result

    return _build_error_message("Request failed!")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, the client will parse the returned JSON object into a dictionary. That dictionary will include the public server IP and Port, which is passed into the connection method when the player attempts to connect to the game server.&lt;/p&gt;

&lt;h1&gt;
  
  
  What’s next?
&lt;/h1&gt;

&lt;p&gt;The team is currently moving forward towards building a full demo of TetraForce within the next few months. The demo will include a handful of zones, a dungeon, and an eventual boss battle.&lt;/p&gt;

&lt;p&gt;The team is looking to include skins in their game as Patreon rewards on the infrastructure side, so a user identity system is here on the horizon.&lt;/p&gt;

&lt;p&gt;If you have any questions about anything, feel free to reach out to me on &lt;a href="https://twitter.com/josephbmanley" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related Links
&lt;/h2&gt;

&lt;p&gt;TetraForce Discord: &lt;a href="https://discord.gg/cxTBVCZ" rel="noopener noreferrer"&gt;https://discord.gg/cxTBVCZ&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;TetraForce Repository: &lt;a href="https://github.com/fornclake/TetraForce" rel="noopener noreferrer"&gt;https://github.com/fornclake/TetraForce&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Infrastructure Repository: &lt;a href="https://github.com/josephbmanley/tetraforce-infrastructure" rel="noopener noreferrer"&gt;https://github.com/josephbmanley/tetraforce-infrastructure&lt;/a&gt;&lt;/p&gt;

</description>
      <category>godot</category>
      <category>aws</category>
      <category>gamedev</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
