欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

ios开发中苹果2D引擎SpriteKit介绍

程序员文章站 2022-04-26 12:41:22
ios开发中苹果2d引擎spritekit介绍,最近研究了苹果自家开发的2d引擎spritekit和3d引擎scenekit,开篇之前,需要客观的讲,如果你要从事的是团队或者公司的项目,还是直接un...

ios开发中苹果2d引擎spritekit介绍,最近研究了苹果自家开发的2d引擎spritekit和3d引擎scenekit,开篇之前,需要客观的讲,如果你要从事的是团队或者公司的项目,还是直接unity搞起,这涉及到开发与维护成本的问题,毕竟spritekit目前无法对跨平台给予支持。但是如果你是一个独立开发者,对苹果原生框架感兴趣,或者只关注与苹果的app store,我想spritekit和scenekit也是个不错的选择。

sprite译作精灵,可以这样理解,在spritekit的世界里,游戏里的怪兽是一个精灵,主角与主角发射的炮弹也是一个精灵,或者说游戏里的一个不会动的背景图,也可以是一个精灵。下面以精灵为切入点,讲解一下一个充满野心的苹果弄出来的2d引擎。

ios开发中苹果2D引擎SpriteKit介绍

一、精灵与场景

1.新建一个xcode工程,可以看到,不管是ios,还是macos,甚至于tvos,都有一个叫game的项目新建方式。我们选择ios的game,新建出一个游戏项目。game与普通工程项目有什么不同,其实就一点项目默认的gameviewcontroller的view是以skview的形式load出来的,下面我们接着新建一个游戏场景skscene,用以装载即将new出来的精灵。

ios开发中苹果2D引擎SpriteKit介绍

2.场景的新建。
注:scene场景的起始坐标是以左下角为(0,0)原点,而非传统view的左上角。

- (menuscene *)menuscene {
    if (!_menuscene) {
        _menuscene = [[menuscene alloc] initwithsize:self.view.bounds.size];
        //@interface menuscene : skscene
        //@end
    }
    return _menuscene;
}



- (void)viewdidload {
    [super viewdidload];

    [(skview *)self.view presentscene:self.menuscene];

    // do any additional setup after loading the view, typically from a nib.
}
@implementation menuscene


-(instancetype)initwithsize:(cgsize)size {

    if (self = [super initwithsize:size]) {
        #if target_os_iphone 
        #define skcolor uicolor
        #else 
        #define skcolor nscolor
        #endif
        // skcolor主要是为了兼容mac的nscolor与ios的uicolor
        self.backgroundcolor = [skcolor whitecolor];

        [self addchild:self.titlenode];
        [self addchild:self.pathlabelnode];
        [self addchild:self.colllabelnode];
        [self addchild:self.physlabelnode];
        [self addchild:self.physcollnode];
    }
    return self;
}

3.游戏场景的切换。与初始化的页面一致,游戏的转场为使用presentscene跳转到新建的scene当中。

- (sklabelnode *)titlenode {
    if (!_titlenode) {
        _titlenode = ({
            sklabelnode *labelnode = [sklabelnode labelnodewithfontnamed:@"chalkduster"];
            labelnode.text = @"spritekit  test";
            labelnode.fontsize = 30;
            labelnode.fontcolor = [skcolor bluecolor];
            labelnode.position = cgpointmake(cgrectgetmidx(self.frame), self.frame.size.height * 0.75);
            labelnode.name = labelnode.text;

            labelnode;
        });
    }
    return _titlenode;
}

-(void)touchesbegan:(nsset *)touches withevent:(uievent *)event {

    for (uitouch *touch in touches) {
        cgpoint location = [touch locationinnode:self];
        sknode *node = [self nodeatpoint:location];
        [self changetogamescenewithnodename:node.name];
    }
}

