nathandemick.com

cocos2d Game Tutorial - Multitouch Asteroids (Part 1)

So I’ve posted some general-purpose tutorials about some of the lower level components of cocos2d, and I figure it’s as good of a time as any to have a tutorial that makes something “real.” So, for this next series, we’re going to concentrate on making a full-fledged game - an Asteroids clone. The game logic that controls Asteroids is pretty darn simple, which means that most of the discussion will be about cocos2d and Objective-C. We’ll also discuss adapting a game that uses a traditional control scheme (joystick & buttons) to use a touchscreen instead.

Let’s get started! Create a new Xcode project that uses a cocos2d template. Now, let’s first think about the most basic game objects in Asteroids that need to be represented on the screen. They are, of course, the player’s ship, the player’s bullets, and the asteroids (yes, in the real game there are aliens as well, but for now let’s work with the smallest number of objects we need, just for simplicity). Our first task will be to create a ship object, place it on the screen, and have it move around based on user input. Each object in our game will be represented by a CCSprite, so I’ve decided to create subclasses of CCSprite to represent the three objects in our game. Right-click on the Resources group in your Xcode sidebar, and add a new file. Choose the cocos2d template that extends CCSprite, and name the file “ship.m.” Now you have a basic class that will be used to represent the player ship. However, at this point the class is the exact same as a CCSprite… let’s add some additional code to it that will be useful. For example, we want to let the ship have a “velocity” property, and then make the ship automatically move based on its velocity. Also (if you remember Asteroids), when an object goes off the game screen, it reappears on the other side. It’d be nice if that happened automatically, and we didn’t have to put logic in the game loop that checks each each visible object. Modify your “ship.h” file to look something like this:


#import "cocos2d.h"

@interface Ship : CCSprite
{
	// Store how fast the ship is moving
	CGPoint velocity;
}

@property CGPoint velocity;

// Have to override this method in order to subclass CCSprite
- (id)initWithTexture:(CCTexture2D*)texture rect:(CGRect)rect;

// This method gets called each time the object is updated in the game loop
- (void)update:(ccTime)dt;

@end

You can see that this new class has access to all the features of a regular sprite, but we’re also giving it a new property, “velocity.” A CGPoint is a struct that has x/y values, so it’s real handy to use when dealing with Cartesian coordinates. For example, we could get the horizontal velocity of the ship by accessing ship.velocity.x. There are also two methods declared here that override some of CCSprite’s default methods. The first method is required if you want to subclass a sprite. The second is where we’ll put all the custom logic that the ship requires. Now let’s take a look at the “ship.m” implementation file.


#import "Ship.h"

@implementation Ship

// Automatically create "setters" and "getters" for the velocity property
@synthesize velocity;

// The init method we have to override - http://www.cocos2d-iphone.org/wiki/doku.php/prog_guide:sprites (bottom of page)
- (id)initWithTexture:(CCTexture2D*)texture rect:(CGRect)rect
{
	// Call the init method of the parent class (CCSprite)
	if ((self = [super initWithTexture:texture rect:rect]))
	{
		// The only custom stuff here is scheduling an update method
		[self scheduleUpdate];
	}
	return self;
}

// Gets updated every game loop iteration
- (void)update:(ccTime)dt
{
	// Move the ship based on the "velocity" variable
	[self setPosition:ccp(self.position.x + velocity.x, self.position.y + velocity.y)];

	// Get window size
	CGSize windowSize = [CCDirector sharedDirector].winSize;

	// If object moves off the bounds of the screen, make it appear on the other size
	if (self.position.x < 0)
		[self setPosition:ccp(windowSize.width, self.position.y)];
	else if (self.position.x > windowSize.width)
		[self setPosition:ccp(0, self.position.y)];

	if (self.position.y < 0)
		[self setPosition:ccp(self.position.x, windowSize.height)];
	else if (self.position.y > windowSize.height)
		[self setPosition:ccp(self.position.x, 0)];
}
@end

