[2d游戏引擎]iOS 2D游戏引擎框架SpriteKit入门
https://att.xiawai.com/data/attachment/forum/202012/18/ipud0hxopu187992.jpg最近用闲暇时间了解了下iOS 7.0 SDK就提供的一个2D游戏引擎框架SpriteKit,用此实现了一个之前比较流行的游戏“保卫萝卜”中的一个小场景,毕竟有具体需求的实践能提高学习效率,在此做个记录总结。
SpriteKit主要用来做2D游戏,将图形渲染和动画用于任意一个精灵,游戏中的任意一个图像元素都可以理解为一个精灵,我们要做的就是通过SpriteKit来管理这些精灵,让它们像我们预想的方向展现或变化。
同样SDK还提供了3D渲染引擎SceneKit,和GPU优化的底层接口来渲染3D图形的API Metal,这些之后再来学习。
先预览一下实现后的小游戏。
保卫萝卜
游戏的工程搭建我们可以直接创建一个XCode提供的Game Project模板,也可以直接在已有的工程中使用。
在创建工程处选择Game模板,路径File->New->Project->Game,样式见下图。
https://upload-images.jianshu.io/upload_images/3816804-4d349d9d99e301ab.png
Game模板
Language选择你喜欢用的Objective-C或Swift,Game Technology这里选择SpriteKit。创建完成后目录结构如下。
https://upload-images.jianshu.io/upload_images/3816804-9bd0463e59fbd829.png
模板目录结构
工程会默认生成一个“Hello,World”的Demo,运行起来可以直接看到效果,按下、抬起、移动手指的交互动作都做了动画,可以感受下。
根视图GameViewController中,调用了GameScene,Hello Wold所有的精灵展现及动画都是在GameScene中处理的。所以想要开始开发游戏之前2d游戏引擎,需要先将这个Hello World Demo相关内容清理掉。视图文件GameScene.sks、动画文件Actions.sks、源码文件GameScene.h、GameScene.m都删除2d游戏引擎,并将GameViewController中的viewDidLoad函数中创建并使用GameScene的代码也相应删除掉。这样就可以在这个GameViewController中创建并使用我们要做的Scene了。
2、已有的工程中开发游戏
在想要实现游戏的界面,将Storyboard或Xib中ViewController的根View,Class类由UIView改成SKView,因为游戏框架的元素都要使用SpriteKit提供的类来实现,方便管理精灵。这样就跟Game模型中的GameViewController一样了。
创建MyScene文件,继承SKScene,后续游戏的内容都将在这个页面开发。
# MyScene.h
import
@interface MyScene : SKScene
@end
# MyScene.m
#import "GameScene.h"
@implementation GameScene
@end
在游戏页面的ViewController中,创建一个SKView,用于展现才刚创建的MyScene。
IBOutlet SKView *_sceneView;
在Xib或Storyboard中添加UIView,将类改为SKView,关联即可。
在ViewController中的viewDidLoad中调用MyScene。
// Create and configure the scene.
CGSize size = CGSizeMake(_sceneView.bounds.size.height, _sceneView.bounds.size.width);
MyScene * scene = ;
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
;
MyScene已经调用完成,后续精灵的展现、动画都在MyScene文件里添加。
//添加背景
- (void)addBackground {
SKSpriteNode *background = ;
background.name = @"bg";
background.size = CGSizeMake(self.size.width, self.size.height);
background.position = CGPointMake(background.size.width/2, background.size.height/2);
;
}
基本图片类的精灵都是通过SKSpriteNode对象来创建,通过spriteNodeWithImageNamed:函数可以将图片做成精灵对象,通过name属性为其命名,用户点击等操作可以通过名字判断出是哪个精灵,后面会介绍到。通过size和postion属性可以指定精灵的宽高和位置。
同样的方法将游戏中用到的一些静态精灵都摆放上去。
- (id)initWithSize:(CGSize)size{
if (self = ) {
self.backgroundColor = ;
;// 背景
; // 起点
; // 终点
;// 其他的羊头、石头、宝箱等装饰物
}
return self;
}
SKColor在iOS上就是UIColor,可以看下定义,是为了在MacOS上和iOS上统一使用,也方便移植。
#if TARGET_OS_IPHONE
#define SKColor UIColor
以上内容就是iOS 2D游戏引擎框架SpriteKit入门的相关内容介绍,喜欢侠外游戏论坛的朋友可以关注我们。
123456下一页 #else
#define SKColor NSColor
#endif
添加完这些精灵的样式如图。
https://att.xiawai.com/data/attachment/forum/202012/18/efe2h1q2ix187993.jpg
https://upload-images.jianshu.io/upload_images/3816804-0dc59bb3f14d68a9.png
九个精灵
这里要涉及到SKAction,简单介绍一下,因为游戏的大部分动作行为都是它来实现的。
SKAction可以实现的动作有很多,列举几个常用的
1、相对位移或绝对位移
2、旋转到指定角度或旋转指定角度
3、改变尺寸或缩放
4、隐藏、显示、渐隐、渐现、指定透明度
5、添加一个或一系列纹理图片
6、加减速、等待
7、播放简单的声音等等
以上是单个动作的SKAction,同样可以通过下面两个函数将多个单一动作整合成复合动作SKAction
+ (SKAction *)sequence:(NSArray *)actions;
+ (SKAction *)group:(NSArray *)actions;
入参是N多个Action,sequence:函数是串行的来执行数组中的所有Action,group:函数是并行的来执行。通过以上这些我们就可以做各种复杂的动画了。
1、首先通过SKAction来为仙人掌加一个张嘴呼吸的动画。
NSArray *array = @[,
,
,
,
];
SKAction *animation = ;
//cactus为仙人掌的SKSpriteNode对象
];
SKAction的animateWithTextures:timePerFrame:函数可以将一组图片按指定的时间间隔像GIF图片一样展示,类似于UIImageView中的animationImages。SKAction的repeatActionForever:函数将GIF的动作无限循环,做成复合动画。精灵通过运行runAction:函数调用它就可以实现仙人掌呼吸动画了。
2、再来给终点的萝卜增加点击交互,点击后抖动和叫声。
因为SKScene继承自UIResponder,所以我们可以使用touchesBegan:withEvent:函数来实现点击
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
for (UITouch *touch in touches) {
CGPoint touchLocation = ;
SKNode *node = ;
if ( && !){
;
}
}
}
上面代码用来判断点击位置是否为萝卜精灵,如果是并且没有在做动作,就执行下面的Action。
- (void)carrotAnimation{
// 播放一组图片
NSArray *array = @[,
,
,
,
,
,
,
,
];
SKAction *animation = ;
// 随机播放一个声音
int random = arc4random();
NSString *str = @"carrot1.mp3";
if (random % 3 == 1) {str = @"carrot2.mp3"; }
elseif (random % 3 == 2) {str = @"carrot3.mp3"; }
SKAction *soundAction = ;
SKAction *groupAction = ];
;
}
为萝卜做了一个GIF和一个随机声音动作,通过group合成一个Action,让萝卜执行。
3、小怪物的出现和行走路径
在起始位置创建一个小怪物,并伴随着一个叫声。
以上内容就是iOS 2D游戏引擎框架SpriteKit入门的相关内容介绍,喜欢侠外游戏论坛的朋友可以关注我们。
上一页123456下一页 // 小怪物
SKSpriteNode *character = ;
https://att.xiawai.com/data/attachment/forum/202012/18/w3um3ewuafs87994.jpg
character.position = CGPointMake(60 + character.size.width/2, self.size.height - character.size.height/2);
;
// 叫声
SKAction *soundAction = ;
;
同时出现一个漩涡,漩涡旋转两圈并消失。
// 出现漩涡
SKSpriteNode *wheel = ;
wheel.position = CGPointMake(character.position.x, character.position.y - 10);
;
// 旋转两圈后消失
SKAction *rotateAction = ;
[wheel runAction:rotateAction completion:^{
;
}];
小怪物蹦蹦跳跳的动画,与仙人掌呼吸动画一样。
NSArray *array = @[,
,
,
];
SKAction *animation = ;
];
按照固定路线行走。(正常需要根据路线、屏幕尺寸等计算出,这里给的定值)
CGMutablePathRef pathRef = CGPathCreateMutable();
CGPathMoveToPoint(pathRef, nil, character.position.x, character.position.y);
CGPathAddLineToPoint(pathRef, nil, 83, 165);
CGPathAddLineToPoint(pathRef, nil, 245, 165);
CGPathAddLineToPoint(pathRef, nil, 245, 225);
CGPathAddLineToPoint(pathRef, nil, 415, 225);
CGPathAddLineToPoint(pathRef, nil, 415, 160);
CGPathAddLineToPoint(pathRef, nil, 580, 160);
CGPathAddLineToPoint(pathRef, nil, 580, self.size.height - character.size.height/2);
SKAction *moveToEndAction = ;
[character runAction:moveToEndAction completion:^{
// 走完全程后,移除小怪物
;
;
}];
// 将小怪物暂存起来,便于后续跟炮台的飞镖做碰撞时使用。
;
CGPathRelease(pathRef);
这里通过followPath:asOffset:orientToPath:duration:函数来让精灵按CGPathRef路线移动10秒。self.monsters这个成员变量是用来存储每一个生成出来的小怪物,如果小怪物走完全程也要相应的移除掉。
我们来连续的创建这样的小怪物7个,时间间隔1秒。
typeof(self) weakSelf = self;
SKAction *actionWaitNextMonster = ;
SKAction *actionAddMonster = [SKAction runBlock:^{
// 添加小怪物的所有动作行为,出场、叫声、行动路线等
;
}];
SKAction *sequenceAction = ];
completion:^{
// isFinish用来标记怪物全部出场
weakSelf.isFinsh = YES;
以上内容就是iOS 2D游戏引擎框架SpriteKit入门的相关内容介绍,喜欢侠外游戏论坛的朋友可以关注我们。
上一页123456下一页 }];
4、炮台和飞镖的展现
添加点击交互,点击背景,会在相应的位置上放上炮台,逻辑与点击萝卜的思路一样。通过touchesBegan:withEvent:函数获取点击事件,判定是否点击在背景上,并且捕获到点击的位置,在该位置上添加炮台。
#pragma mark - UIResponder
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
for (UITouch *touch in touches) {
CGPoint touchLocation = ;
SKNode *node = ;
if ( && !) {
; // 点击萝卜
https://att.xiawai.com/data/attachment/forum/202012/18/g05i0wbocjk87995.png
}
else if ( && !) {
; // 点击背景
}
}
}
创建炮台、飞镖和飞镖移动的逻辑,因为炮台由底座和飞镖两层图片组成,所以这里使用addChild:函数来叠加使用,在炮台出现时再添加个声音 。
- (void)addArrow:(CGPoint)point{
// 两张图片组装成炮台精灵
SKSpriteNode *character1 = ;
character1.position = point;
SKSpriteNode *character2 = ;
;
;
// 放置炮台时添加个声音
SKAction *soundAction = ;
;
// 不断创建飞镖,撇向小怪物的动作
SKAction *actionAdd = [SKAction runBlock:^{
SKSpriteNode *character3 = ;
character3.position = point;
;
if (self.monsters.count > 0) {
SKSpriteNode *monster = ;
CGPoint point = monster.position;
SKAction *rotateAction = ;
SKAction *moveAction = ;
[character3 runAction:moveAction completion:^{
;
;
}];
];
}
;
}];
if (self.monsters.count > 0) {
SKAction *actionWaitNext = ;
SKAction *soundAction = ;
SKAction *groupAction = ];
]]];
}
}
self.projectiles这个成员变量是用来存储场中的飞镖,后续阶段用它来判定是否有打到小怪物。和之前用来存储小怪物的变量self.monsters类似。
这里涉及到SKScene的每一帧的处理循环,下图是苹果SDK SKScene类中提供的。
https://upload-images.jianshu.io/upload_images/3816804-331a70a0b92f0721.png
Frame processing in a scene
//官方解读
1、The scene’supdate:method is called with the time elapsed so far in the simulation. This is the primary place to implement your own in-game simulation, including input handling, artificial intelligence, game scripting, and other similar game logic. Often, you use this method to make changes to nodes or to run actions on nodes.
以上内容就是iOS 2D游戏引擎框架SpriteKit入门的相关内容介绍,喜欢侠外游戏论坛的朋友可以关注我们。
上一页23456下一页 2、The scene processes actions on all the nodes in the tree. It finds any running actions and applies those changes to the tree. In practice, because of custom actions, you can also hook into the action mechanism to call your own code. You cannot directly control the order in which actions are processed or cause the scene to skip actions on certain nodes, except by removing the actions from those nodes or removing the nodes from the tree.
3、The scene’sdidEvaluateActionsmethod is called after all actions for the frame have been processed.
4、The scene simulates physics on nodes in the tree that have physics bodies. Adding physics to nodes in a scene is described inSKPhysicsBody, but the end result of simulating physics is that the position and rotation of nodes in the tree may be adjusted by the physics simulation. Your game can also receive callbacks when physics bodies come into contact with each other, seeSKPhysicsContactDelegate.
5、The scene’sdidSimulatePhysicsmethod is called after all physics for the frame has been simulated.
6、The scene applies any constraints associated with nodes in the scene. Constraints are used to establish relationships in the scene. For example, you can apply a constraint that makes sure a node is always pointed at another node, regardless of how it is moved. By using constraints, you avoid needing to write a lot of custom code in your scene handling.
7、The scene calls itsdidApplyConstraintsmethod.
8、The scene calls itsdidFinishUpdatemethod. This is your last chance to make changes to the scene.
9、The scene is rendered.
这里用到了第一步update:函数,通过CGRectIntersectsRect函数判断两个精灵是否相交,来加入相应的Action。以下是实现update:函数,并写在函数体中的。(这里也可以用碰撞引擎方案来替换)
1、判定飞镖打中小怪物
NSMutableArray *projectilesToDelete = [ init];
for (SKSpriteNode *projectile in self.projectiles) {
NSMutableArray *monstersToDelete = [ init];
// 判定是否有飞镖打中小怪物
for (SKSpriteNode *monster in self.monsters) {
https://att.xiawai.com/data/attachment/forum/202012/18/0himbkxxgsf87996.jpg
if (CGRectIntersectsRect(projectile.frame, monster.frame)) {
;
}
}
// 移除小怪物
for (SKSpriteNode *monster in monstersToDelete) {
;
;
int random = arc4random();
NSString *str = (random % 2 == 0) ? @"Fly162.mp3" : @"Fat242.mp3";
SKAction *soundAction = ;
;
// 该函数为添加小怪物消失时的动画,一个气泡破裂的GIF,就不贴代码了
;
以上内容就是iOS 2D游戏引擎框架SpriteKit入门的相关内容介绍,喜欢侠外游戏论坛的朋友可以关注我们。
上一页3456下一页 }
if (monstersToDelete.count > 0) {
// 飞镖打中小怪物,飞镖也应该消失,先暂存下
;
}
}
// 移除飞镖
for (SKSpriteNode *projectile in projectilesToDelete) {
;
;
}
2、判定小怪物吃到萝卜
NSMutableArray *monstersToDelete = [ init];
for (SKSpriteNode *monster in self.monsters){
if (CGRectIntersectsRect(monster.frame, self.carrot.frame)) {
;
;
SKAction *soundAction = ;
;
passMonsterCount ++; // 记录被咬了几口
NSArray *imageNameArray = @[@"cry1", @"cry2", @"cry3", @"cry4", @"cry5", @"cry6"];
NSString *imageNamed = imageNameArray;
if (imageNamed.length > 0) {
SKTexture *cryTexture = ;
SKAction *action = ;
;
}
}
}
// 移除咬萝卜的小怪物
for (SKSpriteNode *monster in monstersToDelete){
;
;
}
if (passMonsterCount >= 6) {
// 失败!游戏结束!
SKAction *soundAction = ;
, soundAction]] completion:^{
;
}];
}
else if (self.isFinsh && == 0 && passMonsterCount
// 胜利!游戏结束!
SKAction *soundAction = ;
[self runAction:soundAction completion:^{
;
}];
}
至此,一个简单场景下的小游戏就结束了,这里只是用于技术实现,如果作为游戏来讲,需要处理的很要很多,如点击背景放置炮台的避让策略,行走路径根据屏幕尺寸地图样式等如何计算获取,物理碰撞逻辑等等。
以上内容就是iOS 2D游戏引擎框架SpriteKit入门的相关内容介绍,喜欢侠外游戏论坛的朋友可以关注我们。
上一页456 一滴水只有放进大海里才永远不会干涸,一个人只有当他把自己和集体事业融合在一起的时候才能最有力量。
页:
[1]