Android - 在画布上绘制迷宫并实现平滑的角色移动

4
我正在创建一个基于瓷砖的游戏,它使用2组布尔数组来确定需要绘制每堵墙的位置,从而绘制迷宫。
目前我已经将其全部设置好了,只绘制了5x5个迷宫区域(整个迷宫大小为30x30)。然而,当我移动角色时,整个屏幕会跳动,这是因为下一部分迷宫正在被绘制,以保持5x5的纵横比。我尝试了各种不同的方法来使其流畅运行,但似乎无法做到。
请问是否有人可以提供建议或指向一些链接/示例,以便我可以使迷宫和角色的移动都平稳地进行。以下是当前绘制迷宫的主要for循环的基本代码,以及它引用的两个布尔数组,用于绘制墙壁。
// THE CODE TO DRAW THE MAZE AND ITS WALLS
for(int i = 0; i < 5; i++) 
{
    for(int j = 0; j < 5; j++)
    {
        float x = j * totalCellWidth;
        float y = i * totalCellHeight;

        if(currentY != 0)
            indexY = i + currentY - 1;
        else
            indexY = i + currentY;

        if(currentX != 0)
            indexX = j + currentX - 1;
        else
            indexX = j + currentX;

         // Draw Verticle line (y axis)
        if (indexY < vLength && indexX < vLines[indexY].length && vLines[indexY][indexX])
        {
            RectOuterBackground.set((int)x + (int)cellWidth, (int)y, (int)x + (int)cellWidth + 15,  (int)y + (int)cellHeight + 15);
            canvas.drawBitmap(walls, null, RectOuterBackground, null);
        }
        // Draws Horizontal lines (x axis)
        if (indexY < hLength && indexX < hLines[indexY].length && hLines[indexY][indexX])
        {
            RectOuterBackground.set((int)x, (int)y + (int)cellHeight,(int)x + (int)cellWidth + 15,(int)y + (int)cellHeight + 15);
            canvas.drawBitmap(walls, null, RectOuterBackground, null);
        }
    }
}

// THE 2 BOOLEAN ARRAYS THE FORLOOP REFERENCES
boolean[][] vLines = new boolean[][]{
    {true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,true },
    {true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,true },
    {true ,true ,true ,false,false,false,true ,false,false,true ,true },
    {true ,true ,true ,false,false,true ,false,true ,true ,true ,true },
    {true ,true ,false,true ,false,false,true ,false,false,true ,true },
    {true ,true ,false,true ,true ,false,false,false,true ,true ,true },
    {true ,true ,true ,false,false,false,true ,true ,false,true ,true },
    {true ,true ,false,true ,false,false,true ,false,false,true ,true },
    {true ,true ,false,true ,true ,true ,true ,true ,false,true ,true },
    {true ,true ,false,false,false,true ,false,false,false,true ,true },
    {true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,true },
    {true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,true }
 };
boolean[][] hLines = new boolean[][]{
    {true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,true },
    {true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,true },
    {true ,true ,false,false,true ,true ,false,false,true ,false,true ,true },
    {true ,true ,false,false,true ,true ,false,true ,false,false,true ,true },
    {true ,true ,true ,true ,false,true ,true ,false,true ,true ,true ,true },
    {true ,true ,false,false,true ,false,true ,true ,false,false,true ,true },
    {true ,true ,false,true ,true ,true ,true ,false,true ,true ,true ,true },
    {true ,true ,true ,false,false,true ,false,false,true ,false,true ,true },
    {true ,true ,false,true ,false,false,false,true ,false,true ,true ,true },
    {true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,true },
    {true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,true }
};

我愿意完全重写那段代码,如果能使其流畅绘制。然而,我尝试了几种不同的变化,都不能使其平滑,总是有跳跃。我也试图使用canvas translate来实现,先绘制整个迷宫,然后缩放它,只显示5 x 5,然后简单地使用translate沿着移动缩放的部分,同时保持角色在屏幕中心,从而实现平滑移动。但是,我无法使translate平稳移动。

你愿意使用第三方游戏引擎(库),还是想仅使用Android库来解决这个问题? - GareginSargsyan
只要第三方库适合我的当前布局,我使用它没有问题,你有什么想法? - SingleWave Games
展示你的游戏循环类/方法。你是在主线程上运行还是在自己的线程上运行? - Ilya Gazman
我运行自己的线程,基本上只是在角色移动时调用invalidate(),跟踪角色位置,然后引用布尔数组绘制迷宫的相关部分。 - SingleWave Games
4个回答

4

这里是一个完整的迷宫类。它将视图绘制到坐标viewX和viewY上,可以平滑移动(如Boudjnah的答案)或按单元格尺寸的倍数移动。

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.RectF;

