SurfaceView开发[捉小猪]手机游戏 (一)

先上效果图:

哈哈, 说下实现思路:
我们可以把每一个树桩, 小猪, 车厢都看成是一个Drawable, 这个Drawable里面保存了x, y坐标, 我们的SurfaceView在draw的时候, 就把这些Drawable draw出来.

那可能有的小伙伴就会问了:
1. 那小猪是怎么让它跑起来, 并且腿部还不断地在动呢?
2. 还有小猪是怎么找到出路的呢?

刚刚我们讲过小猪是Drawable, 其实我们自定义的这个Drawable就是一个帧动画, 它里面有一个Bitmap数组, 一个currentIndex(这个用来记录当前帧), 我们在子线程里面不断更新这个currentIndex, 当Drawable被调用draw的时候, 就根据currentIndex来从Bitmap数组里面取对应的bitmap出来. 刚刚还讲过Drawable里面保存了当前x, y坐标, 我们的路径动画在播放的时候, 就不断的更新里面的坐标, 另外, SurfaceView那边也不断的调用这些Drawable的draw方法, 把他们画出来, 这样小猪就可以边移动, 边播放奔跑的动画了, 哈哈.

小猪找出路的话, 我们先看看这个:

哈哈哈, 这样思路是不是清晰了好多.
其实我们的SurfaceView里面有一个Rect二维数组, 用来存放这些矩形, 小猪离开手指之后, 就开始从小猪当前所在的矩形,
用广度优先遍历, 找到一条最短的路径(比如: [5,5 5,4 5,3 5,2 5,1 5,0]这样的), 然后再根据这条路径在Rect数组中找到对应的矩形, 最后根据这些对应的矩形的坐标来确定出Path.
哈哈, 有了Path小猪就可以跑了.

下面我们先来看看那个自定义的Drawable怎么写 (下面的那个ThreadPool类就是我们自己封装的一个单例的线程池):

/**
 * 自定义的Drawable,类似于AnimationDrawable
 */
public class MyDrawable extends Drawable implements Cloneable {

    private final int mDelay;//帧延时
    private final byte[] mLock;//控制线程暂停的锁
    private Semaphore mSemaphore;//来用控制线程更新问题
    private Bitmap[] mBitmaps;//帧
    private Paint mPaint;
    private int mCurrentIndex;//当前帧索引
    private float x, y;//当前坐标
    private Future mTask;//帧动画播放的任务
    private volatile boolean isPaused;//已暂停

    public MyDrawable(int delay, Bitmap... bitmaps) {
        mSemaphore = new Semaphore(1);
        mBitmaps = bitmaps;
        mDelay = delay;
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mLock = new byte[0];
    }

    public void start() {
        stop();
        mTask = ThreadPool.getInstance().execute(() -> {
            while (true) {
                synchronized (mLock) {
                    while (isPaused) {
                        try {
                            mLock.wait();
                        } catch (InterruptedException e) {
                            return;
                        }
                    }
                }
                try {
                    Thread.sleep(mDelay);
                } catch (InterruptedException e) {
                    return;
                }
                try {
                    mSemaphore.acquire();
                } catch (InterruptedException e) {
                    return;
                }
                mCurrentIndex++;
                if (mCurrentIndex == mBitmaps.length) {
                    mCurrentIndex = 0;
                }
                mSemaphore.release();
            }
        });
    }

    void pause() {
        isPaused = true;
    }

    void resume() {
        isPaused = false;
        synchronized (mLock) {
            mLock.notifyAll();
        }
    }

    private void stop() {
        if (mTask != null) {
            mTask.cancel(true);
            mTask = null;
            mCurrentIndex = 0;
        }
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        try {
            mSemaphore.acquire();
        } catch (InterruptedException e) {
            return;
        }
        canvas.drawBitmap(mBitmaps[mCurrentIndex], x, y, mPaint);
        mSemaphore.release();
    }

    public void release() {
        stop();
        if (mBitmaps != null) {
            for (Bitmap bitmap : mBitmaps) {
                if (bitmap != null && !bitmap.isRecycled()) {
                    bitmap.recycle();
                }
            }
        }
        mBitmaps = null;
        mPaint = null;
        mTask = null;
    }

    public float getX() {
        return x;
    }

