说明

本系列文章是对<3D Apple Games by Tutorials>一书的学习记录和体会

此书对应的代码地址

SceneKit系列文章目录

更多iOS相关知识查看github上WeekWeekUpProject

01-Scenes场景

在Xcode主菜单中选择File > New > Project.

选择iOS/Application/Game模板,点击Next

输入项目名GeometryFighter,选择Swift语言, SceneKit游戏技术,Universal设备类型, 去掉单元测试的勾,点击Next:

下一步,清理不需要的文件. 删除art.scnassets文件夹. 清理GameViewController.swift文件中的内容:

import UIKit
import SceneKit
class GameViewController: UIViewController {override func viewDidLoad() {super.viewDidLoad()}override var shouldAutorotate: Bool {
return true
}override var prefersStatusBarHidden: Bool {
return true
} }
复制代码

然后在viewDidLoad()前面添加:

var scnView: SCNView!
复制代码

再在prefersStatusBarHidden()下方添加:

func setupView() {scnView = self.view as! SCNView
}
复制代码

并在Main.storyboard中将view类型设置为SCNView.

继续添加属性:

var scnScene: SCNScene!
复制代码

setupView()下方接着写:

func setupScene() {scnScene = SCNScene()scnView.scene = scnScene
}
复制代码

viewDidLoad()中调用这些方法:

 setupView()setupScene()
复制代码

Resources中找到游戏图标,拖放到Assets.xcassets

此时运行游戏,看到的是黑屏.

02-Nodes节点

resources文件夹中拖放GeometryFighter.scnassets到我们的项目中,选中Copy items if needed, Create Groups还有我的项目GeometryFighter,点击Finish.

在项目中选中素材文件,可以查看详情

下面添加启动屏幕.

先点击Assets.xcassets,拖放GeometryFighter.scnassets/Textures/Logo_Diffuse.pngAppIcon下面.

再点击LaunchScreen.storyboard,选中view,设置背景为深蓝色:

从右下的媒体库中,拖放Logo_Diffuse到view中,设置Content ModeAspect Fit:

添加约束:

运行一下:

添加游戏中的背景图片

GameViewController.swiftsetupScene()方法的底部添加:

 scnScene.background.contents = "GeometryFighter.scnassets/Textures/
Background_Diffuse.png"
复制代码

运行一下

添加摄像机

打开GameViewController.swift,在scnScene下方添加新属性:

var cameraNode: SCNNode!
复制代码

并在setupScene()方法下方添加:

func setupCamera() {// 1cameraNode = SCNNode()// 2cameraNode.camera = SCNCamera()// 3cameraNode.position = SCNVector3(x: 0, y: 0, z: 10)// 4scnScene.rootNode.addChildNode(cameraNode)
}
复制代码

其中:

  1. 创建一个空节点并赋值到cameraNode.
  2. 创建一个新的SCNCamera对象,并赋值给cameraNodecamera属性.
  3. 设置摄像机位置(x:0, y:0, z:10).
  4. 添加cameraNode到场景中,作为场景根节点的一个子节点.

完成后,在viewDidLoad()方法中,setupScene()方法后面调用:

setupCamera()
复制代码
添加几何体

添加一个新文件,命名为setupCamera()

打开并更改内容如下:

import Foundation
// 1
enum ShapeType:Int {case box = 0case spherecase pyramidcase toruscase capsulecase cylindercase conecase tube
// 2static func random() -> ShapeType {let maxValue = tube.rawValuelet rand = arc4random_uniform(UInt32(maxValue+1))return ShapeType(rawValue: Int(rand))!
} }
复制代码

代码含义:

  1. 创建一个新的枚举名为ShapeType,用来表示各种不同形状.
  2. 定义一个static方法名为random(),用来产生随机的ShapeType.

GameViewController.swift中,setupCamera()方法下面,添加:

func spawnShape() {// 1var geometry:SCNGeometry// 2switch ShapeType.random() {default:
// 3geometry = SCNBox(width: 1.0, height: 1.0, length: 1.0,chamferRadius: 0.0)
}
// 4let geometryNode = SCNNode(geometry: geometry)// 5scnScene.rootNode.addChildNode(geometryNode)
}
复制代码