The point of interest here is the [self scheduleUpdate] method, which will automatically look for a method called “update,” and then fire it every time the game loop runs. You can see that most of the code in “update” is pretty basic game logic: the ship gets moved based on its’ velocity, and if it goes off the edge of the screen, it loops around to the other side.

Now let’s create a new scene/layer where we can display our brand-spankin’-new “ship” class. Create a new file, have it subclass CCLayer, and name it something like “GameScene.” I’m basically copy/pasting the code that is used in the default “HelloWorldScene.m” file in order to create a new scene. Make “GameScene.h” look like this:


#import "cocos2d.h"
#import "Ship.h"

@interface GameLayer : CCLayer
{
	Ship *ship;
}

+ (id)scene;

@end

Now open up “GameScene.m” and we’ll create the code that will initialize a Ship object and display it on the screen. At this point you should probably draw a ship graphic for your project (I use Acorn, which has a free trial and doesn’t expire). Make the ship’s nose point to the right, since the ship’s rotation will start off at zero (think about the unit circle). Save the image in your project’s Resources folder, then add it to the project by right-clicking the Resources group in your project sidebar, and choosing “Add existing file.” Since the file is already in the project’s directory structure, you can leave the “Copy to project folder” checkbox unchecked. Uhh, so what was I saying? Right, GameScene.m:


#import "GameScene.h"
#import "Ship.h"

@implementation GameLayer

+ (id)scene
{
	// 'scene' is an autorelease object.
	CCScene *scene = [CCScene node];

	// 'layer' is an autorelease object.
	GameLayer *layer = [GameLayer node];

	// add layer as a child to scene
	[scene addChild:layer];

	// return the scene
	return scene;
}

- (id)init
{
	if ((self = [super init]))
	{
		// Set layer to respond to touch events
		[self setIsTouchEnabled:YES];

		// Get window size
		CGSize windowSize = [CCDirector sharedDirector].winSize;

		// Create ship object, position it, then add to layer
		ship = [Ship spriteWithFile:@"ship.png"];
		ship.position = ccp(windowSize.width / 2, windowSize.height / 2);
		[self addChild:ship];

	}
	return self;
}
@end

This does exactly what it looks like: instantiates our Ship class and displays it on the screen. If you build and run the project now, you’ll see the ship smack in the center of the screen. Pretty boring, though… right now the ship has no way of moving. To move it, we’ll have to implement the three touch-detecting methods talked about in my previous tutorial, and determine how the user input will move the ship. The scheme I decided upon is pinch & rotate to rotate the ship, swipe to move the ship in the direction it’s facing, and a single tap to shoot. Notice in the following code there are some variables that store the previous values of certain bits of user input. This is something that’s necessary with movement-based input. With button input, it’s obvious what the player wants to do when they press a button, but when they move their finger on the screen, it’s not so obvious. Are they trying to swipe or tap? During each iteration of the game loop, we only have the current position of the users’ fingers; we don’t know anything about what they did a second before. So, we store some previous values to figure out if a player is tapping or swiping, as well as which direction to rotate the ship. Open up “GameScene.h” and modify it to look like the following:


#import "cocos2d.h"
#import "Ship.h"

@interface GameLayer : CCLayer
{
	Ship *ship;

	// To determine rotation
	float previousTouchAngle, currentTouchAngle;

	// To determine movement/shooting
	CGPoint startTouchPoint, endTouchPoint;
}

+ (id)scene;

@end

Now we’ll add the movement-detecting methods in “GameScene.m.” You can put these anywhere between @implementation GameLayer and @end.


- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
	// This method is passed an NSSet of touches called (of course) "touches"
	// The "allObjects" method returns an NSArray of all the objects in the set
	NSArray *touchArray = [touches allObjects];

	// Only run the following code if there is more than one touch
	if ([touchArray count] > 0)
	{
		// Create "UITouch" objects representing each touch
		UITouch *fingerOne = [touchArray objectAtIndex:0];

		// Convert each UITouch object to a CGPoint, which has x/y coordinates we can actually use
		CGPoint pointOne = [fingerOne locationInView:[fingerOne view]];

		// The touch points are always in "portrait" coordinates - convert to landscape
		pointOne = [[CCDirector sharedDirector] convertToGL:pointOne];

		// We store the starting point of the touch so we can determine whether the touch is a swipe or tap.
		// A tap shouldn't move, so we compare the distance of the starting/ending touches, and if the distance is
		// small enough (we account for a bit of movement, just in case), the input is considered a tap
		startTouchPoint = pointOne;

		// Only run the following code if there is more than one touch
		if ([touchArray count] > 1)
		{
			// Create "UITouch" objects representing each touch
			UITouch *fingerTwo = [touchArray objectAtIndex:1];

			// Convert each UITouch object to a CGPoint, which has x/y coordinates we can actually use
			CGPoint pointTwo = [fingerTwo locationInView:[fingerTwo view]];

			// The touch points are always in "portrait" coordinates - convert to landscape
			pointTwo = [[CCDirector sharedDirector] convertToGL:pointTwo];

			// Initialize the variables used to store the angle of rotation derived from the user's fingers
			currentTouchAngle = previousTouchAngle = CC_RADIANS_TO_DEGREES(atan2(pointOne.x - pointTwo.x, pointOne.y - pointTwo.y));
		}
	}
}

- (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
	// This method is passed an NSSet of touches called (of course) "touches"
	// "allObjects" returns an NSArray of all the objects in the set
	NSArray *touchArray = [touches allObjects];

	// Only run the following code if there is more than one touch
	if ([touchArray count] > 1)
	{
		// We're going to track the first two touches (i.e. first two fingers)
		// Create "UITouch" objects representing each touch
		UITouch *fingerOne = [touchArray objectAtIndex:0];
		UITouch *fingerTwo = [touchArray objectAtIndex:1];

		// Convert each UITouch object to a CGPoint, which has x/y coordinates we can actually use
		CGPoint pointOne = [fingerOne locationInView:[fingerOne view]];
		CGPoint pointTwo = [fingerTwo locationInView:[fingerTwo view]];

		// The touch points are always in "portrait" coordinates - you will need to convert them if in landscape (which we are)
		pointOne = [[CCDirector sharedDirector] convertToGL:pointOne];
		pointTwo = [[CCDirector sharedDirector] convertToGL:pointTwo];

		// Get the angle that's created by the user's two fingers - see http://en.wikipedia.org/wiki/Atan2
		currentTouchAngle = CC_RADIANS_TO_DEGREES(atan2(pointOne.x - pointTwo.x, pointOne.y - pointTwo.y));

		// Compare with the previous angle, to decide whether the change is positive or negative.
		float difference = currentTouchAngle - previousTouchAngle;

		// The ship is then rotated by that difference
		ship.rotation += difference;

		// Store the current angle variable to be used again on the next loop iteration
		previousTouchAngle = currentTouchAngle;
	}
}

- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
	// Get array of touch objects
	NSArray *touchArray = [touches allObjects];

	// Only run this if there's one touch
	if ([touchArray count] == 1)
	{
		// Create "UITouch" objects representing each touch
		UITouch *fingerOne = [touchArray objectAtIndex:0];

		// Convert each UITouch object to a CGPoint, which has x/y coordinates we can actually use
		CGPoint pointOne = [fingerOne locationInView:[fingerOne view]];

		// The touch points are always iin "portrait" coordinates - convert to landscape
		pointOne = [[CCDirector sharedDirector] convertToGL:pointOne];

		// Set the variable that stores the ending touch point
		endTouchPoint = pointOne;

		// Get the distance that the user's finger moved during this touch
		float distance = sqrt(pow(endTouchPoint.x - startTouchPoint.x, 2) + pow(endTouchPoint.y - startTouchPoint.y, 2));

		// If the distance moved (in pixels) is small enough, consider the gesture a tap
		if (distance < 5)
		{
			NSLog(@"Shoot!");
		}
		// Otherwise, it's a swipe
		else
		{
			// Use distance of swipe as a multiplier for the ship velocity (longer swipe, go faster)
			ship.velocity = ccp(cos(CC_DEGREES_TO_RADIANS(ship.rotation)) * distance / 100, -sin(CC_DEGREES_TO_RADIANS(ship.rotation)) * distance / 100);
		}

	}
}

