论述如何基于3种开发工具制作《Monkey Jump》(二)

来源:互联网 发布:网络推广专员要求 编辑:程序博客网 时间:2024/06/04 17:53

论述如何基于3种开发工具制作《Monkey Jump》(二)

发布时间:2012-02-14 17:11:09 Tags:Cocos2d,Monkey Jump,动画,跳跃

作者:Andreas Loew

在本系列教程中,我们使用Cocos2D、TexturePacker和PhysicsEditor来制作一款有趣的横向平台游戏。

在系列教程的第1部分中,我们介绍了这款游戏的设计,制作了精灵层次和我们需要的形状,并开始编写游戏代码。(请点击阅读第3部分)

在上个教程结束前,我们设立了所有的游戏层次,刚刚完成了让随机物体从天空上掉落的制作,还配上音效。

在本教程的第2部分中,我们将把主角添加到游戏中,让它能够移动和跳跃,并开始添加部分游戏玩法。

我们将以上次制作完成的项目为基础开始本教程。如果你还未获得所需内容,可以下载本系列教程的源代码并打开3-DraggingObjects。

开始

在本教程的第1部分中,我们用PhysicsEditor制作了猴子的形状,还是还未将猴子添加到游戏中。现在,让我们来实现这个目标!

以iOS\Cocoa Touch\Objective-C类模板创建新文件,由此来添加Monkey类。将类命名为“Monkey”,将其作为GB2Sprite的次类。记住,同样将Monkey.m文件的扩展名改为.mm。

这个猴子会根据游戏世界中的不同事件做出反应,比如有东西从上方落下时它会举起双手、推动道具和跳跃等。这便是你为何无法使用Cocos2d标准动画例行程序的原因,你需要自行编写执行程序。

为实现上述目标,你需要些许成员变量来存储额外的数据。将以下代码粘贴至Monkey.h中,替换原有代码:

#pragma once

#import “Cocos2d.h”
#import “GB2Sprite.h”

@class GameLayer;

@interface Monkey : GB2Sprite
{
float direction;      // keeps monkey’s direction (from accelerometer)
int animPhase;        // the current animation phase
ccTime animDelay;     // delay until the next animation phase is stated
GameLayer *gameLayer; // weak reference
}

-(id) initWithGameLayer:(GameLayer*)gl;
-(void) walk:(float)direction;

@end

现在,转换到Monkey.mm,将其替换为下列代码:

#import “Monkey.h”
#import “GB2Contact.h”
#import “GMath.h”
#import “Object.h”
#import “SimpleAudioEngine.h”
#import “GameLayer.h”

#define JUMP_IMPULSE 6.0f
#define WALK_FACTOR 3.0f
#define MAX_WALK_IMPULSE 0.2f
#define ANIM_SPEED 0.3f
#define MAX_VX 2.0f

@implementation Monkey

-(id) initWithGameLayer:(GameLayer*)gl
{
// 1 – Initialize the monkey
self = [super initWithDynamicBody:@"monkey"
spriteFrameName:@"monkey/idle/1.png"];

if(self)
{
// 2 – Do not let the monkey rotate
[self setFixedRotation:true];

// 3 – The monkey uses continuous collision detection
// to avoid getting stuck inside fast-falling objects
[self setBullet:YES];

// 4 – Store the game layer
gameLayer = gl;
}

return self;
}

@end

我们来逐步解释initWithGameLayer方法:

1、首先,我们将猴子初始化。猴子的移动会受到物理引擎的影响,所以要将其制作成动态物体。我们使用空闲帧来充当猴子动画和猴子物理形状的首帧。

2、猴子应当保持竖直站立状态,所以我们设定其不可旋转。这意味着猴子会通过Box2d移动,但不会旋转或倾斜。

3、将猴子设置为子弹模式。子弹模式支持对象的持续性碰撞检测。如果不这样设置的话,Box2d会先移动对象然后再进行碰撞检测。对于快速移动的对象来说,可能出现对象因毫无碰撞检测直接穿过另一个对象或卡在另一个对象中。持续性碰撞检测会在对象从当前位置移动到新位置的整个过程中计算碰撞,而不只是在终点。

4、最后,你需要存储游戏层次,分配数值即可。

MonkeyBullet(from raywenderlich)

MonkeyBullet(from raywenderlich)

