This site requires JavaScript, please enable it in your browser!
Greenfoot back
lmoellendorf
lmoellendorf wrote ...

2026/9/1

Black screen with truncated error: "lass: java.lang.RuntimeException - (JavaScrip"

lmoellendorf lmoellendorf

2026/9/1

#
How can I debug this? The game runs fine locally, but the HTML5 version fails. No helpful console output. Only the truncated error message: "lass: java.lang.RuntimeException - (JavaScrip" The game is here: https://www.greenfoot.org/scenarios/36670
lmoellendorf lmoellendorf

2026/9/3

#
To track down the cause of the `RuntimeException` I added some `System.out.println("start of Intro()")` statements to the constructor of my initial world class. They should have shown up in the browser console. But they did not. So I figured the issue must be in the static code of my initial world class. By trial and error I found out it was the `static final` keywords for the world background `GreefootImage` and GreenfootSound`. Here is a diff:
ndex 4d57810..4781524 100644
@@ -8,8 +8,8 @@ import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)
  */
 public class Intro extends World
 {
-    static final GreenfootImage BG_IMAGE = new GreenfootImage("touhou_opening.png");
-    static final GreenfootSound BG_SOUND = new GreenfootSound("dova_nikki_full_mix_30sec.mp3");
+    GreenfootImage BG_IMAGE = new GreenfootImage("touhou_opening.png");
+    GreenfootSound BG_SOUND = new GreenfootSound("dova_nikki_full_mix_30sec.mp3");
     static final int WIDTH = 700;
     static final int HEIGHT = 700;
     static final int VOLUME = 60;
I don't understand why those `static final` are an issue especially since they work well elsewhere. But nevertheless, I was some step further. In order to even come so far, I had to find out and fix some other restrictions of the HTML5 runtime. First I had to remove dependencies to java.nio.* and java.awt.*. For example, to open files I had to use `InputStream` instead of `Files.readString()` Here is a diff:
---------------------------- touhou/FileReader.java ----------------------------
index fc3b427..5c8d52b 100644
@@ -1,6 +1,6 @@
 import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)
-import java.nio.file.*;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.regex.Matcher;
@@ -93,16 +93,23 @@ public class FileReader extends Actor
     {
         try
         {
-            String content = Files.readString(Path.of(filename));
-            int size;
+            InputStream input = getClass().getClassLoader().getResourceAsStream(filename);
+
+            if (input == null)
+            {
+                System.out.println("File not found: " + filename);
+                return;
+            }
+
+            String content = new String(input.readAllBytes());
+            input.close();
 
             segments = parse(content);
             textHeight = 0;
 
-
             for (FontSegment segment : segments)
             {
-                size = segment.font.getSize();
+                int size = segment.font.getSize();
 
                 if (segment.text.equals("\n"))
                 {
In my local version I used `Desktop.getDesktop().browse(uri)` to allow the user to open links to used artwork in my "Credits" view. I had to drop this completely, because it is not possible to open links in HTML5. And it is even not possible to have code depending on the runtime environment. Say to support opening links in the local Greenfoot environment and to drop this support only in HTML5 environment. And there were more issues to solve. In Greenfoot you can use "Set image..." in the class view to assign an image to a class. But this image is not picked up in the HTML5 runtime. Here is a diff showing what I had to change:
------------------------------ touhou/Bullet.java ------------------------------
index e790eca..ad5b2ca 100644
@@ -7,14 +7,17 @@ import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)
  */
 public class Bullet extends Actor
 {
+    static GreenfootImage BULLET = new GreenfootImage("button-purple.png");
     protected int speed = 15;
 
-    Bullet()
+    static
     {
-        GreenfootImage image;
+        BULLET.scale(7, 7);
+    }
 
-        image = getImage();
-        image.scale(7, 7);
+    Bullet()
+    {
+        setImage(BULLET);
         setRotation(270);
     }
Also I learned that `UserInfo.getMyInfo()` returns `null` if the player is not logged in. So I had to fix a null pointer exception:
----------------------------- touhou/MyWorld.java -----------------------------
index 1657634..67567d6 100644
@@ -408,7 +408,7 @@ public class MyWorld extends World
                             myInfo = UserInfo.getMyInfo();
                             score = scoreCounter.getValue();
 
-                            if (score > myInfo.getScore())
+                            if (myInfo != null && score > myInfo.getScore())
                             {
                                 myInfo.setScore(score);
                                 /* write back to server */
A very important lesson I learned is: Do not block! In local Greenfoot, waiting for `Greenfoot.isKeyDown()` in a while loop works because the simulation runs on a separate thread. In HTML5, there is only one thread — the simulation loop itself. So the while loop blocks everything, `isKeyDown()` never updates, and the loop never exits → browser timeout. Here is how I solved it:
------------------------------ touhou/Intro.java ------------------------------
index 8612fdc..9d37034 100644
@@ -19,6 +19,7 @@ public class Intro extends World
     Credits credits;
     License license;
     int frameCount;
+    boolean soundStopped = false;
 
     enum intro
     {
@@ -70,15 +71,20 @@ public class Intro extends World
                 {
                     showText(null, getWidth() / 2, getHeight() / 2);
                     BG_SOUND.stop();
+                }
 
-                    while (BG_SOUND.isPlaying())
-                        /* wait */
-                        ;
+                frameCount++;
 
-                    Greenfoot.setWorld(new MyWorld());
+                if (!soundStopped)
+                {
+                    soundStopped = !BG_SOUND.isPlaying();
+                    /* wait till sound stopped */
+                    break;
                 }
 
-                frameCount++;
+                /* reset sound state for next turn */
+                soundStopped = false;
+                Greenfoot.setWorld(new MyWorld());
                 break;
 
             default:
You can find my code and all changes in my Gitlab repository
You need to login to post a reply.