This is a lot to go through, but if you read through the code, it should be easy to follow the flow. The “ccTouchesBegan” method simply stores some values based on how many touches are detected. The “ccTouchesMoved” method looks to see if the player is using a pinch gesture, and if they are, it figures out the angle between the two fingers (read up on the atan2 function; it’s super useful for 2D games) and rotates the ship. “ccTouchesEnded” checks where the player’s finger left the screen; if it’s in roughly the same place as where it first touched, that’s interpreted as a tap, which shoots. Otherwise the player swiped their finger across the screen, which means they want to move. The ship’s current angle is obtained, and then the ship’s velocity property is set using that angle multiplied by how far the player moved their finger.

Build and run the program, then try using some of the gestures we programmed (hold the option key to simulate pinch/rotate). Hopefully you should see the ship rotating and moving (as well as seeing log messages when you tap). The ship will also wrap around to the opposite side of the screen instead of disappearing when it goes off the edge. Remember, the ship’s movement and position wrapping happen automatically due to the “update” method that we programmed into the class. You don’t have to worry about any of that in the main game loop.

And, that’s a wrap for this installment of the tutorial. Next time we’ll add asteroids and bullets, as well as collision detection. Having problems, or were any sections of the tutorial unclear? Let me know in the comments, and I’ll try to clarify. Thanks for reading!

(Update: continue reading part two and part three!)

· 7 comments

A Holiday Message from Nathan Demick: Why I'm not an Atheist

If you browse the internets with any degree of frequency, no doubt you've seen an essay by Ricky Gervais making the rounds. Gervais is a British actor, most famous for creating the TV show "The Office." In this essay, entitled "A Holiday Message from Ricky Gervais: Why I’m An Atheist," he promotes his atheistic beliefs, apparently as a counterpoint to whatever religious symbolism is still left in our culture's Christmas celebration.

Gervais starts out by attempting to be humble. When people ask him why he doesn't believe in God, he says, "I still give my logical answer because I feel that not being honest would be patronizing and impolite." Unfortunately, his honest answer, as detailed in the rest of the essay, felt very patronizing to me. I've attempted here to form a counter-point: "A Holiday Message from Nathan Demick: Why I'm not an Atheist."

Many people use the idea of empirical science as their reason for not believing in God. "In the ancient past," they reason, "man had no idea how the world worked. They created this idea of a "god" to explain natural phenomena. We now know how the world works, and the supernatural has no place in it. We can't test the existence of a deity, so therefore there is none." Yes, it's impossible to prove there is a God. But there are many pieces of evidence that, if we choose to consider them, point to his existence.

Consider the "lowly" cell: although it is the most basic component of biological life, it is ridiculously complex in its' own right. Just one cell is a marvel of biological engineering... but science doesn't have an answer for how cells originated. There are theories and conjectures, but no repeatable tests that can prove to us how life started. We can believe that life formed through naturalistic means. Or we can believe that the complexity of life was started by God. To be honest, both views seem a bit ridiculous at first glance. "Life was created by an all-powerful, invisible man." Or, "Life formed spontaneously out of nothing." But the first is less ridiculous, because of what I've learned through science.

