手把手教你开发一款IOS飞行射击游戏(三)

news/2024/7/20 20:09:23 标签: IOS, cocos2d, 游戏

这篇里面我们开始制作我们的飞船。

首先介绍一个有用的工具,TexturePacker。

首先我们简单介绍一下使用TexturePacker处理图片素材的好处。这里我们就不得不提一下Cocos2d中Texture的处理方式,在Cocos2d中,CCSprite(精灵)对象的纹理的长宽都是2的幂指数,比如一个精灵由一张100×200的图片初始化,那么实际图片占用的内存有多大呢?答案是128×256B即32KB,有一部分空间被浪费掉了。

我们注意到飞船还有子弹等元素都是很小的图片,如果我们一张一张添加到工程Resources中,一张一张的加载这些图片的话,有很多补足的空间都被浪费了,那么有一个解决方法显而易见,就是把这些小图组合到一起,这样这些小图彼此紧密的组合在一起,很好的补充了那些浪费的空间,节约了内存使用,同时,加载这一张大图的速度要比一张一张加载小图的效率要高得多,可以提高程序的执行速度。

那么我们把小图捏到一起之后,怎么在程序中再把这些图片一张张拆出来呢?必然是用一个文件去记录每个图片的位置信息和大小信息,包括图片是否被旋转等,然后在读取图片的时候通过偏移来找到那张需要的图片。

好了,原理大概说完了,这些TexturePacker都替我们做了,我们打开TexturePacker,看到如下的界面:

手把手教你开发一款<a class=IOS飞行射击游戏(三) - Daniel - KHome" />

我们可以拖拽图片文件到最右侧Sprites列表中,图片会按照最优的方式被组合到一起,我们将下面这几张图片拖拽到里面:

bullet.png:

手把手教你开发一款<a class=IOS飞行射击游戏(三) - Daniel - KHome" />

aircraft1.png: 

手把手教你开发一款<a class=IOS飞行射击游戏(三) - Daniel - KHome" />

aircraft2.png:

手把手教你开发一款<a class=IOS飞行射击游戏(三) - Daniel - KHome" />

aircraft3.png: 

手把手教你开发一款<a class=IOS飞行射击游戏(三) - Daniel - KHome" />

aircraft4.png:

手把手教你开发一款<a class=IOS飞行射击游戏(三) - Daniel - KHome" />

 

得到的结果如下:

手把手教你开发一款<a class=IOS飞行射击游戏(三) - Daniel - KHome" />

在左侧的Data FileName点击右边的文件选择器,文件名中填入:dark-travel-elements-hd.plist,(这个名字也可以自己起,后面加载的时候,相应的代码需要修改为自己起的名字即可),名字后面的-hd后缀需要保留,TextureFormat选择“GZip.compr.PVR(.pvr.gz, Ver.2)”,勾选Premultiply alpha,防止图片边缘出现黑边。Imageformat选择RGBA4444即可,点击AutoSD右侧的设置按钮,点击Apply生成SD版本的图片(HD和SD版本分别是针对Retina屏和非Retina屏所做的区分,有兴趣的童鞋可以查查资料):

手把手教你开发一款<a class=IOS飞行射击游戏(三) - Daniel - KHome" />

设置完成后,点击上边菜单publish按钮生成4个文件:

dark-travel-elements-hd.plist

dark-travel-elements-hd.pvr.gz

dark-travel-elements.plist

dark-travel-elements.pvr.gz

然后我们把这4个文件加入到我们工程的Resources中。

 

下面我们开始添加我们的飞船。

在开始写代码之前,我们考虑一下,这个游戏最终是要进行玩家和敌人(电脑)飞船之间对抗的,玩家飞船和敌人飞船有哪些相同的属性呢,是不是能够抽象出相同的地方创建一个父类呢?

答案是肯定的。我们想一下,飞船都有哪些属性和动作呢?作为一个游戏,飞船肯定需要有生命值,有速度等属性吧,那有哪些动作(或者说方法)呢?移动,射击,爆炸这些方法。

