Tuesday, September 13, 2011

Using Amazon Web Services to make a Node.js Web Sockets Server to Play With

When I was at IGN, one of the presentations we saw included a brief introduction to Node.js and web sockets. After that presentation I couldn't wait to try it out, and I started looking into it just a few days after I got home.

After a lot of google searches and trial-and-error, I was able to get Node.js running with a simple web sockets demo (the obligatory chat room demo) on a virtual private server using the free tier of Amazon Web Services. Now I'm looking forward to all the neat things I'll be able to do, but first I'd like to share how I got my server set up so that you can play too. Disclaimer: some of the things I do, like the liberal use of sudo commands, would probably not be the best approach for a server with anything important on it. Since I'm starting with a virtual server with almost nothing on it and I'm just creating it to play with, these steps are okay for my purposes. Use your discretion.

The first thing you'll need to do is set up an AWS (Amazon Web Services) account. Amazon has a pretty nifty tutorial for that here: http://docs.amazonwebservices.com/AWSEC2/latest/GettingStartedGuide/

You'll want to go through the steps of signing up for EC2, launching an instance, and connecting to it via SSH. Important: When you set up your instance, you want to use one of the Basic Amazon Linux AMIs, and you want to create a "micro" instance. That's what included in the free tier; you'll get charged if you do pretty much anything else. For more info on the free tier you can look here. Also if you're paranoid like me you can frequently check your account activity (under the "account" tab) to make sure you're not being charged for anything.

Once you can access your instance, you'll want to enter this:
$sudo yum groupinstall "Development Tools"
$sudo yum install openssl-devel

The Basic Amazon Linux setup is pretty bare-bones, so you run these commands to install a few things, like git and a c++ compiler, and then the SSL thing, which Node.js needs.

Next you'll go through the steps of installing Node.js:
$git clone git://github.com/joyent/node.git
$cd node
$git checkout v0.4.11
$export JOBS=2 # optional, sets number of parallel commands.
$mkdir ~/local
$./configure --prefix=$HOME/local/node
$sudo make #This takes a while
$sudo make install
$echo 'export PATH=$HOME/local/node/bin:$PATH' >> ~/.profile
$echo 'export NODE_PATH=$HOME/local/node:$HOME/local/node/lib/node_modules' >> ~/.profile
$source ~/.profile
Now you have Node.js installed! You can test it out by simply executing$node and then within node enter some javascript to execute. (press ctrl-C to exit)

Now in order to install packages for Node.js you'll need to install npm. The the way I install it (and to get node.js to be able to listen to port 80, as we'll do later) requires that sudo commands be able to find node, which you can ensure by creating the following links:
$sudo ln -s /home/ec2-user/local/node/bin/node /usr/bin/node
$sudo ln -s /home/ec2-user/local/node/lib/node /usr/lib/node
$sudo ln -s /home/ec2-user/local/node/bin/npm /usr/bin/npm
$sudo ln -s /home/ec2-user/local/node/bin/node-waf /usr/bin/node-waf
Then to install npm you simply execute:
$curl http://npmjs.org/install.sh | sudo sh
Once you have npm installed you'll want to use it to install node-static to serve up static content, and socket.io to use websockets:
$npm install node-static
$npm install socket.io
And now you have all you need!

To test it out you just need to create two files: sockettest.js to run the server, and a 'client' directory in the same place as sockettest.js that contains one file, index.html. I would just use vim to create the files and copy and paste the code from below. Here's the code I used for those files:

sockettest.js
var static = require('node-static');
var http = require('http');

var clientFiles = new static.Server('./client');

var httpServer = http.createServer(function(request, response) {
    request.addListener('end', function () {
        clientFiles.serve(request, response);
    });
});
httpServer.listen(80);

var io = require('socket.io').listen(httpServer);

io.sockets.on('connection', function(socket){
        socket.on('msgsend', function(data){
                var dataText = data.text;
                io.sockets.emit('msgbroadcast', {text:dataText, name:data.name});
        });
});


index.html

<html>
<body>
Username: <input id="nameentry" type="text"></input></br>
Text: <input id="textentry" type="text"></input><br/>
<button id="sendbutton" type="button">Send</button>
<br/><br/>
<div id="console" style="width: 100%; background-color: #dddddd;">
        <strong>SOCKET TEST CHAT</strong><br/>
</div>
</body>
</html>