对于第3个步骤中的子弹模式,如果你正在编写代码的项目中对象数量较少,你可以将Box2d引擎设置为对所有游戏对象运行持续性碰撞检测。但是,当游戏拥有大量对象时,这会给CPU增加负担。因此,在我们这款游戏中,我们只将猴子和快速移动(游戏邦注:从屏幕上方掉落)的对象设置为持续性模式。

要使猴子可用,必须将它添加至GameLayer。打开GameLayer.h,将下列代码添加在顶端的输入声明下:

@class Monkey;

现在,向GameLayer类添加下列成员变量:

Monkey *monkey;

然后,转换至GameLayer.mm,在文件顶端输入Monkey.h:

#import “Monkey.h”

在GameLayer.mm中初始化选择器末端,将猴子初始化,添加到游戏层次中并为猴子设置起始点:

monkey = [[[Monkey alloc] initWithGameLayer:self] autorelease];
[objectLayer addChild:[monkey ccNode] z:10000];
[monkey setPhysicsPosition:b2Vec2FromCC(240,150)];

编译运行,你会看到以下画面:

monkey jump (from raywenderlich)

monkey jump (from raywenderlich)

猴子出现在屏幕上!物体掉落到他头上,它会将其推开。这就是我们想要的结果。

猴子的行走

我们的下个目标是让猴子开始行走,使用加速计作为输入方式。

回到GameLayer.mm中,将下列代码添加到初始化方法末端:

self.isAccelerometerEnabled = YES;

这可以确保,对于内置加速计值的每次改变,GameLayer类都能够自动获得通知。接下来,我们要在GameLayer.mm文件末端添加通知处理器,位置在@end标记之前:

- (void)accelerometer:(UIAccelerometer*)accelerometer
didAccelerate:(UIAcceleration*)acceleration
{
// forward accelerometer value to monkey
[monkey walk:acceleration.y];
}

加速计处理器会调用带有加速计Y轴数值的猴子对象行走方法。这种方法会根据加速计输入来处理和实现猴子的前后移动。

接下来,转换到Monkey.mm,在文件末端添加行走方法(游戏邦注:在@end标记之前)。这个方法会将猴子的新移动方向存储在成员变量中。

-(void) walk:(float)newDirection
{
direction = newDirection;
}

现在尝试编译运行代码,结果什么也没发生。原因在于,尽管方向值已被存储,但是它还未被运用到物理模拟中。要以新移动方向为基础来更新物理模拟,我们需要覆写updateCCFromPhysics选择器,GB2Engine每帧都会调用来更新物理情况。

更新猴子物理效果

将下列代码添加到Monkey.mm中:

-(void) updateCCFromPhysics
{
// 1- Call the super class
[super updateCCFromPhysics];

// 2 – Apply the directional impulse
float impulse = clamp(-[self mass]*direction*WALK_FACTOR,
-MAX_WALK_IMPULSE,
MAX_WALK_IMPULSE);
[self applyLinearImpulse:-b2Vec2(impulse,0) point:[self worldCenter]];
}

在上述代码中,首先你调用超类选择器,根据物理模拟更新猴子的精灵。

随后,你根据存储的方向值将猴子推向正确的方向。重点在于,不要对猴子进行完全的控制。否则,其响应事件(游戏邦注:比如道具掉落或碰撞检测)的“自然”行为无法合理地运行。

你真正做的事情就是,将猴子轻推向正确的方向。因为物理引擎的更新频率为每秒60次,所以保持推力较小很重要。这只是个猴子,不是子弹,尽管我们采用的是子弹模式!

你可以通过对物体施加推力来移动box2D物体。你使用GB2Sprite的applyLinearImpulse方法来实现这个目标,涉及以下两个参数:施加的推力和施力点。

对于施力点,我们将使用物体的重心。将力施加在物体的重心上,这样就不会产生导致物体选择的扭转力。但是,这种情况在我们的游戏中肯定不会出现,因为我们已经将猴子设置为不可旋转。

ApplyLinearImpulse(from raywenderlich)

ApplyLinearImpulse(from raywenderlich)

对于施加的推力,我推荐使用物体的质量来计算。这是因为推力就是质量和速度的产物。

将质量值与存储的方向值相乘。这样当设备稍微倾斜时,产生的推力就比较小,当设备大幅倾斜时,就会产生更大的推力。