我们创建一个新的类作为飞船的父类:Entity继承自CCNode,Entity.h代码如下:

#import"CCNode.h"

#import"cocos2d.h"

#import"TagDefinitions.h"

#import"CommonUtility.h"

 

@interface Entity: CCNode{

   float speed;

   float health;

   float healthRecord; //for reset

   BulletTypes bulletType;     //if the entity just has onetype of bullet, use this

   CCSprite* entitySprite;

}

 

@property(readonly) float speed;

@property(readonly) float height;

@property(readonly) float width;

@property(readonly) CGPoint aimPos;

 

-(void)changePosition:(CGPoint) pos;

-(void)shootBulletAtPosition:(CGPoint) position bulletType:(BulletTypes)bulletType;

-(void)shootBulletAtPosition:(CGPoint) position

                    atTarget:(CGPoint) target

                  bulletType:(BulletTypes) bT;

-(void)shootBulletAtPosition:(CGPoint) position

                     atAngle:(float) angle

                  bulletType:(BulletTypes) bT;

-(void)moveByX:(float)xLength andY:(float)uLength;

- (void)explode;

 

@end

 

Entity.m的代码:

#import"Entity.h"

#import"GameLayer.h"

#import"SimpleAudioEngine.h"

 

@implementationEntity

 

-(void)changePosition:(CGPoint) pos{

   if (CGRectContainsRect([[CommonUtility utility] screenRect], [entitySpriteboundingBox])) {

       float spriteWidth = entitySprite.contentSize.width;

       float spriteHeight = entitySprite.contentSize.height;

       if (pos.x < 0) {

           pos.x = 0;

       }

       else if (pos.x > [CommonUtility utility].screenWidth - spriteWidth){

           pos.x = [CommonUtility utility].screenWidth - spriteWidth;

       }

       

       float heightLimit = [CommonUtility utility].screenScale * 100;

       if (pos.y < heightLimit) {

           pos.y = heightLimit;

       }

       else if (pos.y > [CommonUtility utility].screenHeight - spriteHeight){

           pos.y = [CommonUtility utility].screenHeight - spriteHeight;

       }

   }

   [entitySprite setPosition:pos];

}  //changePosition

 

-(void)moveByX:(float)xLength andY:(float)yLength{

   CGPoint movedLocation = ccp(entitySprite.position.x + xLength,entitySprite.position.y + yLength);

   [self changePosition:movedLocation];

}  //moveByX

 

-(void)shootBulletAtPosition:(CGPoint) position

                  bulletType:(BulletTypes) bT{

}  //shootBulletAtPosition

 

-(void)shootBulletAtPosition:(CGPoint) position

                    atTarget:(CGPoint) target

                  bulletType:(BulletTypes) bT{

}  //shootBulletAtPosition

 

-(void)shootBulletAtPosition:(CGPoint) position

                     atAngle:(float) angle

                  bulletType:(BulletTypes) bT{

}  //shootBulletAtPosition

 

- (float)height{

   return [entitySprite contentSize].height;

}  //height

 

- (float)width{

   return [entitySprite contentSize].width;

}  //width

 

-(CGPoint)position{

   return entitySprite.position;

}  //position

 

- (CGPoint)aimPos{

   return ccp(entitySprite.position.x+self.width*0.5f,entitySprite.position.y+self.height*0.5f);

}

 

- (BOOL)visible{

   return entitySprite.visible;

}  //visible

 

- (float)speed{

   return speed;

}  //speed

 

- (void)explode{

}

 

- (void)dealloc{

   entitySprite = nil;

   [super dealloc];

}

 

@end

 

这里我们实现了changePosition和moveByX:andY:这两个方法,这两个方法都是改变Entity位置的,changePosition中控制对象不会飞出屏幕,同时注意一下下面这行代码:

if(CGRectContainsRect([[CommonUtility utility] screenRect], [entitySpriteboundingBox])){…}

