Parallax Scrolling like old school snes games using Sprite kit - objective-c

Hi I'm using this code to scroll my tile map, what would be the best way to implement parallax scrolling? Ive figured out a way, but it doesn't work very well. :(
- (void)setViewpointCenter:(CGPoint)position {
NSInteger x = MAX(position.x, self.size.width / 2);
NSInteger y = MAX(position.y, self.size.height / 2);
x = MIN(x, (self.map.mapSize.width * self.map.tileSize.width) - self.size.width / 2);
y = MIN(y, (self.map.mapSize.height * self.map.tileSize.height) - self.size.height / 2);
CGPoint actualPosition = CGPointMake(x, y);
CGPoint centerOfView = CGPointMake(self.size.width/2, self.size.height/2);
CGPoint viewPoint = CGPointSubtract(centerOfView, actualPosition);
self.map.position = viewPoint;
}
Thank you all for your help!

Roll your background map a fixed percentage of the foreground sprites. Typically this is only done in the axis most movement happens in (eg horizontal for side-scrollers and vertical for up-scrollers).
The following pseudo-code, assuming a side-scroller where you start at the bottom left of the map, this bottom left is (0,0) and axes increase as you move up/right
backgroundPercent = 80
background_x = foreground_x * backgroundPercent / 100
backgroup_y = foreground_y
Using the above, your background map only needs to be 80% as wide as the foreground

Related

Rotate image in bounds

I try to rotate an image view in its superview so that this image view while rotating always touches superview's borders not crossing them, with appropriate resizing. How can I implement this? The image view should be able to rotate around 360˚.
Here I use calculations based on triangle formulas, considering initial image view diagonal angle.
Maybe I should take into account new bounding frame of the image view after it gets rotated (its x and y coordinates get negative and its frame size after transform gets bigger too).
No success so far, my image view gets sized down too quickly and too much. So my goal as I understand to get proper scale factor for CGAffineTransformScale. Maybe there are other ways to do the same.
// set initial values
_planImageView.layer.affineTransform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1);
_degrees = 0;
_initialWidth = _planImageView.frame.size.width;
_initialHeight = _planImageView.frame.size.height;
_initialAngle = MathUtils::radiansToDegrees(atan((_initialWidth / 2) / (_initialHeight / 2)));
// rotation routine
- (void)rotatePlanWithDegrees:(double)degrees
{
double deltaDegrees = degrees - _degrees;
_initialAngle -= deltaDegrees;
double newAngle = _initialAngle;
double newWidth = (_initialWidth / 2) * tan(MathUtils::degreesToRadians(newAngle)) * 2;
double newHeight = newWidth * (_initialHeight / _initialWidth);
NSLog(#"DEG %f DELTA %f A %f W %f H %f", degrees, deltaDegrees, newAngle, newWidth, newHeight);
double currentScale = newWidth / _initialWidth;
_planImageView.layer.affineTransform = CGAffineTransformScale(CGAffineTransformIdentity, currentScale, currentScale);
_planImageView.layer.affineTransform = CGAffineTransformRotate(_planImageView.layer.affineTransform, (CGFloat) MathUtils::degreesToRadians(degrees));
_degrees = degrees;
self->_planImageView.center = _center;
// NSLog(#"%#", NSStringFromCGRect(_planImageView.frame));
}
EDIT
I overwrote routine thanks to the answer and now it works!
- (void)rotatePlanWithDegrees:(double)degrees
{
double newWidth =
_initialWidth * abs(cos(MathUtils::degreesToRadians(degrees))) +
_initialHeight * abs(sin(MathUtils::degreesToRadians(degrees)));
double newHeight =
_initialWidth * abs(sin(MathUtils::degreesToRadians(degrees))) +
_initialHeight * abs(cos(MathUtils::degreesToRadians(degrees)));
CGFloat scale = (CGFloat) MIN(
self.planImageScrollView.frame.size.width / newWidth,
self.planImageScrollView.frame.size.height / newHeight);
CGAffineTransform rotationTransform = CGAffineTransformMakeRotation((CGFloat) MathUtils::degreesToRadians(degrees));
CGAffineTransform scaleTransform = CGAffineTransformMakeScale(scale, scale);
_planImageView.layer.affineTransform = CGAffineTransformConcat(rotationTransform, scaleTransform);
self->_planImageView.center = _center;
}
When you rotate a rectangle W x H, the bounding box takes the dimensions W' = W |cos Θ| + H |sin Θ|, H' = W |sin Θ| + H |cos Θ|.
If you need to fit that in a W" x H" rectangle, the scaling factor is the smallest of W"/W' and H"/H'.

Objective C - Detect mouse position without event

I'm an objective C noob and I'm making a 2d game that allows players to move a tank with the arrow keys, and aim the turret with the mouse.
Currently, the turret direction is updated (by the method below) using ccMouseMoved. This passes an NSEvent that can then very easily be converted (by convertEventToGL) into coordinates relative to the window (as opposed to relative to the screen). This all works, but it'd like to be able to make the method below update the turret direction when the tank is moved via the arrow keys (I.e. if the tank moves down, the turret will adjust to continue pointing towards the mouse cursor).
How can I achieve this?
-(BOOL) ccMouseMoved:(NSEvent *)event
{
CGSize winSize = [CCDirector sharedDirector].winSize;
int x = MAX(_player.position.x, winSize.width/2);
int y = MAX(_player.position.y, winSize.height/2);
x = MIN(x, (_tileMap.mapSize.width * _tileMap.tileSize.width) - winSize.width / 2);
y = MIN(y, (_tileMap.mapSize.height * _tileMap.tileSize.height) - winSize.height/2);
CGPoint actualPosition = ccp(x, y);
CGPoint mousePosition = [[CCDirector sharedDirector] convertEventToGL:event];
_playerTurret.rotation= -atan2((mousePosition.y - winSize.height/2 - _player.position.y + actualPosition.y),(mousePosition.x - winSize.width/2 - _player.position.x + actualPosition.x)) * 180/M_PI + 180;
return YES;
}
It looks like you can use the NSWindow method mouseLocationOutsideOfEventStream.
mouseLocationOutsideOfEventStream

UIImageView. Zoom and Center to arbitrary rectangle. Can't determine correct center on screen

There is simple iPad application. Views hierarchy is:
Window - UIView - UIImageView
UIImageView has loaded image which is bigger then screen size (i.e. 1280 x 2000 against 768 x 1004). I need to zoom in arbitrary rectangular part of image (fit it on the screen) and then center it on the screen. At the moment I do zooming in following way:
-(IBAction)posIt:(id)sender
{
// rectangle size and origin in image's coordinates. Its received from out of application
CGFloat x = 550.f;
CGFloat y = 1006.f;
CGFloat w = 719.f;
CGFloat h = 850.f;
CGSize frameSize = CGSizeMake(_imgView.image.size.width, _imgView.image.size.height);
_imgView.frame = CGRectMakeWithParts(CGPointMake(0, 0), frameSize);
// Calculate center manually because orientation changing
CGPoint superCenter = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2);
// delta is the factor between size of image size and device screen
CGFloat delta = 768.f/w;
// resizing UIImageView to fit rectangle on the screen
frameSize = CGSizeMake(_imgView.image.size.width * delta, _imgView.image.size.height * delta);
_imgView.frame = CGRectMakeWithParts(CGPointMake(0, 0), frameSize);
// Here is I'm trying to calculate center of rectangle
_imgView.center = CGPointMake(superCenter.x + (superCenter.x - (w/2)*delta - x * delta), superCenter.y + (superCenter.y - (h/2)*delta - y * delta));
}
UIImageView's ContentMode parameter is AspectFit. I believe that to move one point (UIImageView's center) to another one (in order to move rectangle to the center of the superView) I shall use following formula:
newX = oldX + oldX - rectCenterX;
newY = oldY + oldY - rectCenterY;
Since UIImageView is scaled with delta factor, rectCenterX and rectCenterY should be multiplied with same delta. But I'm wrong. Rectangles centers jump out from superView center.
Is there exists some way to make these calculations correctly? What do I miss?
Thanks in advance.
I've managed correct and stable image centering. Instead of calculating center of image I'm calculating UIImageView's frame's origin coordinates like this:
CGPoint newOrigin = CGPointMake((-zoomRect.origin.x * delta) + superCenter.x - (zoomRect.size.width / 2) * delta,
(-zoomRect.origin.y * delta) + superCenter.y - (zoomRect.size.height / 2) * delta);
It turns out there is some difference between calculating center and origin. Anyway problem is solved. Thanks everybody.

Zoom Layer centered on a Sprite

I am in process of developing a small game where a space-ship travels through a layer (doh!), in some situations the spaceship comes close to an enemy, and the whole layer is zoomed in on the space-ship with the zoom level being dependent on the distance between the ship and the enemy. All of this works fine.
The main question, however, is how do I keep the zoom being centered on the space-ship?
Currently I control the zooming in the GameLayer object through the update method, here is the code:
-(void) prepareLayerZoomBetweenSpaceship{
CGPoint mainSpaceShipPosition = [mainSpaceShip position];
CGPoint enemySpaceShipPosition = [enemySpaceShip position];
float distance = powf(mainSpaceShipPosition.x - enemySpaceShipPosition.x, 2) + powf(mainSpaceShipPosition.y - enemySpaceShipPosition.y,2);
distance = sqrtf(distance);
/*
Distance > 250 --> no zoom
Distance < 100 --> maximum zoom
*/
float myZoomLevel = 0.5f;
if(distance < 100){ //maximum zoom in
myZoomLevel = 1.0f;
}else if(distance > 250){
myZoomLevel = 0.5f;
}else{
myZoomLevel = 1.0f - (distance-100)*0.0033f;
}
[self zoomTo:myZoomLevel];
}
-(void) zoomTo:(float)zoom {
if(zoom > 1){
zoom = 1;
}
// Set the scale.
if(self.scale != zoom){
self.scale = zoom;
}
}
Basically my question is: How do I zoom the layer and center it exactly between the two ships? I guess this is like a pinch zoom with two fingers!
Below is some code that should get it working for you. Basically you want to:
Update your ship positions within the parentNode's coordinate system
Figure out which axis these new positions will cause the screen will be bound by.
Scale and re-position the parentNode
I added some sparse comments, but let me know if you have any more questions/issues. It might be easiest to dump this in a test project first...
ivars to put in your CCLayer:
CCNode *parentNode;
CCSprite *shipA;
CCSprite *shipB;
CGPoint destA, deltaA;
CGPoint destB, deltaB;
CGPoint halfScreenSize;
CGPoint fullScreenSize;
init stuff to put in your CCLayer:
CGSize size = [[CCDirector sharedDirector] winSize];
fullScreenSize = CGPointMake(size.width, size.height);
halfScreenSize = ccpMult(fullScreenSize, .5f);
parentNode = [CCNode node];
[self addChild:parentNode];
shipA = [CCSprite spriteWithFile:#"Icon-Small.png"]; //or whatever sprite
[parentNode addChild:shipA];
shipB = [CCSprite spriteWithFile:#"Icon-Small.png"];
[parentNode addChild:shipB];
//schedules update for every frame... might not run great.
//[self schedule:#selector(updateShips:)];
//schedules update for 25 times a second
[self schedule:#selector(updateShips:) interval:0.04f];
Zoom / Center / Ship update method:
-(void)updateShips:(ccTime)timeDelta {
//SHIP POSITION UPDATE STUFF GOES HERE
...
//1st: calc aspect ratio formed by ship positions to determine bounding axis
float shipDeltaX = fabs(shipA.position.x - shipB.position.x);
float shipDeltaY = fabs(shipA.position.y - shipB.position.y);
float newAspect = shipDeltaX / shipDeltaY;
//Then: scale based off of bounding axis
//if bound by x-axis OR deltaY is negligible
if (newAspect > (fullScreenSize.x / fullScreenSize.y) || shipDeltaY < 1.0) {
parentNode.scale = fullScreenSize.x / (shipDeltaX + shipA.contentSize.width);
}
else { //else: bound by y-axis or deltaX is negligible
parentNode.scale = fullScreenSize.y / (shipDeltaY + shipA.contentSize.height);
}
//calculate new midpoint between ships AND apply new scale to it
CGPoint scaledMidpoint = ccpMult(ccpMidpoint(shipA.position, shipB.position), parentNode.scale);
//update parent node position (move it into view of screen) to scaledMidpoint
parentNode.position = ccpSub(halfScreenSize, scaledMidpoint);
}
Also, I'm not sure how well it'll perform with a bunch of stuff going on -- but thats a separate problem!
Why don't you move the entire view, & position it so the ship is in the centre of the screen? I haven't tried it with your example, but it should be straight forward. Maybe something like this -
CGFloat x = (enemySpaceShipPosition.x - mainSpaceShipPosition.x) / 2.0 - screenCentreX;
CGFloat y = (enemySpaceShipPosition.y - mainSpaceShipPosition.y) / 2.0 - screenCentreY;
CGPoint midPointForContentOffset = CGPointMake(-x, -y);
[self setContentOffset:midPointForContentOffset];
...where you've already set up screenCentreX & Y. I haven't used UISCrollView for quite a while (been working on something in Unity so I'm forgetting all by Obj-C), & I can't remember how the contentOffset is affected by zoom level. Try it & see! (I'm assuming you're using a UIScrollView, maybe you could try that too if you're not)

Drawing text with CGContextShowGlyphsAtPoint - how to get font dimensions?

Hoping this is fairly simple. Basically, I'm drawing some text onto an NSView and need to know the "physical" height and width of the glyphs being drawn.
The font is fixed-width so I don't need to worry about kerning, all I need to do is ensure each glyph is centred horizontally and vertically within its "space"
This isn't the actual code but I've re-written it in a way that should make it easier to understand what I'm trying to achieve.
CGFontRef myFontRef = CGFontCreateWithDataProvider(myFontDataProvider);
CGContextSetFont(thisContext, myFontRef);
CGFloat fontSize = 1.0; //will be changeable later
CGContextSetFontSize(thisContext, fontSize);
CGRect charBounds = CGRectMake(x, y, widthPerChar, heightPerChar);
//paint charBounds with background colour
CGFloat textX = x + 2;
CGFloat textY = y + 5;
CGContextShowGlyphsAtPoint(thisContext, textX, textY, (CGGlyph *)displayedString, 1);
If I was able to calculate the width of the displayed glyph it would be easy to work out what textX and textY should be.
Thanks in advance to anybody willing to assist.
Partly worked it out from this code - here's what I came up with:
CGContextSetTextDrawingMode(thisContext, kCGTextInvisible);
CGContextShowGlyphsAtPoint(thisContext, 1, 1, (const CGGlyph *)"x", 1);
CGPoint postPosition = CGContextGetTextPosition(thisContext);
CGFloat textOffsetX = ((postPosition.x - 1) / 2);
CGFloat textOffsetY = (fontSize * 5);
CGContextSetTextDrawingMode(thisContext, kCGTextFillStroke);
Not a huge fan of doing it like this but it's working for now. At least it can be calculated once for each window resize which isn't too bad.
Thanks for the help though!
You can calculate the width of single glyph in this way:
CGRect units;
CGFontGetGlyphBBoxes(font, &glyph, 1, &units);
float width = units.size.width/CGFontGetUnitsPerEm(font)*fontSize;