public class Maze {
    private RectF drawRect = new RectF();

    private Bitmap[] bitmaps;
    private int[][] tileType;

    private float screenWidth, screenHeight;

    /**
     * Initialize a new maze.
     * @param wallBitmap The desired bitmaps for the floors and walls
     * @param isWall The wall data array. Each true value in the array represents a wall and each false represents a gap
     * @param xCellCountOnScreen How many cells are visible on the screen on the x axis
     * @param yCellCountOnScreen How many cells are visible on the screen on the y axis
     * @param screenWidth The screen width
     * @param screenHeight The screen height
     */
    public Maze(Bitmap[] bitmaps, int[][] tileType, float xCellCountOnScreen, float yCellCountOnScreen, float screenWidth, float screenHeight){
        this.bitmaps = bitmaps;
        this.tileType = tileType;

        this.screenWidth = screenWidth;
        this.screenHeight = screenHeight;

        drawRect.set(0, 0, screenWidth / xCellCountOnScreen, screenHeight / yCellCountOnScreen);
    }

    /**
     * Get the type of the cell. x and y values are not coordinates!
     * @param x The x index of the cell
     * @param y The y index of the cell
     * @return The cell type
     */
    public int getType(int x, int y){
        if(y < tileType.length && x < tileType[y].length) return tileType[y][x];
        return 0;
    }

    public float getCellWidth(){ return drawRect.width(); }
    public float getCellHeight(){ return drawRect.height(); }

    /**
     * Draws the maze. View coordinates should have positive values.
     * @param canvas Canvas for the drawing
     * @param viewX The x coordinate of the view
     * @param viewY The y coordinate of the view
     */
    public void drawMaze(Canvas canvas, float viewX, float viewY){
        int tileX = 0;
        int tileY = 0;
        float xCoord = -viewX;
        float yCoord = -viewY;

        while(tileY < tileType.length && yCoord <= screenHeight){
            // Begin drawing a new column
            tileX = 0;
            xCoord = -viewX;

            while(tileX < tileType[tileY].length && xCoord <= screenWidth){
                // Check if the tile is not null
                if(bitmaps[tileType[tileY][tileX]] != null){

                    // This tile is not null, so check if it has to be drawn
                    if(xCoord + drawRect.width() >= 0 && yCoord + drawRect.height() >= 0){

                        // The tile actually visible to the user, so draw it
                        drawRect.offsetTo(xCoord, yCoord); // Move the rectangle to the coordinates
                        canvas.drawBitmap(bitmaps[tileType[tileY][tileX]], null, drawRect, null);
                    }
                }

                // Move to the next tile on the X axis
                tileX++;
                xCoord += drawRect.width();
            }

            // Move to the next tile on the Y axis
            tileY++;
            yCoord += drawRect.height();
        }
    }
}

为了使用这个类,请首先在您的onCreate函数中像这样初始化它。
// 0 == floor, 1 == wall, 2 == different looking wall
int[][] maze = {
        {0, 0, 0, 0, 0},
        {0, 1, 2, 1, 0},
        {0, 0, 0, 1, 0},
        {1, 1, 2, 1, 0},
        {0, 0, 0, 0, 0}
};

Bitmap[] bitmaps = {
        BitmapFactory.decodeResource(getResources(), R.drawable.floor),
        BitmapFactory.decodeResource(getResources(), R.drawable.firstwall)
        BitmapFactory.decodeResource(getResources(), R.drawable.secondwall)
};

// Chance the 480 and 320 to match the screen size of your device
maze = new Maze(bitmaps, maze, 5, 5, 480, 320);

然后在你的onDraw或类似函数中绘制它,代码如下:
maze.draw(canvas, viewX, viewY);

getType(int x, int y)、getCellWidth()和getCellHeight()函数将帮助您实现与迷宫的任何交互。您也可以向该类添加自己的函数。您还可以修改此类以标记任何起始和结束图块。我不会为您制作整个游戏 :)

如果您将字符设置为坐标(x, y),并将其绘制到坐标(x - viewX, y - viewY),则无论视图在哪里,您都可以对其进行固定位置。

希望这有所帮助!


