七、使用更新处理器

更新处理器 为我们提供了一种在每次引擎更新时运行特定部分代码的方法。一些游戏引擎有一个内置的主循环,但是有了 AndEngine,我们可以轻松地创建任意多的循环。本章将涵盖以下食谱:

  • 更新处理器入门
  • 将更新处理器附加到实体
  • 对条件使用更新处理器
  • 处理从游戏中移除实体
  • 添加游戏计时器
  • 根据过去的时间设置实体属性

开始使用更新处理器

更新处理器本质上是我们向实体或引擎注册的代码部分,每当引擎更新场景时都会运行。在大多数情况下,每次绘制帧时都会进行这种更新,而不管实体或场景是否已被更改。更新处理器可能是运行游戏的有力手段,但是过度使用它们或在其中执行繁重的计算会导致性能不佳。这个方法将涵盖向活动添加简单更新处理器的基础知识。

做好准备...

创建一个名为UpdateHandlersActivity的新类,扩展BaseGameActivity。我们将使用这个类来创建一个基本的更新处理器。

怎么做...

按照以下步骤创建一个更新处理器,显示已发生的更新数量:

  1. 将以下定义放入我们的UpdateHandlersActivity类:

    java public static int cameraWidth = 800; public static int cameraHeight = 480; public Scene mScene;public Font fontDefault32Bold; public Text countingText; public int countingInt = 0;

  2. 接下来,将下列重写方法添加到类中。

    java @Override public EngineOptions onCreateEngineOptions() { return new EngineOptions(true, ScreenOrientation.LANDSCAPE_SENSOR, new FillResolutionPolicy(), new Camera(0, 0, cameraWidth, cameraHeight)).setWakeLockOptions( WakeLockOptions.SCREEN_ON); } @Override public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) { fontDefault32Bold = FontFactory.create( mEngine.getFontManager(), mEngine.getTextureManager(), 256, 256, Typeface.create(Typeface.DEFAULT, Typeface.BOLD), 32f, true, Color.BLACK_ARGB_PACKED_INT); fontDefault32Bold.load(); pOnCreateResourcesCallback.onCreateResourcesFinished(); } @Override public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) { mScene = new Scene(); mScene.setBackground(new Background(0.9f,0.9f,0.9f)); pOnCreateSceneCallback.onCreateSceneFinished(mScene); }

  3. 最后,将最后一个方法插入到我们的类中:

    java @Override public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) { countingText = new Text(400f, 240f, fontDefault32Bold, "0", 10, this.getVertexBufferObjectManager()); mScene.attachChild(countingText); mScene.registerUpdateHandler(new IUpdateHandler() { @Override public void onUpdate(float pSecondsElapsed) { countingInt++; countingText.setText( String.valueOf(countingInt)); } @Override public void reset() {} }); pOnPopulateSceneCallback.onPopulateSceneFinished(); }

它是如何工作的...

第一步和第二步包括创建一个简单的BaseGameActivity类。有关创建BaseGameActivity类的更多信息,请参见第 1 章中的了解生命周期食谱。但是,请注意,我们在onCreateResources()方法中创建和加载了一个Font对象。有关字体和使用它们的Text实体的更多信息,请参见第 2 章使用实体中的将文本应用到图层方法。

在第三步中,我们通过将在活动的onCreateResources()方法中创建的fontDefault32Bold字体传递给Text构造器来创建一个Text实体,countingText,并使用位置参数使其在屏幕上居中,最大字符串长度参数为10个字符。将countingText实体附加到场景后,我们注册我们的更新处理器。在我们的更新处理器的onUpdate()方法中,我们增加countingInt整数并将countingText实体的文本设置为整数。这给了我们一个直接的文本显示,在我们的游戏中有多少更新已经发生,因此有多少帧已经被绘制。

另见

  • 了解第一章中的生命周期
  • 将文本应用于图层第 2 章使用实体

将更新处理器附加到实体

除了能够用Scene对象注册更新处理器之外,我们还可以用特定的实体注册更新处理器。通过向实体注册更新处理器,仅当实体附加到引擎场景时,才会调用该处理器。这个方法通过创建一个更新处理器来演示这个过程,该处理器注册了一个初始未连接的实体,它增加了屏幕上的文本。

做好准备...

创建一个名为AttachUpdateHandlerToEntityActivity的新类,扩展BaseGameActivity并实现IOnSceneTouchListener。我们将使用此类将更新处理器附加到Entity对象,该对象将等待附加到场景,直到场景被触摸。

怎么做...