代码含义:

  1. 创建一个占位几何体,稍后会用到.
  2. 定义一个switch语句来处理ShapeType.random()中返回的形状.暂时我们只添加一个立方体形状,其他的稍后添加.
  3. 创建一个SCNBox对象并储存在geometry中.
  4. 创建一个SCNNode实例,命名为geometryNode.构造器使用geometry参数来自动创建一个节点并将几何体附加在上面.
  5. 将节点添加到场景的根节点上.

还需要在viewDidLoad()中调用一下,放在setupCamera()后面:

spawnShape()
复制代码

运行一下,看到一个白方块:

因为立方体节点是从spwnSpape()创建的,会位于场景的(x:0, y:0, z:0).我们又是从cameraNode节点来观察场景的,摄像机节点位置是在(x:0, y:0: z:10),所以正好立方体正好出现在屏幕中间.

为了更方便观察,我们可以打开视图的内置属性,给GameViewController.swift中的setupView()方法再添加几行:

// 1
scnView.showsStatistics = true
// 2
scnView.allowsCameraControl = true
// 3
scnView.autoenablesDefaultLighting = true
复制代码

代码含义:

  1. showStatistics会在屏幕底部启动一个实时的统计面板.
  2. allowsCameraControl能让你用手势(单指轻扫,双指轻扫,双指捏合,双击)控制摄像机的位置.
  3. autoenablesDefaultLighting则创建一个泛光灯来照亮你的场景.

运行一下,看起来好多了!

03-Physics物理效果

导入游戏工具类

拖放GameUtils文件夹到我们的项目中,点击Finish:

物理效果

打开GameViewController.swift,在spawnShape()中的创建geometryNode代码之后添加一行:

 geometryNode.physicsBody =SCNPhysicsBody(type: .dynamic, shape: nil)
复制代码

shape传nil,会自动根据显示的形状创建一个物理形体.

运行一下,会看到随机产生的几何体,自动掉落下去了,这是因为SceneKit的场景会自动打开重力:

添加力

spawnShape()中的创建geometryNode代码之后添加一行:

// 1
let randomX = Float.random(min: -2, max: 2)
let randomY = Float.random(min: 10, max: 18)
// 2
let force = SCNVector3(x: randomX, y: randomY , z: 0)
// 3
let position = SCNVector3(x: 0.05, y: 0.05, z: 0.05)
// 4
geometryNode.physicsBody?.applyForce(force,at: position, asImpulse: true)
复制代码

代码含义:

  1. 创建两个随机的浮点数代表力的x分量和y分量.用到的正是我们添加进项目中的工具类.
  2. 用这些随机数来创建一个向量代表这个力.
  3. 创建另一个向量来表示力施加的位置.这个位置是故意稍微偏离中心一些的,这样就能让物体旋转起来.
  4. 通过调用applyForce(direction: at: asImpulse:)方法将力应用到geometryNode的物理形体上.

运行一下,物体凭空出现后,受到力的作用被抛向空中,飞翔之后,最终受到重力影响下落.

添加更多效果

现在物体是在屏幕中间凭空出现,效果很不好,我们只需要修改摄像机的位置就可以改善.在setupCamera()中更改位置:

cameraNode.position = SCNVector3(x: 0, y: 5, z: 10)
复制代码

下面,还可以给几何体添加一些随机颜色.在spawnShape()方法中添加一行,在创建geometry之后中, 创建geometryNode之前:

geometry.materials.first?.diffuse.contents = UIColor.random()
复制代码

运行一下,物体就有了漂亮的颜色:

04-Render Loop渲染循环

创建

GameViewController.swift中,添加SCNSceneRendererDelegate协议,并实现协议方法:

// 1
extension GameViewController: SCNSceneRendererDelegate {// 2func renderer(_ renderer: SCNSceneRenderer,updateAtTime time: TimeInterval) {// 3spawnShape()
} }
复制代码

在此之前,还要先成为视图的代理.在setupView()方法的末尾添加一行:

scnView.delegate = self
复制代码

此时,已经可以删除viewDidLoad()中对spawnShape()的调用了.运行一下:

可以发现,创建的太多了,场面几乎失控了.我们需要控制一下创建几何体的时间间隔.

cameraNode下方添加一个新属性:

var spawnTime: TimeInterval = 0
复制代码

然后替换renderer(_:updateAtTime:)方法中的内容:

// 1
if time > spawnTime {spawnShape()
// 2spawnTime = time + TimeInterval(Float.random(min: 0.2, max: 1.5))
}
复制代码

