Touch Sprite, make it jump up then fall down again(repeat as many times as spritenode is tapped.)
I tested your code and it seems that using firstBall.userInteractionEnabled = true
is the cause. Without it, it should work. I did some research (here for example), but can't figure out what's the reason of this behavior with userInteractionEnabled
. Or for what reason do you use userInteractionEnabled
?
Update due to update of question
First ball.physicsBody?.restitution = 3
defines the bounciness of the physics body. The default value is 0.2 and the property must be between 0.0 ans 1.0. So if you set it to 3.0 it will cause some unexpected effects. I just deleted it to use the default value of 0.2.
Second, to make the ball jump after tap and increase the score I put
physicsBody?.velocity = CGVectorMake(0, 600)
physicsBody?.applyImpulse(CGVectorMake(0, 1100))
in the touchesBegan
method of the Ball class
Result
How to add 1 point every time SKNode is touched
Step 1
First of all let's create a class for the Ball
class Ball: SKSpriteNode {
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let scene = self.scene as! GameScene
scene.score += 1
}
}
Step 2
Then inside createPlayer()
let's replace this
let sprite = SKSpriteNode(imageNamed: "Ball")
with this
let sprite = Ball(imageNamed: "Ball")
sprite.userInteractionEnabled = true
Step 3
Let's remove the touchesBegan
from GameScene
.
Update
This code is working as expected for me
class GameScene: SKScene {
var scoreLabel: SKLabelNode!
var ball: Ball!
private var score = 0 {
didSet {
scoreLabel.text = "\(score)"
print(score)
}
}
override func didMoveToView(view: SKView) {
let ball = Ball()
ball.position = CGPoint(x:frame.midX, y: frame.midY)
addChild(ball)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
// this is called when you tap outside of the Ball
// use self.ball to make the ball to jump
}
}
class Ball: SKSpriteNode {
init() {
let texture = SKTexture(imageNamed: "ball")
super.init(texture: texture, color: .clearColor(), size: texture.size())
userInteractionEnabled = true
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let scene = self.scene as! GameScene
scene.score += 1
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Make an SKSpriteNode fall when the screen is not being tapped
trying setting your gravity in the game to self.physicsWorld.gravity = CGVectorMake(0,-9.8);
and setting your SKSpriteNode to dynamic.
test.physicsbody.dynamic = YES;
SKAction scale physicsBody
If you set the size of the physicsbody to the size of the circle, you may achieve what you seek.
_circle = [SKSpriteNode spriteNodeWithImageNamed:@"cirlce"];
_circle.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:_circle.size.width/2];
Sprite Kit set Min. and Max. for Jump
Here is an working example on how to make something like:
- long pressed jump based on duration of press
- short (one tap jump)
- restrict character to jump while in the air
- keep character jumping while finger is on screen
Code (Swift 4.x)
import SpriteKit
struct Constants {
static let minimumJumpForce:CGFloat = 15.0
static let maximumJumpForce:CGFloat = 30.0
static let characterSideSpeed:CGFloat = 18.0
}
class GameScene: SKScene,SKPhysicsContactDelegate
{
let CharacterCategory : UInt32 = 0x1 << 1
let PlatformCategory : UInt32 = 0x1 << 2
let WallCategory : UInt32 = 0x1 << 3
var force: CGFloat = 16.0 //Initial force
var pressed = false
var isCharacterOnGround = false // Use this to prevent jumping while in the air
let character = SKSpriteNode(color: .green, size: CGSize(width: 30, height:30))
let debugLabel = SKLabelNode(fontNamed: "Geneva")
override func didMove(to view: SKView)
{
//Setup contact delegate so we can use didBeginContact and didEndContact methods
physicsWorld.contactDelegate = self
physicsWorld.speed = 0.5
//Setup borders so character can't escape from us :-)
self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)
self.physicsBody?.categoryBitMask = WallCategory
self.physicsBody?.collisionBitMask = CharacterCategory
//Setup character
character.position = CGPoint(x: 150, y: 150)
character.physicsBody = SKPhysicsBody(rectangleOf: character.size)
character.physicsBody?.categoryBitMask = CharacterCategory
character.physicsBody?.contactTestBitMask = PlatformCategory
character.physicsBody?.collisionBitMask = PlatformCategory | WallCategory
character.physicsBody?.allowsRotation = false
character.physicsBody?.isDynamic = true
character.physicsBody?.restitution = 0.1
self.addChild(character)
generatePlatforms()
debugLabel.text = " DEBUG: "
debugLabel.fontColor = .white
debugLabel.fontSize = 12.0
debugLabel.position = CGPoint(x: frame.midX, y: frame.midY+100)
self.addChild(debugLabel)
}
func generatePlatforms(){
for i in 1...4
{
let position = CGPoint(x: frame.midX, y: CGFloat(i)*140.0 - 100)
let platform = createPlatformAtPosition(position: position)
self.addChild(platform)
}
}
func createPlatformAtPosition(position : CGPoint)->SKSpriteNode{
let platform = SKSpriteNode(color: .green, size: CGSize(width: frame.size.width, height:20))
platform.position = position
platform.physicsBody = SKPhysicsBody(
edgeFrom: CGPoint(x: -platform.size.width/2.0, y:platform.size.height/2.0),
to:CGPoint(x: platform.size.width/2.0, y: platform.size.height/2.0))
platform.physicsBody?.categoryBitMask = PlatformCategory
platform.physicsBody?.contactTestBitMask = CharacterCategory
platform.physicsBody?.collisionBitMask = CharacterCategory
platform.physicsBody?.allowsRotation = false
platform.name = "platform"
platform.physicsBody?.isDynamic = false
platform.physicsBody?.restitution = 0.0
return platform
}
func jump(force : CGFloat){
if(self.isCharacterOnGround){
self.character.physicsBody?.applyImpulse(CGVector(dx: 0, dy: force))
self.character.physicsBody?.collisionBitMask = WallCategory
self.isCharacterOnGround = false
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.pressed = true
let timerAction = SKAction.wait(forDuration: 0.05)
let update = SKAction.run({
if(self.force < Constants.maximumJumpForce){
self.force += 2.0
}else{
self.jump(force: Constants.maximumJumpForce)
self.force = Constants.maximumJumpForce
}
})
let sequence = SKAction.sequence([timerAction, update])
let repeat_seq = SKAction.repeatForever(sequence)
self.run(repeat_seq, withKey:"repeatAction")
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.removeAction(forKey: "repeatAction")
self.jump(force: self.force)
self.force = Constants.minimumJumpForce
self.pressed = false
}
override func update(_ currentTime: TimeInterval) {
debugLabel.text = "DEBUG: onTheGround : \(isCharacterOnGround), force \(force)"
if(character.position.x <= character.size.width/2.0 + 5.0 && character.physicsBody!.velocity.dx < 0.0 ){
character.physicsBody?.applyForce(CGVector(dx: Constants.characterSideSpeed, dy: 0.0))
}else if((character.position.x >= self.frame.size.width - character.size.width/2.0 - 5.0) && character.physicsBody!.velocity.dx >= 0.0){
character.physicsBody?.applyForce(CGVector(dx: -Constants.characterSideSpeed, dy: 0.0))
}else if(character.physicsBody!.velocity.dx > 0.0){
character.physicsBody!.applyForce(CGVector(dx: Constants.characterSideSpeed, dy: 0.0))
}else{
character.physicsBody!.applyForce(CGVector(dx: -Constants.characterSideSpeed, dy: 0.0))
}
}
func didBegin(_ contact: SKPhysicsContact) {
var firstBody, secondBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if ((firstBody.categoryBitMask & CharacterCategory) != 0 &&
(secondBody.categoryBitMask & PlatformCategory != 0)) {
let platform = secondBody.node! as! SKSpriteNode
// platform.color = UIColor.redColor()
let platformSurfaceYPos = platform.position.y + platform.size.height/2.0
let player = contact.bodyB.node! as! SKSpriteNode
let playerLegsYPos = player.position.y - player.size.height/2.0
if((platformSurfaceYPos <= playerLegsYPos)){
character.physicsBody?.collisionBitMask = PlatformCategory | WallCategory
self.isCharacterOnGround = true
if(self.pressed){
let characterDx = character.physicsBody?.velocity.dx
character.physicsBody?.velocity = CGVector(dx: characterDx!, dy: 0.0)
self.jump(force: Constants.maximumJumpForce)
}
}
}
}
func didEnd(_ contact: SKPhysicsContact) {
var firstBody, secondBody: SKPhysicsBody
if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
firstBody = contact.bodyA
secondBody = contact.bodyB
} else {
firstBody = contact.bodyB
secondBody = contact.bodyA
}
if ((firstBody.categoryBitMask & CharacterCategory) != 0 &&
(secondBody.categoryBitMask & PlatformCategory != 0)) {
let platform = secondBody.node as! SKSpriteNode
let platformSurfaceYPos = platform.position.y + platform.size.height/2.0
let player = contact.bodyB.node as! SKSpriteNode
let playerLegsYPos = player.position.y - player.size.height/2.0
if((platformSurfaceYPos <= playerLegsYPos) && ((character.physicsBody?.velocity.dy)! > CGFloat(0.0))){
character.physicsBody?.collisionBitMask = WallCategory
self.isCharacterOnGround = false
}
}
}
}
Note that this is simple example, and in real application you will probably have to handle states like isOnTheGround in a different way. Right now, to determine if character is on the ground you just set isOnTheGround = true
when character make a contact with platform, and set it to false
in didEndContact
...But there are situations when character can be in contact with platform while in the air (eg. side contact)...
EDIT:
I changed the code to let the player jump while pressed. Here is the result:
Important:
Actual platform implementation and contact handling is up to you and this is not tested. The only purpose of this example is to show you how to jump while pressed. Currently, physicsWorld.speed
is set to 0.5 to make animation slower because its easier to debug like that, but you can change this to default value (1.0).
So, as you can see from the image, while player is on the first platform some small jumps are presented (by simple tapping, or short pressing). Then (player is still on first platform) long press has been made, and player has jumped on second platform. After that, another long press is done, but this time without releasing, and player starts jumping from one platform to another using maximum force.
This needs a lot of tweaking and proper platform & contact detection, but it can give you an idea about how to implement jumping you asked about.
SpriteKitNode not moving
First thoughts I have to check the PhysicsBody
has been initialised - perhaps your optional ? unwrap is failing. So add in the following line below your var ball declaration:
ball.physicsBody = SKPhysicsBody(rectangleOfSize: ball.frame.size)
It may not be the ideal physics body shape, but this should allow your ball to move as per the impulse.
How would I move my node up when there is a touch and hold on the screen in Swift Spritekit?
Firstly, you need to keep track of when the user is touching and holding the screen. You can do this like so, in your SKScene
:
var touchingScreen = false
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesBegan(touches, withEvent: event)
touchingScreen = true
}
override func touchesCancelled(touches: Set<NSObject>!, withEvent event: UIEvent!) {
super.touchesCancelled(touches, withEvent: event)
touchingScreen = false
}
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
super.touchesEnded(touches, withEvent: event)
touchingScreen = false
}
Secondly, you now need to make the object move upwards whilst the user is pressing the screen, again in your SKScene
:
override func update(currentTime: CFTimeInterval) {
if touchingScreen {
// Adjust the CGVector to suit your needs.
sprite.physicsBody!.applyImpulse(CGVector(dx: 0, dy: 50))
}
}
Alternatively, if you want your object to move upwards at a constant velocity, replace:
sprite.physicsBody!.applyImpulse(CGVector(dx: 0, dy: 50))
with
sprite.physicsBody!.velocity = CGVector(dx: yourDx, dy: yourDy)
Hope that helps!
Simulate water/ make sprite float on water in spritekit
There are three possible options I believe you have with regards to simulating water.
1) As mentioned in the comments you could try to use SKFieldNode (iOS 8+). But from personal experience the field node didn't really do it for me because you don't get much control over your simulation with it unless you heavily customize it, in which case you might as well just do your own calculations from scratch and reduce complexity.
2) You could adjust the linear and rotational damping of your sprite when inside the water. In fact, even apple mentions this in the quote from their documentation. However this won't give you buoyancy.
The linearDamping and angularDamping properties are used to calculate
friction on the body as it moves through the world. For example, this
might be used to simulate air or water friction.
3) Perform the calculations yourself. In the update method, check when the body enters you "water" and when it does you can calculate viscosity and/or buoyancy and adjust the velocity of your node accordingly. This in my opinion is the best option but also the more difficult.
Edit: I just wrote a quick example of option 3 in Swift. I think it's what you are looking for. I added factor constants on the top so you can adjust it to get exactly what you want. The motion is applied dynamically so it won't interfere with you current velocity (i.e. you can control your character while in the water). Below is the code for the scene and a gif as well. Keep in mind that the delta time is assumed to be 60 frames a second (1/60) and there is no velocity clamping. These are features you may or may not want depending on your game.
Swift
class GameScene: SKScene {
//MARK: Factors
let VISCOSITY: CGFloat = 6 //Increase to make the water "thicker/stickier," creating more friction.
let BUOYANCY: CGFloat = 0.4 //Slightly increase to make the object "float up faster," more buoyant.
let OFFSET: CGFloat = 70 //Increase to make the object float to the surface higher.
//MARK: -
var object: SKSpriteNode!
var water: SKSpriteNode!
override func didMoveToView(view: SKView) {
object = SKSpriteNode(color: UIColor.whiteColor(), size: CGSize(width: 25, height: 50))
object.physicsBody = SKPhysicsBody(rectangleOfSize: object.size)
object.position = CGPoint(x: self.size.width/2.0, y: self.size.height-50)
self.addChild(object)
water = SKSpriteNode(color: UIColor.cyanColor(), size: CGSize(width: self.size.width, height: 300))
water.position = CGPoint(x: self.size.width/2.0, y: water.size.height/2.0)
water.alpha = 0.5
self.addChild(water)
self.physicsBody = SKPhysicsBody(edgeLoopFromRect: self.frame)
}
override func update(currentTime: CFTimeInterval) {
if water.frame.contains(CGPoint(x:object.position.x, y:object.position.y-object.size.height/2.0)) {
let rate: CGFloat = 0.01; //Controls rate of applied motion. You shouldn't really need to touch this.
let disp = (((water.position.y+OFFSET)+water.size.height/2.0)-((object.position.y)-object.size.height/2.0)) * BUOYANCY
let targetPos = CGPoint(x: object.position.x, y: object.position.y+disp)
let targetVel = CGPoint(x: (targetPos.x-object.position.x)/(1.0/60.0), y: (targetPos.y-object.position.y)/(1.0/60.0))
let relVel: CGVector = CGVector(dx:targetVel.x-object.physicsBody.velocity.dx*VISCOSITY, dy:targetVel.y-object.physicsBody.velocity.dy*VISCOSITY);
object.physicsBody.velocity=CGVector(dx:object.physicsBody.velocity.dx+relVel.dx*rate, dy:object.physicsBody.velocity.dy+relVel.dy*rate);
}
}
override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) {object.position = (touches.anyObject() as UITouch).locationInNode(self);object.physicsBody.velocity = CGVectorMake(0, 0)}
}
Objective-C
#import "GameScene.h"
#define VISCOSITY 6.0 //Increase to make the water "thicker/stickier," creating more friction.
#define BUOYANCY 0.4 //Slightly increase to make the object "float up faster," more buoyant.
#define OFFSET 70.0 //Increase to make the object float to the surface higher.
@interface GameScene ()
@property (nonatomic, strong) SKSpriteNode* object;
@property (nonatomic, strong) SKSpriteNode* water;
@end
@implementation GameScene
-(void)didMoveToView:(SKView *)view {
_object = [[SKSpriteNode alloc] initWithColor:[UIColor whiteColor] size:CGSizeMake(25, 50)];
self.object.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.object.size];
self.object.position = CGPointMake(self.size.width/2.0, self.size.height-50);
[self addChild:self.object];
_water = [[SKSpriteNode alloc] initWithColor:[UIColor cyanColor] size:CGSizeMake(self.size.width, 300)];
self.water.position = CGPointMake(self.size.width/2.0, self.water.size.height/2.0);
self.water.alpha = 0.5;
[self addChild:self.water];
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
}
-(void)update:(NSTimeInterval)currentTime {
if (CGRectContainsPoint(self.water.frame, CGPointMake(self.object.position.x,self.object.position.y-self.object.size.height/2.0))) {
const CGFloat rate = 0.01; //Controls rate of applied motion. You shouldn't really need to touch this.
const CGFloat disp = (((self.water.position.y+OFFSET)+self.water.size.height/2.0)-((self.object.position.y)-self.object.size.height/2.0)) * BUOYANCY;
const CGPoint targetPos = CGPointMake(self.object.position.x, self.object.position.y+disp);
const CGPoint targetVel = CGPointMake((targetPos.x-self.object.position.x)/(1.0/60.0), (targetPos.y-self.object.position.y)/(1.0/60.0));
const CGVector relVel = CGVectorMake(targetVel.x-self.object.physicsBody.velocity.dx*VISCOSITY, targetVel.y-self.object.physicsBody.velocity.dy*VISCOSITY);
self.object.physicsBody.velocity=CGVectorMake(self.object.physicsBody.velocity.dx+relVel.dx*rate, self.object.physicsBody.velocity.dy+relVel.dy*rate);
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.object.position = [(UITouch*)[touches anyObject] locationInNode:self];
self.object.physicsBody.velocity = CGVectorMake(0, 0);
}
@end
Related Topics
Thread Safety of Method Calls on "Shared" Static Constant Property
How to Detect Encoding in Data Based on a String
How to Get Data to Return from Nsurlsessiondatatask in Swift
Add Value to Variable Inside Closure in Swift
How to Determine Whether a Double Is an Integer
How to Read The Property Values of a JSON Error Object Using Combine in Swift
Nsattributedstring Boundingrect Returns Wrong Height
Function Inside Prepareforsegue Will Not Execute
Got Error: Return an Empty String to Bool
Swift: How to Get Image Name from Assets
Set Maximum Characters (To One) in a Nstextfield in Swift
Closure (With Default Value) as Function Parameter
Display Array in a Label in Swift
Collision Detection Leading to Color Detection
Why Can't I Get The Index of a Filtered Realm List
How to Get The Range of The First Line in a String
Protocol Extension Doesn't Work with Rct_Export_View_Property