<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io.connect('http://123.456.789.101');

        var sendButton = document.getElementById("sendbutton");
        var textInput = document.getElementById("textentry");
        var consoleDiv = document.getElementById("console");
        var nameEntry = document.getElementById("nameentry");

        if (sendButton.addEventListener){
        sendButton.addEventListener('click', function(){
                var textToSend = textInput.value;
                var uname = nameEntry.value;
                if (!uname){
                        uname = "anonymous";
                }
                textInput.value = "";
                if (textToSend){
                        socket.emit('msgsend', {text: textToSend, name: uname});
                }
        }, false);
        }else if(sendButton.attachEvent){
        sendButton.attachEvent('onclick', function(){
                var textToSend = textInput.value;
                textInput.value = "";
                var uname = nameEntry.value;
                if (!uname){
                        uname = "anonymous";
                }
                if (textToSend){
                        socket.emit('msgsend', {text: textToSend, name: uname});
                }
        });
        }

        socket.on('msgbroadcast', function(data){
                consoleDiv.innerHTML += "<strong>"+data.name+": </strong>" + data.text + "<br/>";
        });

</script>
Note: Replace the IP address with that for your own instance. It can be found within the public DNS for your instance, just replace the dashes with dots.

To start your chat server, execute:
$sudo node sockettest.js
Then you can navigate to the public DNS for your instance in your browser and test it out! It should look like this:

Username:
Text:


SOCKET TEST CHAT
Try chatting between different browsers and devices. Socket.io includes fall-backs to support just about any browser, so you shouldn't have to worry about it.

And now you have a functioning demo of a web sockets server running Node.js that you can play around with. Have fun!

Tuesday, September 7, 2010

PAX, Day One (2010)

I just posted a video of Grace and I talking about our first day at PAX!




I'll be putting up a couple more PAX videos over the next week or so on my youtube channel.

Thursday, December 17, 2009

Fun with Math: Goblyn Chunks

One of the things I wanted to accomplish with Goblyn Stomp was to create these mobs of monsters, and a real sense of destruction when you kill large numbers of them. I tried to give that sense by having a few pieces of each Goblyn fly away with each "Fantastic Stomp" and each "Remote Myne" blast. However, if each Goblyn Chunk traveled along identical trajectories that were simply translated to different positions it would look a bit lame, so I have the game generate a unique Goblyn Chunk trajectory for each Goblyn, which is a function of the position of the Goblyn relative to the source of the blast. Here's how it works:

There are two different components that make up the Goblyn Chunk starting velocity vector. One is a straight line from the source of the blast to the Goblyn's location, and the other is the upward angle at which the Goblyn chunks fly out. Figuring out the first component is easy, it's just a matter of subtracting the source vector from the Goblyn's position vector:

Vector2.Normalize(Vector2.Subtract(GoblynPosition, Source)) * blastvelocity;

The normalize function gives you a vector with a length of 1 in the direction of the input vector. This way all of the Goblyn Chunks are blasted at a uniform velocity, determined by the variable blastvelocity.

I wanted the upward blast angles to be such that an explosion with many goblyns would have this sort of fountain effect:
To achieve this, you can see that the blast angle is flatter for points further from the center of the blast, and in the center of the blast the blast angle is pretty much 90 degrees. So this statement is used to determine the blast angle:

blastangle = 90f - Vector2.Distance(GoblynPosition, Source) / baScale;
if (blastangle < 10)
{ blastangle = 10; }

Note the baScale factor which is used to adjust the relationship between upward angle and distance from the blast, and the conditional statement that is used to make sure the blast angle is always at least 10 degrees.

Now these two components can be used to create a 3D vector representing the initial Goblyn Chunk velocity, where the XY plane extends across the ground, and the Z component determines a chunk's height above the ground:

ChunkVelocities[0].X =
Vector2.Normalize(Vector2.Subtract(
GoblynPosition, Source)).X * blastvelocity * (float)Math.Cos(blastangle * Math.PI / 180);
ChunkVelocities[0].Y =
Vector2.Normalize(Vector2.Subtract(
GoblynPosition, Source)).Y * blastvelocity * (float)Math.Cos(blastangle * Math.PI / 180);
ChunkVelocities[0].Z =
blastvelocity * (float)Math.Sin(blastangle * Math.PI / 180);


This creates a starting velocity for each Goblyn Chunk. The z component is affected by a gravity value that reduces the upward velocity with each frame, and the x and y components remain constant until the chunk hits the ground (z=0).

Finally, to create slightly different trajectories for multiple chunks coming from the same Goblyn, a random number generator is used to add in slight variations to the trajectory of the first chunk:

