I have created a turn based multiplayer board game using Swift and Game Center that works pretty well. One of the last items I would like to add is a way to keep a player from abandoning a game near the end if they know they are going to lose. It seems like the turnTimeout portion of the endTurn function is built in especially for this purpose, but I cannot get it to work. My endTurn function is below:
func endTurn(_ model: GameModel, completion: #escaping CompletionBlock) {
guard let match = currentMatch else {
completion(GameCenterHelperError.matchNotFound)
return
}
do {
let currenParticipantIndex: Int = (match.participants.firstIndex(of: match.currentParticipant!))!
let nextPerson = [match.participants[(currenParticipantIndex+1) % match.participants.count]]
print("end turn, next participant \(String(describing: nextPerson[0].player?.alias))")
match.endTurn(
withNextParticipants: nextPerson,
turnTimeout: 15,
match: try JSONEncoder().encode(model),
completionHandler: completion
)
} catch {completion(error)}
}
This function takes into account the advice from Anton in the comment of the answer to this question:
Trying to set a time limit on my Game Center game
to update the array of nextParticipant players so that the end of the array is never reached. I've also tried to account for this in my testing by having both player 1 and player 2 delay the end of their turn to see if it would fire (The game is a 2 player game only)
This should also answer this question:
Game Center turn timeouts
The documentation says:
timeoutDate: The time at which the player must act before forfeiting a turn. Your game decides what happens when a turn is forfeited. For some games, a forfeited turn might end the match. For other games, you might choose a reasonable set of default actions for the player, or simply do nothing.
https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/GameKit_Guide/ImplementingaTurn-BasedMatch/ImplementingaTurn-BasedMatch.html
Unfortunately I am unable to get the turnTimeout function to fire at all. I have done a fair amount of research and I found no definitive answer to what function is actually called when it fires (i.e. the player takes longer than the allotted time to take their turn). I would expect that the same function is called for a timeout as a regular call of the endTurn function and the below player listener is called:
func player(_ player: GKPlayer, receivedTurnEventFor match: GKTurnBasedMatch, didBecomeActive: Bool) {
if let vc = currentMatchmakerVC {
currentMatchmakerVC = nil
vc.dismiss(animated: true)
}
print("received turn event")
if !didBecomeActive {
print("\n\n\n player listener did become active")
print("match did change")
NotificationCenter.default.post(name: .matchDidChange, object: match)
} else if didBecomeActive {
print("present game")
NotificationCenter.default.post(name: .presentGame, object: match)
}
}
I am able to get the player listener (received turn event) to fire when endTurn is specifically called from the game, but I do not see anything that is called when the turnTimeout event triggers. If it was the player listener I would see the print statements in the console as well as the notification on the next player's device.
The GKTurnTimeoutDefault is 604,800 and is a Time Interval which I did some research on and arrived at the conclusion that it is in seconds, which is 7 days. I changed it to 0.00001, 15, 2000 and a few values in between but I wasn't able to get it to fire.
I also found the below, but the first has no answer and the second only says the turn timeouts probably warrants its own full answer:
Game Center Turnbased Game turn timeout
How to detect when Game Center turn based match has ended in iOS9?
I am thinking that my mistake is probably that I am unable to find the function that is called when the turn timeout fires, although I might be mistaken on the Time Interval values that I'm putting in there as well.
Thank you for taking the time to review my question :)
Related
I am following the Stanford CS193 SwiftUI iOS Development Course on YouTube and I am having a very difficult time comprehending how a certain piece of the code is working.
import Foundation
struct MemoryGame<CardContent> where CardContent: Equatable {
private(set) var cards: Array<Card>
**private var indexOfTheOneAndOnlyFaceUpCard: Int?**
mutating func choose(_ card: Card) {
if let chosenIndex = cards.firstIndex(where: { $0.id == card.id }),
!cards[chosenIndex].isFaceUp,
!cards[chosenIndex].isMatched
{
**if let potentialMatchIndex = indexOfTheOneAndOnlyFaceUpCard** {
**if cards[chosenIndex].content == cards[potentialMatchIndex].content** {
cards[chosenIndex].isMatched = true
cards[potentialMatchIndex].isMatched = true
}
indexOfTheOneAndOnlyFaceUpCard = nil
} else {
for index in cards.indices {
cards[index].isFaceUp = false
}
indexOfTheOneAndOnlyFaceUpCard = chosenIndex
}
cards[chosenIndex].isFaceUp.toggle()
}
print("\(cards)")
}
I have put asterisks around the lines I'm focusing on. I do not understand how cards[chosenIndex].content is being compared to cards[potentialMatchIndex].content, which stems from me not understanding how the if let potentialMatchIndex = indexOfTheOneAndOnlyFaceUpCard line is working. I believe I have a general understanding of optionals and how this code is running but I truly don't understand where the data/value is coming from for indexOfTheOneAndOnlyFaceUpCard.
Here is the video for reference. He begins talking about these lines of code at 1:12:40. Thank you for any help in advance!
In the memory game, a turn proceeds in two stages.
First, one card is turned face up.
Then, the user chooses another card. At that point, one of two things can happen: the new card is a match with the face up card or it isn't. But either way, that turn is over and all cards are now face down.
Thus, when the user chooses a card, if there is a face up card, we are in the middle of the turn, and we arrange things so there is no face up card; the turn is over.
But if there is no face up card, we are at the start of the turn, and we just turn the chosen card face up and wait for the second part of the turn.
So as far as the variable that tracks the face up card is concerned, the logic works like this:
var faceupCard : Card? = nil
if let card = faceupCard {
faceupCard = nil
} else {
faceupCard = chosenCard
}
So that's very clear, when pared down in that way: if the card is face up, make it not be face up; if it isn't face up, make it be face up.
The reason you are confused, in my opinion, is that this is a really bad way to write the code. The face-up-ness of a card is being used as a signal for whether we are in the middle of the turn or at the start of the turn. That's terrible programming. The way to signal what stage we are at is to have an enum whose job is to say what stage we are at! When you are programming you should say what you mean, not use a value in two different ways as is being done here (both track what card is face up and signal what stage of the turn we're on).
Another reason you might be confused is that everything has to be done in terms of indexes, because Card is a value type (struct). We cannot cycle through Cards, or point directly to the face up Card, or even ask a Card whether it is face up; we have to cycle thru indexes in our array of Cards, and track the index of the face up Card.
I'm currently programming a little pong-game for Mac, but I can't find a way to make the paddles move smoothly. I use the keyDown-function to detect when a key (for example W) is pushed. This executes a run-command:
override func keyDown(with event: NSEvent) {
if event.keyCode == 126 {
pR.run(SKAction.move(by: CGVector(dx: 0, dy: 15), duration: 0))
}
}
The thing is, when I push the arrow up-button (keyCode 126), my sprite/paddle (pR) moves one time, and if I keep holding the button down, it starts moving continuously.
Also, if I have two if-instructions (for example one for both pong players), it seems like there can't be made two inputs at the same time, that is, both players can't push a button and expect a response.
How can I solve these issues? All help is appreciated.
Question 1: Why isn't my paddle moving smoothly?
Why are you running a movement SKAction with a duration of 0?
This is the exact same as changing it's y position
You could making the duration longer like 0.1
If you want to do this, you should remove p1's SKAction every time you click the W key.
Why? You don't want it to run 2 SKActions at the same time.
(I'm assuming pR is your player 1 paddle. Stands for Paddle Right I guess.)
pR.removeAllActions()
pR.run(SKAction.move(by: CGVector(dx: 0, dy: 15), duration: 0.1))
Or, if you want it to be immediate, you could do this instead
(Although your paddle would move quite quickly depending on your frame rate)
pR.position.y += 15
I recommend the first option.
Or, if you like the second option because it's cleaner, you replace the 15 to 10 or 5.
Question 2: There can't be made two inputs at the same time
• 1: It's impossible for you to click 2 buttons at the 'exact' same time.
• 2: What does this mean?
• 3: Swift runs keyDown with the first key you pressed.
• 4: Swift then runs keyDown with the second key you pressed.
(Even if you think you pressed two keys at the same time, you didn't. One was probably pressed a microsecond before the second. It's all about timing. This is the same for SpriteKit on iOS, you cannot have two 'touchesBegan' inputs at the same time. It's quite rare.)
How to fix it:
You should be able to add a second 'if' statement for another key for player 2.
Example code (Except I used a switch to save space)
override func keyDown(with event: NSEvent) {
switch Int(event.keyCode) {
// W Key
case 13: // (Player 1 moves up)
// S Key
case 1: // (Player 1 moves down)
// I Key
case 34: // (Player 2 moves up)
// K Key
case 40: // (Player 2 moves down)
// Someone clicked the wrong key
default: return
}
}
Guess what! This code ^^^ almost works. I haven't answered your third question.
Question 3: Paddle Moves Once, Stops, Then Starts Moving Continuously
This is how the keyDown function works apparently.
Here is what I recommend:
• Keep track in a variable which keys have been pressed
• Use the 'keyUp' function to remove those stored values.
How does this work?
var keysPressed: Set<Int> = []
override func keyDown(with event: NSEvent) {
keysPressed.insert(event.keyCode)
}
override func keyUp(with event: NSEvent) {
keysPressed.remove(event.keyCode)
}
override func update(_ currentTime: TimeInterval) {
for i in keysPressed {
// Put my Movement Switch Statement In Here
}
}
I hope this helps! If you have any questions, please feel free to reply.
Snap, I just realized that this question was answered in the tiny comment section above. Oh well.
I'm wondering if I can get some help on understanding how to use the timeline
I plan to have the character walking along a path with triggers that will activate the timeline,
I have set up a simple state machine on the monkey
which would be triggered by the path to play the timeline, that part works fine and I can see the events firing
How could I play part of the timeline when triggered (is that even possible?)
where the entire sequence is held Walk>Jump>Walk>Look>Walk>Idle
Or what would I need to do to be able to achieve playback of a clip/or part of a sequence of clips
On the monkey:
public enum State
{
WalkHappy,
Look,
Jump,
}
IEnumerator WalkHappyState()
{
Debug.Log("WalkHappy: Enter");
**//walk sequence with timeline here??**
while (state == State.WalkHappy)
{
yield return 0;
}
Debug.Log("WalkHappy: Exit");
NextState();
}
Called on the paths event listener when triggered
private void OnCustomEvent(string eventname)
{
if (eventname.Contains("MonkeyRunningJumpUp"))
{
GameObject tempMonkey = GameObject.FindGameObjectWithTag("Player");
Monkey tempMonkeyState = tempMonkey.GetComponent<Monkey>();
tempMonkeyState.state = Monkey.State.Jump;
}
}
I have seen its possible to control where the playback starts on a timeline, but I don't know how to force a range/clip to play then pause at the end of the clip rather than the end of the entire sequence
THE question is.. how to stop/pause at end of a clip to resume later?
playableDirector = myPlayableDirectorGO.GetComponent<PlayableDirector>();
Debug.Log(timeLinePosition);
playableDirector.time = timeLinePosition / ((TimelineAsset)playableDirector.playableAsset).editorSettings.fps;
playableDirector.RebuildGraph();
playableDirector.Play();
with thanks Natalie :)
First of all, I recommend to install Default Playables if you haven’t done it yet.
Use Timeline Signals and markers to do something at the end of timeline or at certain point. Also, there is PlayableDirector.stopped event.
You can use pause and resume methods;
I'm doing some death animations for a game, and wanted to ask for some help. I want my monster to disappear in a puff of smoke, but not before it animates a slash effect going across his body.
I have 3 animations that I want to use:
weaponSlash - a line that draws across the monster. Looks like you slashed him with a sword.
smoke - a puff of smoke that slowly expands out
monsterFalling - the monster falls back, startled
What I want to do is play it in this order:
Simultaneously, the slash appears & the monster starts to fall back
About 0.25s into the above animation, I want the cloud to start to appear
When the cloud is about to end (so maybe after 1s) I want the monster to disappear
Remove the smoke, the monster, the sword, etc, and drop some coins on the ground
I started like this, as a test that works somewhat: (ignore the above times for now)
//Cancel any current actions, like a monster attacking
monster.removeAllActions()
//since you can't play 3 animations on one node at the same time, you have to create 3 separate nodes for everything
let slash = SKSpriteNode()
let cloud = SKSpriteNode()
cloud.size = monster.size
slash.size = monster.size
monster.addChild(cloud)
monster.addChild(slash)
//Start the slash animation
slash.run(self.player.currentlyEquippedWeapon.attackAnimation())
//Start the cloud animation (how I get it is elsewhere and not relevant)
cloud.run(cloudAnimation)
//Run the monster death animation, followed by the cleanup/coin dropping
monster.run(SKAction.sequence([monster.deathAnimation(), SKAction.wait(forDuration: 1), postDeathActions]))
The variable PostDeathActions above simply removes the monster node and animates some coins falling.
WHERE I NEED SOME HELP
So the above code doesn't work so great in that the animations all run independently of each other. Based on this, you can see how regardless of whether the slash/cloud finish, the monster will run two actions: him falling back, followed by cleanup, which just removes the monster and spawns the coins. As you can see I tried to delay this by adding a 1s delay but this is all somewhat of a hack since I may have different monsters or attacks, etc, that are faster/slower. I'd rather guarantee that everything finishes before I despawn the monster.
I tried to group this into an SKAction.Run like so:
let preDeath = SKAction.run {
[unowned self] in
monster.run(monster.deathAnimation()
slash.run(self.player.currentlyEquippedWeapon.attackAnimation())
cloud.run(cloudAnimation)
}
but this runs everything at the same time again.
What I want to do is sequence it like this (pseudo code):
let preDeathAnimations = SKAction.Group([slash, cloud, monsterDeathAnimation])
])
SKAction.sequence([preDeathAnimations, postDeathActions])
So this way it'll run all 3 before running cleanup.
Is there a way to do something like this? I know Sequnce/Group need to be run against an SKNode, but I don't have 3 separate ones.
Thanks for your time reading this and any advice you can offer!
This is one idea that I had, but you could use threading + state + onCompletion blocks to take the math out of it. I didn't test it out fully but this general concept should work:
let slash = SKAction.fadeIn(withDuration: 0.5)
let fall = SKAction.fadeOut(withDuration: 0.25)
let puff = SKAction.fadeIn(withDuration: 0.1)
// Put in ALL of the actions from ALL parties that you want to happen prior to puff:
func findLongestTime(from actions: [SKAction]) -> TimeInterval {
var longestTime = TimeInterval(0)
for action in actions {
if action.duration > longestTime { longestTime = action.duration }
}
// Note, if you put a sequence into this function I don't know if it will work right..
// Might need another func like `findDurationOfSequence(_ sequence: SKAction) -> TimeInterval
return longestTime
}
// Note, if you have the monster doing more than falling prior to puff, then you will
// need to subtract those as well:
let monsterActionsPriorToPuff = [fall]
// Add the duration of all monster animations prior to puff:
var MAPTP_duration = TimeInterval(0)
for action in monsterActionsPriorToPuff {
MAPTP_duration += action.duration
}
// Calculate our final wait time, with no negative numbers:
var waitTime = findLongestTime(from: [slash, fall]) - MAPTP_duration
if waitTime < 0 { waitTime = 0 }
let wait = SKAction.wait(forDuration: waitTime)
// Our monster sequence (I forgot to add the disappear, just add after puff)
let monsterSequence = SKAction.sequence([fall, wait, puff])
// Player slashes:
SKSpriteNode().run(slash)
// Monster will wait 0.25 seconds after falling,
// for slash to finish before puffing:
SKSpriteNode().run(monsterSequence)
et me know if this idea isn't working I can try updating it.
Ive got the following code to check for agent collision.
I want to fire a MIDI message only once when they start colliding.
Ive got this so far.
void draw(){
//Loop through people, and check collision, then play note, if intersecting
for(int i=0;i<people.size();i++){
Person p = people.get(i);
p.collide(people,collisions);
p.triggerMidi();
p.run();
}
}
public void collide(ArrayList<Person> people, ArrayList<Person> connections) {
for(Person other : people) {
if (other != this) {
if (this.collide(other)) {
this.isIntersecting=true;
//connections.add(other); // when a collision is found, add it to a list for later use.
}
}
}
}
void triggerMidi(){
if(!hasPlayed && this.isIntersecting==true){
MIDI.sendNoteOn(channel, agentNote, 127);
delay(200);
MIDI.sendNoteOff(channel,agentNote, 127);
hasPlayed=true;
}
}
This works to play the sound only once at the start of collision.
But how do I get it to play again at the start of another collision.
Obviously I have to set hasPlayed back to false.
But where?
When I set it to false in the collide loop, the sound play a million times.
Any ideas?
First off, you probably shouldn't have a call to delay() from your drawing thread. That will cause your sketch to become laggy and unresponsive. Instead, you might want to put your sound playing on a different thread.
Then, to answer your original question- do you know how long the note plays for? If so, just record the time that the note starts, and then use that time to check the elapsed time. The millis() function might come in handy for that. When the elapsed time is greater than the duration of the note, then you can set hasPlayed back to false.