我们知道,敌人飞船一开始是不会在屏幕上的,那么他们是怎么出现的呢?突然闪烁出来?也可以其实,不过我个人觉得还是从屏幕右侧慢慢飞出来比较正常,不然敌人就太厉害了哈哈。所以我们将changePosition中的判断逻辑用这个if语句包起来,这样当敌人飞船在屏幕外的时候,就不会执行判断语句导致无法从外面飞进来。

另外在类中我们定义了一些基本属性,这里挑一些不容易理解的解释一下,entitySprite是Entity对应的精灵,屏幕上我们看到的Entity就是这个精灵;另外有个属性需要注意一下,我们定义了一个health,那healthRecord是干啥的呢?想象一下,作为一款飞行射击游戏,是不是需要让玩家看到飞船和敌人的生命值?这就对了,healthRecord记录的就是Entity的原始生命值,health是当前生命值,我们初始化的时候,可以用一个长方形代表Entity的生命值,当生命值改变时,我们用这两个的比例就能够算出这个长方形的长度来,也就是飞船或者敌人的剩余生命值。

另外,我们声明了三个射击方法:

-(void)shootBulletAtPosition:bulletType:

-(void)shootBulletAtPosition:atTarget:bulletType:

-(void)shootBulletAtPosition:atAngle:bulletType:

三个方法都允许指定子弹类型,第一个方法允许飞船射出水平的子弹,第二个方法允许飞船向指定目标射击,第三个方法允许飞船将子弹以一定角度射出。我们目前还没有子弹,所以暂时将这三个方法留空。

好了,父类定义好了,我们先来定义玩家的飞船,创建AirCraft类,继承Entity类,AirCraft.h定义如下:

#import"CCNode.h"

#import"Entity.h"

 

@interfaceAirCraft : Entity{

   NSMutableArray* gunPositions;

}

 

+ (id)airCraft;

+(AirCraft*)sharedAirCraft;

 

-(void)shootBulletAtGunLevel:(int)gunLevel;

-(void)resetHealth:(float) health;

 

@end

 

AirCraft.m代码如下:

#import"AirCraft.h"

#import"cocos2d.h"

#import"TagDefinitions.h"

 

@implementationAirCraft

 

static AirCraft*sharedAirCraftData;

 

+(AirCraft*)sharedAirCraft{

   if (sharedAirCraftData == nil) {

       sharedAirCraftData =[[[AirCraft alloc] init] autorelease];

   }

    

   return sharedAirCraftData;

}

 

+ (id)airCraft{

   return [[[AirCraft alloc] init] autorelease];

}

 

- (id)init{

   if (self = [super init]) {

       gunPositions = [[NSMutableArray arrayWithObjects:[NSNumbernumberWithFloat:0.594937], [NSNumber numberWithFloat:0.651899], [NSNumbernumberWithFloat:0.241379], [NSNumber numberWithFloat:0.37931], [NSNumber numberWithFloat:0.741379],[NSNumber numberWithFloat:0.810345], nil] retain];

       speed = 300;

       health = 1000;

       healthRecord = health;

       entitySprite = [CCSprite spriteWithSpriteFrameName:@"aircraft1.png"];

       CCAnimation* animation = [CCAnimationanimateWithFrameAndRollback:@"aircraft" frameCount:4 delay:0.03fstartPos:1];

       CCAnimate* animate = [CCAnimate actionWithAnimation:animation];

       CCRepeatForever* repeatAction = [CCRepeatForever actionWithAction:animate];

       entitySprite.anchorPoint = CGPointZero;

       entitySprite.position = ccp(100, [CommonUtility utility].screenWidth*0.5f-100);

       [entitySprite runAction:repeatAction];

       [self addChild:entitySprite z:100 tag:AirCraftTag];

   }

    

   return self;

}

 

