android 开发游戏
让我们保持简单。 检查下图。
基本游戏循环 |
我们处理输入,更新内部对象的状态并呈现当前状态。 更新和渲染按逻辑分组。 他们绑在一起,往往一个接一个地执行。
Android中的任何事情都在Activity内发生。 该活动将创建一个View 。 视图是一切发生的地方。 在此进行触摸并显示生成的图像。 可以将“ 活动”想象成一张拥有一张纸的表格(“ 视图” ),使我们能够绘制一些东西。 我们将用铅笔在纸上画一些东西。 这将是我们的触碰,实际的化学React会在纸上发生,因此我们与View交互的结果将生成图像。 Activity和View也是一样 。 如下图所示:
Android游戏循环 |
让我们从项目中打开DroidzActivity.java 。 我们看到线
setContentView(R.layout.main);
这无非就是在创建活动时将默认(R)视图分配给活动。 就我们而言,它发生在启动时。
让我们创建一个我们将使用的新视图 。 View是一个简单的类,可为我们提供事件处理(如onTouch)和可绘制的可见矩形空间。 最简单的方法是扩展Android自己的SurfaceView。 我们还将实现SurfaceHolder.Callback来访问表面变化,例如在表面变化被破坏或设备方向改变时。
MainGamePanel.java
package net.obviam.droidz;
import android.content.Context;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MainGamePanel extends SurfaceView implements
SurfaceHolder.Callback {
public MainGamePanel(Context context) {
super(context);
// adding the callback (this) to the surface holder to intercept events
getHolder().addCallback(this);
// make the GamePanel focusable so it can handle events
setFocusable(true);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
@Override
protected void onDraw(Canvas canvas) {
}
}
上面的代码是一个普通类,它覆盖了我们感兴趣的方法。
除了第15和17行外,没有什么特别的。
getHolder().addCallback(this);
此行将当前类( MainGamePanel )设置为实际表面上发生的事件的处理程序。
setFocusable(true);
上一行使我们的游戏面板更具针对性,这意味着它可以获得焦点,从而可以处理事件。 我们添加了回调并将其集中在构造函数中,这样我们就不会错过。
越过的方法(第20行及以后)将全部使用,但当前将其保留为空。
让我们创建将成为我们实际游戏循环的线程。
MainThread.java
package net.obviam.droidz;
public class MainThread extends Thread {
// flag to hold game state
private boolean running;
public void setRunning(boolean running) {
this.running = running;
}
@Override
public void run() {
while (running) {
// update game state
// render state to the screen
}
}
}
如您所见,这并没有太大作用。 它会覆盖run()方法,并且在运行标志设置为true时,它将执行无限循环。
当前线程尚未实例化,因此让我们在屏幕加载时启动它。
让我们看一下修改后的MainGamePanel类。
package net.obviam.droidz;
import android.content.Context;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MainGamePanel extends SurfaceView implements
SurfaceHolder.Callback {
private MainThread thread;
public MainGamePanel(Context context) {
super(context);
getHolder().addCallback(this);
// create the game loop thread
thread = new MainThread();
setFocusable(true);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
thread.setRunning(true);
thread.start();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
// try again shutting down the thread
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
@Override
protected void onDraw(Canvas canvas) {
}
}
我们添加了以下几行:
第12行将线程声明为私有属性。
private MainThread thread;
在第19行,我们实例化线程。
thread = new MainThread();
在surfaceCreated方法中,将运行标志设置为true,然后启动线程(第30和31行)。 到此方法被称为曲面时,已经创建了曲面,可以安全地开始游戏循环。
看一下surfaceDestroyed方法。
public void surfaceDestroyed(SurfaceHolder holder) {
// tell the thread to shut down and wait for it to finish
// this is a clean shutdown
boolean retry = true;
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
// try again shutting down the thread
}
}
}
在破坏表面之前直接调用此方法。 这里不是设置运行标志的地方,但是我们放入的代码可确保线程干净关闭。 我们只是阻塞线程并等待其死亡。
如果我们现在在模拟器中运行项目,将看不到太多内容,但是我们将使用一些日志记录对其进行测试。 不用担心,因为我将在下一章中介绍日志记录。
您可以在Android网站上找到更多信息。
添加与屏幕的交互
当我们触摸屏幕下部时,我们将退出该应用程序。 如果我们在其他任何地方触摸它,我们只会记录坐标。
在MainThread类中,添加以下行:
private SurfaceHolder surfaceHolder;
private MainGamePanel gamePanel;
public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
我们声明了gamePanel和surfaceHolder变量以及一个以实例为参数的构造函数。
重要的是要同时拥有它们和不仅仅是gamePanel,因为我们在绘制时需要锁定表面,而这只能通过surfaceHolder来完成。
将int实例化线程的MainGamePanel的构造函数更改为
thread = new MainThread(getHolder(), this);
我们将当前的持有者和面板传递给它的新构造函数,以便线程可以访问它们。 我们将在游戏面板中创建游戏更新方法,并从线程中触发它,但目前仍保持原样。
将TAG常量添加到MainThread类。 每个类都有自己的String常量TAG 。 常量的值将是包含该常量的类的名称。 我们正在使用Android自己的日志记录框架,该框架具有两个参数。 firs是标签,它只是一个字符串,用于标识日志消息的来源,第二个是我们要记录的消息。 将类的名称用作标记是一个好习惯,因为这样可以轻松查找日志。
日志记录
要打开日志查看器,请转到Windows-> Show View-> Other …,然后在对话框中选择Android-> LogCat
显示视图-> LogCat |
现在,您应该看到LogCat视图。 这不过是一个控制台,您可以在其中跟踪Android的日志。 这是一个很棒的工具,因为您可以过滤包含特定文本的日志或具有特定标签的日志,这非常有用。
让我们回到我们的代码。 MainThread.java类如下所示:
package net.obviam.droidz;
import android.util.Log;
import android.view.SurfaceHolder;
public class MainThread extends Thread {
private static final String TAG = MainThread.class.getSimpleName();
private SurfaceHolder surfaceHolder;
private MainGamePanel gamePanel;
private boolean running;
public void setRunning(boolean running) {
this.running = running;
}
public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
@Override
public void run() {
long tickCount = 0L;
Log.d(TAG, "Starting game loop");
while (running) {
tickCount++;
// update game state
// render state to the screen
}
Log.d(TAG, "Game loop executed " + tickCount + " times");
}
}
在第08行中,我们定义了用于记录的标签。
在run()方法中,我们定义tickCount ,每次执行while循环(游戏循环)时,它都会增加一次。
我们记录结果。
让我们回到MainGamePanel.java类,在其中我们修改了onTouchEvent方法,以便处理屏幕上的触摸。
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (event.getY() > getHeight() - 50) {
thread.setRunning(false);
((Activity)getContext()).finish();
} else {
Log.d(TAG, "Coords: x=" + event.getX() + ",y=" + event.getY());
}
}
return super.onTouchEvent(event);
}
第02行我们检查屏幕上的事件是否为按下手势的开始( MotionEvent.ACTION_DOWN )。 如果是这样,我们检查触摸是否发生在屏幕下部。 即,手势的Y坐标在屏幕的下部50像素中。 如果是这样,我们将线程的运行状态设置为false,并在主要退出应用程序的主活动上调用finish() 。
注意:屏幕是一个矩形,其左上角坐标为(0,0),右下角坐标为( getWidth() , getHeight() )。
我还修改了DroidzActivity.java类,因此我们记录了它的生命周期。
package net.obviam.droidz;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.Window;
import android.view.WindowManager;
public class DroidzActivity extends Activity {
/** Called when the activity is first created. */
private static final String TAG = DroidzActivity.class.getSimpleName();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// requesting to turn the title OFF
requestWindowFeature(Window.FEATURE_NO_TITLE);
// making it full screen
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
// set our MainGamePanel as the View
setContentView(new MainGamePanel(this));
Log.d(TAG, "View added");
}
@Override
protected void onDestroy() {
Log.d(TAG, "Destroying...");
super.onDestroy();
}
@Override
protected void onStop() {
Log.d(TAG, "Stopping...");
super.onStop();
}
}
第20行将显示变为全屏显示。
仅重写onDestroy()和onStop()方法以记录活动的生命周期。
让我们通过右键单击项目来运行应用程序,然后选择运行方式-> Android应用程序
您应该会看到黑屏。 如果您在上半部分单击几下,然后单击模拟器屏幕的底部,则应用程序应退出。
在此阶段,值得检查日志。
LogCat |
突出显示的行是最有趣的行,就像您匹配时在代码中查找日志一样,您将确切地看到方法调用的顺序。 您还应该看到线程的while循环执行了多少次。 这是一个非常高的数字,但是下一次,我们将在介绍FPS和UPS时更加考虑周期。 那就是每秒的帧数和每秒的更新数 。 我们将创建一个游戏循环,该循环实际上会在屏幕上绘制某些内容,并且每秒将执行指定次数的次数。
我们到目前为止所做的事情:
- 创建全屏应用程序
- 有一个单独的线程控制应用程序
- 拦截基本手势,例如按下手势
- 慷慨地关闭应用程序
在此处下载源代码。
将其导入eclipse中,它应该立即起作用。
参考:来自我们的JCG合作伙伴Tamas Jano的“ Basic Game Loop ”(来自“ Against The Grain ”博客)。
- Android游戏开发教程简介
- Android游戏开发–游戏创意
- Android游戏开发–创建项目
- Android游戏开发–基本游戏架构
- Android游戏开发–使用Android显示图像
- Android游戏开发–在屏幕上移动图像
- Android游戏开发–游戏循环
- Android游戏开发–测量FPS
- Android游戏开发–雪碧动画
- Android游戏开发–粒子爆炸
- Android游戏开发–设计游戏实体–策略模式
- Android游戏开发–使用位图字体
- Android游戏开发–从Canvas切换到OpenGL ES
- Android游戏开发–使用OpenGL ES显示图形元素(原语)
- Android游戏开发– OpenGL纹理映射
- Android游戏开发–设计游戏实体–状态模式
- Android游戏文章系列
翻译自: https://www.javacodegeeks.com/2011/07/android-game-development-basic-game_05.html
android 开发游戏