    public void setX(float x) {
        this.x = x;
    }

    public float getY() {
        return y;
    }

    public void setY(float y) {
        this.y = y;
    }

    public Bitmap getBitmap() {
        Bitmap result = null;
        if (mBitmaps != null && mBitmaps.length > 0) {
            result = mBitmaps[0];
        }
        return result;
    }

    @Override
    public int getIntrinsicWidth() {
        if (mBitmaps.length == 0) {
            return 0;
        }
        return mBitmaps[0].getWidth();
    }

    @Override
    public int getIntrinsicHeight() {
        if (mBitmaps.length == 0) {
            return 0;
        }
        return mBitmaps[0].getHeight();
    }

    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        mPaint.setColorFilter(cf);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    @SuppressWarnings("MethodDoesntCallSuperMethod")
    public MyDrawable clone() {
        return new MyDrawable(0, mBitmaps[0]);
    }
}

start方法大概就是开启一个子线程, 每次指定延时过后就更新currentIndex, currentIndex超出范围就置0, 这样就可以一直循环播放下去了, 哈哈.
mSemaphore是当执行draw的时候, 用来锁定currentIndex不让更新的.

好了, 现在有了Drawable, 我们再来看看Path是怎么播放的:
我们可以先获取到Path上面的点, 有了这些点接下来就非常简单了.
获取Path上面的点坐标的方法大家应该也很熟悉了吧, 代码就不贴出来了,5.0及以上系统用Path的approximate方法, 5.0系统以下的用PathMeasure类. 具体代码在SDK里面也可以找到.
播放Path的话, 我们可以自定义一个PathAnimation:
其实我们自定义的这个PathAnimation播放Path的逻辑也非常简单:当start方法执行的时候,记录一下开始时间,然后一个while循环,条件就是: 当前时间 - 开始时间 < 动画时长, 然后根据当前动画已经播放的时长和总动画时长计算出当前动画的播放进度, 然后我们就可以用这个progress来获取Path上对应的点,看看完整的代码:

public class PathAnimation {

    private Keyframes mPathKeyframes;//关键帧
    private long mAnimationDuration;//动画时长
    private OnAnimationUpdateListener mOnAnimationUpdateListener;//动画更新监听
    private AnimationListener mAnimationListener;//动画事件监听
    private volatile boolean isAnimationRepeat, //反复播放的动画
            isAnimationStopped,//已停止
            isAnimationCanceled, //已取消 (停止和取消的区别: 取消是在动画播放完之前主动取消的,  停止是动画播放完,自动停止的)
            isAnimationEndListenerCalled;//动画已取消的监听已经回调

    PathAnimation(MyPath path) {
        updatePath(path);
    }

    public PathAnimation setDuration(long duration) {
        mAnimationDuration = duration;
        return this;
    }

    void updatePath(MyPath path) {
        //根据系统版本选择更合适的关键帧类
        mPathKeyframes = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? new PathKeyframes(path) : new PathKeyframesSupport(path);
    }

    OnAnimationUpdateListener getUpdateListener() {
        return mOnAnimationUpdateListener;
    }

    void setUpdateListener(OnAnimationUpdateListener listener) {
        mOnAnimationUpdateListener = listener;
    }

    /**
     * 设置动画是否重复播放
     */
    public PathAnimation setRepeat(boolean isAnimationRepeat) {
        this.isAnimationRepeat = isAnimationRepeat;
        return this;
    }

    boolean isAnimationRepeat() {
        return isAnimationRepeat;
    }

    AnimationListener getAnimationListener() {
        return mAnimationListener;
    }

    void setAnimationListener(AnimationListener listener) {
        mAnimationListener = listener;
    }