以物体质量为基础来缩放推力,使我们无需担心在PhysicsEditor中改变物体形状时会影响物体的移动。如果我们不采取这种做法,假设随后我们将猴子做得更小些,相同的推力施加在质量较小的猴子上,会导致其移动过快。

我们还要设定最大值,避免施力过大。最大施力由MAX_WALK_IMPULSE变量确定。

编译运行,还是毫无变化。原因在于,iPhone模拟器无法模拟加速计。所以,从现在开始,你需要在设备上进行测试!换成设备,再次测试游戏。

猴子现在可以左右滑动,但是移动看起来很不自然。

猴子的移动

接下来,我们要向Monkey.mm添加些许代码,让猴子的动画开始运转。在updateCCFromPhysics方法后添加下列代码:

animDelay -= 1.0f/60.0f;
if(animDelay <= 0)
{
animDelay = ANIM_SPEED;
animPhase++;
if(animPhase > 2)
{
animPhase = 1;
}
}

首行代码通过将时延削减到下个动画阶段来更新下个动画的时间。我使用1.0f/60.0f这个值,是因为我猜想应用以60 fps的速度运行,而且updateCCFromPhysics方法不含有能够精确提供每次更新时间间隔的delta时间参数。

如果动画时延下降到0以下,那么动画延迟值便会重置到动画速度,并将当前阶段加1。如果已经达到最高阶段,将循环回最低阶段,这样动画才能不断循环播放。

接下来,我们需要确定猴子面部的朝向。这可以通过以下两种方法实现:

1、使用来自加速计的方向

2、使用猴子的速度矢量

我偏向于使用加速计,因为它会在玩家尝试通过倾斜设备改变方向时立即提供反馈。接下来,我们将同通过加速计对速度改变做出响应。

在updateCCFromPhysics末端添加以下代码:

// determine direction of the monkey
bool isLeft = (direction < 0);

// direction as string
NSString *dir = isLeft ? @”left” : @”right”;

// update animation phase
NSString *frameName;
const float standingLimit = 0.1;
float vX = [self linearVelocity].x;
if((vX > -standingLimit) && (vX < standingLimit))
{
// standing
frameName = [NSString stringWithFormat:@"monkey/idle/2.png"];
}
else
{
// walking
NSString *action = @”walk”;
frameName = [NSString stringWithFormat:@"monkey/%@/%@_%d.png", action, dir, animPhase];
}

// set the display frame
[self setDisplayFrameNamed:frameName];

从根本上说,以上代码实现的是:如果猴子的速度低于standingLimit,它会在空闲动画帧时直视玩家。否则,它会使用与当前方向和动画帧数相符的行走呈现帧。

编译运行。现在,猴子可以自然地奔跑!

放慢移动速度

对于这个结果我仍有不满意的地方,我觉得猴子移动得太快了。我们可以减小施加到他身上的推力,但是这也会让它的移动显得缓慢而笨拙。

我们需要足够大的推力使他快速反应,但是不要过快地移动。

将Monkey.mm中用于updateCCFromPhysics的当前代码替换为下列代码:

// 1 – Call the super class
[super updateCCFromPhysics];

// 2 – Update animation phase
animDelay -= 1.0f/60.0f;
if(animDelay <= 0)
{
animDelay = ANIM_SPEED;
animPhase++;
if(animPhase > 2)
{
animPhase = 1;
}
}

// 3 – Get the current velocity
b2Vec2 velocity = [self linearVelocity];
float vX = velocity.x;

// 4 – Determine direction of the monkey
bool isLeft = (direction < 0);

if((isLeft && (vX > -MAX_VX)) || ((!isLeft && (vX < MAX_VX))))
{
// apply the directional impulse
float impulse = clamp(-[self mass]*direction*WALK_FACTOR,
-MAX_WALK_IMPULSE,
MAX_WALK_IMPULSE);
[self applyLinearImpulse:-b2Vec2(impulse,0) point:[self worldCenter]];
}

// 5 – Get direction as string
NSString *dir = isLeft ? @”left” : @”right”;

// 6 – Update animation phase
NSString *frameName;
const float standingLimit = 0.1;
if((vX > -standingLimit) && (vX < standingLimit))
{
// standing
frameName = [NSString stringWithFormat:@"monkey/idle/2.png"];
}
else
{
// walking
NSString *action = @”walk”;
frameName = [NSString stringWithFormat:@"monkey/%@/%@_%d.png", action, dir, animPhase];
}

