i have a Problem with the method didBeginContact. I have a ball and a wall.
The collision of both objects works fine. If both objects collide, they change their position but the method didBeginContact is not called.
// constants.swift
...
let ballCategory: UInt32 = 0x1 << 0
let wallCategory: UInt32 = 0x1 << 1
...
// Physics balls (ball.swift)
...
init() {
let size = CGSize(width: 32, height: 44)
loadPhysicsBodyWithSize(size: size)
}
func loadPhysicsBodyWithSize(size: CGSize){
physicsBody = SKPhysicsBody(rectangleOf: size)
physicsBody?.categoryBitMask = ballCategory
physicsBody?.contactTestBitMask = wallCategory
physicsBody?.affectedByGravity = false
}
...
// Physics wall (wall.swift)
...
init() {
let size = CGSize(width: 32, height: 44)
loadPhysicsBodyWithSize(size: size)
}
func loadPhysicsBodyWithSize(size: CGSize){
physicsBody = SKPhysicsBody(rectangleOf: size)
physicsBody?.categoryBitMask = wallCategory
physicsBody?.affectedByGravity = false
}
...
// GameScene (gameScene.swift)
class GameScene: SKScene, SKPhysicsContactDelegate {
...
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
}
func didBeginContact(contact: SKPhysicsContact){
print("didBeginContact called")
}
}
Has anybody any idea? Thanks for help!
I solved this by changing
func didBeginContact(contact: SKPhysicsContact)
to
func didBegin(_ contact: SKPhysicsContact)
Related
I am classifying images per frame from ARSession delegate by Vision framework and CoreML in an Augmented Reality app, with ARKit and RealityKit. While processing a frame.capturedImage I am not requesting another frame.capturedImage for performance.
The camera is not giving smooth experience, it gets stuck time to time. Seems like a frame loss.
And I am getting this Warning:
[Session] ARSession <0x122cc3710>: ARSessionDelegate is retaining 14 ARFrames. This can lead to future camera frames being dropped.
My Codes:
import Foundation
import SwiftUI
import RealityKit
import ARKit
import CoreML
struct ARViewContainer: UIViewRepresentable {
var errorFunc: ()->Void
var frameUpdateFunc: ()->Void
#Binding var finalLabel:String
func makeUIView(context: Context) -> ARView {
let arView = ARView(frame: .zero)
let config = ARWorldTrackingConfiguration()
config.planeDetection = [.horizontal,.vertical]
config.environmentTexturing = .automatic
if ARWorldTrackingConfiguration.supportsSceneReconstruction(.mesh){
config.sceneReconstruction = .mesh
}
arView.session.delegate = context.coordinator
arView.session.run(config)
context.coordinator.myView = arView
return arView
}
func updateUIView(_ uiView: ARView, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(finalLabel: $finalLabel, self, funct: self.errorFunc, frameUpdateFunc: self.frameUpdateFunc)
}
class Coordinator: NSObject, ARSessionDelegate {
var objectDetectionService = ObjectDetectionService()
var myView:ARView?
#Binding var finalLabel:String
var parent: ARViewContainer
var efunc:()->Void
var frameUpdateFunc:()->Void
var isLoopShouldContinue = true
var lastLocation: SCNVector3?
//let model = try? MobileNetV2(configuration: .init())
private let classifier = VisionClasifier(mlModel: try? MobileNetV2(configuration: .init()).model)
private var currentBuffer: CVPixelBuffer? = nil
init(finalLabel:Binding<String>,_ arView: ARViewContainer,funct: #escaping ()->Void, frameUpdateFunc:#escaping ()->Void) {
parent = arView
self.efunc = funct
self.frameUpdateFunc = frameUpdateFunc
_finalLabel = finalLabel
}
func session(_ session: ARSession, didFailWithError error: Error) {
//print("Error Tanvir: ",error)
self.efunc()
}
func session(_ session: ARSession, didUpdate frame: ARFrame) {
if isLoopShouldContinue{
self.classifyFrame(currentFrame: frame)
}
let transform = SCNMatrix4(frame.camera.transform)
let orientation = SCNVector3(-transform.m31, -transform.m32, transform.m33)
let location = SCNVector3(transform.m41, transform.m42, transform.m43)
let currentPositionOfCamera = orientation + location
if let lastLocation = lastLocation {
let speed = (lastLocation - currentPositionOfCamera).length()
isLoopShouldContinue = speed < 0.0025
}
lastLocation = currentPositionOfCamera
}
// When ARKit detects a new anchor, it will add it to the ARSession
// Whenever there is a newly added ARAnchor, you will get that anchor here.
// In this short tutorial, we will target the ARPlaneAnchor, and use the information stored
// in that anchor for visualization.
func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
guard let myView = myView else {
return
}
for anchor in anchors {
if anchor is ARPlaneAnchor {
let planeAnchor = anchor as! ARPlaneAnchor
//addPlaneEntity(with: planeAnchor, to: myView)
}
}
}
// ARKit will automatically track and update the ARPlaneAnchor.
// We use that anchor to update the `skin` of the plane.
func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) {
guard let myView = myView else {
return
}
for anchor in anchors {
if anchor is ARPlaneAnchor {
let planeAnchor = anchor as! ARPlaneAnchor
//updatePlaneEntity(with: planeAnchor, in: myView)
}
}
}
// When ARKit remove an anchor from the ARSession, you will get the removed
// anchor here.
func session(_ session: ARSession, didRemove anchors: [ARAnchor]) {
guard let myView = myView else {
return
}
for anchor in anchors {
if anchor is ARPlaneAnchor {
let planeAnchor = anchor as! ARPlaneAnchor
//removePlaneEntity(with: planeAnchor, from: myView)
}
}
}
func addAnnotation(rectOfInterest rect: CGRect, text: String,width:Float,height:Float) {
let point = CGPoint(x: rect.midX, y: rect.midY)
print("point:", point)
//let scnHitTestResults = myView.hitTest(point,
// options: [SCNHitTestOption.searchMode: SCNHitTestSearchMode.all.rawValue])
//guard !scnHitTestResults.contains(where: { $0.node.name == BubbleNode.name }) else { return }
let raycastResult = myView!.raycast(from: point, allowing: .estimatedPlane, alignment: .any)
// guard let raycastQuery = myView!.raycastQuery(from: point,
// allowing: .existingPlaneInfinite,
// alignment: .horizontal),
// let raycastResult = myView.session.raycast(raycastQuery).first else { return }
guard let raycastResult = raycastResult.first else{
print("raycast result failed")
return
}
let anchorExists = myView!.scene.anchors.contains(where: {$0.name == text})
guard anchorExists == false else{
print("anchor Already exists")
return
}
let position = raycastResult.worldTransform.columns.3
let myEntity = create2dEntity(with: position, boundingBox: rect, raycastResult: raycastResult,width:width ,height:height)
let planeAnchorEntity = AnchorEntity()
planeAnchorEntity.name = text
planeAnchorEntity.position = simd_make_float3(position)
planeAnchorEntity.addChild(myEntity)
// Finally, add the entity to scene.
myView!.scene.addAnchor(planeAnchorEntity)
print("anchor added: ", planeAnchorEntity.name)
}
func classifyFrame(currentFrame:ARFrame){
//let currentImageName = photos[currentIndex]
// 2
// 3
print("inside Classify")
//print("CurrentBuffer", currentBuffer)
guard self.currentBuffer == nil else {
//print("CurrentBuffer: ",currentBuffer)
//self.finalLabel = "current buffer problem"
return
}
self.currentBuffer = currentFrame.capturedImage
// guard let model = self.model else {
// return "Model not Found."
// }
let img = CIImage(cvImageBuffer: currentFrame.capturedImage)
let cgImage = convertCIImageToCGImage(inputImage: img)
guard let cgImage = cgImage else{
print("can not convert CGImage")
self.finalLabel = "can not convert CGImage"
return
}
objectDetectionService.detect(on: .init(pixelBuffer: currentFrame.capturedImage)) { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let response):
self.finalLabel = response.classification.description
print("Real Width: ",response.boundingBox.width)
let rectOfInterest = VNImageRectForNormalizedRect(
response.boundingBox,
Int(self.myView!.bounds.width),
Int(self.myView!.bounds.height))
self.addAnnotation(rectOfInterest: rectOfInterest, text: response.classification.description,width: Float(response.boundingBox.width),height: Float(response.boundingBox.height))
print("Success:",response.classification.description)
self.currentBuffer = nil
case .failure(let error):
self.finalLabel = "Detection Failed"
print("Detection failure: ",error.localizedDescription)
self.currentBuffer = nil
break
}
}
}
}
}
func convertCIImageToCGImage(inputImage: CIImage) -> CGImage? {
let context = CIContext(options: nil)
if let cgImage = context.createCGImage(inputImage, from: inputImage.extent) {
return cgImage
}
return nil
}
// The ARPlaneAnchor contains the information we need to create the `skin` of the plane.
func addPlaneEntity(with anchor: ARPlaneAnchor, to view: ARView) {
let planeAnchorEntity = AnchorEntity(.plane([.any],
classification: [.any],
minimumBounds: [0.01, 0.01]))
let planeModelEntity = createPlaneModelEntity(with: anchor)
// Give Entity a name for tracking.
planeAnchorEntity.name = anchor.identifier.uuidString + "_anchor"
planeModelEntity.name = anchor.identifier.uuidString + "_model"
// Add ModelEntity as a child of AnchorEntity.
// AnchorEntity handles `position` of the plane.
// ModelEntity handles the `skin` of the plane.
planeAnchorEntity.addChild(planeModelEntity)
// Finally, add the entity to scene.
view.scene.addAnchor(planeAnchorEntity)
}
func create2dEntity(with position: simd_float4, boundingBox: CGRect, raycastResult:ARRaycastResult, width:Float,height:Float ) -> ModelEntity{
var planeMesh: MeshResource
var color: UIColor
print("horizotal plane")
color = UIColor.red.withAlphaComponent(0.5)
print("Constant width: 0.1 but BoundingBox Width: ",boundingBox.width)
planeMesh = .generatePlane(width: 0.1, height: 0.1)
return ModelEntity(mesh: planeMesh, materials: [SimpleMaterial(color: color, roughness: 0.25, isMetallic: false)])
}
func createPlaneModelEntity(with anchor: ARPlaneAnchor) -> ModelEntity {
var planeMesh: MeshResource
var color: UIColor
if anchor.alignment == .horizontal {
print("horizotal plane")
color = UIColor.blue.withAlphaComponent(0.5)
planeMesh = .generatePlane(width: anchor.extent.x, depth: anchor.extent.z)
} else if anchor.alignment == .vertical {
print("vertical plane")
color = UIColor.yellow.withAlphaComponent(0.5)
planeMesh = .generatePlane(width: anchor.extent.x, height: anchor.extent.z)
} else {
fatalError("Anchor is not ARPlaneAnchor")
}
return ModelEntity(mesh: planeMesh, materials: [SimpleMaterial(color: color, roughness: 0.25, isMetallic: false)])
}
func removePlaneEntity(with anchor: ARPlaneAnchor, from arView: ARView) {
guard let planeAnchorEntity = arView.scene.findEntity(named: anchor.identifier.uuidString+"_anchor") else { return }
arView.scene.removeAnchor(planeAnchorEntity as! AnchorEntity)
}
func updatePlaneEntity(with anchor: ARPlaneAnchor, in view: ARView) {
var planeMesh: MeshResource
guard let entity = view.scene.findEntity(named: anchor.identifier.uuidString+"_model") else { return }
let modelEntity = entity as! ModelEntity
if anchor.alignment == .horizontal {
planeMesh = .generatePlane(width: anchor.extent.x, depth: anchor.extent.z)
} else if anchor.alignment == .vertical {
planeMesh = .generatePlane(width: anchor.extent.x, height: anchor.extent.z)
} else {
fatalError("Anchor is not ARPlaneAnchor")
}
modelEntity.model!.mesh = planeMesh
}
import SceneKit
extension SCNVector3 {
func length() -> Float {
return sqrtf(x * x + y * y + z * z)
}
}
func -(l: SCNVector3, r: SCNVector3) -> SCNVector3 {
return SCNVector3Make(l.x - r.x, l.y - r.y, l.z - r.z)
}
func +(l: SCNVector3, r: SCNVector3) -> SCNVector3 {
return SCNVector3(l.x + r.x, l.y + r.y, l.z + r.z)
}
func /(l: SCNVector3, r: Float) -> SCNVector3 {
return SCNVector3(l.x / r, l.y / r, l.z / r)
}
Detection: (Here is the problem, I guess, in detect method)
import Foundation
import UIKit
import CoreML
import Vision
import SceneKit
class ObjectDetectionService {
var mlModel = try! VNCoreMLModel(for: YOLOv3Int8LUT().model)
//let model = try? YOLOv3Int8LUT(configuration: .init())
lazy var coreMLRequest: VNCoreMLRequest = {
return VNCoreMLRequest(model: mlModel,
completionHandler: self.coreMlRequestHandler)
}()
private var completion: ((Result<Response, Error>) -> Void)?
func detect(on request: Request, completion: #escaping (Result<Response, Error>) -> Void) {
self.completion = completion
//let orientation = .up
let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: request.pixelBuffer)
do {
try imageRequestHandler.perform([coreMLRequest])
} catch {
self.complete(.failure(error))
return
}
}
}
private extension ObjectDetectionService {
func coreMlRequestHandler(_ request: VNRequest?, error: Error?) {
if let error = error {
complete(.failure(error))
return
}
guard let request = request, let results = request.results as? [VNRecognizedObjectObservation] else {
complete(.failure(RecognitionError.resultIsEmpty))
return
}
guard let result = results.first(where: { $0.confidence > 0.8 }),
let classification = result.labels.first else {
complete(.failure(RecognitionError.lowConfidence))
return
}
let response = Response(boundingBox: result.boundingBox,
classification: classification.identifier)
complete(.success(response))
}
func complete(_ result: Result<Response, Error>) {
DispatchQueue.main.async {
self.completion?(result)
self.completion = nil
}
}
}
enum RecognitionError: Error {
case unableToInitializeCoreMLModel
case resultIsEmpty
case lowConfidence
}
extension ObjectDetectionService {
struct Request {
let pixelBuffer: CVPixelBuffer
}
struct Response {
let boundingBox: CGRect
let classification: String
}
}
Why am I getting this warning, and How to get the camera smooth experience?
The session(_ session: ARSession, didUpdate frame: ARFrame) delegate method is called very frequently: many times per second. If your classifyFrame method is doing too much work, it will retain the ARFrame object until after the next frame is delivered to the delegate.
ARKit will warn you when too many frames are retained, typically because a queue is blocked in your delegate.
I have a simple game in which players get three rounds to achieve the highest score . The gameScene exists inside a SwiftUI View and is created like this:
var gameScene: SKScene {
let scene = NyonindoGameScene(
size: CGSize(
width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.height
)
)
scene.viewModel = self.viewModel
scene.scaleMode = .aspectFill
return scene
}
It is called from the body of the view (inside a GeometryReader inside a ZStack) using SpriteView(). The code was working great until I tested on a new iPhone 13, which gave me all kinds of quirky and unexpected behaviors. I won't elaborate on them now as I have fixed most, but I am still left with a "phantom" start button. It is designed to display different text depending on the round being played (viz.: "Start," "Try Again," "Last Chance") using a var that is accurately counting rounds. However, I get this at the end of the first round:
When this Frankenstein button gets tapped, the new round begins. HOWEVER, SKPhysicsContactDelegate didBegin(_:) does not get called and collisions are ignored. (In my general bafflement here, I don't know if this is a separate issue or one that will go away when I solve the overlapping button problem.)
In any case, here is the relevant code for the startButton:
func addStartButton(text: String) {
startButton.removeFromParent() // added as one of many failed remedies
let startButtonLabel = SKLabelNode(text: text)
startButtonLabel.fontName = SKFont.bold
startButtonLabel.fontSize = 40.0
startButtonLabel.fontColor = UIColor.white
startButtonLabel.position = CGPoint(x: 0, y: -12)
startButton.position = CGPoint(x:self.frame.midX, y:self.frame.midY)
startButton.zPosition = 3
startButton.addChild(startButtonLabel)
addChild(startButton)
}
The initial start button is called like this in didMove(to view: SKView):
if attempts == 0 {
addStartButton(text: "Start")
}
And the buttons for the second and third round are called inside a gameOver() function like this:
if attempts == 1 {
startButton.removeFromParent() // again, this is overkill as it gets removed before this...
let text: String = "Try Again!"
addStartButton(text: text)
}
if attempts == 2 {
startButton.removeFromParent()
let text: String = "Last Chance!"
addStartButton(text: text)
}
I originally had a switch statement instead of the two if statements, but that generated the same problem. Print statements to the console suggest that only one button is being called for each round, but the results suggest something different.
Any clues? (Apologies if I haven't provided enough code for an assessment.)
why are you removing the button? change it's label:
class TTESTGameScene: SKScene {
var allBoxes: [SKSpriteNode] = []
var startButton: SKShapeNode = SKShapeNode(rect: CGRect(x: 0, y: 0, width: 200, height: 43), cornerRadius: 20)
override func didMove(to view: SKView) {
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
view.allowsTransparency = true
self.backgroundColor = .clear
view.alpha = 1.0
view.isOpaque = true
view.backgroundColor = SKColor.clear.withAlphaComponent(0.0)
let nextButton = SKShapeNode(rect: CGRect(x: 0, y: view.frame.maxY - 40, width: 66, height: 33), cornerRadius: 20)
nextButton.fillColor = .yellow
nextButton.name = "nextButton"
let nextLabel = SKLabelNode(text: "")
nextLabel.fontSize = 40.0
nextLabel.fontColor = UIColor.white
nextLabel.position = CGPoint(x: 0, y: -12)
nextButton.addChild(nextLabel)
addChild(nextButton)
startButton.fillColor = .red
startButton.name = "startButton"
let startButtonLabel = SKLabelNode(text: "000")
startButtonLabel.fontSize = 30.0
startButtonLabel.fontColor = UIColor.white
startButtonLabel.horizontalAlignmentMode = .center
startButtonLabel.position = CGPoint(x: startButton.frame.size.width/2, y: 10)
startButtonLabel.name = "startButtonLabel"
startButton.position = CGPoint(x:self.frame.midX - startButton.frame.size.width/2, y:self.frame.midY)
startButton.zPosition = 3
startButton.addChild(startButtonLabel)
addChild(startButton)
}
var attempts: Int = 0
func nextLevel() {
//startButton.removeFromParent() // added as one of many failed remedies
var text = ""
if attempts == 0 {
text = "Start"
}
else if attempts == 1 {
text = "Try Again!"
}
else if attempts == 2 {
text = "Last Chance!"
}
if let label = startButton.childNode(withName: "//startButtonLabel") as? SKLabelNode {
label.text = text
attempts += 1
attempts = attempts > 2 ? 0:attempts
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let location = touch.location(in: self.view)
let sceneTouchPoint = self.convertPoint(fromView: location)
let touchedNode = self.atPoint(sceneTouchPoint)
print(touchedNode.name)
if touchedNode.name == "nextButton" {
nextLevel()
}
}
}
// A sample SwiftUI creating a GameScene and sizing it
// at 300x400 points
struct TTESTContentView: View {
var scene: SKScene {
let scene = TTESTGameScene()
scene.size = CGSize(width: 300, height: 400)
scene.scaleMode = .aspectFill
return scene
}
var body: some View {
SpriteView(scene: scene)
.frame(width: 300, height: 400)
//.ignoresSafeArea()
}
}
struct ContentViewTest_Previews: PreviewProvider {
static var previews: some View {
TTESTContentView()
}
}
I try to make an endless background through the nodes, but the background has not become infinite and is interrupted, the third background is not yet shown. After the first show, the number of nodes in the scene grows, how can this be fixed?
import SpriteKit
import GameplayKit
class GameScene: SKScene {
var bgNode: SKNode!
var overlay: SKNode!
var overlayWidth: CGFloat!
//var viewSize: CGSize!
var levelPositionX: CGFloat = 0.0
//var speed: CGFloat = 5.5
override func didMove(to view: SKView) {
setupNode()
//viewSize = CGSize(width: frame.size.width, height:
frame.size.height )
}
func setupNode() {
let worldNode = childNode(withName: "World")!
bgNode = worldNode.childNode(withName: "Background")!
overlay = bgNode.childNode(withName: "Overlay")!.copy() as!
SKNode
overlayWidth = overlay.calculateAccumulatedFrame().width
}
func createBackgroundOverlay() {
let backgroundOverlay = overlay.copy() as! SKNode
backgroundOverlay.position = CGPoint(x: 0.0, y: 0.0)
bgNode.addChild(backgroundOverlay)
levelPositionX += overlayWidth
}
func update() {
bgNode.position.x -= 5
if bgNode.position.x <= -self.frame.size.width {
bgNode.position.x = self.frame.size.width * 2
createBackgroundOverlay()
}
}
override func update(_ currentTime: TimeInterval) {
update()
}
In my endless runner game, I have implemented an endless background and a ground(or floor) much similar to your app. Below I shall discuss the steps i have used in my game.
Step 1: In your GameScene.swift file add these variables.
var backgroundSpeed: CGFloat = 80.0 // speed may vary as you like
var deltaTime: TimeInterval = 0
var lastUpdateTimeInterval: TimeInterval = 0
Step 2: In GameScene file, make setUpBackgrouds method as follows
func setUpBackgrounds() {
//add background
for i in 0..<3 {
// add backgrounds, my images were namely, bg-0.png, bg-1.png, bg-2.png
let background = SKSpriteNode(imageNamed: "bg-\(i).png")
background.anchorPoint = CGPoint.zero
background.position = CGPoint(x: CGFloat(i) * size.width, y: 0.0)
background.size = self.size
background.zPosition = -5
background.name = "Background"
self.addChild(background)
}
for i in 0..<3 {
// I have used one ground image, you can use 3
let ground = SKSpriteNode(imageNamed: "Screen.png")
ground.anchorPoint = CGPoint(x: 0, y: 0)
ground.size = CGSize(width: self.size.width, height: ground.size.height)
ground.position = CGPoint(x: CGFloat(i) * size.width, y: 0)
ground.zPosition = 1
ground.name = "ground"
self.addChild(ground)
}
}
Step 3: Now we have to capture timeIntervals from update method
override func update(_ currentTime: TimeInterval) {
if lastUpdateTimeInterval == 0 {
lastUpdateTimeInterval = currentTime
}
deltaTime = currentTime - lastUpdateTimeInterval
lastUpdateTimeInterval = currentTime
}
Step 4: Here comes the most important part, moving our backgrounds and groungFloor by enumerating child nodes. Add these two methods in GameScene.swift file.
func updateBackground() {
self.enumerateChildNodes(withName: "Background") { (node, stop) in
if let back = node as? SKSpriteNode {
let move = CGPoint(x: -self.backgroundSpeed * CGFloat(self.deltaTime), y: 0)
back.position += move
if back.position.x < -back.size.width {
back.position += CGPoint(x: back.size.width * CGFloat(3), y: 0)
}
}
}
}
func updateGroundMovement() {
self.enumerateChildNodes(withName: "ground") { (node, stop) in
if let back = node as? SKSpriteNode {
let move = CGPoint(x: -self.backgroundSpeed * CGFloat(self.deltaTime), y: 0)
back.position += move
if back.position.x < -back.size.width {
back.position += CGPoint(x: back.size.width * CGFloat(3), y: 0)
}
}
}
}
Step 5: At this point you should get this error:"Binary operator '+=' cannot be applied to two 'CGPoint' operands" in updateBackground and updateGroundMovement methods.
Now we need to implement operator overloading to resolve this problem. Create a new Swift File and name it Extensions.swift and then implement as follows:
// Extensions.swift
import CoreGraphics
import SpriteKit
public func + (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x + right.x, y: left.y + right.y)
}
public func += (left: inout CGPoint, right: CGPoint) {
left = left + right
}
Step 6: call setUpBackgrounds method in didMove(toView:)
override func didMove(to view: SKView) {
setUpBackgrounds()
}
Step 7: Finally call the updateBackground and updateGroundMovement methods in update(_ currentTime) method. updated code is given below:
override func update(_ currentTime: TimeInterval) {
if lastUpdateTimeInterval == 0 {
lastUpdateTimeInterval = currentTime
}
deltaTime = currentTime - lastUpdateTimeInterval
lastUpdateTimeInterval = currentTime
//MARK:- Last step:- add these methods here
updateBackground()
updateGroundMovement()
}
I want to make a game when it's game over, a banner ad shows up, but I found out that the view takes at least one minute to load. I tried doing this on a different thread but it didn't work. I created the view in GameViewController.swift, and added the subview in the GameScene.swift. Also the Game Over Pop up is a set of SKSpriteNodes and SKLabelNodes.
GameViewController.swift
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("READY__$$$$$$$$$$$$$$$$$$$_________________.")
banner = GADBannerView(adSize: kGADAdSizeFullBanner)
banner.adUnitID = "ca-app-pub-3940256099942544/2934735716"
let request = GADRequest()
banner.load(request)
banner.frame = CGRect(x: 0, y: (view?.bounds.height)! - banner.frame.size.height, width: banner.frame.size.width, height: banner.frame.size.height)
banner.rootViewController = self
if let view = self.view as! SKView? {
// Load the SKScene from 'GameScene.sks'
if let scene = SKScene(fileNamed: "IntroScene") {
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
}
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
}
}
override var shouldAutorotate: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return .allButUpsideDown
} else {
return .all
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
override var prefersStatusBarHidden: Bool {
return true
}
}
GameScene.swift
func spawnAd() {
print("READY___________________.")
DispatchQueue.main.async(execute: {
self.view?.addSubview(banner)
})
// DispatchQueue.global(qos: .userInteractive).async {
// DispatchQueue.main.async {
// self.view?.addSubview(banner)
// }
//
// }
}
The Game Over Screen Pop up
func spawnGameOverMenu() {
let dimPanel = SKSpriteNode(color: UIColor.brown, size: self.size)
dimPanel.alpha = 0.0
dimPanel.zPosition = 9
dimPanel.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
self.addChild(dimPanel)
let fadeAlpha = SKAction.fadeAlpha(by: 0.5, duration: 0.6)
dimPanel.run(fadeAlpha)
gameOverMenu = SKSpriteNode(color: bgColor, size: CGSize(width: self.frame.width - 5, height: self.frame.height / 2))
gameOverMenu.position = CGPoint(x: self.frame.midX, y: self.frame.minY - self.frame.height)
gameOverMenu.name = "gameOverMenu"
gameOverMenu.zPosition = 10
self.addChild(gameOverMenu)
spawnGameOverLabel()
spawnResetLbl()
spawnAd()
let moveUp = SKAction.moveTo(y: self.frame.midY, duration: 1.0)
gameOverMenu.run(moveUp)
}
I figured out why it wasn't working, I set the root view controller after I loaded the request.
banner = GADBannerView(adSize: kGADAdSizeFullBanner)
banner.adUnitID = "ca-app-pub-3940256099942544/2934735716"
banner.rootViewController = self
let request = GADRequest()
banner.load(request)
banner.frame = CGRect(x: 0, y: (view?.bounds.height)! - banner.frame.size.height, width: banner.frame.size.width, height: banner.frame.size.height)
I'm trying to solving a problem where a sprite node can jump up through a platform but cannot jump back down. I tried using this code:
override func didMove(to view: SKView) {
if (thePlayer.position.y > stonePlatform1.position.y) == true {
stonePlatform1.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: stonePlatform.size.width * 0.9, height: stonePlatform.size.height * 0.75))
stonePlatform1.physicsBody!.isDynamic = false
stonePlatform1.physicsBody!.affectedByGravity = false
stonePlatform1.physicsBody!.categoryBitMask = BodyType.object.rawValue
stonePlatform1.physicsBody!.contactTestBitMask = BodyType.object.rawValue
stonePlatform1.physicsBody!.restitution = 0.4
}
}
The idea was to turn on the physics body of the platform on when the player is above the platform. However, the physics doesn't work at all when I use this code. In fact I tried using this code:
override func didMove(to view: SKView) {
if (thePlayer.position.y < stonePlatform1.position.y) == true {
stonePlatform1.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: stonePlatform.size.width * 0.9, height: stonePlatform.size.height * 0.75))
stonePlatform1.physicsBody!.isDynamic = false
stonePlatform1.physicsBody!.affectedByGravity = false
stonePlatform1.physicsBody!.categoryBitMask = BodyType.object.rawValue
stonePlatform1.physicsBody!.contactTestBitMask = BodyType.object.rawValue
stonePlatform1.physicsBody!.restitution = 0.4
}
}
and the physics doesn't turn on either. If the IF statement isn't there, the physics does work all of the time.
You can use the node velocity for this platforms, like this:
SpriteKit - Swift 3 code:
private var up1 : SKSpriteNode!
private var down1 : SKSpriteNode!
private var down2 : SKSpriteNode!
private var player : SKSpriteNode!
override func didMove(to view: SKView) {
up1 = self.childNode(withName: "up1") as! SKSpriteNode
down1 = self.childNode(withName: "down1") as! SKSpriteNode
down2 = self.childNode(withName: "down2") as! SKSpriteNode
player = self.childNode(withName: "player") as! SKSpriteNode
up1.physicsBody?.categoryBitMask = 0b0001 // Mask for UoPlatforms
down1.physicsBody?.categoryBitMask = 0b0010 // Mask for downPlatforms
down2.physicsBody?.categoryBitMask = 0b0010 // Same mask
}
override func update(_ currentTime: TimeInterval) {
player.physicsBody?.collisionBitMask = 0b0000 // Reset the mask
// For UP only Platform
if (player.physicsBody?.velocity.dy)! < CGFloat(0.0) {
player.physicsBody?.collisionBitMask |= 0b0001 // The pipe | operator adds the mask by binary operations
}
// For Down only platforms
if (player.physicsBody?.velocity.dy)! > CGFloat(0.0) {
player.physicsBody?.collisionBitMask |= 0b0010 // The pipe | operator adds the mask by binary operations
}
}
Source code with example here: https://github.com/Maetschl/SpriteKitExamples/tree/master/PlatformTest
The example show this:
Green platforms -> Down Only
Red platforms -> Up only
You could try just starting with the physics body as nil and then set the physics values to it after the player is above it. Also, this kind of code should be in the update function. Having it in didMove only lets it get called once.
override func update(_ currentTime: TimeInterval){
if (thePlayer.position.y < stonePlatform1.position.y) && stonePlatform1.physicsBody != nil {
stonePlatform1.physicsBody = nil
}else if (thePlayer.position.y > stonePlatform1.position.y) && stonePlatform1.physicsBody == nil{
setPhysicsOnPlatform(stonePlatform1)
}
}
func setPhysicsOnPlatform(_ platform: SKSpriteNode){
platform.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: stonePlatform.size.width * 0.9, height: stonePlatform.size.height * 0.75))
...
//the rest of your physics settings
}
You should also do some handling for the height of the player and your anchorPoint. Otherwise if your anchorPoint is (0,0) and the player is halfway through the platform, the physics will be applied and a undesirable result will occur.