Adding game center achievements to my game - ios

I was trying to add achievements to one of my games, I made a list and started adding them with image and description in itunes connect, but I can't find a tutorial written in swift, and with a simple function like unlockachievement and the achievement id passed inside that
In my game, inside the viewdidload of the game over screen i made this
if (achievement condition){
//unlock achievement ("achievementID")
}
Is this a correct way to add achievements?In this view i have many stats passed from previous view so if I put the unlockachievement function here the popup should appear at the end of the game without distracting the player
For the leaderboards I made a func that gets score and leaderboard, I want to do the same for achievements, how do I do it?Is it possible?
I also read about achievement progress in the game center view,is it possible to avoid it?Especially with hidden achievements that should show up only when conditions are met

this is how I solved
func loadAchievementPercentages() {
print("get % ach")
GKAchievement.loadAchievementsWithCompletionHandler { (allAchievements, error) -> Void in
if error != nil {
print("GC could not load ach, error:\(error)")
}
else
{
//nil if no progress on any achiement
if(allAchievements != nil) {
for theAchiement in allAchievements! {
if let singleAchievement:GKAchievement = theAchiement {
self.gameCenterAchievements[singleAchievement.identifier!] = singleAchievement
}
}
}
for(id,achievement) in self.gameCenterAchievements {
print("\(id) - \(achievement.percentComplete)")
}
}
}
}
func incrementCurrentPercentageOfAchievement (identifier:String, amount:Double) {
if GKLocalPlayer.localPlayer().authenticated {
var currentPercentFound:Bool = false
if ( gameCenterAchievements.count != 0) {
for (id,achievement) in gameCenterAchievements {
if (id == identifier) {
//progress on the achievement found
currentPercentFound = true
var currentPercent:Double = achievement.percentComplete
currentPercent = currentPercent + amount
reportAchievement(identifier,percentComplete:currentPercent)
break
}
}
}
if (currentPercentFound == false) {
//no progress on the achievement
reportAchievement(identifier,percentComplete:amount)
}
}
}
func reportAchievement (identifier:String, percentComplete:Double) {
let achievement = GKAchievement(identifier: identifier)
achievement.percentComplete = percentComplete
let achievementArray: [GKAchievement] = [achievement]
GKAchievement.reportAchievements(achievementArray, withCompletionHandler: {
error -> Void in
if ( error != nil) {
print(error)
}
else {
print ("reported achievement with % complete of \(percentComplete)")
self.gameCenterAchievements.removeAll()
self.loadAchievementPercentages()
}
})
}
in the view did load I have this
if (conditions for the achievement)
{
incrementCurrentPercentageOfAchievement("achievementID", amount: 100)
}
Tested now and it worked,now i want to do something different,I don't want to increment an achievement percentage but to set the percentage, for example:
Achievement - combo of 10
- Play a game
- Combo of 8 and shows 80% progress on the achievement
- Another game
- Combo of 2
With the code I have now it should unlock the achievement because the percentage is cumulative, so I have to write another function to change the achievement progress percentage and to set it to 20
Now if in a game I met the requirements and unlock the achievement,and in the next game I don't get this condition again, the achievement is still unlocked, right?

Related

Frame dropping when using AVAssetWriter?

I’m working on an app that processes video frames, draws effects on the frame and saves them. When saving the video using AVAssetWriter I get stutters in the resulting video, but when I reduce the amount of processing on every frame, then stutter is reduced.
Writing and processing are on separate processes.
Every processed frame is dispatched in a queue for writing.
Here is the code:
_writingQueue.async {
autoreleasepool {
synchronized(self) {
if self._status.rawValue >= VideoRecordingModelStatus.finishingRecordingPart1.rawValue {
return
}
if !self._haveStartedSession {
self._assetWriter?.startSession(atSourceTime: CMSampleBufferGetPresentationTimeStamp(sampleBuffer))
self._haveStartedSession = true
}
let input = (mediaType == AVMediaType.video) ? self._videoInput : self._audioInput
while !(input?.isReadyForMoreMediaData ?? false) {}
let success = input!.append(sampleBuffer)
if !success {
let error = self._assetWriter?.error
synchronized(self) {
self.transitionToStatus(.failed, error: error as NSError?)
}
}
}
}
}
Resulting video