// 7 – Set the display frame
[self setDisplayFrameNamed:frameName];

正如你已经看到的,我们移除了些许代码,但是主要的改变是增加第3部分中vX变量的代码,将推力代码移动到第4部分囊括在if条件中,如果当前方向的速度低于最大值时就算条件满足。这会防止猴子本身的加速过快。

编译运行。我想现在看起来要好得多。

monkey jump (from raywenderlich)

monkey jump (from raywenderlich)

项目当前状态的源代码在4-WalkingMonkey文件夹中。

跳跃

现在,我们要让猴子能够跳跃。对于这个目标,我们需要让游戏检测到GameLayer上任何地方的接触事件,因为我们想要让猴子在每次接触时跳跃。

打开GameLayer.mm,将下列代码添加到初始化选择器中以激活接触检测:

// enable touches
self.isTouchEnabled = YES;

将下列选择器添加到文件末尾(游戏邦注:在@end标记前)。它通过跳跃方法将接触推向猴子物体。

-(void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[monkey jump];
}

此刻,你或许会注意到我们的Monkey类中并没有跳跃方法。我们接下来变要添加这个方法。转换到Monkey.h,将下列方法定义添加到@end前:

-(void) jump;

现在,打开Monkey.mm,在@end标记前添加下列代码:

-(void) jump
{
[self applyLinearImpulse:b2Vec2(0,[self mass]*JUMP_IMPULSE)
point:[self worldCenter]];

[[SimpleAudioEngine sharedEngine] playEffect:@”jump.caf”
pitch:gFloatRand(0.8,1.2)
pan:(self.ccNode.position.x-240.0f) / 240.0f
gain:1.0 ];
}

上述代码实现的是施加推力让猴子跳跃,同时播放跳跃的音效。我们在本教程的第1部分中已经制作了该音效。

编译运行。触动屏幕让猴子跳跃。

monkey jump(from raywenderlich)

monkey jump(from raywenderlich)

修正跳跃问题

但是,这样的结果并不完美:如果你多次触动屏幕,猴子会跳得过高。更为糟糕的是,镜头不会跟随猴子移动。

让我们先来固定镜头。首先,将下列代码添加到GameLayer.mm中的更新选择器顶端,位于现有代码之上。

// 0 – monkey’s position
float mY = [monkey physicsPosition].y * PTM_RATIO;

上述代码将猴子的Y轴位置值复制到新变量mY中。当然,我们也可以访问猴子的ccNode,从那里获取Y轴坐标。最终结果与将物理位置同PTM_RATIO相乘的结果相同。

现在,将这些代码添加到更新选择器末端,位于第7部分之后。

// 8 – Adjust camera
const float monkeyHeight = 70.0f;
const float screenHeight = 320.0f;
float cY = mY – monkeyHeight – screenHeight/2.0f;
if(cY < 0)
{
cY = 0;
}

这样,我们就可以实现镜头Y轴坐标的计算,保持猴子位于屏幕的中心。将该数值设置为正数,这样镜头就不会移动到地面以下。

接下来,让我们来执行背景的视差滚动,这样猴子的移动会显得更加自然。这种效果很容易实现,只需要将背景层次同小于1.0的因数相乘来设置层次位置即可。这会让背景层次的滚动较慢。层次离镜头越远,因数就必须越小。

在刚刚更新的代码下添加下列代码:

// 9 – Do some parallax scrolling
[objectLayer setPosition:ccp(0,-cY)];
[floorBackground setPosition:ccp(0,-cY*0.8)]; // move floor background slower
[background setPosition:ccp(0,-cY*0.6)];      // move main background even slower

根据你的需要来调整数值。

编译运行。多次触动屏幕看看猴子能否自然跳跃。

道具的堆积

如果你花点时间来体验游戏,你会注意到道具不断从相同的高度处下落,有时甚至低于猴子的当前位置。事实上,经过一段时间后,道具甚至无法下落,因为它们都堆积在生成点上。

改变GameLayer.mm更新方法中的下列代码来解决这个问题:

float yPos = 400;

将上述代码换成以下代码:

float yPos = 400 + mY;

现在,无论猴子位于什么地方,道具都会在离猴子头顶400pt的位置生成。

