I have the following code in my main.swift:
let strategist = GKMinmaxStrategist()
strategist.gameModel = position
strategist.maxLookAheadDepth = 1
strategist.randomSource = nil
let move = strategist.bestMoveForActivePlayer()
...where position is an instance of my GKGameModel subclass Position. After this code is run, move is nil. bestMoveForPlayer(position.activePlayer!) also results in nil (but position.activePlayer! results in a Player object).
However,
let moves = position.gameModelUpdatesForPlayer(position.activePlayer!)!
results in a non-empty array of possible moves. From Apple's documentation (about bestMoveForPlayer(_:)):
Returns nil if the player is invalid, the player is not a part of the game model, or the player has no valid moves available.
As far as I know, none of this is the case, but the function still returns nil. What could be going on here?
If it can be of any help, here's my implementation of the GKGameModel protocol:
var players: [GKGameModelPlayer]? = [Player.whitePlayer, Player.blackPlayer]
var activePlayer: GKGameModelPlayer? {
return playerToMove
}
func setGameModel(gameModel: GKGameModel) {
let position = gameModel as! Position
pieces = position.pieces
ply = position.ply
reloadLegalMoves()
}
func gameModelUpdatesForPlayer(thePlayer: GKGameModelPlayer) -> [GKGameModelUpdate]? {
let player = thePlayer as! Player
let moves = legalMoves(ofPlayer: player)
return moves.count > 0 ? moves : nil
}
func applyGameModelUpdate(gameModelUpdate: GKGameModelUpdate) {
let move = gameModelUpdate as! Move
playMove(move)
}
func unapplyGameModelUpdate(gameModelUpdate: GKGameModelUpdate) {
let move = gameModelUpdate as! Move
undoMove(move)
}
func scoreForPlayer(thePlayer: GKGameModelPlayer) -> Int {
let player = thePlayer as! Player
var score = 0
for (_, piece) in pieces {
score += piece.player == player ? 1 : -1
}
return score
}
func isLossForPlayer(thePlayer: GKGameModelPlayer) -> Bool {
let player = thePlayer as! Player
return legalMoves(ofPlayer: player).count == 0
}
func isWinForPlayer(thePlayer: GKGameModelPlayer) -> Bool {
let player = thePlayer as! Player
return isLossForPlayer(player.opponent)
}
func copyWithZone(zone: NSZone) -> AnyObject {
let copy = Position(withPieces: pieces.map({ $0.1 }), playerToMove: playerToMove)
copy.setGameModel(self)
return copy
}
If there's any other code I should show, let me know.
You need to change the activePlayer after apply or unapply a move.
In your case that would be playerToMove.
The player whose turn it is to perform an update to the game model. GKMinmaxStrategist assumes that the next call to the applyGameModelUpdate: method will perform a move on behalf of this player.
and, of course:
Function applyGameModelUpdate Applies a GKGameModelUpdate to the game model, potentially resulting in a new activePlayer. GKMinmaxStrategist will call this method on a copy of the primary game model to speculate about possible future moves and their effects. It is assumed that calling this method performs a move on behalf of the player identified by the activePlayer property.
func applyGameModelUpdate(gameModelUpdate: GKGameModelUpdate) {
let move = gameModelUpdate as! Move
playMove(move)
//Here change the current Player
let player = playerToMove as! Player
playerToMove = player.opponent
}
The same goes for your unapplyGameModelUpdate implementation.
Also, keep special attention to your setGameModelimplementation as it should copy All data in your model. This includes activePlayer
Sets the data of another game model. All data should be copied over, and should not maintain any pointers to the copied game state. This is used by the GKMinmaxStrategist to process permutations of the game without needing to apply potentially destructive updates to the primary game model.
I had the same problem. Turns out, .activePlayer has to return one of the instances returned by .players. It's not enough to return a new instance with matching .playerId.
simple checklist:
GKMinmaxStrategist's .bestMove(for:) is called
GKMinmaxStrategist's .gameModel is set
GKGameModel's isWin(for:) does not return true before move
GKGameModel's isLoss(for:) does not return true before move
GKGameModel's gameModelUpdates(for:) does not return nil all the time
GKGameModel's score(for:) is implemented
Related
I'm trying to play a local video and apply a CIFilter in realtime with no lag. How can I do that? I already know how to apply a CIFilter to a video in AVPlayer but the performance it's not as fast as I want.
This is my current code:
#objc func exposure(slider: UISlider, event: UIEvent) {
if let touchEvent = event.allTouches?.first {
switch touchEvent.phase {
case .moved:
player.currentItem?.videoComposition = AVVideoComposition(asset: player.currentItem!.asset, applyingCIFiltersWithHandler: { request in
let exposureFilter = CIFilter.exposureAdjust()
exposureFilter.inputImage = request.sourceImage.clampedToExtent()
exposureFilter.ev = slider.value
let output = self.exposureFilter.outputImage!.cropped(to: request.sourceImage.extent)
// Provide the filter output to the composition
request.finish(with: output, context: nil)
})
default:
break
}
}
}
The problem is that you re-create and re-assign the video composition to the player item every time the slider value changes. This is very costly and unnecessary. You can do the following instead:
Create the filter somewhere outside the composition block and keep a reference to it, for instance in a property.
Also, create the composition only once and let it apply the referenced filter (instead of creating a new one with every frame).
When the slider value changes, only set the corresponding parameter value of the filter. The next time the composition will render a frame, it will automatically use the new parameter value because it uses a reference to the just-changed filter.
Something like this:
let exposureFilter = CIFilter.exposureAdjust()
init() {
// set initial composition
self.updateComposition()
}
func updateComposition() {
player.currentItem?.videoComposition = AVVideoComposition(asset: player.currentItem!.asset, applyingCIFiltersWithHandler: { request in
self.exposureFilter.inputImage = request.sourceImage.clampedToExtent()
let output = self.exposureFilter.outputImage!.cropped(to: request.sourceImage.extent)
request.finish(with: output, context: nil)
})
}
#objc func exposureChanged(slider: UISlider) {
self.exposureFilter.ev = slider.value
// we need to re-set the composition if the player is paused to cause an update (see remark below)
if player.rate == 0.0 {
self.updateComposition()
}
}
(By the way, you can just do slider.addTarget(self, action:#selector(exposureChanged(slider:)), for: .valueChanged) to get notified when the slider value changes. No need to evaluate events.)
One final note: There actually is a use case when you want to re-assign the composition, which is when the player is currently paused but you still want to show a preview of the current frame with the filter values change. Please refer to this technical note from Apple on how to do that.
I am making an app using ARKit to measure between two points. The goal is to be able to measure length, store that value, then measure width and store.
The problem I am having is disposing of the nodes after I get the measurement.
Steps so far:
1) added a button with a restartFunction. this worked to reset the measurements but did not remove the spheres from scene, and also made getting the next measurement clunky.
2) set a limit on > 2 nodes. This functionaly works best. But again the spheres just stay floating in the scene.
Here is a screen shot of the best result I have had.
#objc func handleTap(sender: UITapGestureRecognizer) {
let tapLocation = sender.location(in: sceneView)
let hitTestResults = sceneView.hitTest(tapLocation, types: .featurePoint)
if let result = hitTestResults.first {
let position = SCNVector3.positionFrom(matrix: result.worldTransform)
let sphere = SphereNode(position: position)
sceneView.scene.rootNode.addChildNode(sphere)
let tail = nodes.last
nodes.append(sphere)
if tail != nil {
let distance = tail!.position.distance(to: sphere.position)
infoLabel.text = String(format: "Size: %.2f inches", distance)
if nodes.count > 2 {
nodes.removeAll()
}
} else {
nodes.append(sphere)
}
}
}
I am new to Swift (coding in general) and most of my code has come from piecing together tutorials.
I think the issue here is that your are not actually removing the SCNNodes you have added to hierarchy.
Although you are removing the nodes from what I assume is an array of SCNNodes by calling: nodes.removeAll(), you first need to actually remove them from the scene hierarchy.
So what you need to do is call the following function on any node you wish to remove:
removeFromParentNode()
Which simply:
Removes the node from its parent’s array of child nodes.
As such you would do something like this which first removes the nodes from the hierarchy, and then removes them from the array:
for nodeAdded in nodesArray{
nodeAdded.removeFromParentNode()
}
nodesArray.removeAll()
So based on the code provided you could do the following:
if nodes.count > 2 {
for nodeAdded in nodes{
nodeAdded.removeFromParentNode()
}
nodes.removeAll()
}
For future reference, if you want to remove all SCNNodes from you hierarchy you can also call:
self.augmentedRealityView.scene.rootNode.enumerateChildNodes { (existingNode, _) in
existingNode.removeFromParentNode()
}
Whereby self.augmentedRealityView refers to the variable:
var augmentedRealityView: ARSCNView!
Here is a very basic working example based on (and modified from) the code you have provided:
/// Places A Marker Node At The Desired Tap Point
///
/// - Parameter sender: UITapGestureRecognizer
#objc func handleTap(_ sender: UITapGestureRecognizer) {
//1. Get The Current Tap Location
let currentTapLocation = sender.location(in: sceneView)
//2. Check We Have Hit A Feature Point
guard let hitTestResult = self.augmentedRealityView.hitTest(currentTapLocation, types: .featurePoint).first else { return }
//3. Get The World Position From The HitTest Result
let worldPosition = positionFromMatrix(hitTestResult.worldTransform)
//4. Create A Marker Node
createSphereNodeAt(worldPosition)
//5. If We Have Two Nodes Then Measure The Distance
if let distance = distanceBetweenNodes(){
print("Distance == \(distance)")
}
}
/// Creates A Marker Node
///
/// - Parameter position: SCNVector3
func createSphereNodeAt(_ position: SCNVector3){
//1. If We Have More Than 2 Nodes Remove Them All From The Array & Hierachy
if nodes.count >= 2{
nodes.forEach { (nodeToRemove) in
nodeToRemove.removeFromParentNode()
}
nodes.removeAll()
}
//2. Create A Marker Node With An SCNSphereGeometry & Add It To The Scene
let markerNode = SCNNode()
let markerGeometry = SCNSphere(radius: 0.01)
markerGeometry.firstMaterial?.diffuse.contents = UIColor.cyan
markerNode.geometry = markerGeometry
markerNode.position = position
sceneView.scene.rootNode.addChildNode(markerNode)
//3. Add It To The Nodes Array
nodes.append(markerNode)
}
/// Converts A matrix_float4x4 To An SCNVector3
///
/// - Parameter matrix: matrix_float4x4
/// - Returns: SCNVector3
func positionFromMatrix(_ matrix: matrix_float4x4) -> SCNVector3{
return SCNVector3(matrix.columns.3.x, matrix.columns.3.y, matrix.columns.3.z)
}
/// Calculates The Distance Between 2 Nodes
///
/// - Returns: Float?
func distanceBetweenNodes() -> Float? {
guard let firstNode = nodes.first, let endNode = nodes.last else { return nil }
let startPoint = GLKVector3Make(firstNode.position.x, firstNode.position.y, firstNode.position.z)
let endPoint = GLKVector3Make(endNode.position.x, endNode.position.y, endNode.position.z)
let distance = GLKVector3Distance(startPoint, endPoint)
return distance
}
For an example of a measuringApp which might help your development you can look here: ARKit Measuring Example
Hope it helps...
This looks like a logic issue. You're assigning nodes.last to tail just before checking if tail is not nil. So it will never be != nil so you'll never execute the nodes.append(sphere) in the else.
I agree with #dfd. Set a breakpoint to make sure the code is being executed before continuing.
I want a sprite to follow the player and I tried to achieve this with the Pathfinding of GameplayKit. This is working, but there's problem. I get the path from the enemy sprite to the player sprite with the following code:
func moveEnemy(to playerPos: CGPoint){
guard !moving else { return }
moving = true
let startNode = GKGraphNode2D(point: float2(Float(self.position.x), Float(self.position.y)))
let endNode = GKGraphNode2D(point: float2(Float(playerPos.x), Float(playerPos.y)))
PhysicsHelper.graph.connectUsingObstacles(node: startNode)
PhysicsHelper.graph.connectUsingObstacles(node: endNode)
let path = PhysicsHelper.graph.findPath(from: startNode, to: endNode)
guard path.count > 0 else{ return }
var actions = [SKAction]()
for node in path{
if let point2d = node as? GKGraphNode2D{
let point = CGPoint(x:CGFloat(point2d.position.x) , y:CGFloat(point2d.position.y))
let action = SKAction.move(to: point, duration: 1)
actions.append(action)
}
}
let seq = SKAction.sequence(actions)
self.run(seq) {
self.moving = false
}
}
This is called in another function of my enemy in this block:
case .follow:
if allowMovement{
if self.action(forKey: "Hurt") == nil{
// orientEnemy(to: playerPos)
moveEnemy(to: playerPos)
// animateWalk()
}
}
and this function is called in the update function of my GameScene in this block:
if enemy.checkCircularIntersection(player: player, node: enemy, radius: 40){
print("Enemy is close to player")
enemy.update(playerPos: player.position)
}
The checkCircularIntersection function only checks, if the player is near the enemy.
My problem is that the enemy follows the player but when the player moves, the enemy moves to the point where the player stands before, then it stops for a second and then it moves again to the point where the player stood. But the player is already moved away.
Is there a way to let the enemy permanently follow the player and avoid obstacles without stopping?
Your code specifically guards against moving again until the existing move is completed.
guard !moving else { return }
moving = true
If you want to use paths you will have to recompute and reapply the path movement regularly to account for the target's movement.
However, to achieve your aim, Paths are not the most suitable GameKit tool to use. GKAgents are specifically designed for that kind of thing. It will require a significant refactoring of your code, but take a look at Agents, Goals and Behaviours.
I'm doing a small game in Swift 3 and SpriteKit. I want to do a collision with my character and a special object that increases my score in 1, but for some reason, when I detect the collision, the score increases in 2 or 3.
I'm removing from parent my SpriteKitNode but it seems that it doesn't work.
Here's my code:
func checkCollisionsObject(){
enumerateChildNodes(withName: "objeto") {node, _ in
let objeto = node as! SKSpriteNode
if objeto.frame.intersects(self.personaje.frame){
objeto.removeFromParent()
self.actualizarPoints()
//self.labelNivel.text = "Level: \(self.nivel)"
}
}
}
func actualizarPoints() {
self.pointsCounter += 1
points.text = "Points: \(pointsCounter)"
}
The problem is that the collision detection is happening at 60fps (pretty fast). So in that time multiple collision detections are occurring. You are just handling the first one.
I usually like to have a property on the object that I can trigger so that I know whether or not the object has collided, and set it so that it doesn't detect anymore collisions.
In your case the object is just a SKSpriteNode so you would have to set the property in userData or make the object a custom object and have the property in the custom object class
func checkCollisionsObject(){
enumerateChildNodes(withName: "objeto") {node, _ in
let objeto = node as! CustomObject
if objeto.frame.intersects(self.personaje.frame) && objeto.hasCollided == false {
objeto.hasCollided = true
objeto.removeFromParent()
self.actualizarPoints()
}
}
}
func actualizarPoints() {
self.pointsCounter += 1
points.text = "Points: \(pointsCounter)"
}
I have multiple achievements that the user can achieve during the game. When the user goes to the achievement VC, he should see an image when the achievement is accomplished.
I am now using this code in the viewDidLoad function:
if unlockedAchievement1 == true
{
achievement1Text.textColor = UIColor.greenColor()
achievement1Image.image = UIImage(named: "achievement1Unlocked")
}
if unlockedAchievement2 == true
{
achievement2Text.textColor = UIColor.greenColor()
achievement2Image.image = UIImage(named: "achievement2Unlocked")
}
This would work, but it takes a lot of time to copy paste this overtime. How can I shorten this? Should I make a class? I read about for in loops, but I did not quite understood that. Any help is welcome!
I feel like I have seen this before. But anyway...
//use array of bools. First declare as empty array.
//Use Core data to achieve saving the Booleans if you need to.
let achievements:[Bool] = []
let achievementsImage:[UIImage] = []
let achievementsText:[UITextView] = []
func viewDidLoad() {
//Say you have 5 achievements.
for index in 0 ..< 5 {
achievements.append(false)
achievementsImage.append(UIImage(named: "achievementLocked"))
achievementText.append(UITextView())
//Then you can edit it, such as.
achievementText[index].frame = CGRect(0, 0, 100, 100)
achievementText[index].text = "AwesomeSauce"
self.view.addSubview(achievementText[index])
achievementText[index].textColor = UIColor.blackColor()
}
}
//Call this function and it will run through your achievements and determine what needs to be changed
func someFunction() {
for index in 0 ..< achievements.count {
if(achievements[index]) {
achievements[index] = true
achievementsImage[index] = UIImage(named: String(format: "achievement%iUnlocked", index))
achievementsText[index].textColor = UIColor.greenColor
}
}
}
I'd recommend making your unlockedAchievement1, unlockedAchievement2, etc. objects tuples, with the first value being the original Bool, and the second value being the image name. You'd use this code:
// First create your data array
var achievements = [(Bool, String)]()
// Then you can make your achievements.
// Initially, they're all unearned, so the Bool is false.
let achievement1 = (false, "achievement1UnlockedImageName")
achievements.append(achievement1)
let achievement2 = (false, "achievement2UnlockedImageName")
achievements.append(achievement2)
// Now that you have your finished data array, you can use a simple for-loop to iterate.
// Create a new array to hold the images you'll want to show.
var imagesToShow = [String]()
for achievement in achievements {
if achievement.0 == true {
imagesToShow.append(achievement.1)
}
}