S99 Arial, Bld, YW8, 37 points, 105% line spacing

Werbung
Entwicklung eines 2D
Computerspiels in Java
Teil 2
Motivation
•
Arbeiten mit grösseren Programmen!
•
Lesen und Manipulieren von Programmen anderer.
Alle Beispielprogramme sind aus dem Buch:
"Developing Games in Java." von David Brackeen
http://www.brackeen.com/javagamebook
Das Buch wird für die Vorlesung und die Übungen NICHT benötigt,
der Programmcode für Kapitel 2 und 3 sollte übers Web geladen
werden.
Page 1
Spielentwicklung: was haben wir bis jetzt!
SreeenManager.java
Hier werden alle für den Bildschirm relevanten Einstellungen gesetzt.
Vollbildschirm, Pixelauflösung, Bufferstrategy.
Animation.java
Abfolge von Bildern ergibt eine Animation eines Objekts (Person).
AnimationTest.java ( mit main )
Zeigt Animation vor Hintergrund.
Animation
• Dynamisches verändern des Bildes!
Am einfachsten ist eine Abfolge von fertigen Teilbildern.
Bild A
Zeit
ms
0ms
Bild B
200ms
Bild C
275ms
Page 2
Bild B
300ms
end
Animation
Abfolge von einzelnen Teilbildern wobei jedes Bild für eine bestimmte Zeit
präsentiert wird.
Teilbilder :
class AnimFrame {
Image image;
long
endTime;
public AnimFrame( Image image, long endTime ) {
this.image = image;
this.endTime = endTime;
}
}
Animation.java
public synchronized void update(long elapsedTime) {
if (frames.size() > 1) {
animTime += elapsedTime;
public class Animation {
private
private
private
private
ArrayList frames;
int currFrameIndex;
long animTime;
long totalDuration;
/**
Creates a new, empty Animation.
public Animation() { ……… }
if (animTime >= totalDuration) {
animTime = animTime % totalDuration;
currFrameIndex = 0;
}
*/
while (animTime > getFrame(currFrameIndex).endTime) {
currFrameIndex++;
}
public synchronized void addFrame(Image image, }long duration) {……. }
/**
Adds an image to the animation with the specified
duration (time to display the image).
*/
/**
Starts this animation over from the beginning.
/**
Updates this animation's current image (frame), if neccesary. */
/**
Gets this Animation's current image. Returns null if this
animation has no images.
*/
public synchronized void start() { ………..}
*/
public synchronized void update(long elapsedTime) {…………. }
public synchronized Image getImage() {…….. }
private AnimFrame getFrame(int i) { }
}
private class AnimFrame {
}
Page 3
BufferStrategy Klasse
API ---> gibt guten Überblick zu den verschieden Möglichkeiten.
Canvas, Window
besitzen eine
BufferStrategy:
Für das Jframe möchten wir mindestens 2 Buffer:
// Create a general double-buffering strategy
frame.createBufferStrategy(2);
BufferStrategy strategy = frame.getBufferStrategy();
// Render loop
while (!done) {
Graphics g = strategy.getDrawGraphics();
// Draw to graphics
draw(g);
g.dispose();
strategy.show();
// switch to next buffer
}
Verbesserter ScreenManager
'Double Buffering' + 'Active Rendering' sollen von ScreenManager
übernommenen werden.
Weitere Verbesserungen:
Screenmanager vergleicht automatisch mögliche
Displaymodi mit einer vorgegebenen internen Liste.
Da 'Active Rendering' verwendet wird, wird die
automatische paint() Methode abgeschaltet.
frame.setIgnoreRepaint(true)
ScreenManager.java soll hier nicht im Detail besprochen werden,
relativ viel Code zur Anpassung an die vielen verschieden Plattformen!
Page 4
AnimationTest2.java
public class AnimationTest2 {
public static void main(String args[]) { ..changed.. }
private static final DisplayMode POSSIBLE_MODES[] = {
new DisplayMode(1024, 768, 32, 0),
new DisplayMode( 800, 600, 32, 0),
……….
};
private
private
private
private
static final long DEMO_TIME = 10000;
SimpleScreenManager screen;
Image bgImage;
Animation anim;
public void loadImages() { ..as before }
private Image loadImage(String fileName) {..as before }
public void run(DisplayMode displayMode) { ..changed.. }
public void animationLoop() {..changed.. }
}
public void draw(Graphics g) {..as before }
AnimationTest1.java
public void animationLoop() {
long startTime = System.currentTimeMillis();
long currTime = startTime;
AnimationTest2.java
public void animationLoop() {
long startTime = System.currentTimeMillis();
long currTime = startTime;
while (currTime - startTime < DEMO_TIME) {
long elapsedTime =
System.currentTimeMillis() - currTime;
currTime += elapsedTime;
while (currTime - startTime < DEMO_TIME) {
long elapsedTime =
System.currentTimeMillis() - currTime;
currTime += elapsedTime;
// update animation
anim.update(elapsedTime);
// update animation
anim.update(elapsedTime);
// draw and update screen
Graphics2D g = screen.getGraphics();
draw(g);
g.dispose();
screen.update(); // basically strategy.show();
// draw to screen
Graphics g =
screen.getFullScreenWindow().getGraphics();
draw(g);
g.dispose();
// take a nap
try {
Thread.sleep(20);
}
catch (InterruptedException ex) { }
// take a nap
try {
Thread.sleep(20);
}
catch (InterruptedException ex) { }
}
}
}
}
Page 5
AnimationTest1.java
AnimationTest2.java
public static void main(String args[]) {
DisplayMode displayMode;
if (args.length == 3) {displayMode = new DisplayMode(
Integer.parseInt(args[0]),
Integer.parseInt(args[1]),
Integer.parseInt(args[2]),
DisplayMode.REFRESH_RATE_UNKNOWN );
}
else { displayMode = new DisplayMode(800, 600, 16,
DisplayMode.REFRESH_RATE_UNKNOWN);
}
AnimationTest1 test = new AnimationTest1();
test.run(displayMode);
}
public void run(DisplayMode displayMode) {
screen = new SimpleScreenManager();
try {
screen.setFullScreen(displayMode, new JFrame());
loadImages();
animationLoop();
}
finally { screen.restoreScreen();
}
}
public static void main(String args[]) {
AnimationTest2 test = new AnimationTest2();
test.run();
}
public void run() {
screen = new ScreenManager();
try {
DisplayMode displayMode =
screen.findFirstCompatibleMode(POSSIBLE_MODES);
screen.setFullScreen(displayMode);
loadImages();
animationLoop();
}
finally {
screen.restoreScreen();
}
}
AnimationTest 2 .draw()
public void draw(Graphics g) {
// draw background
g.drawImage(bgImage, 0, 0, null);
// draw image
g.drawImage(anim.getImage(), 0, 0, null);
}
Page 6
Schnelle Animation mit „Sprites“
„Sprites“ sind Rasterbilder die einem Hintergrund überlagert werden.
In unserem Fall sind Sprites Animationen (Bildfolgen) die als Ganzes über
den Bildschirm bewegt werden.
Sprite.java
public class Sprite {
private Animation anim;
// position (pixels)
private float x;
private float y;
// velocity (pixels per millisecond)
private float dx;
private float dy;
/**
Constructor
*/
public Sprite (Animation anim) { this.anim = anim; }
/**
Updates this Sprite's Animation and its
position */
public void update (long elapsedTime) {
x += dx * elapsedTime;
y += dy * elapsedTime;
anim.update(elapsedTime);
}
........ + many set and get Methods for sprite parameter
Page 7
Sprite.java (2)
......
/**
Sets and gets this Sprite's current x position.
public float getX() {
return x; }
public float getY() {
return y;
}
public void setX(float x) {
this.x = x;
}
public void setY(float y) {
this.y = y;
}
/**
*/
Gets this Sprite's width, based on the size of the
current image. */
public int getWidth() {
return anim.getImage().getWidth(null); }
public int getHeight() { return anim.getImage().getHeight(null); }
/**
Gets and sets the horizontal velocity of this Sprite in
pixels per millisecond.
*/
public float getVelocityX() {
return dx;
public float getVelocityY() {
return dy; }
}
public void setVelocityX(float dx) { this.dx = dx;
}
public void setVelocityY(float dy) { this.dy = dy;
}
/**
Gets this Sprite's current image.
*/
public Image getImage() { return anim.getImage();
}
}
..........
SpriteTest2.java
public void run() { as usual }
public class SpriteTest2 {
public void animationLoop() { also includes drawFade }
public static void main(String args[]) {
public void drawFade(Graphics2D g, long currTime) { }
SpriteTest2 test = new SpriteTest2();
test.run();
public void update(long elapsedTime) { ....
}
..... updates all Sprites and each Sprite updates ts
animation .... }
private static final DisplayMode POSSIBLE_MODES[] = {
public void draw(Graphics2D g) { ... draws and
new DisplayMode(800, 600, 32, 0),
transforms the sprites
.................};
}
private static final long DEMO_TIME = 10000;
}
private static final long FADE_TIME = 1000;
private static final int NUM_SPRITES = 3;
private ScreenManager screen;
private Image bgImage;
private Sprite sprites[];
public void loadImages() { }
Page 8
Zum vollständigen Spiel
Wir haben:
SreeenManager.java
Animation.java
Sprite.java
Es fehlt noch:
InputManager.java
A)
Interaktion mit Tastatur und Maus
B)
Unterschiedliche Verhalten- / Bewegungsmuster
der Sprites
C)
Verküpfung von A) und B) in
GameAction.java
extend GameCore.java
Die Integration von allem ergibt das Spiel!
abstract GameCore.java
/**
Player.java
( ch03src )
Simple abstract class used for testing.
Subclasses should implement the draw() method. */
public abstract class GameCore {
/**
protected static final int FONT_SIZE = 24;
private static final DisplayMode POSSIBLE_MODES[] = {
Sets full screen mode and initiates and
objects.
*/
public void init() {
}
new DisplayMode(800, 600, 32, 0),
.........
/**
};
Runs through the game loop until stop() is
called. */
public void gameLoop() {
}
private boolean isRunning;
/**
protected ScreenManager screen;
/**
Signals the game loop that it's time to quit
public void update(long elapsedTime) {
*/
// do nothing
public void stop() {
}
isRunning = false;
}
/**
Calls init() and gameLoop()
Updates the state of the game/animation based
on the amount of elapsed time that has passed. */
/**
*/
public void run() { }
Draws to the screen. Subclasses must
override this method.
*/
public abstract void draw(Graphics2D g);
}
Page 9
abstract GameCore.java
/**
( ch03src )
/**
Calls init() and gameLoop()
*/
Sets full screen mode and initiates and objects.
*/
public void run() {
public void init() {
try {
screen = new ScreenManager();
init();
DisplayMode displayMode =
screen.findFirstCompatibleMode(POSSIBLE_MODES);
gameLoop();
screen.setFullScreen(displayMode);
}
finally {
Window window = screen.getFullScreenWindow();
screen.restoreScreen();
window.setFont(new Font("Dialog", Font.PLAIN,
FONT_SIZE) );
}
}
window.setBackground(Color.blue);
window.setForeground(Color.white);
isRunning = true;
}
abstract GameCore.java
( ch03src )
public void gameLoop() {
long startTime = System.currentTimeMillis();
/**
long currTime = startTime;
Calls init() and gameLoop()
*/
while (isRunning) {
public void run() {
long elapsedTime =
try {
System.currentTimeMillis() - currTime;
currTime += elapsedTime;
init();
gameLoop();
// update
}
update(elapsedTime);
finally {
screen.restoreScreen();
// draw the screen
Graphics2D g = screen.getGraphics();
}
draw(g);
}
g.dispose();
screen.update();
// take a nap
try {
Thread.sleep(20); }
catch (InterruptedException ex) { }
}
}
Page 10
Player extends Sprite
Ein Spriteobjekt hat eine
-
Animation
Geschwindigkeit
Position
Aus der Geschwindigkeit, der alten Position und
der Zeit seit dem letzten Update ergibt sich die neue Position.
Ein "Player" soll nun einen Sprung durchführen können.
Ein zweiter Sprung darf erst nach der Landung wieder ausgelöst werden.
Wie soll ein Sprung simuliert werden?
Welche Parameter werden hierzu benötigt?
Wie in der realen Welt?
•
•
•
Wir heben mit einer konstanten Geschwindigkeit nach oben ab!
Wir werden durch die Erdanziehung gebremst und fallen wieder herunter!
Wir kommen auf dem Boden wieder zur Ruhe!
Player extends Sprite
Ein Spriteobjekt hat eine
- Animation
- Geschwindigkeit
- Position
Ein "Player" benötigt zusätzlich:
- Startgeschindigkeit
- Bremsbescheunigung
- Position des Bodens
- Zustandsvariable : IM-SPRUNG
Sprungsimulation:
Vy = Vsprung;
while (PositionY > PositionBoden ) {
Vy = Vsprung; + g* Zeit_seit _Absprung;
}
Page 11
Player extends Sprite
public void jump() {
setVelocityY(-1);
public class Player extends Sprite {
state = STATE_JUMPING;
}
public static final int STATE_NORMAL = 0;
public void update (long elapsedTime) {
public static final int STATE_JUMPING = 1;
// set vertical velocity (gravity effect)
public static final float SPEED = .3f;
if (getState() == STATE_JUMPING) {
public static final float GRAVITY = .002f;
setVelocityY(getVelocityY()
private int floorY;
+ GRAVITY * elapsedTime);
private int state;
}
// move player
public Player(Animation anim) {
super.update(elapsedTime);
super(anim);
state = STATE_NORMAL;
// check if player landed on floor
}
if (getState() ==
public
int getState() { return state;
}
STATE_JUMPING && getY() >= floorY) {
public void setState(int state) { .....}
setVelocityY(0);
public void setFloorY(int floorY) {.....}
setY(floorY);
public void jump() {..... }
setState(STATE_NORMAL);
public void update(long elapsedTime) {..... }
}
}
}
InputManagerTest extends GameCore
InputManagerTest.java
init()
Setzen von :
ScreenManager()
1.
Bildschirmeinstellungen
2.
Spielzustände mit Maus und Tastatur verknüpfen
3.
GameAction()
•
Beenden
•
Pause
•
Bewegungsmuster des Spielers {LINKS, RECHTS, SPRINGEN ... }
Sprites erstellen
gameLoop()
Animationszyklus :
1.
Alle Sprites bzgl. Tastatur und Zeit aktualisieren.
2.
Alle entsprechenden Bilder anzeigen.
3.
Eventuell warten
4.
Wieder zu Schritt eins.
Page 12
InputManagerTest.java
public class InputManagerTest extends GameCore {
public static void main(String[] args) {
new InputManagerTest().run();
}
...............
protected GameAction jump;
protected GameAction exit;
protected GameAction moveLeft;
public void init() {......... }
protected GameAction moveRight;
public boolean isPaused() {......... }
protected GameAction pause;
public void setPaused(boolean p) {.........
}
public void update(long elapsedTime) {........}
protected InputManager inputManager;
public void checkSystemInput() {.........
private Player player;
public void checkGameInput() {......... }
// extends Sprite
}
private Image bgImage;
public void draw(Graphics2D g) {......... }
private boolean paused;
public void createGameActions() {......... }
private void createSprite() {......... }
}
..............
InputManagerTest.java (2)
/**
Calls init() and gameLoop()
*/
public void run() {
public void init() {
try {
init();
gameLoop();
super.init();
// from
// GameCore
inputManager =
new InputManager( screen.getFullScreenWindow());
}
finally {
screen.restoreScreen();
createGameActions();
}
createSprite();
}
paused = false;
}
Page 13
InputManagerTest.java (3)
public void gameLoop() {
public void update(long elapsedTime) {
long startTime = System.currentTimeMillis();
// check input that can happen whether paused or not
long currTime = startTime;
checkSystemInput();
while (isRunning) {
if (!isPaused()) {
long elapsedTime =
// check game input
System.currentTimeMillis() - currTime;
checkGameInput();
currTime += elapsedTime;
// update sprite
// update
player.update(elapsedTime);
update(elapsedTime);
}
}
// draw the screen
Graphics2D g = screen.getGraphics();
public void draw(Graphics2D g) {
draw(g);
g.dispose();
// draw background
screen.update();
g.drawImage(bgImage, 0, 0, null);
// draw sprite
g.drawImage(player.getImage(),
// take a nap
Thread.sleep(20); }
Math.round(player.getX()),
catch (InterruptedException ex) { }
Math.round(player.getY()),
try {
null);
}
}
}
InputManagerTest.java (4)
public void update(long elapsedTime) {
public void checkSystemInput() {
// check input that can happen
if (pause.isPressed()) {
// whether paused or not
setPaused(!isPaused());
checkSystemInput();
}
if (exit.isPressed()) {
if (!isPaused()) {
stop();
// check game input
checkGameInput();
// update sprite
}
}
public void checkGameInput() {
float velocityX = 0;
player.update(elapsedTime);
if (moveLeft.isPressed())
}
{ velocityX -=Player.SPEED; }
if (moveRight.isPressed()) { velocityX +=Player.SPEED;}
}
player.setVelocityX(velocityX);
if (jump.isPressed() &&
player.getState() != Player.STATE_JUMPING)
{
player.jump();
}
}
Page 14
InputManagerTest.java (5)
public void init() {
public void createGameActions() {
jump = new GameAction("jump", GameAction.DETECT_INITAL_PRESS_ONLY);
super.init();
exit = new GameAction("exit", GameAction.DETECT_INITAL_PRESS_ONLY);
moveLeft = new GameAction("moveLeft");
moveRight = new GameAction("moveRight");
inputManager =
pause = new GameAction("pause", GameAction.DETECT_INITAL_PRESS_ONLY);
new InputManager( screen.getFullScreenWindow());
inputManager.mapToKey(exit, KeyEvent.VK_ESCAPE);
createGameActions();
inputManager.mapToKey(pause, KeyEvent.VK_P);
createSprite();
paused = false;
// jump with spacebar or mouse button
}
inputManager.mapToKey(jump, KeyEvent.VK_SPACE);
inputManager.mapToMouse(jump, InputManager.MOUSE_BUTTON_1);
// move with the arrow keys...
inputManager.mapToKey(moveLeft, KeyEvent.VK_LEFT);
inputManager.mapToKey(moveRight, KeyEvent.VK_RIGHT);
// ... or with A and D.
inputManager.mapToKey(moveLeft, KeyEvent.VK_A);
inputManager.mapToKey(moveRight, KeyEvent.VK_D);
}
GameAction.java
Die Klasse
GameAction ordnet jedem Zustand oder jeder
Zustandsänderung im Spiel (Pause, nachLinks, nachRechts, Springen)
einem Zustand einer Taste oder Maus zu (pressed, released, .....).
Damit entsteht eine Trennung vom eigentlichen Abfragen der Events und
den eigentlichen Aktionen im Spiel.
Ein
GameAction-Objekt weiss nicht welcher Tasten oder Mausfunktion
sie zugeordnet ist. Es kennt nur die Zustände in der die entsprechende
Taste oder Maus ist.
InputManager fängt alle Maus und Tastatur Events ab und kennt
welche Taste welche GameAction verändert.
Der
Page 15
GameAction.java
public class GameAction {
public static final int NORMAL = 0;
public static final int DETECT_INITAL_PRESS_ONLY = 1;
private static final int STATE_RELEASED = 0;
private static final int STATE_PRESSED = 1;
private static final int STATE_WAITING_FOR_RELEASE = 2;
private
private
private
private
String name;
int behavior;
int amount;
int state;
public GameAction(String name) {.... }
public GameAction(String name, int behavior) {.... }
/**
Create a new GameAction with the specified behavior. */
public GameAction(String name, int behavior) {
this.name = name;
this.behavior = behavior;
reset();
}
/**
Signals that the key was pressed.
public synchronized void press() {
press(1);
}
*/
/**
Signals that the key was pressed a specified number of
times, or that the mouse move a spcified distance.
public String getName() {.... }
*/
public void reset() {.... }
public synchronized void tap() {....
public synchronized void press(int amount) {
if (state != STATE_WAITING_FOR_RELEASE) {
this.amount+=amount;
state = STATE_PRESSED;
}
}
public synchronized void press() {.... }
public synchronized void press(int amount) {....
}
public synchronized void release() {.... }
public synchronized boolean isPressed() {.... }
}
public synchronized int getAmount() {.... }
}
GameAction.java
public class GameAction {
/**
public static final int NORMAL = 0;
public static final int DETECT_INITAL_PRESS_ONLY = 1;
private static final int STATE_RELEASED = 0;
private static final int STATE_PRESSED = 1;
private static final int STATE_WAITING_FOR_RELEASE = 2;
private
private
private
private
String name;
int behavior;
int amount;
int state;
Returns whether the key was pressed or not since last
checked. */
public synchronized boolean isPressed() {
return (getAmount() != 0);
}
/** For keys, this is the number of times the key was
pressed since it was last checked.
public GameAction(String name) {.... }
public GameAction(String name, int behavior) {.... }
For mouse movement, this is the distance moved. */
public synchronized int getAmount() {
int retVal = amount;
if (retVal != 0) {
public String getName() {.... }
if (state == STATE_RELEASED) {
amount = 0;
public void reset() {.... }
public synchronized void tap() {....
}
else if (behavior == DETECT_INITAL_PRESS_ONLY) {
}
public synchronized void press() {.... }
public synchronized void press(int amount) {....
state = STATE_WAITING_FOR_RELEASE;
amount = 0;
}
}
public synchronized void release() {.... }
public synchronized boolean isPressed() {.... }
public synchronized int getAmount() {.... }
}
}
return retVal;
}
}
Page 16
InputManager.java
InputManager implements KeyListener,
MouseListener,
MouseMotionListener,
MouseWheelListener
Folgende Methoden müssen implementiert werden
void keyPressed(KeyEvent e)
void keyReleased(KeyEvent e)
void keyTyped(KeyEvent e)
Der InputManager fängt alle Maus und Tastatur Events ab und kennt
welche Taste welche GameAction verändert.
InputManager.java
................
public class InputManager implements KeyListener
// ALL MOUSE RELATED FUNCTIONALITY REMOVED
/** Clears all mapped keys and mouse actions to this
GameAction.
*/
{
public void clearMap(GameAction gameAction) {..... }
// key codes are defined in java.awt.KeyEvent.
// most of the codes (except for some rare ones like
// "alt graph") are less than 600.
private static final int NUM_KEY_CODES = 600;
/**
Gets a List of names of the keys and mouse actions
mapped to this GameAction. Each entry in the
List is a string. */
public List getMaps(GameAction gameCode) {..... }
private GameAction[] keyActions =
new GameAction[NUM_KEY_CODES];
/**
Resets all GameActions so they appear like they
haven't been pressed.
*/
public void resetAllGameActions() {..... }
private Component comp;
public InputManager(Component comp) {
this.comp = comp;
/**
Gets the name of a key code.
*/
public static String getKeyName(int keyCode) {
return KeyEvent.getKeyText(keyCode);
}
// register key listeners
comp.addKeyListener(this);
// allow input of the TAB key and other keys
// normally used for focus traversal
comp.setFocusTraversalKeysEnabled(false);
private GameAction getKeyAction(KeyEvent e) {.....
// from the KeyListener interface
public void keyPressed(KeyEvent e) {..... }
public void keyReleased(KeyEvent e) {..... }
public void keyTyped(KeyEvent e) {..... }
}
public void mapToKey(GameAction gameAction, int keyCode) {
keyActions[keyCode] = gameAction;
}
................
}
Page 17
}
InputManager.java (2)
// from the KeyListener interface
public void keyPressed(KeyEvent e) {
GameAction gameAction = getKeyAction(e);
if (gameAction != null) {
gameAction.press();
}
// the key isn't processed for anything else
e.consume();
}
private GameAction getKeyAction(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode < keyActions.length) {
return keyActions[keyCode];
}
else {
return null;
}
}
// from the KeyListener interface
public void keyReleased(KeyEvent e) {
GameAction gameAction = getKeyAction(e);
if (gameAction != null) {
gameAction.release();
}
e.consume();
}
// from the KeyListener interface
public void keyTyped(KeyEvent e) {
e.consume();
}
Wiederholung
InputManagerTest.java
Page 18
Herunterladen