按照以下步骤创建一个活动,演示更新处理器如何依赖其父实体运行:

  1. 在我们的新活动类中插入以下定义:

    java public static int cameraWidth = 800; public static int cameraHeight = 480; public Scene mScene; public Font fontDefault32Bold; public Text countingText; public int countingInt = 0; public Entity blankEntity;

  2. 然后,在类中放置以下被覆盖的方法:

    java @Override public EngineOptions onCreateEngineOptions() { return new EngineOptions(true, ScreenOrientation.LANDSCAPE_SENSOR, new FillResolutionPolicy(), new Camera(0, 0, cameraWidth, cameraHeight)).setWakeLockOptions( WakeLockOptions.SCREEN_ON); } @Override public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) { fontDefault32Bold = FontFactory.create( mEngine.getFontManager(), mEngine.getTextureManager(), 256, 256, Typeface.create(Typeface.DEFAULT, Typeface.BOLD), 32f, true, Color.BLACK_ARGB_PACKED_INT); fontDefault32Bold.load(); pOnCreateResourcesCallback.onCreateResourcesFinished(); } @Override public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) { mScene = new Scene(); mScene.setBackground(new Background(0.9f,0.9f,0.9f)); pOnCreateSceneCallback.onCreateSceneFinished(mScene); }

  3. 接下来,将这个被覆盖的onPopulateScene()方法添加到我们的活动类:

    java @Override public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) { countingText = new Text(400f, 240f, fontDefault32Bold, "0", 10, this.getVertexBufferObjectManager()); mScene.attachChild(countingText); blankEntity = new Entity(); blankEntity.registerUpdateHandler(new IUpdateHandler() { @Override public void onUpdate(float pSecondsElapsed) { countingInt++; countingText.setText( String.valueOf(countingInt)); } @Override public void reset() {} }); mScene.setOnSceneTouchListener(this); pOnPopulateSceneCallback.onPopulateSceneFinished(); }

  4. 最后,在我们的AttachUpdateHandlerToEntityActivity类中插入以下被覆盖的方法进行竞争:

    java @Override public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) { if(pSceneTouchEvent.isActionDown() && !blankEntity.hasParent()) mScene.attachChild(blankEntity); return true; }

它是如何工作的...

第一步和第二步包括创建一个简单的BaseGameActivity类。有关创建基本游戏活动类的更多信息,请参见第 1 章、和工程游戏结构中的了解生命周期食谱。但是,请注意,我们使用onCreateResources()方法创建并加载了一个Font对象。有关字体和使用字体的Text实体的更多信息,请参见第 2 章使用实体中的将文本应用到图层方法。

第三步创建一个文本实体,countingText,并将其附加到我们场景的中心。然后,我们的空白实体blankEntity通过调用Entity()构造函数创建,我们的更新处理器用它注册。请注意,在第四步的onSceneTouchEvent()方法中出现触摸之前,空白实体不会附着到场景。更新处理器的onUpdate()方法只是增加countingText实体的文本,以显示它正在运行的更新处理器。

第四步创建onSceneTouchEvent()方法,当场景被触摸时调用。在将blankEntity附加到场景之前,我们检查以确保触摸事件是向下的动作,并且我们的空白实体还没有父实体。

还有更多...

当运行这个配方时,我们可以看到,直到空白实体被附加到场景中,才调用更新处理器。这种效果类似于覆盖实体的 onManagedUpdate()方法。向实体注册更新处理器的过程对于创建有自己逻辑的敌人或者场景中在显示之前不应该被动画化的部分非常有用。向附加到Scene对象的另一个Entity对象的子Entity对象注册的更新处理器仍将处于活动状态。此外,实体的可见性不影响其注册的更新处理器是否会运行。

另见

  • 本章中的更新处理器入门。
  • 了解第一章中的生命周期
  • 理解和设计实体第二章使用实体
  • 将文本应用于图层第 2 章使用实体
  • 覆盖管理日期第 2 章与实体合作

使用带有条件的更新处理器

为了降低运行计算量大的更新处理器的性能成本,我们可以包含一个条件语句,告诉更新处理器在另一个之上运行一组特定的指令。例如,如果我们有敌人检查玩家是否在他们的视线范围内,我们可以选择让视觉计算每三次更新运行一次。在本食谱中,我们将演示一个简单的条件语句,通过触摸屏幕在性能密集型计算和非常简单的计算之间切换。

做好准备...

创建一个名为UpdateHandlersAndConditionalsActivity的新类,扩展BaseGameActivity并实现IOnSceneTouchListener。我们将使用这个类来演示如何在更新处理器中使用条件语句。