Swift CoreMotion background saving battery

I need to get users activity on background using Core Motion. I was able to reach this goal by adding the locations update as background mode and enable the background location fetch. But as I'm not interest in the user location, this results in waste of battery and a blue sign ( iPhone X ) that indicates the app is updating your locations in background.
Is possible to run core motion in background without update the location of the user in order to use less battery and not show the blue sign to the user??
Thank you really much in advance!!
EDIT
Example function code:
private func startTrackingActivityType() {
activityManager.startActivityUpdates(to: OperationQueue.main) {
[weak self] (activity: CMMotionActivity?) in
guard let activity = activity else { return }
DispatchQueue.main.async {
if activity.walking {
self?.activityTypeLabel.text = "Walking"
} else if activity.stationary {
self?.activityTypeLabel.text = "Stationary"
} else if activity.running {
self?.activityTypeLabel.text = "Running"
} else if activity.automotive {
self?.activityTypeLabel.text = "Automotive"
}
}
}
}
You don't need to be in background at all. Next time your app will launch, you can retrieve history for MotionActivity. In addition you can also get raw historical sensor data. But you should schedule it.
Details: https://developer.apple.com/documentation/coremotion/cmmotionactivitymanager?language=objc

iOS Game Center Leaderboard forces a default score of 0

I'm currently working on a racing game where the player's goal is to complete a task in the shortest time possible.
I need to save the users fastest time, but the Game Center always has a default time of 0 seconds. And since Game Center only saves 1 value per user, it tosses the player's actual time/score.
The only thing I was able to do was to temporarily set the leaderboard to save the longest time. That way a time longer than 0 secs would be saved. Then I would switch it back so that shorter times would get saved.
This is obviously not a solution for a production level game.
Is there any wrong with the way I'm saving or retrieving scores? I'm at a loss for how I can either turn off that default 0 sec time or override it.
func loadLeaderBoard(level level:Int){
let leaderboardRequest = GKLeaderboard() as GKLeaderboard!
leaderboardRequest.identifier = "level\(level)ID"
if leaderboardRequest != nil {
leaderboardRequest.loadScoresWithCompletionHandler({ (score, error) -> Void in
if error != nil {
print(error)
} else {
if let myscore:[GKScore] = score {
self.records.updateValue(myscore.first!.formattedValue!, forKey: level)
}
}
})
}
}
func recordTime(level level: Int, record:Int64){
let score = GKScore(leaderboardIdentifier: "level1ID")
score.value = record
GKScore.reportScores([score]) { (error) -> Void in
if error != nil {
print(error)
}else {
print("Score reported: \(score.value)")
}
}
}

Small lag/jitter when tapping the screen

So my game is almost complete... but there's this little glitch or jitter that occurs when I press and hold my finger on the screen which, now I've noticed, I can't un-notice...
It happens really fast, and only happens when a function is called to handle tap&holds (long press). This happens after 0.2seconds have passed using a timer.
I've tried breakpointing it to pin down where exactly the jitter happens in the code, but it seems I can not fine tune it enough to locate it.
My update method is typical:
override func update(currentTime: CFTimeInterval) {
//calc delta time
if lastUpdateTime > 0 {
dt = currentTime - lastUpdateTime
} else {
dt = 0
}
lastUpdateTime = currentTime
//timer for handleLongPress
if touched {
longTouchTimer += dt
}
if longTouchTimer >= 0.2 && !canLongPressNow {
canLongPressNow = true
handleLongPress()
} else {
canLongPressNow = false
}
...
//switch GameSate
//switch CharacterState
}
My function to handleLongPress is this:
func handleLongPress() {
//switch gameState
//if in gameplay gamestate
if canLongPressNow {
//rotate player
//change character state
startPlayerAnimation_Sliding()
}
touched = false
longTouchTimer = 0
}
The startPlayerAnimation_Sliding() just iterates a texture array of the playerNode.
func startPlayerAnimation_Sliding() {
var textures: Array<SKTexture> = []
for i in 0..<KNumSlideFrames{
textures.append(SKTexture(imageNamed: "slide\(i)"))
}
let playerAnimation = SKAction.animateWithTextures(textures, timePerFrame: 0.3)
player.runAction(SKAction.repeatActionForever(playerAnimation), withKey: "sliding")
}
Is there anything noticeable that may be causing this?
update
I've removed this from my update(..) method, and it seems smooth again... and I have no idea why...? Maybe because it's removing a key (explosion) that hasn't been created yet? or the fact it's removing these keys every frame... Doesn't make sense though... But I'm calling it a night, and looking at this again tomorrow. Thanks for your help so far. Have a good evening. (will update tomorrow)
//for animations
switch characterState {
case .Running:
player.removeActionForKey("exploding")
player.removeActionForKey("sliding")
break
case .Sliding:
player.removeActionForKey("running")
player.removeActionForKey("exploding")
break
case .Exploding:
player.removeActionForKey("running")
player.removeActionForKey("sliding")
break
}
Yikes, how you create textures is what is slowing you down a lot, you are creating new textures every time a touch happens, this is not needed. Instead do:
var textures: Array<SKTexture> = []
var playerAnimation : SKAction?
func loadingPhase() //however this is defined for you
{
for i in 0..<KNumSlideFrames{
textures.append(SKTexture(imageNamed: "slide\(i)"))
}
playerAnimation = SKAction.repeatActionForever(SKAction.animateWithTextures(textures, timePerFrame: 0.3))
}
func startPlayerAnimation_Sliding() {
player.runAction(playerAnimation!, withKey: "sliding")
}

