Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

10. April 2012

 

 

Got an Android device and dreamed about coding while out and about?  Until now your choices have been slim to none, but that’s changed with the release of AIDE.aide  What is AIDE?  It’s a complete Java toolchain, including IDE that runs on your Android device.  Frankly, it’s really really really cool.  Did I mention yet, it’s very cool?  Cause it is!

 

 

Everything you need to create an Android app is included, from a text editor with full auto-completion, full compiler, app signing.  Perhaps more impressively, it also includes a full Git implementation and the ability to load Eclipse projects from your DropBox account.

 

 

Here is AIDE in action on my Samsung Galaxy Note:

 

export_08

 

On my Transformer, with full keyboard, it is actually a remarkably desktop-like development experience, but even more shocking, developing on my phone was actually do-able.  Not something I would want to do every day, but editing code was very natural.  The zooming feature is extremely smooth.

 

 

 

Now ready to be really impressed?

 

 

First off… it’s free!

 

 

Second, it’s just over 6MB in size!  An entire Java toolchain, including IDE in under 6 MB?  Extremely impressive.

 

 

AIDE is still currently in beta, but well worth checking out!  You do require Android 2.2 or higher.

Cool Thing of the Week

5. April 2012

 

 

image

 

Uggh.  Create a new Ice Cream Sandwich ( Android 4 ) emulator in AVD Manager, hit run and KABOOM! “emulator-arm.exe has stopped working”.  Lovely.

 

Still haven’t figured out exactly what the problem is, but I do know disabling the camera “fixes” the problem.  The most confusing part, creating an emulator that runs ICS, but uses a different screen resolution than WXGA and everything works just fine.

 

So, if you are trying to run the Android emulator using WXGA resolution and it keeps crashing, try disabling the camera.  I’ll update if I ever figure out exactly what is causing the problem.  I found this thread, which suggested disabling ATI Tray tools, however, my machine is nVidia based so that isn’t going to do it.

General ,

3. April 2012

 

 

No.

 

 

Well, sorta, just not for free.  Since my earlier post about trying to get Blackberry working with Appcelerator Titanium, I’ve gotten a number of messages, emails and search requests asking exactly this question. “Does Titanium Studio support Blackberry?”.  It is understandable why, Appcelerator sure doesn’t make it easy to figure out!

 

 

So, to actually answer the question, the free version of Titanium does not, and cannot currently be made to support Blackberry development.  It looks like it does, and you can download all the Blackberry SDKs ( except one ), but other than wasting a lot of time, this will accomplish absolutely nothing.

 

 

However, as you can see following this link, you can target Blackberry if you download the Blackberry Beta SDK from Appcelerator.  The catch, in order to have access to that download, you need to be a paying subscriber.

 

 

So,  yes, it supports Blackberry development.

 

 

However… you have to pay.

General

30. March 2012

 

 

Unity is certainly getting more and more popular, and with the (limited time) release of Unityunity3d1 iOS and Android for free, the number of people interested in Unity is sure to rise.  Along with the increase in popularity of Unity, there has been an influx of Unity books.

 

 

Therefore I put together this post to compile all of the books and their primary details into a single page.  This makes picking out which book to buy/read as easy as possible.

 

 

Hope you find it useful.  I am trying to keep this post as comprehensive as possible, so if I missed a book or made a mistake, please let me know!

 

 

 

The Unity Book Round-up

General ,

29. March 2012

 

If you frequent developer related forums, this question probably caused your eyes to roll and the bile to rise in your throat.  People with absolutely no development experience get the game creation bug and of all things want to start with MMOs.  Amazingly enough, there is now a very simple answer…  start here.

 

 

 

 

See, the folks over at Mozilla decided to create an HTML5 MMO to showcase the use of Web Sockets a technology for easy two way communication between a webpage and server ( that isn’t without it’s controversy ).  Additionally it makes use of HTML features including Canvas, local storage, web workers and audio.  Node is used on the server side.  Here is the game in action on my desktop:

 

 

 

