I'm making a game where hero should jump over the enemies using Sprite Kit and Swift. I have 4 types of them but they're basically the same. When I coded appearance of enemies I thought it would be ok if they could appear in group but now I see that it's not very nice. How can I make a random range between enemies to appear? It should provide enough space for hero to land from a jump at least. I tried but end up with something bizarre.
That's how I'm spawning them:
func random() -> UInt32 {
var time = UInt32(60)..<UInt32(200)
return time.startIndex + arc4random_uniform(time.endIndex - time.startIndex + 1)
}
Then I have different file for appearance:
class EnemyAppear {
var nowAppear = false
var waitToAppear = UInt32(0)
var appearInterval = UInt32(0)
init(nowAppear:Bool, waitToAppear:UInt32, appearInterval:UInt32) {
self.nowAppear = nowAppear
self.waitToAppear = waitToAppear
self.appearInterval = appearInterval
}
func shouldRun() -> Bool {
return self.appearInterval > self.waitToAppear
}
}
then I have a function to run them on screen
func enemyRun() {
for(enemy, enemyAppear) in self.enemyStatus {
var thisPet = self.childNodeWithName(enemy)!
if enemyAppear.shouldRun() {
enemyAppear.waitToAppear = random()
enemyAppear.appearInterval = 0
enemyAppear.nowAppear = true
}
And then for each enemy I have a "Status"
var enemyStatus:Dictionary<String,EnemyAppear> = [:]
enemyStatus["mouse"] = EnemyAppear(nowAppear: false, waitToAppear: random(), appearInterval: UInt32(0))
I can't understand how to make them appear just like that but with an empty space between each other and where to put it.
Related
I'm trying to understand how to change a struct (or class?) in an array by using reduce. Creating 4 countdown timers, on tap pause the current timer and start the next. So I tried something like this:
var timer1 = CountdownTimer()
// var timer2 = CountdownTimer() etc.
.onTapGesture(perform: {
var timers: [CountdownTimer] = [timer1, timer2, timer3, timer4]
var last = timers.reduce (false) {
(setActive: Bool, nextValue: CountdownTimer) -> Bool in
if (nextValue.isActive) {
nextValue.isActive = false;
return true
} else {
return false
}
}
if (last) {
var timer = timers[0]
timer.isActive = true
}
})
############# CountdownTimer is a struct ######
struct CountdownTimer {
var timeRemaining: CGFloat = 1000
var isActive: Bool = false
}
This does not work, two errors I'm seeing
the timers in the array are copies of the timers, not the actual timer so changing them doesn't actually change the timers being displayed on screen.
nextValue (i.e. the next timer) can't be changed because it's a let variable in the reduce declaration. I don't know how to change this (or if it's even relevant because presumably it's a copy of the copy of the timer and not the one I actually want to change).
Am I approaching this in a way thats idiomatically wrong for Swift? How should I be changing the original timers?
I agree with Paul about the fact that this should likely all be pulled out into an observable model object. I'd make that model hold an arbitrary list of timers, and the index of the currently active timer. (I'll give an example of that at the end.)
But it's still worth exploring how you would make this work.
First, SwiftUI Views are not the actual view on the screen like in UIKit. They are descriptions of the view on the screen. They're data. They can be copied and destroyed at any time. So they're readonly objects. The way you keep track of their writable state is through #State properties (or similar things like #Binding, #StateObject, #ObservedObject and the like). So your properties need to be marked #State.
#State var timer1 = CountdownTimer()
#State var timer2 = CountdownTimer()
#State var timer3 = CountdownTimer()
#State var timer4 = CountdownTimer()
As you've discovered, this kind of code doesn't work:
var timer = timer1
timer.isActive = true
That makes a copy of timer1 and modifies the copy. Instead, you want WriteableKeyPath to access the property itself. For example:
let timer = \Self.timer1 // Note capital Self
self[keyPath: timer].isActive = true
Finally, reduce is the wrong tool for this. The point of reduce is to reduce a sequence to a single value. It should never have side-effects like modifying the values. Instead, you just want to find the right elements, and then change them.
To do that, it would be nice to be able to easily track "this element and the next one, and the last element is followed by the first." That seems very complicated, but it's surprisingly simple if you include Swift Algorithms. That gives cycled(), which returns a Sequence that repeats its input forever. Why is that useful? Because then you can do this:
zip(timers, timers.cycled().dropFirst())
This returns
(value1, value2)
(value2, value3)
(value3, value4)
(value4, value1)
Perfect. With that I can fetch the first active timer (keypath) and its successor, and update them:
let timers = [\Self.timer1, \.timer2, \.timer3, \.timer4]
if let (current, next) = zip(timers, timers.cycled().dropFirst())
.first(where: { self[keyPath: $0.0].isActive })
{
self[keyPath: current].isActive = false
self[keyPath: next].isActive = true
}
That said, I wouldn't do that. There are subtle requirements here that should be captured in a type. In particular, you have this assumption that there is only one active timer, but nothing enforces that. If that's what you mean, you should make a type that says so. For example:
class TimerBank: ObservableObject {
#Published private(set) var timers: [CGFloat] = []
#Published private(set) var active: Int?
var count: Int { timers.count }
init(timers: [CGFloat]) {
self.timers = timers
self.active = timers.startIndex
}
func addTimer(timeRemaining: CGFloat = 1000) {
timers.append(timeRemaining)
}
func start(index: Int? = nil) {
if let index = index {
active = index
} else {
active = timers.startIndex
}
}
func stop() {
active = nil
}
func cycle() {
if let currentActive = active {
active = (currentActive + 1) % timers.count
print("active = \(active)")
} else {
active = timers.startIndex
print("(init) active = \(active)")
}
}
}
With this, timerBank.cycle() replaces your reduce.
By using the modulus operator ( % ) on Index, we could cycle through last to first without zipping.
let timers = [\Self.timer1, \.timer2, \.timer3, \.timer4]
if let onIndex = timers.firstIndex(where: { self[keyPath: $0].isActive }) {
self[keyPath: timers[onIndex]].isActive = false
let nextIndex = (onIndex + 1) % 4 // instead of 4, could use timers.count
self[keyPath: timers[nextIndex]].isActive = true
}
Is there a particular reason why Apple would choose to only allow collision detection through the world, rather than allowing individual objects to detect when they collide with other things? This seems like a horrible design choice, but since I haven't found much complaining I'm guessing there is something I'm missing. So, is there?
Some great comments here and not sure why either. They base the whole thing on nodes and names of nodes - seems like it could be better as looking things up after the fact is slower. Here is how I handled it, similar to what others are saying.
Since I have a bunch of missiles, I just name the node = "Missi" + String(format: "%04d", vIndex), name = "Explo" + String(format: "%04d", vIndex), etc., that way I can prefix what I'm looking for and go directly to my array of nodes and do my thing. I have hundreds of things going, but the collision function is pretty small.
func physicsWorld(_ world: SCNPhysicsWorld, didBegin contact: SCNPhysicsContact)
{
guard let nodeNameA = contact.nodeA.name else { return }
guard let nodeNameB = contact.nodeB.name else { return }
//print("CONTACT: \(nodeNameA) \(nodeNameB)")
if(nodeNameA.prefix(5) == "Explo" && nodeNameB.prefix(5) == "Enemy")
{
gameControl.defenseMissileHit(vIndex: Int(nodeNameB.suffix(4))!)
}
if(nodeNameA.prefix(5) == "Enemy" && nodeNameB.prefix(5) == "Explo")
{
gameControl.defenseMissileHit(vIndex: Int(nodeNameA.suffix(4))!)
}
if(nodeNameA.prefix(5) == "Explo" && nodeNameB.prefix(5) == "Missi")
{
//print("HIT")
gameControl.enemyMissileHit(vIndex: Int(nodeNameB.suffix(4))!)
}
if(nodeNameA.prefix(5) == "Missi" && nodeNameB.prefix(5) == "Explo")
{
//print("HIT")
gameControl.enemyMissileHit(vIndex: Int(nodeNameA.suffix(4))!)
}
}
I don't subclass nodes, I create classes such as DefenseObject with the stuff I want, then create an array that I can directly access or loop through.
var defenseObjects: [Int:DefenseObject] = [:]
class DefenseObject: ObjectBase
{
var index: Int = 0 // Index of object
var name: String = "" // Object name
var isActive: Bool = false // Object is active
init(vGameType: gameTypes, vCount: Int, position: SCNVector3)
{
super.init(vGameType: vGameType, vIndex: vCount)
isActive = true
name = "Enemy" + String(format: "%04d", vIndex)
}
}
Then I can go right to the index and do defenseObjects[NDex].call(). I also don't try to cleanup the nodes during a wave, I set an isActive switch and hide them. At the end of the wave, I clean 'em up.
Hope that helps.
I've got a question on property observers. There's some example code below. What I want is for the property Analysis.hasChanged to be updated to true if a.value is changed. Is there a way I can do this?
class Number {
var value: Double
init(numberValue: Double) {
self.value = NumberValue
}
}
class Analysis {
var a: Number
var hasChanged = false
init(inputNumber: Number) {
self.a = inputNumber
}
}
testNumber = Number(numberValue: 4)
testAnalysis = Analysis(inputNumber: testNumber)
print(testAnalysis.hasChanged) // will print "false"
testNumber.value = 10
print(testAnalysis.hasChanged) // will still print "false", but I want it to print "true"
In the end, I want the user to be able to be notified if any of their analyses use numbers that have been changed so that they can update the results of the analyses if they choose.
You can use the built-in property observers provided by Swift.
Every time you set a new value, the didSet will be called. You just need to attach the closure, wrapping the desired behaviour, to the Number class
class Number {
var valueDidChangeClosure: (()->())?
var value: Double {
didSet {
//won't call the valueDidChangeClosure
//if the value was changed from 10 to 10 for example..
if oldValue != value {
valueDidChangeClosure?()
}
}
}
init(numberValue: Double) {
self.value = numberValue
}
}
class Analysis {
var a: Number
var hasChanged = false
init(inputNumber: Number) {
self.a = inputNumber
self.a.valueDidChangeClosure = {
self.hasChanged = true
}
}
}
let testNumber = Number(numberValue: 4)
let testAnalysis = Analysis(inputNumber: testNumber)
print(testAnalysis.hasChanged) // will print "false"
testNumber.value = 10
print(testAnalysis.hasChanged) // will print "true"
I would do something like this, I apologize in advance if I have some syntax wrong (I usually use C/C++, think of this as more psudo code since you'd have to have a way to copy Number classes, etc.).
class Number {
var value: Double
init(numberValue: Double) {
self.value = NumberValue
}
}
class Analysis {
var a: Number
var _a: Number
bool hasChanged() {
if (a != _a) {
_a = a
return true;
}
return false;
}
init(inputNumber: Number) {
self.a = inputNumber
self._a = self.a
}
}
testNumber = Number(numberValue: 4)
testAnalysis = Analysis(inputNumber: testNumber)
print(testAnalysis.hasChanged()) // will print "false"
testNumber.value = 10
print(testAnalysis.hasChanged()) // will still print "false", but I want it to print "true"
In the end, I want the user to be able to be notified if any of their analyses use numbers that have been changed so that they can update the results of the analyses if they choose.
I don't know if this really addresses that question, I based my answer off of the code you provided. So there may be additional functionality if you want there to be some triggering method (instead of calling .hasChanged()).
Comparing doubles (and any other floating point type) with '=' or '!=' is not a good idea.
Use epsilon function instead.
Details: jessesquires.com/blog/floating-point-swift-ulp-and-epsilon/
I have been trying for the last 18 months taking all my code out of the ViewController to make it more like MVC should. ( Hence the title )
I've made a small step on my own by being able to do the following within one Class object, but now I want to break it down further
A very generic example: one file contains the data
struct Data {
var x = 0
var y = 0
}
and one file for the operation
class Adder {
var myObject = MyClass()
var z = 1
func addThem() {
z = myObject.x + myObject.y
}
}
now for the salient parts of the ViewController:
var data = Data()
var adder = Adder()
#IBAction func buttonPressed(sender: UIButton) {
// user input via textfield
data.x = Int(numeralOne.text!)!
data.y = Int(numeralTwo.text!)!
adder.addThem() // *
answerLabel.text = String(adder.z)
}
Ultimately I'd like to omit the line commented with the asterisk. I thought OO's encapsulation of (data) away from (adder) allows for adder.z to just automatically update in the background without involving the ViewController. That way a subtractor class (say) can operate on the same two struct properties.
My question? How can correctly referencing them from the VC.
PS. if i include return statements in the function it makes no difference.
It's not clear from your code exactly what the relationship is between the Data struct and MyClass but if a MyClass object has the values of x & y that you need then the following will work
class MyClass {
var x = 1
var y = 2
}
class Adder {
var myObject = MyClass()
var z: Int {
return myObject.x + myObject.y
}
}
let adder = Adder()
print(adder.z)
I'm trying to enumerate through all of the sprite nodes which I have subclassed as VillainSquirrel (as you can see below), and I am trying to access properties specific to the VillainSquirrel Class... however, I am running into difficulties.
This is the error I'm getting:
Tuple types '(SKNode, UnsafeMutablePointer<ObjCBool>)' and '()' have a different number of elements (2 vs. 0)
Here is what I do to try to enumerate through the nodes as VillainSquirrel:
self.enumerateChildNodesWithName("villainType1") {
node as! VillainSquirrel, stop in
if (node.position.y > self.size.height){
node.brownMarker.position = CGPointMake(node.position.x, self.size.height - 10)
node.brownMarker.zPosition = 1
self.addChild(node.brownMarker)
}
}
Here's my VillainSquirrel Class (or at least the beginning of it):
class VillainSquirrel: SKSpriteNode {
var brownMarker = SKSpriteNode()
var brownMarkerVisible: Bool
override init(texture: SKTexture!, color: SKColor, size: CGSize) {
self.brownMarker.zPosition = 1
self.brownMarker.xScale = 0.25
self.brownMarker.yScale = 0.25
self.brownMarker = SKSpriteNode(imageNamed:"brownMarkerTrans.png")
...
Any help you can offer would be greatly appreciated! Thanks!
Figured it out!
self.enumerateChildNodesWithName("villainType1") {
node, stop in
let realnode = node as! VillainSquirrel
if (node.position.y > self.size.height){
realnode.brownMarker.position = CGPointMake(realnode.position.x, self.size.height - 10)
realnode.brownMarker.zPosition = 1
if (!realnode.brownMarkerVisible){
self.addChild(realnode.brownMarker)
realnode.brownMarkerVisible = true
}
}
else if (realnode.brownMarkerVisible){
realnode.brownMarkerVisible = false
realnode.brownMarker.removeFromParent()
}
}
I just had to typeCast the node as my subclassed spriteNode in the block, I couldn't do it with node, stop in