    void start() {
        if (mAnimationDuration > 0) {
            ThreadPool.getInstance().execute(() -> {
                isAnimationStopped = false;
                isAnimationCanceled = false;
                isAnimationEndListenerCalled = false;
                final long startTime = SystemClock.uptimeMillis();
                long currentPlayedDuration;//当前动画已经播放的时长
                if (mAnimationListener != null) {
                    mAnimationListener.onAnimationStart();
                }
                while ((currentPlayedDuration = SystemClock.uptimeMillis() - startTime) < mAnimationDuration) {
                    //如果动画被打断则跳出循环
                    if (isAnimationInterrupted()) {
                        break;
                    }
                    //根据当前动画已经播放的时长和总动画时长计算出当前动画的播放进度
                    float progress = (float) currentPlayedDuration / (float) mAnimationDuration;
                    if (mOnAnimationUpdateListener != null) {
                        if (!isAnimationInterrupted()) {
                            mOnAnimationUpdateListener.onUpdate(progress, mPathKeyframes.getValue(progress));
                        }
                    }
                }
                if (isAnimationRepeat && !isAnimationInterrupted()) {
                    //如果是设置了重复并且还没有被取消,则重复播放动画
                    mPathKeyframes.reverse();
                    if (mAnimationListener != null) {
                        mAnimationListener.onAnimationRepeat();
                    }
                    start();
                } else {
                    isAnimationStopped = true;
                    if (mAnimationListener != null) {
                        //判断应该回调哪一个接口
                        if (isAnimationCanceled) {
                            mAnimationListener.onAnimationCanceled();
                        } else {
                            mAnimationListener.onAnimationEnd();
                        }
                    }
                    //标记接口已回调
                    isAnimationEndListenerCalled = true;
                }
            });
        }
    }

    /**
     会阻塞,直到动画真正停止才返回
     */
    private void waitStopped() {
        isAnimationStopped = true;
        //noinspection StatementWithEmptyBody
        while (!isAnimationEndListenerCalled) {
        }
    }

    /**
     会阻塞,直到动画真正取消才返回
     */
    private void waitCancel(){
        isAnimationCanceled = true;
        //noinspection StatementWithEmptyBody
        while (!isAnimationEndListenerCalled) {
        }
    }

    void stop() {
        waitStopped();
    }

    void cancel() {
        waitCancel();
    }

    /**
     动画被打断
     */
    private boolean isAnimationInterrupted() {
        return isAnimationCanceled || isAnimationStopped;
    }

    public interface OnAnimationUpdateListener {
        void onUpdate(float currentProgress, PointF position);
    }

    public interface AnimationListener {
        void onAnimationStart();//动画开始

        void onAnimationEnd();//动画结束

        void onAnimationCanceled();//动画取消

        void onAnimationRepeat();//动画重复播放
    }
}

我们通过setUpdateListener方法来监听动画进度, OnAnimationUpdateListener接口的onUpdate方法参数还有一个PointF, 这个PointF就是根据动画当前进度从mPathKeyframes中获取到Path所对应的坐标点.

我们来写一个demo来看看这个PathAnimation的效果:

哈哈, 可以了, 是我们想要的效果.

现在动画什么的都准备好了,就差怎么把出路变成Path了,我们先来看看怎么找出路:
上面说到,屏幕上都铺满了矩形,我们可以再创建一个int类型的二维数组,用来保存这些矩形的状态(空闲:0,小猪占用:1,树桩占用:2)
我们把这个数组打印出来是这样的:

    0   0   0   0   0   0   0   0   0
      0   0   0   0   0   0   0   0   0
    0   0   0   0   0   0   0   0   0
      0   0   0   0   0   0   0   0   0
    0   0   0   0   0   0   0   0   0
      0   0   0   0   0   0   0   0   0
    0   0   0   0   0   0   0   0   0
      0   0   0   0   0   0   0   0   0
    0   0   0   0   0   0   0   0   0

我们再看看这个数组:(空闲:0,小猪占用:1,树桩占用:2)

    0   0   0   0   0   0   0   0   0
      0   0   0   0   0   0   0   0   0
    0   0   0   0   2   2   0   0   0
      0   0   0   2   1   2   0   0   0
    0   0   0   2   0   2   0   0   0
      2   0   2   0   2   0   0   0   0
    2   0   0   0   0   0   0   0   0
      0   2   2   2   2   0   0   0   0
    0   2   0   0   0   0   0   0   0

