Month: February, 2011

Announcing the first Corona SDK book

 

UPDATE: Unfortunately I won’t finish the book for now. But, you can get the book “Corona SDK Mobile Game Development: Beginner’s Guide” by Michele Fernandez: http://goo.gl/rb8g2

This is something that has been on the works and I feel it’s time to start talking about it already!

I’m writing the first dedicated book about the Corona SDK, and it will be published by a very good and known publisher. Although I can’t reveal which one yet.

The book will be beginner focused, but that doesn’t mean you won’t learn advanced subjects. Also if you already are a programmer or game developer, it will help you learn Lua and the whole Corona SDK.

You will learn Lua and almost all details of the Corona SDK. The book will be tutorial and example driven: you won’t read boring texts about this or that. You will be actually making games while learning Corona! It will be a ready-to-action book.

I will try to give weekly previews about the book, this way I can get your feedback of what you think and expect. Expect more news soon!

Getting news about the book

There is nothing fancy set yet, but you can already like the Facebook page for the book, add the book’s official site to your bookmarks or follow me on Twitter, @KarnakGames.

Book newsletter. Your e-mail:

The last #iDevBlogADay for now

#iDevBlogADay has new rules, and for this reason, I have to give my spot to another person. It’s been almost 6 months of writing 4 posts per month. It was a pleasant and nice experience. And I could say that my time is over in a good moment: I have to focus on the book as well on the dozens of game development contracts I have on my hands.

That doesn’t mean the blog is over. I will keep writing and updating here, probably not weekly, but at least monthly (or weekly for the book previews). You can find constant updates from me on my Twitter @KarnakGames.

With #iDevBlogADay, my blog went from 200 visits/monthly to more than 4000, which is stable now. Thanks Miguel and all the community for the amazing #iDevBlogADay! And good luck for the blogger that is taking my spot.

How to make a simple physics based shooter game with the Corona SDK

 

Corona Simple ShooterIn this article I’ll share with you a very super simple shooter with Corona. The aim of this code is to show you how simple is to setup physic based behaviors with the Corona SDK, how to setup a game loop, handle basic touches, score system and play sound effects.

About the game

  • We have the player (which is a plane) and this plane is constantly shooting bullets automatically with random speed.
  • Physics based, where the enemies just fall down thanks to the gravity, so we don’t need to worry about moving them.
  • The player and the bullets are of the type “kinematic” so they aren’t affected by gravity.
  • For each bullet that hits an enemy plane, we earn one point, show on the top left of the screen.
  • The only way of losing the game is colliding with an enemy plane.
  • There is pew-pew!

The Complete Game with Assets

I’m using 3 sprites from the SpriteLib by Ari Feldman (those sprites are under GPL, which means we can freely using it under some conditions). For the sounds I used the excellent cfxr.

Download everything here.

NOTE: the code is not ready for production use.

The Source Code

Here is the complete code for the game. It is commented, and you can read it up down, it is placed in a form of tutorial. If you have any doubts/questions, please drop a comment below.

The game code itself without comments is around 150 lines. Also it took me around 20 minutes to make. So you bet it? How simple is that? :)

-- Hide status bar, so it won't keep covering our game objects
display.setStatusBar(display.HiddenStatusBar)

-- Load and start physics
local physics = require("physics")
physics.start()

-- A heavier gravity, so enemies planes fall faster
-- !! Note: there are a thousand better ways of doing the enemies movement,
-- but I'm going with gravity for the sake of simplicity. !!
physics.setGravity(0, 20)

-- Layers (Groups). Think as Photoshop layers: you can order things with Corona groups,
-- as well have display objects on the same group render together at once.
local gameLayer    = display.newGroup()
local bulletsLayer = display.newGroup()
local enemiesLayer = display.newGroup()

-- Declare variables
local gameIsActive = true
local scoreText
local sounds
local score = 0
local toRemove = {}
local background
local player
local halfPlayerWidth