怎么做...

按照以下步骤创建更新处理器,该程序使用条件块来确定要运行的代码:

  1. 在新类中放置以下定义:

    java public static int cameraWidth = 800; public static int cameraHeight = 480; public Scene mScene; public Font fontDefault32Bold; public Text countingText; public int countingInt = 0; public boolean performanceIntensiveLoop = true; public double performanceHeavyVariable;

  2. 然后,添加以下被覆盖的方法:

    java @Override public EngineOptions onCreateEngineOptions() { return new EngineOptions(true, ScreenOrientation.LANDSCAPE_SENSOR, new FillResolutionPolicy(), new Camera(0, 0, cameraWidth, cameraHeight)).setWakeLockOptions( WakeLockOptions.SCREEN_ON); } @Override public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) { fontDefault32Bold = FontFactory.create( mEngine.getFontManager(), mEngine.getTextureManager(), 256, 256, Typeface.create(Typeface.DEFAULT, Typeface.BOLD), 32f, true, Color.BLACK_ARGB_PACKED_INT); fontDefault32Bold.load(); pOnCreateResourcesCallback.onCreateResourcesFinished(); } @Override public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) { mScene = new Scene(); mScene.setBackground(new Background(0.9f,0.9f,0.9f)); pOnCreateSceneCallback.onCreateSceneFinished(mScene); }

  3. 接下来,插入以下被覆盖的onPopulateScene()方法:

    java @Override public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) { countingText = new Text(400f, 240f, fontDefault32Bold, "0", 10, this.getVertexBufferObjectManager()); mScene.attachChild(countingText); mScene.registerUpdateHandler(new IUpdateHandler() { @Override public void onUpdate(float pSecondsElapsed) { if(performanceIntensiveLoop) { countingInt++; for(int i = 3; i < 1000000; i++) performanceHeavyVariable = Math.sqrt(i); } else { countingInt--; } countingText.setText( String.valueOf(countingInt)); } @Override public void reset() {} }); mScene.setOnSceneTouchListener(this); pOnPopulateSceneCallback.onPopulateSceneFinished(); }

  4. 最后,创建这个onSceneTouchEvent()方法来完成我们的活动:

    java @Override public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) { if(pSceneTouchEvent.isActionDown()) performanceIntensiveLoop = !performanceIntensiveLoop; return true; }

它是如何工作的...

在第一步中,我们定义了我们的测试平台共有的变量以及一个布尔performanceIntensiveLoop变量,它告诉我们的更新处理器采取什么操作,以及一个双变量performanceHeavyVariable,我们将在性能密集型计算中使用它。第二步为我们的活动创建标准方法。有关创建BaseGameActivity类的更多信息,请参见第 1 章中的了解生命周期食谱。

在第三步中,我们在向场景注册更新处理器之前创建countingText。每次更新时,它都会检查performanceIntensiveLoop布尔变量,以确定它是应该执行繁重的任务,调用Math类的 sqrt()方法近一百万次,还是简单的任务,递减countingInt变量的文本。

第四步是 onSceneTouchEvent()方法,每次触摸屏幕时切换performanceIntensiveLoop布尔变量。

另见

  • 本章中的更新处理器入门。
  • 了解第一章中的生命周期
  • 将文本应用于图层第 2 章使用实体

处理从游戏中移除实体

在更新处理器中分离实体有时会引发IndexOutOfBoundsException异常,因为实体会在引擎更新过程中被移除。为了避免这种异常,我们创建了一个Runnable参数,在所有其他更新发生后,该参数在更新线程上最后运行。在这个食谱中,我们将使用 BaseGameActivity 类的runOnUpdateThread()方法安全地从游戏中移除一个实体。

做好准备...

创建一个名为HandlingRemovalOfEntityActivity的新类,扩展BaseGameActivity。我们将使用这个类来学习如何从更新处理器中安全地移除实体。

怎么做...