代码含义:

  1. 检查time(当前系统的时间),如果大于spawnTime就产生一个新的形状,否则,什么也不做.
  2. 创建一个物体后,更新spawnTime来决定下一次创建的时机.下一次创建时间应该是在当前时间上增加一个随机量.

运行一下.

移除子节点

spawnShape()方法一直不停地创建新的节点并添加到场景中,但是却没有移除,仅仅是掉落出视线而已.虽然SceneKit有些优化能让场景继续运行下去不卡顿,但我们仍然需要将不要的节点移除掉.

spawnShape()下方,添加几行:

func cleanScene() {// 1for node in scnScene.rootNode.childNodes {// 2if node.presentation.position.y < -2 {// 3node.removeFromParentNode()}
} }
复制代码

代码含义:

  1. 循环遍历场景的根节点.
  2. 这里需要注意,因为物理效果模拟此时正在进行中,所以我们不能简单取物体的position来表示它的真实位置,此时的position反应的是动画开始前的位置.SceneKit在动画期间保存了对象的副本,并用副本来执行动画.要想得到动画进行过程中的实际位置,需要使用presentationNode属性.
  3. 让一个物体消失.

renderer(_: updatedAtTime:)方法中调用cleanScene()方法:

cleanScene()
复制代码

还有一个问题需要处理.默认情况下,SceneKit在没有动画时会进入"暂停"状态.我们可以启用SCNView实例的playing属性来阻止它.

setupView()的最后,添加下面的代码:

scnView.isPlaying = true
复制代码

运行一下,旋转看看物体下落到哪里消失的.

05-Particle Systems粒子系统

运动尾迹

创建一个新分组

命名为Particles,右击分组选择New File,选择iOS/Resource/SceneKit Particle System模板,点击Next继续:

接下来,在Particle system template中选择Fire类型,点击Next.保存为Tail.scnp并点击Create.然后你会看到这样的场景:

在右侧配置粒子系统的属性如下:

配置完成后的最终效果如下,如果你看到的不一样,试着旋转一下摄像机:

GameViewController.swift类中添加下面的代码:

// 1
func createTrail(color: UIColor, geometry: SCNGeometry) ->SCNParticleSystem {// 2let trail = SCNParticleSystem(named: "Trail.scnp", inDirectory: nil)!// 3trail.particleColor = color
// 4trail.emitterShape = geometry
// 5return trail
}
复制代码

代码含义:

  1. 定义一个方法createTrail(_: geometry:)接收colorgeometry参数来创建粒子系统.
  2. 从先前创建的文件里加载粒子系统.
  3. 根据传入的颜色修改粒子的颜色.
  4. 用传入的几何体参数来指定发射器的形状.
  5. 返回新创建的粒子系统.

进入spawnShape()中,找到设置材质颜色的代码,用常量保存起来:

 let color = UIColor.random()
geometry.materials.first?.diffuse.contents = color
复制代码

下一步,在spawnShape()中,在添加力到geometryNode的物理形体上之后,添加下面的代码:

 let trailEmitter = createTrail(color: color, geometry: geometry)
geometryNode.addParticleSystem(trailEmitter)
复制代码

运行一下:

抬头显示面板

GameViewController.swift中添加一个新属性,放在spawnTime后面:

var game = GameHelper.sharedInstance
复制代码

GameViewController最底部,createTail()方法后面,添加下面的方法:

func setupHUD() {game.hudNode.position = SCNVector3(x: 0.0, y: 10.0, z: 0.0)scnScene.rootNode.addChildNode(game.hudNode)
}
复制代码

其中我们是从帮助文件库中调用的game.hudNode.

下一步,我们需要调用setupHUD().在viewDidLoad()方法的底部添加一行:

setupHUD()
复制代码

我们还需要不断更新显示的内容.在renderer(_: updateAtTime:)方法底部,调用game.updateHUD():

game.updateHUD()
复制代码

运行一下,屏幕上方就出现了抬头显示面板:

触摸处理

在我们处理触摸事件之前,我们需要标识出每个物体.最简单的方法就是给他们起个名字.

spawnShape()中添加下面的代码,放在添加粒子系统之后:

if color == UIColor.black {geometryNode.name = "BAD"
} else {geometryNode.name = "GOOD"
}
复制代码

下一步,在GameViewController中, setupHUD()之后,添加下列方法:

func handleTouchFor(node: SCNNode) {if node.name == "GOOD" {game.score += 1node.removeFromParentNode()} else if node.name == "BAD" {game.lives -= 1node.removeFromParentNode()}
}
复制代码

