Posts

Post not yet marked as solved
3 Replies
Well, there are differences between SKLabelNode and UITextField for sure, but most of the functionality is there with SKLabelNode. Frankly, you would need some special reason to not use SKLabelNode if your app is primarily in SpriteKit. I would say there's really only two cases where you want to mix UIKit and SpriteKit:You're making a UIKit app, which uses some element of SpriteKit almost like an embedded thing, for example a mini-game within a normal iPhone app.You're making a SpriteKit app, but you need some functionality from UIKit that SpriteKit doesn't boast (such as a UIWebView, though nowadays even that is deprecated and you should use WKWebView instead, or the automatic scrolling of a UITextField, although I would even recommend programming your own scrolling).Generally mixing should be done with some direct and thought-out goal in mind, because stuff can go wrong very quickly. For example UIKit and SpriteKit live on different times (SpriteKit has it's own game time that affects everything within the package, but wouldn't affect the UIKit elements) and xy-coordinate systems are different.In short, you really should be using SKLabelNode unless you have some exceptional reason to do otherwise. Feel free to present your case.About the rounded corners SKShapeNode issue, I would mainly recommend looking into 9-slice features of SKSpriteNode (https://developer.apple.com/library/archive/documentation/GraphicsAnimation/Conceptual/SpriteKit_PG/Sprites/Sprites.html#//apple_ref/doc/uid/TP40013043-CH9-SW10). Native 9-slice features in SpriteKit allow you to designate parts of an image as corners, sides and middle, so the whole element gets stretched dynamically.That said, I would also recommend considering if you really need dynamic stretching. I've done this stuff for years and years now with a good amount of success too πŸ™‚, so in my experience the simplest and most minimal solutions are usually the best. With apps and games users want understandable and simple, and you end up with a much easier workload yourself, too, with that approach. It's a win-win!
Post not yet marked as solved
3 Replies
Since tutorialNotification is being added to the SKScene itself while txtName actually adds an entirely new Subview, the latter would always be on top of the former. Essentially you're adding a new container on top of the container that includes the SKScene, which will give you a hard time trying to hack depth.Is there a reason you're not using SKLabelNode for the text field? That would solve your issue right away and make it easier to control anyhow, as it would all be within the SKScene. I guess the overall question is: what is your reason for mixing UIKit and SpriteKit?On a side note, I always advise against using SKShapeNode. I violently ripped out all SKShapeNodes from my apps years ago, after I found out a number of issues with them. I was going on a shape bender at some point, but now I'm fully a SKSpriteNode and image assets guy.
Post marked as solved
8 Replies
Ooooh okay πŸ™‚. This was actually familiar from declaring custom class names in Scene Editor. Great, thanks so much, dudes! Everything works now and is sufficiently dynamic with no hardcoding.Big ups!
Post marked as solved
8 Replies
Thanks guys! I tried using Animal.Type to set the class coming from NSClassFromString. The Animal class now also has a required initializer. I didn't really understand module name and fully qualified name stuff. Where should the module name be coming from?Currently the code I have crashes at the NSClassFromString line, assumedly because it can't find the class. Perhaps the missing module name is a reason for that. I'm actually working in SpriteKit, so these classes happen to be subclasses of SKNode: SKNode -> Animal -> Tiger.Current code:var animal: Animal? var classNames = ["Elephant", "Gorilla", "Tiger"] var classIndex = 2 setupAnimal() moveAnimal() func setupAnimal() { let animalName = classNames[classIndex] animal = (NSClassFromString(animalName) as! Animal.Type).init() addChild(animal?) } func moveAnimal() { animal?.move() }
Post marked as solved
8 Replies
Thanks for the response, OOPer πŸ™‚!I tried a bunch of stuff, but definitely creating an entity for animal itself helped, and it made things more streamlined in general. So, I proceeded to make a new class Animal, that the animal type classes subclass. I have my reasons for subclassing rather than using a protocol. In fact, this whole animal theme is only here to make things more understandable.Using a common parent class made things a lot more dynamic and simple. However, I still couldn't get the class-from-string thing to work. I'm trying to avoid hardcoding the collection of Animal subclasses, because the idea is that they come from data and it is assumed that those classes exist. I tried NSClassFromString and Bundle.main.classNamed(), but I just couldn't get them cast and interpreted properly.My current code:var animal: Animal? var classNames = ["Elephant", "Gorilla", "Tiger"] var classIndex = 2 setupAnimal() moveAnimal() func setupAnimal() { let animalName = classNames[classIndex] if animalName == "Elephant" { animal = Elephant() } else if animalName == "Gorilla" { animal = Gorilla() } else if animalName == "Tiger" { animal = Tiger() } addChild(animal?) } func moveAnimal() { animal?.move() }What I'm still trying to do is to get rid of the remaining conditionals by determining the class name from the string. So, if animalName == "Tiger" { animal = Tiger() } should become something along the lines of animal = NSClassFromString(classNames[classIndex]), but I don't know what exactly.
Post marked as solved
4 Replies
Cool, nice to be of service πŸ™‚!For multiple locations, for example, you can access those using the same nodeIndex value, if they happen to be in an array with the same order of items. Sometimes when I have a group of things coming from data, I actually don't loop through nodes themselves, but rather an array of item names or id's. So then since I've used that same array to create the nodes and their respective array, the order is the same. But that's just being clean. It's reasonable that if your main focus is the nodes themselves, you should loop through them and access any other information with their iteration value.Yeah, I guess the only reason you might ever use DispatchQueue in SpriteKit would be to fetch data from a server, which would be an iOS centric operation, not so much a SpriteKit one. It's best to just think of SKActions as the driving force of your game. It feels a little weird to use them for delays in code, because you think of them as visual animations usually, but that is the correct way in SpriteKit.
Post marked as solved
4 Replies
I think you're overthinking this a bit. It's a relatively easy thing to accomplish, especially when the movement animation duration is the same for each node and you want each node to start animating right after its predecessor has finished moving. It would be slightly more difficult if you had specific node durations and delays. Though, even then it would only require two additional duration arrays to follow.Just quickly, some things that should be corrected in your code:Using DispatchQueue with SpriteKit is a big no-no. SpriteKit is like a cardboard box inside another cardboard box, which is iOS. DispatchQueue kinda functions system-wide, so you're looking at timing outside of the SpriteKit universe. So, what you want is to tie timing in with SpriteKit's time, so whenever it does something (such as call a global isPaused variable), your game follows game time, not iOS time. In short, when controlling time in SpriteKit, always use SKActions. However, for frame updates, use the update() function.You might want to move your variables and initiations outside of your loop, so that it doesn't do all that work again and again within each loop cycle.You should always rely on the array to determine how many items, in this case nodes, there are, instead of having a separate variable for it.There are smarter and more secure ways in Swift to handle the loop iteration value than iterating one yourself in code. The same goes for controlling remaining nodes in the loop.There is a built in completion handler for SKActions, which you can use to determine when your sequence is done.Here's my version of what you're trying to do:// This assumes arrayOfNodes already exists. let moveDuration = 1.0 let moveAction = SKAction.move(to: CGPoint(x: 400.0, y: 400.0), duration: moveDuration) for (nodeIndex, node) in arrayOfNodes.enumerated() { node.run(SKAction.sequence([SKAction.wait(forDuration: moveDuration * Double(nodeIndex)), moveAction]), completion: { if nodeIndex == arrayOfNodes.endIndex - 1 { print("COMPLETED GROUP") } }) }So, first we set moveDuration outside of the loop, because it'll be used for moveAction's duration as well as to control delays for each node's movement inside the loop. We then set moveAction using your coordinates and moveDuration. The easiest, most readable and most secure loop to use is a For loop that cycles directly through the nodes in your array, and it's also enumerated, so you can extract the loop iteration value nodeIndex. For each cycle of the loop, the corresponding node in your array is given a run command with a sequence of a delay and then moveAction. The duration of the delay is determined by multiplying moveDuration with nodeIndex, which makes the node at index 0 have a 0.0 delay, then node at index 1 have 1.0 delay, ..., etc. At the end of each run command there's a completion handler, in which the completed message is print only if nodeIndex happens to be the endIndex of the array. This means the print is only done when the last node in the array is reached. In Swift, endIndex of an array means the last index after your last item, so you also need to decrement it back by 1.I didn't test the code, but I don't see why it wouldn't work. Famous last words... πŸ˜€. Anyhow, try it out and ask away if you have any further questions.
Post not yet marked as solved
1 Replies
This happens most likely because the initiation of your scene file has a specific scale mode (see here) along with the sizing fit for the pre-X iPhone screen. So let's say your scene file frame is 640 x 1136 pixels, which is the base resolution of early post-4s iPhones (at 2x pixel depth). At the same time, your scene's scale mode might be for example SKSceneScaleMode.fill. This would stretch the whole screen in height on an iPhone X, because it's comparative screen size is around 640 x 1386 pixels.Scale modes (see here):.fill - Stretches both x and y axis from your scene file frame to the final screen size. The most potential of unintended stretching, so you really need to know what you're doing when using this..aspectFill - Both x and y are scaled with the same factor, which is determined by the larger one. So basically in this case things aren't stretched out of shape at all and you won't see any unused black areas, but you're likely going to see some of your frame be cut off screen..aspectFit - This is exactly the same as .aspectFill, except the scale factor is determined by the smaller one. This means that you'll always see everything on screen and nothing will be stretched out of shape, but you'll see black letterboxes on non-fitting screens..resizeFill - The scene is not scaled at all, but resized based on the dimensions. This to me is the most confusing one.So, anyhow, for you to support multiple aspect ratios and devices, there are really only two options if you don't want stretching, things cutting off or potential black letterbox bars. The first one is to have one scene file where you have your stuff, then you move those things around in code to best fit each screen size universally. It's basically what you had to do in UIKit before Auto Layout came out. Alas, there is no Auto Layout for SpriteKit, so you have to be a bit more clever.The second option is as laborous, or perhaps more, where you have multiple scene files for each aspect ratio. This is a bit of a control freak approach and is really only apt if you're trying to avoid coding by building things mostly visually. Plus, you'll have to personally identify the device's aspect ratio with custom code. I personally use this approach, because it allows me full control of what things look like on each aspect ratio type. Since the iPhone X brought in this crazy tall size, it's more often than not that I have to design a different layout for it, as opposed to an iPad.Not to get too deep into this rabbit hole, but I'll add this just for common knowledge. iOS devices only use four aspect ratios, if we think of post-iOS 9 (we drop out the original iPhone ratio, which died with iPhone 4s). There is the normal iPhone ratio, 71 : 40 (1136 x 640 px), which was established with iPhone 5, went on until iPhone 8 and is still used in iPhone SE. Then there's the new (weird) iPhone ratio, 2436 : 1125 (2436 x 1125 px), which came in with iPhone X and will most likely be the standard going forward. On the iPad side, you have the normal iPad ratio, 4 : 3 (2048 x 1536 px), which is used by all iPads, except one. That one annoying outlier iPad marks the fourth ratio, 275 : 192 (2200 x 1536 px), which exists only on the 2018 iPad Pro 11". So for me personally, I have four scene files, which allows me to directly target each aspect ratio separately, which in some cases has allowed me to make radically different layouts for each one.Anyway, I would recommend you look into all this and figure out how universal you wish to be with your one codebase. If you're doing something pretty simple and just want to have buttons hover a certain distance from the edges of the screen, regardless of what device it is, go with option 1. If you're nuts like me, go with option 2 πŸ˜€. But before anything, experiment with scene scaling modes.