将固定的SurfaceView按垂直方向缩放以填充屏幕并保持宽高比

6

我一直在寻找答案,但似乎找不到一个(这是一种相当非传统的方法来编程安卓游戏,所以可以预料到,至少我认为是这样)。

我正在尝试在安卓系统中创建一个类似GBA风格的RPG游戏(出于怀旧目的)。OpenGL对我来说还不够好,因为在游戏循环中定制性不够强(我只想在需要绘制东西时调用draw,因为我希望这个游戏在使用电池方面非常高效,这意味着让它更像一个轮询应用程序而不是游戏)。我创建了一个自定义的SurfaceView,并通过其自己的线程进行调用,每当需要进行绘制时便会启动该线程(我按照SDK中的样例LunarLander安卓游戏进行了建模)。

问题是,我希望游戏本身是固定大小的(208x160),然后再缩放到屏幕的大小。我遇到的问题是,在扩展SurfaceView的普通参数范围内似乎没有任何方式可以做到这一点。当前状态下的游戏如下图所示。我正在尝试将其拉伸到屏幕的高度并在宽度上保持比例,同时也不会增加可视化游戏的内容(保持固定,无论屏幕大小如何)。

https://istack.dev59.com/EWIdE.webp(我本来想在行内发布实际图像。但是反垃圾措施可以咬我>.<)

目前,我正在获取SurfaceView的画布并直接绘制到它上面,并将我的大小作为硬编码像素尺寸(208px、160px)进行提供。

下面是当前线程获取画布并绘制其内容的样子。有什么更好的方法可以在不改变我希望游戏虚拟占用的大小的情况下绘制到画布上吗?

@Override
    public void run()
    {
        Canvas c = null;
        try
        {
            c = surfaceHolder.lockCanvas(null);
            synchronized (surfaceHolder)
            {
                draw(c);
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (c != null)
                surfaceHolder.unlockCanvasAndPost(c);
        }
    }

这是draw方法(StringBuilder是我自己编码的引擎对象):
public void draw(Canvas canvas)
{       
    canvas.drawColor(Color.GRAY);
    StringBuilder stringBuilder = new StringBuilder(canvas, Color.BLACK, letters);
    stringBuilder.drawString("Oh, hello there!");
    stringBuilder.setLocation(10, 20);
    stringBuilder.drawString("Why am I so small?");
}

有什么想法吗?

你有没有考虑将画布缩放以适应“SurfaceView”? - K-ballo
@K-ballo 画布来自SurfaceView本身...调用run()(线程的运行方法)创建一个画布,然后将画布设置到SurfaceHolder中,接着锁定画布,我在上面绘制,完成后解锁并发布到视图。除非我从LunarLander示例的原始代码中读错了... - TheAssailant
那么...?你不能直接调用scale吗? - K-ballo
我尝试调用scale,但那只适用于我不关心画布大小的情况。在这种情况下,画布的大小由视图设置。如果我使用match_parent然后进行缩放,它会变得更大,当然,但画布的大小不是我想要保持的大小。基本上,我只想绘制到一个固定大小的画布上,然后也许有一种方法可以将其绘制到另一个画布上,但是缩放,这可能适用于此。 - TheAssailant
实际上,您应该像绘制固定大小的画布一样绘制到画布上,但最初调用“scale”以使内容按比例缩放。 - K-ballo
太好了。我正在开发一个单词游戏,其中棋盘和字母架都是在画布上绘制的。我想要的只是缩放棋盘部分。当我剪切棋盘部分时,其他部分会变成黑色。请帮帮我。 - kamal_tech_view
2个回答

5
假设您的draw函数被重命名为drawBase。以下是如何操作:
public void draw(Canvas canvas)
{
    final float scaleFactor = Math.min( getWidth() / 208.f, getHeight() / 160.f );
    final float finalWidth = 208.f * scaleFactor;
    final float finalHeight = 160.f * scaleFactor;
    final float leftPadding = ( getWidth() - finalWidth ) / 2;
    final float topPadding =  ( getHeight() - finalHeight ) / 2;

    final int savedState = canvas.save();
    try {
        canvas.clipRect(leftPadding, topPadding, leftPadding + finalWidth, topPadding + finalHeight);

        canvas.translate(leftPadding, topPadding);
        canvas.scale(scaleFactor, scaleFactor);

        drawBase(canvas);
    } finally {
        canvas.restoreToCount(savedState);
    }
}

问题在于我正在从SurfaceView获取画布,而SurfaceView已经具有来自布局的预定大小。虽然我喜欢这段代码,但它仍然不能创建一个具有固定1.3比例和两侧有柱形填充的游戏视图。右侧仍有额外的游戏区域,这会分散玩家的注意力。 - TheAssailant
@TheAssailant:好了,已经调整了填充和裁剪的代码。 - K-ballo
谢谢!这个(几乎)完美地解决了我的问题。对于我所需的,我只需将topMargin设置为0,并使用原始提供的高度进行剪切。非常完美,非常感谢! - TheAssailant
缩放后,如何滚动SurfaceView的比例部分?感谢您的关注... - kamal_tech_view

2
K-ballo的答案很好,但自从他写下这个答案以来的2.5年里,显示器变得更加密集,这意味着软件渲染的成本越来越高。因此让硬件协助完成工作变得非常重要。
要以特定分辨率进行渲染,可以使用SurfaceHolder#setFixedSize()。这将设置Surface的大小为您指定的精确尺寸。表面将通过硬件缩放器缩放到与视图相匹配的大小,这比软件缩放更有效。该功能在这篇博客文章中有描述,并且您可以在Grafika's的“硬件缩放器练习器”活动(演示视频)中看到它的效果。
您可以通过调整 Surface 的大小(这就是 Grafika 在演示中所做的),或者通过调整视图来保持不同大小的显示器的正确纵横比。您可以在 Grafika 的 AspectFrameLayout 类中找到后者的示例,该类用于各种相机和视频演示。
关于这个问题:
OpenGL 对我来说还不够好,因为它在游戏循环中的可定制性不够(我只希望在需要绘制东西时才调用 draw ...)
您有两个选择。首先,您可以使用 GLSurfaceView#setRenderMode() 禁用连续渲染。其次,您不需要使用 GLSurfaceView 来使用 OpenGL。Grafika 中的大多数 GLES 代码都是在 SurfaceView 或 TextureView 上运行的。

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