-(void)changetogamescenewithnodename:(nsstring *)nodename {

    nslog(@"nodename=%@",nodename);
    if ([nodename isequaltostring:self.pathlabelnode.name]) {
        pathscene *pathscene = [pathscene scenewithsize:self.size];
        // 定制转场类型
        sktransition *reveal = [sktransition revealwithdirection:sktransitiondirectionup duration:0.5];

        [self.scene.view presentscene:pathscene transition:reveal];
    }
    else if ...

4.精灵的新建与添加。精灵的新建有两种形式,一个是直接以图片的形式新建,其size默认为图片的size,另一种则以纹理的形式新建。

// 以图片新建
- (skspritenode *)player {
    if (!_player) {
        _player = [skspritenode spritenodewithimagenamed:@"player"];
        _player.name = @"player";
        _player.position = cgpointmake(self.size.width /2, self.size.height /2);
    }
    return _player;
}
// 以纹理新建
- (skspritenode *)walkman {
    if (!_walkman) {
        _walkman = [skspritenode spritenodewithimagenamed:@"walkr01"];
        _walkman.name = @"walkman";
        _walkman.position = cgpointmake(self.player.position.x, cgrectgetmaxy(self.player.frame) + 30);

        sktexture * texture1 = [sktexture texturewithimagenamed:@"walkr01"];
        sktexture * texture2 = [sktexture texturewithimagenamed:@"walkr02"];
        sktexture * texture3 = [sktexture texturewithimagenamed:@"walkr03"];
        sktexture * texture4 = [sktexture texturewithimagenamed:@"walkr04"];
        sktexture * texture5 = [sktexture texturewithimagenamed:@"walkr05"];

        skaction *animation = [skaction animatewithtextures:@[texture1, texture2, texture3, texture4, texture5] timeperframe:1];
        skaction *action = [skaction repeatactionforever:animation];
        [_walkman runaction:action];
    }
    return _walkman;
}

5、精灵的添加运动事件

-(void)touchesbegan:(nsset *)touches withevent:(uievent *)event {

    for (uitouch *touch in touches) {

        // 添加武器
        skspritenode * arms = [skspritenode spritenodewithimagenamed:@"projectile"];

        arms.position = self.player.position;

        [self addchild:arms];

        cgpoint location = [touch locationinnode:self];

        // 直线轨迹
//        skaction * movetoaction = [skaction moveto:location duration:0.5];;

        // 持续增加
//        skaction * movebyaction = [skaction movebyx:100 y:100 duration:0.3];

        // 改变大小
//        skaction * sizeaction = [skaction resizebywidth:arms.size.width * 1.5 height:arms.size.height * 1.5  duration:0];

        // 旋转
//        skaction * radiansaction = [skaction rotatebyangle:m_pi * 4 duration:movetoaction.duration];

        // 音效
//        skaction * armssound = [skaction playsoundfilenamed:@"pew-pew-lei.caf" waitforcompletion:no];

        skaction * armsaction = [skaction group:@[pathaction, sizeaction,armssound]];

        [arms runaction:armsaction completion:^{
            // 移除
            [arms removefromparent];
        }];
    }
}

二、精灵的接触检测

skscene有一个场景方法,改方法每帧都会触发一次,可供简单的事件分析与监测,比如精灵越界销毁,精灵的接触监测,故事板的得分情况的更新等等。

/**
 override this to perform per-frame game logic. called exactly once per frame before any actions are evaluated and any physics are simulated.

 @param currenttime the current time in the app. this must be monotonically increasing.
 */
- (void)update:(nstimeinterval)currenttime;
-(void)update:(cftimeinterval)currenttime {

    // 怪物与武器的越界移除

    // 更新数字版

    // 检测精灵事件(如点击精灵之后,给它设置个标识,在下一帧的时候做事件处理)
}
注:后面讲述精灵的物理碰撞,能够更准确的进行精灵的碰撞检测

三、精灵的物理引擎

ios开发中苹果2D引擎SpriteKit介绍


// 方形
- (skspritenode *)square {
    if (!_square) {
        _square = [skspritenode spritenodewithimagenamed:@"square"];
        _square.position = cgpointmake(self.size.width * 0.8, cgrectgetmidx(self.frame));
        _square.name = @"square_prey";
        _square.physicsbody = [skphysicsbody bodywithrectangleofsize:_square.size];
    }
    return _square;
}

// 圆形
- (skspritenode *)circle {
    if (!_circle) {
        _circle = [skspritenode spritenodewithimagenamed:@"circle"];
        _circle.position = cgpointmake(self.size.width * 0.65, cgrectgetmidx(self.frame));
        _circle.name = @"circle_prey";
        _circle.physicsbody = [skphysicsbody bodywithcircleofradius:_circle.size.width / 2];
    }
    return _circle;
}

// 三角形
- (skspritenode *)triangle {
    if (!_triangle) {
        _triangle = [skspritenode spritenodewithimagenamed:@"triangle"];
        _triangle.position = cgpointmake(self.size.width * 0.5, cgrectgetmidx(self.frame));
        _triangle.name = @"triangle_prey";

        cgmutablepathref trianglepath = cgpathcreatemutable();
        // 中心
        cgpathmovetopoint(trianglepath, nil, -_triangle.size.width / 2, -_triangle.size.height / 2);
        //
        cgpathaddlinetopoint(trianglepath, nil, _triangle.size.width / 2, -_triangle.size.height / 2);
        cgpathaddlinetopoint(trianglepath, nil, 0, _triangle.size.height / 2);
        cgpathaddlinetopoint(trianglepath, nil, -_triangle.size.width / 2, -_triangle.size.height / 2);

        _triangle.physicsbody = [skphysicsbody bodywithpolygonfrompath:trianglepath];

        cgpathrelease(trianglepath);

    }
    return _triangle;
}

四、精灵的物理碰撞检测

-(instancetype)initwithsize:(cgsize)size {

    if (self = [super initwithsize:size]) {

        self.backgroundcolor = [uicolor whitecolor];

        [self addchild:self.back];

        [self addchild:self.square];
        [self addchild:self.circle];
        [self addchild:self.triangle];

        self.physicsbody = [skphysicsbody bodywithedgeloopfromrect:self.frame];
        self.scene.name = @"self";

        self.physicsbody.categorybitmask = 0x00000001;
        self.physicsbody.collisionbitmask = 0x00000001;
        self.physicsbody.contacttestbitmask = 0x00000001;
        self.physicsworld.contactdelegate = (id )self;
    }
    return self;
}

-(void)touchesbegan:(nsset *)touches withevent:(uievent *)event {

    for (uitouch *touch in touches) {

        cgpoint location = [touch locationinnode:self];

        // 获取点击的sknode
        sknode * node = [self nodeatpoint:location];

        // 新建一个黑球
        node * ball = [skspritenode spritenodewithimagenamed:@"blackball"];
        ball.position = cgpointmake(0, 0);
        ball.name = @"ball";
        ball.physicsbody = [skphysicsbody bodywithcircleofradius:ball.size.width / 2];

        cgpoint offset = cgpointmake(location.x - ball.position.x, location.y - ball.position.y);

        // 斜率
        float ratio = (float) offset.y / (float) offset.x;

        // 速度
        ball.physicsbody.velocity = cgvectormake(1000, 1000 * ratio);

        // 角速度  弧度/秒
        ball.physicsbody.angularvelocity = m_pi * 4;

        // 密度
        ball.physicsbody.density = 100;

        // 弹力
        ball.physicsbody.restitution = 1;

        // 动量 /kg
        ball.physicsbody.mass = 100;

        // 光滑度  0 ~ 1
        ball.physicsbody.friction = 0.5;

        // 是否受重力影响 default value is yes
        ball.physicsbody.affectedbygravity = no;

        // 是否受加速度影响
        ball.physicsbody.allowsrotation = no;

        // 线性阻尼(0:速度从不减弱;1:速度立即减弱)
        ball.physicsbody.lineardamping = 0.5;

        // 角速度阻尼(0:速度从不减弱;1:速度立即减弱)default 0.1
        ball.physicsbody.angulardamping = 0;

        // 物体的类别(一个16进制数)
        ball.physicsbody.categorybitmask = 0x00000001;

        // 设置哪个物体不可与之碰撞(即不可穿透)
        ball.physicsbody.collisionbitmask = 0x00000001;

        // 接触(触发检测函数)
        ball.physicsbody.contacttestbitmask = 0x00000001;

        [self addchild:ball];
    }
}
- (void)didbegincontact:(skphysicscontact *)contact {

    nslog(@"联系中的第一个物体:%@",contact.bodya.node.name);

    nslog(@"联系中的第二个物体:%@",contact.bodyb.node.name);

    nslog(@"联系点的坐标:%@",nsstringfromcgpoint(contact.contactpoint));

    nslog(@"碰撞方向的法向量:%@",nsstringfromcgvector(contact.contactnormal));

    nslog(@"两个物体的碰撞强度(牛顿每秒):%f",contact.collisionimpulse);

    if ([contact.bodya.node.name containsstring:@"prey"] && [contact.bodyb.node.name isequaltostring:@"ball"]) {

        [contact.bodya.node removefromparent];
    }
    if ([contact.bodyb.node.name containsstring:@"prey"] && [contact.bodya.node.name isequaltostring:@"ball"]) {

        [contact.bodyb.node removefromparent];
    }
}

@end