Apologies in advance as this Question involves allot of code.
I have a GameScene with multiple layers holding Nodes/SpriteNodes for HUD/Scores/ etc. One layer, LayerProjectile, holds a Weapon Entity.
Said Entity has both Sprite and Physics Components.
Problem is the Projectile nodes in the LayerProjectile do not interact with nodes in the main layer (worldLayer), of the GameScene.
All physics bodies are dynamic, and the GameScene has its PhysicWorldDelgate
class GamePlayMode: SGScene, SKPhysicsContactDelegate {...
// Weapon Enity (SGEntity is a GKEntity)
class ThrowWeapon : SGEntity {
var spriteComponent: SpriteComponent!
var physicsComponent: PhysicsComponent
init(position: CGPoint, size: CGSize, texture:SKTexture){
super.init();
//Initilse Compnemnets
spriteComponent = SpriteComponent(entity: self, texture: texture, size: size, position: position);
addComponent(spriteComponent);
physicsComponent = PhysicsComponent(entity: self, bodySize: CGSize(width: spriteComponent.node.size.width * 0.8, height: spriteComponent.node.size.height * 0.8), bodyShape: .square, rotation: true);
physicsComponent.setCategoryBitmask(ColliderType.Projectile.rawValue, dynamic: true);
physicsComponent.setPhysicsCollisions(ColliderType.None.rawValue);
physicsComponent.setPhysicsContacts( ColliderType.Destroyable.rawValue | ColliderType.Enemy.rawValue);
physicsComponent.setEffectedByGravity(false);
addComponent(physicsComponent);
//Final Steps of compnements
spriteComponent.node.physicsBody = physicsComponent.physicsBody;
spriteComponent.node.name = "weapon";
name = "weapon";
spriteComponent.node.zPosition = 150;
spriteComponent.node.userData = ["startX" : position.x];
// Handle Contacts
override func contactWith(entity: SGEntity) {
//If contact with Enity in GemeScene
switch entity.name {
case "destructableEntity":
//Remove from Scsene self.parent?.parent?.runAction(SKAction.playSoundFileNamed("110548__noisehag__comp-cover-02.wav", waitForCompletion: false))
if let spriteComponentDestructable = entity.componentForClass(SpriteComponent.self){
let pos = spriteComponentDestructable.node.position;
spriteComponent.node.removeFromParent();
spriteComponentDestructable.node.parent?.runAction(SKAction.playSoundFileNamed("110548__noisehag__comp-cover-02.wav", waitForCompletion: false));
spriteComponentDestructable.node.removeAllActions();
spriteComponentDestructable.node.removeFromParent();
emitterExplosion(pos);
}
// self.runAction(SKAction.sequence([SKAction.fadeAlphaTo(0.0, duration: 0.5), SKAction.removeFromParent()]))
break;
case "enemyEntity":
if let spriteComponentEnemy = entity.componentForClass(SpriteComponent.self){
let pos = spriteComponentEnemy.node.position;
spriteComponent.node.removeFromParent();
spriteComponentEnemy.node.parent?.runAction(SKAction.playSoundFileNamed("110548__noisehag__comp-cover-02.wav", waitForCompletion: false));
spriteComponentEnemy.node.removeAllActions();
spriteComponentEnemy.node.removeFromParent();
emitterExplosion(pos);
} break;
default:
//
break;
}
LayerProjectile, which inherits from Layer- a SKNode, and which handles all weapon entities added to GameScene
class LayerProjectiles: Layer {
override func updateNodes(delta:CFTimeInterval,childNumber:Int,childNode: SKNode) {
print("Player movenment direction: \(playerMovingRighToLeft)");
if playerMovingRighToLeft {
// player moving to right so throw weapon same direction
childNode.position = childNode.position + CGPoint(x: delta * -weaponSpeed, y: 0.0);
childNode.zRotation = childNode.zRotation - (CGFloat(delta) * 10);
} else{
childNode.position = childNode.position + CGPoint(x: delta * weaponSpeed, y: 0.0)
childNode.zRotation = childNode.zRotation - (CGFloat(delta) * 10)
}
// When arrow within this layer moves out of the screen remove it, and the reverse
if childNode.position.x > ((childNode.userData!["startX"] as! CGFloat) + (SKMSceneSize!.width * 1.2)) || childNode.position.x < ((childNode.userData!["startX"]as! CGFloat) - (SKMSceneSize!.width * 1.2)){
childNode.removeFromParent()
}
}
Adding Instance of LayerProjectile and worldLayer (holding all other nodes, some of which have catBitMask set to notify when contact with Weapon Entity), to the GameScene (an instance of GamePlayMode, a SGScene)
class GameSceneState: GKState {
unowned let gs: GamePlayMode
init(scene: GamePlayMode) {
self.gs = scene
}
}
gs.physicsWorld.contactDelegate = gs
gs.physicsWorld.gravity = CGVector(dx: 0.0, dy: -1.0)
//Layers
gs.layerProjectile = LayerProjectiles();
gs.worldLayer = TileLayer(levelIndex: gs.levelIndex, typeIndex: .setMain)
gs.backgroundLayer = SKNode()
gs.overlayGUI = SKNode()
gs.addChild(gs.worldLayer)
gs.addChild(gs.layerProjectile);// For throwing wepaons, made in GamePlayMode
And finally the physics/sprite component settings for a Destrutable entity, which is a child of worldLayer (added to the GaemScene along with LayerProjectile)
class Destructable : SGEntity{
var spriteComponent: SpriteComponent!
var animationComponent: AnimationComponent!
var physicsComponent: PhysicsComponent!
var scrollerComponent: FullControlComponent!
init(position: CGPoint, size: CGSize, texture:SKTexture){
super.init();
//Initilse Compnemnets
spriteComponent = SpriteComponent(entity: self, texture: texture, size: size, position: position);
addComponent(spriteComponent);
physicsComponent = PhysicsComponent(entity: self, bodySize: CGSize(width: spriteComponent.node.size.width * 0.8, height: spriteComponent.node.size.height * 0.8), bodyShape: .square, rotation: false);
physicsComponent.setCategoryBitmask(ColliderType.Destroyable.rawValue , dynamic: true);
physicsComponent.setPhysicsCollisions(ColliderType.Wall.rawValue | ColliderType.Player.rawValue | ColliderType.Destroyable.rawValue); // collides with so bpounces off
physicsComponent.setPhysicsContacts(ColliderType.Wall.rawValue | ColliderType.Player.rawValue | ColliderType.Projectile.rawValue);
physicsComponent.setEffectedByGravity(true);
addComponent(physicsComponent);
//Final Steps of compnements
spriteComponent.node.physicsBody = physicsComponent.physicsBody;
spriteComponent.node.name = "destructableEntity";
name = "destructableEntity";
spriteComponent.node.zPosition = 150;
//spriteComponent.node.userData = ["startX" : position.x];
}
// Handle contacts
override func contactWith(entity: SGEntity) {
//
if entity.name == "weapon"{
print("Crate hit Projectile");
}
}
Instantionation of the Weapon Projectile (when user hits throw button during game play) and Destructible Entities when initializing GameScene and adding all sprite nodes:
let throwWeapon = ThrowWeapon(position: CGPoint(x: self.spriteComponent.node.position.x, y: self.spriteComponent.node.position.y + (self.spriteComponent.node.size.height / 2)), size: CGSize(width: 9, height: 40), texture: textureAtlas.textureNamed("kunai"));
playerEnt.gameScene.layerProjectile.addChild(throwWeapon.spriteComponent.node);
// Destructible Entity
gs.worldLayer.enumerateChildNodesWithName("placeholder_Destructable") { (node, stop) -> Void in
let crate = Destructable(position: node.position, size: CGSize(width: 32, height: 32), texture: tileAtlasInstance.textureNamed("Crate"));
//crate.name = "placeholder_Destructable";
//crate.spriteComponent.node.zPosition = GameSettings.GameParams.zValues.zWorldFront;
self.gs.addEntity(crate, toLayer: self.gs.worldLayer)
}
Any input appreciated.
Added: Category bitmask enum for ColliderTypes:
enum ColliderType:UInt32 {
case Player = 0
case Destroyable = 0b1
case Wall = 0b10
case Collectable = 0b100 // get points
case EndLevel = 0b1000
case Projectile = 0b10000
case None = 0b100000
case KillZone = 0b1000000
case arrow = 0b10000000;
case fire = 0b100000000; // die
case blueFire = 0b1000000000; // die
case greenCrystal = 0b10000000000; //more points like collectables
case barrel1 = 0b100000000000; //die
case barrell2 = 0b1000000000000; //die
case swordPoints = 0b10000000000000; //points
case spikes = 0b100000000000000; //die
case Enemy = 0b1000000000000000; // see Eneemy Entity
// against arrow and fire
}
// The SGEnity class where func handling physics contacts for Enities are called and overridden in Entity (Weapon, Destructible) classes
class SGEntity: GKEntity {
var name = ""
func contactWith(entity:SGEntity) {
//Overridden by subclass
}
// PhyscisComponent
enum PhysicsBodyShape {
case square
case squareOffset;// so the player knowns where the tile physics body is
case circle
case topOutline
case bottomOutline
}
class PhysicsComponent: GKComponent {
var physicsBody = SKPhysicsBody()
init(entity: GKEntity, bodySize: CGSize, bodyShape: PhysicsBodyShape, rotation: Bool) {
switch bodyShape {
case.square:
physicsBody = SKPhysicsBody(rectangleOfSize: bodySize)
break
case.squareOffset:
physicsBody = SKPhysicsBody(rectangleOfSize: bodySize, center: CGPoint(x: 0, y: bodySize.height/2 + 2))
break
case .circle:
physicsBody = SKPhysicsBody(circleOfRadius: bodySize.width / 2)
break
case .topOutline:
physicsBody = SKPhysicsBody(edgeFromPoint: CGPoint(x: (bodySize.width/2) * -1, y: bodySize.height/2), toPoint: CGPoint(x: bodySize.width/2, y: bodySize.height/2))
break
case .bottomOutline:
physicsBody = SKPhysicsBody(edgeFromPoint: CGPoint(x: (bodySize.width/2) * -1, y: (bodySize.height/2) * -1), toPoint: CGPoint(x: bodySize.width/2, y: (bodySize.height/2) * -1))
break
}
physicsBody.allowsRotation = rotation
//Defaults
physicsBody.dynamic = true
physicsBody.contactTestBitMask = ColliderType.None.rawValue
physicsBody.collisionBitMask = ColliderType.None.rawValue
}
func setCategoryBitmask(bitmask:UInt32, dynamic: Bool) {
physicsBody.categoryBitMask = bitmask
physicsBody.dynamic = dynamic
}
func setPhysicsCollisions(bitmask:UInt32) {
physicsBody.collisionBitMask = bitmask
}
func setPhysicsContacts(bitmask:UInt32) {
physicsBody.contactTestBitMask = bitmask
}
func setEffectedByGravity (gravity: Bool){
physicsBody.affectedByGravity = gravity;
}
}
So, contactsBegan is called via PhysicsDelage of the GameScene, :
if let bodyA = contact.bodyA.node as? EntityNode, //SpriteNode
let bodyAent = bodyA.entity as? SGEntity, // COnverted to SGEntity
let bodyB = contact.bodyB.node as? EntityNode, //SprtiteNode
let bodyBent = bodyB.entity as? SGEntity //ERROR HERE : Cast to SGEnity
{
print("SGENity Description: \(bodyBent)");
contactBegan(bodyAent, nodeB: bodyBent)
contactBegan(bodyBent, nodeB: bodyAent)
}else{
print("Cast error")
}
Issue appears to be casting the Projectile Entity to a SGEntity, this fails and contactsWith... of Projectile Entity is never called. All other SGEnities (Destructible) cast to SGEntity with no issues.,...
Related
I implemented #mfessenden suggestion regarding using a custom SKAction to match the SKPhysicsBody to follow the animation using alphaThreshold. ( https://stackoverflow.com/a/54096147 ) But it does not appear to be functioning for me. Where have I gone wrong? Can you help #mfessenden?
var rockFrames: [SKTexture] = []
let rockAnimatedAtlas = SKTextureAtlas(named: "funny")
let numImages = rockAnimatedAtlas.textureNames.count
for i in 1...numImages {
let rockTextureName = "\(i)"
rockFrames.append(rockAnimatedAtlas.textureNamed(rockTextureName))
}
let firstFrameTexture = rockFrames[0]
rock = SKSpriteNode(texture: firstFrameTexture)
rock.position = CGPoint(x: self.frame.midX, y: self.frame.midY + 100)
rock.size = CGSize(width: 480 * wR, height: 270 * hR)
self.addChild(rock)
let rockaction = SKAction.animate(with: rockFrames,
timePerFrame: (1/24),
resize: false,
restore: true)
rock.run(SKAction.repeatForever(rockaction))
typealias Frame = (texture: SKTexture, duration: TimeInterval)
let timePerFrame: TimeInterval = 1/24
let dogFrames: [Frame] = rockFrames.map {
return ($0, timePerFrame)
}
rock = SKSpriteNode(texture: rockFrames.first)
let dogAnimationAction = animateTexturesWithPhysics(dogFrames)
rock.run(dogAnimationAction)
public func animateTexturesWithPhysics(_ frames: [(texture: SKTexture, duration: TimeInterval)], repeatForever: Bool=true) -> SKAction {
var actions: [SKAction] = []
for frame in frames {
// define a custom action for each frame
let customAction = SKAction.customAction(withDuration: frame.duration) { node, _ in
// if the action target is a sprite node, apply the texture & physics
if node is SKSpriteNode {
let setTextureGroup = SKAction.group([
SKAction.setTexture(frame.texture, resize: false),
SKAction.wait(forDuration: frame.duration),
SKAction.run {
node.physicsBody = SKPhysicsBody(texture: frame.texture, alphaThreshold: 0.5, size: frame.texture.size())
node.physicsBody?.isDynamic = false
node.physicsBody?.categoryBitMask = self.arrowCategory
// add physics attributes here
}
])
node.run(setTextureGroup)
}
}
actions.append(customAction)
}
// add the repeating action
if (repeatForever == true) {
return SKAction.repeatForever(SKAction.sequence(actions))
}
return SKAction.sequence(actions)
}
I create a sphere node, I need the user to be able only to rotate (left / right, up / down) and zoom in / out the node, but default he can move the node from the center (with two fingers) - is possible prohibit the user to move the node from the center? thanks for any help
sceneView.scene = scene
cameraOrbit = SCNNode()
cameraNode = SCNNode()
camera = SCNCamera()
// camera stuff
camera.usesOrthographicProjection = true
camera.orthographicScale = 5
camera.zNear = 1
camera.zFar = 100
cameraNode.position = SCNVector3(x: 0, y: 0, z: 70)
cameraNode.camera = camera
cameraOrbit = SCNNode()
cameraOrbit.addChildNode(cameraNode)
scene.rootNode.addChildNode(cameraNode)
let sphere = SCNSphere(radius: 2)
sphere.firstMaterial?.diffuse.contents = UIColor.red
let earthNode = SCNNode(geometry: sphere)
earthNode.name = "sphere"
earthNode.geometry?.materials = [blueMaterial]
scene.rootNode.addChildNode(earthNode)
earthNode.rotation = SCNVector4(0, 1, 0, 0)
let lightNode = SCNNode()
let light = SCNLight()
light.type = .ambient
light.intensity = 200
lightNode.light = light
scene.rootNode.addChildNode(lightNode)
sceneView.allowsCameraControl = true
sceneView.backgroundColor = UIColor.clear
sceneView.cameraControlConfiguration.allowsTranslation = true
sceneView.cameraControlConfiguration.rotationSensitivity = 0.4
You can put similar code into your UIViewController:
//**************************************************************************
// Gesture Recognizers
// MARK: Gesture Recognizers
//**************************************************************************
#objc func handleTap(recognizer: UITapGestureRecognizer)
{
if(data.isNavigationOff == true) { return } // No panel select if Add, Update, EndWave, or EndGame
if(gameMenuTableView.isHidden == false) { return } // No panel if game menu is showing
let location: CGPoint = recognizer.location(in: gameScene)
if(data.isAirStrikeModeOn == true)
{
let projectedPoint = gameScene.projectPoint(SCNVector3(0, 0, 0))
let scenePoint = gameScene.unprojectPoint(SCNVector3(location.x, location.y, CGFloat(projectedPoint.z)))
gameControl.airStrike(position: scenePoint)
}
else
{
let hitResults = gameScene.hitTest(location, options: hitTestOptions)
for vHit in hitResults
{
if(vHit.node.name?.prefix(5) == "Panel")
{
// May have selected an invalid panel or auto upgrade was on
if(gameControl.selectPanel(vPanel: vHit.node.name!) == false) { return }
return
}
}
}
}
//**************************************************************************
#objc func handlePan(recognizer: UIPanGestureRecognizer)
{
if(data.gameState != .run || data.isGamePaused == true) { return }
currentLocation = recognizer.location(in: gameScene)
switch recognizer.state
{
case UIGestureRecognizer.State.began:
beginLocation = recognizer.location(in: gameScene)
break
case UIGestureRecognizer.State.changed:
if(currentLocation.x > beginLocation.x * 1.1)
{
beginLocation.x = currentLocation.x
gNodes.camera.strafeLeft()
}
if(currentLocation.x < beginLocation.x * 0.9)
{
beginLocation.x = currentLocation.x
gNodes.camera.strafeRight()
}
break
case UIGestureRecognizer.State.ended:
break
default:
break
}
}
Yes, all of that is doable. First, create your own camera class and turn off allowsCameraControl. Then you can implement zoom/strafe/whatever.
Here are some examples that may help, just search for these numbers in the stack search bar and find my answers/examples.
57018359 - this post one tells you how to touch a 2d screen (tap) and translate it to 3d coordinates with you deciding the depth (z), like if you wanted to tap the screen and place an object in 3d space.
57003908 - this post tells you how to select an object with a hitTest (tap). For example, if you showed the front of a house with a door and tap it, then the function would return your door node provided you name the node "door" and took some kind of action when it's touched. Then you could reposition your camera based on that position. You'll want to go iterate through all results because there might be overlapping or plus Z nodes
55129224 - this post gives you quick example of creating a camera class. You can use this to reposition your camera or move it forward and back, etc.
Two finger drag:
func dragBegins(vRecognizer: UIPanGestureRecognizer)
{
if(data.gameState == .run)
{
if(vRecognizer.numberOfTouches == 2) { dragMode = .strafe }
}
}
class Camera
{
var data = Data.sharedInstance
var util = Util.sharedInstance
var gameDefaults = Defaults()
var cameraEye = SCNNode()
var cameraFocus = SCNNode()
var centerX: Int = 100
var strafeDelta: Float = 0.8
var zoomLevel: Int = 35
var zoomLevelMax: Int = 35 // Max number of zoom levels
//********************************************************************
init()
{
cameraEye.name = "Camera Eye"
cameraFocus.name = "Camera Focus"
cameraFocus.isHidden = true
cameraFocus.position = SCNVector3(x: 0, y: 0, z: 0)
cameraEye.camera = SCNCamera()
cameraEye.constraints = []
cameraEye.position = SCNVector3(x: 0, y: 15, z: 0.1)
let vConstraint = SCNLookAtConstraint(target: cameraFocus)
vConstraint.isGimbalLockEnabled = true
cameraEye.constraints = [vConstraint]
}
//********************************************************************
func reset()
{
centerX = 100
cameraFocus.position = SCNVector3(x: 0, y: 0, z: 0)
cameraEye.constraints = []
cameraEye.position = SCNVector3(x: 0, y: 32, z: 0.1)
cameraFocus.position = SCNVector3Make(0, 0, 0)
let vConstraint = SCNLookAtConstraint(target: cameraFocus)
vConstraint.isGimbalLockEnabled = true
cameraEye.constraints = [vConstraint]
}
//********************************************************************
func strafeRight()
{
if(centerX + 1 < 112)
{
centerX += 1
cameraEye.position.x += strafeDelta
cameraFocus.position.x += strafeDelta
}
}
//********************************************************************
func strafeLeft()
{
if(centerX - 1 > 90)
{
centerX -= 1
cameraEye.position.x -= strafeDelta
cameraFocus.position.x -= strafeDelta
}
}
//********************************************************************
}
//********************************************************************
func lerp(start: SCNVector3, end: SCNVector3, percent: Float) -> SCNVector3
{
let v3 = cgVecSub(v1: end, v2: start)
let v4 = cgVecScalarMult(v: v3, s: percent)
return cgVecAdd(v1: start, v2: v4)
}
I'm working on my first SpriteKit project and I'm having problems getting collisions to work. The objects just pass right through each other. I thought just setting the category, collision and contact bitmasks would be all that was required. I'm defining my two objects like so:
final class Paddle: SKShapeNode {
init(isLeft: Bool) {
super.init()
...
path = CGPath(rect: rect, transform: nil)
let body = SKPhysicsBody(rectangleOf: rect.size)
body.category(.paddle, collidesWith: .ball, isDynamic: false)
physicsBody = body
}
}
final class Ball: SKShapeNode {
override init() {
super.init()
...
let rect = CGRect(x: 0, y: 0, width: radius * 2, height: radius * 2)
path = CGPath(ellipseIn: rect, transform: nil)
let body = SKPhysicsBody(circleOfRadius: radius)
body.category(.ball, collidesWith: .paddle)
body.linearDamping = 0
body.angularDamping = 0
body.restitution = 1.0
physicsBody = body
}
}
and then I added a helper extension to setup the common physics body properties:
extension SKPhysicsBody {
func category(_ category: ShapeType, collidesWith: ShapeType, isDynamic: Bool = true) {
categoryBitMask = category.rawValue
collisionBitMask = collidesWith.rawValue
contactTestBitMask = collidesWith.rawValue
fieldBitMask = 0
allowsRotation = false
affectedByGravity = false
friction = 0
restitution = 0
self.isDynamic = isDynamic
}
}
I'm trying to have a contact to be detected by SpriteKit in Swift 4. I set a flipper to a certain category and a ball to another one. Then I set their collision and contact mask to each other, I add a contactDelegate in didMove to, and print "contact" to inform that there is a contact in a didEnterFonction. However I cannot detect any contact at all. Even after following all the tutorials, and scouring through similar questions. I do not understand what I am doing wrong. My end goal is to have the ball stop from falling when it hits the flipper, but I am trying to detect any contact at all in the first place which do not work.
import SpriteKit
import GameplayKit
struct PhysicsCategory {
static let none : UInt32 = 0
static let all : UInt32 = UInt32.max
static let flip : UInt32 = 0b1
static let ball: UInt32 = 0b10 // 2
}
class GameScene: SKScene, SKPhysicsContactDelegate {
var entities = [GKEntity]()
var graphs = [String : GKGraph]()
private var lastUpdateTime : TimeInterval = 0
private var label : SKLabelNode?
private var flip1: SKSpriteNode?
private var flip2: SKSpriteNode?
private var spinnyNode : SKShapeNode?
private var balle:SKSpriteNode?
override func sceneDidLoad() {
self.lastUpdateTime = 0
self.flip1 = self.childNode(withName:"//flip1") as? SKSpriteNode
self.flip2 = self.childNode(withName: "//flip2") as? SKSpriteNode
self.balle = self.childNode(withName: "//ball") as?SKSpriteNode
self.flip1?.physicsBody = SKPhysicsBody()
self.flip1?.physicsBody?.affectedByGravity = false
self.flip1?.physicsBody?.allowsRotation = true
self.balle?.physicsBody = SKPhysicsBody();
//self.balle?.physicsBody?.affectedByGravity = true
self.flip2?.physicsBody = SKPhysicsBody()
self.flip2?.physicsBody?.affectedByGravity = false
self.flip2?.physicsBody?.allowsRotation = true
self.balle?.physicsBody?.allContactedBodies()
self.flip1?.physicsBody?.usesPreciseCollisionDetection
= true
self.flip1?.physicsBody?.categoryBitMask = PhysicsCategory.flip
self.flip1?.physicsBody?.contactTestBitMask = PhysicsCategory.ball
self.flip1?.physicsBody?.collisionBitMask = PhysicsCategory.ball
self.flip2?.physicsBody?.categoryBitMask = PhysicsCategory.flip
self.flip2?.physicsBody?.contactTestBitMask = PhysicsCategory.ball
self.flip2?.physicsBody?.collisionBitMask = PhysicsCategory.ball
// Get label node from scene and store it for use later
self.label = self.childNode(withName: "//helloLabel") as? SKLabelNode
if let label = self.label {
label.alpha = 0.0
label.run(SKAction.fadeIn(withDuration: 2.0))
}
// Create shape node to use during mouse interaction
let w = (self.size.width + self.size.height) * 0.05
self.spinnyNode = SKShapeNode.init(rectOf: CGSize.init(width: w, height: w), cornerRadius: w * 0.3)
if let spinnyNode = self.spinnyNode {
spinnyNode.lineWidth = 2.5
spinnyNode.run(SKAction.repeatForever(SKAction.rotate(byAngle: CGFloat(Double.pi), duration: 1)))
spinnyNode.run(SKAction.sequence([SKAction.wait(forDuration: 0.5),
SKAction.fadeOut(withDuration: 0.5),
SKAction.removeFromParent()]))
}
}
override func keyDown(with event: NSEvent) {
switch event.keyCode {
case 0x31:
if let label = self.label {
label.run(SKAction.init(named: "Pulse")!, withKey: "fadeInOut")
}
case 123:
print("left")
self.flip1?.run(SKAction.rotate(byAngle: -1.5, duration: 0.2))
// self.flip1?.physicsBody?.applyAngularImpulse(600)
self.flip1?.run(SKAction.rotate(byAngle: 1.5, duration: 0.1))
case 124:
self.flip2?.run(SKAction.rotate(byAngle: 1.5, duration: 0.2))
self.flip2?.run(SKAction.rotate(byAngle: -1.5, duration: 0.1))
case 15:
self.balle?.run(SKAction.move(to: CGPoint(x: 50,y: 50), duration: 1))
default:
print("keyDown: \(event.characters!) keyCode: \(event.keyCode)")
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
// Initialize _lastUpdateTime if it has not already been
if (self.lastUpdateTime == 0) {
self.lastUpdateTime = currentTime
}
// Calculate time since last update
let dt = currentTime - self.lastUpdateTime
// Update entities
for entity in self.entities {
entity.update(deltaTime: dt)
}
self.lastUpdateTime = currentTime
}
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
}
func didEnter(_ contact: SKPhysicsContact) {
print("contact")
}
}
You haven't given the physic bodies a shape or size. Take a look here and read the 'Creating a Body from a ___' sections.
I have a tileMap and within it some tileNodes, I am looking to only give physics to the tilesNodes that are within the screen view and as the player navigates the world, the tileNodes that come into screen view get physics and the tileNodes that go out of screen view get their physics removed. Any help would be appreciated, even if it means to completely redo my code.
Thank you.
Here is how i been adding physics to my tiles...
static func addPhysicsTo(map: SKTileMapNode){
let tileSize = map.tileSize
let halfWidth = CGFloat(map.numberOfColumns) / 2.0 * tileSize.width
let halfHeight = CGFloat(map.numberOfRows) / 2.0 * tileSize.height
for col in 0..<map.numberOfColumns
{
for row in 0..<map.numberOfRows
{
let tileDefinition = map.tileDefinition(atColumn: col, row: row)
let isUsedTile = tileDefinition?.userData?["Tile"] as? Bool
if (isUsedTile ?? false)
{
let x = CGFloat(col) * tileSize.width - halfWidth
let y = CGFloat(row) * tileSize.height - halfHeight
let rect = CGRect(x: 0, y: 0, width: tileSize.width, height: tileSize.height)
let tileNode = SKShapeNode(rect: rect)
tileNode.position = CGPoint(x: x, y: y)
tileNode.physicsBody = SKPhysicsBody.init(rectangleOf: tileSize, center: CGPoint(x: tileSize.width / 2.0, y: tileSize.height / 2.0))
tileNode.physicsBody?.isDynamic = true
tileNode.physicsBody?.allowsRotation = false
tileNode.physicsBody?.affectedByGravity = false
tileNode.physicsBody?.pinned = true
tileNode.physicsBody?.density = 200.0
tileNode.physicsBody?.categoryBitMask = GameConstants.physicsConstants.tileCategory
tileNode.physicsBody?.collisionBitMask = GameConstants.physicsConstants.playerCategory
//tileNode.physicsBody?.contactTestBitMask = GameConstants.physicsConstants.allCategory
map.addChild(tileNode)
}
}
}
}
The player adds an impulse to the Hero with this function in touchesBegun...
static func applyImpulse(sprite: SKSpriteNode, touchLocationX: CGFloat, touchLocationY: CGFloat) {
let impulseX = (sprite.position.x - touchLocationX ) / 10
let impulseY = (sprite.position.y - touchLocationY ) / 20
sprite.physicsBody?.applyImpulse(CGVector(x: impulseX , y: impulseY))
}
I created a tileNode Class that will activated and deactivate nodes, i just don't know what the rest of the code should look like...
class TileNodes: SKSpriteNode {
var isTileActivated: Bool = false
{
didSet {
physicsBody = isTileActivated ? activatedBody : nil
}
}
private var activatedBody: SKPhysicsBody?
init(with size: CGSize) {
super.init(texture: nil, color: UIColor.clear, size: size)
//I ASSUME THE CODE GOES HERE
physicsBody = isTileActivated ? activatedBody : nil
name = GameConstants.nameConstants.tileName
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Based on some of the things Ive found online, it seems like I need to use didSimulatePhysics() but i don't know how..
override func didSimulatePhysics() {
for node in tileMap[GameConstants.nameConstants.tileName] {
if let tileNode = node as? TileNodes {
}
}
}