65.9K
CodeProject 正在变化。 阅读更多。
Home

iPhone 游戏框架:第一阶段教程

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.70/5 (10投票s)

2009年6月2日

CPOL

5分钟阅读

viewsIcon

57787

downloadIcon

431

本教程的目标是建立一个基本的屏幕管理系统,并使其能够立即开始编写游戏代码。

  1. 导航到 ~library/Application Support/Developer/Shared/Xcode/ 并创建以下文件夹:Project Templates/iPhone Templates/ .. 您的最终目录结构应类似于 ~library/Application Support/Developer/Shared/Xcode/Project Templates/iPhone Templates/
  2. OpenGL Screen Controller.zip 解压缩到您的 iPhone Templates 目录中。这将添加 OpenGL Screen Controller 文件夹和项目模板。您的新目录结构将如下所示:~library/Application Support/Developer/Shared/Xcode/Project Templates/iPhone Templates/OpenGL Screen Controller/(项目文件在此处)
  3. 打开 XCode,创建一个新项目,您会在左侧看到“User Templates”,其中有一个子类别“iPhone Templates”,单击它将显示您的 Screen Controller 模板。

查看代码,了解它是如何实现的。构建并运行它,如果您单击“Level Play”,它应该会过渡到一个新屏幕(游戏屏幕)。在游戏屏幕上,单击框架内的任何其他位置,它将从屏幕控制器中删除该屏幕并返回到标题屏幕。跟踪代码,了解它是如何实现的,并开始尝试。

各位,坐稳了,这将是一个相当大的教程。本教程的目标是建立一个基本的屏幕管理系统,并使其能够立即开始编写游戏代码。我想先说一句:这不是启动项目的唯一方法!在编程世界中,解决问题的方法有很多很多。有些人可能更喜欢使用 Cocos2D 来开发 iPhone,而另一些人则可能喜欢从头开始构建自己的引擎。我这样做更多是为了自己的学习过程,现在我已经学到了我能学到的东西,我想把知识分享给所有还在为我已解决的问题而挣扎的其他人。

另外,请记住,这个屏幕管理系统基本上是从 XNA 示例( http://creators.xna.com/ )中的 GameStateManagement 演示移植到 Objective-C 的,并进行了一些修改。目前使用 XNA 的任何人应该都能比较轻松地将其移植过来,因为其中很多代码对您来说应该很熟悉。

那么,现在我把这些说完了,让我们开始吧!您的第一步是前往 Jeff 的博客 iPhoneDevelopment 并获取 Updated OpenGL Xcode Project Template。

下一步是按照他的说明进行安装!安装完成后,使用他的模板加载一个新项目。您会发现一些地方有所不同,并且添加了很多内容。打开类子目录中的 GLView.m 文件,并添加以下四个方法

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
	[controller touchesBegan:touches withEvent:event InView:self  WithTimer:animationTimer];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
	[controller touchesMoved:touches withEvent:event InView:self  WithTimer:animationTimer];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
	[controller touchesEnded:touches withEvent:event InView:self  WithTimer:animationTimer];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
	[controller touchesCancelled:touches withEvent:event InView:self  WithTimer:animationTimer];
}

这将实现,当您的 iPod touch 或 iPhone 被触摸时,一个消息将被发送到 GLView。我们将捕获该消息,并将其发送到 GLViewController。好了,做完了吗?太棒了!现在进入有趣的部分。

打开您的 GLViewController.h 文件。您将要输入相当多的代码,我会在 .m 文件中解释所有内容,所以现在只需将您的 .h 文件调整为如下所示。您会发现 .m 文件被注释得非常详细,以显示每个内容是什么以及它的作用,我也会在这里添加一些额外的注释。这是 GLViewController.h 文件。