-(CGPoint)gunPositionAtIndex:(int) gunIndex{

   float x;

   if (gunIndex == 2 || gunIndex == 3) {

       x = [[gunPositions objectAtIndex:1] floatValue];

   }else{

       x = [[gunPositions objectAtIndex:0] floatValue];

   }

   float y = [[gunPositions objectAtIndex:gunIndex + 1] floatValue];

   return ccp(entitySprite.position.x + entitySprite.contentSize.width * x,entitySprite.position.y + entitySprite.contentSize.height * y);

}

 

-(void)shootBulletAtGunLevel:(int)gunLevel{

   int minLevel = 1;

   int maxLevel = 2;

   if (gunLevel < minLevel) {

       gunLevel = minLevel;

   }

   else if (gunLevel > maxLevel){

       gunLevel = maxLevel;

   }

   if (gunLevel == minLevel) {

       [self shootBulletAtPosition:[self gunPositionAtIndex:2]bulletType:SmallRoundBullet];

       [self shootBulletAtPosition:[self gunPositionAtIndex:3]bulletType:SmallRoundBullet];

   }

   else if (gunLevel == maxLevel) {

       [self shootBulletAtPosition:[self gunPositionAtIndex:1]bulletType:PoweredBullet];

       [self shootBulletAtPosition:[self gunPositionAtIndex:2]bulletType:PoweredBullet];

       [self shootBulletAtPosition:[self gunPositionAtIndex:3]bulletType:PoweredBullet];

       [self shootBulletAtPosition:[self gunPositionAtIndex:4]bulletType:PoweredBullet];

   }

}

 

-(void)resetHealth:(float) h{

   health = h;

   healthRecord = h;

}

 

- (void)dealloc{

   sharedAirCraftData = nil;

   [gunPositions removeAllObjects];

   [gunPositions release];

   [super dealloc];

}

 

@end

 

这里我们首先跳过init方法,先说明一下其他变量和方法,我们保存了一个gunPositions数组,这个是用来做什么的呢?我们观察飞船图片,我们给飞船画了4个子弹发射装置(早知道这么麻烦,当初就画一个了T_T),所以我们一共有4个位置发射子弹,而不是从飞船的头部发射,gunPositions定义了这4个发射装置在飞船上的位置,通过他们,我们能够控制子弹精确地从这些位置发射出来(说白了就是设置子弹的初始位置在这些地方)。通过init方法初始化好(这些位置是写死的,如果换飞船图片的话,这些位置就失效了),通过gunPositionAtIndex来获取每个位置。另外,方法shootBulletAtGunLevel是做什么的呢?为了增加游戏性,我们定义飞船可以发射两种子弹,一种是高杀伤的子弹,一种是频率快的子弹,玩家可以根据不同类型的敌人发射不同的子弹,因此,我们通过这个方法来决定玩家发射子弹的数量和类型,发射频率后面会添加控制逻辑,各位稍安勿躁。另外实现了resetHealth并且重载了dealloc方法,这里不多做说明。

好了,我们回头看看init方法,首先是一些初始化的语句,这里不多说,我们看后面CCAnimation的这些语句:

CCAnimation*animation = [CCAnimation animateWithFrameAndRollback:@"aircraft"frameCount:4 delay:0.03f startPos:1];

       CCAnimate* animate = [CCAnimate actionWithAnimation:animation];

       CCRepeatForever* repeatAction = [CCRepeatForever actionWithAction:animate];

       entitySprite.anchorPoint = CGPointZero;

       entitySprite.position = ccp(100, [CommonUtility utility].screenWidth*0.5f-100);

       [entitySprite runAction:repeatAction];

首先你会发现编译器提示你找不到animateWithFrameAndRollback方法,你会发现CCAnimation类里面没有这个方法,不卖关子了,这是我们利用object-c的Category特性对CCAnimation类进行扩展的方法,这个扩展类我们稍后奉上,首先先看下这几行代码,第一行我们定义一个动画效果,动画由aircraft1/2/3/4.png组成,实现了飞船后面的喷气效果,第二句将CCAnimation转换为一个Action对象,然后我们定义一个死循环的动画Action(CCRepeatForever),这个Action会一直重复播放初始化的animate这个动画效果,然后我们用CCSprite的runAction来播放这个循环动画效果,这样就实现了飞船的喷气效果。

