Ce218 - Lecture notes 5 PDF

Title Ce218 - Lecture notes 5
Course Computer Game Programming
Institution University of Essex
Pages 48
File Size 696.1 KB
File Type PDF
Total Downloads 35
Total Views 160

Summary

Lecture notes 15
2015/2016
Lecturer: Dr Norbert Volker...


Description

CE218.5 MVC, Game Loop and Concurrency Images and Transparency, Sounds Basic Multiplayer Mode using RMI A large part of the lecture and labs material was written by Simon Lucas for previous incarnations of this module

Model-View-Controller Architecture (MVC) 







Model-view-controller is an architectural pattern for implementing non-trivial user interactions. The key goal of MVC is to decouple  rendering of data in the user interface (= "view") from  the code that deals with the underlying data ("model") The "controller" component act as an intermediary  it receives user inputs and determines the response by making appropriate calls to the model and the view components. In a general MVC architecture, a model item may be displayed in multiple views  views may be created dynamically

MVC Diagram Handles user interaction

Controller

View Displays data to users

Model Underlying data including operations

Note: this diagram shows a version of MVC where views retrieve data from the model without going via the controller

Asteroids Game as MVC Instance 

"Model" 

GameObject, Ship, Asteroid, Bullet, ...(apart from draw-

methods and presentation-related fields like "color") 

"View" 

View (-> method paintComponent)



draw-methods in different game objects SoundManager, ImageManager, Sprite, Particle, …





"Controller" 

 

BasicKeys, Action, ...

"Auxiliary": Vector2D, Constants, ... Overall "system" class combining all components 

Game

Making the Game Loop more Flexible // basic loop AsteroidsGame game = new AsteroidsGame(); while (true) { game.update(); game.view.repaint(); Thread.sleep(DELAY); // DT = DELAY/ 1000 }

 



This issues a repaint-request for every update. We may want to update the model faster than the view,  increase model precision by running it faster May need to experiment to get the best setting  Observe graphics or even collect detailed timing data

// alternative game loop where views are only updated once // the model has been updated MODEL_SPEED number of times int i = 0; while (true) { long time0 = System.currentTimeMillis(); game.update(); i++; if (i == MODEL_SPEED) { game.view.repaint(); i = 0; } Thread.sleep(Math.max(0, MODEL_DURATION + time0 - System.currentTimeMillis())); }





If you introduce such a change, you may have to adapt speedrelated constants in other parts of the program as well Note the improved sleeping-time computation above

(Java) Threads  

A process can have many threads Each thread is an independent flow of control 



Threads can share the same address space 





separate threads running in the same process can access the same objects via references

Java threads are managed by JVM 



Thread concurrency may be "real" (-> multi-core processors) or just "apparent" (-> interleaved execution)

scheduler activates/ deactivates threads

A Java program starts with thread main() Switching threads is relatively fast, and recent OS/ CPUs have thread support

Threads in Asteroids 



 



Our current Asteroid design involves the main() thread and the GUI display/ event-handling thread started in the JEasyFrame. Thread main() executes the game object update methods.  Some of these read an Action object that is set by the GUI thread, see event handler defined in class BasicKeys. The main() thread also regularly requests a GUI repaint. The GUI thread executes method View.paintComponent.  This reads the list of game objects. That list, and its elements, are updated from the main() thread Question: Consider re-designing asteroids so that every game object runs on its own thread. Why would this be a bad idea?

Threads: Interleaving 

Consider two threads t1, t2 with run() methods consisting of two "atomic" statements each: t1: t2:



public void run() {a; b} public void run() {c; d}

Possible "interleaved" execution sequences after starting both threads (t1.start(); t2.start();): (a; b; c; d), (a; c; b; d), (a; c; d; b); (c; d; a; b), (c; a; d; b), (c; a; b; d)



All you know is "a before b" and "c before d" 

even that could be wrong due to compiler optimizations, see http://www.jcp.org/en/jsr/detail?id=133

Threads: Race Conditions 

Concurrent threads may try to access a shared variable or object. In the absence of synchronisation, a concurrent "write" can lead to strange effects. 





These are called "race conditions" 



If both threads try to "write" to the object, the result may be an object with some parts updated by the first thread, and some by the second. If one thread tries to read an object while the other one writes to it, the reading thread may retrieve an object where some parts have already been updated by the writing thread, while other parts have not. Their outcome may by non-deterministic.

Concurrent reads by two threads are fine.

Avoiding Race Conditions 

The underlying issue is that statement execution is not "atomic"  



Java has some constructs that help to avoid race conditions when writing concurrent programs   



the execution of statements (like a method body) may be interleaved with the execution of other statements in fact, even execution of a single simple statement (like increment or decrement) may not be atomic

synchronisation of methods and blocks locks, atomic methods/ variables executors and fork/join

In CE218, we will mainly use "synchronised blocks".

Synchronised Blocks 