If, while on a walk, you come across an arrangement of rocks (let's use Stonehenge as an example), you might consider how the stones came to be in that formation. Well, even primitive structures like Stonehenge are obviously the result of someone's will. How can we consider the intricacies of life to be the result of some ex nihilo naturalistic phenomenon? It's like finding an iPad on the ground and saying, "Hey, that's a pretty cool rock!" Science tells us that nature does not produce order from disorder, or life from nothing.

In Romans 1:20, Paul writes, "For since the creation of the world God’s invisible qualities—his eternal power and divine nature—have been clearly seen, being understood from what has been made, so that people are without excuse." Nature itself (and science, which is the observation of nature) is evidence for God.

Just as we must choose between atheism and theism, a theist must choose what deity to believe in. That choice is made by examining the philosophy that a religion espouses, and deciding whether the physical evidence and our observations about life fit in with that philosophy. Studying religious texts is the easiest way to do this in many cases. In the case of Christianity, this would mean determining whether the Bible (a combination of Jewish and Christian religious writings) is a valid source of knowledge.

One way of determining its' validity is to look at its' contents. For example, the Bible claims to have been written by God through the inspiration of human authors. Part of their writings include predictions of the future. If these predictions were verified to be false, we could immediately determine the text to be unreliable. However, the oldest copies of Biblical texts have been dated to long before the events they predict (for example, when Jesus was supposed to appear — Google "Daniel 9"). Is this proof that God exists? No, but it's another chunk of evidence that you should consider before making your decision.

Some people today reject "Christian morals." They feel that such rules infringe on human rights — to live in whatever way seems best to them. "That's the way I am; I can choose for myself," they might say. Well, consider the psychotic serial murderer. Why can't he live his life in the way that comes most naturally for him? "It's not right," you might say. But who are you to say what is right for others? Does society determine what's right? In 1940's-era Germany, "right" was genocide against "ethnically inferior" people. Without an absolute source of truth, the morality of our actions is completely relative.

The Bible claims that humans were created to live in a certain way. Humans were created perfect, but made the choice to decide good and bad for themselves. When our ancestors did this, they introduced evil into the world — man's will instead of God's will. This is evident when we consider what's observable about people: we have capacity for greatness, but also depravity. In this case, the Bible perfectly describes the human condition, which is another piece of evidence that I consider in my choice.

These are some of the things I think about when others question my belief. I'm sure that this was a bit more rambling and disjointed than I'd hoped, but let me know in the comments if you'd like clarification. Just as I use the critiques of others as an opportunity for reflection, I hope you will too. In the end, your decision is your decision; just make sure that you've considered the evidence first.

· 1 comments

Review: Kometen

This week's iOS app review is a game called Kometen, which was programmed by Erik Svedäng, with art and music by Niklas Åkerblad. Erik is most well-known for his game Blueberry Garden, which won the Seumas McNally Grand Prize at the 2009 Game Developer's Conference. I was initially drawn to try Kometen based on Erik's reputation, as well as the game's whimsical art direction.

The premise of Kometen is that you control a comet, and set out to explore the universe. The universe of Kometen contains many planets, which you can orbit by tapping them. Swiping the screen will disengage you from orbiting a particular planet, and you'll speed off in a straight line. You can't just travel infinitely, however; your comet has a boost meter, which gets depleted a little bit each time you leave the orbit of a planet. Fortunately, many planets have edible rings, made up of bits of food, which you can eat to recharge. When your boost meter is full, you can swipe across the whole screen to travel at light speed for a short time. Your goal is to fully explore the universe, which entails discovering a number of hidden planets. Tapping your comet will bring up a map, which helps you navigate. The map shows you planets you haven't yet discovered; tapping one will bring up a navigation arrow on the game screen, which continually points in the direction of your destination.

I really found myself enjoying the mechanics of the game, as well as the art and music. From what I understand, all the art was painted in watercolor on paper, then scanned into the game. The music, while a bit repetitive, is pretty good, and fits in well with the game's mood. The touch controls are also very well thought out — the taps and finger swipes used to move your character feel very natural. From a production standpoint, I was also impressed; the whole game is put together very well.

After playing for a while, however, I kinda got bored. The goal of the game is exploration, and to gain satisfaction from interacting with and traveling to different planets. While that's fun for a while, I soon get bored without more concrete goals. When you travel to a new, hidden planet, the only benefit you get is to see some cool, unique artwork, and to have that planet revealed on your mini-map. Once you get tired of looking at the art, there's not much else that is very compelling from a gameplay standpoint. For me, a game is fun if the player can compete against something, whether that's trying to beat a difficult level, or best a high score, or whatever. Kometen just doesn't have enough gameplay to keep me coming back.

★★★☆☆ — Kometen on the App Store (Affiliate Link)

· 0 comments

Intro to Load Testing with JMeter (part deux!)

If you've been following along, you'll know that JMeter is most commonly used to do automated testing; you can create a bunch of virtual users to perform a set of pre-determined actions on your website. In my previous post, I detailed how to manually create a list of actions for your test users to perform. However, manually creating long lists of test actions is kind of a pain! Fortunately, JMeter includes a proxy utility that can intercept your browser requests and store them as user actions.

Fire up JMeter, and you'll be presented with a new plan. Create a new thread group (your users) in the Test Plan (as detailed in the previous tutorial). Now, instead of manually adding HTTP Requests for these users to perform, we'll hook up a proxy server to create the HTTP Requests for us.

Right-click on the WorkBench item in the sidebar, and select Add > Non-Test Elements > HTTP Proxy Server. You don't have to configure anything here, just click the Start button at the bottom of the config window to start the proxy server on port 8080. Now you'll have to configure your browser to pass its' requests through the proxy server. In Mac OS X, you do this system-wide by accessing your System Preferences. Go to System Preferences > Network > Advanced (the "Advanced" button is in the lower-right corner of the window). When the advanced options window pops up, click the Proxies tab, then check the Web Proxy (HTTP) option, and change the Web Proxy Server port to 8080. Click OK to save your changes, then Apply to make them take effect.

Next, open your browser and go to the JMeter homepage (http://jakarta.apache.org/jmeter/). Click a few links, and then go back to your JMeter window. There should now be an arrow next to your thread group, meaning that the group contains some actions. Click the arrow to expand the list, and you should see requests for all the resources that you requested in your browser. You'll see that requests have been made for every resource your browser asked for, such as images and CSS files. You can remove those requests if you want, or leave them in for a more realistic simulation.

Don't forget to go back into your Network settings and disable the proxy server, otherwise you won't be able to make HTTP requests when you close JMeter. However, this method of creating JMeter test cases is way easier than its' manual counterpart.

· 0 comments

Humble Indie Bundle #2

I'm sure you're already aware of this promotion going on, but just in case, I'll do a little PSA. The second iteration of the Humble Indie Bundle is happening right now. It's become popular these days for smaller, independent developers (of both games and other software) to steeply discount their products and sell them as part of a promotional bundle. While they don't make as much per sale, there are usually a lot more sales. For example, a person might only want one piece of software in the bundle, but they buy it anyway because they think it's a good deal. Then all the bundle participants benefit.

The original Humble Indie Bundle was made pretty popular because the organizers donated a portion of their proceeds to charity (offering very high profile independent games such as World of Goo, Aquaria, and Gish in the bundle also helped). Although I had already purchased Gish and Aquaria, I bought the bundle to help support the developers. This second bundle isn't quite as compelling for me... the only game I'm interested in playing is Braid (I'm on a Mac and don't have an Xbox 360, so haven't played it yet). But I'm going to buy it anyway, and you should too. Hopefully the continued success of these kind of promotions will be great for both gamers (get games for cheap) and developers (get money to eat).

· 2 comments