@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0720"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C09C12501C49B18B00D1DB6A"
BuildableName = "Clean OSX.app"
BlueprintName = "Clean OSX"
ReferencedContainer = "container:clean.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C09C12501C49B18B00D1DB6A"
BuildableName = "Clean OSX.app"
BlueprintName = "Clean OSX"
ReferencedContainer = "container:clean.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C09C12501C49B18B00D1DB6A"
BuildableName = "Clean OSX.app"
BlueprintName = "Clean OSX"
ReferencedContainer = "container:clean.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C09C12501C49B18B00D1DB6A"
BuildableName = "Clean OSX.app"
BlueprintName = "Clean OSX"
ReferencedContainer = "container:clean.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -4,6 +4,11 @@
<dict>
<key>SchemeUserState</key>
<dict>
<key>Clean OSX.xcscheme</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
</dict>
<key>clean.xcscheme</key>
<dict>
<key>orderHint</key>
@@ -27,6 +32,11 @@
<key>primary</key>
<true/>
</dict>
<key>C09C12501C49B18B00D1DB6A</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>
</plist>
@@ -8,46 +8,92 @@
import SceneKit

enum GroundType: Int {
enum GroundType : Int {
case InTheAir
case Surface
}

enum Action : Int {
case Idle
case Walking
case Lifting
// case Dropping
}

class Character {

init() {
let characterScene = SCNScene(named: "game.scnassets/baby/idle.scn")!
let initialSceneName = sceneNameForAction(.Idle)
let characterScene = SCNScene(named: initialSceneName)!
let characterTopLevelNode = characterScene.rootNode.childNodes[0]
node.addChildNode(characterTopLevelNode)

let (min, max) = node.boundingBox
let collisionCapsuleRadius = CGFloat(max.x - min.x) * 0.4
let collisionCapsuleHeight = CGFloat(max.y - min.y)
let collisionCapsuleHeight = self.height()

let collidorGeometry = SCNCapsule(capRadius: collisionCapsuleRadius, height: collisionCapsuleHeight)
let characterCollisionNode = SCNNode()
characterCollisionNode.name = "collider"
characterCollisionNode.name = "collision"
characterCollisionNode.position = SCNVector3(0.0, collisionCapsuleHeight * 0.51, 0.0)
characterCollisionNode.physicsBody = SCNPhysicsBody(type: .Dynamic, shape:SCNPhysicsShape(geometry: SCNCapsule(capRadius: collisionCapsuleRadius, height: collisionCapsuleHeight), options:nil))
characterCollisionNode.physicsBody!.contactTestBitMask = BitmaskCollision
characterCollisionNode.physicsBody = SCNPhysicsBody(type: .Kinematic, shape:SCNPhysicsShape(geometry: collidorGeometry, options:nil))
characterCollisionNode.physicsBody!.contactTestBitMask = BitmaskCollision | BitmaskLiftable
node.addChildNode(characterCollisionNode)

walkAnimation = CAAnimation.animationWithSceneNamed("game.scnassets/baby/run.scn")
walkAnimation.usesSceneTimeBase = false
walkAnimation.fadeInDuration = 0.3
walkAnimation.fadeOutDuration = 0.3
walkAnimation.repeatCount = Float.infinity
walkAnimation.speed = Character.speedFactor
walkAnimation.animationEvents = [
SCNAnimationEvent(keyTime: 0.1) { (_, _, _) in self.playFootStep() },
SCNAnimationEvent(keyTime: 0.6) { (_, _, _) in self.playFootStep() }
]
}

let node = SCNNode()

// MARK: Lifting
var liftingOriginalPosition: SCNVector3?
var lifting: SCNNode! {
willSet {
if isLifting {
let offset = node.convertPosition(SCNVector3Make(0, 0, 2), toNode: lifting)
let liftedFinalPosition = SCNVector3Make(lifting.position.x + offset.x, 1, lifting.position.z + offset.z)
print(liftedFinalPosition)
lifting.position = liftedFinalPosition
}
}
didSet {
dropZoneVisible = isLifting
if isLifting {
liftingOriginalPosition = lifting.position
}
}
}

var isLifting : Bool {
get {
return lifting != nil
}
}

var dropzone : Dropzone!
var dropZoneVisible: Bool = false {
didSet {
if dropZoneVisible {
dropzone = Dropzone()
node.addChildNode(dropzone)
} else {
dropzone.removeFromParentNode()
}
}
}

func dropObject() {
lifting = nil
transitionToAction(.Idle)
}

func liftObject(object: SCNNode) {
lifting = object
transitionToAction(.Lifting)
}

// MARK: Movement
static let speedFactor = Float(1.5)
static let speedFactor = Float(3.0)
private var groundType = GroundType.InTheAir
private var previousUpdateTime = NSTimeInterval(0.0)
private var accelerationY = SCNFloat(0.0) // gravity simulation
@@ -60,81 +106,106 @@ class Character {
}
}

func height() -> CGFloat {
// let (min, max) = node.boundingBox
// return max.y - min.y
return CGFloat(1.0)
}

func walkInDirection(direction: float3, time: NSTimeInterval, scene: SCNScene) -> SCNNode? {
if previousUpdateTime == 0.0 {
previousUpdateTime = time
}

let deltaTime = Float(min(time - previousUpdateTime, 1.0 / 60.0))
let characterSpeed = deltaTime * Character.speedFactor * 2.0
let characterSpeed = deltaTime * Character.speedFactor
previousUpdateTime = time

// move
if direction.x != 0.0 && direction.z != 0.0 && groundType != .InTheAir {
if direction.x != 0.0 || direction.z != 0.0 {// && groundType != .InTheAir {
let position = float3(node.position)
node.position = SCNVector3(position + direction * characterSpeed)
directionAngle = SCNFloat(atan2(direction.x, direction.z))

if lifting != nil {
let (min, max) = (lifting?.boundingBox)!
let liftingObjectHeight = max.y - min.y
let objectY = self.height() + liftingObjectHeight
let liftingObjectPosition = SCNVector3Make(CGFloat(position.x), objectY, CGFloat(position.z))
lifting?.position = liftingObjectPosition
}

isWalking = true
}
else {
isWalking = false
}

// altitude
var position = node.position
var p0 = position
var p1 = position

let maxRise = SCNFloat(0.08)
let maxJump = SCNFloat(10.0)
p0.y -= maxJump
p1.y += maxRise

// Do a vertical ray intersection
var groundNode: SCNNode?
let results = scene.physicsWorld.rayTestWithSegmentFromPoint(p1, toPoint: p0, options:[SCNPhysicsTestCollisionBitMaskKey: BitmaskCollision, SCNPhysicsTestSearchModeKey: SCNPhysicsTestSearchModeClosest])

if let result = results.first {
let groundAltitude = result.worldCoordinates.y
groundNode = result.node

return nil
}

// MARK: Animations
private func transitionToAction(action: Action) {
let key = identifierForAction(action)
if node.animationForKey(key) == nil {
node.addAnimation(characterAnimationForAction(action), forKey: key)

let threshold = SCNFloat(1e-5)
let gravityAcceleration = SCNFloat(0.18)
if groundAltitude < position.y - threshold {
accelerationY += SCNFloat(deltaTime) * gravityAcceleration
if groundAltitude < position.y - 0.2 {
groundType = .InTheAir
for oldKey in node.animationKeys {
if oldKey != key {
node.removeAnimationForKey(oldKey, fadeOutDuration: transitionDurationForAction(action))
}
}
else {
accelerationY = 0
}

position.y -= accelerationY

if groundAltitude > position.y {
accelerationY = 0
position.y = groundAltitude
groundType = .Surface
}

node.position = position
}

return groundNode
}

// MARK: Animations
private func identifierForAction(action: Action) -> String {
switch(action) {
case .Idle:
return isLifting ? "idle-lifting" : "idle"
case .Walking:
return isLifting ? "walk-lifting" : "walk"
case .Lifting:
return "lift"
}
}

private func sceneNameForAction(action : Action) -> String {
let identifier = identifierForAction(action)
return "game.scnassets/baby/\(identifier).scn"
}

private func transitionDurationForAction(action: Action) -> CGFloat {
if action == .Idle && isLifting {
return 0.2
} else {
return 0.5
}
}

private var walkAnimation: CAAnimation!
func characterAnimationForAction(action: Action) -> CAAnimation {
let name = sceneNameForAction(action)
let animation = CAAnimation.animationWithSceneNamed(name)!
animation.fadeInDuration = transitionDurationForAction(action)

if action != .Lifting {
animation.repeatCount = Float.infinity
}

if action == .Walking {
animation.speed = Character.speedFactor
}

return animation
}

private var isWalking: Bool = false {
didSet {
if oldValue != isWalking {
if isWalking {
node.addAnimation(walkAnimation, forKey: "walk")
transitionToAction(.Walking)
} else {
node.removeAnimationForKey("walk", fadeOutDuration: 0.2)
transitionToAction(.Idle)
}
}
}
@@ -10,6 +10,36 @@ import simd
import SceneKit
import GameController

#if os(OSX)

protocol KeyboardAndMouseEventsDelegate {
func keyDown(view: NSView, theEvent: NSEvent) -> Bool
func keyUp(view: NSView, theEvent: NSEvent) -> Bool
}

let Space : UInt16 = 49

private enum KeyboardDirection : UInt16 {
case Left = 123
case Right = 124
case Down = 125
case Up = 126

var vector : float2 {
switch self {
case .Up: return float2( 0, -1)
case .Down: return float2( 0, 1)
case .Left: return float2(-1, 0)
case .Right: return float2( 1, 0)
}
}
}

extension RoomViewController: KeyboardAndMouseEventsDelegate {
}

#endif

extension RoomViewController {

// MARK: Controller orientation
@@ -18,20 +48,26 @@ extension RoomViewController {
private static let controllerDirectionLimit = float2(1.0)

internal func controllerDirection() -> float2 {
// if let dpad = controllerDPad {
// if dpad.xAxis.value == 0.0 && dpad.yAxis.value == 0.0 {
// controllerStoredDirection = float2(0.0)
// } else {
// let inputValue = float2(dpad.xAxis.value, -dpad.yAxis.value * RoomViewController.controllerAcceleration)
// print(inputValue)
// controllerStoredDirection = clamp(controllerStoredDirection + inputValue,
// min: -RoomViewController.controllerDirectionLimit,
// max: RoomViewController.controllerDirectionLimit)
// }
// }
if let dpad = controllerDPad {
if dpad.xAxis.value == 0.0 && dpad.yAxis.value == 0.0 {
controllerStoredDirection = float2(0.0)
} else {
let inputValue = float2(dpad.xAxis.value, -dpad.yAxis.value * RoomViewController.controllerAcceleration)
controllerStoredDirection = clamp(controllerStoredDirection + inputValue,
min: -RoomViewController.controllerDirectionLimit,
max: RoomViewController.controllerDirectionLimit)
}
}
return controllerStoredDirection
}

internal func setupGameControllers() {
#if os(OSX)
roomView.eventsDelegate = self
#endif
}


// MARK: Events
#if os(iOS)
@@ -70,4 +106,33 @@ extension RoomViewController {
}

#endif

#if os(OSX)

func keyDown(view: NSView, theEvent: NSEvent) -> Bool {
if let direction = KeyboardDirection(rawValue: theEvent.keyCode) {
if !theEvent.ARepeat {
controllerStoredDirection += direction.vector
}
return true
} else if (theEvent.keyCode == Space) {
self.character.dropObject()
return true
}

return false
}

func keyUp(view: NSView, theEvent: NSEvent) -> Bool {
if let direction = KeyboardDirection(rawValue: theEvent.keyCode) {
if !theEvent.ARepeat {
controllerStoredDirection -= direction.vector
}
return true
}

return false
}

#endif
}
@@ -0,0 +1,28 @@
//
// Dropzone.swift
// clean
//
// Created by Gabriel O'Flaherty-Chan on 2016-01-24.
// Copyright © 2016 Gabrieloc. All rights reserved.
//
import SceneKit

class Dropzone : SCNNode {

override init()
{
super.init()

let geometry = SCNPlane(width: 1, height: 1)
geometry.firstMaterial?.diffuse.contents = NSColor.yellowColor()
self.geometry = geometry
self.rotation = SCNVector4Make(-1, 0, 0, CGFloat(M_PI / 2.0))
self.position = SCNVector3Make(0, 0.001, 2)
}

required init(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
}
@@ -0,0 +1,32 @@
//
// LiftableObject.swift
// clean
//
// Created by Gabriel O'Flaherty-Chan on 2016-01-15.
// Copyright © 2016 Gabrieloc. All rights reserved.
//
import SceneKit

class LiftableObject {

init() {
let geometry = SCNSphere(radius: 1)
geometry.firstMaterial = SCNMaterial()
geometry.firstMaterial?.diffuse.contents = NSColor.redColor()
node.geometry = geometry

// let collisionNode = SCNNode()
// collisionNode.name = "collision"
// collisionNode.physicsBody = SCNPhysicsBody(type: .Dynamic, shape: SCNPhysicsShape(geometry: geometry, options: nil))
// collisionNode.physicsBody!.categoryBitMask = BitmaskCollision
// collisionNode.physicsBody!.physicsShape = SCNPhysicsShape(node: node, options: [SCNPhysicsShapeTypeKey: SCNPhysicsShapeTypeConcavePolyhedron])
// collisionNode.position = SCNVector3(0.0, 1.0, 0.0)
// node.addChildNode(collisionNode)
node.physicsBody = SCNPhysicsBody(type: .Kinematic, shape: SCNPhysicsShape(geometry: geometry, options: nil))
node.physicsBody!.categoryBitMask = BitmaskLiftable
}

let node = SCNNode()
}
@@ -9,5 +9,23 @@
import SceneKit

class RoomView: SCNView {
var eventsDelegate: KeyboardAndMouseEventsDelegate?

#if os(OSX)

override func keyDown(theEvent: NSEvent) {
guard let eventsDelegate = eventsDelegate where eventsDelegate.keyDown(self, theEvent: theEvent) else {
super.keyDown(theEvent)
return
}
}

override func keyUp(theEvent: NSEvent) {
guard let eventsDelegate = eventsDelegate where eventsDelegate.keyUp(self, theEvent: theEvent) else {
super.keyUp(theEvent)
return
}
}

#endif
}
@@ -6,17 +6,18 @@
// Copyright © 2015 Gabrieloc. All rights reserved.
//
import UIKit

import SceneKit
import GameController

// Collision bit masks
let BitmaskCollision = 1 << 2
let BitmaskWater = 1 << 3
let BitmaskCollision = 1 << 2
let BitmaskLiftable = 1 << 3

#if os(iOS) || os(tvOS)
import UIKit
typealias ViewController = UIViewController
#elseif os(OSX)
import AppKit
typealias ViewController = NSViewController
#endif

@@ -26,13 +27,10 @@ class RoomViewController: ViewController, SCNSceneRendererDelegate, SCNPhysicsCo
return view as! RoomView
}

private let character = Character()
private var camera: SCNNode!
let character = Character()
private var cameraNode: SCNNode!

// Camera
private var currentGround: SCNNode!
private var mainGround: SCNNode!
private var groundToCameraPosition = [SCNNode: SCNVector3]()
// Controls
internal var controllerDPad: GCControllerDirectionPad?
@@ -56,35 +54,43 @@ class RoomViewController: ViewController, SCNSceneRendererDelegate, SCNPhysicsCo
self.roomView.scene = scene
self.roomView.playing = true
self.roomView.loops = true
self.roomView.showsStatistics = true
// self.roomView.allowsCameraControl = true
scene.rootNode.addChildNode(character.node)

let startPosition = scene.rootNode.childNodeWithName("startingPoint", recursively: true)!
character.node.transform = startPosition.transform

camera = scene.rootNode.childNodeWithName("camera", recursively: true)!
let lookAtConstraint = SCNLookAtConstraint(target: character.node)
lookAtConstraint.gimbalLockEnabled = true;
camera.constraints = [lookAtConstraint]
let startPosition = scene.rootNode.childNodeWithName("startingPoint", recursively: true)!.position
character.node.position = startPosition

cameraNode = scene.rootNode.childNodeWithName("camera", recursively: true)!
// let lookAtConstraint = SCNLookAtConstraint(target: character.node)
// lookAtConstraint.gimbalLockEnabled = true;
// cameraNode.constraints = [lookAtConstraint]
// Collisions
var collisionNodes = [SCNNode]()
scene.rootNode.enumerateChildNodesUsingBlock { (node, _) in
switch node.name {
case let .Some(s) where s.rangeOfString("collision") != nil:
collisionNodes.append(node)
default:
break;
}
}

for node in collisionNodes {
node.hidden = false
setupCollisionNode(node)
}
// var collisionNodes = [SCNNode]()
// scene.rootNode.enumerateChildNodesUsingBlock { (node, _) in
// switch node.name {
// case let .Some(s) where s.rangeOfString("collision") != nil:
// collisionNodes.append(node)
// default:
// break;
// }
// }
scene.physicsWorld.contactDelegate = self
roomView.delegate = self

let box = LiftableObject()
box.node.position = SCNVector3Make(2, 1, 2)
scene.rootNode.addChildNode(box.node)

// for node in collisionNodes {
// node.hidden = false
// setupCollisionNode(node)
// }
setupGameControllers()
}

private func setupCollisionNode(node: SCNNode) {
@@ -118,18 +124,22 @@ class RoomViewController: ViewController, SCNSceneRendererDelegate, SCNPhysicsCo
func renderer(renderer: SCNSceneRenderer, updateAtTime time: NSTimeInterval) {
let scene = roomView.scene!
let direction = characterDirection()

character.walkInDirection(direction, time: time, scene: scene)

let characterPosition = character.node.position
cameraNode.position = SCNVector3Make(characterPosition.x - 2, cameraNode.position.y, characterPosition.z + 4)
// cameraNode.rotation = SCNVector4Make(33, 45, 0, 0)
}

// MARK: SCNPhysicsContactDelegate
func physicsWorld(world: SCNPhysicsWorld, didBeginContact contact: SCNPhysicsContact) {
contact.match(category: BitmaskCollision) { (matching, other) in
self.characterNode(other, hitWall: matching, withContact: contact)
}
contact.match(category: BitmaskLiftable) { (matching, _) in
self.character.liftObject(matching)
}
}

func physicsWorld(world: SCNPhysicsWorld, didUpdateContact contact: SCNPhysicsContact) {
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
Binary file not shown.
Binary file not shown.
BIN +20 Bytes (100%) clean/game.scnassets/baby/idle.scn
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN -2.48 KB (80%) clean/game.scnassets/room.scn
Binary file not shown.
Binary file not shown.