[Java Lang Spec, 2nd ed.]: A synchronized statement acquires a mutual-exclusion lock on behalf of the executing thread, executes a block, then releases the lock.  While the executing thread owns the lock, no other thread may acquire the lock.  So other threads may have to wait until they get their turn. To put it differently: if your Java program has several synchronised blocks all with the same lock object, then at most one thread can be executing code in any of these blocks at any time.  This guarantees "mutual exclusion".

Concurrency in Asteroids: Game Object Collection 

As mentioned in the Lab 3 instructions, Java will throw a ConcurrentModificationException if the program adds or removes asteroids from the gameobject collection during a "for-each" iteration. 





Possible solutions involve using a ListIterator or keeping separate lists of "pending"/ "alive" objects.

Java throws the same exception if the game-object collection is modified from the main-thread while the GUI thread tries to draw its elements, A simple solution is to ensure atomicity with the help of synchronised blocks, see code below.

public Game() { … private void update() { // Collision handling code omitted ... List alive = new ArrayList(); for (GameObject object : objects) { if (!object.dead) { object.update(); alive.add(object); } } if (ship.bullet != null) { alive.add(ship.bullet); ship.bullet = null; } synchronized (Game.class) { objects.clear(); objects.addAll(alive); } }

public class View extends JComponent { … @Override public void paintComponent(Graphics g0) { Graphics2D g = (Graphics2D) g0; // paint the background g.setColor(BG_COLOR); g.fillRect(0, 0, getWidth(), getHeight()); synchronized(Game.class){ for (GameObject object : gameObjects) object.draw(g); } } … }

Concurrency in Asteroids: Action Object 









Recall that the Action object is written by the GUI thread, and it is read by update methods on the main-thread. This does not involve any concurrent writing, and all Action fields are primitive values. The "worst that can happen" is that the main-thread reads Action objects that are only partially up-to-date.  e.g. maybe the Action field thrust has already been assigned a new value while turn is still pending. It is unlikely that this concurrency will impede the Asteroids game experience.  precise timing of Action object updates depends on the details of keyboard/ GUI thread event-handling anyhow Note that this would be different if mutual consistency of the various Action fields was important.

Concurrency in Asteroids: Game Object Update 









If you look at the synchronisation above, you will notice that it does not guard the update of individual game objects. Hence the view may display game objects that are not entirely up-to-date.  For example, it may draw an asteroid where the field s.x has already been updated, while s.y still has its old value. Such inconsistencies could be avoided by guarding each game object update with a synchronised block. Or you could even surround the whole body of Game.update() with a synchronized block to eliminate any interleaving. It would require detailed tests/ analysis to assess whether these synchronisations are worth it.  Trade-off: improved view consistency versus performance

Synchronisation Remarks 



Synchronisation can lead to "deadlock" situations where two threads each hold a lock, and each thread waits for the other thread to release its lock. Synchronisation adds a runtime overhead. 



Synchronisation is absolutely necessary in some applications so as to avoid corruption of data. 



This may be negligible or not, depending on app details.

Think financial applications...

Synchronisation may be required sometimes in game programming. 

Given the potential performance hit, be mindful not to have lots of unnecessary synchronisation in your code.

Asteroids Concurrency: Alternatives 



We can also solve the race condition related to the "game object collection" without synchronisation. Approach 1: run game updates on a SwingTimer  



Method game.update() is put inside an ActionListener that is registered with a SwingTimer The SwingTimer is executed on the GUI thread, so no interference with drawing is possible.

Approach 2:  

Run the game in "full screen exclusive mode" (see below) and use "active rendering" instead of repaint requests. Again, we can ensure that rendering happens in-between updates, so there is no access from the rendering code to the game object collection while it is being updated.

Full Screen Exclusive Mode  

Java has support for a "Full Screen Exclusive Mode". This suspends the OS windows system - instead drawing is done directly by the program.   



In order to make full use of the capabilities of this mode, you will have to rewrite your rendering code. 



So you can avoid the overhead caused by having to handle move/ resize/ expose/ cover events propagated by the OS. The result should be improved graphics performance. It also allows re-setting the screen resolution.

Make use of "manual" double buffering and dealing with HW accelerated images that are kept in video RAM.

Please see online Java tutorial if you want to use this.

Full Screen Appearance: JEasyFrameFull 



If you simply want the game to make use of the full screen, please see utilities.JEasyFrameFull This will create an  

undecorated window which has the same boundaries as the device



Please note that this is not the "full screen exclusive mode" from the previous slide.



In either case, do not forget to add an ESC key listener so users can exit the full-screen mode. 

The effect of ESC key could be a switch to windowed mode, or even to exit the game.

Image Fundamentals: Transparency 

Images can be   



When using images for game objects, you probably want a transparent image. 



opaque: every pixel will be visible transparent: every pixel will be either completely visible, or completely "see-through" translucent: pixels have a partial see-through effect.

Otherwise the background of the image (e.g. what is outside of the "character" in the bounding box) will appear as a "white box" or similar.

Translucency is useful if you want some special/ realistic effects.

Transparency and java.awt.Color 



In Java, every colour has an "alpha value" that indicates its transparency. It can be indicated either in the range 0..1 or 0..255. 







Check with the API doc which range is used by a particular method or constructor, e.g. some methods take a float (0..1) while other return an int (0..255)

An alpha value of 0 means that the color is completely transparent. An alpha value of 1 (resp. 255) means that the color is completely opaque. Sometimes called "alpha channel" that is in addition to the RED, GREEN and BLUE channels of RGB.

Creating an Image with Transparency   







Image editor (like PhotoShop, Paint.NET, GIMP, ...) Load raw image that has no transparency. Some editors have an automatic "background selector".  Called a "magical wand" After applying this, you may need to smoothen the outline of the main image element using a "lasso" tool. "Crop" the main element  For some editors, you may have to "invert the selection" and then apply "cut" (or similar) Save in png format  NOT in jpeg format as that does not support transparency.  gif format only has 8-bit colours.

Loading Images 

Recommended method: ImageIO.read() in package javax.imageio 



Image loading involves file I/O, decoding, caching 



This means it is a relatively slow operation.

Make sure you load images before the GUI starts. 



This avoids the need for MediaTracker or ImageIcon hacks you may find in some older Java games literature.

Don't wait with loading until you use the image for the first time - you may get a temporary "freeze" of the screen until the paintComponent invocation with the load operation has finished.

You can read images from local files as well as from arbitrary URLs.

Class BufferedImage 

Method ImageIO.read() returns a BufferedImage which is a subclass of java.awt.Image. 

 

This is main Image class we will use. Once we have an object of this class, it means the image is stored in main memory and is "ready to use".

Each image has a width and a height. BufferedImage also has some support for "tiles" which can be useful if you computer game is tiled, e.g. the graphics are made up from smaller tiles that can be composed to produce different images.

Organising Image Loading  







Your game program may feature a lot of images. Class utilities.ImageManager below defines a couple of auxiliary loadImage methods that know about the directory where images are kept and the image file extension.  It also defines a Map which keeps track of loaded images. By default the file name is used as key, but you can also associate an image with another name. In my own Asteroids implementation, all images are declared and loaded (using a static block) in class Constants. But you could also define a game-specific ImageManager class in which you declare and load all images. Whichever approach you choose, I would suggest to bundle the loading of images into a couple of methods/ classes.

public class ImageManager { final static String path = "images/"; final static String ext = ".png"; static Map images = new HashMap(); public static Image getImage(String s) { ... } public static Image loadImage(String fname) throws IOException { ... } public static Image loadImage(String imName, String fname) throws IOException {...} public static void loadImages(String[] fNames) throws IOException { ... } public static void loadImages(Iterable fNames) throws IOException {...} }

Drawing Images 

Classes Graphics/ Graphic2D have various drawImage() method. The simplest version is: boolean Graphics.drawImage( Image img, int x, int y, ImageObserver observer);







// top left corner

The ImageObserver argument relates to asynchronous loading of images. It is not needed when drawing a BufferedImage, so it is usually set to null. Other drawImage variations allow you to specify which part of the image to draw, and how to scale it. Affine transformations are often useful to translate, rotate or scale images, see earlier lecture.

More Java2D Operations  

Drawing text (font attributes, anti-aliasing, ....) Fancy line styles and fill patterns 



Including gradients and textures with patterns

Image filters 

blur, sharpen, ... (-> lighting effects, texturing)



Create image Clipping the draw region. ... Please see online Java2D tutorial for examples.



Efficiency of operations depends on CPU/ GPU.

  

Sprites 







A sprite is an image or animation that is associated with some (game) object as part of a larger scene.  the term implies some "graphical overlay" Sprites can represent  player characters  NPC: "non player characters"  as well as other game objects including power-ups Please see design of a Sprite class on next slide(s)  Alternative design: extend GameObject class  Note use of affine transforms in the draw()-method Animating a rotating Asteroid (or another object) using a precomputed sequence of "rotated images" is likely to be faster  see "sprite sheets" in later lecture

public class Sprite { public Image image; public Vector2D position, direction; public double width, height; … public void draw(Graphics2D g) { double imW = image.getWidth(null); double imH = image.getHeight(null); AffineTransform t = new AffineTransform(); t.rotate(direction.angle(), 0, 0); t.scale(width / imW, height / imH); t.translate(-imW / 2.0, -imH / 2.0); AffineTransform t0 = g.getTransform(); g.translate(position.x, position.y); g.drawImage(image, t, null); g.setTransform(t0); } }

Power-Ups 

Beneficial game objects that   





improve offensive or defensive capabilities, improve/ restore "health", "energy", possibly even to the extend of giving "extra lives" or help the player in other ways

May be pre-placed, distributed randomly, or carried by some NPCs. Might be collectable by   

simple "touch" beating an enemy, or require a sequence of user actions.

Game Worlds and Mini-Maps 



Many games feature larger worlds where the screen only displays a part of the world at any given time. In such games, the player-character is typically kept in the centre (area) of the screen.  





Player movement appears as if a the wo...


Similar Free PDFs