I have UIImageView (ball) and it moves around the screen but I want it to bounce in a random direction off a specific image (named computer.paddle)
This is my code:
if (CGRectIntersectsRect (ball.frame, computerPaddle.frame)) {
if (ball.center.x < computerPaddle.center.x) {
(ballVelocity.x =- ballVelocity.x);
}
}
NB I am using the rotation landscape right and I want it to bounce of the right side of the paddle to the right.
I am happy to make any UIIntegers or any thing else that is needed.
Add the following line in the inner block:
ballVelocity.y += arc4random() % (2 * MAX_VARIANCE) - MAX_VARIANCE;
MAX_VARIANCE, in this case, is the maximum amount of variation you want from the "normal" value of ballVelocity.y. If you need a finer degree of variation, you can increase MAX_VARIANCE by some double k and multiply the expression by 1/k. For example,
ballVelocity.y += (arc4random() % (2*100*MAX_VARIANCE) - 100*MAX_VARIANCE)*0.01;
Related
So I am developing a game using Spritekit that uses a pyramid of Sprites (let's say circles for a simple instance). The user can choose the amount of rows of sprites they would like to have in the game. The sprites are to form a pyramid, so if you have 1 row, you have 1 sprite node. It increases by 2 the farther down you go (the more rows you choose) - creating the pyramid shape. So if a user picked 3 rows, the game board would look like this:
O
O O O
O O O O O
However, when it gets to 5 rows, it loses its pyramid shape because the screen is only so wide and it has to fit all the elements onto the screen (elements are more smushed together in rows further down).
My question is, to fix this issue, what would I have to do to make the pyramid resize and change its spacing between elements depending on how many rows are chosen? Would I have to multiply the spacing by a certain factor? I have also heard of people adding layers onto the screen - maybe drawing the sprites in some sort of container so that it always resizes the pyramid to fit the screen without skewing the pyramid shape?
Your idea is correct! Make a SKNode container, then update it's .size property, or do .setScale.
(not at xcode right now, pardon if not 100%)
// Say that our scene's size is 400x400:
let bkg = SKShapeNode(rectangleOfSize: self.size)
bkg.addChild(firstSprite)
bkg.addChild(secondSprite) // And so on...
// Find the farthest point in bkg:
var farthestX = CGFloat(0)
for node in bkg.children {
if node.position.x + node.frame.size.width / 2 > farthestX {
farthestX = node.position.x + node.frame.size.width / 2
}
}
// Do math to resize the bkg:
if self.size.width < farthestX {
let scaler = self.size.width / farthestX
bkg.setScale(scaler)
}
This should work, or at least the general idea should work... You would want to check for Y values and Height as well.
You can easily compute a symmetrical size proportional to the number of rows and resize your sprites accordingly. This is my idea in pseudocode:
let computedSize = deviceWidth/(2*(rows-1) + 1)
for sprite in sprites {
sprite.size.width = computedSize
sprite.size.height = computedSize
}
I have a camera in the center of SceneKit scene at position(0,0,0)
I'm rotation the camera around the y-axis to show different parts of the scene.
I want to show arrows at the left/right side of my screen to show in which direction the user has to move to view a target object.
How can I calculate if a ScnNode is positioned at the left of right side of my view direction?
I know:
position and rotation of the camera node
position of the target node
Calculate the vector to the node relative to the camera. Calculate the vector by subtracting the camera position from the node position
Use atan2 to convert the vector into an angle. The Apple docs are bit thin on explanation. There's a tutorial on raywenderlich.com.
E.g.
let v = CGPoint(x: nodePosition.x - cameraPosition.x, y: nodePosition.y - cameraPosition.y);
let a = atan2(v.y, v.x) // Note: order of arguments
The angle is in radians, and will range between -π and π (-180 to 180 degrees). If the value is negative the node is to the left of the camera vector, positive values mean the node is to the right, and zero means the node is straight ahead.
Conveniently, you can use this angle as-is to actually perform the rotation if you want. Just increment the camera angle by some fraction of the angle to the node.
E.g.
cameraRotation += a * 0.1
Caveats:
"Forward" or "Up" is not necessarily going to be the same as your internal coordinate system. Usually 0 is to the right, so if you want to show an arrow or other UI you may need to add or subtract an amount from the angle (usually -(π/2) to rotate a quarter turn).
The angle will always only be between -π and +π. If the node happens to fall directly behind the camera (exactly 180 degrees) you will get either -π or +π, and you might see the angle jump between these two extremes as the node cross over this discontinuity. You sometimes see the side effects of this in games where a nav arrow will point to a target then suddenly flick to the opposite side of the screen. There are various ways to handle this. One easy way is to use a low pass filter on the angle (ie accumulate multiple samples over time).
There is an easier way with projectPoint funcion witch show the image as 2d element in your view and based on where it is you can locate where the user should look
func getIfHeIsLookingNew(sceneWidth: CGFloat, nodePosition: SCNVector3){
if(nodePosition.z < 1){
if(nodePosition.x > (Float(sceneWidth))){
print("Look Right")
}else if(nodePosition.x < 0){
print("Look Left")
}else{
print("Correct")
return
}
}else if(nodePosition.x < 0){
print("Look Right")
}else{
print("Look Left")
}
}
And use it like below
let sceneWidth:CGFloat = self.sceneView.frame.width
getIfHeIsLookingNew(sceneWidth: sceneWidth, nodePosition: self.sceneView.projectPoint(targetNode.position))
This is language-agnostic question, more about model of my game.
I have a snake game with elements, but I move the elements smoothly, they don't just move 1 block each time, but instead they move some amount of pixels every frame.
I have an update loop that calculates the positions of the element, but I am stuck on correct calculations.
I have heading for each element:
typedef NS_ENUM(int, kElementHeading)
{
kElementHeadingNorth = 1,
kElementHeadingSouth,
kElementHeadingEast,
kElementHeadingWest
};
I also have velocity (x, y) that determines in what direction snake is going. I have problem with snake movement, because my elements are in wrong positions. I managed to localize the thing for 2 elements, but my solution fails on more elements.
First solution I tried is to save point of rotation where the head changes direction. This worked, but due to different circumstances element can move different amount of pixels each turn. Often the element would skip the point. I tried increasing the zone where it should rotate, but it adds up error. I tried fixing this error, but element would still separate from snake (quite often).
On the second try I decided to keep the snake head in center of the screen and move the world around it. It worked good for 2 elements, as I just smoothly move the next element to desired position relatively to head. But this fails badly on more elements. If you make fast turns they start dancing and not following the path.
Third thing that I tried is leaving a path for other elements to follow. But that didn't work because I intend to keep my snake on center of the screen and technically it never moves to create a path.
I'm looking to replicate the movement pattern like in Nimble Quest (or any snake).
How should I implement snake elements moving to have no errors?
Here is my code for the first method, problem with it is that often the elements would fall off. The code is pretty self-explanatory. Rotation points are the places where to change direction.
CFTimeInterval delta = self.lastTime - currentTime;
CGPoint currentPosition = self.playerSnake.head.sprite.position;
CGPoint velocity = self.playerSnake.velocity;
self.playerSnake.head.sprite.position = CGPointMake(currentPosition.x + velocity.x * delta * CONSTANTSPEEDFACTOR , currentPosition.y + velocity.y * delta * CONSTANTSPEEDFACTOR);
for (SnakeElement *element in self.playerSnake.elements) {
CGPoint currentPositionE = element.sprite.position;
CGPoint velocityE = element.velocity;
element.sprite.position = CGPointMake(currentPositionE.x + velocityE.x * delta * CONSTANTSPEEDFACTOR , currentPositionE.y + velocityE.y * delta * CONSTANTSPEEDFACTOR);
}
BOOL markToDelete = NO;
NSDictionary *deleteDictionary;
for (NSDictionary *dict in self.playerSnake.rotationPoints) {
CGPoint positionCoordinate = CGPointFromString(dict[#"position"]);
CGPoint velocityNew = CGPointFromString(dict[#"velocity"]);
double newAngle = [dict[#"angle"] doubleValue];
for (SnakeElement *element in self.playerSnake.elements) {
int xDifference = element.sprite.position.x - positionCoordinate.x;
int yDifference = element.sprite.position.y - positionCoordinate.y;
if ((xDifference > -2 && xDifference < 2) && (yDifference > -2 && yDifference < 2) ) {
element.velocity = velocityNew;
element.sprite.position = CGPointMake(element.sprite.position.x + xDifference, element.sprite.position.y + yDifference);
SKAction *action = [SKAction rotateToAngle:newAngle duration:0.2 shortestUnitArc:YES];
[element.sprite runAction:action];
if ([element isEqual:[self.playerSnake.elements lastObject]]) {
markToDelete = YES;
deleteDictionary = dict;
}
}
}
}
[self.playerSnake.rotationPoints removeObject:deleteDictionary];
If I try increase the catch zone for the turning point, the elements tend to fall off more often then when it is 1 or 2 pixels wide. I'm not sure why this happens.
This is what I was suggesting you do in the comments in terms of handling your turning on points :
1.. calculate the distance that the element should move that frame based on speed and your elapsed time since last frame. (delta)
2.. calculate distance from element's current position to the turn point. This is the beforeDistance I spoke of in the comments
3.. calculate the distance the element should move towards the NEW target turning point AFTER the turn
afterDistance = distanceToMoveThisFrame - beforeDistance
4.. Calculate the new position for your element, starting at the current turning point towards the next target turning point of the element using afterDistance
If you follow this logic, you will NEVER overshoot or undershoot the turning point.
I'm making an app that (among other things) displays a simplified compass image that rotates according to the device's rotation. The problem is that simply doing this:
float heading = -1.0f * M_PI * trueHeading / 180.0f; //trueHeading is always between 0 and 359, never 360
self.compassNeedle.transform = CGAffineTransformMakeRotation(heading);
inside CLLocationManager's didUpdateHeading method makes the animation ugly and choppy.
I have already used Instruments to find out whether its simply my app not being able to render at more than 30-48 fps, but that's not the case.
How can I smooth out the image view's rotation so that it's more like Apple's own Compass app?
Instead of using the current instant value, try using the average of the last N values for the true heading. The value may be jumping around a lot in a single instant but settle down "in the average".
Assuming you have a member variable storedReadings which is an NSMutableArray:
-(void)addReading(float):newReading
{
[storedReadings addObject:[NSNumber numberWithFloat:newReading]];
while([storedReadings count] > MAX_READINGS)
{
[storedReadings removeObjectAtIndex:0];
}
}
then when you need the average value (timer update?)
-(float)calcReading
{
float result = 0.0f;
if([storedReadings count] > 0)
{
foreach(NSNumber* reading in storedReadings)
{
result += [reading floatValue];
}
result /= [storedReadings count];
}
return result;
}
You get to pick MAX_READINGS a priori.
NEXT LEVEL(S) UP
If the readings are not jumping around so much but the animation is still choppy, you probably need to do something like a "smooth" rotation. At any given time, you have the current angle you are displaying, theta (store this in your class, start it out at 0). You also have your target angle, call it target. This is the value you get from the smoothed calcReading function. The error is defined as the difference between the two:
error = target-theta;
Set up a timer callback with a period of something like 0.05 seconds (20x per second). What you want to do is adjust theta so that the error is driven towards 0. You can do this in a couple of ways:
thetaNext += kProp * (target - theta); //This is proportional feedback.
thetaNext += kStep * sign(target-theta); // This moves theta a fixed amount each update. sign(x) = +1 if x >= 0 and -1 if x < 0.
The first solution will cause the rotation to change sharply the further it is from the target. It will also probably oscillate a little bit as it swings past the "zero" point. Bigger values of kProp will yield faster response but also more oscillation. Some tuning will be required.
The second solution will be much easier to control...it just "ticks" the compass needle around each time. You can set kStep to something like 1/4 degree, which gives you a "speed" of rotation of about (1/4 deg/update) * (20 updates/seconds) = 5 degrees per second. This is a bit slow, but you can see the math and change kStep to suit your needs. Note that you may to "band" the "error" value so that no action is taken if the error < kStep (or something like that). This prevents your compass from shifting when the angle is really close to the target. You can change kStep when the error is small so that it "slides" into the ending position (i.e. kStep is smaller when the error is small).
For dealing with Angle Issues (wrap around), I "normalize" the angle so it is always within -Pi/Pi. I don't guarantee this is the perfect way to do it, but it seems to get the job done:
// Takes an angle greater than +/- M_PI and converts it back
// to +/- M_PI. Useful in Box2D where angles continuously
// increase/decrease.
static inline float32 AdjustAngle(float32 angleRads)
{
if(angleRads > M_PI)
{
while(angleRads > M_PI)
{
angleRads -= 2*M_PI;
}
}
else if(angleRads < -M_PI)
{
while(angleRads < -M_PI)
{
angleRads += 2*M_PI;
}
}
return angleRads;
}
By doing it this way, -pi is the angle you reach from going in either direction as you continue to rotate left/right. That is to say, there is not a discontinuity in the number going from say 0 to 359 degrees.
SO PUTTING THIS ALL TOGETHER
static inline float Sign(float value)
{
if(value >= 0)
return 1.0f;
return -1.0f;
}
//#define ROTATION_OPTION_1
//#define ROTATION_OPTION_2
#define ROTATION_OPTION_3
-(void)updateArrow
{
// Calculate the angle to the player
CGPoint toPlayer = ccpSub(self.player.position,self.arrow.position);
// Calculate the angle of this...Note there are some inversions
// and the actual image is rotated 90 degrees so I had to offset it
// a bit.
float angleToPlayerRads = -atan2f(toPlayer.y, toPlayer.x);
angleToPlayerRads = AdjustAngle(angleToPlayerRads);
// This is the angle we "wish" the arrow would be pointing.
float targetAngle = CC_RADIANS_TO_DEGREES(angleToPlayerRads)+90;
float errorAngle = targetAngle-self.arrow.rotation;
CCLOG(#"Error Angle = %f",errorAngle);
#ifdef ROTATION_OPTION_1
// In this option, we just set the angle of the rotated sprite directly.
self.arrow.rotation = CC_RADIANS_TO_DEGREES(angleToPlayerRads)+90;
#endif
#ifdef ROTATION_OPTION_2
// In this option, we apply proportional feedback to the angle
// difference.
const float kProp = 0.05f;
self.arrow.rotation += kProp * (errorAngle);
#endif
#ifdef ROTATION_OPTION_3
// The step to take each update in degrees.
const float kStep = 4.0f;
// NOTE: Without the "if(fabs(...)) check, the angle
// can "dither" around the zero point when it is very close.
if(fabs(errorAngle) > kStep)
{
self.arrow.rotation += Sign(errorAngle)*kStep;
}
#endif
}
I put this code into a demo program I had written for Cocos2d. It shows a character (big box) being chased by some monsters (smaller boxes) and has an arrow in the center that always points towards the character. The updateArrow call is made on a timer tick (the update(dt) function) regularly. The player's position on the screen is set by the user tapping on the screen and the angle is based on the vector from the arrow to the player. In the function, I show all three options for setting the angle of the arrow:
Option 1
Just set it based on where the player is (i.e. just set it).
Option 2
Use proportional feedback to adjust the arrow's angle each time step.
Option 3
Step the angle of the arrow each timestep a little bit if the error angle is more than the step size.
Here is a picture showing roughly what it looks like:
And, all the code is available here on github. Just look in the HelloWorldLayer.m file.
Was this helpful?
I've been reading about AABB collision. I have a pretty rough implementation of it at the moment. I am now able to detect when two rectangles intersect on both the X and Y axis.
To calculate the depth of intersection on X, I basically do:
overlapX = (Game1.p[0].boundingBox.halfWidth + Game1.p[1].boundingBox.halfWidth)
- (Math.Abs(Game1.p[0].boundingBox.center.X - Game1.p[1].boundingBox.center.X));
Right now I am just testing the depth on X as I want to see where the problem is. I am not taking the direction, left or right, into account. I am just assuming the p[0] object is colliding on the left side of p[1].
Then for the actual movement and collision detection:
public void Move(float x, float y)
{
Position.X += x;
Position.Y += y;
overlapX = (Game1.p[0].boundingBox.halfWidth
+ Game1.p[1].boundingBox.halfWidth)
- (Math.Abs(Game1.p[0].boundingBox.center.X
- Game1.p[1].boundingBox.center.X));
overlapY = (Game1.p[0].boundingBox.halfHeight
+ Game1.p[1].boundingBox.halfHeight)
- (Game1.p[1].boundingBox.center.Y
- Game1.p[0].boundingBox.center.Y);
if (Collision.testAABBAABBX(
Game1.p[0].boundingBox, Game1.p[1].boundingBox) &&
Collision.testAABBAABBY(
Game1.p[0].boundingBox, Game1.p[1].boundingBox))
{
if (overlapX < overlapY) Position.X -= overlapX;
}
}
Now when I collide with p[1], overlapX reads 1. So position.X should -= 1. However, here is where the problem begins. Here is an image during the overlap. You can see that there is indeed an overlap shown by a vertical yellow line:
So my problem is that when I instruct the position.X to move back by the overlap value, it does not, at least not until I move the rectangle either up or down. If I do that, it magically corrects itself:
Any clues on what's going on? Maybe it's right in front of me and I'm not seeing it, but the response only seems to trigger after the next movement.
Let me know if more info is needed.
This happens because your collision resolution code is placed within your motion code, which doesn't seem to run unless you make a move.
The solution is to move your collision resolution code outside your Move method into a separate method that is called by Update every frame.