回到猴子上。你已经注意到猴子在空中时依然可以保持不断跳跃。这是不合理的。我们需要进行修正,猴子只有在接触到地面的时候才能够跳跃。

让我们从计算猴子与地面的接触数量开始。

给Monkey.h添加新变量:

int numFloorContacts;     // number of floor contacts

转换到Monkey.mm,将下列两个新碰撞检测处理器添加到文件末端(游戏邦注:@end标记前):

-(void) beginContactWithFloor:(GB2Contact*)contact
{
numFloorContacts++;
}

-(void) endContactWithFloor:(GB2Contact*)contact
{
numFloorContacts–;
}

正如其名称所表示的那样,第1个检测猴子与地面接触/碰撞的起点,第2个检测碰撞重点。在beginContact选择器中,我们增加了地面接触变量的数值,然后降低其在endContact选择器中的数值。

在猴子与地面每次接触的开始或结束,GBox2D会调用这些选择器。记住:我们之前为地面制作了独立的类,这样GBox2D现在就能够调用带有类名称的恰当选择器。

现在,如果猴子站在地面上,那么numFloorContacts值应当至少是1。将这个代码囊括到Monkey.mm的跳跃方法中,添加if条件,查看猴子在跳跃前是否真正站在地面上:

-(void) jump
{
if(numFloorContacts > 0)
{
[self applyLinearImpulse:b2Vec2(0,[self mass]*JUMP_IMPULSE)
point:[self worldCenter]];


}
}

编译运行,效果看起来很不错。只是,当猴子站在物体上时,它失去了跳跃的能力。要解决这个问题,我们需要将猴子与物体的接触视为同地面的接触。

这执行起来很容易。只需要在Monkey.mm末端添加更多的碰撞处理例行程序,将物体接触视为地面接触来计算即可:

-(void) beginContactWithObject:(GB2Contact*)contact
{
// count object contacts as floor contacts
numFloorContacts++;
}

-(void) endContactWithObject:(GB2Contact*)contact
{
// count object contacts as floor contacts
numFloorContacts–;
}

编译运行,现在游戏看起来很棒,我们已经得到了可以玩的游戏!

推动道具

接下来,让我们来改善游戏玩法,让猴子可以左右推动道具,让他可以将双手放在头顶,保护自己免被下落的道具砸中。

你还记得第1部分中你是如何在猴子的左右侧添加传感器的吗?现在他们派上用场了!传感器的关键在于,在PhysicsEditor中设置“Id”参数。现在,你就要检索这个数值!

但是,在我们这么做之前,我们需要添加些许实例变量来计算左右传感器和猴子头部的接触数量。在Monkey.h中添加这些变量:

int numPushLeftContacts;
int numPushRightContacts;
int numHeadContacts;

接下来,beginContactWith和endContactWith选择器拥有我们可以用来确定猴子哪个部分与物体接触的接触参数,我们添加到PhysicsEditor的“Id”值以用户数据的形式存储在每个固定装置中。所以,将Monkey.mm现有的物体接触处理器替换为下列代码:

-(void) beginContactWithObject:(GB2Contact*)contact
{
NSString *fixtureId = (NSString *)contact.ownFixture->GetUserData();
if([fixtureId isEqualToString:@"push_left"])
{
numPushLeftContacts++;
}
else if([fixtureId isEqualToString:@"push_right"])
{
numPushRightContacts++;
}
else if([fixtureId isEqualToString:@"head"])
{
numHeadContacts++;
}
else
{
// count others as floor contacts
numFloorContacts++;
}
}

-(void) endContactWithObject:(GB2Contact*)contact
{
NSString *fixtureId = (NSString *)contact.ownFixture->GetUserData();
if([fixtureId isEqualToString:@"push_left"])
{
numPushLeftContacts–;
}
else if([fixtureId isEqualToString:@"push_right"])
{
numPushRightContacts–;
}
else if([fixtureId isEqualToString:@"head"])
{
numHeadContacts–;
}
else
{
// count others as floor contacts
numFloorContacts–;
}
}

从新代码中可以看到,你检索了Id,在这里我们将调用fixtureId,通过访问接触参数的固定装置随后通过GetUserData方法访问固定装置用户数据来实现。

现在,我们正在跟踪接触,我们可以更新猴子的动画帧来处理这些额外的事件。

以下是各种动画的对应表格:

objects(from raywenderlich)