//
//	The View Controller is a service which mananges one or more GameScreen
//	instances. It maintains a stack of screens, calls their Update and Draw
//	methods at the appropriate times, and automatically routes the input to
//	the topmost active screen.
//
//	Created by XNA Development Team ( http://creators.xna.com/ ) as a
//	ScreenManager.cs GameComponent.
//
//  Adapted for iPhone Game Development by Craig Giles on 1/1/09.
//

//
//	Import statements
//
#import <UIKit/UIKit.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>

#import "Texture2D.h"
#import "InputManager.h"
#import "GameScreen.h"
#import "TitleScreen.h"

@class GLView;
@interface GLViewController : UIViewController
{
	NSMutableArray *screens;
	NSMutableArray *screensToUpdate;
	InputManager *input;
	Texture2D *blankTexture;

	bool isInitialized;
	bool traceEnabled;	

	UIView *gameView;
	CGRect viewport;
}

//
//	Properties
//
@property (nonatomic, retain) NSMutableArray *screens;
@property (nonatomic, retain) NSMutableArray *screensToUpdate;
@property (nonatomic, retain) InputManager *input;
@property (nonatomic, retain) Texture2D *blankTexture;

@property (nonatomic, readwrite) bool isInitialized;
@property (nonatomic, readwrite) bool traceEnabled;

@property (nonatomic, retain) UIView *gameView;
@property (nonatomic, readwrite) CGRect viewport;

//
//	Methods
//
- (void) setupView:(GLView*)view;
- (void) loadContent;
- (void) addScreen:(GameScreen *)screen;
- (void) removeScreen:(GameScreen *)screen;
- (void) releaseScreen:(GameScreen *)screen;
- (void) updateView:(GLView *)view WithTime:(float)deltaTime;
- (void) drawView:(GLView *)view WithTime:(float)deltaTime;
- (void) traceScreens;
- (void) fadeBackBufferToBlack:(double)alpha;

//
//	Touches events
//
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event InView:(UIView *)touchView WithTimer:(NSTimer *)timer;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event InView:(UIView *)touchView WithTimer:(NSTimer *)timer;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event InView:(UIView *)touchView WithTimer:(NSTimer *)timer;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event InView:(UIView *)touchView WithTimer:(NSTimer *)timer;

@end

仔细查看 .h 文件,了解屏幕管理器可以做什么,这是一个好主意。显然,您可以处理触摸事件(还记得我们从 GLView 连接的那些吗?),而且,通过查看这些方法,您将能够向屏幕管理器添加和删除屏幕,更新和绘制,将后缓冲淡化为黑色,以及其他一些功能。让我们看看它是如何工作的!

//
//	The View Controller is a service which mananges one or more GameScreen
//	instances. It maintains a stack of screens, calls their Update and Draw
//	methods at the appropriate times, and automatically routes the input to
//	the topmost active screen.
//
//	Created by XNA Development Team ( http://creators.xna.com/ ) as a
//	ScreenManager.cs GameComponent.
//
//  Adapted for iPhone Game Development by Craig Giles on 1/1/09.
//

//
//	Import commands from Jeff's template
//
#import "GLViewController.h"
#import "GLView.h"
#import "OpenGLCommon.h"
#import "ConstantsAndMacros.h"

//
//	This indicates weather or not the game will be played in
//	landscape or portrait mode. While in landscape mode, the
//	screen will be rotated, but also the input will be off.
//	Touch coordinates for the screen will have to be converted
//	before it reaches the input manager class.
//
const bool LANDSCAPE_MODE = NO;

//
//	The time it takes for a game screen to transition
//	These can be over written in the game screen init.
//	If there is no values in the game screen itself, these
//	will be used as the default values.
//
const float TRANSITION_ON_TIME = .70f;
const float TRANSITION_OFF_TIME = .20f;

//
//	Implementation of the Screen Manager class
//
@implementation GLViewController

//
//	Getters / Setters
//
@synthesize screens;
@synthesize screensToUpdate;
@synthesize input;
@synthesize blankTexture;

