Nostalgia Post: j2me, Gravity Defied, 64kb

This article does not pursue any practical goals - I just wondered how about 15 years ago, developers managed to make fully functional applications and games for weak phones of the time.



  • If anything, I have nothing to do with this game.

, floating-point , . "3" β€” fixed-point . , . , j2me Android.


, j2me β€” java (, 1.3), .jar PC. β€” . , - β€” API j2me .


.


, , , .


Nokia 5200, " " :


  • 2006
  • 128x160 1.8
  • 262 . ( , 6 R, G, B ).
  • - bluetooth .
  • ( , 7 )
  • microSd , , , 256 .
  • - opera mini (, , )
  • 640x480 .
  • MIDP 2.0 (wiki)
  • usb
  • -
  • , , β€” 100 , , floating-point . , - .

, β€” , 50-200 .


Write once, run everywhere


java . , . , platform- , .


, β€” . β€” . . , .jar j2me .class java. , Canvas, MIDlet javax.microedition.


, . Android : .class , .dex ( , .. dex 65 ), apk, ART.


, . , . , . , Vector synchronized. Vector , ArrayList ArrayList , .


β€” java . (int, boolean, ...) . Vector Object, .


, go , java 1.3. go - , java 2004 1.5 . 1.3.


j2me β€” .jar .class ( 1.3, 20 ), Gravity defied PC. "" java javax.microedition , . , : , java.awt.Image javax.microedition.lcdui.Image.


J2ME



javax.microedition. , - , , - , , β€” . javax.microedition.lcdui.


MIDlet.


startApp().
β€” pauseApp() startApp() . destroyApp().


, notifyPaused() notifyDestroyed() β€” , .
, " " resumeRequest()


, notifyPaused(), , .


, lifecycle .


, Runnable - :


Thread thread;
boolean isRunning;
boolean needToDestroy = false;

public void startApp() {
    isRunning = true;
    if (thread != null) {
        thread = new Thread(this);
        thread.start();
    }
}

public void pauseApp() {
    isRunning = false;
}

public void destroyApp(boolean unconditional) {
    needToDestroy = true;
}

, .


void run(){
    while(!needToDestroy) {
        //   
        //     - .
        while (!isRunning) {
            Thread.sleep(100);
        }
    }
    notifyDestroyed();
}

, 100 β€” , . , pauseApp() startApp()


Canvas


β€” Canvas. Canvas java.


repaint(), , . UI- paint(Graphics g). , - , Canvas β€” . β€” repaint() paint(...) .


, :
hasPointerEvents(), hasPointerMotionEvents(), hasRepeatEvents(), true. pointerDragged(int x, int y) ( ), pressed( ) released( ).
β€” , .



, β€” , . . , , . , 240360 . β€” β€” .


- , "" , . j2me - , , .., . , nokia 5800 , .


MIDlet Pascal


- , . java2me , , MIDlet pascal . j2me β€” , , java.


?


, , , Gravity Defied 64 .


-, .jar β€” zip , 122.1 kB


  • META-INF MANIFEST.MF 3.8 kB, SHA-1 MD5 . .
  • levels.mrg 5.1 kB 30 . 170 . .
  • 11 . 10.8 kB.
  • .class , 98.3 kB, 15 . β€” 127 , 24.3 kB. :
    • , (4 ), 127 174 .
    • β€” 470 .
    • 9 β€” 1.6 6 kB.
    • god-like :


. fernflower β€” , intelliJ IDEA. β€” , . .


IDEA β€” , .


: /gradlew jar, build .jar
: mkdir decompiled && java -jar fernflower.jar -ren=1 GravityDefied_java decompiled


ren , if for. , java . ren=1 field_42 method_135 β€” , .


2004 β€” , , :


  • - , . .class .
  • else, int ..
  • . "" Micro, .
  • , fixed-point , . , (int)((long)a * b >> 16)
  • , , .


β€” , . , , -. , .


- Netbeans, j2me SDK. , , ant. .


, Linux. gradle . β€” javax.microedition.*. β€” PC- java. . , .


watch ./gradlew run . ctrl+S .


.
19 :


  • 8 javax.microedition.rms.
  • 10 .lcdui, , , Canvas ..).
  • MIDlet β€” , .

, .


Micro, . - , startApp MIDlet .


:


protected void pauseApp() {
    c = true;
    if (!b) {
        this.gameToMenu();
    }
    System.gc();
}

, c isPaused.


"" :


public void a() {
    if (this.recordStore != null) {
        try {
            this.e.closeRecordStore();
            return;
        } catch (RecordStoreException var1) {
        }
    }
}

- closeMethodStore.


, intelliJ IDEA - . β€” , , Image.loadImage(name):


this.p = Image.createImage("/splash.png");

, p splashImage.


- β€” , . , … Jetbrains β€” , β€” . , , .


- . β€” loadImage()… null . .


: , Image:


public class Image {
    public final java.awt.Image image;

    private Image(java.at.Image image) {
        this.image = image;
    }

    public Graphics getGraphics() {
        throw new RuntimeException();
    }

    public int getWidth() {
        return image.getWidth(null);
    }

    public static Image createImage(int w, int h) {
        return new Image(new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB));
    }
    ...
}

, API , .


CanvasImpl, JPanel. Canvas.
, c :


