Smooth parllax movement
by Dubai Coder · in Torque Game Builder · 01/10/2007 (3:00 am) · 16 replies
I'm trying to develop a side scroller and I'm unsure how to go about setting up parallax scrolling. In a custum made engine I used previously I populated various layers with the reqired tiles and then moved each layer according to the desired speed.
However, I cannot seem to figure out if moving individuals layers at different speeds is possible in TGB. How would I go about getting parallax movement in my levels?
Note: I have seen the the shooter demo, and though it has parallax movement it is not what I'm looking for. I need to build each layer uing tiles rather than scroller objects.
However, I cannot seem to figure out if moving individuals layers at different speeds is possible in TGB. How would I go about getting parallax movement in my levels?
Note: I have seen the the shooter demo, and though it has parallax movement it is not what I'm looking for. I need to build each layer uing tiles rather than scroller objects.
#2
@Hesham: My solution was to use a second scenegraph for the background. Then I added it to the GUI behind the first one. So the background wasn't affected by the "action" camera. :)
01/11/2007 (10:19 am)
I have done the exact same by using two scenegraphs but I am also intested in your solution, Thomas. :)@Hesham: My solution was to use a second scenegraph for the background. Then I added it to the GUI behind the first one. So the background wasn't affected by the "action" camera. :)
#3
01/11/2007 (11:55 am)
Me three Thomas...
#4
Yes I would like to have a look too.
@Oliver:
I will try the method you mentioned, though I'm not sure if it will work for multiple layers that have various speeds.
01/11/2007 (1:13 pm)
@Thomas:Yes I would like to have a look too.
@Oliver:
I will try the method you mentioned, though I'm not sure if it will work for multiple layers that have various speeds.
#5
http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=11779
01/11/2007 (10:27 pm)
That is exactly what I did. Check out my .plan athttp://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=11779
#6
Which one is better for performance ? Second Scenegraph or Moving Sprite ?
01/13/2007 (4:19 am)
Well, a good question would be : Which one is better for performance ? Second Scenegraph or Moving Sprite ?
#7
01/13/2007 (5:37 am)
@Benjamin: Optimizing before running into troubles won't do any good... I haven't run into any performance problems(on my graphically lame ThinkPad R50e) and I will start checking for bottlenecks only after I did. :)
#8
You can set up your scene windows (as many layers as you like) as Oliver explains. In the background level files you want to make all the objects the sizes that they would appear if they were in the foreground, rather than the background. In other words, if a mountain is supposed to be huge, make it huge. It helps to use something that appears in the foreground level as a size reference. When your level loads, you want to load all the background levels into their respective scene windows and mount all the scene windows to whatever object the camera should be tracking. This works because it's possible to mount a scene window to an object that isn't in the scenegraph that it currently has loaded. In other words, all the scene windows will be mounted to an object in the foreground window's level.
After doing just this, you'll probably notice that the background stuff looks huge when you run your game (as though it were all in the foreground). This next part will require some tweaking, but basically what you need to do is set the zoom on each scene window based on how far away they should appear.
A basic rule of thumb for setting zoom is: 1 / "relative distance"
So if the mountain should be 10 times as far as the player, set the zoom to 0.1 (i.e. 1/10) on the scene window with the mountain in it.
Once this is set up, parallax will automatically work. Since the all the scene windows are mounted to the same object, the views of the different levels are moving at the same speed, but the cameras that are zoomed out further will apear to move slower.
If you have multiple levels, it's important to note that closer layers should have a higher zoom (up to 1 which is the "action plane" - the window where the action happens), and progressively further layers should have a lower zoom.
Using this method, you can also give the appearance of foreground objects (in front of the action plane) moving past the camera quickly by placing a scene window in front of the action window and giving it a zoom greater than 1.
01/16/2007 (3:02 am)
Oh, right.. the multiple scenegraph thing. Yea, that's way easier to set up. The main problem with the object group method is that it becomes a pain to modify or add things to the groups later on. Once you form a group, you're pretty much stuck with it. I suggest going with the "multiple scenegraph/scene window" method.You can set up your scene windows (as many layers as you like) as Oliver explains. In the background level files you want to make all the objects the sizes that they would appear if they were in the foreground, rather than the background. In other words, if a mountain is supposed to be huge, make it huge. It helps to use something that appears in the foreground level as a size reference. When your level loads, you want to load all the background levels into their respective scene windows and mount all the scene windows to whatever object the camera should be tracking. This works because it's possible to mount a scene window to an object that isn't in the scenegraph that it currently has loaded. In other words, all the scene windows will be mounted to an object in the foreground window's level.
After doing just this, you'll probably notice that the background stuff looks huge when you run your game (as though it were all in the foreground). This next part will require some tweaking, but basically what you need to do is set the zoom on each scene window based on how far away they should appear.
A basic rule of thumb for setting zoom is: 1 / "relative distance"
So if the mountain should be 10 times as far as the player, set the zoom to 0.1 (i.e. 1/10) on the scene window with the mountain in it.
Once this is set up, parallax will automatically work. Since the all the scene windows are mounted to the same object, the views of the different levels are moving at the same speed, but the cameras that are zoomed out further will apear to move slower.
If you have multiple levels, it's important to note that closer layers should have a higher zoom (up to 1 which is the "action plane" - the window where the action happens), and progressively further layers should have a lower zoom.
Using this method, you can also give the appearance of foreground objects (in front of the action plane) moving past the camera quickly by placing a scene window in front of the action window and giving it a zoom greater than 1.
#9
I had to split this up into two posts so it would fit...
Parallax.cs (part 1)
01/16/2007 (3:03 am)
The above method is much cleaner, but just for kicks, here's the parallax manager I wrote up a couple months ago. The interface could be cleaned up a little bit to make it function more like a proper singleton, but it worked well enough for what I needed. One problem with how this works is that if you set scroll speed to 1 the minor lag between the camera movement and the resulting parallax updates is obvious. That doesn't happen when using the scenegraph method.I had to split this up into two posts so it would fit...
Parallax.cs (part 1)
//---------------------------------------------------------------------------------------------
// Torque Game Builder
// Copyright (C) GarageGames.com, Inc.
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
// ParallaxManager!
// _______________
// This was written to simplify the creation of parallax effects. The ParallaxManager is a
// ScriptObject with two SimSets:
// -objectGroupSet
// -scrollerSet
//
// The difference between the two is that the objects will be moved (positions changed), while
// the scrollers will just have their scroll speed adjusted.
//
// When adding things to the sets, their class or superclass field must be set to
// ParallaxObjectGroup or ParallaxScroller and they must have a field
// called ScrollSpeed with a float that represents the desired speed they will move relative
// to the target. The field will default to 1 (meaning 'same as camera'). Numbers lower than
// one mean slower movement, such as for a background. Higher numbers mean faster movement.
//
// Restrictions:
// -Only t2dSceneObjectGroup objects can be added to the objectGroupSet.
// -Only t2dScroller objects can be added to the scrollerSet.
//
// Usage:
// -First set up your scene object groups and scrollers.
// -Set their class or superclass field to "ParallaxObjectGroup" or "ParallaxScroller"
// -Add a dynamic field on them called "ScrollSpeed" and give it a value
// (numbers smaller than 1 mean less movement relative to the camera, more than 1 means more, 1 means equal)
// -Name your sceneGraph and in the onLevelLoaded function call %this.initializeParallaxManager();
// (the function returns the new parallax manager)
// -Call either setTarget or setScrollSpeed on the parallaxManager
// -Call start on the parallax manager
// -Write an onUpdateScene callback on your scenegraph namespace that calls %this.getParallaxManager().update();
//
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
// t2dSceneGraph::initializeParallaxManager
// Initializes the parallax scroller manager on a SceneGraph.
//---------------------------------------------------------------------------------------------
function t2dSceneGraph::initializeParallaxManager( %this )
{
// create a parallax manager
%this.parallaxManager = ParallaxManager::createParallaxManager();
}
//---------------------------------------------------------------------------------------------
// t2dSceneGraph::hasParallaxManager
// Returns true if this scenegraph has a parallax manager. Returns false otherwise.
//---------------------------------------------------------------------------------------------
function t2dSceneGraph::hasParallaxManager( %this )
{
// check for a parallax manager
if( isObject( %this.parallaxManager ) )
if( %this.parallaxManager.class $= "ParallaxManager" )
return true;
// no parallax manager - return false
return false;
}
//---------------------------------------------------------------------------------------------
// t2dSceneGraph::getParallaxManager
// Returns the parallax manager object ID.
//---------------------------------------------------------------------------------------------
function t2dSceneGraph::getParallaxManager( %this )
{
// return parallax manager
return %this.parallaxManager;
}
//---------------------------------------------------------------------------------------------
// t2dSceneWindow::getPositionX
// Hack to make parallax manager work with scene windows
// ..which don't normally have getPositionX
//---------------------------------------------------------------------------------------------
function t2dSceneWindow::getPositionX( %this )
{
return getWord( %this.getCurrentCameraPosition(), 0 );
}
//---------------------------------------------------------------------------------------------
// ParallaxManager::createParallaxManager
// Creates the parallax manager.
//---------------------------------------------------------------------------------------------
function ParallaxManager::createParallaxManager()
{
// create a new parallax manager
%parallaxManager = new ScriptObject()
{
class = ParallaxManager;
scrollSpeed = 0;
scrollModifier = 1;
active = false;
};
%parallaxManager.objectGroupSet = new SimSet();
%parallaxManager.scrollerSet = new SimSet();
return %parallaxManager;
}
//---------------------------------------------------------------------------------------------
// ParallaxManager::setTarget
// Sets the object to base the parallax effect on.
// For the effect to look right, this should be the camera.
//---------------------------------------------------------------------------------------------
function ParallaxManager::setTarget( %this, %target )
{
// set the target
%this.target = %target;
// set the current and last target positions
%this.currentTargetPosition = %target.getPositionX();
%this.lastTargetPosition = %this.currentTargetPosition;
}
//---------------------------------------------------------------------------------------------
// ParallaxManager::setScrollSpeed
// Sets the base scroll speed of the scroller.
// This only applies if there is no target set (if the target field is set to "").
// Default is (0).
//---------------------------------------------------------------------------------------------
function ParallaxManager::setScrollSpeed( %this, %scrollSpeed )
{
// set the base scroll speed
%this.scrollSpeed = %scrollSpeed;
}
//---------------------------------------------------------------------------------------------
// ParallaxManager::setScrollModifier
// Sets the base scroll speed multiplier to be used in all calculations.
// Default is (1).
//---------------------------------------------------------------------------------------------
function ParallaxManager::setScrollModifier( %this, %scrollModifier )
{
// set the base scroll speed
%this.scrollModifier = %scrollModifier;
}
#10
01/16/2007 (3:34 am)
Parallax.cs (part 2)//---------------------------------------------------------------------------------------------
// ParallaxManager::start
// Begins the effect.
//---------------------------------------------------------------------------------------------
function ParallaxManager::start( %this, %scrollSpeed )
{
// set the active flag to true
%this.active = true;
// set the scroll speed if applicable
if( %scrollSpeed !$= "" )
%this.scrollSpeed = %scrollSpeed;
// set lastTime so update has an accurate time
%this.lastTime = getSimTime();
}
//---------------------------------------------------------------------------------------------
// ParallaxManager::pause
// Pauses the effect.
//---------------------------------------------------------------------------------------------
function ParallaxManager::pause( %this )
{
// set the active flag to false
%this.active = false;
}
//---------------------------------------------------------------------------------------------
// ParallaxManager::addObject
// Adds a ParallaxObject to one of the two SimSets.
// Objects must be either t2dSceneObjectGroup or t2dScroller objects.
// They must also have ParallaxScroller or ParallaxObjectGroup set as their
// class or superclass field.
//---------------------------------------------------------------------------------------------
function ParallaxManager::addObject( %this, %object )
{
// check for ParallaxObject class
if( %object.class !$= "ParallaxObjectGroup" && %object.class !$= "ParallaxScroller")
{
warn( "Error adding object to ParallaxManager: class field must be 'ParallaxScroller' for t2dScrollers and 'ParallaxObjectGroup' for t2dSceneObjectGroups." );
%object.dumpClassHierarchy();
return false;
}
// get c++ class of object
%objectClass = %object.getClassName();
// check for valid ParallaxObject object
if( %objectClass $= "t2dScroller" )
{
// zero out scrolling and add to scroller set
%object.scrollX = 0;
%this.scrollerSet.add( %object );
return true;
}
//else if( %objectClass $= "t2dSceneObjectGroup" )
//{
// add to object group set
%this.objectGroupSet.add( %object );
return true;
//}
// no valid c++ class found: warn and return false
warn( "Error adding object to ParallaxManager: object must be a t2dScroller or t2dSceneObjectGroup." );
return false;
}
//---------------------------------------------------------------------------------------------
// ParallaxManager::update
// This is where all the parallax objects are adjusted.
//---------------------------------------------------------------------------------------------
function ParallaxManager::update( %this )
{
// check if the parallax manager is active
if( %this.active == false )
return false;
// find delta time since last update and set lastTime
%this.currentTime = getSimTime();
%deltaTime = %this.currentTime - %this.lastTime;
%this.lastTime = %this.currentTime;
// check if there is a target
if( isObject( %this.target ) )
{
// get the target's current position
%this.currentTargetPosition = %this.target.getPositionX();
// check for change in position
if( %this.currentTargetPosition != %this.lastTargetPosition )
{
// get difference and update scroll speed
%posDiff = %this.lastTargetPosition - %this.currentTargetPosition;
%this.setScrollSpeed( ( %posDiff / %deltaTime ) * 1000 );
// update lastTargetPosition
%this.lastTargetPosition = %this.currentTargetPosition;
}
}
// check for a new scroll speed and update scrollers
if( %this.scrollSpeed != %this.lastScrollSpeed )
{
// update scrollers
%count = %this.scrollerSet.getCount();
for( %i = 0; %i < %count; %i++ )
{
%scroller = %this.scrollerSet.getObject( %i );
%scroller.scrollX = %scroller.scrollSpeed * %this.scrollSpeed * %this.scrollModifier;
}
// set lastScrollSpeed to the current scroll speed
%this.lastScrollSpeed = %this.scrollSpeed;
}
// check if scroll speed is not zero
if( %this.scrollSpeed != 0 )
{
// update scene object group positions
%count = %this.objectGroupSet.getCount();
for( %i = 0; %i < %count; %i++ )
{
%objectGroup = %this.objectGroupSet.getObject( %i );
// get desired move units per second
%baseDeltaPos = -%objectGroup.scrollSpeed * %this.scrollSpeed * %this.scrollModifier;
// convert to move units per time passed since last update
%deltaPos = ( %baseDeltaPos / 1000 ) * %deltaTime;
// apply the change
%currentPos = %objectGroup.getPositionX();
%objectGroup.setPositionX( %currentPos + %deltaPos );
}
}
}
//---------------------------------------------------------------------------------------------
// ParallaxScroller::onLevelLoaded
// When a ParallaxObject is loaded it adds itself to its scenegraph's parallax manager.
//---------------------------------------------------------------------------------------------
function ParallaxScroller::onLevelLoaded( %this, %sceneGraph)
{
// check if the scenegraph has a parallax manager
if( !%sceneGraph.hasParallaxManager() )
return;
// initialize scroll speed field
if( %this.scrollSpeed $= "" )
%this.scrollSpeed = 1;
// get the parallax manager and add this object to it
%parallaxManager = %sceneGraph.getParallaxManager();
// add this to the manager
%parallaxManager.addObject( %this );
}
//---------------------------------------------------------------------------------------------
// ParallaxObjectGroup::onLevelLoaded
// Hacky workaround for t2dSceneObjectGroup's lack of an onLevelLoaded callback.
//---------------------------------------------------------------------------------------------
function ParallaxObjectGroup::onAdd( %this )
{
// schedule the scene object group to register itself next frame
// to make sure the parallax manager has been created.
%this.schedule( 0, "registerParallaxObjectGroup" );
}
//---------------------------------------------------------------------------------------------
// ParallaxObjectGroup::registerParallaxObjectGroup
// Add this ParallaxObjectGroup to its scenegraph's parallax manager.
//---------------------------------------------------------------------------------------------
function ParallaxObjectGroup::registerParallaxObjectGroup( %this )
{
// check if the scenegraph has a parallax manager
%sceneGraph = sceneWindow2D.getSceneGraph();
if( !isObject( %sceneGraph ) || !%sceneGraph.hasParallaxManager() )
return;
// initialize scroll speed field
if( %this.scrollSpeed $= "" )
%this.scrollSpeed = 1;
// get the parallax manager and add this object to it
%parallaxManager = %sceneGraph.getParallaxManager();
// add this to the manager
%parallaxManager.addObject( %this );
}
#11
01/31/2007 (10:40 pm)
I'm attempting to create parallax scrolling in my game using multiple scenegraphs, but I am stuck immediately because I don't know how to create another scenegraph. Can I create it somehow within TGB or do I have to do it through code? It looked like Oliver had multiple scenegraphs listed in one of the TGB editors.
#12
Using this, in your level create a dummy background object as my .plan shows and give it a fileName to load... the rest gets fired up automatically: The Background objects uses your new window to load the level.. if the level contains scrolling backgrounds a parallax manager gets created and the objects register themselves with it. Now all you have to do is call update. :)
01/31/2007 (11:50 pm)
You create a second scenegraph in your main GUI, but you have to add it before the original one, otherwise the new one will overdraw your action scenewindow with your background. After you have done that you can use the window method .loadLevel() to load your background like you normally do with your levels. Here is what I did to control the parallax scrolling:function createParallaxBackgroundManager()
{
%temp = new ScriptObject()
{
class = "ParallaxBackgroundManager";
};
%temp.backgrounds = new t2dSceneObjectSet();
return %temp;
}
function ParallaxBackgroundManager::update(%this)
{
%cameraPositionX = getWord(sceneWindow2D.getCurrentCameraPosition(), 0);
%cameraPositionY = getWord(sceneWindow2D.getCurrentCameraPosition(), 1);
for(%i = 0; %i < %this.backgrounds.getCount(); %i++)
{
%currentScroller = %this.backgrounds.getObject(%i);
%currentScroller.setScrollPosition(%cameraPositionX * %currentScroller.scrollFactorX SPC
%cameraPositionY * %currentScroller.scrollFactorY);
}
}
function ParallaxBackground::onLevelLoaded(%this, %sceneGraph)
{
if(!isObject($parallaxBackgroundManager))
$parallaxBackgroundManager = createParallaxBackgroundManager();
$parallaxBackgroundManager.backgrounds.add(%this);
}
function Background::onLevelLoaded(%this, %sceneGraph)
{
backgroundWindow2D.loadLevel("MarshallMaryJane/data/levels/" @ %this.fileName);
%this.schedule(0, "safeDelete");
}Using this, in your level create a dummy background object as my .plan shows and give it a fileName to load... the rest gets fired up automatically: The Background objects uses your new window to load the level.. if the level contains scrolling backgrounds a parallax manager gets created and the objects register themselves with it. Now all you have to do is call update. :)
#13
OK. I got the scenewindows and cameras working but I decided the best way to do it is to use Oliver's parallax manager, but I don't know how to update it. I update player movement in updateScene and I assume I should update the parallax manager there but am unsure how.
02/01/2007 (1:45 pm)
EDIT:OK. I got the scenewindows and cameras working but I decided the best way to do it is to use Oliver's parallax manager, but I don't know how to update it. I update player movement in updateScene and I assume I should update the parallax manager there but am unsure how.
#14
in "OnUpdateScene"... doesn't that work for you? Oh and I forgot to mention.. you should delete the object yourself onLevelEnded, otherwise you might cause memory leaks. :)
02/02/2007 (2:18 am)
Hmm, I simply do:if(isObject($parallaxBackgroundManager))
{
$parallaxBackgroundManager.update();
}in "OnUpdateScene"... doesn't that work for you? Oh and I forgot to mention.. you should delete the object yourself onLevelEnded, otherwise you might cause memory leaks. :)
#16
02/02/2007 (6:37 am)
Ah ok.. happy scrolling! ;)
Torque Owner Thomas Buscaglia