
Goal
One of the issue i got during developing of the 2D game Red Cabriolet was creation of landscape with taste of 3D environment. In other words:- the landscape mast be unlimited;
- placed behind the main stage;
- and gives feeling of 3D space in 2D;
Here is result
Game : Red Cabriolet
To implement such I've used cyclic background add parallax effect.
Step by step
Cyclic background
Example : Infinity cyclic background
Start with cyclic background. The main idea of it that we have finite pool of entities that placed in some pattern and when an entity goes out of the viewport we should place it to opposite side of view port.
Something like that

To implement such behaviour on darlingjs we need: one component to mark entity that should be cyclic and one system to implement cyclic behaviour.
Here is implementation (You really don't need to implement it to use cyclic, it has placed here to show principles of implementation of such behaviour in darlingjs):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var m = darlingjs.m('ngCyclic'); | |
/** | |
* Marker for entity with cyclic pattern behaviour | |
*/ | |
m.$c('ngCyclic', { | |
/** | |
* PatternWidth, if value <= 0.0 that pattern doesn't cyclic in that direction | |
*/ | |
patternWidth: 0.0, | |
/** | |
* PatternHeight, if value <= 0.0 that pattern doesn't cyclic in that direction | |
*/ | |
patternHeight: 0.0 | |
}); | |
/** | |
* Mark entity by component if it outside the ViewPort | |
*/ | |
m.$c('ngMarkIfOutsideOfTheViewPortVertical2D', { | |
/** | |
* handler callback get $entity and top/bottom edge | |
*/ | |
handler: null, | |
/** | |
* apply component-markers for any $entity that goes outside | |
* can be {string} name of component, or object with key, value | |
* where key is component name, value is config of component | |
*/ | |
marker: null, | |
/** | |
* auto-remove current component | |
*/ | |
autoRemove: true | |
}); | |
/** | |
* Mark entity by component if it outside the ViewPort | |
*/ | |
m.$c('ngMarkIfOutsideOfTheViewPortHorizontal2D', { | |
/** | |
* handler callback get $entity and top/bottom edge | |
*/ | |
handler: null, | |
/** | |
* apply component-markers for any $entity that goes outside | |
* can be {string} name of component, or object with key, value | |
* where key is component name, value is config of component | |
*/ | |
marker: null, | |
/** | |
* auto-remove current component | |
*/ | |
autoRemove: true | |
}); | |
/** | |
* Mark entity by component if it outside the ViewPort | |
*/ | |
m.$c('ngMarkIfInsideOfTheViewPortVertical2D', { | |
/** | |
* handler callback get $entity and top/bottom edge | |
*/ | |
handler: null, | |
/** | |
* apply component-markers for any $entity that goes inside | |
* can be {string} name of component, or object with key, value | |
* where key is component name, value is config of component | |
*/ | |
marker: null, | |
/** | |
* auto-remove current component | |
*/ | |
autoRemove: true | |
}); | |
/** | |
* Mark entity by component if it outside the ViewPort | |
*/ | |
m.$c('ngMarkIfInsideOfTheViewPortHorizontal2D', { | |
/** | |
* handler callback get $entity and top/bottom edge | |
*/ | |
handler: null, | |
/** | |
* apply component-markers for any $entity that goes inside | |
* can be {string} name of component, or object with key, value | |
* where key is component name, value is config of component | |
*/ | |
marker: null, | |
/** | |
* auto-remove current component | |
*/ | |
autoRemove: true | |
}); | |
/** | |
* System that implement 2D cyclic layer | |
*/ | |
m.$s('ng2DCyclicLayer', { | |
$require: ['ngCyclic', 'ng2D'], | |
$addEntity: ['$entity', function($entity) { | |
if ($entity.ngCyclic.patternWidth) { | |
insideConfigHorizontal2D.minWidth = $entity.ngCyclic.patternWidth; | |
$entity.$add('ngMarkIfInsideOfTheViewPortHorizontal2D', | |
insideConfigHorizontal2D); | |
outsideConfigHorizontal2D.minWidth = $entity.ngCyclic.patternWidth; | |
$entity.$add('ngMarkIfOutsideOfTheViewPortHorizontal2D', | |
outsideConfigHorizontal2D); | |
} | |
if ($entity.ngCyclic.patternHeight) { | |
insideConfigVertical2D.minHeight = $entity.ngCyclic.patternHeight; | |
$entity.$add('ngMarkIfInsideOfTheViewPortVertical2D', | |
insideConfigVertical2D); | |
outsideConfigVertical2D.minHeight = $entity.ngCyclic.patternHeight; | |
$entity.$add('ngMarkIfOutsideOfTheViewPortVertical2D', | |
outsideConfigVertical2D); | |
} | |
}] | |
}); | |
/** | |
* Define handler for entity that goes outside | |
* | |
* @private | |
* @ignore | |
* @type {{handler: Function}} | |
*/ | |
var outsideConfigHorizontal2D = { | |
handler: function($entity, lowerEdge, higherEdge) { | |
if (lowerEdge) { | |
//if goes left place to right side | |
$entity.ng2D.x += $entity.ngCyclic.patternWidth; | |
} else if (higherEdge) { | |
//if goes right place to left side | |
$entity.ng2D.x -= $entity.ngCyclic.patternWidth; | |
} | |
//handle of returning to the viewport | |
if (!$entity.ngMarkIfInsideOfTheViewPortHorizontal2D) { | |
$entity.$add('ngMarkIfInsideOfTheViewPortHorizontal2D', | |
insideConfigHorizontal2D); | |
} | |
//stop handling of goes outside | |
$entity.$remove('ngMarkIfOutsideOfTheViewPortHorizontal2D'); | |
} | |
}; | |
/** | |
* Define handler for entity that goes outside | |
* | |
* @private | |
* @ignore | |
* @type {{handler: Function}} | |
*/ | |
var outsideConfigVertical2D = { | |
handler: function($entity, lowerEdge, higherEdge) { | |
if (lowerEdge) { | |
$entity.ng2D.y += $entity.ngCyclic.patternHeight; | |
} else if (higherEdge) { | |
$entity.ng2D.y -= $entity.ngCyclic.patternHeight; | |
} | |
//handle of returning to the viewport | |
if (!$entity.ngMarkIfInsideOfTheViewPortVertical2D) { | |
$entity.$add('ngMarkIfInsideOfTheViewPortVertical2D', | |
insideConfigVertical2D); | |
} | |
//stop handling of goes outside | |
$entity.$remove('ngMarkIfOutsideOfTheViewPortVertical2D'); | |
} | |
}; | |
/** | |
* Start handle again if goes inside of viewPort | |
* | |
* @private | |
* @ignore | |
* @type {{marker: {ngMarkIfOutsideOfTheViewPortHorizontal2D: {handler: Function}}}} | |
*/ | |
var insideConfigHorizontal2D = { | |
marker: { | |
ngMarkIfOutsideOfTheViewPortHorizontal2D: outsideConfigHorizontal2D | |
} | |
}; | |
/** | |
* Start handle again if goes inside of viewPort | |
* | |
* @private | |
* @ignore | |
* @type {{marker: {ngMarkIfOutsideOfTheViewPortVertical2D: {handler: Function}}}} | |
*/ | |
var insideConfigVertical2D = { | |
marker: { | |
ngMarkIfOutsideOfTheViewPortVertical2D: outsideConfigVertical2D | |
} | |
}; | |
/** | |
* System that waits for entity goes outside of viewport in vertical dimension | |
* and handle that situation | |
*/ | |
m.$s('ngMarkIfOutsideOfTheViewPortVertical2D', { | |
$require: ['ng2D', 'ngMarkIfOutsideOfTheViewPortVertical2D'], | |
$update: ['$entity', 'ng2DViewPort', function($entity, ng2DViewPort) { | |
var component = $entity.ngMarkIfOutsideOfTheViewPortVertical2D; | |
var viewPortHeight = component.viewPortHeight || | |
0.5 * Math.max(ng2DViewPort.height, component.minHeight); | |
component.viewPortHeight = viewPortHeight; | |
outsideOf($entity, | |
'ngMarkIfOutsideOfTheViewPortVertical2D', | |
component, | |
$entity.ng2D.y, | |
ng2DViewPort.lookAt.y, | |
viewPortHeight | |
); | |
}] | |
}); | |
/** | |
* System that waits for entity goes outside of viewport in horizontal dimension | |
* and handle that situation | |
*/ | |
m.$s('ngMarkIfOutsideOfTheViewPortHorizontal2D', { | |
$require: ['ng2D', 'ngMarkIfOutsideOfTheViewPortHorizontal2D'], | |
$update: ['$entity', 'ng2DViewPort', function($entity, ng2DViewPort) { | |
var component = $entity.ngMarkIfOutsideOfTheViewPortHorizontal2D; | |
var viewPortWidth = component.viewPortWidth || | |
0.5 * Math.max(ng2DViewPort.width, component.minWidth); | |
component.viewPortWidth = viewPortWidth; | |
outsideOf($entity, | |
'ngMarkIfOutsideOfTheViewPortVertical2D', | |
component, | |
$entity.ng2D.x, | |
ng2DViewPort.lookAt.x, | |
viewPortWidth | |
); | |
}] | |
}); | |
/** | |
* System that waits for entity goes inside of viewport in vertical dimension | |
* and handle that situation | |
*/ | |
m.$s('ngMarkIfInsideOfTheViewPortVertical2D', { | |
$require: ['ng2D', 'ngMarkIfInsideOfTheViewPortVertical2D'], | |
$update: ['$entity', 'ng2DViewPort', function($entity, ng2DViewPort) { | |
var component = $entity.ngMarkIfInsideOfTheViewPortVertical2D; | |
var viewPortHeight = component.viewPortHeight || | |
0.5 * Math.max(ng2DViewPort.height, component.minHeight); | |
component.viewPortHeight = viewPortHeight; | |
insideOf($entity, | |
'ngMarkIfOutsideOfTheViewPortVertical', | |
component, | |
$entity.ng2D.y, | |
ng2DViewPort.lookAt.y, | |
viewPortHeight | |
); | |
}] | |
}); | |
/** | |
* System that waits for entity goes inside of viewport in horizontal dimension | |
* and handle that situation | |
*/ | |
m.$s('ngMarkIfInsideOfTheViewPortHorizontal2D', { | |
$require: ['ng2D', 'ngMarkIfInsideOfTheViewPortHorizontal2D'], | |
$update: ['$entity', 'ng2DViewPort', function($entity, ng2DViewPort) { | |
var component = $entity.ngMarkIfInsideOfTheViewPortHorizontal2D; | |
var viewPortWidth = component.viewPortWidth || | |
0.5 * Math.max(ng2DViewPort.width, component.minWidth); | |
component.viewPortWidth = viewPortWidth; | |
insideOf($entity, | |
'ngMarkIfInsideOfTheViewPortHorizontal2D', | |
component, | |
$entity.ng2D.x, | |
ng2DViewPort.lookAt.x, | |
viewPortWidth | |
); | |
}] | |
}); | |
/** | |
* check is $entity is outside of viewPort | |
* | |
* @private | |
* @ignore | |
* | |
* @param $entity | |
* @param componentName | |
* @param component | |
* @param componentPosition | |
* @param viewPortPosition | |
* @param viewPortSize | |
*/ | |
function outsideOf($entity, componentName, component, | |
componentPosition, viewPortPosition, viewPortSize) { | |
var crossBottom = viewPortPosition + viewPortSize < componentPosition, | |
crossTop = componentPosition < viewPortPosition - viewPortSize; | |
if (crossTop || crossBottom) { | |
var handler = component.handler; | |
applyMarker($entity, component.marker); | |
if (component.autoRemove) { | |
$entity.$remove(componentName); | |
} | |
callIfHasHandler(handler, $entity, crossTop, crossBottom); | |
} | |
} | |
/** | |
* check is $entity is inside of viewPort | |
* | |
* @ignore | |
* | |
* @private | |
* | |
* @param $entity | |
* @param componentName | |
* @param component | |
* @param componentPosition | |
* @param viewPortPosition | |
* @param viewPortSize | |
*/ | |
function insideOf($entity, componentName, component, | |
componentPosition, viewPortPosition, viewPortSize) { | |
var crossBottom = viewPortPosition + viewPortSize >= componentPosition, | |
crossTop = componentPosition >= viewPortPosition - viewPortSize; | |
if (crossTop && crossBottom) { | |
var handler = component.handler; | |
applyMarker($entity, component.marker); | |
if (component.autoRemove) { | |
$entity.$remove(componentName); | |
} | |
callIfHasHandler(handler, $entity, crossTop, crossBottom); | |
} | |
} | |
/** | |
* Execute handle if has it defined | |
* | |
* @private | |
* @ignore | |
* @param handler | |
* @param $entity | |
* @param lowerEdge | |
* @param higherEdge | |
*/ | |
function callIfHasHandler(handler, $entity, lowerEdge, higherEdge) { | |
if (handler) { | |
handler($entity, lowerEdge, higherEdge); | |
} | |
} | |
/** | |
* apply markers from component | |
* | |
* @ignore | |
* | |
* @private | |
* | |
* @param $entity | |
* @param marker | |
*/ | |
function applyMarker($entity, marker) { | |
if (darlingutil.isString(marker)) { | |
if (!$entity[marker]) { | |
$entity.$add(marker); | |
} | |
} else if (darlingutil.isObject(marker)) { | |
for (var key in marker) { | |
if (!$entity[key]) { | |
$entity.$add(key, marker[key]); | |
} | |
} | |
} | |
} |
fork on gist
Usage
By the way cyclic module has already placed in native darlingjs modules and you don't need to implement it. So all you need it's easily load module, set dependency and add it to the world.To apply cyclic behaviour to entity just add component to needed entity and set pattern-width.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var w = darlingjs.w('theCyclicWorld', ['ngCyclic']); | |
//add systems for implement cyclic behaviour | |
world.$add('ng2DCyclicLayer'); | |
world.$add('ngMarkIfOutsideOfTheViewPortVertical2D'); | |
world.$add('ngMarkIfOutsideOfTheViewPortHorizontal2D'); | |
world.$add('ngMarkIfInsideOfTheViewPortVertical2D'); | |
world.$add('ngMarkIfInsideOfTheViewPortHorizontal2D'); | |
//add entity | |
world.$e('theEntity', { | |
ng2D: { | |
x: 10.0, | |
y: 10.0 | |
}, | |
ngCyclic: { | |
patternWidth: 1000, | |
patternHeight: 1000 | |
} | |
}); | |
//start simulation | |
world.$start(); |
fork on gist
Pattern Width
to use cyclic background you also need to define pattern width or/and height. Hint: to right cycling it's better to use pattern width/height great then viewport width/height.
In next tutorial I'll describe implementation of parallax effect in darlingjs.
- subscribe to darlingjs G+ Group;
- subscribe my twitter;
- or this blog;
Harrah's Casino & Racetrack - Mapyro
ReplyDeleteFind parking costs, 대구광역 출장안마 opening hours 경기도 출장안마 and a parking map 대전광역 출장마사지 of Harrah's 성남 출장안마 Casino & 포항 출장마사지 Racetrack 6355 777 Harrah's Blvd S in San Diego County,