@synthesize isInitialized;
@synthesize traceEnabled;

@synthesize gameView;
@synthesize viewport;

//
//	Setup View handles setting up OpenGL's Projection Matrix and
//	enables all states needed for rendering the game to screen.
//
-(void)setupView:(GLView*)view
{
	//	Set the view to the gameView
	gameView = view;

	//	Modify the Projection matrix
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	//
	//	Orthof projection is used for 2d games. This sets the coordinates to
	//	(0, 0) at the top left corner of the screen, and as you move downward
	//	your y value will increase. As you move to the right, your x value will
	//	increase.
	//	(left, right, bottom, top, near, far)
	//
	//	If the game is going to be played in landscape mode, enable
	//	it via the bool switch at the top of the GLViewController.m file.
	if ( LANDSCAPE_MODE )
	{
		viewport = CGRectMake(0, 0, 480, 320);
		glViewport(0, 0, viewport.size.height, viewport.size.width);
		glRotatef(-90, 0, 0, 1);
        glOrthof(0, viewport.size.width, viewport.size.height, 0, -10.0, 10.0);  

	}
	else	//	Game is to be played in portrait
	{
		viewport = CGRectMake(0, 0, 320, 480);
		glViewport(0, 0, viewport.size.width, viewport.size.height);
		glOrthof(0.0, viewport.size.width, viewport.size.height, 0.0, -1.0, 1.0);	

	}

	//
	//	Setup Model view matrix
	//	Load graphics settings
	//
	glMatrixMode(GL_MODELVIEW);

	glDisable(GL_DEPTH_TEST);
	glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);	

	//	needed to draw textures using Texture2D
	glEnable(GL_TEXTURE_2D);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glEnableClientState(GL_VERTEX_ARRAY);

	//	enables alpha for transparent textures
	//	I forget where I got these commands, iDevGames.net I think
	glAlphaFunc(GL_GREATER, 0.1f);
	glEnable(GL_ALPHA_TEST);

	//
	//	Setup clear color (cornflower blue'ish)
	//	Call me crazy, but I got so used to this color developing
	//	for XNA. This is a little nod to the Microsoft Development
	//	Team :)
	//
	glLoadIdentity();
	glClearColor(.39f, 0.58f, 0.920f, 1.0f); 

	//
	//	Call the view controllers loadContent method
	//
	[self loadContent];

}

//
//	Loads all content needed to run the screen manager. This is seperated
//	from the setupView method in order to seperate what is needed from OpenGL
//	to setup the screen, and what is needed from the screen manager to set
//	up the game structure.
//
- (void)loadContent
{
	//
	//	Allocate memory for your arrays to hold the Screens "stacks."
	//	All game screens will be housed in this array for easy manipulation.
	//
	screens = [[NSMutableArray alloc] init];
	screensToUpdate = [[NSMutableArray alloc] init];

	//
	//	Allocate memory for the input manager and the blankTexture used
	//	to fade the screen in / out.
	//
	input = [[InputManager alloc] init];
	input.isLandscape = LANDSCAPE_MODE;
	blankTexture = [[Texture2D alloc] initWithImage:[UIImage imageNamed:@"blankTexture.png"]];

	for (GameScreen *screen in screens)
		[screen loadContent];

	//
	//	Once we are initialized, set the bool values to appropriate values.
	//
	isInitialized = YES;
	traceEnabled = NO;

	//
	//	Adds a title screen to the game stack. This will be taken out
	//	later, and right now is only used for debugging purposes. It
	//	will be replaced with your splash screen or game introduction
	//	screen.
	//
	TitleScreen *newScreen = [[TitleScreen alloc] init];
	[self addScreen:newScreen];
	[newScreen release];
}

