问题
I can't get the didBeginContact method to fire, I have been trying for a while and I can't spot the error, could use a fresh set of eyes:
- (void)viewDidLoad {
[super viewDidLoad];
self.lastRender = nil;
self.accelX = 0.0;
self.accelY = 0.0;
self.accelZ = 0.0;
self.isLooping = TRUE;
self.tripWire = TRUE;
self.lastPaddleNode = [[SCNNode alloc] init];
self.paddleNode = [[SCNNode alloc] init];
SCNPlane* paddlePlane = [SCNPlane planeWithWidth:0.067056 height:0.138176];
self.paddleNode.geometry = paddlePlane;
self.paddleNode.geometry.firstMaterial.diffuse.contents = [UIColor colorWithRed:133.0/255.0f green:158.0/255.0f blue:122.0/255.0f alpha:0.8];
self.paddleNode.simdTransform = matrix_identity_float4x4;
self.paddleNode.name = @"paddle";
SCNPhysicsBody* paddlePhys = [[SCNPhysicsBody alloc] init];
paddlePhys.type = SCNPhysicsBodyTypeDynamic;
paddlePhys.physicsShape = [SCNPhysicsShape shapeWithGeometry:paddlePlane options:nil];
//paddlePhys.affectedByGravity = FALSE;
paddlePhys.categoryBitMask = SCNPhysicsCollisionCategoryAll;
paddlePhys.collisionBitMask = SCNPhysicsCollisionCategoryAll;
paddlePhys.contactTestBitMask = SCNPhysicsCollisionCategoryAll;
paddlePhys.usesDefaultMomentOfInertia = TRUE;
self.paddleNode.physicsBody = paddlePhys;
SCNNode* ball = [[SCNNode alloc] init];
SCNPhysicsBody* phys = [[SCNPhysicsBody alloc] init];
phys.type = SCNPhysicsBodyTypeDynamic;
SCNSphere* sphere = [SCNSphere sphereWithRadius:0.067f];
phys.physicsShape = [SCNPhysicsShape shapeWithGeometry:sphere options:nil];
ball.geometry = sphere;
ball.geometry.firstMaterial.diffuse.contents = [UIColor yellowColor];
//phys.affectedByGravity = FALSE;
phys.categoryBitMask = SCNPhysicsCollisionCategoryAll;
phys.collisionBitMask = SCNPhysicsCollisionCategoryAll;
phys.contactTestBitMask = SCNPhysicsCollisionCategoryAll;
phys.usesDefaultMomentOfInertia = TRUE;
phys.velocityFactor = SCNVector3Make(10.0, 10.0, 10.0);
ball.physicsBody = phys;
ball.physicsBody.continuousCollisionDetectionThreshold = 0.134f;
ball.simdPosition = simd_make_float3(0.0, 0.27, -0.27);
ball.name = @"ball";
//phys.velocityFactor = SCNVector3Make(10, 10, 10);
[self.sceneView setPreferredFramesPerSecond:60];
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
// Set the view's delegate
self.sceneView.delegate = self;
// Show statistics such as fps and timing information
self.sceneView.showsStatistics = YES;
//phys.velocityFactor = SCNVector3Make(10, 10, 10);
// Create a new scene
SCNScene* scene = [[SCNScene alloc] init];
scene.physicsWorld.contactDelegate = self;
self.sceneView.scene = scene;
[locationManager startUpdatingHeading];
while(!(self.locationManager.heading.trueHeading <= 360 && self.locationManager.heading.trueHeading >= 359)) {
NSLog(@"heading: %f", self.locationManager.heading.trueHeading);
if (self.locationManager.heading.trueHeading <= 360 && self.locationManager.heading.trueHeading >= 359) {
break;
}
}
[self.sceneView.scene.rootNode addChildNode:self.paddleNode];
[self.sceneView.scene.rootNode addChildNode:ball];
[self startUpdates];
}
All I am trying to do write now is the delegate method to fire when the two nodes in my game collide:
- (void)physicsWorld:(SCNPhysicsWorld *)world didBeginContact:(SCNPhysicsContact *)contact {
NSLog(@"in didBeginContact");
CollisionCategory contactMask =
contact.nodeA.physicsBody.categoryBitMask | contact.nodeB.physicsBody.categoryBitMask;
// first, sort out what kind of collision
if (contactMask == (CollisionCategoryPaddle | CollisionCategoryBall)) {
// next, sort out which body is the missile and which is the rocket
// and do something about it
if (contact.nodeA.physicsBody.categoryBitMask == CollisionCategoryPaddle) {
NSLog(@"nodeA is paddle!");
//[self hitRocket:contact.nodeB withMissile:contact.nodeA];
} else {
NSLog(@"nodeB is paddle!");
//[self hitRocket:contact.nodeA withMissile:contact.nodeB];
}
}
}
Note
I am aware that I am using SCNPhysicsCollisionCategoryAll
and then checking for a different kind of bitmask in the method. That is not my problem, my problem is before that, I never see this log statement, which is the first line in my didBeginContact
delegate method:
NSLog(@"in didBeginContact");
Why isn't my method firing? I think I set the delegate correctly. Thanks.
UPDATE
My viewDidLoad
looks like this now, per commenter's suggestion:
- (void)viewDidLoad {
[super viewDidLoad];
// Create a new scene
SCNScene* scene = [[SCNScene alloc] init];
scene.physicsWorld.contactDelegate = self;
self.sceneView.scene = scene;
// Set the view's delegate
self.sceneView.delegate = self;
SCNPlane* paddlePlane = [SCNPlane planeWithWidth:0.067056 height:0.138176];
self.paddleNode = [SCNNode nodeWithGeometry:paddlePlane];
self.paddleNode.geometry.firstMaterial.diffuse.contents = [UIColor colorWithRed:133.0/255.0f green:158.0/255.0f blue:122.0/255.0f alpha:0.8];
self.paddleNode.simdTransform = matrix_identity_float4x4;
self.paddleNode.name = @"paddle";
[self.sceneView.scene.rootNode addChildNode:self.paddleNode];
SCNPhysicsBody* paddlePhys = [[SCNPhysicsBody alloc] init];
paddlePhys.type = SCNPhysicsBodyTypeDynamic;
paddlePhys.physicsShape = [SCNPhysicsShape shapeWithGeometry:self.paddleNode.geometry options:@{SCNPhysicsShapeTypeKey: SCNPhysicsShapeTypeBoundingBox}];
//paddlePhys.affectedByGravity = FALSE;
paddlePhys.categoryBitMask = SCNPhysicsCollisionCategoryAll;
paddlePhys.collisionBitMask = SCNPhysicsCollisionCategoryAll;
paddlePhys.contactTestBitMask = SCNPhysicsCollisionCategoryAll;
//paddlePhys.usesDefaultMomentOfInertia = TRUE;
self.paddleNode.physicsBody = paddlePhys;
SCNNode* ball = [[SCNNode alloc] init];
[self.sceneView.scene.rootNode addChildNode:ball];
SCNPhysicsBody* phys = [[SCNPhysicsBody alloc] init];
phys.type = SCNPhysicsBodyTypeDynamic;
SCNSphere* sphere = [SCNSphere sphereWithRadius:0.067f];
phys.physicsShape = [SCNPhysicsShape shapeWithGeometry:sphere options:@{SCNPhysicsShapeTypeKey: SCNPhysicsShapeTypeConvexHull}];
ball.geometry = sphere;
ball.geometry.firstMaterial.diffuse.contents = [UIColor yellowColor];
//phys.affectedByGravity = FALSE;
phys.categoryBitMask = SCNPhysicsCollisionCategoryAll;
phys.collisionBitMask = SCNPhysicsCollisionCategoryAll;
phys.contactTestBitMask = SCNPhysicsCollisionCategoryAll;
phys.usesDefaultMomentOfInertia = TRUE;
phys.velocityFactor = SCNVector3Make(10.0, 10.0, 10.0);
ball.physicsBody = phys;
ball.physicsBody.continuousCollisionDetectionThreshold = 0.134f;
ball.simdPosition = simd_make_float3(0.0, 0.27, -0.27);
ball.name = @"ball";
//phys.velocityFactor = SCNVector3Make(10, 10, 10);
// Show statistics such as fps and timing information
self.sceneView.showsStatistics = YES;
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingHeading];
while(!(self.locationManager.heading.trueHeading <= 360 && self.locationManager.heading.trueHeading >= 358)) {
NSLog(@"heading: %f", self.locationManager.heading.trueHeading);
if (self.locationManager.heading.trueHeading <= 360 && self.locationManager.heading.trueHeading >= 358) {
break;
}
}
[self.sceneView setPreferredFramesPerSecond:60];
self.lastRender = nil;
self.accelX = 0.0;
self.accelY = 0.0;
self.accelZ = 0.0;
self.isLooping = TRUE;
self.tripWire = TRUE;
self.lastPaddleNode = [[SCNNode alloc] init];
[self startUpdates];
}
I do agree with the commenter that this is probably a problem with how things are ordered here, I just can't see what it is...helllppp, I'm droowwnnninng...
UPDATE
I changed my bitwise masks as follows, I didn't use a custom enum type because I only have two objects, so I think I can just use ...All
and ...Default
, but it isn't working like this. I will try custom enum type when I understand the bitwise AND operation, if someone could explain to me the left handed/right handed part, and how to do the operation?
//Paddle
paddlePhys.categoryBitMask = SCNPhysicsCollisionCategoryDefault;
paddlePhys.collisionBitMask = SCNPhysicsCollisionCategoryAll;
paddlePhys.contactTestBitMask = SCNPhysicsCollisionCategoryAll;
...
//Ball
phys.categoryBitMask = SCNPhysicsCollisionCategoryAll;
phys.collisionBitMask = SCNPhysicsCollisionCategoryDefault;
phys.contactTestBitMask = SCNPhysicsCollisionCategoryDefault;
Should this work? I also have my paddle set like this now:
paddlePhys.type = SCNPhysicsBodyTypeKinematic;
paddlePhys.physicsShape = [SCNPhysicsShape shapeWithGeometry:self.paddleNode.geometry options:@{SCNPhysicsShapeTypeKey: SCNPhysicsShapeTypeBoundingBox}];
My ball is this:
phys.type = SCNPhysicsBodyTypeDynamic;
phys.physicsShape = [SCNPhysicsShape shapeWithGeometry:sphere options:@{SCNPhysicsShapeTypeKey: SCNPhysicsShapeTypeConvexHull}];
All together right now I have:
- (void)viewDidLoad {
[super viewDidLoad];
self.sceneView.delegate = self;
// Show statistics such as fps and timing information
self.sceneView.showsStatistics = YES;
SCNScene* scene = [[SCNScene alloc] init];
// Create a new scene
SCNPlane* paddlePlane = [SCNPlane planeWithWidth:0.067056 height:0.138176];
paddlePlane.firstMaterial.doubleSided = YES;
self.paddleNode = [SCNNode nodeWithGeometry:paddlePlane];
self.paddleNode.geometry.firstMaterial.diffuse.contents = [UIColor colorWithRed:133.0/255.0f green:158.0/255.0f blue:122.0/255.0f alpha:0.8];
self.paddleNode.simdTransform = matrix_identity_float4x4;
self.paddleNode.name = @"paddle";
SCNPhysicsBody* paddlePhys = [[SCNPhysicsBody alloc] init];
paddlePhys.type = SCNPhysicsBodyTypeKinematic;
paddlePhys.physicsShape = [SCNPhysicsShape shapeWithGeometry:self.paddleNode.geometry options:@{SCNPhysicsShapeTypeKey: SCNPhysicsShapeTypeBoundingBox}];
//paddlePhys.affectedByGravity = FALSE;
paddlePhys.categoryBitMask = SCNPhysicsCollisionCategoryDefault;
paddlePhys.collisionBitMask = SCNPhysicsCollisionCategoryAll;
paddlePhys.contactTestBitMask = SCNPhysicsCollisionCategoryAll;
//paddlePhys.usesDefaultMomentOfInertia = TRUE;
self.paddleNode.physicsBody = paddlePhys;
self.paddleNode.simdPivot = [self makeTranslationMatrix:0 ty: 0 tz:0.1];
SCNNode* ball = [[SCNNode alloc] init];
SCNPhysicsBody* phys = [[SCNPhysicsBody alloc] init];
SCNSphere* sphere = [SCNSphere sphereWithRadius:0.067f];
phys.type = SCNPhysicsBodyTypeDynamic;
phys.physicsShape = [SCNPhysicsShape shapeWithGeometry:sphere options:@{SCNPhysicsShapeTypeKey: SCNPhysicsShapeTypeConvexHull}];
ball.geometry = sphere;
ball.geometry.firstMaterial.diffuse.contents = [UIColor yellowColor];
//phys.affectedByGravity = FALSE;
phys.categoryBitMask = SCNPhysicsCollisionCategoryAll;
phys.collisionBitMask = SCNPhysicsCollisionCategoryDefault;
phys.contactTestBitMask = SCNPhysicsCollisionCategoryDefault;
phys.usesDefaultMomentOfInertia = TRUE;
phys.velocityFactor = SCNVector3Make(10.0, 10.0, 10.0);
ball.physicsBody = phys;
ball.physicsBody.continuousCollisionDetectionThreshold = 0.134f;
ball.simdPosition = simd_make_float3(0.0, 0.27, -0.27);
ball.name = @"ball";
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingHeading];
while(!(self.locationManager.heading.trueHeading <= 360 && self.locationManager.heading.trueHeading >= 358)) {
NSLog(@"heading: %f", self.locationManager.heading.trueHeading);
if (self.locationManager.heading.trueHeading <= 360 && self.locationManager.heading.trueHeading >= 358) {
break;
}
}
[self.sceneView setPreferredFramesPerSecond:60];
self.lastRender = nil;
self.accelX = 0.0;
self.accelY = 0.0;
self.accelZ = 0.0;
self.isLooping = TRUE;
self.tripWire = TRUE;
self.lastPaddleNode = [[SCNNode alloc] init];
[scene.rootNode addChildNode:ball];
[scene.rootNode addChildNode:self.paddleNode];
scene.physicsWorld.contactDelegate = self;
self.sceneView.scene = scene;
[self startUpdates];
}
I am also wondering if the problem might be in the block for the CMDeviceMotion events, here is that:
- (void)startUpdates {
//dispatch_queue_t myQueue = dispatch_queue_create("com.eamon.corona_pong", DISPATCH_QUEUE_SERIAL);
// Determine the update interval.
NSTimeInterval updateInterval = 1.0/60.0;
// Create a CMMotionManager object.
CMMotionManager *mManager = [(AppDelegate *)[[UIApplication sharedApplication] delegate] sharedManager];
if ([mManager isDeviceMotionAvailable] == YES) {
[mManager setDeviceMotionUpdateInterval:updateInterval];
// do something appropriate here
if ([CMMotionManager availableAttitudeReferenceFrames] & CMAttitudeReferenceFrameXTrueNorthZVertical) {
[mManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryCorrectedZVertical toQueue:[NSOperationQueue mainQueue] withHandler:^(CMDeviceMotion * _Nullable motion, NSError * _Nullable error) {
//[self handleMotionWrapper:motion];
[SCNTransaction begin];
[SCNTransaction setAnimationDuration:0];
//simd_float4x4 rotateY = [self makeYRotationMatrix:270.0];
//simd_float4x4 rotate = [self makeXRotationMatrix:270.0];
simd_float4x4 cameraTrans = self.sceneView.session.currentFrame.camera.transform;
simd_float4x4 temp = simd_mul(cameraTrans, [self makeYRotationMatrix:270.0]);
//simd_float4x4 rotated = simd_mul(temp, [self makeYRotationMatrix:135]);
simd_float4x4 transMatrixMotion = [self makeTranslationMatrix:self.lastPaddleNode.simdPosition.x ty:self.lastPaddleNode.simdPosition.y - 0.1 tz:self.lastPaddleNode.simdPosition.z];
self.paddleNode.simdPosition = simd_make_float3(simd_mul(simd_make_float4(temp.columns[3][0], temp.columns[3][1], temp.columns[3][2], 1.0), transMatrixMotion));
self.paddleNode.simdTransform = temp;
[SCNTransaction commit];
self.lastPaddleNode = self.paddleNode;
self.tripWire = FALSE;
}];
}
}
}
I'm am still working out the motion code, but I figure the collision should work, even though I don't have the paddle oriented exactly how I want yet (I just need to rotate it 90 degrees around the y-axis, but it is proving to be annoying, but shouldn't matter for collision detection, right?)
Also, what about SCNPlane
, I also made my SCNPlane
(the paddle) double-sided with the doubleSided
attribute of the firstMaterial
attribute but it did not help, maybe I need to use a shallow SCNBox? I have tried it before, but maybe some of the changes I have made would mean it would work if I make my paddle and SCNBox
instead of a double-sided SCNPlane
?
THANKS!
UPDATE I thought I would share my latest attempt, doesn't work but I think it might be closer, might be easier to spot the problem:
typedef NS_OPTIONS(NSUInteger, CollisionCategory)
{
CollisionCategoryPaddle = 1 << 0,
CollisionCategoryBall = 1 << 1,
};
- (void)viewDidLoad {
[super viewDidLoad];
self.sceneView.delegate = self;
// Show statistics such as fps and timing information
self.sceneView.showsStatistics = YES;
SCNScene* scene = [[SCNScene alloc] init];
self.sceneView.scene = scene;
self.sceneView.scene.physicsWorld.contactDelegate = self;
// Create a new scene
SCNBox* paddlePlane = [SCNBox boxWithWidth:0.067056 height:0.138176 length:0.01 chamferRadius:0.0];
paddlePlane.firstMaterial.doubleSided = YES;
self.paddleNode = [SCNNode nodeWithGeometry:paddlePlane];
self.paddleNode.geometry.firstMaterial.diffuse.contents = [UIColor colorWithRed:133.0/255.0f green:158.0/255.0f blue:122.0/255.0f alpha:0.8];
self.paddleNode.simdTransform = matrix_identity_float4x4;
self.paddleNode.name = @"paddle";
[self.sceneView.scene.rootNode addChildNode:self.paddleNode];
SCNPhysicsBody* paddlePhys = [[SCNPhysicsBody alloc] init];
paddlePhys.type = SCNPhysicsBodyTypeKinematic;
paddlePhys.physicsShape = [SCNPhysicsShape shapeWithGeometry:self.paddleNode.geometry options:nil];
//paddlePhys.affectedByGravity = FALSE;
paddlePhys.categoryBitMask = CollisionCategoryPaddle;
paddlePhys.collisionBitMask = CollisionCategoryBall;
paddlePhys.contactTestBitMask = CollisionCategoryBall;
//paddlePhys.usesDefaultMomentOfInertia = TRUE;
self.paddleNode.physicsBody = paddlePhys;
SCNPhysicsBody* phys = [[SCNPhysicsBody alloc] init];
SCNSphere* sphere = [SCNSphere sphereWithRadius:0.067f];
self.ballNode = [[SCNNode alloc] init];
self.ballNode.name = @"ball";
self.ballNode.geometry = sphere;
self.ballNode.geometry.firstMaterial.diffuse.contents = [UIColor yellowColor];
//self.ballNode.physicsBody.continuousCollisionDetectionThreshold = (CGFloat)0.134f;
self.ballNode.simdPosition = simd_make_float3(0.0, 0.27, -0.27);
[self.sceneView.scene.rootNode addChildNode:self.ballNode];
phys.type = SCNPhysicsBodyTypeDynamic;
phys.physicsShape = [SCNPhysicsShape shapeWithGeometry:self.ballNode.geometry options:nil];
phys.categoryBitMask = CollisionCategoryBall;
phys.collisionBitMask = CollisionCategoryPaddle;
phys.contactTestBitMask = CollisionCategoryPaddle;
//phys.usesDefaultMomentOfInertia = TRUE;
//phys.velocityFactor = SCNVector3Make(10.0, 10.0, 10.0);
self.ballNode.physicsBody = phys;
//phys.affectedByGravity = FALSE
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingHeading];
while(!(self.locationManager.heading.trueHeading <= 360 && self.locationManager.heading.trueHeading >= 358)) {
NSLog(@"heading: %f", self.locationManager.heading.trueHeading);
if (self.locationManager.heading.trueHeading <= 360 && self.locationManager.heading.trueHeading >= 358) {
break;
}
}
[self.sceneView setPreferredFramesPerSecond:60];
self.lastRender = nil;
self.accelX = 0.0;
self.accelY = 0.0;
self.accelZ = 0.0;
self.isLooping = TRUE;
self.tripWire = TRUE;
self.lastPaddleNode = [[SCNNode alloc] init];
[self startUpdates];
}
I tried this way too, althought I'm not sure if I am using it right:
phys.physicsShape = [SCNPhysicsShape shapeWithGeometry:self.ballNode.geometry options:@{SCNHitTestOptionCategoryBitMask:[NSNumber numberWithUnsignedInt:CollisionCategoryBall], SCNPhysicsTestCollisionBitMaskKey:[NSNumber numberWithUnsignedInt:CollisionCategoryPaddle]}];
回答1:
1.) Add the
scene
to thesceneView
2.) Declare the nodes you will be using for the objects you will be giving physics too.
3.) Make necessary modifications to instantiated objects (color, etc.).
--ATTN STEP 4 is VERY IMPORTANT--
4.) Only after above steps (1, 2, and 3) do you add the
physicsBody
details to the object! (a.k.anode.physicsBody.xxx
)
- Add the nodes to the scene.
In my opinion the way that SceneKit
handles this part of things is ridiculous and needs revision. Too many hours spent in a situation where an SCNNode that hasn't even been added to scene yet, is causing problems because of the order your are instantiating it's attributes :(
来源:https://stackoverflow.com/questions/61315179/didbegincontact-delegate-method-not-firing-for-arkit-collision-detection