for (int i = 1; i < ChunkVelocities.Length; i++)
{
ChunkVelocities[i].X = ChunkVelocities[0].X +
4 * (float)GobRand.NextDouble() - 2;
ChunkVelocities[i].Y = ChunkVelocities[0].Y +
4 * (float)GobRand.NextDouble() - 2;
ChunkVelocities[i].Z = ChunkVelocities[0].Z +
4 * (float)GobRand.NextDouble() - 2;
}


And that's how you create flying Goblyn Chunks! I hope this was informative and somewhat interesting. Leave a comment if you have anything to add or if you'd like anything clarified.


Goblyn Stomp Released!

Good news! Goblyn Stomp has been approved, and can now be downloaded on the Xbox Live Indie Games channel! Here's the trailer:




So get out there and get stompin'!

Friday, November 20, 2009

A Brief History of Goblyn Stomp

I've wanted to make video games since I was about four. When I first heard about XNA I was super excited, because it would give regular people a chance to develop games on a console for very little up-front investment. I began fantasizing about what kinds of games I could develop, and started poking around with XNA on some of my friends' computers (my own computer, unfortunately, didn't have the pixel shader required to run the software.) I'm a huge fan of Castle Crashers, and so I wanted to start with a game kind of like it.

Once I got married, my wife got to share all my bank accounts, and I got to share her laptop :) So a couple months after we got married I convinced her to let me install the XNA framework on our laptop, and then I got to work.

It couldn't be exactly like Castle Crashers, so I was thinking about having waves and waves of one-hit-kill enemies instead, kind of like Geometry Wars (...or the alien ship level in Castle Crashers....) I started animating little red dots with legs and figured out how to get them to wander around randomly:

video
An old cell phone video from several months ago. I can't believe Blogger can play this...

Over time I added a main controllable character and gave him some attacks, still using stick figure animations and a photo of a farm owned by one of my Dad's relatives:

The cane spin attack used to be a poorly drawn chainsaw

So once I had the basics of the gameplay coded, I started trying to think of a theme and a style that I could use for my game (poorly-drawn stick figures seemed like a lousy theme.) One day, as I was searching for graph paper templates online for a household project, I came across Kevin MacLeod's website. Among the many offerings on his website is royalty-free music. I browsed through some of it, listened to a few pieces in his "Silent Film Score" section, and I had found my theme. At the time, I thought that a video game made to look like a silent movie was the greatest, most original idea I could have stumbled across. Of course, halfway though drawing Chap Scaliwag's animations I learned of The Misadventures of P.B. Winterbottom (which looks awesome, by the way), but I decided to stick with the silent movie theme anyway, as I didn't want to start over with my drawings.

All in all it took me about 9 months to make Goblyn Stomp, start to finish. I hope you enjoy it! I plan on adding entries from time to time that go into detail about how I developed specific aspects of the game, so if there's anything in particular you're interested in, feel free to leave a comment and let me know.

Goblyn Stomp Tips

So in the process of making the game, I've put in more than a few hours playing Goblyn Stomp. I'd like to share some tips I've come up with in my playing:
  • At the beginning of the game (the first 25 kills), you can hold down the A button to jump repeatedly. There is, however, a brief pause between jumps that could give a goblyn time to bite you.
  • If you're having trouble lining up your stomps, try lining up your shadow with the goblyns' shadows.
  • The fantastic stomp is great for taking out clustered goblyns, but it also leaves Chap vulnerable for a short time. Try to target isolated groups that fit within the stomping radius.
  • Until you start really getting mobbed with goblyns, try to save your health tonicks until you have 3 bites (or less) left.
  • Use your remote mynes! You can only drop 10 at a time before detonating them, but you can use as many as you like. I've found that laying down a horizontal line across the screen is a particularly good way to clear a path through a stampede.
  • Stampeding goblyns seem to avoid the sidewalk...

Still having trouble? Have any tips of your own? Leave a comment!

Saturday, November 7, 2009

Welcome!

Thanks for checking us out! I've got the websites forwarding here until I have the time to make a real website (or the money to pay someone else to do it.) My current project is a game called Goblyn Stomp, an XBox Live Indie game that I would describe as a cross between Geometry Wars and Castle Crashers watered down to a $1 version and set in the early 1900's. Here's a screen shot:


My goal is to finish the game and submit it for review before Thanksgiving, so it'll hopefully be up on XBox Live by early December.