AgentsCatalog是Apple的一个例子,他是基于OC的,我认为重写这些例子是个学习语言的好方法。首先,你的目标是正确的,通过观摩源码,你知道如何达到目标,其次你所使用的手段也是正确的,你可以从中学到某个类的用法,几个类的相互关系等等。
前几天我一直纠结一个事情,我打算把工具条合并到窗口的标题栏上去,就像Safari那样,我查了很多资料一直没能解决,但是这个例子无意间解决了这个问题。而且只有一行代码。你建立一个Cocoa应用,就像下面这样。
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet weak var window: NSWindow!
@IBOutlet weak var skView: SKView!
@IBOutlet weak var sceneControl: NSSegmentedControl!
@IBAction func selectScene(sender: NSSegmentedControl) {
let sceneTYpe = AAPLSceneType(rawValue: sender.selectedSegment)
let scene = AAPLGameScene.sceneWithType( sceneTYpe!, size: CGSizeMake(800, 600))
scene.scaleMode = SKSceneScaleMode.AspectFit
self.skView.presentScene(scene)
}
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
self.window.titleVisibility = NSWindowTitleVisibility.Hidden
// Configure the view.
self.skView.ignoresSiblingOrder = true
self.skView.showsFPS = true
self.skView.showsNodeCount = true
// Present the scene.
self.selectScene(self.sceneControl )
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
你的工具条就合并到window标题栏上去了。其他什么也不用做。接下来你要重写下面这个类:
这是所有后面用到的 Scene 类基类,他本身是SKSence的子类。你会看到 SKComponentSystem(组件系统)和 GKAgent2D(跟踪代理)的用法。这里处理了游戏事件循环和对鼠标的响应。
import Cocoa
import SpriteKit
import GameplayKit
enum AAPLSceneType: Int {
case AAPLSceneTypeSeek = 0,
AAPLSceneTypeWander,
AAPLSceneTypeFlee,
AAPLSceneTypeAvoid,
AAPLSceneTypeSeparate,
AAPLSceneTypeAlign,
AAPLSceneTypeFlock,
AAPLSceneTypePath,
AAPLSceneTypesCount
}
let AAPLDefaultAgentRadius: CGFloat = 40.0
class AAPLGameScene: SKScene {
var sceneName: String?
required override init(size: CGSize) {
super.init(size: size)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// A component system to manage per-frame updates for all agents.
var agentSystem: GKComponentSystem?
// An agent whose position tracks that of mouseDragged (OS X) or touchesMoved (iOS) events.
// This agent has no display representation, but can be used to make other agents follow the mouse/touch.
var trackingAgent: GKAgent2D?
// YES when the mouse is dragging (OS X) or a touch is moving
var seeking: Bool = false
var stopGoal: GKGoal?
var lastUpdateTime: NSTimeInterval = 0
class func sceneWithType( sceneType: AAPLSceneType, size: CGSize) -> AAPLGameScene {
var sceneClass: AAPLGameScene.Type
switch (sceneType) {
case AAPLSceneType.AAPLSceneTypeSeek:
sceneClass = AAPLSeekScene.self
break;
case AAPLSceneType.AAPLSceneTypeWander:
sceneClass = AAPLWanderScene.self
break;
case AAPLSceneType.AAPLSceneTypeFlee:
sceneClass = AAPLFleeScene.self
break;
case AAPLSceneType.AAPLSceneTypeAvoid:
sceneClass = AAPLAvoidScene.self
break;
case AAPLSceneType.AAPLSceneTypeSeparate:
sceneClass = AAPLSeparateScene.self
break;
case AAPLSceneType.AAPLSceneTypeAlign:
sceneClass = AAPLAlignScene.self
break;
case AAPLSceneType.AAPLSceneTypeFlock:
sceneClass = AAPLFlockScene.self
break;
case AAPLSceneType.AAPLSceneTypePath:
sceneClass = AAPLPathScene.self
break;
default:
sceneClass = AAPLGameScene.self
break;
}
return sceneClass.init(size: CGSizeMake(800, 600))
}
override func didMoveToView(view: SKView) {
self.agentSystem = GKComponentSystem( componentClass: GKAgent2D.self )
self.trackingAgent = GKAgent2D()
let x = CGRectGetMidX(self.frame)
let y = CGRectGetMidY(self.frame)
self.trackingAgent!.position = vector_float2( Float(x) , Float(y))
}
override func update(currentTime: NSTimeInterval) {
// Calculate delta since last update and pass along to the agent system.
if (lastUpdateTime == 0) {
lastUpdateTime = currentTime;
}
let delta = currentTime - lastUpdateTime
lastUpdateTime = currentTime
self.agentSystem!.updateWithDeltaTime( delta )
}
override func mouseDown(theEvent: NSEvent) {
self.seeking = true
}
override func mouseUp(theEvent: NSEvent) {
self.seeking = false
}
override func mouseDragged(theEvent: NSEvent) {
let position = theEvent.locationInNode(self)
self.trackingAgent!.position = vector_float2 (Float(position.x), Float(position.y))
}
}
import Cocoa
import SpriteKit
import GameplayKit
class AAPLAgentNode: SKNode, GKAgentDelegate{
var agent = GKAgent2D()
var color: SKColor = SKColor.highlightColor()
var drawsTrail: Bool = false
var triangleShape: SKShapeNode!
var particles: SKEmitterNode!
var defaultParticleRate: CGFloat = 0
init( scene: SKScene, radius: CGFloat, position: CGPoint ){
super.init()
self.position = position;
self.zPosition = 10;
scene.addChild(self)
// An agent to manage the movement of this node in a scene.
agent = GKAgent2D()
agent.radius = Float(radius)
agent.position = (vector_float2)( Float(position.x), Float(position.y))
agent.delegate = self;
agent.maxSpeed = 100;
agent.maxAcceleration = 50;
// A circle to represent the agent's radius in the agent simulation.
let circleShape = SKShapeNode(circleOfRadius: radius)
circleShape.lineWidth = 2.5
circleShape.fillColor = SKColor.grayColor()
circleShape.zPosition = 1
self.addChild(circleShape)
// A triangle to represent the agent's heading (rotation) in the agent simulation.
var points = Array(count: 4, repeatedValue: CGPoint())
let triangleBackSideAngle = CGFloat((135.0 / 360.0) * (2 * M_PI))
points[0] = CGPointMake(radius,0); // Tip.
points[1] = CGPointMake(radius * cos(triangleBackSideAngle), radius * sin(triangleBackSideAngle)); // Back bottom.
points[2] = CGPointMake(radius * cos(triangleBackSideAngle), -radius * sin(triangleBackSideAngle)); // Back top.
points[3] = CGPointMake(radius, 0); // Back top.
let triangleShape = SKShapeNode(points: &points, count: 4)
triangleShape.lineWidth = 2.5;
triangleShape.zPosition = 1;
self.addChild( triangleShape)
// A particle effect to leave a trail behind the agent as it moves through the scene.
particles = SKEmitterNode(fileNamed: "Trail.sks")
defaultParticleRate = particles!.particleBirthRate;
particles!.position = CGPointMake(-radius + 5, 0);
particles!.targetNode = scene;
particles!.zPosition = 0;
self.addChild( particles!)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// GKAgentDelegate
func agentWillUpdate(agent: GKAgent){
}
/*
* Called after [GKAgent updateWithDeltaTime:] is called each frame.
*/
func agentDidUpdate(agent: GKAgent){
// Agent and sprite use the same coordinate system (in this app),
// so just convert vector_float2 position to CGPoint.
let agent2D = agent as! GKAgent2D
self.position = CGPointMake( CGFloat(agent2D.position.x), CGFloat(agent2D.position.y))
self.zRotation = CGFloat(agent2D.rotation)
}
}
//
// AAPLWanderScene.swift
// TestAgents
//
// Created by wuzhiqiang on 15/11/14.
// Copyright © 2015年 wuzhiqiang. All rights reserved.
//
import Cocoa
import SpriteKit
import GameplayKit
class AAPLWanderScene: AAPLGameScene {
override func didMoveToView(view: SKView) {
super.didMoveToView(view)
let pt = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame))
let wanderer = AAPLAgentNode( scene: self, radius: 40, position: pt )
wanderer.color = SKColor.cyanColor()
wanderer.agent.behavior = GKBehavior( goal: GKGoal( toWander:10), weight: 100)
self.agentSystem!.addComponent( wanderer.agent)
}
}
下面我们来描述一下程序的工作过程。
在AppDelegate里面,我们建立一个Scence,8个中的一个,这里是AAPLWanderScene,漫游型。
这个 AAPLWanderScene 建立一个自己的 Node (一艘船),然后建立并将 GKBehavior 对象赋给了 节点的agent.behavior 属性。
最后,将这个节点作为一个组件加入到组件系统。
就是这样。程序就会运行,组件系统会工作,定时循环更新他的每个组件。你的小船会自动出发漫游。
在节点里,甚至还加入了粒子效果,你能看到小船后面的水泡。