下一步,在GameViewController中, handleTouchFor(_:)之后,添加下列方法:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
{
// 1let touch = touches.first!// 2let location = touch.location(in: scnView)// 3let hitResults = scnView.hitTest(location, options: nil)// 4if let result = hitResults.first {
// 5handleTouchFor(node: result.node)}
}
复制代码

代码含义:

  1. 拿到可用的touch.此处如果玩家用了多根手指就会有多个touch.
  2. 从屏幕坐标转换到scnView的坐标.
  3. hitTest(_: options:)返回一个SCNHitTestResult对象数组,代表着从用户触摸点发出的射线碰到的所有物体.
  4. 检查第一个结果是否可用.
  5. 将第一个碰到的节点传递给触摸处理方法,它可以计算增加分数或减少生命值.

最后一步,需要禁用摄像机控制:

scnView.allowsCameraControl = false
复制代码

运行一下,用手指触摸就会毁灭!

爆炸粒子效果

再创建一个粒子效果,命名为Explode.scnp.尝试着自己配置一下,让它看起来像这样:

可以用下面的图片作为参考:

可以在projects/challenge/ GeometryFighter文件夹中找到已经完成的Explode.scnp文件.

接着还需要将这个效果用起来.在GameViewController中, touchesBegan(_: withEvent)方法后面,添加下面的代码:

// 1
func createExplosion(geometry: SCNGeometry, position: SCNVector3,rotation: SCNVector4) {// 2let explosion =SCNParticleSystem(named: "Explode.scnp", inDirectory:nil)!explosion.emitterShape = geometryexplosion.birthLocation = .surface// 3let rotationMatrix =SCNMatrix4MakeRotation(rotation.w, rotation.x,rotation.y, rotation.z)let translationMatrix =SCNMatrix4MakeTranslation(position.x, position.y,position.z)let transformMatrix =SCNMatrix4Mult(rotationMatrix, translationMatrix)// 4scnScene.addParticleSystem(explosion, transform: transformMatrix)
}
复制代码

代码含义:

  1. createExplosion(_: position: rotation:)接收三个参数:geometry定义了粒子效果的形状,positionrotation帮助放置爆炸效果到场景中.
  2. 加载Explode.scnp,将其用作发射器.发射器使用geometry作为emitterShape,这样粒子就可以从形状的表面发射出来.
  3. 创建旋转矩阵和平移矩阵,相乘得到复合变换矩阵.
  4. 调用addParticleSystem(_: wtihTransform)将爆炸效果添加到场景中.

handleTouchFor(_:)中添加两次下面的代码-"good"分支一次,"bad"分支一次.添加在移除节点之前:

createExplosion(geometry: node.geometry!,  position: node.presentation.position,rotation: node.presentation.rotation)
复制代码

这里,我们又使用了presentation,因为物理效果模拟正在移动节点.

运行一下,点击爆炸!

这个效果可以在projects/ challenge/GeometryFighter文件夹中找到.

彩蛋

为了让游戏更好玩,还可以添加很多彩蛋效果,比如:

  • 游戏状态管理:比如点击开始游戏,暂停/开始,游戏结束等.
  • 启动闪屏:根据游戏状态提供不同的效果.
  • 声音效果:根据玩家的操作,提供声音反馈
  • 摄像机抖动:剧烈爆炸会产生剧烈冲击波,添加摄像机抖动来模拟冲击波效果.

这些效果都可以在projects/juiced/GeometryFighter文件夹中找到最终完成品.打开尝试一下吧.