下面我们补上CCAnimation的扩展类:

CCAnimation+Helper.h:

#import"CCAnimation.h"

#import"cocos2d.h"

 

@interfaceCCAnimation (Helper)

 

+(CCAnimation*)animateWithFrame:(NSString*) frame

                     frameCount:(int) frameCount

                          delay:(float)delay

                       startPos:(int) startPos;

 

+(CCAnimation*)animateWithFrameAndRollback:(NSString*) frame

                                frameCount:(int) frameCount

                                     delay:(float) delay

                                  startPos:(int) startPos;

 

@end

 

CCAnimation+Helper.m:

#import"CCAnimation+Helper.h"

 

@implementationCCAnimation (Helper)

 

+(CCAnimation*)animateWithFrame:(NSString *)frame

                     frameCount:(int)frameCount

                          delay:(float)delay

                       startPos:(int) startPos{

   NSMutableArray* frames = [NSMutableArray arrayWithCapacity:frameCount];

   CCSpriteFrameCache* frameCache = [CCSpriteFrameCache sharedSpriteFrameCache];

   for (int i = 0; i < frameCount; i++) {

       NSString* file = [NSString stringWithFormat:@"%@%i.png", frame,i+startPos];

       CCSpriteFrame* frame = [frameCache spriteFrameByName:file];

       [frames addObject:frame];

   }

    

   return [CCAnimation animationWithSpriteFrames:frames delay:delay];

}

 

+(CCAnimation*)animateWithFrameAndRollback:(NSString*) frame

                                frameCount:(int) frameCount

                                     delay:(float) delay

                                  startPos:(int) startPos{

   NSMutableArray* frames = [NSMutableArray arrayWithCapacity:frameCount];

   CCSpriteFrameCache* frameCache = [CCSpriteFrameCache sharedSpriteFrameCache];

   for (int i = 0; i < frameCount; i++) {

       NSString* file = [NSString stringWithFormat:@"%@%i.png", frame,i+startPos];

       CCSpriteFrame* frame = [frameCache spriteFrameByName:file];

       [frames addObject:frame];

   }

   for (int i = frameCount-2; i > -1; i--) {

       NSString* file = [NSString stringWithFormat:@"%@%i.png", frame,i+startPos];

       CCSpriteFrame* frame = [frameCache spriteFrameByName:file];

       [frames addObject:frame];

   }

    

   return [CCAnimation animationWithSpriteFrames:frames delay:delay];

}

 

@end

这里解释一下下面这个方法:

(CCAnimation*)animateWithFrame:frameCount:delay:startPos:

我们在命名动画的每一帧图片的时候,按照动画帧的播放顺序定义png文件名,如aircraft1.png —aircraft4.png,因此在添加CCSpriteFrame的时候,我们可以提供一个文件名,如“aircraft”,提供一个start值和count值,然后用一个循环把1— 4这4张图片加载到数组中,然后从用数组创建CCAnimation动画。另一个方法定义了创建循环动画的方法,这里不再赘述了。这里用到了一个类:

CCSpriteFrameCache*frameCache = [CCSpriteFrameCache sharedSpriteFrameCache];

我们需要在GameLayer中将我们用TextPacker创建的那个组装好的plist文件load到这个Cache中,我们在GameLayer中添加如下方法:

-(void)initCaches{

   CCSpriteFrameCache* frameCache = [CCSpriteFrameCache sharedSpriteFrameCache];

   [frameCache addSpriteFramesWithFile:@"dark-travel-elements.plist"];

}

然后我们在GameLayer的init方法中添加对这个方法的调用:

- (id)init{

   if (self = [super init]) {

       [self initCaches];

       [self initBackground];

       [self scheduleUpdate];

   }

    

   return self;

}

