I am programming a game on XCode's SpriteKit. I have a variable NodeGamePlay (an SKNode) and I set it to NodeGamePlay.paused=true when I hit a pause button. I have tracked this through various NSLog statements scattered throughout my code and when I set NodeGamePlay.paused=true, the game actually pauses, but it seems to somewhat arbitrarily unpause itself (I don't even have a NodeGamePlay.paused=false anywhere in the code).
Anyways, I have not had any success tracking down when this is getting changed incorrectly to paused=false. I tried using the "watch variable" feature, to watch the variable NodeGamePlay, but the debug won't stop/alert me that NodeGamePlay switches between true & false. I was wondering if there is some method of tracking THIS property and having the debug stop as soon as the value is changed?
Setting .paused == TRUE on children nodes in Sprite Kit produces some odd behavior in my experience, at least in iOS 7, because when a parent node has its .paused set to FALSE, it sets all of its child nodes .paused to FALSE, etc.
So it creates a scenario where you need to override setPaused on your NodeGamePlay class to check a different flag to see if it should allow .paused to be set to FALSE when its parent calls it.
-(void)setPaused:(BOOL)paused
{
if (paused == NO && self.iAmAllowedToUnPauseWhenParentTellsMe == NO) {
return;
}
[super setPaused:paused];
}
Related
I have a UI testing issue with some code I've got from another developer (Whose not with the project anymore). The code display a simple alert box at the top of the screen when there's an error and I cannot get my UI test to see the UILabel within it.
The structure of the box looks like this:
UIView
+ UILabel
+ UIButton
And in the custom class for the UIView there is this code:
// Original code
accessibilityIdentifier = "AlertBar" // Required for UI Testing.
// Bits I've added trying to make messageLabel visible to UI Test
isAccessibilityElement = true
accessibilityElements = [messageLabel]
messageLabel.accessibilityIdentifier = "AlertBarMessage"
messageLabel.isAccessibilityElement = true
}
If I breakpoint in the middle of my UI Test and dump the app into the log I can see the UIView:
Attributes: Application, pid: 60317, label: 'The App'
Element subtree:
→Application, 0x600001df8c40, pid: 60317, label: 'The App'
Window (Main), 0x600001dfa060, {{0.0, 0.0}, {390.0, 844.0}}
... app ui logged here!
Other, 0x600001dce220, {{4.0, 47.0}, {382.0, 107.7}}, identifier: 'AlertBar'
So it's clear that the alert is visible to XCTest, but nothing inside it. As you can tell from my added code I've been trying all sorts of things but so far I've not been able to make the UILabel (AlertBarMessage) visible.
What am I missing?
As per usual, after typing up a stackOVerFlow question I go back to the code and figure out the problem :-) In this case it was the isAccessibilityElement = true on the view. Making it accessible was effectively hiding the nested UILabel so turning it off fixed my test.
I know there are ways in Accessibility to setup the view correctly for accessibility so that screen readers see it as a single element and can read the message, but I'll have to work on that later for now.
I'm making pretty heavy use of SKCropNode in my game both for stylistic uses and also in one case where I've built my own SpriteKit version of UIScrollView. I've noticed that when I get a touch event or when a gesture recognizer fires at a certain point, SKScene.nodeAtPoint(...) is still hitting nodes that are hidden at the touch point from the crop node.
How do I prevent SKScene.nodeAtPoint(...) from hitting cropped content, and instead return the next visible node beneath it?
You can't. Everything is based on the frame of the content, and with crop nodes, it is huge. The only thing you can do is check the point of touch, use nodesAtPoint to get all nodes, then enumerate one by one, checking whether or not the touch point is touching a visible pixel by either checking the pixel data, or having a CGPath outlining of your sprite and checking if you are inside that.
I managed to work my way around this one, but it isn't pretty and it doesn't work in every scenario.
The idea is, when a touch is recorded, I iterate over all nodes at that point. If any of those nodes are (or are children of) SKCropNodes, I check the frame of the mask on the crop node. If the touch lies outside the mask, we know the node is not visible at that point and we can assume the touch didn't hit it.
The issue with this method is that I don't do any evaluation of transparency within the crop mask - I just assume it's a rectangle. If the mask is an abnormal shape, I may give false positives on node touches.
extension SKScene {
// Get the node at a given point, but ignore those that are hidden due to SKCropNodes.
func getVisibleNodeAtPoint(point: CGPoint) -> SKNode? {
var testedNodes = Set<SKNode>()
// Iterate over all the nodes hit by this click.
for touchedNode in self.nodesAtPoint(point) {
// If we've already checked this node, skip it. This happens because nodesAtPoint
// returns both leaf and ancestor nodes, and we test the ancestor nodes while
// testing leaf nodes.
if testedNodes.contains(touchedNode) {
continue
}
var stillVisible = true
// Walk the ancestry chain of the target node starting with the touched
// node itself.
var currentNode: SKNode = touchedNode
while true {
testedNodes.insert(currentNode)
if let currentCropNode = currentNode as? SKCropNode {
if let currentCropNodeMask = currentCropNode.maskNode {
let pointNormalizedToCurrentNode = self.convertPoint(point, toNode: currentCropNode)
let currentCropNodeMaskFrame = currentCropNodeMask.frame
// Check if the touch is inside the crop node's mask. If not, we
// know we've touched a hidden point.
if
pointNormalizedToCurrentNode.x < 0 || pointNormalizedToCurrentNode.x > currentCropNodeMaskFrame.size.width ||
pointNormalizedToCurrentNode.y < 0 || pointNormalizedToCurrentNode.y > currentCropNodeMaskFrame.size.height
{
stillVisible = false
break
}
}
}
// Move to next parent.
if let parent = currentNode.parent {
currentNode = parent
} else {
break
}
}
if !stillVisible {
continue
}
// We made it through the ancestry nodes. This node must be visible.
return touchedNode
}
// No visible nodes found at this point.
return nil
}
}
I have an object I want to drag around the screen with the mouse in Processing. I set acquired to true on mouse down over the object, and to false on mouse up, thus:
void mousePressed() {
if (overThing()) {
acquired = true;
}
}
void mouseReleased() {
acquired = false;
}
I then query acquired in my update(), and drag the object if it is true.
void update() {
\\ other stuff...
if (acquired) {
\\ drag thing code ...
}
}
This all works fine in Processing. mouseReleased() gets called whether I release the mouse inside or outside the active window.
However, when I move the code to Chrome, using processing.js (v1.4.8), mouseReleased() is not called if I release the mouse outside the canvas (whether the mouse is still over the web page, or outside the browser window). So when I return the (now unclicked) mouse to the canvas, the object is still getting dragged around.
I tried including a test of mousePressed in update(), but that also returns true in these circumstances.
Any help on what I need to do to make mouse state changes outside the canvas visible with processing.js?
I don't know about Processing specifically, but releasing mouse buttons outside a widget is a common issue in GUI development.
I suspect that you have no way of knowing the precise time when the mouse is released outside the widget, but you do have two options:
Set acquired = false in mouseOut(), as #Kevin suggests.
I assume there is some type of mouseEntered() method in Processing, and also some way of knowing if the mouse button is currently pressed (either a global variable, or an event object passed to mouseEntered()). You can catch the mouse entered event, check if the mouse has been released, and set acquired = false then.
Like so:
void mouseEntered() {
if (mouse button is pressed) {
acquired = false;
}
}
Edit: From your comments, #Susan, it seems like there is a bug in processing.js, where mousePressed is not set to false if the mouse button is released outside the canvas. One thing pointing to this being a bug is that the mouse movement example on the processing website also shows this behaviour.
Depending upon how much control you have over the website this is going on, and how much effort you want to go to, you could fix the bug yourself by writing some javascript (separate from your processing code):
Define a mouseUp() event on the page <body>, to catch all mouse release events on the page.
In the mouseUp() event, check if the event comes from your Processing control. (There is probably an event object passed to the mouseUp() function, and you might have to give your Processing control an ID to identify it)
If the event doesn't come from your Processing control, then fire a mouseUp event yourself, on the Processing control. This should (hopefully!) trigger a mouse event inside your Processing code.
I'm not sure what Processing will make of the mouse (x,y) position being outside its control when it handles the event you send it. You might want to set a flag on the event object (assuming you can add extra data to the event object) to say "don't use the (x,y) position of this event - it's outside the control".
Edit2: It was easier than I thought! Here is the JavaScript code to detect the mouse being released outside of the Processing canvas and send the mouseReleased event to the canvas. I've tested it on the mouse movement example from the Processing website, and it fixes the bug.
It uses jQuery (although it could be re-written to not use jQuery), and it assumes your Processing canvas has the ID "processingCanvas":
$(':not(processingCanvas)').mouseup(function(){
Processing.getInstanceById('processingCanvas').mouseReleased();
});
To use this code, include it anywhere in your page (in a JavaScript file or in <script> tags) and make sure you have the jQuery library included before this code.
The Processing object allows JavaScript to call any functions defined in your Processing code. Here I've used it to call Processing's built in mouseReleased() function, but if you wanted to call a custom function to handle the mouse-released-outside state differently, then you could.
You should use the mouseOut() function to detect when the mouse leaves the sketch:
void mouseOut() {
acquired = false;
}
More info in the reference here.
I'm having a problem with a variable I'm using to track the status of a user activity. In a GUI I have a button that, on clicking the button launches a second GUI. In that GUI, the user can either complete the activity started in the first GUI or not.
If the user cancels the second GUI, then the idea is to go back to the first GUI, leaving all variables and lists with their current values. If the second GUI completes the activity of the first GUI, then all variables and lists should be reset.
To track this, I have a variable (Boolean complete) initially set to FALSE. In the second GUI, when the "OK" button is clicked (rather than the "Cancel" button), the second GUI calls a method in the first GUI, changing the value of "complete" to TRUE.
To see what the heck is going on, I have System.out.println at several points allowing me to see the value of "complete" along the way. What I see is this:
Launching first GUI - complete = FALSE
Launching second GUI - complete = FALSE
Clicking "OK" in second GUI - complete = TRUE
Second GUI closes itself, returning to complete first GUI activity
First GUI finishes activity with complete = FALSE
I'm assuming it is because I am launching the second GUI with a showandwait, and when the method containing the showandwait begins, the value of "complete" = FALSE. The value changes in the WAIT part of show and wait, then the method continues and that is where I get the value still being FALSE, though it was changed to TRUE.
Here is a summary of the code in question (if you need exact code, it's longer, but I can post on request):
completeButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
try {
System.out.println("b4 calc = " + complete); // complete = FALSE
// all the code to create the calcStage
calcStage.showAndWait(); // second GUI, which calls a method in THIS
// class that changes complete to TRUE. That method
// (in THIS file) also has a println that shows the change.
getComplete(); // tried adding this method to check the value of
// "complete" after the change made by the calcStage
// (which calls a method in this same file)
System.out.println("Complete? " + complete);
// this shows complete = FALSE,
// though in the calcStage it was changed to TRUE
if (salecomplete) {
// code that should reset all variables and lists if the activity was completed
}
}
}
}
The question here is why does the second GUI successfully change the value of "complete", but when I return to the first GUI it still sees complete as FALSE? And how can I get around this?
Try having the controller of the second GUI calling a method in the first GUI's controller to modify that complete variable
For example:
// Code to handle the OK button being pressed
#Override
public void handle(ActionEvent t) {
// Do validation and work
//reference to the first controller object
firstController.setComplete(true);
}
In my app, I have a couple of UISlider instances to change various values. The values are displayed right next to the slider, as well as rendered in a 3d space in another visible part of the app.
The 3d part includes some rather heavy calculations, and right now it doesn't seem possible to update it live as the slider changes. That would imply that I'd have to set the slider's continuous property to NO, therefore only getting updates when the slider has finished changing.
I'd prefer to have the displayed value update live, however. Is there a way to have a slider that is continuous (so I can update my value-label in real time) and still sends some kind of message once the user has finished interacting with it? My gut feeling right now is to subclass UISlider and override the touchesEnded: method. Is that feasible?
You can do this with simple target/actions.
Set a target and action for the UIControlEventValueChanged event, and then another target and action for the UIControlEventTouchUpInside event. With the continuous property set to YES, the value changed event will fire as the slider changes value, while the touch up inside event will only fire when the user releases the control.
I just had to do this, so I looked up touch properties, and used the full IBAction header.
This should be a viable alternative for people who want some extra control, though Jas's is definitely easier on the code side.
- (IBAction)itemSlider:(UISlider *)itemSlider withEvent:(UIEvent*)e;
{
UITouch * touch = [e.allTouches anyObject];
if( touch.phase != UITouchPhaseMoved && touch.phase != UITouchPhaseBegan)
{
//The user hasn't ended using the slider yet.
}
}
:D
Also note you should connect the UIControlEventTouchUpOutside event as well in case the user drags his finger out of the control before lifting it.
In Swift 3:
#IBAction func sliderValueChanged(_ slider: UISlider, _ event: UIEvent) {
guard let touch = event.allTouches?.first, touch.phase != .ended else {
// ended
return
}
// not ended yet
}