按照以下步骤,我们可以看到如何在不引发异常的情况下将实体从其父实体中移除:

  1. HandlingRemovalOfEntityActivity类中插入以下定义:

    java public static int cameraWidth = 800; public static int cameraHeight = 480; public Scene mScene; public Rectangle spinningRect; public float totalElapsedSeconds = 0f;

  2. 接下来,将这些被覆盖的方法添加到类中:

    java @Override public EngineOptions onCreateEngineOptions() { return new EngineOptions(true, ScreenOrientation.LANDSCAPE_SENSOR, new FillResolutionPolicy(), new Camera(0, 0, cameraWidth, cameraHeight)).setWakeLockOptions( WakeLockOptions.SCREEN_ON); } @Override public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) { pOnCreateResourcesCallback.onCreateResourcesFinished(); } @Override public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) { mScene = new Scene(); mScene.setBackground(new Background(0.9f,0.9f,0.9f)); pOnCreateSceneCallback.onCreateSceneFinished(mScene); }

  3. 最后,在活动中放置以下onPopulateScene()方法完成:

    java @Override public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) { spinningRect = new Rectangle(400f, 240f, 100f, 20f, this.getVertexBufferObjectManager()); spinningRect.setColor(Color.BLACK); spinningRect.registerUpdateHandler(new IUpdateHandler() { @Override public void onUpdate(float pSecondsElapsed) { spinningRect.setRotation( spinningRect.getRotation()+0.4f); totalElapsedSeconds += pSecondsElapsed; if(totalElapsedSeconds > 5f) { runOnUpdateThread(new Runnable() { @Override public void run() { spinningRect.detachSelf(); }}); } } @Override public void reset() {} }); mScene.attachChild(spinningRect); pOnPopulateSceneCallback.onPopulateSceneFinished(); }

它是如何工作的...

在第一步中,我们定义了正常的BaseGameActivity变量以及一个正方形的Rectangle对象spinningRect,它将原地旋转,以及一个浮动变量totalElapsedSeconds,以记录自我们的更新处理器开始以来已经过去了多少秒。第二步创建标准的BaseGameActivity方法。有关创建和工程活动的更多信息,请参见第 1 章和工程游戏结构中的了解生命周期食谱。

在第三步中,我们通过调用位于屏幕中心的Rectangle构造函数来创建在第一步中定义的spinningRect矩形。然后通过 setColor()方法将Rectangle对象设置为黑色。接下来,它注册了我们的更新处理器,记录经过的时间,如果活动开始后超过5秒,则从屏幕上删除矩形。请注意,我们从场景中分离矩形的方法是调用runOnUpdateThread()。该方法在更新周期结束时将Runnable参数传递给要运行的发动机。

另见

  • 本章中的更新处理器入门。
  • 了解第一章中的生命周期
  • 第 2 章中对图层应用图元处理实体

添加游戏计时器

许多游戏倒计时,并要求玩家在给定的时间内完成任务。这样的挑战对玩家来说是有回报的,并且通常会增加游戏的重播价值。在前面的配方中,我们记录了总的运行时间。在这个方法中,我们将从一个时间开始,并从它减去更新处理器提供的经过时间。

做好准备...

创建一个名为GameTimerActivity的新类,扩展BaseGameActivity。我们将使用这个类从更新处理器创建一个游戏定时器。

怎么做...

