I'm working on a Mario Clone for Mac and would like to make the player jump on a curved line when the right and up arrow are pressed.I've heard about UIBezierPaths being the solution to this, but sadly I have no clue how to use them.Can someone please explain what code I would write in order to make a UIBezierPath as well as make Mario follow the path in a jump?Thank you.
Somewhat of the way I would like my player to jump:
!http://projects.haskell.org/diagrams/doc/images/d8527a0188182ef5.png
My Player Movements So far:
func jump(){
// 250 , 120
let jumpUp = SKAction.moveTo(y: 250, duration: 0.25)
let jumpDown = SKAction.moveTo(y: 120, duration: 0.25)
let jumpSequence = SKAction.sequence([jumpUp,jumpDown])
player.run(jumpSequence)
}
func userMoveRight(){
player.xScale = 0.23
level.run(SKAction.sequence([SKAction.moveBy(x: -20, y: 0, duration: 0.2)]))
background.run(SKAction.sequence([SKAction.moveBy(x: -20, y: 0, duration: 0.2)]))
question.run(SKAction.sequence([SKAction.moveBy(x: -20, y: 0, duration: 0.2)]))
}
func userMoveLeft(){
player.xScale = -0.23
background.run(SKAction.sequence([SKAction.moveBy(x: 20, y: 0, duration: 0.2)]))
level.run(SKAction.sequence([SKAction.moveBy(x: 20, y: 0, duration: 0.2)]))
question.run(SKAction.sequence([SKAction.moveBy(x: 20, y: 0, duration: 0.2)]))
}
override func keyDown(with theEvent: NSEvent) {
let keyCode = theEvent.keyCode
//Moving Right
if keyCode == 124 {
userMoveRight()
}
//Moving Left
if player.position.x <= 490, background.position.x <= 1513, keyCode == 123 {
userMoveLeft()
}
//Jump
if keyCode == 126 , player.position.y == 120 {
jump()
}
}
Related
My issue is that when I press the button that runs the dealToPlayer function(), everything acts as if a card has been dealt, updates total value of cards dealt, but it does not show any animation of the card being dealt or placed. Why might this be? I am not using auto layout for this scene at all.
func dealToPlayer() {
let index = Int(arc4random_uniform(UInt32(deckCards.count)))
let drawnCard = deckCards.remove(at: index)
randomCards.append(drawnCard)
randomCard = randomCards[3 + cardSelect]
image = UIImageView(frame: CGRect(x: self.deckLocation.x, y: self.deckLocation.y, width: self.width, height: self.height))
image.image = mapping[randomCard]
cardsOut = cardsOut + 1
print("cards out is currently: \(cardsOut)")
cardSelect = cardSelect + 1
UIView.animate(withDuration: 0.5, delay: 1.5, options: [], animations: {
self.image.frame = CGRect(x: self.cardTwoLocation.x - (self.cardsOut * self.thirdWidth), y: self.cardTwoLocation.y, width: self.width, height: self.height)
}, completion: nil)
checkPlayerValue()
}
First, make sure your image view is added to your view controller.
Second, make sure your animation is called by putting a break point.
Third, print out the location and make sure the new x and y are valid, and they are different than the previous x and y.
Here is a sample duplicate of your code an it works fine
image = UIImageView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
image.backgroundColor = .red
self.view.addSubview(image)
UIView.animate(withDuration: 0.5, delay: 1.5, options: [], animations: {
self.image.frame = CGRect(x: 200, y: 200, width: 100, height: 100)
}, completion: nil)
I created a function that spawns a SKSpriteNode with name Ball and I created another function to animate it with 2 different colors. After they spawned I need to move one Ball to my touch.location, but when I create this it always takes the last spawned Node to move the location. So I just need to move one of a few balls that are currently in the scene and not the last one added.
Can someone please help me. Thanks in advance
// Spawn forever
let action = SKAction.sequence([SKAction.run(spawnRandomBall), SKAction.wait(forDuration: 1.2)])
run(SKAction.repeatForever(action))
}
//-------------------------------------------------------------------------------------------------//
func randomLocation (){
let randomNumber = Int(arc4random() % 2) // 0 and 1
if randomNumber == 0 {
roodball.physicsBody?.applyImpulse(CGVector(dx: 20, dy: 20))
blauwball.physicsBody?.applyImpulse(CGVector(dx: 20, dy: 20))
} else {
roodball.physicsBody?.applyImpulse(CGVector(dx: -20, dy: -20))
blauwball.physicsBody?.applyImpulse(CGVector(dx: -20, dy: -20))
}
}
//.....................................................................................................//
func spawnRandomBall (){
let randomNumber = Int(arc4random() % 2) // 0 and 1
if randomNumber == 0 {
spawnRoodBall()
} else {
spawnBlauwBall()
}
roodball.physicsBody = SKPhysicsBody(circleOfRadius: roodball.size.height / 2)
roodball.physicsBody?.allowsRotation = false
roodball.physicsBody?.affectedByGravity = false
roodball.physicsBody?.friction = 0
roodball.physicsBody?.restitution = 1
roodball.physicsBody?.linearDamping = 0
roodball.physicsBody?.angularDamping = 0
roodball.physicsBody?.velocity = CGVector(dx: 1, dy: 1)
blauwball.physicsBody = SKPhysicsBody(circleOfRadius: blauwball.size.height / 2)
blauwball.physicsBody?.allowsRotation = false
blauwball.physicsBody?.affectedByGravity = false
blauwball.physicsBody?.friction = 0
blauwball.physicsBody?.restitution = 1
blauwball.physicsBody?.linearDamping = 0
blauwball.physicsBody?.angularDamping = 0
blauwball.physicsBody?.velocity = CGVector(dx: 1, dy: 1)
randomLocation()
}
//.....................................................................................................//
func spawnRoodBall () {
roodball = SKSpriteNode(imageNamed: "BalRood")
roodball.position = CGPoint(x: 360, y: self.frame.midY - 38)
roodball.anchorPoint = CGPoint(x: 0.5, y: 0.5)
roodball.size = CGSize(width: 50, height: 50)
roodball.run(SKAction.animate(with: roodTextureArray, timePerFrame: 1.3))
roodball.zPosition = 40
let wait = SKAction.wait(forDuration: 6.5)
let remove = SKAction.removeFromParent()
roodball.run(SKAction.sequence([wait, remove]), withKey: "waitRemove")
self.addChild(roodball)
}
//...................................................................................................//
func spawnBlauwBall () {
blauwball = SKSpriteNode(imageNamed: "BalBlauw")
blauwball.position = CGPoint(x: 360, y: self.frame.midY - 38)
blauwball.anchorPoint = CGPoint(x: 0.5, y: 0.5)
blauwball.size = CGSize(width: 50, height: 50)
blauwball.run(SKAction.animate(with: blauwTextureArray, timePerFrame: 1.3))
blauwball.zPosition = 40
let wait = SKAction.wait(forDuration: 6.5)
let remove = SKAction.removeFromParent()
blauwball.run(SKAction.sequence([wait, remove]), withKey: "waitRemove")
self.addChild(blauwball)
}
I'm beginner in Spritekit and I try to make background move vertically
I did know how
this what in the controller
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = GameScene(size: view.bounds.size)
let skView = view as! SKView
skView.showsFPS = true
skView.showsNodeCount = true
skView.ignoresSiblingOrder = true
scene.scaleMode = .resizeFill
skView.presentScene(scene)
}
and this the scene
class GameScene: SKScene {
var background = SKSpriteNode()
override func didMove(to view: SKView) {
let backgroundTexture = SKTexture(imageNamed: "bbbgg.png")
let shiftBackground = SKAction.moveBy(x: 0, y: -backgroundTexture.size().height, duration: 5)
let replaceBackground = SKAction.moveBy(x: 0, y:backgroundTexture.size().height, duration: 0)
let movingAndReplacingBackground = SKAction.repeatForever(SKAction.sequence([shiftBackground,replaceBackground]))
for i in 1...3{
background=SKSpriteNode(texture:backgroundTexture)
background.position = CGPoint(x: 0, y: 0)
background.size.height = self.frame.height
background.run(movingAndReplacingBackground)
self.addChild(background)
}
}
}
the problem is the background move horizontally and after it arrives to the end of screen disappear, then begins move from the beginning of the screen again
I want it work vertically without disappearing
The background move horizontally because declarations of moveBy (shiftBackground, replaceBackground) works on the x axe (horizontally). You need to work on the y axe (vertically).
let shiftBackground = SKAction.moveBy(x: 0, y: -backgroundTexture.size().height, duration: 5)
let replaceBackground = SKAction.moveBy(x: 0, y: backgroundTexture.size().height, duration: 0)
At the first line, the background move from it position to it position - it height. At the second line, it return to it first position (I think moveBy is relative).
_
The background move again because you use the function SKAction.repeatForever (movingAndReplacingBackground). I think you can remove it.
let movingAndReplacingBackground = SKAction.sequence([shiftBackground,replaceBackground])
Edit :
I forget the loop.
In the loop, you give to the background an initial position.
background.position = CGPoint(x: backgroundTexture.size().width/2 + (backgroundTexture.size().width * CGFloat(i)), y: self.frame.midY)
Try to replace it by 0, middle if you want an immediately visible background
background.position = CGPoint(x: 0, y: self.frame.midY)
Or -height, 0 if you want the background appear by the top of the screen. In this case you need to replace shiftBackground and replaceBackground
background.position = CGPoint(x: -backgroundTexture.size().height, y: 0)
and
let shiftBackground = SKAction.moveBy(x: 0, y: backgroundTexture.size().height, duration: 5)
let replaceBackground = SKAction.moveBy(x: 0, y: 0, duration: 0)
I'm not sure, but I think, if you want only one mouvement, you can remove th sequence :
class GameScene: SKScene {
var background = SKSpriteNode()
override func didMove(to view: SKView) {
let backgroundTexture = SKTexture(imageNamed: "bbbgg.png")
let scrollByTopBackground = SKAction.moveBy(x: O, y: backgroundTexture.size().height, duration: 5)
background = SKSpriteNode(texture:backgroundTexture)
background.position = CGPoint(x: -backgroundTexture.size().height, y: self.frame.midY)
background.size.height = self.frame.height
background.run(scrollByTopBackground)
self.addChild(background)
}
}
Edit to answer you, try this...
class GameScene: SKScene {
var background = SKSpriteNode()
override func didMove(to view: SKView) {
let backgroundTexture = SKTexture(imageNamed: "bbbgg.png")
let shiftBackground = SKAction.moveBy(x: 0, y: -backgroundTexture.size().height, duration: 5)
let replaceBackground = SKAction.moveBy(x: 0, y:backgroundTexture.size().height, duration: 0)
let movingAndReplacingBackground = SKAction.repeatForever(SKAction.sequence([shiftBackground,replaceBackground]))
for i in 0...2 {
background=SKSpriteNode(texture:backgroundTexture)
background.position = CGPoint(x: 0, y: self.frame.midY + (backgroundTexture.size().height * CGFloat(i)))
background.size.height = self.frame.height
background.run(movingAndReplacingBackground)
self.addChild(background)
}
}
I'm trying to create a parallax cloud effect with different speeds and sizes but i'm having a hard time when trying to add a different Y coordinate, not always in the same Y for all the clouds.
So I have 5 clouds, with its respective X and Y coordinate, and I use the SKAction.moveByX() function.
All the clouds starts from for example FRAME_WIDTH + 200 (out of bounds) and they end at -100. After that I reset the X to FRAME_WIDTH + 200 and do a "forever" sequence.
That was working perfectly, but I would like to add a random Y coordinate every time the animation finishes.
I was able to do it, but that would only change the Y coordinate once.
How can I achieve this?
Here's my current code:
func addClouds() {
let cloud1 = SKSpriteNode(imageNamed: "cloud1")
let cloud2 = SKSpriteNode(imageNamed: "cloud2")
let cloud3 = SKSpriteNode(imageNamed: "cloud3")
let cloud4 = SKSpriteNode(imageNamed: "cloud4")
let cloud5 = SKSpriteNode(imageNamed: "cloud5")
cloud1.zPosition = ZIndexPosition.CLOUD
cloud1.position = CGPoint(x: self.FRAME_WIDTH - 100, y: self.FRAME_HEIGHT / 2 - 150)
cloud1.size = CGSize(width: 44, height: 14)
cloud2.zPosition = ZIndexPosition.CLOUD
cloud2.position = CGPoint(x: self.FRAME_WIDTH + 150, y: self.FRAME_HEIGHT - 100)
cloud2.size = CGSize(width: 104, height: 16)
cloud3.zPosition = ZIndexPosition.CLOUD
cloud3.position = CGPoint(x: self.FRAME_WIDTH - 50, y: self.FRAME_HEIGHT - 200)
cloud3.size = CGSize(width: 8, height: 6)
cloud4.zPosition = ZIndexPosition.CLOUD
cloud4.position = CGPoint(x: self.FRAME_WIDTH + 200, y: self.FRAME_HEIGHT / 2 - 50)
cloud4.size = CGSize(width: 116, height: 32)
cloud5.zPosition = ZIndexPosition.CLOUD
cloud5.position = CGPoint(x: self.FRAME_WIDTH, y: self.FRAME_HEIGHT - 250)
cloud5.size = CGSize(width: 24, height: 6)
let resetCloud1YPos = SKAction.moveToY(self.randomCloud1, duration: 0)
let resetCloud2YPos = SKAction.moveToY(self.randomCloud2, duration: 0)
let resetCloud3YPos = SKAction.moveToY(self.randomCloud3, duration: 0)
let resetCloud4YPos = SKAction.moveToY(self.randomCloud4, duration: 0)
let resetCloud5YPos = SKAction.moveToY(self.randomCloud5, duration: 0)
let resetCloud1Pos = SKAction.moveToX((self.FRAME_WIDTH * 2) - 100, duration: 0)
let resetCloud2Pos = SKAction.moveToX((self.FRAME_WIDTH * 2) - 150, duration: 0)
let resetCloud3Pos = SKAction.moveToX((self.FRAME_WIDTH * 2) - 50, duration: 0)
let resetCloud4Pos = SKAction.moveToX((self.FRAME_WIDTH * 2) - 200, duration: 0)
let resetCloud5Pos = SKAction.moveToX((self.FRAME_WIDTH * 2) - 20, duration: 0)
let moveCloud1 = SKAction.moveToX(-100, duration: 28)
let cloud1Sequence = SKAction.sequence([moveCloud1, resetCloud1Pos, resetCloud1YPos])
let cloud1Forever = SKAction.repeatActionForever(cloud1Sequence)
let moveCloud2 = SKAction.moveToX(-100, duration: 24)
let cloud2Sequence = SKAction.sequence([moveCloud2, resetCloud2Pos, resetCloud2YPos])
let cloud2Forever = SKAction.repeatActionForever(cloud2Sequence)
let moveCloud3 = SKAction.moveToX(-100, duration: 35)
let cloud3Sequence = SKAction.sequence([moveCloud3, resetCloud3Pos, resetCloud3YPos])
let cloud3Forever = SKAction.repeatActionForever(cloud3Sequence)
let moveCloud4 = SKAction.moveToX(-100, duration: 13)
let cloud4Sequence = SKAction.sequence([moveCloud4, resetCloud4Pos, resetCloud4YPos])
let cloud4Forever = SKAction.repeatActionForever(cloud4Sequence)
let moveCloud5 = SKAction.moveToX(-100, duration: 18)
let cloud5Sequence = SKAction.sequence([moveCloud5, resetCloud5Pos, resetCloud5YPos])
let cloud5Forever = SKAction.repeatActionForever(cloud5Sequence)
cloud1.runAction(cloud1Forever)
cloud2.runAction(cloud2Forever)
cloud3.runAction(cloud3Forever)
cloud4.runAction(cloud4Forever)
cloud5.runAction(cloud5Forever)
self.addChild(cloud1)
self.addChild(cloud2)
self.addChild(cloud3)
self.addChild(cloud4)
self.addChild(cloud5)
}
randomCloud1,2,3,4 and 5 is just a random generation within the screen height bounds.
Thanks in advance.
If I understand what you're trying to accomplish, the following code should help you:
// ViewController.swift
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
//create and position a test SKSpriteNode
let cloud = SKSpriteNode()
cloud.color = SKColor.blueColor()
cloud.position = CGPoint(x: self.frame.size.width - 100, y: 400)
cloud.size = CGSize(width: 44, height: 14)
self.addChild(cloud)
//pass the SKSpriteNode into the moveNode function
self.moveNode(cloud)
}
//reposition the node
func repositionNode(node: SKSpriteNode) {
print("repositioning node")
node.position = CGPointMake(self.frame.size.width - 100, self.randomCloudPosition())
print(node.position.y)
self.moveNode(node)
}
//move the node again
func moveNode(node: SKSpriteNode) {
node.runAction(SKAction.moveToX(-100, duration: 28), completion: {
self.repositionNode(node)
})
}
//return a random number between 300 and 799 (change this for your specific case)
func randomCloudPosition() -> CGFloat {
return CGFloat(arc4random_uniform(500) + 300) //should be between 300 and 799
}
}
You should be able to create your five clouds (or however many) and then just pass each one into moveNode and the animation will continue indefinitely
For example if you have an image of a trampoline, and a character jumping on it. Then you want to animate how the trampoline bends down in the center.
To do this I would have to take a bitmap and apply it to a densely tessellated OpenGL ES mesh. Then apply texture to it. Then deform the mesh.
Does SpriteKit support this or can it only display textures as-is?
With iOS 10, SKWarpGeometry has been added that allows you to deform sprites. Warps are defined using a source and destination grid containing eight control points, these points define the warp transform; Sprite Kit will take care of tessellation and other low level details.
You can set the wrap geometry for a sprite directly, or animate warping with SKActions.
SKAction.animate(withWarps: [SKWarpGeometry], times: [NSNumber]) -> SKAction?
SKAction.animate(withWarps: [SKWarpGeometry], times: [NSNumber], restore: Bool) -> SKAction?
SKAction.warp(to: SKWarpGeometry, duration: TimeInterval) -> SKAction?
UPDATE Nov 2016
On Xcode 8+, the following code snippet can be used in a playground: Drag the control points and see the resulting SKSpriteNode get deformed accordingly.
// SpriteKit warp geometry example
// Copy the following in an Xcode 8+ playground
// and make sure your Assistant View is open
//
import UIKit
import PlaygroundSupport
import SpriteKit
let view = SKView(frame: CGRect(x: 0, y: 0, width: 480, height: 320))
let scene = SKScene(size: view.frame.size)
view.showsFPS = true
view.presentScene(scene)
PlaygroundSupport.PlaygroundPage.current.liveView = view
func drawCanvas1(frame: CGRect = CGRect(x: 0, y: 0, width: 200, height: 200)) -> UIImage {
UIGraphicsBeginImageContext(frame.size)
//// Star Drawing
let starPath = UIBezierPath()
starPath.move(to: CGPoint(x: frame.minX + 100.5, y: frame.minY + 24))
starPath.addLine(to: CGPoint(x: frame.minX + 130.48, y: frame.minY + 67.74))
starPath.addLine(to: CGPoint(x: frame.minX + 181.34, y: frame.minY + 82.73))
starPath.addLine(to: CGPoint(x: frame.minX + 149, y: frame.minY + 124.76))
starPath.addLine(to: CGPoint(x: frame.minX + 150.46, y: frame.minY + 177.77))
starPath.addLine(to: CGPoint(x: frame.minX + 100.5, y: frame.minY + 160))
starPath.addLine(to: CGPoint(x: frame.minX + 50.54, y: frame.minY + 177.77))
starPath.addLine(to: CGPoint(x: frame.minX + 52, y: frame.minY + 124.76))
starPath.addLine(to: CGPoint(x: frame.minX + 19.66, y: frame.minY + 82.73))
starPath.addLine(to: CGPoint(x: frame.minX + 70.52, y: frame.minY + 67.74))
starPath.close()
UIColor.red.setFill()
starPath.fill()
UIColor.green.setStroke()
starPath.lineWidth = 10
starPath.lineCapStyle = .round
starPath.lineJoinStyle = .round
starPath.stroke()
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
class ControlNode : SKShapeNode {
override init() {
super.init()
self.path = CGPath(ellipseIn: CGRect.init(x: 0, y: 0, width: 10, height: 10), transform: nil)
self.fillColor = UIColor.red
self.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
typealias UpdateHandler = (ControlNode) -> ()
var positionUpdated: UpdateHandler? = nil
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.fillColor = UIColor.yellow
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first, let parent = self.parent {
let pos = touch.location(in: parent).applying(CGAffineTransform(translationX: -5, y: -5))
self.position = pos
positionUpdated?(self)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
self.fillColor = UIColor.red
}
var warpPosition: vector_float2 {
if let parent = self.parent {
let size = parent.frame.size
let pos = self.position.applying(CGAffineTransform(translationX: -size.width/2 + 5, y: -size.height/2 + 5))
return vector_float2(
x: Float(1 + pos.x / size.width),
y: Float(1 + pos.y / size.height)
)
}
return vector_float2(x: 0, y: 0)
}
}
extension SKSpriteNode {
func addControlNode(x: CGFloat, y: CGFloat) -> ControlNode {
let node = ControlNode()
node.position = CGPoint(
x: x * frame.width - frame.width / 2 - 5,
y: y * frame.height - frame.height / 2 - 5
)
self.addChild(node)
return node
}
}
let texture = SKTexture(image: drawCanvas1())
let image = SKSpriteNode(texture: texture)
image.position = CGPoint(x: 200, y: 160)
scene.addChild(image)
let controlPoints: [(x:CGFloat, y:CGFloat)] = [
(0, 0),
(0.5, 0),
(1.0, 0),
(0, 0.5),
(0.5, 0.5),
(1.0, 0.5),
(0, 1.0),
(0.5, 1.0),
(1.0, 1.0),
]
let sourcePositions = controlPoints.map {
vector_float2.init(Float($0.x), Float($0.y))
}
var controlNodes: [ControlNode] = []
controlPoints.forEach { controlNodes.append(image.addControlNode(x: $0.x, y: $0.y)) }
for node in controlNodes {
node.positionUpdated = {
[weak image]
_ in
let destinationPoints = controlNodes.map {
$0.warpPosition
}
image?.warpGeometry = SKWarpGeometryGrid(columns: 2, rows: 2, sourcePositions: sourcePositions, destinationPositions: destinationPoints)
}
}
PS: I referred to the SKWarpGeometry documentation.
Edit: See #Benzi's answer for a nice option using the SKWarpGeometry API available in iOS 10 / tvOS 10 / macOS 10.12. Prior to those releases, Sprite Kit could only display textures as billboards — the remainder of this answer addresses that pre-2016 situation, which might still be relevant if you're targeting older devices.
You might be able to get the distortion effect you're going for by using SKEffectNode and one of the geometry distortion Core Image filters, though. (CIBumpDistortion, for example.) Keep a reference to the effect node or its filter property in your game logic code (in your SKScene subclass or wherever else you put it), and you can adjust the filter's parameters using setValue:forKey: for each frame of animation.