-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* initial failure to add auto-camera, need to rethink some UI stuff * run swiftformat * super basic implementation of auto-camera implemented * reset default birthrate * implement auto camera in mac and screensaver builds * refactor most camera logic into a new class * set up autocamera to track a body node throughout the tank * adjust camera body movement speed * run swiftformat * fix main interface not coming back if disabling auto-camera by tank touch * add more zoom levels and remove some unneeded code * get auto-zoom working * adjust debug log lines * change minimum zoom level for auto-camera * fix bug where camera body is not present in scene after creating a new tank * cleanup * fix mac/screensaver version * fix tvos build * fix some auto-camera related bugs UI not being dismissed when deselecting creature auto-camera snapping to a distant body when enabled zoom not functioning correctly when selecting a creature when in auto-camera mode auto-camera mode doesn't enable when zoomed on a specific creature * fix bugs in tvos interface * run swiftformat
- Loading branch information
Showing
13 changed files
with
429 additions
and
95 deletions.
There are no files selected for viewing
8 changes: 8 additions & 0 deletions
8
Aeon Garden.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>IDEDidComputeMac32BitWarning</key> | ||
<true/> | ||
</dict> | ||
</plist> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// | ||
// AeonCameraBodyNode.swift | ||
// Aeon Garden | ||
// | ||
// Created by Brad Root on 2/9/20. | ||
// Copyright © 2020 Brad Root. All rights reserved. | ||
// | ||
|
||
import SpriteKit | ||
|
||
class AeonCameraBodyNode: SKNode, Updatable { | ||
var lastUpdateTime: TimeInterval = 0 | ||
var currentTarget: CGPoint? | ||
var targetingTimer: Timer? | ||
var targetTimeLimit: TimeInterval = 30 | ||
public var movementSpeed: CGFloat = 8 | ||
public var turnSpeed: CGFloat = 750 | ||
|
||
required init?(coder _: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
override init() { | ||
super.init() | ||
|
||
physicsBody = SKPhysicsBody(circleOfRadius: 20) | ||
physicsBody?.categoryBitMask = 0 | ||
physicsBody?.collisionBitMask = 0 | ||
physicsBody?.allowsRotation = true | ||
physicsBody?.affectedByGravity = false | ||
physicsBody?.restitution = 0.8 | ||
physicsBody?.friction = 0.1 | ||
physicsBody?.mass = 1 | ||
physicsBody?.linearDamping = 0.5 | ||
physicsBody?.angularDamping = 1 | ||
zPosition = 1 | ||
|
||
// let body = SKSpriteNode(texture: foodTexture) | ||
// body.size = CGSize(width: 40, height: 40) | ||
// body.zPosition = 1 | ||
// body.name = "AeonCameraBodySprite" | ||
// addChild(body) | ||
} | ||
|
||
func update(_ currentTime: TimeInterval) { | ||
lastUpdateTime = currentTime | ||
move() | ||
} | ||
|
||
@objc func pickRandomTarget() { | ||
if let scene = scene as? AeonTankScene { | ||
if let randomNode = scene.creatureNodes.randomElement() { | ||
Log.debug("Picked new target for camera body: \(randomNode.position)") | ||
currentTarget = randomNode.position | ||
|
||
targetingTimer?.invalidate() | ||
|
||
targetingTimer = Timer.scheduledTimer( | ||
timeInterval: targetTimeLimit, | ||
target: self, | ||
selector: #selector(pickRandomTarget), | ||
userInfo: nil, | ||
repeats: false | ||
) | ||
} | ||
} | ||
} | ||
|
||
// MARK: - Movement | ||
|
||
func distance(point: CGPoint) -> CGFloat { | ||
return CGFloat(hypotf(Float(point.x - position.x), Float(point.y - position.y))) | ||
} | ||
|
||
func angleBetween(pointOne: CGPoint, andPointTwo pointTwo: CGPoint) -> CGFloat { | ||
let xdiff = (pointTwo.x - pointOne.x) | ||
let ydiff = (pointTwo.y - pointOne.y) | ||
let rad = atan2(ydiff, xdiff) | ||
return rad - (CGFloat.pi / 2) // convert from atan's right-pointing zero to CG's up-pointing zero | ||
} | ||
|
||
func move() { | ||
if let toCGPoint = currentTarget { | ||
// Thrust | ||
let radianFactor: CGFloat = CGFloat.pi / 180 | ||
let rotationInDegrees = zRotation / radianFactor | ||
let newRotationDegrees = rotationInDegrees + 90 | ||
let newRotationRadians = newRotationDegrees * radianFactor | ||
|
||
let thrustVector: CGVector = CGVector( | ||
dx: cos(newRotationRadians) * movementSpeed, | ||
dy: sin(newRotationRadians) * movementSpeed | ||
) | ||
|
||
physicsBody?.applyForce(thrustVector) | ||
|
||
// Rotation | ||
var goalAngle = angleBetween(pointOne: position, andPointTwo: toCGPoint) | ||
var creatureAngle = atan2(physicsBody!.velocity.dy, physicsBody!.velocity.dx) - (CGFloat.pi / 2) | ||
|
||
creatureAngle = convertRadiansToPi(creatureAngle) | ||
goalAngle = convertRadiansToPi(goalAngle) | ||
|
||
let angleDifference = convertRadiansToPi(goalAngle - creatureAngle) | ||
let angleDivisor: CGFloat = turnSpeed | ||
|
||
physicsBody?.applyTorque(angleDifference / angleDivisor) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
// | ||
// AeonCameraNode.swift | ||
// Aeon Garden | ||
// | ||
// Created by Brad Root on 2/9/20. | ||
// Copyright © 2020 Brad Root. All rights reserved. | ||
// | ||
|
||
import SpriteKit | ||
|
||
class AeonCameraNode: SKCameraNode, Updatable { | ||
var body: AeonCameraBodyNode = AeonCameraBodyNode() | ||
var selectedNode: SKNode? | ||
var autoCameraIsEnabled: Bool = false | ||
let cameraMoveDuration: TimeInterval = 0.25 | ||
var lastUpdateTime: TimeInterval = 0 | ||
var zoomTimer: Timer? | ||
var currentZoomState: zoomState = .zoomOut | ||
weak var interfaceDelegate: AeonTankInterfaceDelegate? | ||
|
||
enum zoomState { | ||
case fullZoom | ||
case threeQuartersZoom | ||
case halfZoom | ||
case quarterZoom | ||
case zoomOut | ||
} | ||
|
||
// MARK: - Functions | ||
|
||
func selectedNode(_ node: SKNode) { | ||
if selectedNode == node { | ||
deselectNode() | ||
} else { | ||
Log.info("📷 Selected Node") | ||
if let currentCreature = selectedNode as? AeonCreatureNode, currentCreature != node { | ||
currentCreature.hideSelectionRing() | ||
|
||
if let cameraBody = node as? AeonCameraBodyNode { | ||
// If previously selected node was a creature, | ||
// and the new node is the camera body, | ||
// move the camera body there for a soft transition | ||
cameraBody.position = currentCreature.position | ||
} | ||
} | ||
|
||
selectedNode = node | ||
if let newCreature = selectedNode as? AeonCreatureNode { | ||
newCreature.displaySelectionRing(withColor: .aeonBrightYellow) | ||
interfaceDelegate?.creatureSelected(newCreature) | ||
zoom(.fullZoom) | ||
} else { | ||
changeCameraZoomLevel() | ||
} | ||
} | ||
} | ||
|
||
func deselectNode(animated: Bool = true) { | ||
Log.info("Deselected Node") | ||
if let currentCreature = selectedNode as? AeonCreatureNode { | ||
currentCreature.hideSelectionRing() | ||
if animated { | ||
interfaceDelegate?.creatureDeselected() | ||
} | ||
} | ||
selectedNode = nil | ||
if animated { | ||
zoom(.zoomOut) | ||
} | ||
} | ||
|
||
func zoom(_ state: zoomState, speed: TimeInterval = 1) { | ||
removeAllActions() | ||
switch state { | ||
case .fullZoom: | ||
run(SKAction.scale(to: 0.4, duration: speed)) | ||
currentZoomState = .fullZoom | ||
case .threeQuartersZoom: | ||
run(SKAction.scale(to: 0.55, duration: speed)) | ||
currentZoomState = .halfZoom | ||
case .halfZoom: | ||
run(SKAction.scale(to: 0.7, duration: speed)) | ||
currentZoomState = .halfZoom | ||
case .quarterZoom: | ||
run(SKAction.scale(to: 0.85, duration: speed)) | ||
currentZoomState = .halfZoom | ||
case .zoomOut: | ||
guard let scene = scene else { fatalError("Camera is not in a scene.") } | ||
removeAllActions() | ||
let scaleAction = SKAction.scale(to: 1, duration: speed) | ||
let moveAction = SKAction.move( | ||
to: CGPoint(x: scene.size.width / 2, y: scene.size.height / 2), | ||
duration: 1 | ||
) | ||
run(SKAction.group([scaleAction, moveAction])) | ||
currentZoomState = .zoomOut | ||
} | ||
} | ||
|
||
func update(_ currentTime: TimeInterval) { | ||
if let selectedNode = self.selectedNode { | ||
let cameraAction = SKAction.move(to: selectedNode.position, duration: cameraMoveDuration) | ||
run(cameraAction) | ||
} | ||
body.update(currentTime) | ||
lastUpdateTime = currentTime | ||
} | ||
|
||
func startAutoCamera() { | ||
Log.debug("📷 Auto camera started...") | ||
|
||
if let scene = scene, body.scene == nil { | ||
Log.debug("📷 Creating camera body in scene.") | ||
scene.addChild(body) | ||
} | ||
|
||
body.position = position | ||
|
||
selectedNode(body) | ||
body.pickRandomTarget() | ||
} | ||
|
||
func stopAutoCamera() { | ||
Log.debug("📷 Auto camera stopped.") | ||
} | ||
|
||
@objc func changeCameraZoomLevel() { | ||
Log.debug("📷 Camera auto-zoom updated.") | ||
|
||
zoomTimer?.invalidate() | ||
|
||
zoomTimer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(changeCameraZoomLevel), userInfo: nil, repeats: false) | ||
|
||
let randomZoomLevels: [zoomState] = [.halfZoom, .threeQuartersZoom, .fullZoom] | ||
zoom(randomZoomLevels.randomElement()!, speed: 20) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.