按照以下步骤使用更新处理器创建游戏计时器:

  1. 将这些变量定义放到我们新的活动类中:

    java public static int cameraWidth = 800; public static int cameraHeight = 480; public Scene mScene; public Font fontDefault32Bold; public Text countingText; public float EndingTimer = 10f;

  2. 接下来,插入以下标准的、被覆盖的方法:

    java @Override public EngineOptions onCreateEngineOptions() { return new EngineOptions(true, ScreenOrientation.LANDSCAPE_SENSOR, new FillResolutionPolicy(), new Camera(0, 0, cameraWidth, cameraHeight)).setWakeLockOptions( WakeLockOptions.SCREEN_ON); } @Override public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) { fontDefault32Bold = FontFactory.create( mEngine.getFontManager(), mEngine.getTextureManager(), 256, 256, Typeface.create(Typeface.DEFAULT, Typeface.BOLD), 32f, true, Color.BLACK_ARGB_PACKED_INT); fontDefault32Bold.load(); pOnCreateResourcesCallback.onCreateResourcesFinished(); } @Override public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) { mScene = new Scene(); mScene.setBackground(new Background(0.9f,0.9f,0.9f)); pOnCreateSceneCallback.onCreateSceneFinished(mScene); }

  3. 最后,将这个被覆盖的onPopulateScene()方法添加到GameTimerActivity类:

    java @Override public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) { countingText = new Text(400f, 240f, fontDefault32Bold, "10", 10, this.getVertexBufferObjectManager()); mScene.attachChild(countingText); mScene.registerUpdateHandler(new IUpdateHandler() { @Override public void onUpdate(float pSecondsElapsed) { EndingTimer-=pSecondsElapsed; if(EndingTimer<=0) { // The timer has ended countingText.setText("0"); mScene.unregisterUpdateHandler(this); } else { countingText.setText(String.valueOf( Math.round(EndingTimer))); } } @Override public void reset() {} }); pOnPopulateSceneCallback.onPopulateSceneFinished(); }

它是如何工作的...

在第一步中,我们定义了公共BaseGameActivity变量以及设置在10秒的EndingTimer浮动变量。第二步为我们的活动创建通用方法。有关创建基本游戏活动类的更多信息,请参见第 1 章、和工程游戏结构中的了解生命周期食谱。

在第三步中,我们创建countingText实体,并使用我们的场景注册一个更新处理器,该程序使用pSecondsElapsed变量递减EndingTimer变量,直到它到达0。当它到达0时,我们只需通过调用场景的 unregisterUpdateHandler()方法从场景中注销更新处理器。在实际游戏中,计时器结束可能会结束一个关卡,甚至会召唤下一波敌人攻击玩家。

另见

  • 本章中的更新处理器入门。
  • 了解第一章中的生命周期
  • 将文本应用于图层第 2 章使用实体

根据经过的时间设置实体属性

跨设备的一致性是移动游戏开发更重要的方面之一。玩家希望游戏能够根据他们设备的屏幕进行适当的缩放,但是游戏开发的另一个重要的,也是经常被忽视的方面是基于动作和动画的时间,而不是引擎更新。在这个方法中,我们将使用更新处理器来设置实体的属性。

做好准备...

创建一个名为SettingEntityPropertiesBasedOnTimePassedActivity的新类,扩展BaseGameActivity。我们将使用这个类来演示如何使用更新处理器及时设置实体属性。

怎么做...

按照以下步骤,查看我们如何根据 udate 中经过的时间来设置实体的属性:

  1. 在活动中定义以下变量:

    java public static int cameraWidth = 800; public static int cameraHeight = 480; public Scene mScene; public Rectangle spinningRect;

  2. 然后,将这些被覆盖的方法放入类中:

    java @Override public EngineOptions onCreateEngineOptions() { return new EngineOptions(true, ScreenOrientation.LANDSCAPE_SENSOR, new FillResolutionPolicy(), new Camera(0, 0, cameraWidth, cameraHeight)).setWakeLockOptions( WakeLockOptions.SCREEN_ON); } @Override public void onCreateResources(OnCreateResourcesCallback pOnCreateResourcesCallback) { pOnCreateResourcesCallback.onCreateResourcesFinished(); } @Override public void onCreateScene(OnCreateSceneCallback pOnCreateSceneCallback) { mScene = new Scene(); mScene.setBackground(new Background(0.9f,0.9f,0.9f)); pOnCreateSceneCallback.onCreateSceneFinished(mScene); }

  3. 最后,在活动结束时插入这个 onPopulateScene()方法来完成它:

    java @Override public void onPopulateScene(Scene pScene, OnPopulateSceneCallback pOnPopulateSceneCallback) { spinningRect = new Rectangle(400f, 240f, 100f, 20f, this.getVertexBufferObjectManager()); spinningRect.setColor(Color.BLACK); spinningRect.registerUpdateHandler(new IUpdateHandler() { @Override public void onUpdate(float pSecondsElapsed) { spinningRect.setRotation( spinningRect.getRotation() + ((pSecondsElapsed*360f)/2f)); } @Override public void reset() {} }); mScene.attachChild(spinningRect); pOnPopulateSceneCallback.onPopulateSceneFinished(); }

它是如何工作的...

与本章中的其他食谱一样,我们首先创建公共BaseGameActivity变量。对于这个食谱,我们还定义了一个Rectangle物体,spinningRect,它将以每秒特定的转数旋转。有关创建和工程活动的更多信息,请参见第 1 章和工程游戏结构中的了解生命周期食谱。

在第三步中,我们通过首先创建我们的spinningRect矩形来填充onPopulateScene()方法,然后我们用它来注册我们的更新处理器。在更新处理器的onUpdate()方法中,我们通过 getRotation()方法将矩形的旋转设置为等于其当前旋转,再加上将pSecondsElapsed变量调整为设定的每秒旋转次数的计算。下图显示了我们游戏中的更新是如何不具有相等的持续时间,因此必须利用pSecondsElapsed参数而不是常数值的:

How it works...

还有更多...

我们在更新处理器的 onUpdate()方法中使用的计算将Rectangle对象设置为每秒旋转一半。如果我们将计算的(pSecondsElapsed*360f)部分乘以4,矩形将以每秒 4 转的速度旋转。对于基于时间的线性运动,只需将每秒所需的像素乘以pSecondsElap ed变量。

另见

  • 本章中的更新处理器入门。
  • 了解第一章中的生命周期