Game Center achievements not supported

I am having some problems trying to implement game center achievements into an iOS game. The game already has the player authentication and leaderboards setup and they are working fine. The first error I get is when using this method:
GKAchievementDescription.loadAchievementDescriptionsWithCompletionHandler
It gives me an error which states two things:
"The requested operation could not be completed due to an error communicating with the server."
"App does not support achievements."
Five achievements have been added to iTunes connect so I don't no why it says it doesn't support them. The other issues are when I use this method:
GKAchievement.reportAchievements
When this is called the error in the completion handler is nil, but it returns "no bundle for bundleID: (null)". The achievement banner doesn't show and there is no achievements tab in the game centre view.
The app was recently transferred to another developer but he then wanted some extra features added to it, so I'm using a provisioning profile and developer certificate provided by him so I can test game center and in app purchases properly. It seems like the problem I'm having is relating to the transfer?
So my question is what could be the problem causing the game to 'not support achievements'?
Any help would be much appreciated,
thank you.
Your question is a bit vague, how are those two methods looking exactly in your code. Its hard to help with two lines of a function
You load code should look something like this
/// Load achievements
func loadAchievements(){
print("Loading submitted achievements")
GKAchievement.loadAchievementsWithCompletionHandler( { (achievements, error:NSError?) -> Void in
guard error == nil else {
print("Error loading achievements progress: \(error)")
return
}
guard let validAchievements = achievements else { return }
for achievement in validAchievements {
print("Name: \(achievement.identifier)")
print("Percentage: \(achievement.percentComplete)")
print("Date: \(achievement.lastReportedDate)")
self.achievementsDict.updateValue(achievement, forKey: achievement.identifier!)
}
})
}
You report code should look something like this
/// Save achievement progress
func reportAchievementProgress(percent: Double, achievementID: String) {
guard self.localPlayer.authenticated else { return }
let achievement = self.checkAchievement(achievementID) as GKAchievement? //
if achievement != nil {
achievement!.percentComplete = percent
achievement!.showsCompletionBanner = true
GKAchievement.reportAchievements([achievement!], withCompletionHandler: { (error:NSError?) -> Void in
guard error == nil else {
print(error)
return
}
print("Reported achievement: \(achievementID)) to: \(percent) %")
})
}
}
/// Check achievement
private func checkAchievement(achievementID: String) -> GKAchievement {
var achievement = self.achievementsDict[achievementID]
if achievement == nil {
print("Achievement with no previous progress, saving...")
achievement = GKAchievement(identifier: achievementID)
self.achievementsDict.updateValue(achievement!, forKey: achievement!.identifier!)
}
return achievement!
}
achievementsDict is a dictionary where you cache your achievements.
var achievementsDict = [String: GKAchievement]()
Does this help?

Resources