SpriteKit 框架入门 demo
第一步:创建SKNode类目
SKNode+Extra.h文件
-(void)receiveAttacker:(SKNode *)arracker contact:(SKPhysicsContact *)contact;
-(void)friendlyBumpFrom:(SKNode *)node;
SKNode+Extra.m文件
-(void)receiveAttacker:(SKNode *)arracker contact:(SKPhysicsContact *)contact{//敌人被导弹击中调用
}
-(void)friendlyBumpFrom:(SKNode *)node{
}
第二步:使用工程创建c头文件HpysicsCategories.h文件
typedef NS_OPTIONS(uint32_t, HpysicsCategories) {
PlayerCategory = 1<<1,//拉掩码 使用OR运算符将多个值拼在一起
EnemyCategory = 1<<2,
PlayerMissileCategory = 1<<3
};
第三步:创建c头文件GrameGeometry.h文件
//返回一个新的CGvect 其中每个v元素都已经乘以m
static inline CGVector BIDVectorMultiplay(CGVector v, CGFloat m){
return CGVectorMake(v.dx*m, v.dy*m);
}
//返回一个表示p1到p2距离的CGVect
static inline CGVector BIDVectorBetweenPoints(CGPoint p1, CGPoint p2){
return CGVectorMake(p2.x-p1.x, p2.y-p1.y);
}
//通过勾股定理计算向量的长度返回 CGFloat
static inline CGFloat BIDVectorLength(CGVector v){
return sqrtf(powf(v.dx, 2.0)+powf(v.dy, 2));
}
//根据两个 坐标 ,通过勾股定理两者之间的距离
static inline CGFloat BIDPointDistance(CGPoint p1, CGPoint p2){
return sqrtf(powf(p2.x-p1.x, 2)-powf(p2.y-p1.y,2));
}
第4步:创建炸弹BulletNode.m
@interface BulletNode ()
@property (assign, nonatomic) CGVector thrust;//发射导弹向量
@end
@implementation BulletNode
-(instancetype)init{
if (self = [super init]) {
SKLabelNode *dot = [SKLabelNode labelNodeWithFontNamed:@"Courier"];
dot.fontColor = [SKColor blackColor];
dot.fontSize = 40;
dot.text = @".";
[self addChild:dot];
SKPhysicsBody *body = [SKPhysicsBody bodyWithCircleOfRadius:1];//创建物理特性
body.dynamic = YES;
body.categoryBitMask = PlayerMissileCategory;
body.contactTestBitMask = EnemyCategory;
body.collisionBitMask = EnemyCategory;
body.mass = 0.01;
self.physicsBody = body;
self.name = [NSString stringWithFormat:@"bullet %p",self];
}
return self;
}
+(instancetype)bulletFrom:(CGPoint)start toward:(CGPoint)destination{//物理引
BulletNode *bullet = [[self alloc]init];
bullet.position = start;
CGVector movement = BIDVectorBetweenPoints(start, destination);//向量
CGFloat magnitude = BIDVectorLength(movement);//长度
if (magnitude==0.0f) {
return nil;
}
CGVector scaledMovement = BIDVectorMultiplay(movement, 1/magnitude);
CGFloat thrustMagnitude = 100.0;
bullet.thrust = BIDVectorMultiplay(scaledMovement, thrustMagnitude);
[bullet runAction:[SKAction playSoundFileNamed:@"shoot.wav" waitForCompletion:NO]];
return bullet;
}
-(void)applyRecurringForce{//推动物理形体发射 每一帧调用
[self.physicsBody applyForce:self.thrust];
}
BulletNode.h
+(instancetype)bulletFrom:(CGPoint)start toward:(CGPoint)destination;
-(void)applyRecurringForce;
第5步:创建敌人PlayRivalNode.m
//玩家移动
-(instancetype)init{
if (self = [super init]) {
self.name = [NSString stringWithFormat:@"Rival %p",self];
[self initNodeGraph];
[self initPhysicsBody];
}
return self;
}
-(void)initNodeGraph{
SKLabelNode *lb = [SKLabelNode labelNodeWithFontNamed:@"Courier-Bold"];
lb.fontColor = [SKColor darkGrayColor];
lb.fontSize = 20;
lb.text = @"x x";
lb.position = CGPointMake(0, 15);
// lb.name = @"lable";
[self addChild:lb];
SKLabelNode *lb1 = [SKLabelNode labelNodeWithFontNamed:@"Courier-Bold"];
lb1.fontColor = [SKColor darkGrayColor];
lb1.fontSize = 20;
lb1.text = @"x";
// lb1.position = CGPointMake(0, 15);
// lb.name = @"lable";
[self addChild:lb1];
SKLabelNode *lb2 = [SKLabelNode labelNodeWithFontNamed:@"Courier-Bold"];
lb2.fontColor = [SKColor darkGrayColor];
lb2.fontSize = 20;
lb2.text = @"x x";
lb2.position = CGPointMake(0, -15);
// lb.name = @"lable";
[self addChild:lb2];
}
-(void)initPhysicsBody{
SKPhysicsBody *body = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(40, 40)];
body.affectedByGravity = YES;
body.categoryBitMask = EnemyCategory;
body.contactTestBitMask = EnemyCategory | PlayerCategory;
body.mass = 0.2;
body.angularDamping = 0.0f;
body.linearDamping = 0.0f;
self.physicsBody = body;
}
-(void)receiveAttacker:(SKNode *)arracker contact:(SKPhysicsContact *)contact{//敌人被导弹击中调用
self.physicsBody.affectedByGravity = YES;//开启重力影响
[self runAction:[SKAction playSoundFileNamed:@"enemyHit.wav"
waitForCompletion:NO]];
CGVector force = BIDVectorMultiplay(arracker.physicsBody.velocity,contact.collisionImpulse);
CGPoint myContact = [self.scene convertPoint:contact.contactPoint toNode:self];
[self.physicsBody applyForce:force atPoint:myContact];
NSString *parth = [[NSBundle mainBundle] pathForResource:@"MissileExplosion" ofType:@"sks"];
SKEmitterNode *explosion = [NSKeyedUnarchiver unarchiveObjectWithFile:parth];
explosion.numParticlesToEmit = 20;
explosion.position = contact.contactPoint;
[self.scene addChild:explosion];
}
-(void)friendlyBumpFrom:(SKNode *)node{
self.physicsBody.affectedByGravity = YES;//是敌人受重力影响
}
PlayRivalNode.h
@interface PlayRivalNode : SKNode
@end
第6:创建玩家PlaySKNode.m
//玩家移动
-(instancetype)init{
if (self = [super init]) {
self.name = [NSString stringWithFormat:@"Player %p",self];
[self initNodeGraph];
[self initPhysicsBody];
}
return self;
}
-(void)initNodeGraph{
SKLabelNode *lb = [SKLabelNode labelNodeWithFontNamed:@"Courier"];
lb.fontColor = [SKColor darkGrayColor];
lb.fontSize = 40;
lb.text = @"v";
lb.zRotation = M_PI;
lb.name = @"lable";
[self addChild:lb];
}
-(void)initPhysicsBody{
SKPhysicsBody *body = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(40, 40)];
body.affectedByGravity = NO;//是否开启重力
body.categoryBitMask = PlayerCategory;
body.contactTestBitMask = EnemyCategory;
body.collisionBitMask = 0;
self.physicsBody = body;
}
//返回将来移动所用的时间
-(CGFloat)moveToWard:(CGPoint)loaction{
[self removeActionForKey:@"movement"];//移除上一次移动
[self removeActionForKey:@"wobbling"];
CGFloat distance = BIDPointDistance(self.position,loaction);//
CGFloat pixels = [UIScreen mainScreen].bounds.size.width;//移动像素
CGFloat duration = 2.0 * distance / pixels;
[self runAction:[SKAction moveTo:loaction duration:duration] withKey:@"movement"];
CGFloat wobbleTime = 0.3;
CGFloat halfWobbleTime = wobbleTime * 0.5;
SKAction *wobbling = [SKAction sequence:@[[SKAction scaleTo:0.2 duration:halfWobbleTime],
[SKAction scaleTo:1.0 duration:halfWobbleTime]]];
NSUInteger wobbleCount = duration/wobbleTime;
[self runAction:[SKAction repeatAction:wobbling count:wobbleCount] withKey:@"wobbling"];//轻微摆动
return duration;
}
-(void)receiveAttacker:(SKNode *)arracker contact:(SKPhysicsContact *)contact{// 碰撞后的动作 敌人被导弹击中调用
NSString *parth = [[NSBundle mainBundle] pathForResource:@"EnemyExplosion" ofType:@"sks"];
[self runAction:[SKAction playSoundFileNamed:@"playerHit.wav"
waitForCompletion:NO]];
SKEmitterNode *explosion = [NSKeyedUnarchiver unarchiveObjectWithFile:parth];
explosion.numParticlesToEmit = 50;
explosion.position = contact.contactPoint;
[self.scene addChild:explosion];
}
-(void)friendlyBumpFrom:(SKNode *)node{
// self.physicsBody.affectedByGravity = YES;//是敌人受重力影响
}
PlaySKNode.h
@interface PlaySKNode : SKNode
-(CGFloat)moveToWard:(CGPoint)loaction;
@end
第7:创建开始场景StartScene.m
-(instancetype)initWithSize:(CGSize)size{
if (self = [super initWithSize:size]) {
self.backgroundColor = [SKColor greenColor];
SKLabelNode *lb = [SKLabelNode labelNodeWithFontNamed:@"Courier"];
lb.text = @"SpriteKit";
lb.fontColor = [UIColor blackColor];
lb.fontSize = 48;
lb.position = CGPointMake(self.frame.size.width*0.5, self.frame.size.height*0.7);
[self addChild:lb];
SKLabelNode *lb1 = [SKLabelNode labelNodeWithFontNamed:@"Courier"];
lb1.text = @"SpriteKit demo";
lb1.fontColor = [UIColor blackColor];
lb1.fontSize = 20;
lb1.position = CGPointMake(self.frame.size.width*0.5, self.frame.size.height*0.3);
[self addChild:lb1];
}
return self;
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
SKTransition *transition = [SKTransition doorwayWithDuration:1.0];
SKScene *game = [[GameScenes alloc]initWithSize:self.frame.size];
[self.view presentScene:game transition:transition];
[self runAction:[SKAction playSoundFileNamed:@"gameStart.wav" waitForCompletion:NO]];
}
StartScene.h
@interface StartScene : SKScene
@end
第8:创建结束场景GameOverScene.m
-(instancetype)initWithSize:(CGSize)size{
if (self = [super initWithSize:size]) {
self.backgroundColor = [SKColor purpleColor];
SKLabelNode *text = [SKLabelNode labelNodeWithFontNamed:@"Courier"];
text.text = @"Game Over";
text.fontColor = [SKColor whiteColor];
text.fontSize = 50;
text.position = CGPointMake(self.frame.size.width*0.5, self.frame.size.height*0.5);
[self addChild:text];
}
return self;
}
-(void)didMoveToView:(SKView *)view{
[self performSelector:@selector(goToStart) withObject:nil afterDelay:3.0];
}
-(void)goToStart{
SKTransition *transition = [SKTransition flipVerticalWithDuration:1.0];
SKScene *game = [[StartScene alloc]initWithSize:self.frame.size];
[self.view presentScene:game transition:transition];
}
第9 ;使用工程创建两种不同粒子
第一种:MissileExplosion.sks 如图
第二种:EnemyExplosion.sks 如图
第10:创建游戏场景GameScenes.m
#import "PlaySKNode.h"
#include "GrameGeometry.h"
#import "PlayRivalNode.h" //添加对手
#define ARC4RANDOM_MAX 0x100000000
#import "BulletNode.h"//添加导弹
#import "SKNode+Extra.h"
#import "GameOverScene.h" //结束场景
@interface GameScenes ()<SKPhysicsContactDelegate>//碰撞检查
@property (strong, nonatomic) PlaySKNode *playNode;
@property (strong, nonatomic) SKNode *enemies;
@property (strong, nonatomic) SKNode *playerbullets;
//@property (strong, nonatomic) SKPhysicsWorld *physicsWorld;
//@property (assign, nonatomic) BOOL finished;
@end
@implementation GameScenes
+(instancetype)sceneWithSize:(CGSize)size levenNumber:(NSUInteger)levelNumber{
return [[self alloc] initWithSize:size levenNumber:levelNumber];
}
-(instancetype)initWithSize:(CGSize)size{
return [self initWithSize:size levenNumber:1];
}
-(instancetype)initWithSize:(CGSize)size levenNumber:(NSUInteger)levelNumber{
if (self = [super initWithSize:size]) {
_levelNumber = levelNumber;
_playerLives = 5;
self.backgroundColor = [SKColor whiteColor];
SKLabelNode *skLbLives = [SKLabelNode labelNodeWithFontNamed:@"Courier"];
skLbLives.fontSize = 16.f;
skLbLives.fontColor = [SKColor blackColor];
skLbLives.name = @"LivesLbale";
skLbLives.text = [NSString stringWithFormat:@"Lives: %lu",(unsigned long)_playerLives];
skLbLives.verticalAlignmentMode = SKLabelVerticalAlignmentModeTop;
skLbLives.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeRight;
skLbLives.position = CGPointMake(self.frame.size.width, self.frame.size.height);
[self addChild:skLbLives];
SKLabelNode *skLbLives1 = [SKLabelNode labelNodeWithFontNamed:@"Courier"];
skLbLives1.fontSize = 16.f;
skLbLives1.fontColor = [SKColor blackColor];
skLbLives1.name = @"LivesLbale";
skLbLives1.text = [NSString stringWithFormat:@"Lives: %lu",(unsigned long)_levelNumber];
skLbLives1.verticalAlignmentMode = SKLabelVerticalAlignmentModeTop;
skLbLives1.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;
skLbLives1.position = CGPointMake(0, self.frame.size.height);
[self addChild:skLbLives1];
self.playNode = [PlaySKNode node];//向场景插入玩家
self.playNode.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetHeight(self.frame)*0.1);
[self addChild:self.playNode];
self.enemies = [SKNode node];
[self addChild:self.enemies];
[self spawnEnemies];
self.playerbullets = [SKNode node];
[self addChild:self.playerbullets];
// self.physicsWorld = [[SKPhysicsWorld alloc]init];
self.physicsWorld.gravity = CGVectorMake(0, -1);//掉落方向
self.physicsWorld.contactDelegate = self;
}
return self;
}
//添加敌人
-(void)spawnEnemies{
NSUInteger count = log(self.levelNumber) + self.levelNumber;
for (NSUInteger i=0; i< count; i++) {
PlayRivalNode *enemy = [PlayRivalNode node];
CGSize size = self.frame.size;
CGFloat x = (size.width *0.8*arc4random()/ARC4RANDOM_MAX)+(size.width*0.1);
CGFloat y = (size.height *0.5 *arc4random()/ARC4RANDOM_MAX)+(size.height*0.5);
enemy.position = CGPointMake(x, y);
[self.enemies addChild:enemy];
}
}
//玩家生命
-(void)setPlayerLives:(NSUInteger)playerLives{
_playerLives = playerLives;
SKLabelNode *lives = (id)[self childNodeWithName:@"LivesLbale"];
lives.text = [NSString stringWithFormat:@"Lives: %lu",(unsigned long)_playerLives];
}
-(void)didMoveToView:(SKView *)view {
/* Setup your scene here */
// SKLabelNode *myLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
//
// myLabel.text = @"Hello, World!";
// myLabel.fontSize = 45;
// myLabel.position = CGPointMake(CGRectGetMidX(self.frame),
// CGRectGetMidY(self.frame));
//
// [self addChild:myLabel];
}
//物理碰撞世界
-(void)didBeginContact:(SKPhysicsContact *)contact{
if (contact.bodyA.categoryBitMask == contact.bodyB.categoryBitMask) {
//两个物体是同一个物理类型
SKNode *nodeA = contact.bodyA.node;
SKNode *nodeB = contact.bodyB.node;
[nodeA friendlyBumpFrom:nodeB];
[nodeB friendlyBumpFrom:nodeA];
}else{
SKNode *attacker = nil;
SKNode *attackee = nil;
if (contact.bodyA.categoryBitMask > contact.bodyB.categoryBitMask) {
//a 攻击 b
attacker = contact.bodyA.node;
attackee = contact.bodyB.node;
}else{
// b 攻击 a
attacker = contact.bodyB.node;
attackee = contact.bodyA.node;
}
if ([attackee isKindOfClass:[PlaySKNode class]]) {
self.playerLives --;
}
//处理攻击和被攻击
if (attacker) {
[attackee receiveAttacker:attacker contact:contact];
[self.playerbullets removeChildrenInArray:@[attacker]];
[self.enemies removeChildrenInArray:@[attackee]];
}
}
}
-(void)triggerGrameOver{
self.finished = YES;
NSString *parth = [[NSBundle mainBundle] pathForResource:@"EnemyExplosion" ofType:@"sks"];
SKEmitterNode *explosion = [NSKeyedUnarchiver unarchiveObjectWithFile:parth];
explosion.numParticlesToEmit = 200;
explosion.position = self.playNode.position;
[self addChild:explosion];
[self.playNode removeFromParent];
SKTransition *transition = [SKTransition doorsOpenVerticalWithDuration:1.0];
SKScene *gameOver = [[GameOverScene alloc]initWithSize:self.frame.size];
[self.view presentScene:gameOver transition:transition];
[self runAction:[SKAction playSoundFileNamed:@"gameOver.wav" waitForCompletion:NO]];
}
-(BOOL)checkForGameOver{
if (self.playerLives == 0) {
[self triggerGrameOver];
return YES;
}
return NO;
}
//触摸处理
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
if (location.y<CGRectGetHeight(self.frame)*0.2) {
CGPoint target = CGPointMake(location.x, self.playNode.position.y);
CGFloat duration = [self.playNode moveToWard:target];
}else{
BulletNode *bullet = [BulletNode bulletFrom:self.playNode.position toward:location];
if (bullet) {
[self.playerbullets addChild:bullet];
}
}
}
}
//下一个关卡
-(void)gotoNextLevel{
self.finished = YES;
SKLabelNode *lb = [SKLabelNode labelNodeWithFontNamed:@"Courier"];
lb.text = @"Level Complete!";
lb.fontColor = [SKColor blueColor];
lb.fontSize = 32;
lb.position = CGPointMake(self.frame.size.width*0.5, self.frame.size.height*0.5);
[self addChild:lb];
GameScenes *nextLevel = [[GameScenes alloc]initWithSize:self.frame.size levenNumber:self.levelNumber+1];
nextLevel.playerLives = self.playerLives;
[self.view presentScene:nextLevel transition:[SKTransition flipHorizontalWithDuration:1.0]];
}
-(void)updateBullets{
NSMutableArray *bulletToRemove = [NSMutableArray array];
for (BulletNode *bullet in self.playerbullets.children) {
if (!CGRectContainsPoint(self.frame, bullet.position)) {//清除移动到场外的导弹
[bulletToRemove addObject:bullet];//标记以清除的导弹
continue;
}
[bullet applyRecurringForce];//推动剩下的导弹
}
[self.playerbullets removeChildrenInArray:bulletToRemove];
}
-(void)updateEnemies{
NSMutableArray *enemisToRemove = [NSMutableArray array];
for (SKNode *node in self.enemies.children) {
if (!CGRectContainsPoint(self.frame, node.position)) {
[enemisToRemove addObject:node];//移除敌人
continue;
}
}
if (enemisToRemove.count>0) {
[self.enemies removeChildrenInArray:enemisToRemove];
}
}
-(void)checkForNextLevel{
if (self.enemies.children.count==0) {
[self gotoNextLevel];
}
}
-(void)update:(CFTimeInterval)currentTime {//帧更新
if (self.finished) {
return;
}
[self updateBullets];//导弹主动力
[self updateEnemies];//刷新敌人
if (![self checkForGameOver]) {
[self checkForNextLevel];//管卡是否结束
}
/* Called before each frame is rendered */
}
GameScenes.h
@interface GameScenes : SKScene
@property (assign, nonatomic) NSUInteger levelNumber;
@property (assign, nonatomic) NSUInteger playerLives;
@property (assign, nonatomic) BOOL finished;
+(instancetype)sceneWithSize:(CGSize)size levenNumber:(NSUInteger)levelNumber;
-(instancetype)initWithSize:(CGSize)size levenNumber:(NSUInteger)levelNumber;
@end
第11:控制器加载游戏创建GameViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view.
SKView * skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES;
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = YES;
StartScene *scene1 = [[StartScene alloc]initWithSize:CGSizeMake(self.view.frame.size.width, self.view.frame.size.height)];
scene1.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[skView presentScene:scene1];
// Create and configure the scene.
// GameScenes *scene = [GameScenes nodeWithFileNamed:@"GameScene"];
// scene.scaleMode = SKSceneScaleModeAspectFill;
//
// // Present the scene.
// [skView presentScene:scene];
}
- (BOOL)shouldAutorotate
{
return YES;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
return UIInterfaceOrientationMaskAllButUpsideDown;
} else {
return UIInterfaceOrientationMaskAll;
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (BOOL)prefersStatusBarHidden {
return YES;//隐藏顶部状态栏
}
总结:
本次使用SpriteKit创建一个简单游戏。