本文共 8478 字,大约阅读时间需要 28 分钟。
这是我们人机交互课的实验,老师对我们的要求如下:
参考该博客
我程序的框架是参考该博客中的程序,它已经实现了地图的绘制、用键盘控制人物的移动,所以其它的功能我是在该程序的基础上实现的。 其余的不说了,先说说原来程序的不足之处这是我很不喜欢源程序的地方,因为很多地方用的直接是数值,一方面很难让人知道该数值的含义,另一方面也使得若换不同的地图、不同屏幕像素的手机等,得重新改动,比如UpdateHero方法中,如何判断接下来是人物移动还是地图滑动,在if中用了很多数值,一开始看的话,你根本不知道这些数值代表什么!
在我的程序中,我多定义了些变量,由变量算出这些数值,这样换地图的话只要更改地图的宽高,换手机的话就什么都不用改。给出前后代码的对比。//原来的人物往下移动的处理if (mHeroScreenY >= 320) { if (mHeroIndexY >= 10 && mHeroIndexY <= 20) { mMapPosY -= HERO_STEP; } else { mHeroScreenY += HERO_STEP; }} else { mHeroScreenY += HERO_STEP;}/* * 我对人物往下移动的处理 * 先对代码做一些解释,当然,数值是来源于原来代码中的。 * 地图上下移动有个范围,地图上界即屏幕上界~地图下界即屏幕下界 * 地图最上端为屏幕最上端时,320/32=10 * 地图最下端为屏幕最下端时,(800-160)/32=20 * 但若<=20,则地图最下面会有一小部分重复往上动,走到那里会出现多个人 改成17后,就没事了。 * 这是因为有的手机像素高度比较大(大于800),所以会出现最下面地图重复移动的情况 */if (mHeroScreenY >= h * 2) { if (mHeroIndexY >= h * 2 / 32 && mHeroIndexY <= (SCENCE_HEIGHT - h) / 32) { //mMapPosY:地图的坐标,即人物不动,地图往上移动,也就是地图的Y坐标减小 mMapPosY -= HERO_STEP; } else { mHeroScreenY += HERO_STEP; }} else { // 人就在屏幕上往下移动 mHeroScreenY += HERO_STEP;}
我参考的源程序对碰撞进行了处理,但是处理考虑不全面!在控制人物移动过程中,刚开始,嗯,不错,碰撞处理挺好的。可是当你人物继续移动,直到人物在屏幕上位置不变,而地图开始移动的时候,这是若碰到物体,你会发现竟然穿过去了!!!我通过用Log.v()方法输出想要的内容,调试了好久,终于被我发现了错误。
先看看对碰撞的处理:
if (mCollision[mHeroIndexY][mHeroIndexX] == -1) { mHeroPosX = mBackHeroPosX; mHeroPosY = mBackHeroPosY; mHeroScreenY = mBackHeroScreenY; mHeroScreenX = mBackHeroScreenX; //下面是我增加的 mMapPosX = mBackmMapPosX; mMapPosY = mBackmMapPosY; isAcotrCollision = true;} else { mBackHeroPosX = mHeroPosX; mBackHeroPosY = mHeroPosY; mBackHeroScreenX = mHeroScreenX; mBackHeroScreenY = mHeroScreenY; //下面是我增加的 mBackmMapPosX = mMapPosX; mBackmMapPosY = mMapPosY; isAcotrCollision = false;}
若发生碰撞,人物在屏幕、地图中的x、y坐标进行回退,即回退到前一次的。细心的童鞋可以发现,它还缺少了一种可能。那就是当人物过了屏幕宽或高的三分之二,实际上是地图在滑动了。针对该种情形下发生碰撞,源代码中并没对地图的x,y坐标进行回退!所以导致地图在滑动的时候,即使发生碰撞,地图还在滑动,这就导致人物穿墙的问题!
原先是通过键盘控制的,若要增加触屏控制人物移动,其实很好办,因为原先的代码都已经把框架给写好了,原文中通过下面四个变量,来判断人物是往哪里移动。
/** 按键下 **/ private boolean mIskeyDown = false; /** 按键左 **/ private boolean mIskeyLeft = false; /** 按键右 **/ private boolean mIskeyRight = false; /** 按键上 **/ private boolean mIskeyUp = false;
所以在触屏的代码中,我们只要根据手指不同的位置,通过原来代码中的下面方法:
public void setKeyState(int keyCode, boolean state) { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_DOWN: mIskeyDown = state; break; case KeyEvent.KEYCODE_DPAD_UP: mIskeyUp = state; break; case KeyEvent.KEYCODE_DPAD_LEFT: mIskeyLeft = state; break; case KeyEvent.KEYCODE_DPAD_RIGHT: mIskeyRight = state; break; } mAllkeyDown = state;}
用setKeyState()将对应方向的布尔变量设置为true,人自然而然就会往对应方向移动了。
一指触控的代码是实现了onTouchEvent()方法,该方法属于Activity,所以凡是继承了Activity的类,都可以覆盖该方法。 关于该方法,可以见 怎么控制人物移动呢,我是通过获取手指的x,y坐标与人物在屏幕中的x,y坐标的差的绝对值,哪个大就在哪个方向上移动。具体见代码:public boolean onTouchEvent(MotionEvent event) { //这是两指触控实现缩放的,在这里先注释掉 //if (event.getPointerCount() == 2) { // setMultiTouch(event); //} float x = event.getX(); float y = event.getY(); float dx, dy; float mX = mAnimView.mHeroScreenX, mY = mAnimView.mHeroScreenY; switch (event.getAction()) { // 触摸结束 case MotionEvent.ACTION_UP: isFirst = true; oldRate = rate; // 手指离开屏幕,则人物不动,下面是我增加的方法,使所有方向都为false mAnimView.setKeyStateFalse(); return false; case MotionEvent.ACTION_DOWN: // mAnimView.setKeyStateFalse(); dx = x - mX; dy = y - mY; int i,j; float adx=Math.abs(dx); float ady=Math.abs(dy); if (adx >= ady) { // move from left -> right //这里为什么>35,是因为我还要用<35的那些范围用于其它的功能实现。 //后面的48,也是同样道理。 if (dx > 35.0f) { mAnimView.setKeyState(KeyEvent.KEYCODE_DPAD_RIGHT, true); } else if (dx<-35.0f){ mAnimView.setKeyState(KeyEvent.KEYCODE_DPAD_LEFT, true); } } else { // move from top -> bottom or bottom -> top if (dy > 48.0f) { mAnimView.setKeyState(KeyEvent.KEYCODE_DPAD_DOWN, true); } else if(dy<-48.0f){ mAnimView.setKeyState(KeyEvent.KEYCODE_DPAD_UP, true); } } return true; /** * MOVE是介于MotionEvent.ACTION_DOWN和MotionEvent.ACTION_UP之间的, * 所以通过MOVE来控制人物移动 如果用MotionEvent.ACTION_DOWN的话, * 则至少在我的手机上,只能每次按一下走一步,太不方便了 */ case MotionEvent.ACTION_MOVE: dx = x - mX; dy = y - mY; if (Math.abs(dx) >= Math.abs(dy)) { // move from left -> right // or right -> left if (dx > 0.0f) { mAnimView.setKeyState(KeyEvent.KEYCODE_DPAD_RIGHT, true); } else { mAnimView.setKeyState(KeyEvent.KEYCODE_DPAD_LEFT, true); } } else { // move from top -> bottom or bottom -> top if (dy > 0.0f) { mAnimView.setKeyState(KeyEvent.KEYCODE_DPAD_DOWN, true); } else { mAnimView.setKeyState(KeyEvent.KEYCODE_DPAD_UP, true); } } return true; } return super.onTouchEvent(event);}
《Android游戏编程之从零开始》里面有讲到,参考这个链接吧。我是通过上面中注释掉的setMultiTouch(event)方法来实现地图缩放的,详情的话就参加我的代码,本文最后会给出下载链接。 这里遇到一个问题,那就是当地图不断缩小时,屏幕上剩余部分会残留之前刷新的图片,所以每次缩小之前,得先清屏一下。
if (isShrink) { Paint p = new Paint(); p.setXfermode(new PorterDuffXfermode(Mode.CLEAR)); mCanvas.drawPaint(p); p.setXfermode(new PorterDuffXfermode(Mode.SRC));}//接下来可以通过mCanvas.drawColor(Color.WHITE)来改变背景颜色
我通过加速度感应器,实现了两个功能:1.控制人物移动,2.通过摇一摇换人物主角(牧场物语矿石镇的伙伴男女主人公)
参考下面两个文章:没办法, 老师要求要用两个传感器,可是发现可以用的传感器除了加速度感应,其它的实在没什么。。。所以勉强用了个光线传感器,若光线暗的话,就把背景从白色亮的变为灰色(保护眼睛)。。。真的是为了用而用。。。
两个传感器实现代码如下:
/*传感器相关变量 *private SensorManager mSensorMgr = null; *Sensor mSensor = null; *Sensor mSensor2 = null; *mSensorMgr = (SensorManager) getSystemService(SENSOR_SERVICE); *mSensor = mSensorMgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); *mSensor2 = mSensorMgr.getDefaultSensor(Sensor.TYPE_LIGHT); *mSensorMgr.registerListener(this, mSensor,SensorManager.SENSOR_DELAY_GAME); *mSensorMgr.registerListener(this, mSensor2,SensorManager。SENSOR_DELAY_GAME); */public void onSensorChanged(SensorEvent event) { // 加速度传感器 if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { mGX = event.values[0]; mGY = event.values[1]; mGZ = event.values[2]; setKeyStateFalse(); if (Math.abs(mGX) >= Math.abs(mGY) && Math.abs(mGX) >= 1.0) { if (mGX >= 0) { // 向左走 setKeyState(KeyEvent.KEYCODE_DPAD_LEFT, true); } else { // 向右走 setKeyState(KeyEvent.KEYCODE_DPAD_RIGHT, true); } } else if (Math.abs(mGX) < Math.abs(mGY) && Math.abs(mGY) >= 1.0) { // 这里之所以设置成一个大于6.0,一个小于3.0,是因为人拿手机的时候,手机基本上就是倾斜的,mGY肯定大于0. if (1.0= 7.0) { // 向下走 setKeyState(KeyEvent.KEYCODE_DPAD_DOWN, true); } } //摇一摇换主角 int minValue=19; if (Math.abs(mGX) > minValue || Math.abs(mGY) > minValue || Math.abs(mGZ) > minValue) { roleId=(roleId+1)%2; } } //光线传感器 else if(event.sensor.getType() == Sensor.TYPE_LIGHT){ float lv=event.values[0]; if(lv>10.0f){ isNight=false; } else{ isNight=true; } }}
之前在3.1触屏控制人物移动的代码中提到,选取abs|dx|>35或者abs|dy|>48,才允许人物朝相应的方向移动。之所以留出人物周围一部分的范围,是用于种植一些农产品。就是当人物在农田中时,手指触摸人物一小部分范围内,可以种上东西。对种上东西的地方再触摸一次,就收割农产品。(说简单点,其实就是画上一个图片、消除一个图片。。。)
给个图:这里用到了mMapAcotor数组,若值为-1,表示没有什么。若为0~5,对应6种不同的农产品。根据不同的索引值,在地图位置画上对应农产品的位图。在OnTouchEvent方法中增加的部分代码:
if(adx<=32.0f){ //先算出手指触摸的地方在地图中对应的数组索引 i=(-mAnimView.mMapPosX+(int)x)/mAnimView.TILE_WIDTH; j=(-mAnimView.mMapPosY+(int)y)/mAnimView.TILE_HEIGHT; if(mAnimView.mMapAcotor[j][i]==-1){ //随机一个农产品 int tmp=rand.nextInt(mAnimView.totOfProds); mAnimView.mMapAcotor[j][i]=tmp; //mAnimView.numOfProds[tmp]++; } else{ mAnimView.isHarvest=true; //起不了作用,屏幕没有显示收割。。。 //mAnimView.numOfProds[mAnimView.mMapAcotor[j][i]]--; mAnimView.mMapAcotor[j][i]=-1; }}
详细就见代码,不说了,给个截图吧