这时候就要用到一个 “广度优先遍历” ,思路就是 (逻辑有点复杂,一次看不懂看多几次就明白了):

    先传入这个状态数组和当前小猪的坐标;
    创建一个List<List<Point>>,存放出路,名字就叫做footprints吧;
    创建一个队列,这个队列存放待查找的点;
    当前小猪的坐标先放进footprints;
    当前小猪的坐标入队;
    标记小猪坐标已经走过;
    进入循环 (循环条件就是队列不为空){
        创建一个临时的footprints;(因为最多可能有6个新的路径)
        队头出队;
        寻找周围6个方向(上,下,左,右,左上,右上,左下,右下)可以到达的位置 (不包括越界的、标记过的、不是空闲的);
        遍历这个可到达位置的数组{
            遍历footprints{
                检查队头的坐标是否跟footprints的元素(List<Point>)的最后一个元素(Point)的位置(x,y)是一样的(即可以链接)
                (比如: 现在footprints是[[(5,5), (5,4)], [(6,5), (6,4)]],队头的坐标是(5,4), 那可达位置的数组就可能是[(5,3), (5,5), (4,4), (4,5), (6,4), (6,5)]){
                    则创建一个新的List<Point>;
                    add footprints的元素(比如: [(5,5), (5,4)]);
                    再add可达位置的坐标(比如: (5,3);
                    临时的footprints add 这个新的List (那临时的footprints现在就是 [[(5,5), (5,4), (5,3)]]了);
                }
            }
            检查本次可达位置的坐标是否已经是在边界 (已经找到出路){
                footprints add 临时的footprints的元素;
                遍历footprints{
                    判断footprints的元素的最后一位是否边界位置{
                        return 这个footprints的元素; (必然是最短的路径);
                    }
                }
            }
            队列入队本次可达位置的坐标;
        }
        (本次没有找到出路) footprints addAll 临时的footprints的元素,准备下一轮循环;
    }
    执行到了这里, 即表示没有出路, 如果footprints里面是空的话,我们直接返回null,如果不为空,就返回footprints最后一个元素,即能走的最长的一条路径;

好了,我们看看代码是怎么写的 (WayData等同于Point, 里面也保存有x, y坐标点):

    public static List<WayData> findWay(int[][] items, WayData currentPos) {
        //获取数组的尺寸
        int verticalCount = items.length;
        int horizontalCount = items[0].length;
        //创建队列
        Queue<WayData> way = new ArrayDeque<>();
        //出路
        List<List<WayData>> footprints = new ArrayList<>();
        //复制一个新的数组 (因为要标记状态)
        int[][] pattern = new int[verticalCount][horizontalCount];
        for (int vertical = 0; vertical < verticalCount; vertical++) {
            System.arraycopy(items[vertical], 0, pattern[vertical], 0, horizontalCount);
        }
        //当前坐标入队
        way.offer(currentPos);
        //添加进集合
        List<WayData> temp = new ArrayList<>();
        temp.add(currentPos);
        footprints.add(temp);
        //标记状态 (已走过)
        pattern[currentPos.y][currentPos.x] = STATE_WALKED;

        while (!way.isEmpty()) {
            //队头出队
            WayData header = way.poll();
            //以header为中心,获取周围可以到达的点(即未被占用,未标记过的)(这个方法在获取到可到达的点时,会标记这个点为: 已走过)
            List<WayData> directions = getCanArrivePos(pattern, header);
            //创建临时的footprints
            List<List<WayData>> footprintsTemp = new ArrayList<>();
            //遍历可到达的点
            for (int i = 0; i < directions.size(); i++) {
                WayData direction = directions.get(i);

                for (List<WayData> tmp : footprints) {
                    //检查是否可以链接
                    if (canLinks(header, tmp)) {
                        List<WayData> list = new ArrayList<>();
                        list.addAll(tmp);
                        list.add(direction);
                        footprintsTemp.add(list);
                    }
                }
                //检查是否已达到边界
                if (isEdge(verticalCount, horizontalCount, direction)) {
                    if (!footprintsTemp.isEmpty()) {
                        footprints.addAll(footprintsTemp);
                    }
                    //返回最短的出路
                    for (List<WayData> tmp : footprints) {
                        if (!tmp.isEmpty() && isEdge2(verticalCount, horizontalCount, tmp)) {
                            return tmp;
                        }
                    }
                }
                //本次未找到出路,入队这个可到达的点
                way.offer(direction);

            }
            //准备下一轮循环
            if (!footprintsTemp.isEmpty()) {
                footprints.addAll(footprintsTemp);
            }
        }
        //没有出路,返回能走的最长的一条路径;
        return footprints.isEmpty() ? null : footprints.get(footprints.size() - 1);
    }

getCanArrivePos方法:

    /**
     寻找周围6个方向可以到达的位置(不包括越界的,标记过的,不是空闲的)
     */
    public static List<WayData> getCanArrivePos(int[][] items, WayData currentPos) {
        int verticalCount = items.length;
        int horizontalCount = items[0].length;
        List<WayData> result = new ArrayList<>();
        int offset = currentPos.y % 2 == 0 ? 0 : 1, offset2 = currentPos.y % 2 == 0 ? 1 : 0;
        for (int i = 0; i < 6; i++) {
            WayData tmp = getNextPosition(currentPos, offset, offset2, i);
            if ((tmp.x > -1 && tmp.x < horizontalCount) && (tmp.y > -1 && tmp.y < verticalCount)) {
                if (items[tmp.y][tmp.x] != Item.STATE_SELECTED && items[tmp.y][tmp.x] != Item.STATE_OCCUPIED && items[tmp.y][tmp.x] != STATE_WALKED) {
                    result.add(tmp);
                    items[tmp.y][tmp.x] = STATE_WALKED;
                }
            }
        }
        //打乱它,为了让方向顺序不一样, 即每次都不同
        Collections.shuffle(result);
        return result;
    }

getNextPosition方法:

    /**
     根据当前方向获取对应的位置
     */
    private static WayData getNextPosition(WayData currentPos, int offset, int offset2, int direction) {
        WayData result = new WayData(currentPos.x, currentPos.y);
        switch (direction) {
            case 0:
                //左
                result.x -= 1;
                break;
            case 1:
                //左上
                result.x -= offset;
                result.y -= 1;
                break;
            case 2:
                //左下
                result.x -= offset;
                result.y += 1;
                break;
            case 3:
                //右
                result.x += 1;
                break;
            case 4:
                //右上
                result.x += offset2;
                result.y -= 1;
                break;
            case 5:
                //右下
                result.x += offset2;
                result.y += 1;
                break;
        }
        return result;
    }

我们执行findWay方法,就会得到这个结果:

    0   0   0   0   0   0   0   0   0
      0   0   0   0   0   0   0   0   0
    0   0   0   0   2   2   0   0   0
      0   0   0   2   1   2   0   0   0
    0   0   0   2   *   2   0   0   0
      2   0   2   *   2   0   0   0   0
    2   *   *   *   0   0   0   0   0
      *   2   2   2   2   0   0   0   0
    0   2   0   0   0   0   0   0   0

哈哈,是不是很好玩, 我们将这条出路的坐标,分别获取到对应的Rect,再根据这个Rect的坐标来连接成Path, 然后我们的小猪就可以跑啦.

本文到此结束,有错误的地方请指出,谢谢大家!

SurfaceView开发[捉小猪]手机游戏 (二)

完整代码地址: https://github.com/wuyr/CatchPiggy

游戏主页: https://wuyr.github.io/

到此这篇关于SurfaceView开发[捉小猪]手机游戏 (一)的文章就介绍到这了,更多相关SurfaceView游戏内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android实现美女拼图游戏详解

    先来看看效果: 图片切分很多份,点击交换拼成一张完整的:这样关卡也很容易设计,3 3:4 4:5 5:6 6:一直下去 加了个切换动画,效果还是不错的,其实游戏就是自定义了一个控件,下面我们开始自定义之旅. 游戏的设计 首先我们分析下如何设计这款游戏: 1.我们需要一个容器,可以放这些图片的块块,为了方便,我们准备使用RelativeLayout配合addRule实现 2.每个图片的块块,我们准备使用ImageView 3.点击交换,我们准备使用传统的TranslationAnimation来实

  • Android拼图游戏 玩转从基础到应用手势变化

    相信大家在小的时候都玩过拼图游戏,现如今,手机普及,能在手机上玩的游戏越来越多,于是乎,重温小时候,编写这个简易拼图游戏,而且也能进一步加深Android的一些基础知识. 老规矩,先是效果图: 这里我把为了演示效果,把图片打乱的很少,在代码里可以更改. 首先,有个默认的图片,可以用来拼图,也可以选择你喜欢的图片进行拼图,拼图的过程会记录移动的步数,并且当游戏胜利的时候会弹出一个笑脸提示,游戏胜利,用了多少步数. ps:感兴趣的完全可以继续在这上面进行扩展,比如增加游戏难度的选项,可以将图片分成更

  • SurfaceView开发[捉小猪]手机游戏 (二)

    我们在上一回(Android使用SurfaceView开发<捉小猪>小游戏 (一))搞懂了这个模式的基本实现思路,小猪如何找出最短的逃跑路线和如何播放路径动画. 还封装了我们自己的PathAnimation和Drawable. 还差下面树桩出现的效果: 哈哈,记得植物大战僵尸里面有个关卡的道具出现也是这种效果的. 本来做这个效果的时候,想着用一个方便快捷的方法:一个新线程中,不断遍历已出现的树桩,然后判断是否已到达目标位置,如果未到达就直接 x - - 后来发现,用这个方法存在三个问题: 1.

  • 基于Android平台实现拼图小游戏

    一.需求描述 拼图是一款益智类经典游戏了,本游戏学习了一些前辈们的经验,整体来说讲,将图片用切图工具进行切割,监听用户手指滑动事件,当用户对凌乱的图片,在一定的时间内拼凑恢复成原来的样子,则成功闯关. 根据游戏不同的关卡对图片进行动态的切割.玩家可以在随意交换任意两张图片,通过遍历切割好的每块图片,将用户选中的图片,进行替换: 其中主要的功能为: 动态对图片进行切割成所需要的份数. 玩家任意点击的两张图片能够进行正确交换. 实现交换图片的动画切换效果. 实现过关逻辑. 实现游戏时间逻辑控制. 游

  • Android实现九宫格拼图游戏

    经常有同学问到,使用Android能不能开发游戏呢?能开发那些游戏呢?由于操作系统和开发语言局限,一般开发安卓手机游戏,我们很少使用其自带语言开发.而是使用指定编译器和语言完成,能够使界面更流畅,用户体验感更好.但是对于一些常见小游戏,使用JAVA语言开发运行,还是不在话下的,那在本篇博客中,我将给大家简单介绍一下,九宫格拼图游戏的开发过程,基本逻辑和思路我将在代码的注释中体现. 九宫格拼图游戏,相信大家小时候都玩过.大概逻辑是,将1张图采用3*3的方式,分成9部分,将第3行3列的小图取出,打乱

  • Android利用ViewDragHelper轻松实现拼图游戏的示例

    前言 最近一段时间看了一些介绍ViewDragHelper的博客,感觉这是一个处理手势滑动的神奇,看完以后就想做点东西练练手,于是就做了这个Android拼图小游戏. 先上个效果图 源码 https://github.com/kevin-mob/Puzzle ViewDragHelper 其实ViewDragHelper并不是第一个用于分析手势处理的类,gesturedetector也是,但是在和拖动相关的手势分析方面gesturedetector只能说是勉为其难. 关于ViewDragHelp

  • Android实现拼图小游戏

    本文实例为大家分享了Android实现拼图小游戏的具体代码,供大家参考,具体内容如下 目标效果: 1.activity_main.xml页面: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schem

  • SurfaceView开发[捉小猪]手机游戏 (一)

    先上效果图: 哈哈, 说下实现思路: 我们可以把每一个树桩, 小猪, 车厢都看成是一个Drawable, 这个Drawable里面保存了x, y坐标, 我们的SurfaceView在draw的时候, 就把这些Drawable draw出来. 那可能有的小伙伴就会问了: 1. 那小猪是怎么让它跑起来, 并且腿部还不断地在动呢? 2. 还有小猪是怎么找到出路的呢? 刚刚我们讲过小猪是Drawable, 其实我们自定义的这个Drawable就是一个帧动画, 它里面有一个Bitmap数组, 一个curr

  • C#开发答题赢钱游戏(自动答题器)

    一.前言 现在最火的直播游戏,那就是答题赢钱直播了,如百万英雄.芝士超人.花椒直播.冲顶大会等等,这些游戏的玩法都很简单,答对12题即可瓜分奖金了.玩法虽然简单,但是要能完全答对12题难度还是挺高的,毕竟要非常广的知识面! 二.怎么做自动答题 要能自动答题,肯定是要能知道题目及答案了.题目可以从直播界面获取,方法就是OCR或者搞到数据接口了---嘿嘿,都说了简单教你,所以OCR或者分析数据接口这些复杂的操作,我肯定不教你了(我也不会啊^_%)-- 幸好,现在市面上有一些非常有技术"逼格"

  • pygame开发:马赛逻辑小游戏的代码实现

    目录 一.游戏简介 二.核心代码解析 三.pygame开发流程 1.从创建窗口到棋盘绘制 2.点击方格改变颜色 2.1.点击事件 2.2.碰撞检测 2.3.方格变色 2.4.阵列转换 2.5.效果初见 3.显示提示信息 四.游戏演示视频 一.游戏简介 马赛逻辑,是一个类似数独和扫雷的逻辑小游戏,根据棋盘周围的数据提示点亮方格,因外形像马赛克而得名.在手机游戏中有多款 APP 可以体验该游戏,如 Peak.Nonogram.Crossme 等.但在 PC 端,笔者暂时还未发现复刻版,于是打算自己动

  • Flutter开发之对角棋游戏实现实例详解

    目录 前沿 演示效果 对角棋规则 实现思路 具体实现 1. 绘制棋盘 2. 绘制棋子 3. 手势处理 4. 游戏规则 优化 总结 前沿 关于对角棋相信大家都不陌生,其凭借着规则简单又灵活多变成为我们童年不可缺少的益智游戏. 今天我将用Flutter来实现一个对角棋游戏,即巩固自己Flutter的绘制和手势知识,也希望这篇文章对大家有所帮助. 演示效果 老规矩,我们先演示下实现的最终效果: 对角棋规则 首先我们还是回顾下对角棋游戏的规则,这里借用 百度百科 的规则说明: 棋盘:象棋棋盘中,将士所在

  • HTML5开发Kinect体感游戏的实例应用

    HTML5开发Kinect体感游戏的实例应用 一.简介 我们要做的是怎样一款游戏? 在前不久成都TGC2016展会上,我们开发了一款<火影忍者手游>的体感游戏,主要模拟手游章节<九尾袭来 >,用户化身四代,与九尾进行对决,吸引了大量玩家参与. 表面上看,这款游戏与其它体感体验无异,实际上,它一直运行于浏览器Chrome下,也就是说,我们只需要掌握前端相应技术,就可以开发基于Kinect的网页体感游戏. 二.实现原理 实现思路是什么? 使用H5开发基于Kinect的体感游戏,其实工作

  • 移动开发之自适应手机屏幕宽度

    移动开发之自适应手机屏幕宽度 1.使用meta标签,这也是普遍使用的方法,理论上讲使用这个标签是可以适应所有尺寸的屏幕的,但是各设备对该标签的解释方式及支持程度不同造成了不能兼容所有浏览器或系统. 首先解释该标签的含义: <meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no&qu

  • Android开发中判断手机是否安装了QQ或者微信

    下面一段代码给大家分享了android中判断手机是否安装了qq或者微信,代码简单易懂,非常不错,具有参考借鉴价值,需要的的朋友参考下吧 public static boolean isWeixinAvilible(Context context) { final PackageManager packageManager = context.getPackageManager();// 获取packagemanager List<PackageInfo> pinfo = packageMana

  • javascript+css3开发打气球小游戏完整代码

    效果知识点: css3画气球, 自定义属性运用,随机阵列, DOM元素操作,高级回调函数与参数复传,动态布局,鼠标事件,定时器运用,CSS3新增样式等. css代码如下: <style> {margin:0;padding:0;} body{background:#434343;overflow:hidden} .balloon{ position:absolute; left:0; top:0; margin:auto; width:160px; height:160px; 圆角: 左上 右

  • Android实现手机游戏隐藏虚拟按键

    手机游戏实现Android隐藏虚拟按键,供大家参考,具体内容如下 在华为等型号的手机会有虚拟按键,在进入游戏的时候,需要全屏隐藏这个按键,并在下拉状态栏的时候,会重新呼出虚拟按键. 游戏的加载过程中,初始化检查更新,然后进入到游戏画面,实际上是android的2个view的切换. 其中MainActivity中,在onCreate()方法中添加如下函数并且复写一个方法就可以了. @Override protected void onCreate(Bundle icicle) { hideNavi

随机推荐