//
//	When the view controller exits, we will need to clean up any memory used.
//
- (void)dealloc
{
	//
	//	setup a delete screens array and add all of the current game screens
	//	to this array. We will then cycle through all game screens, unloading
	//	their content, and releasing them from the view controller. After all
	//	is said and done, we will then remove the deleteScreens array, and
	//	continue on releasing any other memory allocated for the view controller.
	//
	NSMutableArray *deleteScreens = [[NSMutableArray alloc] initWithArray:screens];

	for (GameScreen *screen in deleteScreens)
	{
		[self removeScreen:screen];
		[self releaseScreen:screen];
	}

	[deleteScreens release];
	[screens release];
	[screensToUpdate release];
	[input release];
	[blankTexture release];

    [super dealloc];
}

//
//	If the game is going over memory, this method will be called by the device
//	warning that we are running low on memory and should release any un needed
//	items.
//
- (void)didReceiveMemoryWarning
{
	//	<<TODO: Unload any un-needed game content here>>
    [super didReceiveMemoryWarning];
}

//
//	Add a screen to the view controller
//
- (void) addScreen:(GameScreen *)screen
{
	//
	//	When adding a screen to the view controller, we will be
	//	setting some default values for the screen, and then call
	//	the screens "loadContent" method. Once everything is loaded,
	//	the view controller will retain the screen and add it to the
	//	screens array.
	//
	screen.controller = self;
	screen.viewport = self.viewport;
	screen.transitionOnTime = TRANSITION_ON_TIME;
	screen.transitionOffTime = TRANSITION_OFF_TIME;
	screen.currentScreenState = TransitionOn;
	screen.transitionPosition = 1;
	[screen loadContent];

	[screen retain];
	[screens addObject:screen];
}

//
//	Unload all game content from the screen. This in turn
//	sets a flag within the screen itself, that the content has
//	been unloaded, and in the controllers Update method, all game
//	screens that have been unloaded will be released from memory.
//
- (void) removeScreen:(GameScreen *)screen
{
	//unload any content it has stored
	[screen unloadContent];
}

//
//	Release all game screens from memory, that have had their content
//	unloaded. This will release all screens themselves, as well as remove
//	them from the screens arrays.
//
- (void) releaseScreen:(GameScreen *)screen
{
	//	remove the screen from all screen arrays
	[screens removeObject:screen];
	[screensToUpdate removeObject:screen];

	//	deallocate any memory used for the screen
	[screen release];
}

//
//	Update every screen in the screens stack, keeping track
//	of which screens are covered and which are fully active.
//	if a screen is fully active and "on top" of the stack, it
//	should receive any input.
//
- (void) updateView:(GLView *)view WithTime:(float)deltaTime
{
	//	Read the touch input
	[input update:deltaTime];

	//	make a copy of hte master screen list, to avoid confusion if
	//	the process of updating one screens adds or removes others.
	[screensToUpdate removeAllObjects];

	for(GameScreen *screen in screens)
		[screensToUpdate addObject:screen];

	bool otherScreenHasFocus = NO;
	bool coveredByOtherScreen = NO;

	//	loop as long as there are screens waiting to be updated
	while ([screensToUpdate count] > 0)
	{
		//	pop the topmost screen off the waiting list
		GameScreen *screen = [screensToUpdate objectAtIndex:([screensToUpdate count] - 1)];

		[screensToUpdate removeObjectAtIndex:[screensToUpdate count] - 1];

		//	update the screen
		[screen update:deltaTime OtherScreenHasFocus:otherScreenHasFocus
								CoveredByOtherScreen:coveredByOtherScreen];

		if ([screen currentScreenState] == TransitionOn ||
			[screen currentScreenState] == Active)
		{
			//	if this is the first active screen we came across,
			//	give it a chance to handle input.
			if (!otherScreenHasFocus)
			{
				[screen handleInput:input];
				otherScreenHasFocus = YES;
			}

			//	if this is an active non-popup, inform any subsequent
			//	screens that they are covered by it
			if (![screen isPopup])
				coveredByOtherScreen = YES;

		}
	}

	//	do we need to print the debug trace?
	if (traceEnabled)
		[self traceScreens];

	//
	//	For every screen that had their content unloaded.. release
	//	the memory used for that screen here. We do this up front
	//	to ensure that any released screen doesn't get their update
	//	or draw methods called, when there is nothing to update or draw.
	//
	for (GameScreen *screen in screens)
	{
		if (screen.hasBeenUnloaded)
		{
			[self releaseScreen:screen];
		}
	}
}