-- Keep the texture for the enemy and bullet on memory, so Corona doesn't load them everytime
local textureCache = {}
textureCache[1] = display.newImage("assets/graphics/enemy.png"); textureCache[1].isVisible = false;
textureCache[2] = display.newImage("assets/graphics/bullet.png");  textureCache[2].isVisible = false;
local halfEnemyWidth = textureCache[1].contentWidth * .5

-- Adjust the volume
audio.setMaxVolume( 0.85, { channel=1 } )

-- Pre-load our sounds
sounds = {
	pew = audio.loadSound("assets/sounds/pew.wav"),
	boom = audio.loadSound("assets/sounds/boom.wav"),
	gameOver = audio.loadSound("assets/sounds/gameOver.wav")
}

-- Blue background
background = display.newRect(0, 0, display.contentWidth, display.contentHeight)
background:setFillColor(21, 115, 193)
gameLayer:insert(background)

-- Order layers (background was already added, so add the bullets, enemies, and then later on
-- the player and the score will be added - so the score will be kept on top of everything)
gameLayer:insert(bulletsLayer)
gameLayer:insert(enemiesLayer)

-- Take care of collisions
local function onCollision(self, event)
	-- Bullet hit enemy
	if self.name == "bullet" and event.other.name == "enemy" and gameIsActive then
		-- Increase score
		score = score + 1
		scoreText.text = score

		-- Play Sound
		audio.play(sounds.boom)

		-- We can't remove a body inside a collision event, so queue it to removal.
		-- It will be removed on the next frame inside the game loop.
		table.insert(toRemove, event.other)

	-- Player collision - GAME OVER
	elseif self.name == "player" and event.other.name == "enemy" then
		audio.play(sounds.gameOver)

		local gameoverText = display.newText("Game Over!", 0, 0, "HelveticaNeue", 35)
		gameoverText:setTextColor(255, 255, 255)
		gameoverText.x = display.contentCenterX
		gameoverText.y = display.contentCenterY
		gameLayer:insert(gameoverText)

		-- This will stop the gameLoop
		gameIsActive = false
	end
end

-- Load and position the player
player = display.newImage("assets/graphics/player.png")
player.x = display.contentCenterX
player.y = display.contentHeight - player.contentHeight

-- Add a physics body. It is kinematic, so it doesn't react to gravity.
physics.addBody(player, "kinematic", {bounce = 0})

-- This is necessary so we know who hit who when taking care of a collision event
player.name = "player"

-- Listen to collisions
player.collision = onCollision
player:addEventListener("collision", player)

-- Add to main layer
gameLayer:insert(player)

-- Store half width, used on the game loop
halfPlayerWidth = player.contentWidth * .5

-- Show the score
scoreText = display.newText(score, 0, 0, "HelveticaNeue", 35)
scoreText:setTextColor(255, 255, 255)
scoreText.x = 30
scoreText.y = 25
gameLayer:insert(scoreText)

--------------------------------------------------------------------------------
-- Game loop
--------------------------------------------------------------------------------
local timeLastBullet, timeLastEnemy = 0, 0
local bulletInterval = 1000

local function gameLoop(event)
	if gameIsActive then
		-- Remove collided enemy planes
		for i = 1, #toRemove do
			toRemove[i].parent:remove(toRemove[i])
			toRemove[i] = nil
		end

		-- Check if it's time to spawn another enemy,
		-- based on a random range and last spawn (timeLastEnemy)
		if event.time - timeLastEnemy >= math.random(600, 1000) then
			-- Randomly position it on the top of the screen
			local enemy = display.newImage("assets/graphics/enemy.png")
			enemy.x = math.random(halfEnemyWidth, display.contentWidth - halfEnemyWidth)
			enemy.y = -enemy.contentHeight

			-- This has to be dynamic, making it react to gravity, so it will
			-- fall to the bottom of the screen.
			physics.addBody(enemy, "dynamic", {bounce = 0})
			enemy.name = "enemy"

			enemiesLayer:insert(enemy)
			timeLastEnemy = event.time
		end

		-- Spawn a bullet
		if event.time - timeLastBullet >= math.random(250, 300) then
			local bullet = display.newImage("assets/graphics/bullet.png")
			bullet.x = player.x
			bullet.y = player.y - halfPlayerWidth

			-- Kinematic, so it doesn't react to gravity.
			physics.addBody(bullet, "kinematic", {bounce = 0})
			bullet.name = "bullet"

			-- Listen to collisions, so we may know when it hits an enemy.
			bullet.collision = onCollision
			bullet:addEventListener("collision", bullet)

			bulletsLayer:insert(bullet)

			-- Pew-pew sound!
			audio.play(sounds.pew)

			-- Move it to the top.
			-- When the movement is complete, it will remove itself: the onComplete event
			-- creates a function to will store information about this bullet and then remove it.
			transition.to(bullet, {time = 1000, y = -bullet.contentHeight,
				onComplete = function(self) self.parent:remove(self); self = nil; end
			})

			timeLastBullet = event.time
		end
	end
end

-- Call the gameLoop function EVERY frame,
-- e.g. gameLoop() will be called 30 times per second ir our case.
Runtime:addEventListener("enterFrame", gameLoop)

--------------------------------------------------------------------------------
-- Basic controls
--------------------------------------------------------------------------------
local function playerMovement(event)
	-- Doesn't respond if the game is ended
	if not gameIsActive then return false end

	-- Only move to the screen boundaries
	if event.x >= halfPlayerWidth and event.x <= display.contentWidth - halfPlayerWidth then
		-- Update player x axis
		player.x = event.x
	end
end

-- Player will listen to touches
player:addEventListener("touch", playerMovement)

How to take in-game screenshots with Cocos2D and upload them to a Facebook album

 

I’ll be covering a very cool feature to add a new social feature in your game: posting in-game screenshots straight from the game to a Facebook album! I managed to do it today for a client game and it worked smoothly, so why not sharing?

What is covered in this article?

  1. Taking a screenshot via code from your Cocos2D game and storing it in an UIImage.
  2. Integrating Facebook into your Cocos2D game.
  3. Posting the screenshot to the Facebook account of the player (upon permission).

Taking Screenshots in Cocos 2D

This step was the easiest one for me. Oh wait, it’s not that easy to take a screenshot of the actual state of your game: what I have done was using a code written by Manu Corporat (Infinity Field developer). Grab the “Cocos2D Screenshot” methods here and add them inside the class CCDirectorIOS (cocos2d/Platforms/iOS/CCDirectorIOS.m) – it works for iPhone, iPhone 4 and iPad screenshots, e.g. all iOS devices and versions.

You can now take screenshots from your game by just calling:

UIImage *screenshot = [[CCDirector sharedDirector] screenshotUIImage];

You can even save this image in a folder (or even the Photo Album):

NSString *savePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/Screenshot.png"];

// Write image to PNG
[UIImagePNGRepresentation(tempImage) writeToFile:savePath atomically:YES];

Integrating Facebook into your Cocos2D game

- First of all, Setup a New App on Facebook: http://www.facebook.com/developers/. Once the application is created, click on the left tab “Mobile and Devices”, change the “Application type” to “Native App” and copy the “Application ID”.

- Open your Xcode project and add the following keys to your info.plist: Key: URL types -> Key: Item 0 -> Key: URL Schemes -> Key: Item 0 -> Value: fbYOUR_APP_ID (include the fb), it should look like:

Facebook - info.plist

- Clone the Facebook iOS-SDK from Git: https://github.com/facebook/facebook-ios-sdk.

Drag the “src” folder into your Xcode project.

- If you want the official “Login with Facebook” blue button, open the folder “sample” that you cloned with the Facebook SDK, and right click “FBConnect.bundle” -> Show Package Contents -> copy and paste the images you want in your Cocos2D project. Consider renaming @2x to -hd.

- Import FBConnect.h into your Cocos2D Scene or Layer, the one in which you want to open the Facebook connection:

#import "FBConnect.h"

- Your class interface has to implement the Facebook delegates, as well have some members:

@interface HelloWorld : CCLayer
{
	// Facebook
	Facebook *facebook;
	BOOL isFBLogged;

	// GUI
	CCLayer *shareLayer;
	CCMenuItem *facebookLoginButton, *facebookLogoutButton;
	CCLabelTTF *message;
}

- Add the GUI to handle facebook messages, as well the Login button (in this case you do as you wish, the way I’m doing is just an easy example). Although my GUI code is just an example, note that I’m inserting them inside a child CCLayer. When we are going to take the screenshot, we won’t want the Facebook button in the screenshot, nor the label saying “SHARE”, so I’ll just turn visibility of the layer on and off, instead of turning every element individually.

-(id) init
{
	if( (self=[super init] )) {
		CGSize size = [[CCDirector sharedDirector] winSize];

		CCSprite *gameElement = [CCSprite spriteWithFile:@"poolplane.png"];
		gameElement.position = ccp(size.width * .5, gameElement.contentSize.height * .5);
		[self addChild:gameElement];

		shareLayer = [CCLayer node];
		[self addChild:shareLayer];

		facebookLoginButton = [CCMenuItemImage itemFromNormalImage:@"LoginWithFacebookNormal.png" selectedImage:@"LoginWithFacebookPressed.png" disabledImage:@"LoginWithFacebookPressed.png" target:self selector:@selector(facebookLogin)];

		CCMenu *fbMenu = [CCMenu menuWithItems:facebookLoginButton, facebookLogoutButton, nil];
		fbMenu.position = ccp(size.width * .5, size.height * .5 + facebookLoginButton.contentSize.height * 3);
		[shareLayer addChild:fbMenu];

		message = [CCLabelTTF labelWithString:@"Share in Facebook!" fontName:@"Marker Felt" fontSize:24];
		message.position = ccp(size.width * .5, fbMenu.position.y + facebookLoginButton.contentSize.height);
		[shareLayer addChild:message];
	}
	return self;
}

Logging in into Facebook and getting permissions

We may enable the user to login into his Facebook account by simply calling the Facebook:authorize method passing in the permissions we want to get. We also may already hide unecessary items for the screenshot before calling the Facebook authorize method. You will know why later on.

- (void) facebookLogin
{
	// The screenshot is going to be taken instantly after the login,
	// so already hide GUI/unecessary stuff
	shareLayer.visible = NO;

	if (facebook == nil) {
		facebook = [[Facebook alloc] initWithAppId:@"FACEBOOK-APP-ID"];
	}	

	NSArray* permissions =  [[NSArray arrayWithObjects:
							  @"publish_stream", @"offline_access", nil] retain];

	[facebook authorize:permissions delegate:self];
}

Now we implement the delegate functions to handle the login request responses. They are very simple. The game will take the screenshot right after logging in (inside FBSessionsDelegate:fbDidLogin) and that is why we’ve hidden the GUI before logging in (the takeScreenshot screen is detailed below).

#pragma mark -
#pragma mark FBSessionDelegate
/**
 * Called when the user has logged in successfully.
 */
- (void)fbDidLogin {
	isFBLogged = YES;
	[self takeScreenshot];
}

/**
 * Called when the user canceled the authorization dialog.
 */
-(void)fbDidNotLogin:(BOOL)cancelled {
	if (cancelled) {
		[message setString:@"Login cancelled. No Login, No Share, No Game! :)"];
	} else {
		[message setString:@"Error. Please try again."];
	}

	shareLayer.visible = YES;
}

Taking the screenshot, uploading to Facebook and handling the responses

This is where the real magic happens: sending the screenshot to Facebook. It’s a very simple GRAPH API call to “me/photos” that sends the UIImage we make with the CCDirector:screenshotUIImage method.

According to the documentation this call uploads an image to the application’s album in the user account: “We automatically create an album for your application if it does not already exist. All photos from your application will be published to the same automatically created album.”

- (BOOL) takeScreenshot
{
	UIImage *tempImage = [[CCDirector sharedDirector] screenshotUIImage];

	NSMutableDictionary* params = [NSMutableDictionary dictionaryWithObjectsAndKeys:
								   tempImage,@"message",
								   nil];

	[facebook requestWithGraphPath:@"me/photos"
						 andParams:params
					 andHttpMethod:@"POST"
					   andDelegate:self];

	[message setString:@"Uploading screenshot. Please wait..."];
	shareLayer.visible = YES;

	return YES;
}

