import java.applet.Applet; import java.awt.*; import java.awt.image.*; /** * Testbed for the basics. * * Note that the event handling is slightly more complicated than other templates. * I don't guarantee that all events will come through cleanly: in particular, I don't * guarantee that mouse clicks will get an accurate position if the mouse is moving. * However, I do make two guarantees: * 1. The (mouseX, mouseY) will be the location of a recent mouse event - i.e. you * won't get the x of one event and the y of another. * 2. If a key is pressed or a mouse button is pressed at least once between two * iterations of the game loop, the press can be detected. At the moment I don't * fully expose this, so if the user manages to press a key twice during a 10ms * sleep I only handle one, but the fact that the key was pressed twice is stored. */ public class Skeleton extends Applet implements Runnable { // Constants. // Must be at least 1026 if using Java 1.0 event model or someone accidentally // hitting Ins will get AIOOBE. Pick a number used elsewhere and reuse it to // save a constant pool entry. private static final int NUM_KEYS = 1026; // Set false if not using mouse and the compiler should eliminate the overhead. private static final boolean USING_MOUSE = true; private static final int WIDTH = 640; private static final int HEIGHT = 480; // We overload the key buffer for mouse state to save fields. These "key codes" correspond to unlikely // Unicode values (unless you expect players to be pressing Ctrl-A to Ctrl-D). private static final int VK_MOUSEXY = 1; private static final int VK_MOUSELMB = 2; private static final int VK_MOUSERMB = 3; private static final int VK_MOUSEMMB = 4; // Shared state. In the spirit of lock-free synchronisation, this is written // only by the event thread, and it advances values rather than setting / // resetting. This ensures that quick key presses aren't lost. private int[] keyEventCount = new int[NUM_KEYS]; @Override public void start() { new Thread(this).start(); } public void run() { // Set up event handling. Note: keyDown[key] and keyPressed[key] are 0 // for false; any other value should be interpreted as true. int[] prevKeyEventCounts = new int[NUM_KEYS]; int[] keyDown = new int[NUM_KEYS]; int[] keyPressed = new int[NUM_KEYS]; int mouseX = 0, mouseY = 0; // Game state. int x = 320; int y = 320; int bgcol = 0; // Set up backbuffer for rendering. BufferedImage buf = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB); // Uncomment if you want to set pixels directly. //int[] ibuf = ((DataBufferInt)buf.getRaster().getDataBuffer()).getData(); while (!isActive()) /* Busy-wait */; while (isActive()) { try { long now = System.currentTimeMillis(); // Events. for (int i = 0; i < NUM_KEYS; i++) { int eventCount = keyEventCount[i]; // An odd number of events means that it's down. Simple! // NB If you're worried about key repeat you might want // instead to say // keyDown[i] = (delta >> 1) + (eventCount & 1); keyDown[i] = eventCount & 1; // The key was pressed since the last loop if the number of events // since then is at least two, by the pigeonhole principle, or if // the number of events since then is positive and the key is down. // Since we only need to catch the second case when delta == 1, // we can simplify the key-is-down test a bit. int delta = eventCount - prevKeyEventCounts[i]; keyPressed[i] = (delta >> 1) + (delta & eventCount); prevKeyEventCounts[i] = eventCount; } if (USING_MOUSE) { int mouseXY = keyEventCount[VK_MOUSEXY]; mouseX = mouseXY >> 16; mouseY = (short)mouseXY; // Restores sign - alternative is (mouseXY << 16) >> 16; } // Logic. if (keyDown[Event.LEFT] != 0) x--; if (keyDown[Event.RIGHT] != 0) x++; if (keyDown[Event.UP] != 0) y--; if (keyDown[Event.DOWN] != 0) y++; if (keyPressed[VK_MOUSELMB] != 0) bgcol++; if (keyPressed[VK_MOUSERMB] != 0) bgcol--; // Render to backbuffer. Graphics g = buf.getGraphics(); int bg = 0xff << ((bgcol & 3) << 3); g.setColor(new Color(bg)); g.fillRect(0, 0, WIDTH, HEIGHT); g.setColor(new Color(0xffffff)); g.drawString("Score", x, y); g.fillOval(mouseX - 7, mouseY - 7, 15, 15); if (keyDown['a'] != 0) g.fillRect(0, 0, 100, 100); if (keyPressed['a'] != 0) g.fillRect(100, 0, 100, 100); // Render backbuffer to front buffer and tidy. Graphics gapp = getGraphics(); if (gapp != null) { gapp.drawImage(buf, 0, 0, this); gapp.dispose(); } g.dispose(); // Sleep. There are various ways of doing this, but IME you need to // sleep rather than yield under Linux. long t = System.currentTimeMillis(); long sleep = 20 - (t - now); now = t; if (sleep > 0) { Thread.sleep(sleep); } } catch (Throwable ie) { } } } @Override public boolean handleEvent(Event evt) { // Non-key events have 0 for their key field. keyEventCount[evt.key]++; if (USING_MOUSE && (evt.id >> 4 == 31)) // In range 496 to 511, so for practical purposes it's a mouse event { keyEventCount[VK_MOUSEXY] = (evt.x << 16) + (evt.y & 0xffff); // MOUSE_DOWN or MOUSE_UP if ((evt.id & 0xf) <= 6) keyEventCount[VK_MOUSELMB + (evt.modifiers >> 2)]++; } return false; } }