image

 

It is graphically simple, but when I played there were 73 other players online and it played fairly flawlessly.  I also fired it up on my Android Phone ( Samsung Galaxy Note ) and here are the results:

 

SC20120329-103613

 

Encouraging….

 

export_03

 

 

And… failure.  It never leaves the connecting to server screen.  Perhaps it’s just me, but for now it simply doesn’t work on my phone.  Will try later on my ICS tablet and iPhone, it may just be launch day jitters so I will try again later.

 

 

 

Failures aside, why am I talking about a primitive browser based MMO on this site?  Well, that’s because Mozilla made the complete source code available on GitHub!

 

 

Here for example is the code from character.js for defining the players character.  As you can see, once you get over the Javascriptisms, it’s quite clean and easy to follow:

 

define(['entity', 'transition', 'timer'], function(Entity, Transition, Timer) { var Character = Entity.extend({ init: function(id, kind) { var self = this; this._super(id, kind); // Position and orientation this.nextGridX = -1; this.nextGridY = -1; this.orientation = Types.Orientations.DOWN; // Speeds this.atkSpeed = 50; this.moveSpeed = 120; this.walkSpeed = 100; this.idleSpeed = 450; this.setAttackRate(800); // Pathing this.movement = new Transition(); this.path = null; this.newDestination = null; this.adjacentTiles = {}; // Combat this.target = null; this.unconfirmedTarget = null; this.attackers = {}; // Health this.hitPoints = 0; this.maxHitPoints = 0; // Modes this.isDead = false; this.attackingMode = false; this.followingMode = false; }, clean: function() { this.forEachAttacker(function(attacker) { attacker.disengage(); attacker.idle(); }); }, setMaxHitPoints: function(hp) { this.maxHitPoints = hp; this.hitPoints = hp; }, setDefaultAnimation: function() { this.idle(); }, hasWeapon: function() { return false; }, hasShadow: function() { return true; }, animate: function(animation, speed, count, onEndCount) { var oriented = ['atk', 'walk', 'idle']; o = this.orientation; if(!(this.currentAnimation && this.currentAnimation.name === "death")) { // don't change animation if the character is dying this.flipSpriteX = false; this.flipSpriteY = false; if(_.indexOf(oriented, animation) >= 0) { animation += "_" + (o === Types.Orientations.LEFT ? "right" : Types.getOrientationAsString(o)); this.flipSpriteX = (this.orientation === Types.Orientations.LEFT) ? true : false; } this.setAnimation(animation, speed, count, onEndCount); } }, turnTo: function(orientation) { this.orientation = orientation; this.idle(); }, setOrientation: function(orientation) { if(orientation) { this.orientation = orientation; } }, idle: function(orientation) { this.setOrientation(orientation); this.animate("idle", this.idleSpeed); }, hit: function(orientation) { this.setOrientation(orientation); this.animate("atk", this.atkSpeed, 1); }, walk: function(orientation) { this.setOrientation(orientation); this.animate("walk", this.walkSpeed); }, moveTo_: function(x, y, callback) { this.destination = { gridX: x, gridY: y }; this.adjacentTiles = {}; if(this.isMoving()) { this.continueTo(x, y); } else { var path = this.requestPathfindingTo(x, y); this.followPath(path); } }, requestPathfindingTo: function(x, y) { if(this.request_path_callback) { return this.request_path_callback(x, y); } else { log.error(this.id + " couldn't request pathfinding to "+x+", "+y); return []; } }, onRequestPath: function(callback) { this.request_path_callback = callback; }, onStartPathing: function(callback) { this.start_pathing_callback = callback; }, onStopPathing: function(callback) { this.stop_pathing_callback = callback; }, followPath: function(path) { if(path.length > 1) { // Length of 1 means the player has clicked on himself this.path = path; this.step = 0; if(this.followingMode) { // following a character path.pop(); } if(this.start_pathing_callback) { this.start_pathing_callback(path); } this.nextStep(); } }, continueTo: function(x, y) { this.newDestination = { x: x, y: y }; }, updateMovement: function() { var p = this.path, i = this.step; if(p[i][0] < p[i-1][0]) { this.walk(Types.Orientations.LEFT); } if(p[i][0] > p[i-1][0]) { this.walk(Types.Orientations.RIGHT); } if(p[i][1] < p[i-1][1]) { this.walk(Types.Orientations.UP); } if(p[i][1] > p[i-1][1]) { this.walk(Types.Orientations.DOWN); } }, updatePositionOnGrid: function() { this.setGridPosition(this.path[this.step][0], this.path[this.step][1]); }, nextStep: function() { var stop = false, x, y, path; if(this.isMoving()) { if(this.before_step_callback) { this.before_step_callback(); } this.updatePositionOnGrid(); this.checkAggro(); if(this.interrupted) { // if Character.stop() has been called stop = true; this.interrupted = false; } else { if(this.hasNextStep()) { this.nextGridX = this.path[this.step+1][0]; this.nextGridY = this.path[this.step+1][1]; } if(this.step_callback) { this.step_callback(); } if(this.hasChangedItsPath()) { x = this.newDestination.x; y = this.newDestination.y; path = this.requestPathfindingTo(x, y); this.newDestination = null; if(path.length < 2) { stop = true; } else { this.followPath(path); } } else if(this.hasNextStep()) { this.step += 1; this.updateMovement(); } else { stop = true; } } if(stop) { // Path is complete or has been interrupted this.path = null; this.idle(); if(this.stop_pathing_callback) { this.stop_pathing_callback(this.gridX, this.gridY); } } } }, onBeforeStep: function(callback) { this.before_step_callback = callback; }, onStep: function(callback) { this.step_callback = callback; }, isMoving: function() { return !(this.path === null); }, hasNextStep: function() { return (this.path.length - 1 > this.step); }, hasChangedItsPath: function() { return !(this.newDestination === null); }, isNear: function(character, distance) { var dx, dy, near = false; dx = Math.abs(this.gridX - character.gridX); dy = Math.abs(this.gridY - character.gridY); if(dx <= distance && dy <= distance) { near = true; } return near; }, onAggro: function(callback) { this.aggro_callback = callback; }, onCheckAggro: function(callback) { this.checkaggro_callback = callback; }, checkAggro: function() { if(this.checkaggro_callback) { this.checkaggro_callback(); } }, aggro: function(character) { if(this.aggro_callback) { this.aggro_callback(character); } }, onDeath: function(callback) { this.death_callback = callback; }, /** * Changes the character's orientation so that it is facing its target. */ lookAtTarget: function() { if(this.target) { this.turnTo(this.getOrientationTo(this.target)); } }, /** * */ go: function(x, y) { if(this.isAttacking()) { this.disengage(); } else if(this.followingMode) { this.followingMode = false; this.target = null; } this.moveTo_(x, y); }, /** * Makes the character follow another one. */ follow: function(entity) { if(entity) { this.followingMode = true; this.moveTo_(entity.gridX, entity.gridY); } }, /** * Stops a moving character. */ stop: function() { if(this.isMoving()) { this.interrupted = true; } }, /** * Makes the character attack another character. Same as Character.follow but with an auto-attacking behavior. * @see Character.follow */ engage: function(character) { this.attackingMode = true; this.setTarget(character); this.follow(character); }, disengage: function() { this.attackingMode = false; this.followingMode = false; this.removeTarget(); }, /** * Returns true if the character is currently attacking. */ isAttacking: function() { return this.attackingMode; }, /** * Gets the right orientation to face a target character from the current position. * Note: * In order to work properly, this method should be used in the following * situation : * S * S T S * S * (where S is self, T is target character) * * @param {Character} character The character to face. * @returns {String} The orientation. */ getOrientationTo: function(character) { if(this.gridX < character.gridX) { return Types.Orientations.RIGHT; } else if(this.gridX > character.gridX) { return Types.Orientations.LEFT; } else if(this.gridY > character.gridY) { return Types.Orientations.UP; } else { return Types.Orientations.DOWN; } }, /** * Returns true if this character is currently attacked by a given character. * @param {Character} character The attacking character. * @returns {Boolean} Whether this is an attacker of this character. */ isAttackedBy: function(character) { return (character.id in this.attackers); }, /** * Registers a character as a current attacker of this one. * @param {Character} character The attacking character. */ addAttacker: function(character) { if(!this.isAttackedBy(character)) { this.attackers[character.id] = character; } else { log.error(this.id + " is already attacked by " + character.id); } }, /** * Unregisters a character as a current attacker of this one. * @param {Character} character The attacking character. */ removeAttacker: function(character) { if(this.isAttackedBy(character)) { delete this.attackers[character.id]; } else { log.error(this.id + " is not attacked by " + character.id); } }, /** * Loops through all the characters currently attacking this one. * @param {Function} callback Function which must accept one character argument. */ forEachAttacker: function(callback) { _.each(this.attackers, function(attacker) { callback(attacker); }); }, /** * Sets this character's attack target. It can only have one target at any time. * @param {Character} character The target character. */ setTarget: function(character) { if(this.target !== character) { // If it's not already set as the target if(this.hasTarget()) { this.removeTarget(); // Cleanly remove the previous one } this.unconfirmedTarget = null; this.target = character; } else { log.debug(character.id + " is already the target of " + this.id); } }, /** * Removes the current attack target. */ removeTarget: function() { var self = this; if(this.target) { if(this.target instanceof Character) { this.target.removeAttacker(this); } this.target = null; } }, /** * Returns true if this character has a current attack target. * @returns {Boolean} Whether this character has a target. */ hasTarget: function() { return !(this.target === null); }, /** * Marks this character as waiting to attack a target. * By sending an "attack" message, the server will later confirm (or not) * that this character is allowed to acquire this target. * * @param {Character} character The target character */ waitToAttack: function(character) { this.unconfirmedTarget = character; }, /** * Returns true if this character is currently waiting to attack the target character. * @param {Character} character The target character. * @returns {Boolean} Whether this character is waiting to attack. */ isWaitingToAttack: function(character) { return (this.unconfirmedTarget === character); }, /** * */ canAttack: function(time) { if(this.canReachTarget() && this.attackCooldown.isOver(time)) { return true; } return false; }, canReachTarget: function() { if(this.hasTarget() && this.isAdjacentNonDiagonal(this.target)) { return true; } return false; }, /** * */ die: function() { this.removeTarget(); this.isDead = true; if(this.death_callback) { this.death_callback(); } }, onHasMoved: function(callback) { this.hasmoved_callback = callback; }, hasMoved: function() { this.setDirty(); if(this.hasmoved_callback) { this.hasmoved_callback(this); } }, hurt: function() { var self = this; this.stopHurting(); this.sprite = this.hurtSprite; this.hurting = setTimeout(this.stopHurting.bind(this), 75); }, stopHurting: function() { this.sprite = this.normalSprite; clearTimeout(this.hurting); }, setAttackRate: function(rate) { this.attackCooldown = new Timer(rate); } }); return Character; });

 

 

 

So now, the next time somebody asks you “I want to create an MMO, now what?”, you have an easy answer.  Send them over to Browser Quest.

 

 

Oh, and for the record… starting your game development career off with an MMO is still a right stupid idea! Smile

 

 

EDIT: As per a comment on reddit, this code doesn’t appear to be released under a specific license yet.  Keep that in mind before using any of this code in your own project.  Of course, this should be rectified shortly.

News ,

Month List

Popular Comments