The last step is handling the responses: the upload may fail or be successful. For the first case we would need to alert the user about the error andshow the Facebook button again and enable a new upload, for the later we have to show a positive notification for the user. In both case we need to show the GUI layer again.

#pragma mark -
#pragma mark FBRequestDelegate
/**
 * Called when a request returns and its response has been parsed into
 * an object. The resulting object may be a dictionary, an array, a string,
 * or a number, depending on the format of the API response. If you need access
 * to the raw response, use:
 *
 * (void)request:(FBRequest *)request
 *      didReceiveResponse:(NSURLResponse *)response
 */
- (void)request:(FBRequest *)request didLoad:(id)result {
	[message setString:@"Photo posted in the \"APP NAME\" album on your account!"];
	shareLayer.visible = YES;

}

/**
 * Called when an error prevents the Facebook API request from completing
 * successfully.
 */
- (void)request:(FBRequest *)request didFailWithError:(NSError *)error {
	[message setString:@"Error. Please try again."];
	shareLayer.visible = YES;
}

Example/Template project

I’ve packed everything in a Cocos2D Xcode project, ready to be used and compiled: just change your Facebook App’s ID and adapt in your project.
Download the example project here.

This post is part of iDevBlogADay, a group of indie iOS development blogs featuring two posts per day. You can keep up with iDevBlogADay through the web site, RSS feed, Twitter.

Tips #1: Cornerstone, AgileZen and Compressed Cocos2D textures with Texture Packer

 

I’m really busy this week, so this is not an article. I wanted to share some tips from what I’ve been dealing recently, that I found to be very useful.

Cocos2D pixel formats and highly compressed textures with Texture Packer

One of the games I’m working on has dozens of sprite sheets, and most of them are 2048×2048. That is a killer even for the iPhone 4 and iPad, and I can’t even consider older devices.

Consider using 4 Sprite Sheets at once that are 2048×2048 each and PNG: the game wouldn’t even run, because the consumed memory would be too big. Also, the loading times would be terrible.

What is the solution?

  1. Get Texture Packer.
  2. Group background images and other squared sprites in the same sprite sheet, choose the RGB565 pixel format, select “FloydSteinberg+Alpha” as the Dithering algorithm and export it as a compressed PVR texture (pvr.ccz). BINGO! The image looks the same as 32-bits pixel format, the file size is smaller than PNG, and the loading time is decreased from around 15s to 1s (at least with my 2048×2048 spritesheet!).
  3. For sprites with alpha/transparency required, you may use RGBA4444 + FloydSteinberg+Alpha for those with a few to no gradients, or even RGBA8888, but in both cases export them as pvr.ccz! Using RGBA4444 reduces the memory usage by half. For RGBA8888 you still have the same memory usage, but since it is pvr.ccz, at least the loading times are cut.The problem is that most of the times RGBA4444 looks bad, so you may give a chance to RGGBA8888 + PVR.CCZ.

To learn more about Texture Packer and Cocos2D textures, read How to Create and Optimize Sprite Sheets in Cocos2D with Texture Packer and Pixel Formats by Ray Wenderlich!

On the end: Texture Packer is probably the most important tool for a Cocos2D developer.

The real good Mac SVN Client: Cornerstone!

After I wrote the last article dealing with SVN, where I recommended Versions as the best Mac client, I received lots of suggestions in Twitter and in the comments to use Cornerstone. I decided to try it, and now I can’t even open Versions anymore. The only problem is buying another $59 license!

From a tweet I posted (that got retweeted by the developers of Cornerstone): “After using Cornerstone for some days, I can’t even use Versions anymore. It lacks everything and looks old. #svn”.

AgileZen and Lean project management

Got to know about agile lean project management yesterday, which lead me to meet AgileZen – probably the simplest project management system on earth, yet probably the most efficient.

You can read a good overview of AgileZen here: AgileZen for Solo Remote Development.

This post is part of iDevBlogADay, a group of indie iOS development blogs featuring two posts per day. You can keep up with iDevBlogADay through the web site, RSS feed, Twitter.