2
\$\begingroup\$

Disclaimer: I'm a bit new to stack exchange and programming so go easy :)

I'm currently working on a small text-based Oregon Trail type game and I'd like to add a typewriter effect to slow down the pacing of the game. The main class that actually runs the game implements runnable and the text is rendered using g.drawString() in the render method. I've tried rendering each character separately using a for loop and thread.sleep() but it just appeared on the screen all at once. Here is the code I used for that:

for(int i = 1; i <= events.result.length(); i++) {
    try{
        thread.sleep(millisPer);//0.5s pause between characters
    } catch(InterruptedException ex) {
        Thread.currentThread().interrupt();
    }
    g.drawString(events.result.substring(0, i), 300, 300);
}

I also tried getting the system time like this:

package mains;

import java.awt.Graphics;

public class Typewriter{
    long stallTime = 0;

    public void typeResult(Graphics g, String result)   {
        int spacing = 300;
        for(int i = 0; i < result.length(); i++) {

            if(getElapsedTime() > 1) {
                g.drawString(result.substring(i, i+1), spacing, 300);
                spacing+=5;
            }


        }
    }

    public int getElapsedTime(){
        return (int)((System.currentTimeMillis()-stallTime)/1000);

    }


    public void setStallTime(){
        stallTime=System.currentTimeMillis();
    }

}

This had the same effect. Are there any other means of adding a delay that work with my class? Thanks for the help. The full game class is below:

package mains;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;



public class Game extends Canvas implements Runnable{

    private static final long serialVersionUID = -6672699999969016018L;
    private boolean isRunning = false;
    private Thread thread;
    private GameState state;
    Window window = new Window(1000,800,"Prototype", this);
    EventManager events = new EventManager();
    BufferedImage image = ImageLoader.loadImage("/textures/Syria.png");
    Typewriter typewriter = new Typewriter(); 

    public Game() {
        this.addMouseListener(new MouseInput(this));
        this.addMouseListener(window);
        state = state.MainMenu;
        start();

    }

    public static void main(String args[]) {
        new Game();
    }

    private void start() {
        isRunning = true;
        thread = new Thread(this);
        thread.start();


    }
    private void stop()  {
        isRunning = false;
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public GameState getGameState(){
        return state;
    }

    public void setGameState(GameState state){
        this.state = state;
    }


    public void run() {
        //game loop - lifted from Zach from RealTutsGML 
        this.requestFocus();
        long lastTime = System.nanoTime();
        double amountOfTicks = 60.0;
        double ns = 100000000 / amountOfTicks;
        double delta = 0;
        long timer = System.currentTimeMillis();
        int frames = 0;
        while(isRunning) {
            long now = System.nanoTime();
            delta += (now - lastTime) / ns;
            lastTime = now;
            while(delta >= 1) {
                tick();
                //updates++;
                delta--;
            }
            render();
            frames++;

            if(System.currentTimeMillis() - timer > 1000) {
                timer += 1000;
                frames = 0;

                //updates = 0;
            }
        }
        stop();
    }


    private void tick() {
        events.tick();

        if(events.distance >= 100) {
            state = state.Escape;

        }

        if(events.getHealth() <= 0) {
            state = state.Death;
        }

    }


    private void render() {
        BufferStrategy bs = this.getBufferStrategy();
        if(bs == null) {
            this.createBufferStrategy(3);
            return;
        }

        Graphics g = bs.getDrawGraphics();

        //ANYTHING BETWEEN THIS LINE AND g.dispose() IS RENDERED

        if(state == state.MainMenu) {
            g.drawImage(image,-100,0, null);
            g.setColor(Color.red);
            g.fillRect(100, 700, 50, 50);
        }


        if(state == state.Escape) {
            g.setColor(Color.blue);
            g.fillRect(0, 0, 1000, 800);
            g.setColor(Color.white);
            g.drawString("YOU ESCAPED! :)", 500,500);

        }

        if(state == state.Death) {
            g.setColor(Color.black);
            g.fillRect(0, 0, 1000, 800);
            g.drawString("YOU DIED! :(", 500,500);

        }



        if(state == state.CharScreen) {
            g.setColor(Color.black);
            g.fillRect(0,0,1000,1000);
            g.setColor(Color.blue);
            g.fillRect(200, 700, 50, 50);
            g.setColor(Color.yellow);
            g.fillRect(700, 200, 100, 50);
            g.setColor(Color.green);
            g.fillRect(700, 300, 100, 50);
            g.setColor(Color.red);
            g.fillRect(700, 400, 100, 50);
            g.setColor(Color.orange);
            g.fillRect(700, 500, 100, 50);
            g.setColor(Color.white);
            g.drawString("Day " + events.getDays(), 300, 100);
            g.drawString("Time " + events.getHours(), 300, 200);


            typewriter.typeResult(g, events.result);
            //g.drawString(events.result, 300, 300);
            g.drawString("Distance: " + events.distance, 500, 200);
            g.drawString("Health: " + events.getHealth(), 300, 400);
            g.drawString("Sustenance: " + events.getSustenance(), 300, 500);
            g.drawString("Sanity: " + events.getSanity(), 300, 600);
            g.drawString("Exhaustion: " + events.getExhaustion(), 300, 700);

        }

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

}
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Welcome to StackExchange! You may find the answers at https://stackoverflow.com/questions/5478962/problem-with-thread-sleepx-in-a-for-loop-java helpful.

The short explanation is that Java needs to redraw the graphics on the main thread, but you've put it to sleep with thread.sleep(), so only when your loop is finished does Java regain access to the thread it needs to actually update the display.

You'll want to use a swing Timer, which handles invoking code on the main thread when each timer "tick" happens.

\$\endgroup\$
3
  • \$\begingroup\$ Sounds good. I'll go check out the Timer API. Thanks! \$\endgroup\$ Commented Apr 27, 2018 at 19:34
  • \$\begingroup\$ Update: So I've learned how to use the swing Timer and I've managed to get it working with System.out.println(); The problem is that I need to call timer.start() somewhere and I'm not sure where to put it in my code. The actual instantiation of the Timer (and action listener) needs to be in the render method because drawString() is part of a Graphics object g so I'm in a bit of a predicament. Any thoughts? \$\endgroup\$ Commented Apr 28, 2018 at 23:51
  • \$\begingroup\$ If you have a specific question about how to getDrawGraphics from outside of a render method, you should ask a new question. Otherwise, your problem sounds like you need to run a one-time event, which means you can probably start it from your constructor, or in run before the main loop \$\endgroup\$
    – Jimmy
    Commented Apr 30, 2018 at 17:03

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .