Add boundaries to an SKScene - objective-c

I'm trying to write a basic game using Apple's Sprite Kit framework. So far, I have a ship flying around the screen, using SKPhysicsBody. I want to keep the ship from flying off the screen, so I edited my update method to make the ship's velocity zero. This works most of the time, but every now and then, the ship will fly off the screen.
Here's my update method.
// const int X_MIN = 60;
// const int X_MAX = 853;
// const int Y_MAX = 660;
// const int Y_MIN = 60;
// const float SHIP_SPEED = 50.0;
- (void)update:(CFTimeInterval)currentTime {
if (self.keysPressed & DOWN_ARROW_PRESSED) {
if (self.ship.position.y > Y_MIN) {
[self.ship.physicsBody applyForce:CGVectorMake(0, -SHIP_SPEED)];
} else {
self.ship.physicsBody.velocity = CGVectorMake(self.ship.physicsBody.velocity.dx, 0);
}
}
if (self.keysPressed & UP_ARROW_PRESSED) {
if (self.ship.position.y < Y_MAX) {
[self.ship.physicsBody applyForce:CGVectorMake(0, SHIP_SPEED)];
} else {
self.ship.physicsBody.velocity = CGVectorMake(self.ship.physicsBody.velocity.dx, 0);
}
}
if (self.keysPressed & RIGHT_ARROW_PRESSED) {
if (self.ship.position.x < X_MAX) {
[self.ship.physicsBody applyForce:CGVectorMake(SHIP_SPEED, 0)];
} else {
self.ship.physicsBody.velocity = CGVectorMake(0, self.ship.physicsBody.velocity.dy);
}
}
if (self.keysPressed & LEFT_ARROW_PRESSED) {
if (self.ship.position.x > X_MIN) {
[self.ship.physicsBody applyForce:CGVectorMake(-SHIP_SPEED, 0)];
} else {
self.ship.physicsBody.velocity = CGVectorMake(0, self.ship.physicsBody.velocity.dy);
}
}
}
At first, I used applyImpulse in didBeginContact to push the ship back. This made the ship bounce, but I don't want the ship to bounce. I just want it to stop at the edge.
What is the right way to make the ship stop once it reaches the edge? The code above works most of the time, but every now and then the ship shoots off screen. This is for OS X—not iOS—in case that matters.

Check out this link...
iOS7 SKScene how to make a sprite bounce off the edge of the screen?
[self setPhysicsBody:[SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame]]; //Physics body of Scene
This should set up a barrier around the edge of your scene.
EDIT:
This example project from Apple might also be useful
https://developer.apple.com/library/mac/samplecode/SpriteKit_Physics_Collisions/Introduction/Intro.html

Your code is not clear in what the velocity variables represent. Keep in mind that if the velocity is too high your ship will have travelled multiple points between updates. For example, your ship's X/Y is at (500,500) at the current update. Given a high enough velocity, your ship could be at (500,700) at the very next update. If you had your boundary set at (500,650) your ship would already be past it.
I suggest you do a max check on velocity BEFORE applying it to your ship. This should avoid the problem I outlined above.
As for bouncy, bouncy... did you try setting your ship's self.physicsBody.restitution = 0; ? The restitution is the bounciness of the physics body. If you use your own screen boundaries, then I would recommend setting those to restitution = 0 as well.

Your best bet would be to add a rectangle physics body around the screen (boundary). Set the collision and contact categories of the boundary and player to interact with each other. In the didBeginContact method you can check if the bodies have touched and, if they have, you can call a method to redirect the ship.
Your problem is that your update method may not be checking the location frequently enough before the ship gets off screen.

This will help you to define you screen edges in Swift.
self.physicsBody = SKPhysicsBody ( edgeLoopFromRect: self.frame )

Related

Is the GML debugger showing me values for the wrong object/sprite?

When the player (the_guy_obj) collides with food (berry_obj) and hits enter, the berry plays an "eaten" animation and stops at the last frame.
At the same time, the player plays an "eating" animation, and, upon reaching the last frame, switches to a static sprite. The following is written in their collide events:
(under berry_obj)
hitEnter = keyboard_check_pressed(vk_enter);
if(hitEnter)
{
sprite_index = br_ani;
}
if(image_index == 4)
{
image_speed = 0;
}
(under the_guy_obj)
if (wasHit == true) {
if (lastPressed_L) //checks if facing left
{
sprite_index = eat_left;
}
if (lastPressed_R) //checks if facing right
{
sprite_index = eat_right;
}
if (lastPressed_D) //checks if facing down
{
sprite_index = down_eat;
}
if (lastPressed_U) //checks if facing up
{
sprite_index = up_eat;
}
}
While I'm debugging, "sprite_index" and "image_speed" return the same values in both collision events.
In the actual game, it works just fine, each object changes to the sprite I set it to.
What's going on? Is it reading these values as the same? Is it switching back and forth when different events are triggered?
If I recall correctly: sprite_index returns the ID of the sprite, and image_index returns the frame of the current sprite. where image_speed returns the value you've set at the image sprite on how much interval is inbetween the sprites.
Perhaps by coincidence, the numbers you were comparing matched each other?

Tile Collision In GML

I am making a game based on the game AZ on the website Y8, and I am having problems with tile collisions.
the player moves basically by giving it speed when up is pressed, then rotating left or right.
direction = image_angle;
if(keyForward)
{
speed = 2;
}
else speed = 0;
// rotate
if(keyRotateLeft)
{
image_angle = image_angle + 5;
}
if(keyRotateRight)
{
image_angle = image_angle - 5;
}
then I said when the player collides with the tile speed = 0. But the player gets stuck and can't move anymore. is there a better way to do this.
A simple approach would be as following:
Attempt to rotate
Check if you are now stuck in a wall
If you are, undo the rotation.
A more advanced approach would be to attempt pushing the player out of solids while rotating.
Alternatively, you may be able to get away with giving the player a circular mask and not rotating the actual mask (using a user-defined variable instead of image_angle).

Cesium: Having the camera in an entity's first person view

I would like to have my camera follow the first-person view of a moving entity. I do not believe that trackedEntity will work for this use case because I don't want to look at the entity, but I want to look out from it. I would also like the user to be able to use the mouse to turn the camera with respect to the moving entity (for example, to look out the left window of a moving plane).
In a traditional game engine, I would do this by attaching the camera to the entity, so it would move with it, but retain its own local transform with respect to the entity so that it was free to move with respect to the entity.
The only way I can think of right now is to keep track of the "user-controlled" transform separately and multiply it with the entity transform at every clock tick. Is there a better way?
Have a look at Cesium's Cardboard sandcastle example. Here you are on board of a hot-air balloon and perceive the world from there. After scrolling out, you can pan with the mouse to look around. Since the calculations are quite complicated, I cannot give any details how it works, but it seems that the camera view is aligned to the moving direction of the entity. The essential part of the script is:
// Set initial camera position and orientation to be when in the model's reference frame.
var camera = viewer.camera;
camera.position = new Cesium.Cartesian3(0.25, 0.0, 0.0);
camera.direction = new Cesium.Cartesian3(1.0, 0.0, 0.0);
camera.up = new Cesium.Cartesian3(0.0, 0.0, 1.0);
camera.right = new Cesium.Cartesian3(0.0, -1.0, 0.0);
viewer.scene.postUpdate.addEventListener(function (scene, time) {
var position = entity.position.getValue(time);
if (!Cesium.defined(position)) {
return;
}
var transform;
if (!Cesium.defined(entity.orientation)) {
transform = Cesium.Transforms.eastNorthUpToFixedFrame(position);
} else {
var orientation = entity.orientation.getValue(time);
if (!Cesium.defined(orientation)) {
return;
}
transform = Cesium.Matrix4.fromRotationTranslation(
Cesium.Matrix3.fromQuaternion(orientation),
position
);
}
// Save camera state
var offset = Cesium.Cartesian3.clone(camera.position);
var direction = Cesium.Cartesian3.clone(camera.direction);
var up = Cesium.Cartesian3.clone(camera.up);
// Set camera to be in model's reference frame.
camera.lookAtTransform(transform);
// Reset the camera state to the saved state so it appears fixed in the model's frame.
Cesium.Cartesian3.clone(offset, camera.position);
Cesium.Cartesian3.clone(direction, camera.direction);
Cesium.Cartesian3.clone(up, camera.up);
Cesium.Cartesian3.cross(direction, up, camera.right);
});
Maybe you can try to modify the camera vectors or multiply the transform with another rotation matrix to simulate turning one's head (to look left/right/back) while being in the initial perspective. For instance, you can try to combine the example above with code from a repository called Cesium First Person Camera Controller.
Had to figure this out myself as well.
Camera.setView and self-defined utility functions are your friend.
E.g. Here is a naive implementation of rotation (does not work well when the pitch of the camera is too "birds-eye-view" like):
Cesium.Camera.prototype.rotateView = function(rotation) {
let { heading, pitch, roll } = rotation;
heading = this.heading + (heading || 0);
pitch = this.pitch + (pitch || 0);
roll = this.roll + (roll || 0);
const destination = this.position;
this.setView({
destination,
orientation: {
heading,
pitch,
roll
}
});
};
Similarly you can update the position with the position of the entity by providing destination.

Preventing SKSpriteNode from going off screen

I have a SKSpriteNode that moves with the accelerometer by using the following code:
-(void)processUserMotionForUpdate:(NSTimeInterval)currentTime {
SKSpriteNode* ship = (SKSpriteNode*)[self childNodeWithName:#"fishderp"];
CMAccelerometerData* data = self.motionManager.accelerometerData;
if (fabs(data.acceleration.y) > 0.2) {
[gameFish.physicsBody applyForce:CGVectorMake(0, data.acceleration.y)];
}
}
This works well however, the node (gamefish) moves off the screen. How can I prevent this and have it stay on the screen?
Try using an SKConstraint which was designed exactly for this purpose and introduced in iOS8:
Just add this to the setup method of the gameFish node. The game engine will apply the constraint after the physics has run. You won't have to worry about it. Cool huh?
// get the screensize
CGSize scr = self.scene.frame.size;
// setup a position constraint
SKConstraint *c = [SKConstraint
positionX:[SKRange rangeWithLowerLimit:0 upperLimit:scr.width]
Y:[SKRange rangeWithLowerLimit:0 upperLimit:scr.width]];
gameFish.constraints = #[c]; // can take an array of constraints
The code depends on whether you have added the gameFish node to self or to another node (something like a "worldNode"). If you have added it to self, look at the code below:
// get the screen height as you are only changing your node's y
float myHeight = self.view.frame.size.height;
// next check your node's y coordinate against the screen y range
// and adjust y if required
if(gameFish.position.y > myHeight) {
gameFish.position = CGPointMake(gameFish.position.x, myHeight);
}
For the bottom you can do a check of < 0 or whatever value you need.

SpriteKit: How to simulate magnetic force

I am developing a game using iOS SpriteKit. I am trying to make an object in this game that will pull things towards it and the force will get greater as objects come closer to it, think of a magnet or a black hole. I've been having a difficult time figuring out what properties to change to get this nodes physicsBody to attract other nodes as they pass by.
In iOS 8 and OS X 10.10, SpriteKit has SKFieldNode for creating forces that apply to bodies in an area. This is great for things like buoyancy, area-specific gravity, and "magnets".
Watch out, though — the magneticField you get from that class is probably not what you want for the kind of "magnets" gameplay you might be looking for. A magnetic field behaves as per real-world physics at the micro level... that is, it deflects moving, charged bodies. What we usually think of as magnets — the kind that stick to your fridge, pick up junked cars, or make a hoverboard fly — is a higher-level effect of that force.
If you want a field that just attracts anything (or some specific things) nearby, a radialGravityField is what you're really after. (To attract only specific things, use the categoryBitMask on the field and the fieldBitMask on the bodies it should/shouldn't interact with.)
If you want a field that attracts different things more or less strongly, or attracts some things and repels others, the electricField is a good choice. You can use the charge property of physics bodies to make them attracted or repelled (negative or positive values) or more or less strongly affected (greater or less absolute value) by the field.
Prior to iOS 8 & OS X 10.10, SpriteKit's physics simulation doesn't include such kinds of force.
That doesn't keep you from simulating it yourself, though. In your scene's update: method you can find the distances between bodies, calculate a force on each proportional to that distance (and to whatever strength of magnetic field you're simulating), and apply forces to each body.
yes you can create magnetic force in sprite kit
-(void)didSimulatePhysics
{
[self updateCoin];
}
-(void) updateCoin
{
[self enumerateChildNodesWithName:#"coin" usingBlock:^(SKNode *node, BOOL *stop) {
CGPoint position;
position=node.position;
//move coin right to left
position.x -= 10;
node.position = position;
//apply the magnetic force between hero and coin
[self applyMagnetForce:coin];
if (node.position.x <- 100)
[node removeFromParent];
}];
}
-(void)applyMagnetForce:(sprite*)node
{
if( gameViewController.globalStoreClass.magnetStatus)
{
//if hero under area of magnetic force
if(node.position.x<400)
{
node.physicsBody.allowsRotation=FALSE;
///just for fun
node.physicsBody.linearDamping=10;
node.physicsBody.angularVelocity=10*10;
//where _gameHero is magnet fulling object
[node.physicsBody applyForce:CGVectorMake((10*10)*(_gameHero.position.x- node.position.x),(10*10)*(_gameHero.position.y-node.position.y)) atPoint:CGPointMake(_gameHero.position.x,_gameHero.position.y)];
}
}
}
remember both hero and coin body need be dynamic
Well seems like now you can since Apple introduced SKFieldNode in iOS 8.
Well you use the following code snippets to do what you're looking for, but it doesn't have attraction and repulsion properties
Body code:
let node = SKSpriteNode(imageNamed: "vortex")
node.name = "vortex"
node.position = position
node.run(SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat.pi, duration: 1)))
node.physicsBody = SKPhysicsBody(circleOfRadius: node.size.width / 2)
node.physicsBody?.isDynamic = false
node.physicsBody?.categoryBitMask = CollisionTypes.vortex.rawValue
node.physicsBody?.contactTestBitMask = CollisionTypes.player.rawValue
node.physicsBody?.collisionBitMask = 0
addChild(node)
Upon contact with the that blackhole body:
func playerCollided(with node: SKNode) {
if node.name == "vortex" {
player.physicsBody?.isDynamic = false
isGameOver = true
score -= 1
let move = SKAction.move(to: node.position, duration: 0.25)
let scale = SKAction.scale(to: 0.0001, duration: 0.25)
let remove = SKAction.removeFromParent()
let sequence = SKAction.sequence([move, scale, remove])
player.run(sequence) { [unowned self] in
self.createPlayer()
self.isGameOver = false
}
}