我是Java和Android开发的新手,无法完全弄清楚如何将您的代码实现到我的代码中,同时保持布尔数组结构以绘制迷宫并使其平稳移动。我尝试过绘制整个迷宫并进行缩放,这很好用,但是无法使其平稳移动,因为它总是重新绘制迷宫,导致跳跃式移动。 - SingleWave Games
我编辑了我的答案,并提供了完整的迷宫类代码。将其提供墙壁位图、布尔数组(true表示有墙,false表示无墙)、屏幕上所需单元格数量(在您的情况下为5)和屏幕尺寸(可用于测试目的)。此代码假定您只想绘制墙壁。如果您希望此类还绘制地板或者您有许多类型的墙需要绘制,请告诉我。 - Finnboy11
明天早上我会尝试实现这个,关于墙和地面,是的,理想情况下我需要铺设地板,而墙可能会有一些改变,不过我将手动修改在其他地方显示的位图。 - SingleWave Games
我尝试使用Tiled,但是我无法弄清如何在不完全重写整个应用程序的情况下实现.tmx文件。 - SingleWave Games
一个快速的跟进问题,这是否意味着我可以从我的onDraw中删除所有代码?另外,我已经将您的Maze类实现到我的GameView中,如何正确调用它?因为它目前是从我的Game.class中调用的,该类从我的Maze.class中获取特定数据,例如起始位置、结束位置和迷宫详情。谢谢。 - SingleWave Games
显示剩余5条评论

3
使用LibGDX或其他Android游戏引擎,例如AndEngine(我没有使用过这个)。仅使用Android库开发游戏非常困难且容易出错(您必须编写很多样板代码,而这些游戏引擎已经为您完成了)。LibGDX可以通过以下方式简化您的任务:
  • 其图形API是基于OenGL实现的(在像您这样的简单2D游戏中,不会出现性能问题)
  • 它对基于2D瓷砖的游戏有良好的支持(https://code.google.com/p/libgdx-users/wiki/Tiles
  • 它是跨平台引擎。支持的平台包括iOS、BlackBerry、桌面(Windows、Linux和Mac)和HTML 5。

这很有用,但需要我重新编写整个游戏,这太耗时了,是否有其他方法可以解决我的问题? - SingleWave Games
@LandLPartners 只需渲染部分,不需要整个游戏。 - GareginSargsyan
好的,有道理。不过我还有几个后续问题。首先,这个算法是否仍然可以用Java编写?另外,我能否使用相同的for循环结构来处理两组布尔数组以创建迷宫,就像我的代码所示?此外,这个算法可以轻松地在Eclipse中实现吗? - SingleWave Games
@LandLPartners LibGDX是一个Java库。您可以使用它编写Java代码,该代码将在我回答中提到的所有平台上运行。您可以在Eclipse中使用它。它只是一个库(几个jar文件)!我认为这个维基包含了您所有问题的答案。使用LibGDX绘制迷宫将更容易使用瓷砖地图。 - GareginSargsyan
好的,我会在接下来的几天里阅读所有文档并尝试一下。 - SingleWave Games
我正在尝试使用我使用Tiled创建的.tmx文件,但似乎我需要重新编写整个应用程序,这对我来说不可行。我只需要简单地替换onDraw部分,你知道我可以解决问题的其他方法吗? - SingleWave Games

1

你可以考虑的一种方法是始终将角色保持在屏幕中央。不要移动你的角色,只需移动迷宫的偏移量。这种方法在许多旧的(可能也有新的)角色扮演游戏中很常见。


嗨,我已经尝试过了,并得出结论,中心的字符是必须的。然而,我似乎无法使迷宫平滑地绘制,即随着用户移动逐像素绘制。用户位置从一个瓷砖移动到另一个瓷砖。你能提供一些示例代码吗? - SingleWave Games
这里有一种方法,但实际编码的工作就交给你或其他用户吧:不要在 5 x 5 的网格上操作,而是在 7 x 7 的网格上进行。在通过滑动包含所有方格的画布平稳地将角色移动到下一个方格后,更新所有 7 个方格的新偏移量。当然,你仍然需要考虑边缘情况,这会要求用户离开屏幕中心。 - Eran Boudjnah
1
是的,那就是我想要实现的东西,但是我在编码方面遇到了困难。也许今晚我会重新开始并再试一次。 - SingleWave Games
嗨,我已经尝试实现了这个,但是无法使其正常工作。我是Java和Android开发的新手,无法弄清楚如何在invalidate()时平滑绘制它,您能否指向一些示例代码? - SingleWave Games
尝试一下这个教程以及该网站上的其他教程,它们似乎非常适合您的需求:http://obviam.net/index.php/the-android-game-loop/ - Eran Boudjnah

0

在OpenGL ES视图中绘制整个迷宫位图并调整视口/相机位置怎么样?这将为您提供在穿过迷宫时产生漂亮效果的机会,并且是绝对平滑的。


我曾考虑过这个,但这需要我重新编写整个游戏,而我的游戏已经完成了,只是缺少平滑移动角色的部分。 - SingleWave Games
并不是这样。你仍然只需要编写一个5x5的区域+合理的家庭+为了平稳导航,比如一个15x15的迷宫,以便每个迷宫都已经在显示区域附近,以实现平稳导航。由于使用位图,您不必重新绘制已经绘制过的区域。 - Peter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接