public void paintComponent(Graphics g) {
    if (upscale == 1) {
        canvas.paint(new javax.microedition.lcdui.Graphics(g));
    } else {
        canvas.paint(new javax.microedition.lcdui.Graphics(screen.getGraphics()));
        g.drawImage(screen, 0, 0, width * upscale, height * upscale, Color.WHITE, null);
    }
}

javax.microedition.lcdui.Graphics(g) β€” awt.Graphics, .


, . "" , .


, , , , β€” . PC. ? - 64 * 64, , 1 * 1 , 240 * 320 . , , , , .


, . , , , , , .


, , j2me PC. β€” 2010 -, - . - - β€” .


!


, .


.jar . . , , . - Alert Canvas Displayable , Canvas. , .


gradle :


  • emulator , , . , , .
  • app-original, .jar
  • app-from-sources - . .


, , . "" , , .


, .


Fixed-point physics


. , 16 β€” . , , -32768.0 32767.999984 c 1.0 / 65536.


β€” .


int.


: int, , "". long. 64 32 . 16 16- int.


: . long, 16 . - , 32 β€” 16.


sin, cos atan2.


β€” 64 β€” 0 90 . , β€” . . , .




, . Canvas.hasPointerEvents() true, , . β€” " ", " ", " " - "" . , , .


Level format


, β€” 170 . ? β€” .


, β€” / .., . . 0xff, int , , β€” dx , dy.


for (int i = 1; i < pointsCount; ++i) {
    byte modeOrDx;
    if ((modeOrDx = var1.readByte()) == -1) {
        offsetY = 0;
        offsetX = 0;
        pointX = var1.readInt();
        pointY = var1.readInt();
    } else {
        pointX = modeOrDx;
        pointY = var1.readByte();
    }
    offsetX += pointX;
    offsetY += pointY;
    this.addPointSimple(offsetX, offsetY);
}


.



GPU full hd , (128*160 240*320). 3 - , api . , β€” 90-180-270 .


, β€” .
, 32 16 . 48*48 1091 .


, , - API 3 3 - 3-. , - .


β€” 2 3.



. β€” 100 "23" "64".


if (time10MsToStringCache[time10MsPart] == null) {
    String zeroPadding;
    if (time10MsPart >= 10) {
        zeroPadding = "";
    } else {
        zeroPadding = "0";
    }
    time10MsToStringCache[time10MsPart] = zeroPadding + time10Ms % 100L;
}

, , . , -.


No-MVP


MenuManager ( ) . - , β€” - , .


, - , . : Android:


  • layout . .
  • stings.txt.
  • Activity, .
  • LifecycleObserver, Adapter , Activity .
  • Adapter DataSource.
  • " ", Bundle DataSource .
  • DataSouce SQLite .

: , ? , .



, TimerOrMotoPartOrMenuElem .
, , :


  • -
  • , " "

, , , β€” .


, , 64 . , - , "" .



, , , , . ", " β€” .


:


public void init() {
    long timeToLoading = 3000L;
    Thread.yield();
    this.gameCanvas = new GameCanvas(this);
    Display.getDisplay(this).setCurrent(this.gameCanvas);
    this.gameCanvas.requestRepaint(1);

    while (!this.gameCanvas.isShown()) {
        this.goLoadingStep();
    }

    long deltaTimeMs;
    while (timeToLoading > 0L) {
        deltaTimeMs = this.goLoadingStep();
        timeToLoading -= deltaTimeMs;
    }

    this.gameCanvas.requestRepaint(2);

    for (timeToLoading = 3000L; timeToLoading > 0L; timeToLoading -= deltaTimeMs) {
        deltaTimeMs = this.goLoadingStep();
    }

    while (gameLoadingStateStage < 10) {
        this.goLoadingStep();
    }

    this.gameCanvas.requestRepaint(0);
    this.isInited = true;
}

goLoadingStep() . goLoadingStep(). goLoadingStep , .


goLoadingStep() :


private long goLoadingStep() {
    ++gameLoadingStateStage;
    this.gameCanvas.repaint();
    long startTimeMillis = System.currentTimeMillis();
    switch (gameLoadingStateStage) {
        case 1:
            this.levelLoader = new LevelLoader();
            break;
        case 2:
            this.gamePhysics = new GamePhysics(this.levelLoader);
            this.gameCanvas.init(this.gamePhysics);
            break;
        case 3:
            this.menuManager = new MenuManager(this);
            this.menuManager.initPart(1);
            break;
            .... ///   5-8
        case 9:
            this.menuManager.initPart(7);
            break;
        case 10:
            this.gameCanvas.setMenuManager(this.menuManager);
            this.gameCanvas.setViewPosition(-50, 150);
            this.setMode(1);
            break;
        default:
            --gameLoadingStateStage;

            try {
                Thread.sleep(100L);
            } catch (InterruptedException var3) {
            }
    }

    return System.currentTimeMillis() - startTimeMillis;
}

MenuManager.initPart() switch case .


, , . , .



2004 . Excitera Mobile Awards 2004 (EMA04) best-in-show. Codebrew Software :


  • Tors BjΓΆrn Henrik Johansson β€” system/game logic/interface, testing, levels design
  • Set Elis Norman β€” graphics/physics/mathematics/system/tools programming, levels design
  • Per David Jacobsson β€” physics programming, game graphics, levels design

In addition, there is a port of this game for android made by our compatriots, but it just uses a decompiled code.


Since there have been no complaints about that code for five years, I think that my attempt to decompile the original application and understand it will not do any harm. Now the game is of historical interest and allows you to take a closer look at the era of small smartphones that they could.


All Articles