Permalink
Browse files

Overhaul: Now displaying in perspective using an objects z distance. …

…Acceleration is now continuous. Introduced RaceControl static object to hold global values.
  • Loading branch information...
1 parent 12c669e commit 3364151661ef7de757542c3914d3835f9fccf0e2 John Conomikes committed Aug 31, 2011
@@ -0,0 +1,38 @@
+var cocos = require('cocos2d');
+var events = require('events');
+var geom = require('geometry');
+
+var PNode = require('PerspectiveNode').PerspectiveNode;
+
+// Represents a single question to be answered by the player
+// TODO: Build with an options object to allow easier initialization when customizing away from default values
+var Intermission = PNode.extend({
+ fired : false, // True if the intermission has already fired
+ selector: null, // Selector to change to during the intermission
+ init: function(selector, z) {
+ Intermission.superclass.init.call(this, {xCoordinate:0, zCoordinate: z});
+
+ // Initialize all variables
+ this.set('selector', selector);
+
+ // Schedule the per frame update
+ this.scheduleUpdate();
+ },
+
+ // Manages question timing and movement
+ update: function(dt) {
+ var te = this.get('timeElapsed') + dt;
+ this.set('timeElapsed', te);
+
+ if(PNode.cameraZ >= this.get('zCoordinate') && !this.get('fired')) {
+ this.set('fired', true);
+ events.trigger(this, 'changeSelector', this.get('selector'));
+ }
+
+ Intermission.superclass.update.call(this, dt);
+ },
+});
+
+// TODO: Write static helper for building an options object to initialize a question
+
+exports.Intermission = Intermission
View
@@ -1,22 +1,22 @@
// Import the cocos2d module
var cocos = require('cocos2d');
-var LabelBG = cocos.nodes.Node.extend({
- label: null, //The label that the class wraps
- bgColor: null, //The color of the background that will be behind the label
- init: function(opts, bgColor) {
+var PNode = require('PerspectiveNode').PerspectiveNode;
+
+var LabelBG = PNode.extend({
+ label : null, //The label that the class wraps
+ bgColor: '#FFFFFF', //The color of the background that will be behind the label
+ init: function(opts) {
// You must always call the super class version of init
- LabelBG.superclass.init.call(this);
+ LabelBG.superclass.init.call(this, opts);
this.set('label', cocos.nodes.Label.create(opts));
this.addChild({child: this.get('label')});
if(opts.hasOwnProperty('bgColor')) {
this.set('bgColor', opts['bgColor']);
}
- else {
- this.set('bgColor', '#FFFFFF');
- }
+
this.set('contentSize', this.get('label').get('contentSize'));
},
// Draws the background for the label
@@ -25,19 +25,18 @@ var LabelBG = cocos.nodes.Node.extend({
context.fillStyle = this.get('bgColor');
context.fillRect(size.width * -0.6, size.height * -0.75, size.width * 1.2, size.height * 1.5);
- }
+ },
});
// Static helper function to build the creation options object
-LabelBG.helper = function(string, fontColor, bgColor, fontSize, fontName) {
- var opts = Object();
- opts['string'] = string;
- opts['fontColor'] = fontColor;
- opts['bgColor'] = bgColor;
- opts['fontSize'] = fontSize;
- opts['fontName'] = fontName;
-
- return opts;
+LabelBG.helper = function(String, FontColor, BgColor, FontSize, FontName) {
+ return {
+ string : String,
+ fontColor : FontColor,
+ bgColor : BgColor,
+ fontSize : FontSize,
+ fontName : FontName
+ };
}
exports.LabelBG = LabelBG
@@ -0,0 +1,143 @@
+var cocos = require('cocos2d');
+var geom = require('geometry');
+var events = require('events');
+var util = require('util');
+
+var PerspectiveNode = cocos.nodes.Node.extend({
+ visibility : 1, // Content scale multiplier, used BEFORE clamping
+ minScale : null, // Minimum scale for this node due to perspective distance (null disables minimum)
+ maxScale : null, // Maximum scale for this node due to perspective distance (null disables maximum)
+ xCoordinate : 0, // The node's X position in the world
+ zCoordinate : 0, // The node's Z position in the world
+ silent : false, // If set to true, will not fire events
+ added : false, // True once added to the scene
+ lockX : false, // Set to true to lock X value
+ lockY : false, // Set to true to lock Y value
+ alignV : 0, // Vertical alignment of the node (0 left - 0.5 center - 1 right)
+ alignH : 0, // Horizontal alignment of the node (0 top - 0.5 center - 1 bottom)
+ dropoffDist : -10, // Distance behind the camera that node requests removal from scene
+ init: function(opts) {
+ PerspectiveNode.superclass.init.call(this);
+
+ this.set('position', new geom.Point(0, 0));
+
+ //Set properties from the option object
+ util.each('visibility minScale maxScale xCoordinate zCoordinate silent lockX lockY alignV alignH dropoffDist'.w(), util.callback(this, function (name) {
+ if (opts[name]) {
+ this.set(name, opts[name]);
+ }
+ }));
+ },
+
+ // Explicitly start update, even if not in scene
+ kickstart: function () {
+ cocos.Scheduler.get('sharedScheduler').scheduleUpdate({target: this, priority: 0, paused: false});
+ },
+
+ onEnter: function() {
+ this.set('added', true);
+ PerspectiveNode.superclass.onEnter.call(this);
+ },
+
+ // Called when removed from the scene, explicitly stops the update function instead of pausing
+ onExit: function () {
+ cocos.Scheduler.get('sharedScheduler').unscheduleUpdateForTarget(this);
+ events.clearInstanceListeners(this);
+ PerspectiveNode.superclass.onExit.call(this);
+ },
+
+ scale: function (s) {
+ s *= this.get('visibility');
+
+ // Apply clamps to scale as needed
+ if(this.get('minScale') != null) {
+ s = Math.max(this.get('minScale'), s);
+ }
+ if(this.get('maxScale') != null) {
+ s = Math.min(this.get('maxScale'), s);
+ }
+
+ // Set scale
+ this.set('scaleX', s);
+ this.set('scaleY', s);
+
+ return s;
+ },
+
+ update: function (dt) {
+ var distance = this.get('zCoordinate') - PerspectiveNode.cameraZ;
+
+ // Only worry about drawing once node is on our side of the horizon
+ if(distance > PerspectiveNode.horizonDistance) {
+ }
+ else if(distance <= PerspectiveNode.horizonDistance && distance > this.get('dropoffDist')) {
+ // Make sure that the node gets added to the scene graph once it should be visible
+ if(!this.get('added') && !this.get('silent')) {
+ events.trigger(this, 'addMe', this);
+ }
+
+ // Perspective transform
+ var scale = PerspectiveNode.horizonDistance * PerspectiveNode.horizonScale / distance;
+ var screenX, screenY;
+ var lockX = this.get('lockX'), lockY = this.get('lockY');
+
+ // Check to see if X axis is locked
+ if(!lockX) {
+ screenX = PerspectiveNode.roadOffset + PerspectiveNode.roadWidthPix / 2 * (1 + scale * 2.0 * (this.get('xCoordinate') / PerspectiveNode.roadWidth));
+ }
+ else {
+ screenX = this.get('position').x;
+ }
+
+ // Check to see if Y axis is locked
+ if(!lockY) {
+ var yScale = (1.0 / (1.0 - PerspectiveNode.horizonScale)) * (scale - PerspectiveNode.horizonScale);
+ screenY = PerspectiveNode.horizonStart + PerspectiveNode.horizonHeight * (yScale);
+ }
+ else {
+ screenY = this.get('position').y;
+ }
+
+ // Apply scaling
+ scale = this.scale(scale);
+
+ // Account for horizontal alignment
+ if(!lockX) {
+ screenX += this.get('alignH') * this.get('contentSize').width * scale;
+ }
+ else {
+ screenX = this.get('alignH') * this.get('contentSize').width * scale;
+ }
+
+ //Account for vertical alignment
+ if(!lockY) {
+ screenY -= this.get('alignV') * this.get('contentSize').height * scale;
+ }
+ else {
+ screenY = -1 * this.get('alignV') * this.get('contentSize').width * scale;
+ }
+
+ // Set position
+ this.set('position', new geom.Point(screenX, screenY));
+ }
+ // Once the node drops too far back, notify for removal
+ else if (!this.get('silent')) {
+ events.trigger(this, 'removeMe', this);
+ }
+ }
+});
+
+// Static constants
+PerspectiveNode.horizonStart = 50; // From top of screen to start of horizon in pixels
+PerspectiveNode.horizonHeight = 550; // From horizonStart to the bottom of the screen in pixels
+PerspectiveNode.horizonDistance = 100; // In meters from the camera
+PerspectiveNode.horizonScale = 0.05; // Scale of objects on the horizon
+PerspectiveNode.roadWidth = 9.0; // Width of road at bottom of the screen in meters
+PerspectiveNode.roadWidthPix = 600; // Width of road at bottom of the screen in pixels
+PerspectiveNode.roadOffset = 100; // Number of pixels from the left hand side that the road starts at
+
+// Static variables
+PerspectiveNode.cameraZ = 0; // Current Z coordinate of the camera
+PerspectiveNode.carDist = 6; // Distance of the car from the camera in meters
+
+exports.PerspectiveNode = PerspectiveNode
View
@@ -2,24 +2,24 @@ var cocos = require('cocos2d');
var geom = require('geometry');
var LabelBG = require('LabelBG').LabelBG;
+var PNode = require('PerspectiveNode').PerspectiveNode;
// Represents the player in the game
-var Player = cocos.nodes.Node.extend({
- selector: null, // Label that represents the value the player has control over
- wipeoutDuration: null, // Duration remaining on a wipeout
- selectorX: null, // The X coordinate that the label should be at, ignoring rotational transforms
- selectorY: null, // The Y coordinate that the label should be at, ignoring rotational transforms
+var Player = PNode.extend({
+ selector : null, // Label that represents the value the player has control over
+ wipeoutDuration : 0, // Duration remaining on a wipeout
+ wipeoutRotSpeed : 0, // Rotational velocity in degrees per second for the current wipeout
+ selectorX : null, // The X coordinate that the label should be at, ignoring rotational transforms
+ selectorY : null, // The Y coordinate that the label should be at, ignoring rotational transforms
init: function() {
- Player.superclass.init.call(this);
+ Player.superclass.init.call(this, {xCoordinate:0, zCoordinate: PNode.carDist});
// Load the car sprite for the player
var sprite = cocos.nodes.Sprite.create({file: '/resources/car1.png',});
sprite.set('scaleX', 0.5);
sprite.set('scaleY', 0.5);
this.addChild({child: sprite});
- this.set('wipeoutDuration', 0);
-
this.scheduleUpdate();
},
@@ -31,31 +31,40 @@ var Player = cocos.nodes.Node.extend({
this.removeChild(prev);
}
- // Create the new selector
- var opts = Object()
- opts["string"] = newVal;
- opts["fontColor"] = '#000000';
- var selector = LabelBG.create(opts, '#FFFFFF');
- selector.set('position', new geom.Point(selector.get('contentSize').width / 2, 80));
- this.set('selectorX', selector.get('contentSize').width / 2);
- this.set('selectorY', 80);
- this.addChild({child: selector});
- this.set('selector', selector);
+ // Create the new selector if one is provided
+ if(newVal != null) {
+ var opts = Object()
+ opts["string"] = newVal;
+ opts["fontColor"] = '#000000';
+ var selector = LabelBG.create(opts, '#FFFFFF');
+ selector.set('position', new geom.Point(selector.get('contentSize').width / 2, 80));
+ this.set('selectorX', selector.get('contentSize').width / 2);
+ this.set('selectorY', 80);
+ this.addChild({child: selector});
+ this.set('selector', selector);
+ }
+ else {
+ this.set('selector', null);
+ }
},
- // Sets the wipeout status of the car, causing it to spin
- // Use multiples of 0.5 seconds, otherwise the car will stop spinning at an off angle and then snap to driving forward
- wipeout: function(duration) {
+ // Sets the wipeout status of the car, causing it to spin over time
+ wipeout: function(duration, spins) {
this.set('wipeoutDuration', duration);
+ this.set('wipeoutRotSpeed', spins * 360.0 / duration);
},
// Keep the selector from rotating with the car
update: function(dt) {
+ this.set('zCoordinate', PNode.cameraZ + PNode.carDist);
+
+ Player.superclass.update.call(this, dt);
+
var pos = this.get('position');
//Spin the car as a result of getting a wrong answer
if(this.get('wipeoutDuration') > 0) {
- this.set('rotation', this.get('rotation') + 720 * dt)
+ this.set('rotation', this.get('rotation') + this.get('wipeoutRotSpeed') * dt)
this.set('wipeoutDuration', this.get('wipeoutDuration') - dt);
}
// Otherwise just rotate the player as they move to keep the visual angles realistic
@@ -68,18 +77,20 @@ var Player = cocos.nodes.Node.extend({
}
}
- // Do not rotate the label, keep its facing constant
- var rot = this.get('rotation');
- this.get('selector').set('rotation', rot * -1);
-
- //Cocos works in degrees, Math works in radians, so convert
- rot = rot * Math.PI / 180.0
-
- // Keep the label in a fixed position beneath the car, regardless of car rotation
- var x = this.get('selectorX') * Math.cos(rot) + this.get('selectorY') * Math.sin(rot)
- var y = -1 * this.get('selectorX') * Math.sin(rot) + this.get('selectorY') * Math.cos(rot)
-
- this.get('selector').set('position', new geom.Point(x, y));
+ if(this.get('selector') != null) {
+ // Do not rotate the label, keep its facing constant
+ var rot = this.get('rotation');
+ this.get('selector').set('rotation', rot * -1);
+
+ //Cocos works in degrees, Math works in radians, so convert
+ rot = rot * Math.PI / 180.0
+
+ // Keep the label in a fixed position beneath the car, regardless of car rotation
+ var x = this.get('selectorX') * Math.cos(rot) + this.get('selectorY') * Math.sin(rot)
+ var y = -1 * this.get('selectorX') * Math.sin(rot) + this.get('selectorY') * Math.cos(rot)
+
+ this.get('selector').set('position', new geom.Point(x, y));
+ }
},
});
Oops, something went wrong.

0 comments on commit 3364151

Please sign in to comment.