Friday, May 10, 2013

Cyclic background / On Darlingjs


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
Red cabriolet
To implement such I've used cyclic background add parallax effect.

Step by step

Cyclic background

Example : Infinity cyclic background
Red cabriolet
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
cyclic background
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):
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]);
}
}
}
}
view raw cyclic.js hosted with ❤ by GitHub

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.
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();
view raw cyclic-world.js hosted with ❤ by GitHub

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.
pattern withxheight
In next tutorial I'll describe implementation of parallax effect in darlingjs.
Stay tuned! See you :)

1 comment :

  1. Harrah's Casino & Racetrack - Mapyro
    Find parking costs, 대구광역 출장안마 opening hours 경기도 출장안마 and a parking map 대전광역 출장마사지 of Harrah's 성남 출장안마 Casino & 포항 출장마사지 Racetrack 6355 777 Harrah's Blvd S in San Diego County,

    ReplyDelete