I'm working in Spritekit and I'm pretty new to it. I'm creating a game with levels and for each level I'm trying to position the sprites in my game to be proportionate to the screen size. I'm calling their positions from plist files. Since I am positioning each sprite according to the screen's dimensions and size, I would have to position it something like this
Sprite.position = CGPointMake(self.scene.size.width /2, self.scene.size.height /2);
My question is, how would I be able to convert this position from my plist? I tried using
Sprite.position = CGPointFromString(level[#"Sprite"]);
and typing
CGPointMake(self.scene.size.width /2, self.scene.size.height /2);
in my plist file, but it's not working.
This is how you access data from a plist:
NSString* path = [[ NSBundle mainBundle] bundlePath];
NSString* finalPath = [ path stringByAppendingPathComponent:#"MyGameData.plist"];
NSDictionary *plistData = [NSDictionary dictionaryWithContentsOfFile:finalPath];
The simplest way would probably be to store the x and y positions in 2 separate strings. Something like PlayerPositionX and PlayerPositionY
Then you convert the string to a float like this:
float myFloat = [myString floatValue];
You can also convert a single string into a CGPoint like this:
CGPoint myPoint = CGPointFromString([NSString stringWithFormat:#"{%#}",textField.text]);
The string has to be in the format {x,y}
(Credit to jrturton for his answer here).
You could solve the problem of positioning your sprites proportionate to the screen size in the following way:
First you create your plist like this:
Sprite = {
x = 20;
y = 20;
}
Then you can obviously get the values with:
NSDictionary *spriteInfo = plist[#"Sprite"];
NSNumber *xPosition = spriteInfo[#"x"];
NSNumber *yPosition = spriteInfo[#"y"];
Now you can interpret these values in two ways:
They represent the position your sprite should be on a default screen (ex: 320x568) and you get the actual position with:
actionPosition = xPosition * screenSize / defaultScreenSize;
//ex: 40 = 20 * 640 / 320, if the current device has a width of 640.
Or the represent % values, aka the sprite should be at 20% of the screen, thus you'd get the actual position with:
actionPosition = screenSize * xPosition / 100;
//ex: 128 = 20 * 640 / 100, if the current device has a width of 640.
Related
Progress so far:
So what I have at the moment is this:
(the green point represents the parent "BlankNode, adding children then rotating them around that node,
Im a bit stick how to get it work properly, for some reason they dont sit next to eachother but opposite (as showen in http://i.stack.imgur.com/w7QvS.png)
inGameLevel
myArc = [[Arcs alloc]initWithArcCount:myAmmountOfSprites];
[self addChild:myArc];
My wish is for the sprite.rotation to be slightly offset from the next loaded...here they are split...
(The diagram belows showing the arc shape I would like to load the sprites in)
**With one stick loaded, maybe its easier to spot the mistake
(if I load a second sprite it loads directly opposite to the previous and not at the expected angle incremented
In this version I have just loaded the stick and blanknode, positioned it using anchor points, Im confused how the rotation works... **
SKSpriteNode *blank = [[SKSpriteNode alloc]
///like the otherone
blank.zRotation=0;
blank.anchorPoint = CGPointMake(0.5, 0.5);
[self addChild:blank];
//set to 0 value so I can see what its natural state is (it is vertical and above the parent node)
//but this value will be incremented each time a new sprite is added
int rotationAmount = 0;
Rotation = Rotation-rotationAmount; //will increment
objectPic = [SKSpriteNode spriteNode....as normal
//use blank nodes anchorpoint
objectPic.anchorPoint = blank.anchorPoint;
//Rotation
objectPic.zRotation = Rotation;
float moveUp_donut = 0.3;
//"moveUp_donut" moving this value up moves the stick up
//and outward from the center
objectPic.anchorPoint =
CGPointMake(0.0,-moveUp_donut); //(0.0,-moveOutward);
[blank addChild:objectPic];
}
}
I have made an xcode project available for anyone interested to have a look at the problem, hopefully you can explain how to get the rotation working correctly.
at the moment it is just loading one sprite, so you might need to play with the setting,
myArc = [[Arcs alloc]initWithArcCount:addLotsOfSticks];
//and play with the rotation ammount
int rotationAmount = 3;
http://www.filedropper.com/rotationtest
Solution Found! see below:
🌸
A huge thanks to WangYudong for giving such a great answer!
I made a sample project and hope it can help. The algorithm is not base on your project, so make some change to fit your need.
Firstly, add a blank node to the middle of the scene:
self.blank = [[SKSpriteNode alloc] initWithColor:[SKColor greenColor]size:CGSizeMake(20, 20)];
self.blank.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:self.blank];
Then, create the stick:
- (SKSpriteNode *)newStick
{
SKSpriteNode *stick = [[SKSpriteNode alloc] initWithColor:[SKColor redColor]size:CGSizeMake(5, 100)];
return stick;
}
And given the amount of sticks, the radius (of the inner circle), the starting radian and ending radian, add a method:
- (void)loadStickArcWithStickAmount:(NSUInteger)amount radius:(CGFloat)radius startRadians:(CGFloat)startRad endRadians:(CGFloat)endRad
{
for (NSUInteger index = 0; index < amount; index++) {
SKSpriteNode *stick = [self newStick];
CGFloat halfStickLength = stick.size.height / 2;
CGFloat rotateRad = startRad + (endRad - startRad) / (amount - 1) * index;
stick.zRotation = M_PI_2 + rotateRad;
stick.position = CGPointMake((radius + halfStickLength) * cos(rotateRad),
(radius + halfStickLength) * sin(rotateRad));
[self.blank addChild:stick];
}
}
Some hints:
rotateRad divides radians of endRad - startRad.
M_PI_2 is an offset of zRotation.
Trigonometric maths calculates the position of sticks.
Both anchor points of blank node and stick remain default (0.5, 0.5).
Use the method:
[self loadStickArcWithStickAmount:27 radius:50.0 startRadians:M_PI endRadians:2*M_PI];
to achieve the following result:
I'm making a game that doesn't use Autolayout, and have used constraints to scale everything and it worked perfectly. However I cant seem to figure this out, I'm using arc4random to randomly position X and Y coordinates of a UIButton on the 4.7 inch screen. When I try running it on the smaller screen it plots the UIButton off the screen at times. How can I scale arc4random up and down depending on screen size.
-(void)position{
Randompositionx = arc4random() %270;
Randompositionx = Randompositionx + 51;
Randompositiony = arc4random() %411;
Randompositiony = Randompositiony +163;
UIButton.center = CGPointMake(Randompositionx, Randompositiony);
}
You should be using arc4random_uniform rather than arc4random and the modulo operator. It gives numbers without "modulo bias".
You need to adapt the upper value of your random number based on screen size. T_77's answer was a step in the right direction, but still not right.
The code below assumes the button you want to move is called myButton.
It uses margin values of 20 all around. Change the values as desired.
EDIT: I updated it to use the height and width of the button in the position calculation. With the updated code the button should never be closer to any edge than the margin value, even if the button size changes. No magic numbers, either. It should adapt to the size of it's superview.
-(void)position
{
CGRect frame = myButton.superview.bounds;
CGFloat leftMargin = 20; //use whatever values you want for magins
CGFloat rightMargin = 20;
CGFloat topMargin = 20;
CGFloat bottomMargin = 20;
CGFloat randomX, randomY;
CGFloat xMax = frame.size.width-leftMargin-rightMargin-
button.bounds.size.width/2;
randomX = arc4random_uniform(xMax) + leftMargin;
CGFloat yMax = frame.size.height-topMargin-bottomMargin-
button.bounds.size.height/2;
randomY = arc4random_uniform(yMax) + topMargin;
myButton.center = CGPointMake(randomX, randomY);
}
Also note that if you're using auto layout you shouldn't move the button's position directly, and instead should have position constraints and modify their constants, then call layoutIfNeeded. Otherwise the first thing that causes your layout to change will cause your button to revert to it's previous position.
Try this
-(void)position{
CGRect frame = [self.view frame];
Randompositionx = arc4random() %270;
Randompositionx = Randompositionx + CGRectGetMinX(frame);
Randompositiony = arc4random() %411;
Randompositiony = Randompositiony +CGRectGetMinY(frame);
UIButton.center = CGPointMake(Randompositionx, Randompositiony);
}
Now you can position your UIButton within your view.
I've been trying to get my MKMapView to detect whether or not a tap was on a tile with alpha > 0. I'm quite new at ObjC and Xcode as well so this functionality is a bit over my head. All help will me greatly appreciated!
So far I've tried many different strategies but always come up short. We have custom classes to replace MKOverlay and MKOverlayView that implement each respectively so I've been trying to grab the tiles when they're created and save them to an array to later reference in the MKMapViewController when the map is touched.
- (NSArray *)tilesInMapRect:(MKMapRect)rect zoomScale:(MKZoomScale)scale
{
NSInteger z = zoomScaleToZoomLevel(scale);
// Number of tiles wide or high (but not wide * high)
NSInteger tilesAtZ = pow(2, z);
NSInteger minX = floor((MKMapRectGetMinX(rect) * scale) / TILE_SIZE);
NSInteger maxX = floor((MKMapRectGetMaxX(rect) * scale) / TILE_SIZE);
NSInteger minY = floor((MKMapRectGetMinY(rect) * scale) / TILE_SIZE);
NSInteger maxY = floor((MKMapRectGetMaxY(rect) * scale) / TILE_SIZE);
NSMutableArray *tiles = nil;
for (NSInteger x = minX; x <= maxX; x++) {
for (NSInteger y = minY; y <= maxY; y++) {
// As in initWithTilePath, need to flip y index to match the gdal2tiles.py convention.
NSInteger flippedY = abs(y + 1 - tilesAtZ);
NSString *tileKey = [[NSString alloc] initWithFormat:#"%d/%d/%d", z, x, flippedY];
if ([tilePaths containsObject:tileKey]) {
if (!tiles) {
tiles = [NSMutableArray array];
}
MKMapRect frame = MKMapRectMake((double)(x * TILE_SIZE) / scale,
(double)(y * TILE_SIZE) / scale,
TILE_SIZE / scale,
TILE_SIZE / scale);
NSString *path = [[NSString alloc] initWithFormat:#"%#/%#.png", tileBase, tileKey];
ImageTile *tile = [[ImageTile alloc] initWithFrame:frame path:path];
[tiles addObject:tile];
[myTiles addObject:tile];
[path release];
[tile release];
}
[tileKey release];
}
}
return tiles;
}
That's where I populate the array which is a "class variable". If I comment out the [tiles addObject:tile]; I get the background of the map drawn but no buildings so I think adding specifically those tiles is correct.
Then in the mapviewController gesture handler function I check if the touch is in the tile.frame which is is for 8 out of 32 (it can be 0 if you click far from the buildings and the total changes when you zoom around, but always gets bigger)which seems like an odd number. But pretending that that works correctly I check the alpha at that point using a modified version of this answerer's function: how to get the RGBA value of UIImage in the specific clicked point
but I don't know if that works for mapView's like it would for imageViews. I think I might need to translate the context but I've never worked with contexts before...
Sorry for so much text! Maybe this isn't even possible? I'll add more code if clarification is needed. Any input would help!
Is there a way to rotate a Node in SpriteKit around an arbitrary point?
I now I can manipulate the anchorPoint of my Node, but that is not sufficient if the rotation point I want to use lies outside of the Node.
What is the best way to achieve this kind of rotation in SpriteKit?
Since you're asking for the best way, here's one that works well (best is subjective):
Create an SKNode and set its position to the center of rotation. Add the node that should rotate around that center as child to the center node. Set the child node's position to the desired offset (ie radius, say x + 100). Change the rotation property of the center node to make the child node(s) rotate around the center point. The same works for cocos2d btw.
I was also trying to solve this problem a few weeks back, and did not implement the anchor points solution because I did not want to have to worry about removing the anchor point when lets say the object collides with another node and should leave its orbit and bounce away.
Instead, I came up with two solutions, both of which work if tweaked. The first took a long time to perfect, and is still not perfect. It involves calculating a certain number of points around a center position offset by a set radius, and then if a certain object comes in a certain distance of the center point, it will continually use physics to send the object on a trajectory path along the "circumference" of the circle, points that it calculated (see above).
There are two ways of calculating points with a radius
The first uses the pythagorean theorem, and the second ultimately uses trigonometry proper.
In the first, you increment a for loop by a certain amount, while it is less that 361 (degree), and for each iteration of the loop, calculate using sine and cosine a point with that angle at a certain radius from the center point.
The second uses the pythagorean theorem, and its code is below:
After you calculate points, you should create a scheduled selector [<object> scheduled selector...]; or a timer in your didMoveToView, or use a fixed update method, in addition to an instance variable called int which will hold the index of the next location to which your object will move. Every time the timer method is called, it will move the object to the next point in your calculate points array using your own or the below code labeled physicsMovement; You can play around with the physics values, and even the frequency of the ttimer for different movement effects. Just make sure that you are getting the index right.
Also, for more realism, I used a method which calculates the closest point in the array of calculated point to the object, which is called only once the collision begins. It is also below labeled nearestPointGoTo.
If you need any more help, just say so in the comments.
Keep Hacking!
I used the second, and here is the source code for it:
The code itself didn't go through
Second point calculation option
+(NSArray *)calculatePoints:(CGPoint)point withRadius:(CGFloat)radius numberOfPoints: (int)numberOfPoints{ //or sprite kit equivalent thereof
// [drawNode clear];
NSMutableArray *points = [[NSMutableArray alloc]init];
for (int j = 1; j < 5; j++) {
float currentDistance;
float myRadius = radius;
float xAdd;
float yAdd;
int xMultiplier;
int yMultiplier;
CCColor *color = [[CCColor alloc]init]; //Will be used later to draw the position of the node, for debugging only
for (int i = 0; i < numberOfPoints; i += 1){
//You also have to change the if (indextogoto == <value>) in the moveGumliMethod;
float opposite = sqrtf( powf(myRadius, 2) - powf(currentDistance, 2) );
currentDistance = i;
switch (j) {
case 1:
xMultiplier = 1;
yMultiplier = 1;
xAdd = currentDistance;
yAdd = opposite;
color = [CCColor blueColor];
break;
case 2:
xMultiplier = 1;
yMultiplier = -1;
xAdd = opposite;
yAdd = currentDistance;
color = [CCColor orangeColor];
break;
case 3:
xMultiplier = -1;
yMultiplier = -1;
xAdd = currentDistance;
yAdd = opposite;
color = [CCColor redColor];
break;
case 4:
xMultiplier = -1;
yMultiplier = 1;
xAdd = opposite;
yAdd = currentDistance;
color = [CCColor purpleColor];
break;
default:
break;
}
int x = (CGFloat)(point.x + xAdd * xMultiplier); //or sprite kit equivalent thereof
int y = (CGFloat)(point.y + yAdd * yMultiplier); //or sprite kit equivalent thereof
CGPoint newPoint = CGPointMake((CGFloat)x,(CGFloat)y); //or sprite kit equivalent thereof
NSValue *pointWrapper = [NSValue valueWithCGPoint:newPoint]; //or sprite kit equivalent thereof
NSLog(#"Point is %#",pointWrapper);
[points addObject:pointWrapper];
}
}
return points;
}
Calculating Nearest Point To Object
-(CGPoint)calculateNearestGumliPoint:(CGPoint)search point { // MY Character is named Gumli
float closestDist = 2000;
CGPoint closestPt = ccp(0,0);
for (NSValue *point in points) {
CGPoint cgPoint = [point CGPointValue];
float dist = sqrt(pow( (cgPoint.x - searchpoint.x), 2) + pow( (cgPoint.y - searchpoint.y), 2));
if (dist < closestDist) {
closestDist = dist;
closestPt = cgPoint;
}
}
return closestPt;
}
I think the best way to make this work is through two SKNode and joint them with SKPhysicsJointPin (Look at the pin example below)
I tried to hang a door sign (SKSpriteNode) on my door(`SkScene), and would like to rotate around on the hanging spot when someone touch it
What I did is making a 1x1 SKNode with a HUGH mass and disabled it's gravity effects.
var doorSignAnchor = SKSpriteNode(color: myUIColor, size: CGSize(width: 1, height: 1))
doorSignAnchor.physicsBody = SKPhysicsBody(rectangleOf: doorSignAnchor.frame.size)
doorSignAnchor.physicsBody!.affectedByGravity = false // MAGIC PART
doorSignAnchor.physicsBody!.mass = 9999999999 // MAGIC PART
var doorSignNode = SKSpriteNode(imageNamed:"doorSign")
doorSignNode.physicsBody = SKPhysicsBody(rectangleOf: doorSignNode.frame.size)
and created a SKPhysicsJointPin to connect them all
let joint = SKPhysicsJointPin.joint(
withBodyA: doorSignAnchor.physicsBody!,
bodyB: doorSignNode.physicsBody!,
anchor: doorSignAnchor.position)
mySkScene.physicsWorld.add(joint)
So it will move like actual door sign, rotate around an arbitrary point (doorSignAnchor)
Reference:
Official document about Sumulating Physics
How to Make Hanging Chains With SpriteKit Physis Joints
I am experimenting with the Google Maps for iOS SDK latest version 1.2.1.2944 to animate a GMSGroundOverlay. The user has control over the image sequence, so using an animated UIImage isn't a possibility sadly, so i'm loading in the UIImage on the fly. The GMSGroundOverlay.icon is set to the UIImage that is being updated.
Aside from high memory usage, I seem to have struck a limitation in that whenever I try to overlay a UIImage using GMSGroundOverlay.icon that is more than 1000px x 1000px, it crashes. Referencing a UIImage of 1000px x 1000px gets around the crash.
It strikes me though that maybe I should utilise CATiledLayer for handling the image to only load into memory and subsequently into the icon property of GMSGroundOverlay, but has anyone had any experience of using CATiledLayer with Google Maps for iOS SDK and sequencing images as an animated GMSGroundOverlay?
I got this answer from pressinganswer.com, i think it may helps you.
As currently I cannot use the "position" keypath for animating, I ended up animating it using the "latitude" and "longitude" keypaths separately.
First calculate the points and add them to 2 separate arrays, one for latitude value (y) and one for longitude (x) and then use the values property in CAKeyFrameAnimation to animate. Create 2 CAKeyFrameAnimation objects (1 for each axis) and group them together using CAAnimationGroup and animate them together to form a circle.
In my equation I vary the length of the radius on each axis so that I can also generate an oval path.
NSMutableArray *latitudes = [NSMutableArray arrayWithCapacity:21];
NSMutableArray *longitudes = [NSMutableArray arrayWithCapacity:21];
for (int i = 0; i <= 20; i++) {
CGFloat radians = (float)i * ((2.0f * M_PI) / 20.0f);
// Calculate the x,y coordinate using the angle
CGFloat x = hDist * cosf(radians);
CGFloat y = vDist * sinf(radians);
// Calculate the real lat and lon using the
// current lat and lon as center points.
y = marker.position.latitude + y;
x = marker.position.longitude + x;
[longitudes addObject:[NSNumber numberWithFloat:x]];
[latitudes addObject:[NSNumber numberWithFloat:y]];
}
CAKeyframeAnimation *horizontalAnimation = [CAKeyframeAnimation animationWithKeyPath:#"longitude"];
horizontalAnimation.values = longitudes;
horizontalAnimation.duration = duration;
CAKeyframeAnimation *verticleAnimation = [CAKeyframeAnimation animationWithKeyPath:#"latitude"];
verticleAnimation.values = latitudes;
verticleAnimation.duration = duration;
CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
group.animations = #[horizontalAnimation, verticleAnimation];
group.duration = duration;
group.repeatCount = HUGE_VALF;
[marker.layer addAnimation:group forKey:[NSString stringWithFormat:#"circular-%#",marker.description]];