I have what is perhaps a stupid question about Apple's new GameplayKit.
I am creating a 2D grid-based node layout for my game. I mostly love the functionality of the GKGraphNode2D
, but would like to tweak it in one way. I'd like to conditionally add a penalty when traversing a certain kind of node pair. In other words, I want some nodes connected in a straight-forward way, and some nodes connected such that their traversal distance is modified by my app.
I thought subclassing GKGraphNode2D
and overriding -costToNode:
and -estimatedCostToNode:
would work perfectly! I'd return the value supplied by super
sometimes, and tweak it for my game at other times. These two methods are methods on GKGraphNode
, the superclass of GKGraphdNode2D
.
Unfortunately, when I try to do this, it appears that -costToNode:
and -estimatedCostToNode:
are never called on my GKGraphNode2D
subclass. I would expect these methods to be invoked when I call -findPathFromNode:toNode:
on my GKGraph
object that contains a bunch of my GKGraphNode2D
subclass objects.
What am I doing wrong?
The following is my code.
Create two classes, CheapNode
and ExpensiveNode
, that are subclasses of GKGraphNode2D
, as follows:
@import UIKit;
@import GameplayKit;
@interface CheapNode : GKGraphNode2D
@end
@implementation CheapNode
- (float)estimatedCostToNode:(GKGraphNode *)node {
return 1;
}
- (float)costToNode:(GKGraphNode *)node {
return 1;
}
@end
and
@import UIKit;
@import GameplayKit;
@interface ExpensiveNode : GKGraphNode2D
@end
@implementation ExpensiveNode
- (float)estimatedCostToNode:(GKGraphNode *)node {
return 100;
}
- (float)costToNode:(GKGraphNode *)node {
return 100;
}
@end
Then in a test, I've created some nodes, connected them, added them to a graph, and sent the findPathFromNode:toNode:
message to the graph. Then, I check the path that the graph finds, and I don't find what I expect.
- (void)testNodeCostsUsed {
/*
This is the graph we are creating:
A---B---C
| |
F---E---D
where all of the nodes are `CheapNode` objects, except 'B', which is an
`ExpensiveNode` object.
This test finds a path from node A to node C.
If the cost methods in the `CheapNode` and `ExpensiveNode` subclasses
are used, the route going from A to C around B (down, then right, then up)
should be used, since the cost of going from B to C is 100, and the cost of
going from any other node to any other node is 1
(see implementation of these classes).
Instead, we see `GKGraph` choosing the "shortest" route in terms of number
of nodes, from the top left immediately to the right to the top right.
*/
CheapNode *nodeA = [[CheapNode alloc] initWithPoint:(vector_float2){0, 0}];
ExpensiveNode *nodeB = [[ExpensiveNode alloc] initWithPoint:(vector_float2){1, 0}];
CheapNode *nodeC = [[CheapNode alloc] initWithPoint:(vector_float2){2, 0}];
CheapNode *nodeD = [[CheapNode alloc] initWithPoint:(vector_float2){2, 1}];
CheapNode *nodeE = [[CheapNode alloc] initWithPoint:(vector_float2){1, 1}];
CheapNode *nodeF = [[CheapNode alloc] initWithPoint:(vector_float2){0, 1}];
[nodeA addConnectionsToNodes:@[ nodeB, nodeF ] bidirectional:YES];
[nodeC addConnectionsToNodes:@[ nodeB, nodeD ] bidirectional:YES];
[nodeE addConnectionsToNodes:@[ nodeF, nodeD ] bidirectional:YES];
NSArray *allNodes = @[ nodeA, nodeB, nodeC, nodeD, nodeE, nodeF ];
GKGraph *graph = [GKGraph graphWithNodes:allNodes];
NSArray *nodes = [graph findPathFromNode:nodeA toNode:nodeC];
NSArray *expectedPath = @[ nodeA, nodeF, nodeE, nodeD, nodeC ];
NSArray *prohibitedPath = @[ nodeA, nodeB, nodeC ];
XCTAssert([nodes isEqualToArray:expectedPath], @"");
XCTAssertFalse([nodes isEqualToArray:prohibitedPath], @"");
}
This test case fails. I also notice that estimatedCostToNode:
and costToNode:
are never sent to my GKGraphNode2D
subclasses (as verified by log statements and breakpoints).
Here is a sample project demonstrating this issue. It should build and run on the latest developer beta of Xcode (as of 08/31/2015, Xcode beta 6).
I have submitted the sample code and the description in this StackOverflow question as a bug (http://www.openradar.me/22524760) if anyone is interested in duping. If the bug persists after iOS 9 is released, I will use a developer support ticket to try to resolve this issue.
Apple got back to me on my Developer Support Ticket. They admitted this is a bug, and it (as far as I can tell) seems to be fixed in iOS 9.2 beta 2 (7C46t). 🎉
I have updated the sample project to illustrate another bug with this framework.
Apple responded to my developer support ticket related to this issue and told me it was a known issue, and that there was a potential fix in the iOS SDK 9.2 beta 2, released November 3, 2015. I verified that the tests in my test project pass in 9.2b2, and so I expect that version to resolve this issue.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With