I have a number of text contols in a scrollView that can be zoomed. In order to redraw the controls at a higher resolution to avoid blurry text, I set each view's contentScaleFactor in the view hierarchy as explained here. Everything works fine for labels and textfields but textViews do not redraw at the higher scale factor. I noticed that the only other subview for textViews that may make a difference if set is a private class UIWebDocumentView which implements content like UIWebView ( ie WebKit) but the new scale factor is ignored if set at either level ( UITextView or UIWebDocumentView ).
Any ideas how to reset the scale factor ( resolution ) for TextViews specifically ?
Setting the contentScaleFactor and contentsScale is in fact the key, as #dbotha pointed out, however you have to walk the view and layer hierarchies separately in order to reach every internal CATiledLayer that actually does the text rendering. You also need to account for the screen scale.
The implementation would be something like this:
- (void)updateForZoomScale:(CGFloat)zoomScale {
CGFloat screenAndZoomScale = zoomScale * [UIScreen mainScreen].scale;
// Walk the layer and view hierarchies separately. We need to reach all tiled layers.
[self applyScale:(zoomScale * [UIScreen mainScreen].scale) toView:self.textView];
[self applyScale:(zoomScale * [UIScreen mainScreen].scale) toLayer:self.textView.layer];
}
- (void)applyScale:(CGFloat)scale toView:(UIView *)view {
view.contentScaleFactor = scale;
for (UIView *subview in view.subviews) {
[self applyScale:scale toView:subview];
}
}
- (void)applyScale:(CGFloat)scale toLayer:(CALayer *)layer {
layer.contentsScale = scale;
for (CALayer *sublayer in layer.sublayers) {
[self applyScale:scale toLayer:sublayer];
}
}
You can than call this when the zoom scale changes (part of UIScrollViewDelegate):
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
[self updateForZoomScale:scale];
}
I filed an enhancement request here: rdar://21443666 (http://www.openradar.me/21443666). There's also a sample project with the workaround attached.
Be sure to apply the contentScaleFactor to all subviews of the UITextView. I've just tested the following with a UITextView and found it to work:
- (void)applyScale:(CGFloat)scale toView:(UIView *)view {
view.contentScaleFactor = scale;
view.layer.contentsScale = scale;
for (UIView *subview in view.subviews) {
[self applyScale:scale toView:subview];
}
}
Related
I'm trying to zoom and pan an image (TImage) with an object (TShape) on it in a VCL Windows application running on an all-in-one pc with touch display.
I put the image in a panel (TPanel) so when I zoom it, it remains always inside the panel.
Then I put a shape (TShape) on the image.
What I would like to get is to zoom and pan the image and the shape should zoom and move with the image.
I started from the Embarcadero sample "Mobile Snippets - InteractiveGestures - ImageZoom" and with the following code I managed to do image zoom and pan with two fingers:
void __fastcall TForm1::Image1Gesture(TObject *Sender, const TGestureEventInfo &EventInfo, bool &Handled) {
TPointF LImageCenter;
double expance;
TPointF movement;
try {
if (EventInfo.GestureID == static_cast<short>(Vcl::Controls::igiZoom)) {
if (!EventInfo.Flags.Contains(TInteractiveGestureFlag::gfBegin) &&
!EventInfo.Flags.Contains(TInteractiveGestureFlag::gfEnd)) {
// zoom the image
TImage * LImage = dynamic_cast<TImage*>(Image1);
LImageCenter.x = LImage->Left + (LImage->Width / 2);
LImageCenter.y = LImage->Top + (LImage->Height / 2);
expance = EventInfo.Distance - FLastDistance;
if (((LImage->Width + (expance * AR))> MIN_DIM) &&
((LImage->Height + (expance))> MIN_DIM)) {
LImage->Width = (int)(LImage->Width + (expance * AR));
LImage->Height = (int)(LImage->Width / AR );
}
LImage->Left = LImageCenter.X - LImage->Width / 2;
LImage->Top = LImageCenter.Y - LImage->Height / 2;
}
FLastDistance = EventInfo.Distance;
}
else if (EventInfo.GestureID == static_cast<short>(Vcl::Controls::igiPan)) {
if (!EventInfo.Flags.Contains(TInteractiveGestureFlag::gfBegin) &&
!EventInfo.Flags.Contains(TInteractiveGestureFlag::gfEnd)) {
// move the image
TImage * LImage = dynamic_cast<TImage*>(Image1);
movement.x = EventInfo.Location.X - FLastLocation.x;
movement.y = EventInfo.Location.Y - FLastLocation.y;
LImage->Left += movement.x;
LImage->Top += movement.y;
}
FLastLocation.x = EventInfo.Location.X;
FLastLocation.y = EventInfo.Location.Y;
}
} catch (Exception &e) {
ShowMessage(e.Message);
} }
AR is the image aspect ratio and MIN_DIM is a define to set the minimum image dimensions for the zoom.
The pan movement is not so great...
I don't know how to make the shape to behave in accordance with the image zoom and move.
The shape parent is the panel under the image, not the image, so the shape is completely independent from the image changes.
I can't set the image as the shape parent.
Whatever suggestion to get the desired result will be appreciated.
The pan movement is not smooth because EventInfo.Location.X and EventInfo.Location.Y do not vary in a smooth way.
If I start the pan movement and I stop, even if fingers are still EventInfo.Location continues to change.
Is there a way to avoid this behavior?
I have to do something like this in my Android App with LibGDX:
The ball is a Image that has a drag listener and added to a Stage:
ballImage.addListener(new DragListener() {
public void drag(InputEvent event, float x, float y, int pointer) {
ballImage.moveBy(x - ballImage.getWidth() / 2, y - ballImage.getHeight() / 2);
}
});
which works great. But I have a problem with scaling the ball. I can't scale the ball and maintain it's position and drag stop working normally (ball jump outside screen just on touch). I tried something like this:
massSlider.addListener(new ChangeListener() {
public void changed (ChangeEvent event, Actor actor) {
float x = ballImage.getX() - ballImage.getWidth() / 2;
float y = ballImage.getY() - ballImage.getHeight() / 2;
ballImage.moveBy(x, y);
ballImage.setScale(massSlider.getValue());
ballImage.moveBy(-x, -y);
}
});
Also, I was trying to use scaleBy()/setScale() and moveBy()/setPosition(), or to not move the ball at all before and after scaling, but nothing seems to work. What I do wrong?
Scaling is usually something that you want to (and easily can) avoid. It is nice to use scaling e.g. for actions to create a short pop out or pop in effect. But apart from that it's usually much better to adjust the size instead. So instead of using setScale(a) you do: setSize(a * unscaledWidth, a * unscaledHeight). Note that you will have to keep track of the unscaled width and height for this.
I have a custom animated UIViewController transition, and it seems that there is a bug in iOS that screws up the layout in landscape orientation. In the main animation method, i'm given a mix of landscape and portrait views. (In portrait the views are all portrait, so no problem.)
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView *containerView = [transitionContext containerView];
// fromViewController.view => landscape, transform
// toViewController.view => portrait, transform
// containerView => portrait, no transform
[containerView addSubview:toViewController.view];
// ...animation... //
}
I know that the frame property is not reliable when a view has a transform - so I'm guessing this is the root of the problem. In landscape mode, the to/from viewControllers views have a 90 deg clockwise transform [0 -1 1 0]. I've tried using bounds/center to size and position the view, as well removing the transform and then reapplying it, but UIKit fights me and insists on displaying the view as portrait. Annoying!
In the screenshot, the dark grey is the UIWindow background, and the red is the added modal view controller which should cover the whole screen.
Anyone found a workaround?
Ok, the fix is surprisingly simple:
Set the toViewController frame to the container before adding the view to the container.
toViewController.view.frame = containerView.frame;
[containerView addSubview:toViewController.view];
Update: There is still a limitation in that you don't know the orientation of the frame. It is portrait initially, but stretched into landscape when it is displayed on screen. If you wanted to slide in the view from the right, in landscape it might slide in from the "top" (or the bottom if viewing the other landscape!)
I came across this issue and I just don't feel that the above solutions do this any justice. I propose a solution that doesn't require hacky code and hard coded frames.
UIView has an awesome function to convert a CGRect into the coordinate space of another (namely; +[UIView convertRect:fromView:]). So I want to detail a far simpler way one can achieve this effect in any orientation without any hardcoded values. In this example lets say we want a simple animation that slides a view in from the right of the screen.
So in our animator's animateTransition(:) we could simply perform the following:
Swift
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let toView = toViewController.view
let fromView = fromViewController.view
let containerView = transitionContext.containerView()
if(isPresenting) {
//now we want to slide in from the right
let startingRect = CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0)
toView.frame = containerView.convertRect(startingRect, fromView:fromView);
containerView.addSubview(toView)
let destinationRect = containerView.convertRect(fromView.bounds, fromView: fromView)
UIView.animateWithDuration(transitionDuration(transitionContext),
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.7,
options: .BeginFromCurrentState,
animations: { () -> Void in
toView.frame = destinationRect
}, completion: { (complete) -> Void in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
})
} else {
//we want to slide out to the right
let endingRect = containerView.convertRect(CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0), fromView: fromView)
UIView.animateWithDuration(transitionDuration(transitionContext),
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.7,
options: .BeginFromCurrentState,
animations: { () -> Void in
fromView.frame = endingRect
}, completion: { (complete) -> Void in
if !transitionContext.transitionWasCancelled() {
fromView.removeFromSuperview()
}
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
})
}
}
Objective-C
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView *toView = toViewController.view;
UIView *fromView = fromViewController.view;
UIView *containerView = [transitionContext containerView];
if(self.isPresenting) {
//now we want to slide in from the right
CGRect startingRect = CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0);
toView.frame = [containerView convertRect:startingRect fromView:fromView];
[containerView addSubview:toView];
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
toView.frame = [containerView convertRect:fromView.bounds
fromView:fromView];
}
completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
} else {
//we want to slide out to the right
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
CGRect endingRect = CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0);
fromView.frame = [containerView convertRect:endingRect fromView:fromView];
}
completion:^(BOOL finished) {
[fromView removeFromSuperview];
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
I hope this helps someone else who came here in the same boat (if it does, an up-vote won't hurt :) )
The existing answer goes part way but not all the way (we want proper frames and rotation handling on both devices, all orientations, for both animated and interactive transitions).
This blog post helps:
http://www.brightec.co.uk/blog/ios-7-custom-view-controller-transitions-and-rotation-making-it-all-work
And it quotes an Apple Support person stating the true nature of the problem:
"For custom presentation transitions we setup an intermediate view between the window and the windows rootViewController's view. This view is the containerView that you perform your animation within. Due to an implementation detail of auto-rotation on iOS, when the interface rotates we apply an affine transform to the windows rootViewController's view and modify its bounds accordingly. Because the containerView inherits its dimensions from the window instead of the root view controller's view, it is always in the portrait orientation."
"If your presentation animation depends upon the orientation of the presenting view controller, you will need to detect the presenting view controller's orientation and modify your animation appropriately. The system will apply the correct transform to the incoming view controller but you're animator need to configure the frame of the incoming view controller."
But it doesn't address interactive transitions.
I worked out a complete solution to the problem here:
https://github.com/alfiehanssen/Cards
Essentially, you need to calculate the frames of your viewControllers based on the orientation of one of the viewControllers (toViewController or fromViewController) rather than the bounds of the transitionContext's containerView.
I was stumped with this issue as well. I didn't like the switch/case solution too much. I ended up creating this function instead:
#implementation UIView (Extras)
- (CGRect)orientationCorrectedRect:(CGRect)rect {
CGAffineTransform ct = self.transform;
if (!CGAffineTransformIsIdentity(ct)) {
CGRect superFrame = self.superview.frame;
CGPoint transOrigin = rect.origin;
transOrigin = CGPointApplyAffineTransform(transOrigin, ct);
rect.origin = CGPointZero;
rect = CGRectApplyAffineTransform(rect, ct);
if (rect.origin.x < 0.0) {
transOrigin.x = superFrame.size.width + rect.origin.x + transOrigin.x;
}
if (rect.origin.y < 0.0) {
transOrigin.y = superFrame.size.height + rect.origin.y + transOrigin.y;
}
rect.origin = transOrigin;
}
return rect;
}
- (CGRect)orientationCorrectedRectInvert:(CGRect)rect {
CGAffineTransform ct = self.transform;
if (!CGAffineTransformIsIdentity(ct)) {
ct = CGAffineTransformInvert(ct);
CGRect superFrame = self.superview.frame;
superFrame = CGRectApplyAffineTransform(superFrame, ct);
CGPoint transOrigin = rect.origin;
transOrigin = CGPointApplyAffineTransform(transOrigin, ct);
rect.origin = CGPointZero;
rect = CGRectApplyAffineTransform(rect, ct);
if (rect.origin.x < 0.0) {
transOrigin.x = superFrame.size.width + rect.origin.x + transOrigin.x;
}
if (rect.origin.y < 0.0) {
transOrigin.y = superFrame.size.height + rect.origin.y + transOrigin.y;
}
rect.origin = transOrigin;
}
return rect;
}
Basically, you can create your frame rects using the portrait or landscape coordinates but run it through the function with the view's transform before applying it to the view. With this method, you can use bounds to get correct view size.
CGRect endFrame = toViewController.view.frame;
CGRect startFrame = endFrame;
startFrame.origin.y = fromViewController.view.bounds.size.height;
endFrame = [fromViewController.view orientationCorrectedRect:endFrame];
startFrame = [fromViewController.view orientationCorrectedRect:startFrame];
toViewController.view.frame = startFrame;
One solution is to have a very short (or zero-second) transition, then once the transition is finished and your view controller is presented, it will have the correct transforms applied to it. You then perform your animations from within the presented view controller itself.
I try to draw simple text in my android game on libgdx, but it's look sharp. How to make text look smooth in different resolutions? My Code:
private BitmapFont font;
font = new BitmapFont();
font.scale((ppuX*0.02f));
font.draw(spb, "Score:", width/2-ppuX*2f, height-0.5f*ppuY);
font.getRegion().getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear);
This gets the texture used in a BitmapFont and changes its filtering to bilinear, allowing higher resulting image quality while both up- and downscaling it at the cost of slightly slower (the difference is usually not noticeable) GPU rendering.
One solution is to use the FreeType extension to libgdx, as described here. This allows you to generate a bitmap font on the fly from a .ttf font. Typically you would do this at startup time once you know the target resolution.
Here's an example:
int viewportHeight;
BitmapFont titleFont;
BitmapFont textFont;
private void createFonts() {
FileHandle fontFile = Gdx.files.internal("data/Roboto-Bold.ttf");
FreeTypeFontGenerator generator = new FreeTypeFontGenerator(fontFile);
FreeTypeFontParameter parameter = new FreeTypeFontParameter();
parameter.size = 12;
textFont = generator.generateFont(parameter);
parameter.size = 24;
titleFont = generator.generateFont(parameter);
generator.dispose();
}
You should definitly have a quick look on custom font shaders and/or DistanceField-Fonts. They're easy to understand and similarly easy to implement:
https://github.com/libgdx/libgdx/wiki/Distance-field-fonts
DistanceFieldFonts stay smooth, even when you upscale them:
Create a .fnt file using hiero which is provided by libgdx website.
Set the size of font to 150; it will create a .fnt file and a .png file.
Copy both files into your assets folder.
Now declare the font:
BitmapFont font;
Now in create method:
font = new BitmapFont(Gdx.files.internal("data/100.fnt"), false); // 100 is the font name you can give your font any name
In render:
font.setscale(.2f);
font.draw(batch, "whatever you want to write", x,y);
In general you don't get sharp text because you are designing your game for a certain resolution and when you move to a different device, Libgdx scales everything to match the new resolution. Even with linear filtering scaling is bad on text because round corners are easily distorted. In a perfect world you would create the content dynamically at runtime according to the number of pixels available to you and not a single automatic scale would be used.
This is the approach I'm using: Building everything for small screen (480 x 320), and when you open it on a bigger resolution, I load the BitmapFont with a higher size and apply and inverse scale to the one that Libgdx will later do automatically.
Here's an example to make things clearer:
public static float SCALE;
public static final int VIRTUAL_WIDTH = 320;
public static final int VIRTUAL_HEIGHT = 480;
public void loadFont(){
// how much bigger is the real device screen, compared to the defined viewport
Screen.SCALE = 1.0f * Gdx.graphics.getWidth() / Screen.VIRTUAL_WIDTH ;
// prevents unwanted downscale on devices with resolution SMALLER than 320x480
if (Screen.SCALE<1)
Screen.SCALE = 1;
FreeTypeFontGenerator generator = new FreeTypeFontGenerator(Gdx.files.internal("data/Roboto-Regular.ttf"));
// 12 is the size i want to give for the font on all devices
// bigger font textures = better results
labelFont = generator.generateFont((int) (12 * SCALE));
// aplly the inverse scale of what Libgdx will do at runtime
labelFont.setScale((float) (1.0 / SCALE));
// the resulting font scale is: 1.0 / SCALE * SCALE = 1
//Apply Linear filtering; best choice to keep everything looking sharp
labelFont.getRegion().getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear);
}
Bitmap fonts are textures and if you want to make smaller textures look smoother when you are resizing them to bigger sizes you need to make sure you use the right texture filter.
This blog post deals with such issues
With many things deprecated after the update, this is what's working for me:
public void regenerateFonts(OrthographicCamera cam, Game game) {
int size = 18;
if (cam != null && game != null) {
// camera and game are provided, recalculate sizes
float ratioX = cam.viewportWidth / game.getW();
float ratioY = cam.viewportHeight / game.getH();
System.out.println("Ratio: [" + ratioX + ":" + ratioY + "]");
size *= ratioY;
}
// font parameters for this size
FreeTypeFontParameter params = new FreeTypeFontParameter();
params.flip = true; // if your cam is flipped
params.characters = LETTERS; // your String containing all letters you need
params.size = size;
params.magFilter = TextureFilter.Linear; // used for resizing quality
params.minFilter = TextureFilter.Linear; // also
// Lato Light generator
FreeTypeFontGenerator generator = new FreeTypeFontGenerator(Gdx.files.internal("fonts/Lato-Light.ttf"));
// make the font
fontLatoLight = generator.generateFont(params);
generator.dispose(); // dispose to avoid memory leaks
}
And when you want to render it on the screen:
// text rendering
fontLatoLight.setColor(Color.WHITE); // set color here (has other overloads too)
fontLatoLight.draw(batch, "Hello World!", xCoord, yCoord);
My Solution for smooth text with Libgdx
I use BitmapFont and I generate 3 different size same fonts using Hiero tool
example Arial 16 , Arial 32, Arial 64
I put them in my assets file and use (load) only one of them depeding on the size of screen
if(Gdx.graphics.getWidth() < (480*3)/2)
{
textGametFont = BitmapFont(Gdx.files.internal(nameFont+16+".fnt"),
Gdx.files.internal(nameFont+16+".png"), false);
}else
{
if(Gdx.graphics.getWidth() < (3*920)/2)
{
textGametFont = new BitmapFont(Gdx.files.internal(nameFont+32+".fnt"),
Gdx.files.internal(nameFont+32+".png"), false);
}else
{
textGametFont = new BitmapFont(Gdx.files.internal(nameFont+64+".fnt"),
Gdx.files.internal(nameFont+64+".png"), false);
}
}
then I use this line of code to higher result quality of down and up Scaling
textGametFont.getRegion().getTexture().setFilter(TextureFilter.Linear, TextureFilter.Linear);
scale the image
to handle the size of the font for all type of resolution of device I use those two functions
public static float xTrans(float x)
{
return x*Gdx.graphics.width/(YourModel.SCREEN_WIDTH);
}
public static float yTrans(float y)
{
return y*Gdx.graphics.height/YourModel.SCREEN_Height;
}
the model screen resolution that i use is
SCREEN_WIDTH = 480
SCREEN_HEIGHT = 320
Set the scale to the font
textGametFont.setScale((xtrans(yourScale)+ ytrans(yourScale))/2f);
and finally draw your text
textGametFont.draw(batch, "WINNER !!", xTrans(250), yTrans(236));
Hope this was clear and helpful !!!
private BitmapFont font;
font = new BitmapFont();
font.scale((ppuX*0.02f));
font.draw(spb, "Score:", width/2-ppuX*2f, height-0.5f*ppuY);
Check out [this](http://www.badlogicgames.com/wordpress/?p=2300) blog post.
??? This just explains how to use the .scale() method which I'm stating is deprecated in the current release.
In scene2d, if you want apply antialiasing to all your labels, put this on constructor of your first screen:
skin.getFont("default-font").getRegion().getTexture().setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);
This is the first screen in my game:
...
public class MainMenuScreen implements Screen {
public MainMenuScreen() {
...
skin.getFont("default-font").getRegion().getTexture().setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear);
}
}
Font name is in ui.json file, check for BitmapFont and Label$LabelStyle section:
"com.badlogic.gdx.graphics.g2d.BitmapFont": {
"default-font": {
"file": "default.fnt"
}
},
"com.badlogic.gdx.scenes.scene2d.ui.Label$LabelStyle": {
"default": {
"font": "default-font",
"fontColor": "white",
}
},
OK so i have a character that moves with the mouse. I need it to stay in the center of the screen(kind of like a platformer game). I can't figure out how to access the camera and move it. (Note: I have tried Vcam and moving all of the other objects but Vcam makes the file slow or something [or so i have heard] and moving the other objects in kind of like cheating [and for my needs is insufficient]) I don't have any code because i don't know where to start. Maybe someone can point me into the right direction.
Thanks,
Thor
One way is to store everyhting in one DisplayObject and then move that single object based on the camera movement. Instead of moving the camera, move the main container the opposite direction of the camera. I'm not sure why you seem to suggest a strategy like this is "cheating" as it is a perfectly suitable way to doing this.
This is my previous answer on a similar question found here.
What I do here is:
Create a Map class with a property camera which is another custom class MapCamera.
The MapCamera has five properties:
_x
_y
map - a reference to the instance of Map owning this MapCamera
offsetX
offsetY
The offset values represent the x and y spacing from the left and top edges of the screen, which should be set to half of the stage width and height so that the camera will centre on the stage correctly.
The _x and _y properties are private, and have getters and setters.
The getters are pretty basic:
public function get x():Number{ return _x; }
public function get y():Number{ return _y; }
The setters are where the viewport will be altered, like so:
public function set x(n:Number):void
{
_x = n;
map.x = -(_x + offsetX);
}
public function set y(n:Number):void
{
_y = n;
map.y = -(_y + offsetY);
}
From here, you add your children into the Map container and then can simply go:
map.camera.x = player.x;
map.camera.y = player.y;
Which will cause the player to always be in the centre of the screen.
Your camera is only a vector that modifies position of all renderable objects.
myMovieClip.x = movingClipPosition.x + camera.x
So if the camera.x is moved to the right, this will make the object move the left, giving the impression of a "camera".