[SceneKit专题]20-仿水果忍者小游戏Geometry-Fighter相关推荐

  1. 前端制作水果忍者小游戏

    演示地址​​​(第一次打开有点卡哦)水果忍者小游戏http://kiss-rebounds.gitee.io/fruitninja-game/ 水果忍者小游戏 - 经典版点击这里 实现了得分和连击等信 ...

  2. 用Python写了一个水果忍者小游戏

    点击上方"菜学Python",选择"星标"公众号 超级无敌干货,第一时间送达!!! 水果忍者的玩法很简单,尽可能的切开抛出的水果就行. 今天小五就用python ...

  3. 用【Python】写了一个水果忍者小游戏,玩过之后爱不释手

    前言 水果忍者到家都玩过吧,但是Python写的水果忍者你肯定没有玩过.今天就给你表演一个新的,用Python写一个水果忍者. 水果忍者的玩法很简单,尽可能的切开抛出的水果就行. 今天就用python ...

  4. 【练手项目】用Python写了一个水果忍者小游戏

    水果忍者的玩法很简单,尽可能的切开抛出的水果就行. 今天就用python简单的模拟一下这个游戏.在这个简单的项目中,我们用鼠标选择水果来切割,同时炸弹也会隐藏在水果中,如果切开了三次炸弹,玩家就会失败 ...

  5. H5版仿制微信跳一跳小游戏,网页版仿微信跳一跳小游戏源码,实现了跳一跳的基本核心功能

    H5版仿制微信跳一跳小游戏,网页版仿微信跳一跳小游戏源码,实现了跳一跳的基本核心功能 完整代码下载地址:H5版仿制微信跳一跳小游戏,网页版仿微信跳一跳小游戏源码 运行截图 Project setup ...

  6. C语言题目水果忍者是一款,《水果忍者网页游戏论文.doc

    <水果忍者网页游戏论文 本科毕业论文(设计) 题 目: 基于HTML5的水果忍者 网页游戏的开发 学 院: 软件技术学院 专 业: 数字媒体艺术 姓 名: 赵婷婷 指导教师: 李晓娜 2014 ...

  7. Swift基于ARKit的仿抖音潜水艇小游戏

    抖音的潜水艇小游戏只能玩一会儿,不尽兴,于是想着自己开发一个. ARKit的各种入门介绍这里就不说了,网上一堆都是,自己注意甄别. 第一步,创建一个具有增强现实功能AR的项目: 选择语言Swift, ...

  8. h5忍者小游戏源码下载

    下载地址一款忍者html5小游戏源码,可以当做手机端h5小游戏.在游戏中点击来改变忍者行动的路线,在游戏途中尽可能多的获得金币,要注意的是地图会随机生成,现在就让我们一起来试试吧! dd:

  9. 微信火柴人html5小游戏,20个好玩的微信小游戏推荐!你玩过几个?

    50000+游戏爱好者已加入我们! 每天推荐好玩游戏! 加入我们,沐沐带你发现好游戏! 只有你想不到, 没有我找不到的好游戏! 「良心好游戏推荐」 搜罗了好玩的微信小游戏大全, 模拟经营游戏.恐怖游戏 ...

最新文章

  1. TextBoxSuggest,输入框提示工具,输入建议,输入匹配,辅助输入,输入即时提示,文本编辑器,Visual Studio效果,高速查询引擎,哈希树,模糊匹配,百万条零毫秒
  2. 显卡mx150和230哪个好_MX250显卡等于GTX1050?笔记本显卡MX250和MX150的区别对比
  3. c语言结构体与共同体课件,《结构体与共同体》PPT课件.ppt
  4. smb 限制大文件上传_单个文件大小 上传百度云盘 微信发送 有大小限制 怎么破?...
  5. 俄罗斯方块---九宫格版
  6. 电脑数据恢复,哪种方法靠谱?
  7. 使用Python实现简单的随机数字抽奖
  8. epson r1900 清零软件_爱普生打印机清零软件
  9. pads2007 LISENCE 报错解决方案
  10. 收货地址列表html,收货地址.html
  11. ZXing实现扫描或选取图片识别二维码及条码功能
  12. 用笔记本做wifi热点
  13. 【教程】用微信创建生日提醒
  14. 汇编指令及其英文全称
  15. 如何使用canvas进行画图
  16. 迈阿密色主题学科导航 HTML5静态开源
  17. Python 电影评分分析
  18. 【数据预处理】CoCo数据集标注文件.json转yolov5标注文件.txt格式
  19. 让我思潮翻滚的IBM面试内容
  20. Arduino Uno资料简介

热门文章

  1. 华为Q22免拆机卡刷第三方固件(附教程)
  2. 中职计算机应用教资面试教案
  3. 基于SSM-CRM设计模式的员工信息-CRUD
  4. oracle数据库提交数据关键字,Oracle数据库的关键字
  5. jsp mysql花店_基于jsp的花店网站-JavaEE实现花店网站 - java项目源码
  6. 【Erlang/OTP入门】基于进程的并发编程和分布式
  7. 删除电脑缓存的密码(VSCode编辑器提交代码提示: Failed to authenticate to git remote)
  8. AIGC 后下一个巨大的风口:AI生成检测
  9. Android培训班(45)
  10. 在线pdf转word文档——speedpdf免费的PDF转Word转换器