【问题标题】:Flickering image that running on a thread在线程上运行的闪烁图像
【发布时间】:2016-06-04 17:54:01
【问题描述】:

在我的“SpcaeShip”游戏中,我有一个循环游戏,可以更新和绘制我的所有对象。现在我想在游戏中添加一个小行星数组列表并绘制它们,但问题是如果我在循环游戏中绘制小行星数组,游戏运行速度非常慢。我的解决方案是在计时器线程上绘制小行星,游戏速度非常完美。我唯一的问题是我的小行星一直在闪烁。我需要使用 bufferStrategy 或类似的东西吗?

这是我在游戏循环中的绘制方法(在这个循环中我调用了游戏状态绘制方法):

private void tick()
{
 //update all objects
}

private void render()
    {
        //how many buffers(hold the same data as our screen) the canvas would use
        bs = display.getCanvas().getBufferStrategy();


        if(bs == null)
        {
            display.getCanvas().createBufferStrategy(3);
            return;
        }


        g = bs.getDrawGraphics();


        g.clearRect(0, 0, width, height);

        if(State.getState() != null)
        {
            State.getState().render(g);

        }   


        bs.show();
        g.dispose();

    }

public void run()
{

    init();

    int fps = 60;
    double timePerTick = 1000000000/ fps;
    double delta = 0;
    long now;
    long lastTime =System.nanoTime();
    long timer = 0;


    //game loop
    while(running)
    {
        now = System.nanoTime();
        delta += (now - lastTime) / timePerTick;
        timer += now - lastTime;
        lastTime = now;

        if(delta >= 1)
        {
            tick();
            render();
        }
    }

    stop();
}

这是我的游戏状态方法:

import java.awt.Graphics;    
import java.awt.event.ActionEvent;    
import java.awt.event.ActionListener;    
import java.util.ArrayList;         
import javax.swing.Timer;
public class GameState extends State 
{

    private SpaceShip spaceShip;
    private BgHandler bgHandler;
    private ArrayList<Meteor> meteors;

    public GameState(Handler handler)
    {
        super(handler);

        bgHandler = new BgHandler(handler.getWidth(), handler.getHeight());
        spaceShip = new SpaceShip(handler, handler.getWidth() / 2 - Things.DEFAULT_OBJECT_WIDTH / 2,
                handler.getHeight() - Things.DEFAULT_OBJECT_HEIGHT - 100);
        meteors = new ArrayList<Meteor>();

        for(int i = 0; i < 5; i++)
        {
            Meteor m = new Meteor(handler,(int) (Math.random() * (handler.getWidth() - Things.DEFAULT_OBJECT_WIDTH ))
                    , -Things.DEFAULT_OBJECT_WIDTH,
                    Things.DEFAULT_OBJECT_WIDTH,
                    Things.DEFAULT_OBJECT_HEIGHT);

            meteors.add(m);
        }

        timer.start();

    }

    Timer timer = new Timer (50, new ActionListener()
    {
        public void actionPerformed(ActionEvent e)
        {
            tickMeteor();
            renderMeteor(); 

        }
    });

    public void tickMeteor()
    {
        for(int i = 0; i < meteors.size(); i++)
        {
            Meteor m = meteors.get(i);

            if(m.getVisible())
            {
                m.tick();
            }
            else
            {
                meteors.remove(i);
            }   
        }
    }

    public void renderMeteor()
    {

        for(Meteor m: meteors)
        {
            Meteor m1 = (Meteor) m;

            if(m1.getVisible())
            {
                m1.render(handler.getGame().getGraphics());
            }
        }
    }


    @Override
    public void tick()
    {
        spaceShip.tick();
        for(int i = 0; i < meteors.size(); i++)
        {
            Meteor m = meteors.get(i);

            if(m.getVisible())
            {
                m.tick();
            }
            else
            {
                meteors.remove(i);
            }   
        }
    }

    }

    @Override
    public void render(Graphics g)
    {
        bgHandler.render(g);
        spaceShip.render(g);

    }    
}    

【问题讨论】:

  • 检查if(State.getState() != null) 生成空白帧的频率
  • 如果你使用的是 BufferStategy,那么你不需要使用 Swing Timer,你可以使用自己的 Thread,因为你不再需要担心与 EDT 同步绘画过程,因为它们是独立的进程
  • 好的,一切都应该在同一个线程中更新和绘制,并且应该在同一个过程中一起完成

标签: java bufferedimage flicker 2d-games


【解决方案1】:

这个m1.render(handler.getGame().getGraphics()) 大错特错。

流星的渲染应该在render方法中完成,并使用与其他元素相同的Graphics上下文,所以它们都在同一个pass中渲染

永远不要使用getGraphics,因为它在正常绘制周期之外提供了对Graphics 上下文的引用,这意味着在下一个绘制周期中绘制的任何内容都将被删除

老实说,我不知道您的“运行”方法要达到什么目的,但基本上,它是随心所欲的,这可能会不必要地消耗 CPU 周期,从而窃取程序其他部分的周期。

在运行循环中,sleep 绝不允许将时间分配给其他线程(或进程)。

以下示例尝试根据完成实际工作所用的时间量来保持每秒 60 个滴答的恒定速度更新周期。

public void run() {

    int fps = 60;
    long nanosInSecond = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS);
    long timePerTick = (long)(nanosInSecond / (double)fps);

    //game loop
    while (true) {
        long now = System.nanoTime();

        try {
            // This is simulating the update and rendering process
            Thread.sleep(5);
        } catch (InterruptedException ex) {
        }
        long delta = System.nanoTime() - now;
        long timer = timePerTick - delta;
        timer = TimeUnit.MILLISECONDS.convert(timer, TimeUnit.NANOSECONDS);
        if (timer > 0) {
            try {
                System.out.println("sleep for " + timer + " milliseconds");
                Thread.sleep(timer);
            } catch (InterruptedException ex) {
            }
        }
    }

}

【讨论】:

  • 好的,我现在在渲染方法中使用相同的图形上下文,流星不再闪烁,但游戏运行非常缓慢,因为我需要渲染流星数组.有什么办法可以解决这个问题?
  • 通常,您会在更新之间使用可变延迟来保持恒定的帧速率,因此如果您想要 60fps,则需要每 16 毫秒更新和渲染一帧,但如果更新/渲染阶段需要10ms,你只需要“睡眠”6ms。由于您没有提供实际执行此操作的“游戏循环”代码,因此很难说您应该做什么
  • 一堆脱离上下文的代码 sn-ps 并没有真正的帮助,也许考虑提供一个 runnable example 来证明你的问题。这不是代码转储,而是您正在做的事情的一个示例,它突出了您遇到的问题。这将减少混乱并获得更好的响应。
  • 抱歉,我有点难以理解它是如何工作的(这是我的第一个游戏)。我看到了这个 const 帧速率如何工作的教程,并将其复制到我的代码中:youtube.com/…我可以知道更新/渲染阶段需要多少时间?
  • 你得到开始渲染/更新之前的时间,你减去它完成后的时间,它代表经过的时间,你从“正常”睡眠需要的时间中减去它你有你的实际睡眠值
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-06-07
  • 2020-08-27
  • 2010-10-29
  • 2020-04-12
  • 2012-08-22
  • 2015-04-25
  • 2012-05-13
相关资源
最近更新 更多