objects(from raywenderlich)

使用上述表格,我们将Monkey.mm中updateCCFromPhysics的第6部分做如下修改:

// 6 – Update animation phase
const float standingLimit = 0.1;
NSString *frameName = nil;
if((vX > -standingLimit) && (vX < standingLimit))
{
if(numHeadContacts > 0)
{
// Standing, object above head
frameName = [NSString stringWithFormat:@"monkey/arms_up.png"];
}
else
{
// Just standing
frameName = [NSString stringWithFormat:@"monkey/idle/2.png"];
}
}
else
{
if(numFloorContacts == 0)
{
// Jumping, in air
frameName = [NSString stringWithFormat:@"monkey/jump/%@.png", dir];
}
else
{
// Determine if monkey is pushing an item
bool isPushing =  (isLeft && (numPushLeftContacts > 0))
|| (!isLeft && (numPushRightContacts > 0));

// On the floor
NSString *action = isPushing ? @”push” : @”walk”;

frameName = [NSString stringWithFormat:@"monkey/%@/%@_%d.png", action, dir, animPhase];
}
}

编译运行。简直堪称完美!现在猴子的表现正是我们想要的结果。

monkey jump(from raywenderlich)

monkey jump(from raywenderlich)

让猴子更为强壮

但是,在玩一段时间后,我觉得猴子在某些条件下应当更加强壮。现在,他还很弱小,当物体落在他头上时无法轻易脱困。让我们给这种条件下的猴子额外的力量。

以下是目前Monkey.mm中控制猴子跳跃的跳跃选择器代码:

[self applyLinearImpulse:b2Vec2(0,[self mass]*JUMP_IMPULSE)
point:[self worldCenter]];

将其替换成:

float impulseFactor = 1.0;

// if there is something above monkey’s head make the push stronger
if(numHeadContacts > 0)
{
impulseFactor = 2.5;
}
[self applyLinearImpulse:b2Vec2(0,[self mass]*JUMP_IMPULSE*impulseFactor)
point:[self worldCenter]];

这就我们的猴子来说就像是兴奋剂!现在,当有物体落在他头上时他可以使用是原来2.5倍的推力,这应当能够帮助他应对多数物体。

我们也改变下猴子需要推动物体时的行走推力。转向updateCCFromPhysics剪切第6部分的下列代码:

// Determine if monkey is pushing an item
bool isPushing =  (isLeft && (numPushLeftContacts > 0))
|| (!isLeft && (numPushRightContacts > 0));

将该代码粘贴到第4部分,修改如下所示:

// 4 – Determine direction of the monkey
bool isLeft = (direction < 0);

// Determine if monkey is pushing an item
bool isPushing =  (isLeft && (numPushLeftContacts > 0))
|| (!isLeft && (numPushRightContacts > 0));

if((isLeft && (vX > -MAX_VX)) || ((!isLeft && (vX < MAX_VX))))
{
// apply the directional impulse
float impulse = clamp(-[self mass]*direction*WALK_FACTOR,
-MAX_WALK_IMPULSE,
MAX_WALK_IMPULSE);
if(isPushing)
{
impulse *= 2.5;
}
[self applyLinearImpulse:-b2Vec2(impulse,0) point:[self worldCenter]];
}

编译运行,这样游戏看起来会好很多。但是仍然有个问题,当猴子的头部轻擦坠落的物体时,新的跳跃力量会让它跳出屏幕边界!

我们需要在updateCCFromPhysics方法中限制他的最大速度。将下列代码添加到updateCCFromPhysics方法第3部分的末端:

const float maxVelocity = 5.0;
float v = velocity.Length();
if(v > maxVelocity)
{
[self setLinearVelocity:maxVelocity/v*velocity];
}

应当注意的是,在上述代码中,我们直接修改了由Box2d引擎控制的数值,因而会影响到物理引擎的整体行为。你应当尽量避免进行此类改动。

编译运行。我比较喜欢猴子现在的行为。他反应快速,且足够强壮来推动物体,但是不会变得无法控制。

下一步计划

你可以下载本教程的所有源代码。

本部分教程已接近尾声!项目当前形势的源代码在名为5-MonkeyJumpAndRun的文件夹中。

在本系列教程的最后一部分中,我们将添加某些表现上的提升,为游戏添加HUD层次。(本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

原创粉丝点击