I have 2 different textures for my character that overlap/are displayed too fast
while moving the character. How can I set a duration for the animation, so the textures always switch at the same speed while moving the character?
This is my code:
override func touchesMoved(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let animatePlayerStart = SKAction.setTexture(SKTexture(imageNamed: "Player\(i).png"))
// Determine speed for character movement
var minDuration:CGFloat = 0.7;
var maxDuration:CGFloat = 1.8;
var rangeDuration:CGFloat = maxDuration - minDuration;
var actualDuration:NSTimeInterval = NSTimeInterval((CGFloat(arc4random())%rangeDuration) + minDuration)
let move = SKAction.moveTo(location, duration:actualDuration)
player.runAction(SKAction.sequence([animatePlayerStart, move]))
// i determines which texture is going to be displayed
if(self.i == 2) {
self.i = 1
}
else{
self.i++
}
}
}
You are changing texture in touchesMoved which is called fast, thus the effect you are currently getting. To change textures after pre-defined period of time you can use this method:
+ animateWithTextures:timePerFrame:
import SpriteKit
class GameScene: SKScene {
let hero = SKSpriteNode(imageNamed: "heroState_A")
let textureA = SKTexture(imageNamed: "heroState_A")
let textureB = SKTexture(imageNamed: "heroState_B")
override func didMoveToView(view: SKView) {
/* Setup your scene here */
//Because hero is already initialized with textureA, start from textureB
let animation = SKAction.animateWithTextures([textureB,textureA], timePerFrame:0.5)
hero.position = CGPoint(x: CGRectGetMidX(frame), y: CGRectGetMidY(frame))
addChild(hero)
//Start animation
hero.runAction(SKAction.repeatActionForever(animation),withKey:"heroAnimation")
//Removing heroAnimation
//You can stop this animation by hero.removeAllActions, but if you run animation with key, you can remove just that particular action, which gives you more control
let stop = SKAction.runBlock({
if(self.hero.actionForKey("heroAnimation") != nil){
self.hero.removeActionForKey("heroAnimation")
}
})
//Just an example, in real app you will do this at certain events (eg. when player stops his movement)
runAction(SKAction.sequence([SKAction.waitForDuration(5),stop]))
}
}
Related
I'm trying to learn ARKIT and make a small demo app to draw in 3D.
The following is the code I wrote and so far there are no problems:
import UIKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
#IBOutlet weak var sceneView: ARSCNView!
#IBOutlet weak var DRAW: UIButton!
#IBOutlet weak var DEL: UIButton!
let config = ARWorldTrackingConfiguration()
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView.session.run(config)
self.sceneView.delegate = self
}
func renderer(_ renderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: TimeInterval) {
guard let pointOfView = sceneView.pointOfView else {return}
let transform = pointOfView.transform
let cameraOrientation = SCNVector3(-transform.m31,-transform.m32,-transform.m33)
let cameraLocation = SCNVector3(transform.m41,transform.m42,transform.m43)
let cameraCurrentPosition = cameraOrientation + cameraLocation
DispatchQueue.main.async {
if (self.DRAW.isTouchInside){
let sphereNode = SCNNode(geometry: SCNSphere(radius: 0.02))
sphereNode.position = cameraCurrentPosition
self.sceneView.scene.rootNode.addChildNode(sphereNode)
sphereNode.geometry?.firstMaterial?.diffuse.contents = UIColor.red
print("RED Button is Pressed")
}else if (self.DEL.isTouchInside){
self.sceneView.scene.rootNode.enumerateChildNodes{
(node, stop) in
node.removeFromParentNode()
}
}else{
let pointer = SCNNode(geometry: SCNSphere(radius: 0.01))
pointer.name = "pointer"
pointer.position = cameraCurrentPosition
self.sceneView.scene.rootNode.enumerateChildNodes({(node,_) in
if node.name == "pointer"{
node.removeFromParentNode()
}
})
self.sceneView.scene.rootNode.addChildNode(pointer)
pointer.geometry?.firstMaterial?.diffuse.contents = UIColor.purple
}
}
}
}
func +(left:SCNVector3,right:SCNVector3) -> SCNVector3 {
return SCNVector3Make(left.x + right.x, left.y + right.y, left.z + right.z)
}
As you can see, I set the scene and configure it,
I create a button to draw when pressed, a pointer (or viewfinder) that takes the center of the scene and a button to delete the nodes inserted.
Now I would like to be able to move the cameraCurrentPosition to a different point from the center: I would like to move it if possible with a touch on the screen taking the position of the finger.
If possible, could someone help me with the code?
Generally speaking, you can't programmatically move the Camera within an ARSCN, the camera transform is the physical position of the device relative to the virtual scene.
With that being said, one way you could draw the user touches to the screen is using the touchesMoved method within your View Controller.
var touchRoots: [SCNNode] = [] // list of root nodes for each set of touches drawn
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// get the initial touch event
if let touch = touches.first {
guard let pointOfView = self.sceneView.pointOfView else { return }
let transform = pointOfView.transform // transformation matrix
let orientation = SCNVector3(-transform.m31, -transform.m32, -transform.m33) // camera rotation
let location = SCNVector3(transform.m41, transform.m42, transform.m43) // location of camera frustum
let currentPostionOfCamera = orientation + location // center of frustum in world space
DispatchQueue.main.async {
let touchRootNode : SCNNode = SCNNode() // create an empty node to serve as our root for the incoming points
touchRootNode.position = currentPostionOfCamera // place the root node ad the center of the camera's frustum
touchRootNode.scale = SCNVector3(1.25, 1.25, 1.25)// touches projected in Z will appear smaller than expected - increase scale of root node to compensate
guard let sceneView = self.sceneView else { return }
sceneView.scene.rootNode.addChildNode(touchRootNode) // add the root node to the scene
let constraint = SCNLookAtConstraint(target: self.sceneView.pointOfView) // force root node to always face the camera
constraint.isGimbalLockEnabled = true // enable gimbal locking to avoid issues with rotations from LookAtConstraint
touchRootNode.constraints = [constraint] // apply LookAtConstraint
self.touchRoots.append(touchRootNode)
}
}
}
override func func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let translation = touch.location(in: self.view)
let translationFromCenter = CGPoint(x: translation.x - (0.5 * self.view.frame.width), y: translation.y - (0.5 * self.view.frame.height))
// add nodes using the main thread
DispatchQueue.main.async {
guard let touchRootNode = self.touchRoots.last else { return }
let sphereNode : SCNNode = SCNNode(geometry: SCNSphere(radius: 0.015))
sphereNode.position = SCNVector3(-1*Float(translationFromCenter.x/1000), -1*Float(translationFromCenter.y/1000), 0)
sphereNode.geometry?.firstMaterial?.diffuse.contents = UIColor.white
touchRootNode.addChildNode(sphereNode) // add point to the active root
}
}
}
Note: solution only handles a single touch, but it is simple enough to extend the example to add multi-touch support.
Explanation
I'm trying to speed up my game when touching the screen (and decreasing its speeds when touching again). I kinda made it work, but not fully.
When touching the screen, all actions are removed and ran again (so it can read the new gameSpeed). But the sprites that were already spawned, won't be applied to the new action, so it still runs by the last gameSpeed. Is there any way to make all the sprites be speeded up?
Code
This is the test code I made (you can download it here):
import SpriteKit
class GameScene: SKScene {
// MARK: - Declare
lazy var frameHeight : CGFloat = CGFloat(self.frame.height)
var ball = SKSpriteNode()
var speeded = Bool()
var gameSpeed = CGFloat(1)
// MARK: - Setup
func setupBall(){
ball = SKSpriteNode(imageNamed: "ball")
ball.position = CGPointMake(self.frame.width / 2, self.frame.height)
}
// MARK: - Action
func actionsBall(){
// Spawn ball
self.setupBall()
self.addChild(self.ball)
// Move down and remove when go off screen
let duration = (NSTimeInterval((0.003 / gameSpeed) * frameHeight))
let moveBall = SKAction.moveByX(0, y: -frameHeight, duration: duration)
let removeBall = SKAction.removeFromParent()
self.ball.runAction(SKAction.sequence([moveBall, removeBall]))
}
// MARK: - First Event
override func didMoveToView(view: SKView) {
/* Setup your scene here */
// Start spawning, moving and removing
runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.waitForDuration((0.00075 / Double(gameSpeed)) * Double(frameHeight)), SKAction.runBlock(self.actionsBall)])))
}
// MARK: - Touch
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
if speeded == false{
speeded = true
gameSpeed = 2
removeAllActions()
runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.waitForDuration((0.00075 / Double(gameSpeed)) * Double(frameHeight)), SKAction.runBlock(self.actionsBall)])))
}
else if speeded == true{
speeded = false
gameSpeed = 1
removeAllActions()
runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.waitForDuration((0.00075 / Double(gameSpeed)) * Double(frameHeight)), SKAction.runBlock(self.actionsBall)])))
}
}
// MARK: - Update
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
Thanks in advance,
Luiz.
In the didMove method of your SKScene class, add the following:
physicsWorld.speed = CGFloat(2.0)
Most likely you simply didn't pass it as a CGFloat or it was not included in the proper method.
I am making a game where as the main sprite/player moves constantly, he/she needs to jump through barriers.
I need help with how to set a constant velocity for my moving sprite. When I try and do this in the SpriteKit update function, I can’t apply an impulse to jump whenever the user taps the screen.
Here is my code. I commented the places where I am having trouble:
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
/* Called when a touch begins */
if (gameStarted == false) {
gameStarted = true
mainSprite.physicsBody?.affectedByGravity = true
mainSprite.physicsBody?.allowsRotation = true
let spawn = SKAction.runBlock({
() in
self.createWalls()
})
let delay = SKAction.waitForDuration(1.5)
let spawnDelay = SKAction.sequence([spawn, delay])
let spawnDelayForever = SKAction.repeatActionForever(spawnDelay)
self.runAction(spawnDelayForever)
let distance = CGFloat(self.frame.height + wallPair.frame.height)
let movePipes = SKAction.moveByX(0, y: -distance - 50, duration: NSTimeInterval(0.009 * distance)) // Speed up pipes
let removePipes = SKAction.removeFromParent()
moveAndRemove = SKAction.sequence([movePipes, removePipes])
} else {
if died == true {
}
else {
mainSprite.physicsBody?.applyImpulse(CGVectorMake(0, 20)) // TRYING TO APPLY AN IMPULSE TO MY SPRITE SO IT CAN JUMP AS IT MOVES
}
}
for touch in touches {
let location = touch.locationInNode(self)
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
updateSpritePosition()
mainSprite.physicsBody?.velocity = CGVectorMake(400, 0) // SETS A CONSTANT VELOCITY, HOWEVER I CAN NOT APPLY AN IMPULSE.
}
The problem is that you're overwriting the velocity in the update method. So even though you added an impulse, it gets immediately overwritten by code in the update. Try overwriting just the dx part of the velocity.
override func update(currentTime: CFTimeInterval) {
updateSpritePosition()
mainSprite.physicsBody?.velocity = CGVectorMake(400, mainSprite.physicsBody?.velocity.dy)
}
I have this sene with few nodes in it. It is circle inside circle inside circle. Pressing on smallest circle inside, I have this animation made with few SKActions.
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
/* Called when a touch begins */
let growOut = SKAction.scaleTo(1.2, duration: 0.3)
let growIn = SKAction.scaleTo(1.0, duration: 0.5)
let glowOut = SKAction.fadeAlphaTo(0.5, duration: 0.3)
let glowIn = SKAction.fadeAlphaTo(1, duration: 0.5)
let sOut = SKAction.group([glowOut, growOut])
let sIn = SKAction.group([glowIn, growIn])
let circleTouched = SKAction.sequence([sOut, sIn])
let circleRepeat = SKAction.repeatActionForever(circleTouched)
for touch in touches {
let location = (touch as! UITouch).locationInNode(self)
if let theCircle = nodeAtPoint(location) as SKNode?{
if theCircle.name == "SmallCircle" {
theCircle.runAction(circleRepeat, withKey: "circleTouched")
}
}
}
}
When touch ends, I remove this action like so:
override func touchesEnded(touches: Set<NSObject>, withEvent event: UIEvent) {
let growIn = SKAction.scaleTo(1.0, duration: 0.5)
let glowIn = SKAction.fadeAlphaTo(1, duration: 0.5)
let sIn = SKAction.group([glowIn, growIn])
for touch in touches {
let location = (touch as! UITouch).locationInNode(self)
if let theCircle = nodeAtPoint(location) as SKNode?{
theCircle.runAction(sIn)
theCircle.removeActionForKey("circleTouched")
}
}
}
But when I move my finger out of this circle with actions on it, it keeps on playing. I've tried to fix it with touchesMoved function, but it acts kind of strange for me.
override func touchesMoved(touches: Set, withEvent event: UIEvent) {
let growIn = SKAction.scaleTo(1.0, duration: 0.5)
let glowIn = SKAction.fadeAlphaTo(1, duration: 0.5)
let sIn = SKAction.group([glowIn, growIn])
let circleRepeat = SKAction.repeatActionForever(circleTouched)
for touch in touches {
let location = (touch as! UITouch).locationInNode(self)
if let theCircle = nodeAtPoint(location) as SKNode?{
if theCircle.name != "SmallCircle" {
println("I'M MOVING FROM NODE!!!")
theCircle.runAction(sIn)
theCircle.removeActionForKey("circleTouched")
}
}
}
}
So I receive this "I'M OUT OF THE NODE" signal, but action don't stop.
Where am I wrong? The same code works for touchesEnded function.
The problem happens because of this one
if let theCircle = nodeAtPoint(location) as SKNode?
Everytime you move your mouse, "theCircle" resets. For example, for the first time, you click the circle, "theCircle" is the circle you clicked, hence the animation is attached to it. For the second time, say, you clicked the background, this time "theCircle" is the background, so it does not have the animation you set, therefore there is no way to remove "the animation".
The solution is, you declare the circle as a scope level variable, usually inside the class, at the top:
var smallCircle: SKSpriteNode!
Then in didMoveToView(view: SKView), configure the circle(if you use .sks):
smallCircle = childNodeWithName("the circle name") as! SKSpriteNode
smallCircle.name = "SmallCircle"
This time, you can point to the circle in touchMoved:
for touch in (touches as! Set<UITouch>) {
let location = touch.locationInNode(self)
if let theCircle = nodeAtPoint(location) as SKNode?{
if theCircle.name != "SmallCircle" {
smallCircle.runAction(sIn)
smallCircle.removeActionForKey("circleTouched")
}
}
}
At last, you will find the animation stopped.
I am trying to detect when the player object collides with the other objects in my game. This is my current code:
import SpriteKit
class GameScene: SKScene {
let player = SKSpriteNode(imageNamed: “Box”)
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.whiteColor()
player.position = CGPoint(x: size.width/2, y: size.height/2)
addChild(player)
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(addObject),
SKAction.waitForDuration(1)
])
))
runAction(SKAction.repeatActionForever(
SKAction.sequence([
SKAction.runBlock(addSecondObject),
SKAction.waitForDuration(1)
])
))
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
player.position = location
}
}
func EndGame() {
println("GAME OVER")
}
func Collision() {
if (CGRectIntersectsRect(player.frame, object.frame )) {
[EndGame];
}
if (CGRectIntersectsRect(player.frame, object1.frame)) {
[EndGame];
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
func addObject() {
let object = SKSpriteNode(imageNamed: "object1”)
object.name = "object1”
object.position = CGPoint(x: size.width/4, y: size.height/4)
self.addChild(object)
}
func addSecondObject() {
let object = SKSpriteNode(imageNamed: "object2”)
object.name = "object2”
object.position = CGPoint(x: size.width/2, y: size.height/2)
self.addChild(object)
}
}
So you can see my collision code is this:
func Collision() {
if (CGRectIntersectsRect(player.frame, object.frame )) {
[EndGame];
}
if (CGRectIntersectsRect(player.frame, object1.frame)) {
[EndGame];
}
}
The problem is that because object and object 1 variables are private to func (addObject) and func (addSecondObject), I can't call them in the above code. When they collide, currently I just want EndGame() to run which prints "Game Over" in the console.
I don't know if the method I have taken for collision detection is correct, but any help would be great! Thanks :)
For very basic sprites, yes, its right. You can imagine a diagonal line image "/" would trigger a collision if the top left corner of it overlapped with something, even though the actual object doesn't overlap.
Apple has a nice page discussing collisions here : https://developer.apple.com/library/ios/documentation/GraphicsAnimation/Conceptual/CodeExplainedAdventure/HandlingCollisions/HandlingCollisions.html
Also, numerous youtube videos talk about how to get this sort of thing working. eg : https://www.youtube.com/watch?v=dX0KvKtc3_w
You need to make it so the collision detection code runs(possible on a timer or something) and then, perhaps, pass in the objects you want to check or make those objects members of the class (IBOutlet) or similar strategy to gain access to them.
You have to use SKPhysicsbody:
struct PhysicsCategory {
static let None : UInt32 = 0
static let Player : UInt32 = 0b1
static let Object : UInt32 = 0b10
}
player.physicsBody = SKPhysicsBody(rectangleOfSize: player.size) // 1
player.physicsBody?.dynamic = true // 2
player.physicsBody?.categoryBitMask = PhysicsCategory.Player// 3
player.physicsBody?.contactTestBitMask = PhysicsCategory.Object//4
player.physicsBody?.collisionBitMask = PhysicsCategory.None // 5
You set the SKPhysicsBody and make it the size of your player-object
Then you make it dynamic. That the physicsbody is dynamic.
That is the categoryBitMask. It's like the identifier of your SKNode. So that other SKNodes can register on you and say: "Hey I want to know if I touch that guy".
That's the contactTestbitMask which handles which SKNodes should interact with you. So you say "If that node touches me, say something".
What should happen, if this node collides.
For more informations and a nice starter tutorial, use raywenderlichs tutorial.