callback接口:
只要继承SurfaceView类并实现SurfaceHolder.Callback接口就可以实现一个自定义的SurfaceView了,SurfaceHolder.Callback在底层的Surface状态发生变化的时候通知View,SurfaceHolder.Callback具有如下的接口:
surfaceCreated(SurfaceHolder holder):当Surface第一次创建后会立即调用该函数。程序可以在该函数中做些和绘制界面相关的初始化工作,一般情况下都是在另外的线程来绘制界面,所以不要在这个函数中绘制Surface。
surfaceChanged(SurfaceHolder holder, int format, int width,int height):当Surface的状态(大小和格式)发生变化的时候会调用该函数,在surfaceCreated调用后该函数至少会被调用一次。
SurfaceHolder 类:
它是一个用于控制surface的接口,它提供了控制surface 的大小,格式,上面的像素,即监视其改变的。
SurfaceView的getHolder()函数可以获取SurfaceHolder对象,Surface 就在SurfaceHolder对象内。虽然Surface保存了当前窗口的像素数据,但是在使用过程中是不直接和Surface打交道的,由SurfaceHolder的Canvas lockCanvas()或则Canvas lockCanvas()函数来获取Canvas对象,通过在Canvas上绘制内容来修改Surface中的数据。如果Surface不可编辑或则尚未创建调用该函数会返回null,在 unlockCanvas() 和 lockCanvas()中Surface的内容是不缓存的,所以需要完全重绘Surface的内容,为了提高效率只重绘变化的部分则可以调用lockCanvas(Rect rect)函数来指定一个rect区域,这样该区域外的内容会缓存起来。在调用lockCanvas函数获取Canvas后,SurfaceView会获取Surface的一个同步锁直到调用unlockCanvasAndPost(Canvas canvas)函数才释放该锁,这里的同步机制保证在Surface绘制过程中不会被改变(被摧毁、修改)。
// 备注2
我没有在该surfaceview的初始化函数中将其 ScreenW 与 ScreenH 进行赋值,这里要特别注意,如果你在初始化调用ScreenW = this.getWidth();和ScreenH = this.getHeight();那么你将得到很失望的值 全部为0;原因是和接口Callback接口机制有关,当我们继承callback接口会重写它的surfaceChanged()、surfaceCreated()、surfaceDestroyed(),这几个函数当surfaceCreated()被执行的时候,真正的view才被创建,也就是说之前得到的值为0 ,是因为初始化会在surfaceCreated()方法执行以前执行,view没有的时候我们去取屏幕宽高肯定是0,所以这里要注意这一点;
//备注3
这里我把draw的代码都try起来,主要是为了当画的内容中一旦抛出异常了,那么我们也能 在finally中执行该操作。这样当代码抛出异常的时候不会导致Surface出去不一致的状态。
surfaceview中确实有 onDraw这个方法,但是surfaceview不会自己去调用!!!
而我代码中的ondraw 也好 draw 也好,都是我自己定义的一个方法。。。放在线程中不断调用的,一定要注意!!
,在继承view中,因为onDraw方法是系统自动调用的,不像在surfaceview这里这样去在run里面自己去不断调用, 在view中我们可以抵用 invalidate()/postInvalidate() 这两种方法实现让系统调用onDraw方法,这里也是和surfaceview中的不同之一!
其实这就是一个简单的游戏架构了,当然还少了按键处理,声音播放等等......
剖析游戏开发用view还是sarfaceView
1: 在J2ME中我们用Display和Canvas来实现这些,而Google Android中涉及到显示的为view类,Android游戏开发中比较重要和复杂的就是显示和游戏逻辑的处理
2: SurfaceView是从View基类中派生出来的显示类,直接子类有GLSurfaceView和VideoView,可以看出GL和视频播放以及Camera摄像头一般均使用SurfaceView,到底有哪些优势呢? SurfaceView可以控制表面的格式,比如大小,显示在屏幕中的位置,最关键是的提供了SurfaceHolder类
3:对于Surface相关的,Android底层还提供了GPU加速功能,所以一般实时性很强的应用中主要使用SurfaceView而不是直接从View构建,同时后来做android 3d OpenGL中的GLSurfaceView也是从该类实现
4:SurfaceView和View最本质的区别在于,surfaceView是在一个新起的单独线程中可以重新绘制画面而View必须在UI的主线程中更新画面。
5:当使用surfaceView 由于是在新的线程中更新画面所以不会阻塞你的UI主线程。但这也带来了另外一个问题,就是事件同步。比如你触屏了一下,你需要surfaceView中thread处理,一般就需要有一个event queue的设计来保存touch event,这会稍稍复杂一点,因为涉及到线程同步。
所以基于以上,根据游戏特点,一般分成两类。
1) 被动更新画面的。比如棋类,这种用view就好了。因为画面的更新是依赖于 onTouch 来更新,可以直接使用 invalidate。 因为这种情况下,这一次Touch和下一次的Touch需要的时间比较长些,不会产生影响。
2 主动更新。比如一个人在一直跑动。这就需要一个单独的thread不停的重绘人的状态,避免阻塞main UI thread。所以显然view不合适,需要surfaceView来控制。
3.Android中的SurfaceView类就是双缓冲机制。因此,开发游戏时尽量使用SurfaceView而不要使用View,这样的话效率较高,而且SurfaceView的功能也更加完善。
考虑以上几点,所以我一直都选用 SurfaceView 来进行游戏开发。
终于做到在SurfaceView中添加组件!!!!并且相互交互数据!!!!
那么在SurfaceView 中并一同显示组件也就到底完结了,回顾下,一共分为3步,1.将我们的SurfaceView 作为一个组件view 和其他组件一同放置到布局中,当然布局的方式和显示的方式大家自己随自己喜欢定义! 2.在我们的SurfaceView中一定要使用两个构造函数的构造函数,一定!一定! 就这里有区别,别的还是该怎么处理就怎么处理,就是构造函数换了 3.交互数据,对其按键的绑定在 activity中完成,别把view绑定在咱们的SurfaceView中啊,否则报错- -、 \\\
游戏中添加音频-详解MediaPlayer与SoundPool的利弊以及各个在游戏中的用途
使用MediaPlayer来播放音频文件存在一些不足:
例如:资源占用量较高、延迟时间较长、不支持多个音频同时播放等。
这些缺点决定了MediaPlayer在某些场合的使用情况不会很理想,例如在对时间精准度要求相对较高的游戏开发中。
相对于使用SoundPool存在的一些问题:
1. SoundPool最大只能申请1M的内存空间,这就意味着我们只能使用一些很短的声音片段,而不是用它来播放歌曲或者游戏背景音乐(背景音乐可以考虑使用JetPlayer来播放)。
2. SoundPool提供了pause和stop方法,但这些方法建议最好不要轻易使用,因为有些时候它们可能会使你的程序莫名其妙的终止。还有些朋友反映它们不会立即中止播放声音,而是把缓冲区里的数据播放完才会停下来,也许会多播放一秒钟。
3. 音频格式建议使用OGG格式。使用WAV格式的音频文件存放游戏音效,经过反复测试,在音效播放间隔较短的情况下会出现异常关闭的情况(有说法是SoundPool目前只对16bit的WAV文件有较好的支持)。后来将文件转成OGG格式,问题得到了解决。
4.在使用SoundPool播放音频的时候,如果在初始化中就调用播放函数进行播放音乐那么根本没有声音,不是因为没有执行,而是SoundPool需要一准备时间!囧。当然这个准备时间也很短,不会影响使用,只是程序一运行就播放会没有声音罢了,所以我把SoundPool播放写在了按键中处理了
SoundPool最大只能申请1M的内存空间,这就意味着我们只能使用一些很短的声音片段
触屏事件中的Bug解决方案以及禁止横屏和竖屏切换
在某些游戏中我们可能需要禁止横屏和竖屏切换,其实实现这个要求很简单,只要在
AndroidManifest.xml 里面加入这一行 android :screenOrientation="landscape "(landscape 是横向,portrait 是纵向)。
在android中每次屏幕的切换动会重启Activity,所以应该在Activity销毁前保存当前活动的状态,在Activity再次Create的时候载入配置。在activity加上android:configChanges="keyboardHidden|orientation"属性,就不会重启activity.而是去调用onConfigurationChanged(Configuration newConfig). 这样就可以在这个方法里调整显示方式.
第一点:
在surfaceview中我们的onKeyDown 虽然是重写了view的函数,但是仍然需要在初始化的时候去声明获取焦点,setFocusable(true); 如果不调用此方法,那么会造成按键无效。原因是因为如果是自己定义一个继承自View的类,重新实现onKeyDown方法后,只有当该View获得焦点时才会调用onKeyDown方法,Actvity中的onKeyDown方法是当所有控件均没有处理该按键事件时,才会调用.
第二点:
也是今天主要需要讲得的触屏响应的函数,onTouchEvent()! 重写此函数的时候默认最后一句是依照基类的返回方式,return super.onTouchEvent(event); 然后我们在其中去判定 MotionEvent.ACTION_MOVE、MotionEvent.ACTION_DOWN、MotionEvent.ACTION_UP 相对应触屏操作的 拖动、按下、抬起;对此一切都是正确的,但是真正的的运行起项目的时候发现 Log.v("Himi", "ACTION_MOVE"); 这里log的"ACTION_MOVE",永远不会执行!!!为此我找到了解决方法,那么先解释下为什么会出现此类情况。