好了,貌似大功告成了,编译,运行。

哈哈,是不是和之前一样,只有背景?那就对了,我们还没把飞船加到场景中呢。在GameLayer的init方法中scheduleUpdate那行的上面加上下面的代码:

AirCraft* airCraft= [AirCraft sharedAirCraft];

[selfaddChild:airCraft z:3 tag:AirCraftTag];

这样飞船就被加入到场景中了,现在运行一下看看,飞船已经出现在屏幕上了,再仔细观察飞船尾部,可以看到喷气动画了吧,效果如下:

手把手教你开发一款<a class=IOS飞行射击游戏(三) - Daniel - KHome" />

 

好了,今天先到这儿,下一篇我们将使用开源代码为飞船加上摇杆控制,敬请期待~


http://www.niftyadmin.cn/n/878304.html

相关文章

java 解压缩zip_java解压缩zip

依赖的包&#xff1a;org.apache.antant1.8.2package com.jd.dlink.service.utils;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.util.Enumeration;import o…

手把手教你开发一款IOS飞行射击游戏(四)

好了&#xff0c;我们继续我们的IOS之旅。 想必大家玩儿手游的时候都用过虚拟摇杆&#xff0c;通过虚拟摇杆在控制屏幕上的元素移动&#xff0c;做各种动作等等。今天我们就通过引用一个开源的代码来完成虚拟摇杆和控制按键的制作&#xff0c;并通过摇杆来控制我们的飞船进行移…

umap算法_UMAP的初步了解及与t-SNE的比较

降维是机器学习中的可视化和理解高维数据的强大工具。t-SNE是最广泛使用的可视化技术之一&#xff0c;但其性能在大型数据集中会受到影响。UMAP是McInnes等人的一项新技术。与t-SNE相比&#xff0c;它具有许多优势&#xff0c;最显著的是提高了速度并更好地保存了数据的全局结构…

手把手教你开发一款IOS飞行射击游戏(五)

我们接着上一篇教程&#xff0c;继续开发我们的游戏。 本篇教程我们在之前的基础上添加子弹&#xff0c;然后用之前创建的按钮控制飞船发射子弹。 首先介绍一下CCSpriteBatchNode类&#xff0c;我们知道&#xff0c;在射击类游戏中&#xff0c;我们发射的&#xff0c;敌人发射的…

java套娃_[GXYCTF2019]禁止套娃

0x00 知识点无参数RCEeval($_GET[exp]);1.利用超全局变量进行bypass&#xff0c;进行RCE 2.进行任意文件读取形式&#xff1a;if(; preg_replace(/[^\W]\((?R)?\)/, , $_GET[code])) { eval($_GET[code]);}也就是说 正则匹配了一些关键字比如et导致很多函数不能用&#xff0…

手把手教你开发一款IOS飞行射击游戏(六)

这一篇我们为我们的飞船添加各种敌人飞船。 首先&#xff0c;我们准备一下敌人飞船的各种图片&#xff1a; boss.png&#xff1a; bigdaddy.png&#xff1a; powermaker.png&#xff1a; speedkiller.png&#xff1a; littleworm.png&#xff1a; 自己随便画的&#xff0c;名字…

java ee中javamail注解_JavaEE-JavaMail 01 简介

一. JavaMail 简介JavaMail API提供了一种与平台无关和协议独立的框架来构建邮件和消息应用程序JavaMail API提供了一组抽象类定义构成一个邮件系统的对象, 它是阅读, 撰写和发送电子信息的可选包[标准扩展]JavaMail API支持的协议:SMTPPOPIMAPMIMENNTPJavaMail开发准备下载Jav…

Java游戏计时方法_如何在Java中计时方法执行

您应该在调用之前获得开始时间&#xff0c;并在方法执行之后获得结束时间。所不同的是所花费的时间。示例import java.util.Calendar;public class Tester {public static void main(String[] args) {long startTime Calendar.getInstance().getTimeInMillis();longRunningMet…