//
//	Draw the game screens from "Bottom to Top." This is done
//	in order to ensure that any pop'up screens are drawn on top
//	of the full screen below it.
//
- (void) drawView:(GLView *)view WithTime:(float)deltaTime
{
	//	Clear the screen to preset color before drawing
	glClear(GL_COLOR_BUFFER_BIT);

	//	Draw every screen in the screens array
	for (GameScreen *screen in screens)
	{
		//if the screens content has been unloaded, don't draw
		if (screen.hasBeenUnloaded)
			continue;

		[screen draw:deltaTime];
	}
}

//
//	Helper method designed to draw the screen names currently
//	in the game stack in order to see if they are being added
//	and removed correctly.
//
- (void) traceScreens
{
	//	<<TODO: Input code to draw the screen names>>
}

//
//	Helper method to draw a translecent black fullscreen sprite, used
//	for fading screens in and out, and for darkening the background
//	behind pop up screens.
//
- (void) fadeBackBufferToBlack:(double)alpha
{
	glColor4f(alpha,alpha,alpha,alpha);
	[blankTexture drawInRect:self.viewport];
	glColor4f(1, 1, 1, 1);
}

//
//	When the screen is touched by the user, the GLView will pass along a message
//	to the view controller that the screen has been touched. The view controller
//	will take the message, and pass it along to the input manager where the
//	necessary information will be stored and filtered to the game screen that
//	will handle the user input.
//
//	In order for this to work, in your GLView, you need to write the following
//	4 methods:
//	- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
//	- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
//	- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
//	- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
//
//	Those methods will call the methods below.
//
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event InView:(UIView *)touchView WithTimer:(NSTimer *)timer
{
	[input touchesBegan:touches withEvent:event InView:touchView  WithTimer:timer];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event InView:(UIView *)touchView WithTimer:(NSTimer *)timer
{
	[input touchesMoved:touches withEvent:event InView:touchView  WithTimer:timer];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event InView:(UIView *)touchView WithTimer:(NSTimer *)timer
{
	[input touchesEnded:touches withEvent:event InView:touchView  WithTimer:timer];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event InView:(UIView *)touchView WithTimer:(NSTimer *)timer
{
	[input touchesCancelled:touches withEvent:event InView:touchView  WithTimer:timer];
}

@end

正如您所看到的,这是一个相当大的类。之所以这么大,是因为我有一个糟糕的习惯,就是把所有东西都注释得一塌糊涂!我一直认为注释越多越好,因为如果您一年后回来想调整您的游戏,注释可以帮助您回忆起每个功能的作用。

GLViewController(屏幕管理器)类中的注释解释了所有内容的作用,但如果您有任何疑问,请随时在此处发布。

现在我们需要一个或两个游戏屏幕!!请记住,以下代码永远不应直接用于创建游戏屏幕。我的意思是,将其用作超类,并用您的屏幕从中继承。例如,您的 TitleScreen 应该继承自 GameScreen。明白了吗?

这是 GameScreen.h 文件

//
//	A screen is a single layer that has update and draw logic, and which
//	can be combined with other layers to build up a complex screen system
//	or menu system, or even dialog system.
//
//	Developed by XNA Development Studio ( http://creators.xna.com/ )
//	Modified for the iPhone Gameing Framework by Craig Giles on 1/1/09.
//

#import <Foundation/Foundation.h>

#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>

#import "InputManager.h"
#import "Texture2D.h"

//
//	Enum to describe the screens transition state
//
enum ScreenState {
	TransitionOn = 0,
	Active,
	TransitionOff,
	Hidden
};

//
//	Forward Declarations
//
@class GLViewController;

@interface GameScreen : NSObject
{
@private
	GLViewController *controller;
	CGRect viewport;

	bool hasBeenUnloaded;
	bool isPopup;

	float transitionOnTime;
	float transitionOffTime;
	float transitionPosition;
	float transitionAlpha;
	enum ScreenState currentScreenState;

	bool isExiting;
	bool isActive;
	bool otherScreenHasFocus;
}

@property (nonatomic, retain) GLViewController *controller;
@property (readwrite) CGRect viewport;

@property (readwrite) bool hasBeenUnloaded;
@property (readwrite) bool isPopup;

@property (readwrite) float transitionOnTime;
@property (readwrite) float transitionOffTime;
@property (readwrite) float transitionPosition;
@property (readwrite) float transitionAlpha;

@property (readwrite) enum ScreenState currentScreenState;

@property (readwrite) bool isExiting;
@property (readwrite) bool isActive;
@property (readwrite) bool otherScreenHasFocus;

- (void) loadContent;
- (void) unloadContent;

- (void) handleInput:(InputManager *)input;
- (void) update:(float)deltaTime	OtherScreenHasFocus:(bool)otherFocus	CoveredByOtherScreen:(bool)coveredByOtherScreen;
- (bool) updateTransition:(float)deltaTime	TransitionTime:(float)transition	Direction:(int)direction;
- (void) draw:(float)deltaTime;

- (void) exitScreen;

@end

这是 GameScreen.m 文件

//
//	A screen is a single layer that has update and draw logic, and which
//	can be combined with other layers to build up a complex screen system
//	or menu system, or even dialog system.
//
//	Developed by XNA Development Studio ( http://creators.xna.com/ )
//	Modified for the iPhone Gameing Framework by Craig Giles on 1/1/09.
//

#import "GameScreen.h"

@implementation GameScreen

//
// Properties
//
@synthesize controller;
@synthesize viewport;

@synthesize hasBeenUnloaded;
@synthesize isPopup;

@synthesize transitionOnTime;
@synthesize transitionOffTime;
@synthesize transitionPosition;
@synthesize transitionAlpha;

@synthesize currentScreenState;

@synthesize isExiting;
@synthesize otherScreenHasFocus;

@dynamic isActive;
- (bool) isActive
{
	return !otherScreenHasFocus &&
	(currentScreenState == TransitionOn ||
	 currentScreenState == Active);
}

//
// Constructor(s) / destructors
//
- (id) init
{
	self = [super init];
	if (self != nil)
	{
		//initializations go here
		isExiting = NO;
	}
	return self;
}

- (void) dealloc
{
	//Deallocations go here
	[super dealloc];
}

//
// Loads all content associated with the current screen
//
- (void) loadContent
{

}

//
// Unloads all content associated with the current screen
//
- (void) unloadContent
{
	//	Release the screen manager
	[controller release];

	//	inidicate that the screens content has been unloaded
	hasBeenUnloaded = YES;
}

//
// Allows the screen to perform its update logic.
//
- (void) handleInput:(InputManager *)input
{
}

//
//	Updates the base screen. Since any game screen
//	wil be inheriting from this class, the game screen will
//	call this update method. This update just helps with the
//	transition between two screens, and if a screen is
//	transitioning on and off.
//
- (void) update:(float)deltaTime	OtherScreenHasFocus:(bool)otherFocus	CoveredByOtherScreen:(bool)coveredByOtherScreen
{
	otherScreenHasFocus = otherFocus;

	if (isExiting)
	{
		//if the screen is going to die, it should transition off
		currentScreenState = TransitionOff;

		if (![self updateTransition:deltaTime TransitionTime:transitionOffTime Direction: 1])
		{
			//when transition finishes, remove the screen
			[controller removeScreen:self];
		}
	}
	else if (coveredByOtherScreen)
	{
		//if the screen is covered by another, it should transition off
		if ([self updateTransition:deltaTime TransitionTime:transitionOffTime Direction: 1])
		{
			//scren is still transitioning
			currentScreenState = TransitionOff;
		}
		else
		{
			//transition has finished
			currentScreenState = Hidden;
		}
	}
	else
	{
		if ([self updateTransition:deltaTime TransitionTime:transitionOnTime Direction: -1])
		{
			//still busy transitioning
			currentScreenState = TransitionOn;
		}
		else
		{
			//transition finished
			currentScreenState = Active;
		}
	}
}

//
//	Helper method for updating the screen transition position
//	(how much the screen has faded in / out)
//
- (bool) updateTransition:(float)deltaTime TransitionTime:(
    float)time Direction:(int)direction
{
	//	how much should we move by?
	float transitionDelta;

	if (time <= 0)
		transitionDelta = 1;
	else
		transitionDelta = deltaTime / time;

	//update the transition position
	transitionPosition += transitionDelta * direction;

	//did we reach the end of the transition?
	if (direction < 0 && transitionPosition <= 0 ||
		direction > 0 && transitionPosition >= 1)
	{
		//clamp transition position to 0, 1, or value;
		if (transitionPosition >= 1)
			transitionPosition = 1;
		else if (transitionPosition <= 0)
			transitionPosition = 0;

		return NO;
	}//end "end of transition"

	//otherwise, we are still busy transitioning
	return YES;
}

//
//	Each screen will have their own draw method. EVERY SCREEN
//	should call [super draw:deltaTime] in order to draw
//	the fade correctly when the screen manager wishes to fade
//	the screen in or out.
//
- (void) draw:(float)deltaTime
{
	[self.controller fadeBackBufferToBlack:self.transitionPosition];
}

//
// Tells the screen to go away. Unlike [controller removeScreen] which
// instantly kills the screen, this method respects the transition timings
// and will give the scren a chance to gradually transition off.
//
- (void) exitScreen
{
	if (transitionOffTime == 0)
	{
		//if the screen has zero transition time, kill it
		[controller removeScreen:self];
	}
	else
	{
		//otherwise flag that it should transition off and exit
		isExiting = YES;
	}
}

@end

好了,屏幕管理器和游戏屏幕类就完成了。但请记住——在 InputManager 构建完成之前,这不会成功编译。为什么不尝试自己弄一会儿,看看您能想出什么?我将在我的下一个更新中发布本教程的第二部分,详细介绍我如何实现我的输入管理器和两个屏幕(TitleScreen 和 PausedScreen)。

在我离开之前还有两件事:我真的很想找到一种方法将此变成一个模板供任何人使用,但我根本不知道怎么做。如果您知道如何做,请在下面的论坛上留言。

其次,如果您有任何问题、评论或俏皮话,请发布。我非常擅长阅读大家的评论,并在需要时给您回复。您可以发表评论或给我发送电子邮件,我会回复您。

编辑
我只是想让大家知道,这篇文章自从首次发布以来已经做了一些更改。我刚刚在 GLViewController 中的几个地方修改了代码。进行了以下更改

  • 在 dealloc 方法的 for 循环中添加了 [self releaseScreen:screen];
  • 在视图控制器顶部添加了 const bool LANDSCAPE_MODE = NO;,并在 setup view 方法中添加了一个 if / else 语句,以帮助设置横向视图而不是纵向视图。(第一个屏幕管理器是只为纵向视图编写的)
  • 在 loadContent 方法中添加了 input.isLandscape = LANDSCAPE_MODE;

再次感谢太多的人帮助我达到我目前的知识水平。我希望这对你们中的一些人有所帮助!祝大家编码愉快!

在此处 阅读原始博客文章

© . All rights reserved.