COPYRIGHT NOTICE. COPYRIGHT 2007-2019 by Clinton Jeffery.
For use only by the University of Idaho CS 328
Actually, the waterfall model is "all wrong" for game development, which
should probably follow a "natural evolution", meaning working prototypes
start out with textual depictions of core game logic, proceed to simple
graphics, and then evolve more powerful 3D and multi-user capabilities.
Whether or not each individual game should evolve in this manner, there
is another possible application of the metaphor.
Corollary Evolutionary Hypothesis: just as embryos, fetuses, and infants
are claimed to go through the earlier stages of evolution of our species,
our individual capability to write computer games might best be "grown"
one game at a time by implementing each and every game and game genre
that was previously done by our ancestors. When we can clone Wow,
then we can talk about implementing Wow's successor.
Having defined an interface, you use it my writing code that declares things
as Speaker, for example a parameter. LibGDX does this in spades, internally,
that is largely how it makes use of your code.
newPong.java
Lecture 4
BBlearn
The course should be available now on Bblearn, with a place for you to turn
in your homework #2. Let me know if it is not.
The Six Core Modules of GDX
All are accessed through a core class called Gdx
. Gdx is
a class not an object, but since you use it almost entirely in terms of
static member variables, it can be thought of as a
Singleton
(design pattern) or as a package of global variables (ugh).
They are introduced here in terms of responsibilities, with rudimentary
example fragments to give the flavor of their API and typical uses.
-
Gdx.app
, the application module
- logging, shutdown, "persist" data, query platform and memory, and
send messages to the render thread (if your program has other threads)
Gdx.app.setLogLevel(Application.LOG_DEBUG);
Gdx.app.exit();
Preferences p = Gdx.app.getPreferences("settings.prefs");
p.putInteger(key, val);
p.flush();
switch(Gdx.app.getType()) {
...
}
Gdx.app.postRunnable(new Runnable() {
@Override
public void run() { ... }
});
-
Gdx.graphics
, the graphics module. Interestingly, there is
a separate module Gdx.gl
that provides almost exactly the API
of OpenGL, the industry standard portable 2D and 3D graphics API. OpenGL
is a whole big can of worms. Simple GDX programs might only directly
reference it to clear the screen each frame; most graphics rendering is
done through other GDX classes that turn around and call OpenGL.
- frame timing, display size, ...
Gdx.graphics.getDeltaTime();
Gdx.graphics.getFramesPerSecond();
Gdx.graphics.getWidth();
Gdx.graphics.getHeight();
Gdx.gl.glClearColor();
Gdx.gl.glClear();
-
Gdx.input
- you have to implement an InputProcessor and set it.
Warning: screen coordinates might have nothing to do with
output display coordinates!
Gdx.input.setInputProcessor();
Gdx.input.getX();
Gdx.input.getY();
Gdx.input.isTouched();
Gdx.input.isButtonPressed();
Gdx.input.isKeyPressed();
-
Gdx.files
, the file system module
- The lowest common denominator is really low.
Gdx.files.internal(); // app folders, e.g. assets
Gdx.files.external(); // ~, or SD card
-
Gdx.net
, the network module
- HTTP, and lower-level networking
Gdx.net.sendHttpRequest();
Gdx.net.cancelHttpRequest();
Gdx.net.newClientSocket();
Gdx.net.newServerSocket();
-
Gdx.audio
, the audio module
- play sound f/x or music files. WAV, MP3 [, OGG].
Gdx.audio.newSound();
Gdx.audio.newMusic();
- Last time we talked a bit about several of the classes.
- We started with LauncherBeta, a trivial main() that creates a Game
object and passes into the application instance.
- For desktop, the application instance is a LwjglApplication;
libGDX attains portability by allowing other platforms to provide
alternative Application objects.
- In a top-down introduction, we then looked at StarfishCollectorBeta,
a subclass of GameBeta that (a) declares a turtle, starfish, ocean, and
win message, (b) initializes them and places them on a "main stage",
and (c) provides an update method that terminates if the turtle ever
manages to catch (overlap) the starfish.
- We finished with a brief look at the Turtle class, which is an ActorBeta
that responds to user arrow key presses by moving.
- If you read the book, it will tell you about GameBeta and ActorBeta,
two classes Dr. Stemkoski uses to extend libGDX with common behavior
for the games in his book.
- GameBeta is a subclass of Game.
- ActorBeta is a subclass of Actor,
that draws a 2D image, and provides "hit detection" for whether
another object overlaps its bounding rectangle.
- There is no Game.java or Actor.java in this game's code.
Big questions interrupt us here:
- in an object oriented system, how do you tell which objects are
in application source code, and which are in libraries?
- Where is the API and description of the Actor class, that
Dr. Stemkoski's ActorBeta extends?
Just keep following what you can see, and asking questions
- In order to extend Actor, the ActorBeta code has to import it.
- The import is for a
com.badlogic.gdx.scenes.scene2d.Actor
.
- If you google
libgdx Actor class
you get a link to its
reference docs, which list Actor's many methods (act(), draw(), ...).
- Our textbook
says that many kinds of Actor objects are placed in a container object
called a Stage that calls their methods at appropriate times each frame.
-
GameBeta bundles in a Stage object and calls the stage act() to process
input (i.e. controls), and update() to update the model,
and then a draw() to render the view. It does this each frame.
- As OOP goes, Dr. S's code is "relatively" clean. There is still a lot
of libGDX functionality for us to absorb.
Summarizing all the libGDX functionality from Chapter 2 is almost a matter
of: listing all the imports in all the .java files, and for each import,
seeing what instances of that class are used and what methods of that class
are called. This would give you "the subset of libGDX used by this game",
a tiny projection of the overall gigantic libGDX library. Let's brute force
that a bit.
The following summary is all the imports from com.badlogic.gdx.... in
chapter 2.
- scenes.scene2d.Actor
- These are "entities" that may appear on the screen and act/interact. API:
act(), draw(), setSize(), setPosition(), setVisible(), getX(), getY(), ...
- graphics.Texture
- These are 2D images, typically loaded from files. API:
getWidth(), getHeight()
- graphics.g2d.Batch
- Batch objects bundle together a number of graphic operations to be
buffered and delivered together to the GPU. API: setColor(), draw()
- graphics.g2d.TextureRegion
- A TextureRegion allows a portion of a texture to be used as a texture,
and allows multiple images to be substituted in as called for. API:
setRegion()
- graphics.Color
- Holds r, g, b, and a values that may be used in output operations.
- math.Rectangle
- Basic math abstraction. API: setSize(), setPosition(), overlaps()
- Game
- Implements default values for (7?) standard methods required by an
application (launcher) object. API: create(), initialize(), render(),
update(), ...
- Gdx
- As discussed above, this is a facade for libGDX's
major components. It just provides access to a lot of static
functions. API: input.isKeyPressed(), graphics.getDeltaTime(), gl.glClearColor(), gl.glClear()
- graphics.GL20
- OpenGL 2.0 graphics things. API: GL20.GL_COLOR_BUFFER_BIT.
- scenes.scene2d.Stage
- Container that holds a bunch of Actors, and tells them all what to do
at the appropriate times in the game's main loop.
API: addActor(), act(), draw()
- backends.lwjgl.LwjglApplication
- The platform-specific code to run a Game on Lightweight Java Gaming Library
- ApplicationListener
- In general, Listener's are objects that ask to be informed of, and handle,
when asynchronous events occur in other objects. Imported in StarfishCollectorBeta but not used.
- Input.Keys
- Keys.LEFT, Keys.RIGHT, Keys.UP, Keys.Down
Lecture 5
A Bit O Java
Java has generics, which are the same thing as C++ templates.
public class Pair<T>
{
private T first, second;
...
Pair(T t1, T t2) { first = t1; second = t2; }
// methods operate on the T's
}
Pair<String> z = new Pair<String>("Foo", "Manchu");
Value Based Animation
- Value based animation is animation by modifying values.
For example, change a position
coordinate or rotation angle and redraw.
- You can think of it as "programmed animation",
since your source code determines the animation.
- The main benefit of the Stage/Actor Scene2D optional part of GDX
is that GDX Actors can do many kinds of Actions
- Actions often take an amount to modify a value, and a duration of
how long in which to do it. They then do the interpolation of
in-between calculations for the frames between start and end duration.
- Example use (creat an Action object, attach it to an actor):
Action spin = Actions.rotateBy(180, 2); // take two seconds to turn around
starfish.addAction(spin);
- Actions all run in parallel by default. To run in sequence you can add
an action after all current actions are completed
starfish.addAction(Actions.after(shift));
- There are also iterations:
Actions.repeat(spin, 2)
Actions.forever(spin)
- Other Actions in Ch3:
Actions.moveBy(dx, dy, duration) // "translate"
??? // must be one for "scale"
Image Based Animation
What can we Learn from Pong
Anything that appears in both Starfish Collector and Pong must be
indispensable.
- ShapeRenderer for drawing graphics
- sr methods: setProjectionMatrix, begin, [draw various graphics], end
GDX Naming
Perhaps the reason GDX is often called libGDX is that
if you google GDX you get some Gold Mining financials.
I will use both GDX and LibGDX and consider the terms interchangeable.
Beware openJDK
- LibGDX has at times required real Oracle Java.
- You may have to uninstall OpenJDK if it is on your system, or set
your PATH environment variable, and possibly JAVA_HOME, to put Oracle
Java ahead of OpenJDK Java.
Old Example: on my Fedora Linux at home, /usr/bin/java on my PATH was OpenJDK,
and when I installed the Oracle Java, it was placed in
/usr/java/jdk1.8.0_65/bin/java. I had to modify my ~/.profile to include
/usr/java/jdk1.8.0_65/bin prior to /usr/bin. I set my JAVA_HOME to
/usr/java/jdk1.8.0_65. Then gdx-setup ran for me.
As a reminder, gdx-setup may take a couple few minutes to run, if it is
downloading gradle and libgdx components. If you get to BUILD SUCCESSFUL
then you finished OK. If you do NOT get BUILD SUCCESSFUL, you are NOT OK.
Similarly, when you first run ./gradlew desktop:run it may take
a couple minutes.
A gdx-setup script
If you run linux, you may want to place this somewhere on your path
(as file gdx-setup), and marked
user-executable (chmod u+x), this might save you some typing.
#!/bin/sh
java -jar ~/bin/gdx-setup.jar $*
- In addition to the harmless "Android SDK location" message noted earlier,
gdx-setup issues a "Usage:" message (normally an error exit
message when a UNIX command has been used incorrectly) on healthy startup.
- If you supply ALL arguments listed in the Usage: message, gdx-setup will
run entirely noninteractively; it could thus be run from inside an IDE or
whatever.
- When I did this, the build failed because RoboVM elements
did not download OK, so there might or might not be a problem with it.
A 2nd look at the GDX "life cycle"
Interface ApplicationListener has six methods, one for each system state:
void create();
- typically: create/initialize all game state / objects
void render();
- Erase/(re)draw application display, from scratch. Called once/frame.
Many GDX applications also execute game logic from here, and even read
user input.
void resize(width, height);
- Called when a resize() occurs.
void pause();
- Phone apps should be pausable at any time. How does this work for
real-time multiplayer games? You also get a pause() on your way to a
dispose().
void resume();
- If you pause, you can be resumed. Its unclear whether you're guaranteed
that a resume() will happen; the app could get terminated if memory
reclamation requires it.
void dispose();
- This is what you are called on your way to an exit. It should just free
resources.
The following dubious flow chart was given in a GDX book by Andreas Oehlke
for the GDX life cycle. How does it compare with the simpler game loop from
the last lecture?
Starter Classes
In a GDX application,
the actual main() procedure is placed in a platform-specific
starter class. It lives way off in a separate directory
from where your application code lives. Minimize the code you
change here, if you want to remain portable.
package com.packtpub.libgdx.demo;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
public class Main {
public static void main(String[] args) {
LwjglApplicationConfiguration cfg = new
LwjglApplicationConfiguration();
cfg.title = "demo";
cfg.width = 480;
cfg.height = 320;
new LwjglApplication(new MyDemo(), cfg);
}
}
lecture 6
Physics
These notes are from [Oehlke]. Stemkoski Ch 3 provides a similar treatment.
- Most(?) of game physics is about motion.
- every actor or game entity is extended with fields to support
motion, usually in an abstract superclass
- Space is wasted on non-mobile objects; could create
MobileAbstract and ImmobileAbstract classes,
but unless you have a Lot of non-mobile objects, it won't matter.
- Units are generally meters/second and the like. It is probably
important for you to mention your units explicitly in comments.
- They will get multiplied by a (float deltaTime)
to provide per-frame proportional changes.
- Vector2's used for independent x and y values
-
velocity
- speed, m/s
-
terminalVelocity
- minima/maxima
-
friction
- negative force, basically deceleration, applied equally to x and y,
should not be a Vector2 but it is
- acceleration
- m/s/s just like in high school physics
- bounds
- a "bounding box", type Rectangle, for collision detection
public void update(float deltaTime)
{
updateMotionX(deltaTime);
updateMotionY(deltaTime);
position.x += velocity.x * deltaTime;
position.y += velocity.y * deltaTime;
}
"updateMotion*" should be called "updateVelocity*". It means to apply
friction and acceleration. [Oehlke] uses the following algorithm.
Stemkoski's is similar (is it identical?).
- if object is moving, apply friction, which always pulls toward 0
- apply acceleration
- cap abs(velocity) at +/- terminalVelocity
Updating Motion
Stemkoski does things a fair bit differently, actually.
protected void updateMotionX (float deltaTime) {
if (velocity.x != 0) {
// Apply friction
if (velocity.x > 0) {
velocity.x =
Math.max(velocity.x - friction.x * deltaTime, 0);
} else {
velocity.x =
Math.min(velocity.x + friction.x * deltaTime, 0);
}
}
// Apply acceleration
velocity.x += acceleration.x * deltaTime;
// Make sure the object's velocity does not exceed the
// positive or negative terminal velocity
velocity.x = MathUtils.clamp(velocity.x,
-terminalVelocity.x, terminalVelocity.x);
}
GDX MathUtils
- fake class, mostly static methods
- providing faster, less accurate mathematics for games.
- sometimes: use floats instead of doubles
- sometimes: use table lookup instead of (slow) instruction
- double clamp(x, min, max);
GDX Application Code Notes
- "implements ApplicationListener" vs. "extends ApplicationAdapter"
-
extends ApplicationAdapter
, used by gradle gdx-setup,
inherits
default versions of the six functions, so you override as needed.
- Most smaller GDX apps benefit from subclassing ApplicationAdapter
- If you are overriding all six, you get nothing from ApplicationAdapter
so you might as well just implement.
- OrthographicCamera
- for 2D; for 3D one might switch to a perspective camera
- SpriteBatch
- In this context, a "batch" is a collection of graphics operations,
buffered together for performance reasons. Similar concepts (e.g.
Display Lists) are found in many graphics APIs.
- Texture
- A texture is a 2D image used within a larger scene. It is often
a fill pattern. In 3D (triangular slices out of) textures are used
to cover the faces of triangles. Also in 3D: many platforms/APIs
require that the 2D texture images have width and height that are
powers of 2. More about this later.
- TextureRegion
- Actually, in the industry it is pretty routine to lump together a whole
ton of textures within a single, large 2D image, and then use different
little slices out of a texture to define the fill patterns for different
objects within a scene.
- Sprite
- A sprite is an object used for placement of a graphical object
within a scene. Traditionally the term was used for 2D and if the
placement was just an (x,y) a sprite was about the same as a texture.
Because it is OpenGL-based,
in GDX the concept of a sprite is a bit more sophisticated.
It knows how to rotate and scale, and has a
reference to a TextureRegion from which its visual contents are taken.
Class Diagram for a GDX Game
From Nair/Oehlke's libGDX book:
Pixmaps and Textures
-
We have previously seen Textures, which are an abstraction
of (usually static) 2D images in GPU memory.
- Pixmaps are 2D arrays of pixels in CPU memory. They can be
drawn on, and then written out to textures and thence onto the screen.
- Pixmap pixmap = new Pixmap(width, height, Format.RGBA8888);
- from com.badlogic.gdx.graphics.Pixmap
pixmap.setColor(1, 0, 0, 0.5f);
pixmap.fill();
// Draw a yellow-colored X shape on square
pixmap.setColor(1, 1, 0, 1);
pixmap.drawLine(0, 0, width, height);
pixmap.drawLine(width, 0, 0, height);
// Draw a cyan-colored border around square
pixmap.setColor(0, 1, 1, 1);
pixmap.drawRectangle(0, 0, width, height);
- Texture texture = new Texture(pixmap);
- The way for you to think about it is: Pixmap lives in CPU memory,
texture lives in GPU memory. Pixmap is easily writable; texture
is almost write-once, read-only.
Polling vs. Event-driven input
Code like
if (Gdx.input.isKeyPressed(Keys.A)) ...
is an example of polling, "the continuous execution approach".
- this can peg your CPU at 100%; very bad, especially on battery devices.
- if many input entities have to be polled each frame,
(say, every key on the keyboard), your
frame rate will plummet and your game will be less than responsive
even if burning 100% CPU is OK.
- Instead, you want your code to be called for events.
LibGDX Topic for Today: InputProcessor
- A set of event "callback" methods
- abstracts traditional "raw-mode" low-level keyboard
(games do not typically use higher-level cooked keyboard input,
a "text input widget" GUI element would be the logical next level)
- low-level mouse/touch input (separate mechanism for higher-level gestures)
- LibGDX target platforms might or might not have keyboard/mouse;
using this interface limits portability unless you provide
alternative input mechanisms. Example: PageUp key for
scrolling up vs. scroll wheel on mouse vs. "pan" gesture on touch screen
InputProcessor vs. InputAdapter
- As per ApplicationListener vs. ApplicationAdapter,
LibGDX offers both an interface and a class that you can extend.
- Generally, you can
extend InputAdapter, unless your class needs to extend something else.
-
This is an important part of GDX's lowest common denominator
API that must run on all supported platforms.
InputProcessor interface summary
- boolean keyDown(int keycode);
- start of a key "press"
- boolean keyUp(int keycode);
- end of a key "release"
- boolean keyTyped(char character);
- higher-level: this key was pressed. less common in video games because...
- boolean mouseMoved(int x, int y);
- pointer location, possibly without any key involved
- boolean scrolled(int amount);
- scroll wheel, amount may be positive or negative
- boolean touchDown(int x, int y, int pointer, int button);
- start of mouse click or finger "press"
- boolean touchDragged(int x, int y, int pointer);
- pointer movement while a pointer "pressed"
- boolean touchUp(int x, int y, int pointer, int button);
- end of a mouse click or finger "release"
- compare lower-level keyDown/keyUp vs. higher level keyTyped
- switch from chain of "if" statements to a switch, e.g. in keyUp()
- "lowest common denominator" API has many pieces that don't exist on
various platforms
- a game that has to run everywhere has to have multiple ways of doing
everything
- scroll amount might be only 1 or -1 on some platforms
- touch "pointer" more or less which finger, in chronological order of
chording
- apparently no info on force/size/angle of touch
lecture 7
Public Service Announcements
1. Did you all see the game job hiring flyers posted in the JEB CS area?
2. If anyone might be interested in internships, here's one.
The Science & Engineering community at Hill AFB is accepting resumes for
the Premier College Intern Program (PCIP) for qualifying JUNIORS interested
in a paid summer internship (starting May 2020) that may lead to a full time
developmental position after graduation.
This program offers Juniors who are currently enrolled fulltime (12+
semester hours) in a qualifying 4-year program, a paid internship lasting 12
weeks in the summer prior to their senior year at the GS-04, Step 1 pay
grade (approximately $14.54/hr).
Requirements to qualify for the PCIP (not all inclusive) are as follows:
- U.S. Citizenship
- Enrollment as a full-time student in a baccalaureate degree program
- Maintain a cumulative GPA of 2.95 or higher
Once students successfully complete the internship, they will return to
school to complete their degree. Upon graduation, they may be
non-competitively converted to a position within the 3-year PALACE Acquire
(PAQ) Logistics Trainee Program. PAQ trainees will start at the GS-07 level
and be eligible for promotion every 52 weeks through program completion.
PAQ graduates are placed on permanent positions within the GS workforce.
LOCATION OF INTERNSHIP: Hill Air Force Base (AFB), UT
FOR INTERESTED STUDENTS TO APPLY: Please send Resume, Current Transcripts,
and Proof of Full-Time Enrollment to: Cindy Pestotnik at
cynthia.pestotnik@us.af.mil.
DEADLINE: 30 Sep 19
Recent Student install-libGDX Experiences
- Oracle JDK 8 221 is probably the best (most stable) for the libGDX
version distributed as gdx-setup.jar at present
- the BlueJ OpenJDK 11 ran fine with the libGDX .jar's that came from
our book website
- Someone tried Oracle JDK 12 while I watched; it issued runtime
warnings about illegal reflection that made me very nervous.
- Folks running 64-bit Windows: don't try to use 32-bit Java, that
didn't seem to go well for someone
Bit o Java for the Day
ArrayList is dynamically-resizable generic list, with set operations.
import java.util.ArrayList;
public class bar {
public static void main(String[]args) {
ArrayList<String> names = new ArrayList<String>();
names.add("Lee");
names.add("Daniela");
names.add("Chris");
// names.size() would return 3
// names.contains("Lee") would return true
for (String name : names) {
System.out.println(name);
}
}
}
The BaseActor class will extract/return for you (sub)lists of actors that
are instances of a particular type/class on a given Stage:
for (BaseActor rockActor : BaseActor.getList(mainStage, "Rock"))
It's a bit "meta", but here is the getList method:
public static ArrayList getList(Stage stage, String className)
{
ArrayList<BaseActor> list = new ArrayList<BaseActor>();
Class theClass = null;
try { theClass = Class.forName(className); }
catch (Exception error) { error.printStackTrace(); }
for (Actor a : stage.getActors()) {
if ( theClass.isInstance( a ) )
list.add( (BaseActor)a );
}
return list;
}
Physics
Compare this version of basic Physics (from
Starfish Collector) with last lecture's.
- libGDX Vector2 represents a vector
as (x,y) components, but provides API functions that let you treat
a vector as a (length,angle) and change length without changing
angle or vice-versa, i.e. it does a bit of trigonometry for you
- friction (deceleration) should probably be a Vector2 but isn't
public void applyPhysics(float dt)
{
// apply acceleration
velocityVec.add( accelerationVec.x * dt, accelerationVec.y * dt );
float speed = getSpeed();
// decrease speed (decelerate) when not accelerating
if (accelerationVec.len() == 0)
speed -= deceleration * dt;
// keep speed within set bounds
speed = MathUtils.clamp(speed, 0, maxSpeed);
// update velocity
setSpeed(speed);
// apply velocity
moveBy( velocityVec.x * dt, velocityVec.y * dt );
// reset acceleration
accelerationVec.set(0,0);
}
Collision Detections, first pass
Collision Response and Prevention
Suppose you detect a collision. What do you do? Depends on the game
mechanics and the objects involved. You might:
- halt the player's attempted move in his tracks
- slow/reduce the move, calculate a smaller allowed move, by using
smaller time increments or by "backing up" until the overlap goes away
- change the movement direction, e.g. sliding along a wall
- interpret the collision in-game as, e.g. an attack, a blow, or an
"eat" or "pick up" operation on the object you collided with (e.g. Pac Man)
libGDX provides a relatively low-pain way to just prevent the overlap.
You can ask overlapConvexPolygons to calculate a MinimumTranslationVector,
if that's what your game calls for:
public Vector2 preventOverlap(BaseActor other)
{
Polygon poly1 = this.getBoundaryPolygon();
Polygon poly2 = other.getBoundaryPolygon();
// initial test to improve performance
if (!poly1.getBoundingRectangle().overlaps(poly2.getBoundingRectangle()))
return null;
MinimumTranslationVector mtv = new MinimumTranslationVector();
boolean polygonOverlap =
Intersector.overlapConvexPolygons(poly1, poly2, mtv);
if ( !polygonOverlap )
return null;
this.moveBy( mtv.normal.x * mtv.depth, mtv.normal.y * mtv.depth );
return mtv.normal;
}
World Boundaries
Given a world coordinate system (0,0,width,height) Rectangle named
worldBounds
, here's code to force any given (rectangle-based)
Actor stays within the world.
public void boundToWorld()
{
if (getX() < 0) setX(0);
if (getX() + getWidth() > worldBounds.width)
setX(worldBounds.width - getWidth());
if (getY() < 0) setY(0);
if (getY() + getHeight() > worldBounds.height)
setY(worldBounds.height - getHeight());
}
A camera that follows (is centered on) an Actor may need to STOP following
the Actor when it gets near an edge, or the camera's sliding window will be
showing things beyond the world boundaries.
Screens
- Game objects can set an active screen, changing major modes of operation.
- Screen objects provide render() and update() methods, and can contain
Stage objects, etc.
The Game object delegates responsibility for those operations to the
active screen, when screens are used
- Starfish Collector ends up with at least two screens: the current game
level, and the "menu screen" that is displayed on startup and when paused.
What I Learned about LibGDX on my Sabbatical (TTP)
- Turning The Pages is a "reader" to allow public access to a few
rare/fragile historical books kept at NLM/NIH
- I implemented a prototype "next generation" app for two of their books
(replacing Flash with HTML5...using libGDX)
- Not a game, but learned stuff useful for libGDX games
- HTML5 target more fragile than desktop, but reasonably capable
- Maya animations in LibGDX
- Camera controls for layering
a 2D HUD with translucent widgets atop a 3D scene
Will show show you bits of code when it sheds additional light on libGDX
topics we are talking about.
We will use it for examples when hitting specific topics.
Because a software engineering project deserves documentation.
Check out the class diagram, etc.
InputProcessor in the main TTP class
- implement InputProcessor for key/mouse events
- I did not like libGDX's GUI features, rolled my own for HUD.
This meant I had to figure out which event applied to which screen
button on my own. My widgets perform a hittest(x,y), to help out.
- keyDown(code) switches to values like Input.Keys.UP.
Videogames tend to start continuous actions until key is released.
- keyUp(code) mostly sets continuous actions to 0 to turn them off
- scrolled(direction) handles a scroll wheel
- mouseMoved(x,y) handles pointer motion, often ignored unless dragging
- normal_x() and normal_y() convert pixels to world normal coords
beware: viewport skews this
- touchDragged(x,y,p) - maybe also mouse drags?
- touchUp(x,y,p,b) - hit tests on buttons, if mode is OK and button is on
- touchDown(x,y,p,b) - a no-op, but beware: event system sometimes has a
touchDown with no corresponding touchUp
- do not be confused by touchDown(float x,float y,float p,float b) -
GestureListener requires this, if you use gestures. TTP implements
an empty one, i.e. it does not use it and I did not sort out why
the competing definitions are in LibGDX.
lecture 8
Games and Society
See highlights from Games and Society
Go through at least the first 15 or so slides.
Thoughts on Games and Society
- punishments/consequences (to game developers) for violating
social norms highly inconsistent
- many other countries not so flexible, e.g. German laws against
putting Nazi's in games
- Games have many positive social effects (weddings due to on-line chats
or directly in-game in EverQuest or WoW)
- Lots of more direct positive outcomes from games, such as learning.
- Game addiction is a huge problem and game developers are incentivized
to make it as addictive as possible.
- Money for games comes at the expense of... what?
Reading Assignment
Read Stemkoski Chapter 4, it is pretty short, 15 pages or so.
Using Stemkoski Code in a gdx-setup based project
- Motivation: use non-Bluej IDE, newer libGDX, port to more platforms...
- put Launcher.java under desktop in directory gradle expects it
- all other *.java files go in core/src/.../...
- use packages as setup by gdx-setup (add package declaration to 12 .java)
- add packages to "qualified names" in calls to getList() and count()
(learned the hard way; ClassNotFoundException, NullPointerException)
- assets in core/assets/
- drop the "assets/" from names of assets in source (about 10x)
(learned thanks to a student office visit)
- no +libs .jar's, use them wherever gdx-setup accesses them from
Arcade Games
Asteroids (1979) is one of the earliest and simplest arcade games.
SpaceWar in the 1960's appears as though it was clearly inspiration,
but Asteroids was an incredible phenomenon when it came out.
Stemkoski implements a very rough prototype for a similar game in
Chapter 4 called Space Rocks.
Chapter 4 discusses
- using an InputProcessor (we have already discussed)
- using a InputMultiplexer and allowing both Screen and Stage objects to
be input processors
- Actors that are attached to other Actors (thrusters, shields and
explosions)
- Actions that impose a delay and remove an Actor after a period of time
- a wraparound space
- collision detections resolved by adding Actors or Actions, and by the
remove() method
Let's see if we can find all these as we look through the code.
lecture 9
Status of HW#2
- As of 9/15 10pm, 20 submissions (27 expected).
- If you have not finished
your homework #2, I expect a visit or message from you updating me on your
status.
- I downloaded the 20 solutions and it was 186MB. Individuals ranged from
675k to 29.3MB. If your .zip file was greater than 10MB for Starfish
Collector, Sharknado Edition, you should probably find out why, and
learn to submit a .zip file with just the source code and assets and
build files necessary to build your project. For example, turning in a
full copy of the libGDX source code might not be very friendly of you.
- I graded those of the solutions that ran out of the box for me, using
instructions from a README (execution instructions are required unless
you can run via "gradlew desktop:run" or via "package.bluej").
- The rest of you who have turned in HW#2 are in grade purgatory.
- Given instructions, I am willing to try to run from non-BlueJ IDE's,
but not willing to work very hard at it.
- If your program doesn't run easily for me, plan to demo it,
either coming in or via Zoom. But its best if it runs for me;
perhaps resubmit with instructions, after you doublecheck if it
runs for you from your .zip
Status of HW#3
HW#3 is posted. 3 weeks for an arcade game. Give me more of a real-ish
playable game this time.
Reading Assignment
If you haven't yet, read Chapters 4-6 of the text.
LibGDX Notes
- Have you found libGDX's online reference yet?
- Useful to find the lists of methods for the many predefined classes,
like Pixmap
Game State, part 1: a Taxonomy of Visible Game Objects
- Game State == "the Model" in MVC
- Whole game state == combination of static and dynamic state
- Much of static state can be created by artists and storytellers
- Do hardwired animations (e.g. Maya models, or six sprites that
make up an animated GIF) count as static state?
- Computer scientists implement the dynamic parts.
- Some games are 90% static/constant, 10% player changing their state.
- Other games are maybe < 10% static, almost entirely mutable things,
changing continuously.
- Decorations vs. Entities
- Most games have stateless eye candy (decorations) that contrasts with
in-game entities that feature either
mutable-state and/or interactive features that figure into the
game mechanics.
- Objects vs. Items
- "Levels" in games such as side-scrollers divide entities up
into two categories: objects that are permanent parts of the
level, and items that go away when the user picks them up.
- You could represent Objects and Items by the same mechanism.
- But do you want to. Why or why not?
- BaseGame is a Singleton
- "naked
new
" objects in LevelScreen.java
- naive brute force for-loops, a CPU hole
- Consider setBoundaryPolygon, from BaseActor.java.
- is a really dumb boundaryPolygon even worth it?
- potential redundant computations of boundaryPolygon, another CPU hole
lecture 10
Chapter 5 is long (43 pages) and covers a lot. It will take multiple lectures
to hit the highlights, and I also want to be talking about game design.
Java Feature of the Day: Lambdas
- Classic lambdas:
- functions without names, from functional programming
paradigm.
- Part of bigger themes such as: constructing new code on the fly,
modifying it at runtime, passing it as a parameter, making code
more flexible, etc.
- But in Java you can't store methods in variables or pass
them as parameters (darn!)
- Java lambdas
- classes without names
- you can mimic a function lambda by creating a class with only one
method
- Declare code that wants to use such a thing to take a parameter
that complies with an interface for one function.
public interface Function {
public void run();
}
// code that wants to use
public class ClientCode {
private Function f;
public void setFunction(Function func) { f = func; }
public void useFunction() { f.run(); }
}
// "anonymous inner class"
client.setFunction( new Function() {
public void run() { System.exit(0); }
}
);
// lambda, method name etc. "inferred"
client.setFunction( () -> { System.exit(0); } );
Text and User Interfaces
Stemkoski Chapter 5 presents materials on displaying text and implementing
user interfaces in two contexts:
- extending Starfish Collector, and
- a short technology demo for a 2D graphical text adventure akin to the
1980's games such as Leisure Suit Larry or King's Quest, or maybe
Escape from Monkey Island.
LibGDX Features relevant to Text
- You can draw text in libGDX using either the BitmapFont class or the
freetype extension for scalable .TTF fonts
- freetype requires that you click a checkbox when
you run gdx-setup.jar, or include some extra .jar files in your +libs
Basic Gdx Text Display
Adapted/updated non-trivially from gamefromscratch.com:
- HelloWorld.java
- font.draw() takes pixel cartesian coordinates from BOTTOM left corner
- GDX comes with only ONE (1 !) font, a bitmap font (Arial 15).
- Arial 15 is probably too small for most games
- Bitmap fonts do not scale well (pixelation)
- Your game may of course add other (perhaps bigger) fonts as assets
- see the BitmapFont reference page for more
- file format BMFont is not very standard, you might have to (learn how
to) generate your own for other fonts in order to use this API much
- bitmap fonts don't scale/resize super-well
- Good enough for games like Hangman
Heiro
FreetypeFontGenerator
- One way to use truetype, the whole point of which is to do scalable
fonts, is to instead use them to generate BitmapFonts
- This is slow enough that you probably don't really want to do it
on the fly at runtime, but you are doing it under program control,
with Java code
- might avoid shipping a lot of fat bitmap fonts with
product and instead generating them the first execution of your app,
or on demand
- freetype fonts are often used on the fly, without a BitmapFont (?)
FreeTypeFontGenerator fg = new
FreeTypeFontGenerator(Gdx.files.internal("assets/myFont.ttf"));
FreeTypeFontParameter fp = new FreeTypeFontParameter();
fp.size = 48;
fp.color = Color.WHITE;
fp.borderWidth = 2;
fp.borderColor = Color.BLACK;
fp.borderStraight = true;
fp.minFilter = TextureFilter.Linear;
fp.magFilter = TextureFilter.Linear;
BitmapFont bf = fg.generateFont(fp);
Bitmap Fonts in Labels
For use in GUI's, e.g. with multiple buttons of similar appearance:
public static LabelStyle labelStyle;
...
labelStyle = new LabelStyle();
// labelStyle.font = new BitmapFont(); // Arial 15
labelStyle.font = new BitmapFont(Gdx.files.internal("myFont.fnt"));
...
private Label starfishLabel;
starfishLabel = new Label("# remaining: ", labelStyle);
starfishLabel.setColor(Color.CYAN);
starfishLabel.setPosition(20, 520);
uiStage.addActor(starfishLabel);
...
starfishLabel.setText("# remaining: " + num);
Buttons
- Creation similar to label, followed by adding a "Listener"
- Listener is a lambda that gets called when button is pressed.
- Lambda will have to access other portions of MVC, probably forcing
you to have some global(s) (a.k.a public statics)
ButtonStyle bs = new ButtonStyle();
// ... init buttonstyle parameters
Button restartButton = new Button (bs);
restartButton.setColor(Color.RED);
restartButton.setPosition(720,520);
uiStage.addActor(restartButton);
...
restartButton.addListener(
(Event e) -> {
if (!(e instanceof InputEvent) ||
!((InputEvent)e).getType().equals(Type.touchDown) )
return false;
StarfishGame.setActiveScreen( new LevelScreen() );
return false;
}
);
lecture 11
Game Design
We did slides 1-28
lecture 12
Game Design
A couple more points from the slide deck. We may talk about design more
later.
- Its all about interactivity
- don't make choices for the player
- keep cut scenes brutally short
- Characters and personality make a game world engaging
- bold stereotypes are better than colorless characters
- use action, not description/exposition to define characters
- Gameplay Trumps Story
- if gameplay conflicts with story, gameplay must win
Stemkoski Notes (Ch5)
- Ninepatches
- Because GUI widgets need to look "decent" on a wide range of display sizes
- Accessibility Principle #1: Redundancy
- Important elements of the user interface might have 2+ ways of invoking
them, for mouse pointer vs. for touch vs. for keyboard for example.
LibGDX Tables
- This is a Scene2D GUI thing for laying out widgets scalably.
- In the history of GUI's, wars have been fought over whether to
leave the programmer in fine-grained complete control over the
details of their GUI, or whether to make the GUI responsible for
details, with the user just specifying general structure.
- This design question mimics that of typesetting documents: do you
prefer WYSIWYG, or LaTeX? Let the programmer specify structure and let
libgdx control layout details.
- "Smart" automatic layout semantics like this, akin to LaTeX doing the
layout for you, are awesome. They were wiped out in terms of market
share by GUI Interface Builder tools. Similarly, typesetting languages
like LaTeX or troff are prominent, but became a tiny fraction of the
market share once WYSIWIG word processors became ubiquitous.
- Summary: GUI classes are well but where the heck is a libGDX GUI builder?
- LibGDX Tables contain rows and columns of cells that
- start from the smallest size necessary for their content
- autoscale up to the largest element in their row/column
Table t = new Table();
t.add(a);
t.add(b);
t.row();
t.add(c);
t.add(d);
add()
returns the Cell instance that was created/added to the table
so you may well see some or all calls to add stored in member variables or
collection classes in order to provide more direct access to specific cells.
Table t = new Table();
foo = t.add(a);
bar = t.add(b);
t.row();
baz = t.add(c);
zap = t.add(d);
- Many cell methods can be called on these returned Cell instances,
controlling width, height, expansion/fill mode, alignment, etc.
Example: Starfish Collector title screen wants a big label spanning two
buttons underneath.
Table uiTable = new Table();
uiTable.setFillParent(true);
uiStage.addActor(uiTable);
uiTable.add(title).colspan(2);
uiTable.row();
uiTable.add(startButton);
uiTable.add(quitButton);
As another example, from the main screen HUD:
uiTable.pad(10);
uiTable.add(starfishLabel).top();
uiTable.add().expandX().expandY();
uiTable.add(restartButton).top();
This places the points on the left and the restart on the right, both at the
top of the HUD.
A Good LibGDX Game Example from the Web
With particularly good running commentary. Consider this a reading assignment.
These are things you should learn as a by-product of looking at the
Rain Catcher game.
- Setup practice. You should actually create a few projects with
gdx-setup to get a feel for its parameters and options, and find out
(for example) the simplest package organization that it will take.
- Androidisms. I am not emphasizing Android in this class, but if
we did, we'd probably want to learn more about AndroidManifest.xml.
- Launcher. This launcher creates a window that's 800x480. But is
that virtual 800x480, or physical 800x480? Compare the desktop,
HTML, and android launchers. What is common? What is different?
- Cameras let you set a virtual resolution, independent of physical.
Cameras have to be applied to SpriteBatch objects. camera.unproject()
is interesting, what does it do?
- Assets. Many uses of Gdx.files.internal("..."). Preloading at
create() time vs. loading on demand. Caching loaded assets vs. reloading.
Under what circumstances do assets need to be freed?
- Rectangles. Note that they are pure math. Graphical rendering is
completely separate.
- What is the difference between an array in Java, an Array in libGDX,
and an ArrayList in Java?
- In general, games that play in real-time should avoid garbage collections,
by statically allocating all resources. What does this entail?
- How does this program implement raindrops falling at 200 pixels/second?
- How does this program organize its Model View Controller?
- What is an Iterator and how do they work?
lecture 13
Q&A
From a student:
- GUIs use an event-processing loop, but games like Pong and Breakout
seem to want to own the event loop, what do I do?
- Obvious options include: 1) implement your own GUI so you don't give up
control of the main loop, or 2) chop your action into tiny slices and
make them fit in whatever correct spots the GUI allows such thing.
GUIs often offer either an "idle function" (called when
no user input is available) or a "clock" or "alarm" event (called
periodically at programmable time steps). Perform one iteration of what
used to be your main loop either each time you get control, or more
likely, once every so many calls, after you can tell enough real time
has elapsed. LibGDX tends to tell you (or allow you task) how many
milliseconds since the last render; some things like 2D animation can
be broken smoothly into such chunks, while other things need to only
happen when larger time units have passed.
Another Look at Resources/Assets
"Resources" was the Windows dev. term; "assets" seems to be the GDX term.
Means: images, sounds and the like that are bundled with and used in your
program.
- GUI programs, beginning with Xerox Alto, use
Large amounts of static data along with the code.
- The (binary image/sound) data is edited differently than code
- (ancient) DOS Model: ship a directory, load from files
- pros and cons; "open", assets easy to steal, insecure etc.
- (classic) Mac Model: deal with this in the OS
- filesystem has resource fork and data fork
- (classic) Windows Model: deal with this in the compiler/linker
- "Resource Compiler" turns .bmp into .obj for example
- Cloud Model: (re)download as needed from URLs
- popular in web apps and other thin clients. Note that this imposes
severe constraints on asset richness and/or internet speed/quality.
- Tablet/Phone Model: ship a directory, load from files
- package it as an "App" instead of a .zip file, includes installer etc.
Collections of Images and Texture Atlases
- texture atlas (sprite sheet) == image that holds subimages
- fewer textures on GPU improves performance
- in OpenGL (re)binding the current texture is expensive
- using different parts of it is cheap
- package non-power-of-two images inside large power-of-two texture
- extreme case: entire games textures, one giant 2^n x 2^n image
- CanyonBunny example: 11 textures go into one atlas:
- an auxiliary description file, a .pack file, contains coordinates
of subimages.
GDX Texture Packer
Loading and Tracking Assets
We have already seen:
Texture texture =
new Texture(Gdx.files.internal("texture.png"));
...
batch.draw(texture, x, y);
- assets allocated on GPU
- not covered by Java's garbage collector
- pause event implies you may have lost your asset, reload
- explicit dispose() allows you to free an asset
- AssetManager class
- tracks a list of assets for you
- can load assets in background
- Oehlke writes his own wrapper class on top of it
- a singleton class: Assets
- implement Disposable and AssetErrorListener
- create an assetManager, call assetManager.setErrorListener()
- call assetManager.load() with the name of the .pack file
- call assetManager.finishLoading() before using any assets
- inner classes proposed for organizing and caching assets:
public class Assets implements Disposable, AssetErrorListener {
public class AssetBunny {
public final AtlasRegion head;
public AssetBunny(TextureAtlas atlas) {
head = atlas.findRegion("bunny_head");
}
}
public AssetBunny bunny;
...
public void init(...) {
...
TextureAtlas atlas = new assetManager.get(packfile);
...
bunny = new AssetBunny(atlas);
}
...
}
- load the bunny, through the manager, in create() and on each resume()
- After going to all the trouble to make separate named fields for
each TextureRegion in the Assets class, Oehlke creates an Array of
TextureRegion objects that are references to the ones in the manager,
an array (lowercase) of Sprite objects, and
uses for-loop to bind sprites to TextureRegions.
Level Data
In real games, level design is a major issue; a level editor often has to
be built.
- What is an appropriate file format for describing a game level?
- Need to specify terrain, objects, monsters/spawnpoints, etc.
- CanyonBunny uses a classic technique: use an image file, interpret
colors of pixels as types of terrain, objects, etc.
- white=player, purple=feather, yellow=coin, green=rock, black=space
- can use GIMP, Windows Paint etc as level editor
- magnify/zoom image so pixels are large enough to pick out
CanyonBunny Source
I'm sharing a bunch of sidescroller sample code from a program called
CanyonBunny that was written by Andreas Oehlke and described in a book on
libGDX. I used the book in a previous instance of CS 328 but students and
I felt that it was not really great as a textbook for this course.
Nevertheless, feel free to check it out:
- Oehlke's book is called Learning LibGDX Game Development; its second
edition cheekily adds a new first author but most of the book is Oehlke's
- Oehlke's
CanyonBunny source on github (at least it looks like its his;
a bunch of other github projects for canyonbunny look like they
were done by students working through his book)
Discussion of Launcher Icons
What the program looks like when it is not running. You would see it in
the File Explorer, for any code or data file which, if clicked, would
run your program. Classically icons in
Windows were small, e.g. 16x16 or 32x32. Modern systems may use 64x64
or larger, or allow multiple icon sizes for different purposes, e.g.
in a menu (small) versus in a file explorer window (large).
- platform dependent == technique may be different each platform
- Windows had (has?) its own special file format for launcher icons
- GDX uses nice generic .png files, any filename you want, but
following bullets appear to be Android-specific. Research how to
do it e.g. on desktop?
Android | Desktop
|
---|
|
- In the desktop launcher, you can use the LwjglApplicationConfiguration
method: addIcon("filename", Files.FileType.Internal). Its docs say:
Typically three icons should be provided: 128x128 (for Mac), 32x32 (for Windows and Linux), and 16x16 (for Windows). But fundamentally, this is an
incomplete answer, since it is a runtime answer. It will show in the taskbar
as the program is running. What about before runtime?
- Placing an icon with a specific Windows program was traditionally a
task of a Windows application installer program
- There are ways associate the
icon with a file extension (such as .jar or .class), but that's too general
- For traditional Windows binaries, the icon was bundled into the executable
by a resource compiler and/or linker.
|
lecture 14
Midterm Date Selection
- There will be a midterm in this class.
- The midterm will be based on
material from the textbook and the lecture notes as presented in class.
- Primary topics: LibGDX architecture and Game Design
- Also fair game: things you should know from doing your homeworks.
- Date: October 18 by popular demand
Signs and Dialog Boxes [Stemkoski Ch5]
- this part of Ch 5 is about temporary text widgets that
can be made visible and invisible under program control
- Stemkoski's terminology is nonstandard
- A dialog box in normal GUI's is a rectangular popup that can contain
an arbitrary collection of
widgets to perform one task, or set a number of related attributes.
- A Stemkoski DialogBox is a large label or textbox that can
be turned on or off
- A Stemkoski Sign is a decoration, a small image (icon?) that looks
like a sign with a proximity sensor. It stores one sign's worth of
text in a String, but is too small to actually
display the text of the sign. Instead, if the turtle gets near it,
the Sign passes its String into a DialogBox (text box) that
pops up
- multi-use; can set font size, color, background color...
- longer text, uses Label's setWrap(true) to wrap for multiple lines
public class DialogBox extends BaseActor {
private Label dialogLabel; // ...
}
public class Sign extends BaseActor {
private String text;
}
... within the update method:
for(BaseActor signActor : BaseActor.getList(mainStage, "Sign")) {
Sign sign = (Sign)signActor;
...
boolean nearby = turtle.isWithinDistance(4, sign);
if (nearby && !sign.isViewing) {
dialogBox.setText( sign.getText() );
dialogBox.setVisible( true );
sign.setViewing( true );
} else if (sign.isViewing && !nearby) {
dialogBox.setText(" "); // probably unnecessary
dialogBox.setVisible(false);
sign.setViewing( false );
}
}
Cutscenes
- Using Actors and Actions to tell a non-interactive story, maybe what
these classes were designed for and are best at.
- Might be more like "in game" programmed behavior than like a real
cutscene that feels like a higher-fidelity cartoon style cinematic
- Code is mostly too trivial to go through blow-by-blow in class
- As a reminder, there is a rich suite of Action objects that can be
queued up and performed in order by an Actor.
- A Stemkoski SceneSegment class associates one actor with one action,
while a "Scene" class manages an ArrayList of such segments and plays
them in order
- Note: [Stemkoski's] Scene sounds like and uses, but is not part of
the libGDX
Scene2D package.
- I will go further and say: it contains StarfishCollector specific
assumptions about how to interpret directions/actions like
moveToOutsideRight()
- Note: According to [this post by Scene2D's author], Scene2D
might not be the best for action/arcade games...but
Stemkoski at least proves it is usable for those genres.
Each SceneSegment provides start(), finish(), and isFinished()
methods that facilitate a Scene's playback of a sequence of SceneSegments.
Class Scene's loadNextSegment() looks like:
public void loadNextSegment()
{
if ( isLastSegment() ) return;
segmentList.get(index).finish();
index++;
segmentList.get(index).start();
}
All of this delightful stuff is perhaps best viewed in the project
it was written for:
I really wanted the punchline of this one to be something based on
"my dog ate my homework", but its nothing so sophisticated as that.
Java Constructor Question, from another libGDX game
class Rock extends AbstractGameObject. Does its constructor inherit or
otherwise do the work of initializing fields performed in the
constructor for AbstractGameObject?
public abstract class AbstractGameObject {
public Vector2 position, dimension, origin, scale;
...
public AbstractGameObject() {
position = new Vector2();
dimension = new Vector2(1, 1);
origin = new Vector2();
scale = new Vector2(1, 1);
...
}
}
...
public class Rock extends AbstractGameObject {
...
public Rock() {
init();
}
}
A: Because the AbstractGameObject has a default constructor,
that is, one with no parameters, yes, it is called prior to Rock's
own constructor, on each Rock instance that gets created.
Tiling Images
- Tiled images are carefully crafted, perhaps by hand,
so that they can be repeated without a visible seam.
- Oehlke's rock internals and mountains, for example, are so repeated.
- Why: fill large area with small texture, without
stretching/distorting it
- Also: avoid busting your GPU memory and/or your memory bus bandwidth.
- Will matter more on low-end hardware, e.g. phones
For now, you do not need to be a tiling expert, but you should know what it
is, and know that you could learn to go do it (in PhotoShop or GIMP) if you
needed to. There are high powered algorithms to tile an image automatically,
but they give mixed results. For some applications they work well.
The best general introduction on tiling that I know is
Paul Bourke's Tiling
Textures on the Plane. It is old but good.
This is one of twelve (12!) draw() methods within class SpriteBatch.
Lots of complexity.
public void draw(
Texture texture,
float x, float y,
float originX, float originY,
float width, float height,
float scalex, float scaleY,
float rotation,
int srcX, int srcY,
int srcWidth, srcHeight,
boolean flipX, boolean flipY
);
lecture 15
LibGDX/Java Tidbit of the Day
- When you say Gdx.files.internal(filename) you get back a "handle"
- You might be feeding that data into another library such as JSON (and)
getting access to it at a higher level, but if you need to parse it
yourself...
- f.readString() reads entire file into one big string
- String[] fileData = f.readString().split("\n") gives you the whole file
as an array of lines
Swing vs. JavaFx
- Stemkoski mentions two alternatives for using conventional Java
GUI toolkits
- LibGDX apps mostly don't use either because they aren't portable enough
- Swing: ancient of days, from Sun/Oracle, it was the second big Java GUI
class library.
Widgets look like Windows 98 or something. No longer in development.
- JavaFX:
- intended to span desktop and web apps; phone ports of a commercial
variant, Gluon; doubt its easily portable across libGDX targets
- dialogs look more like win7/win10
- you may have had to install it in order to install BlueJ
- developed by Oracle, dumped off on OpenJDK product
- explicit multi-thread design
JFx File Chooser
Here's how to overly complicate a modal dialog:
new JFXPanel();
finished = false;
Platform.runLater( () -> {
FileChooser fileChooser = new FileChooser();
File file;
file = fileChooser.showOpenDialog(null);
if (file != null) fileHandle = new FileHandle(file);
finished = true;
}
);
while (!finished) {
// wait for fileChooser
}
return fileHandle
- employing a separate thread to run the GUI is a common trope; fine
- this implementation is embarrassing
Audio
- original PC's had nothing but a beep speaker
- you cannot really count on a user having audio when they play your game.
Except maybe if you write Rock Band.
- there is music, then there are effects
- music has duration, feels like threads. Think about
- mixing? is usually a bad idea
- looping? can easily get tiresome
- random select? ya fine, but little connection to rest of game
- intermittency? giving the user a rest
- transitioning from one track to another, e.g. at state or area changes
- special effects are usually very short. Think about
- mixing? more viable than for music.
- limited vocabulary? How many distinct sounds will a user interpret?
- literal vs. metaphorical.
- in higher-end games, audio is placed at locations, treated similar to
lighting
So, what will it take for us to get some more audio into our final
projects? What kinds of audio would be useful in your project? Where
can you get it?
I would think it might include:
- audio effects for combat or other game "states"
- alerts marking change of state in the game
(ship sighted, land ho, arrival in port etc)
- introduction and winning/losing
- theme music for major zones (soundscape?)
Audio in LibGDX
GDX plays
- .wav
- .wav is a Microsoft uncompressed PCM file format. Fine for short
sound effects, too big/fat for longer music
- .mp3
- heavily compressed, proprietary format
- .ogg files
- heavily compressed, open source format
We earlier saw the two interfaces
- Sound
- for effects < one second, load+decode hardwired together,
intended to be sent straight to the device
- Music
- for longer sounds, a stream-oriented interface, to keep memory
footprint from going crazy. Note streaming burns more CPU overall.
Sound interface
Sound sound = Gdx.audio.newSound(Gdx.files.internal("sound.wav"));
...
id = sound.play(...); // control later via id
...
sound.stop(); // or sound.stop(id);
sound.dispose();
Actually, there are three play() methods and three loop() methods
that can be used: no parameters, a volume (0.0-1.0) parameter, or
a three parameter call: volume, pitch, and pan. pitch raises (> 1.0)
or lowers(< 1.0) frequency and playback speed. pan moves the sound
left (< 0.0) or right (> 1.0). Volume, pitch, pan, and looping
of a sound can also be set by calling set methods and passing an id.
Music Interface
Music music = Gdx.audio.newMusic(Gdx.files.internal("music.mp3"));
...
music.play();
music.pause();
music.stop();
...
music.dispose();
There are setters on Music (setVolume, setPan, setLooping), as well as
two queries: isPlaying() and getPosition(), where get position can say
where (in ms) in the music the playback is at.
Lower Level Audio Interfaces
-
AudioDevice
and AudioRecorder
are low-level interfaces
- not available in HTML5!
- these are the audio equivalent of machine code, or lower.
Raw data is very general, but big/fat/noisy.
- not clear (to me) whether GDX supports also high-level
formats (e.g. MIDI) or very high level formats
(e.g. Color Computer BASIC
had a
PlayString
function that let you play tunes from a
human-readable ASCII encoding).
- Big device memories have rendered moot many of the higher level schemes
invented to do sound (and graphics too) procedurally.
To give you an idea of how low-level they are, AudioDevice let's you write
PCM-encoded audio samples directly to the device as an array of floats or
shorts:
AudioDevice audioDevice = Gdx.audio.newAudioDevice(samplerate, mono_p);
...
audioDevice.writeSamples(samples, offset, numSamples);
...
audioDevice.dispose();
AudioRecorder reads 16-bit PCM samples (a.k.a. short ints) into an array:
AudioRecorder audioRecorder =
Gdx.audio.newAudioRecorder(samplerate, mono_p);
...
audioRecorder.read(samples, offset, numSamples);
...
audioRecorder.dispose();
lecture 16
Coeur D'Alene Students
I am told your midterm must be taken at the NIC
Testing Center. Please locate that facility, and see what requirements
they will have for you to take that exam at 12:30 on October 18.
Java Tidbits of the Day
When to .size
and when to .length
-
.size()
is a method on an ArrayList
-
.length
is a built-in property of arrays, very awkward
for a non-object.
More inner classes
Stemkoski totally embraces "public inner classes" in his keystroke recorder.
Also, note the totally intentional use of float
vs.
Float
. What is the difference?
public class SongData {
private String songName;
private float songDuration;
...
public class KeyTimePair {
private String key;
private Float time;
...
}
}
Having fun with glorified "csv" files:
String rawData = f.readString();
String[] dataArray = rawData.split("\n");
song.setName(dataArray[0]);
song.setDuration(Float.parseFloat(dataArray[1]));
for (int i=2; i<dataArray.length; i++) {
String[] keyTimeData = dataArray[i].split(",");
String key = keyTimeData[0];
Float time = Float.parseFloat(keyTimeData[1]);
keyTimeList.add( new KeyTimePair(key, time));
}
super()
and super.foo()
In case I didn't discuss it, you should know in Java what these are
and when are they used.
-
super(arg1, ...)
- is how to invoke a superclass's non-default constructor, from within
your constructor, in order to get inherited variables initialized
-
super.foo(...)
-
is how to invoke a superclass's version of method foo(), only needed
if/when you override with your own (subclass) version of foo(), and
usually invoked from within your version of foo(), in order to add or
modify to what the superclass foo() is doing. This is called
method combination and is common in OOP.
Sound Generators
- sfxr
- RANDOMIZE until you are happy, then tune/tweak. Export to a .wav.
cfxr is a Cocoa (Mac) version of sfxr featuring slight improvements.
- bfxr
- adds save/load/mix of multiple sounds. lock button in individual
parameters. "Mutation" button makes it finally clear: these tools
are using the human as a neural network evaluation function, LOL.
Sound in Stemkoski Ch 6
Note typographic error (p.144): newSound should read newMusic
Sound effect = Gdx.audio.newSound(Gdx.files.internal("beep.wav"));
Music song = Gdx.audio.newMusic(Gdx.files.internal("song.mp3"));
- Audio volume from 0.0f to 1.0f, used in setVolume() method. "mute"=0.0f
- setLooping(true) to repeat
- play() to start playing
- dispose() to free up a Music when you are done with it
Tips on Sounds from "CanyonBunny" (Oehlke)
- place .wav's and such in assets/ (or assets/music and assets/sounds)
- wire into Assets an inner class AssetSounds with Sound object member
variables for each sound.
- register the sound files with the libGDX
asset manager
- write an AssetSounds init() method that takes an assetManager
and (re)loads all the assets on demand.
- calls to play sounds or music should use volumes given in options menu
- maybe add an AudioManager to provide a simple API and make it
impossible to forget to factor in options menu settings
Triggers for audio at various points in the code then look like:
AudioManager.instance.play(Assets.instance.sounds.jump);
Note the lame use of .instance
to refer to the
(singleton) instance of the class. Can you think of alternatives?
lecture 17
Reminder: Midterm October 18
- Half of October 16 class will be a midterm review.
- what fraction of audio budget should be effects vs. music?
- not all folks setup theater-quality audio
- not all folks care so much about audio
- audio support on linux has traditionally been a pain
- spotty driver support
- variations on mixer presence and capability
- competing API's/libraries
- most games playable without audio.
- are we giving up some great opportunities here?
- is this property important to retain, or not?
- easy to make a list of sounds to incorporate
- moderately easy to get a low-quality recording of each sound
- hard to get a high-quality recording of each sound
- hard to get folks to commit to multiple samples per sound
- hard to deal with very many concurrent sounds in complex situations
An old deck on Audio; some background concepts and theory
lecture 18
Due date (10/12) was tweaked so that it is not due midterms week.
An old deck on Audio; some background concepts and theory. Start from
slide 14.
Side Scroller (Stemkoski Ch7)
Chapter 7 is a short chapter.
You should read all of Stemkoski Ch. 7; I will hit the highlights in class.
An Infinite (1D) Playground
- An earlier lecture discussed seamless tiling textures
- Tiling textures can be generated algorithmically from
ordinary images with occasionally satisfactory results, or
via tools like Photoshop or Gimp.
- Although I am used to using seamless tiling to stretch a small
texture over a large in-game object or area, Stemkoski decides
to use two of them to stretch to cover infinity...
- He uses a window slightly smaller than one texture, and slides
a texture to the opposite side whenever a view moves fully off of it.
The relevant logic is an if-statement added to the act() method of such
objects. This code assumes the infinite-scrolling object ("sky" or "ground")
moves left at a constant rate. Some side-scrollers would instead be
following the player's avatar around.
public void act(float dt) {
super.act(dt);
applyPhysics(dt);
if (getX() + getWidth() < 0) {
moveBy( 2 * getWidth(), 0);
}
}
Gravity
public void act(float dt) {
super.act(dt);
setAcceleration(800);
accelerateAtAngle(270); // down, in degrees
applyPhysics(dt);
for (BaseActor g : BaseActor.getList(this.getStage(), "Ground")) {
if (this.overlaps(g)) {
setSpeed(0);
preventOverlap(g);
}
}
if (getY() + getHeight() > getWorldBounds().height) {
setSpeed(0);
boundToWorld();
}
}
Stars
- In Plane Dodger, the plane can score points by flying into "stars"
- New stars are created continuously
- Stars flow the opposite direction of the plane and de-spawn if
they make it off the far end of the screen.
- Stars "twinkle" by slightly growing/shrinking over time, done via
a scaling Action rather than an animation.
- Note: to avoid garbage collections, create a fixed set of star
objects and re-use them, rather than continually creating new ones.
(Does Stemkoski do that? No...). From the update() code:
starTimer += dt;
if (starTimer > starSpawnInterval) {
new Star (800, MathUtils.random(100, 500), mainStage);
starTimer = 0;
}
for(BaseActor star : BaseActor.getList(mainStage, "Star")) {
if (plane.overlaps(star)) {
star.remove();
score++;
scoreLabel.settext( Integer.toString(score) );
}
}
Enemy Planes
- Code is like stars
- you don't score points by hitting them; you die.
- you get a point for every enemy that you didn't hit, when it despawns
- Spawn interval reduces over time, down to a minimum
Java Enums
In a game like a side scroller, Java
Enums might be a good way to interpret the level data presented as
a big image, interpreting colors as contents at different locations within a
level. Code that reads/parses level information and instantiates a game's
data structures from it might be called a level loader.
Before looking at the level loader:
- In order to interpret pixels from the map, one can use a Java enum,
one value for each pixel color/interpretation.
- In C/C++, enums are just a set of named integer constants
- In Java, enums are a class with N instances, which are constants.
- If constructor takes params, enum's values should provide constants
to pass in to those params, and initialize corresponding fields
- Syntactic sugar for extending java.lang.Enum
- Compared with a Singleton, enums define N-gletons. Note though
that the Singleton design pattern is not so constrained regarding
the constantness of its member variables
- You can use a switch to select among enum values
public enum BLOCK_TYPE {
EMPTY(0, 0, 0), // black
ROCK(0, 255, 0), // green
PLAYER_SPAWNPOINT(255, 255, 255), // white
ITEM_FEATHER(255, 0, 255), // purple
ITEM_GOLD_COIN(255, 255, 0); // yellow
private int color;
private BLOCK_TYPE (int r, int g, int b) {
color = r << 24 | g << 16 | b << 8 | 0xff;
}
public boolean sameColor (int color) {
return this.color == color;
}
public int getColor () {
return color;
}
}
lecture 19
A Sample Sidescroller's class Level
Ideas from Oehlke's libGDX book, which describes a sidescroller game
called CanyonBunny.
- The whole game state is a combination of static and dynamic
- a "Level" class plays a role similar to Stage.
- Instead of one
gigantic collection of all the Actors, from which you constantly
have to ask for subsets by class, track the different kinds of
actors separately
- static level data includes decorations and static entities, e.g. obstacles
- dynamic things can be pre-specified in level map, spawn at specific
locations and times, show up randomly, or be player controlled
- class Level is an aggregate of all the static stuff.
- goal: read level map (image) and initialize this class from it
- method: brute force
public class Level {
... enum...
// objects
public Array<Rock> rocks;
// decoration
public Clouds clouds;
public Mountains mountains;
public WaterOverlay waterOverlay;
public Level (String filename) {
init(filename);
}
private void init (String filename) { ... }
public void render (SpriteBatch batch) { ... }
}
method init()
private void init (String filename) {
// objects
rocks = new Array<Rock>();
// load image file that represents the level data
Pixmap pixmap = new Pixmap(Gdx.files.internal(filename));
// scan pixels from top-left to bottom-right
int lastPixel = -1;
for (int pixelY = 0; pixelY < pixmap.getHeight(); pixelY++) {
for (int pixelX = 0; pixelX < pixmap.getWidth(); pixelX++) {
...
float baseHeight = pixmap.getHeight() - pixelY;
int currentPixel = pixmap.getPixel(pixelX, pixelY);
...
}
}
}
- after fetching the current pixel, an int,
- CanyonBunny uses
a long chain of if-statements to do the right thing for each
(color-coded) type of level content
- Easy to understand and verify correct, but
- does not scale well; code should use a switch.
- Figure out how to use enum types in switches,
or do not use an enum at all
if (BLOCK_TYPE.EMPTY.sameColor(currentPixel)) {
// do nothing
}
else if (BLOCK_TYPE.ROCK.sameColor(currentPixel)) {
if (lastPixel != currentPixel) {
obj = new Rock();
float heightIncreaseFactor = 0.25f;
offsetHeight = -2.5f;
obj.position.set(pixelX, baseHeight * obj.dimension.y
* heightIncreaseFactor + offsetHeight);
rocks.add((Rock)obj);
}
else {
rocks.get(rocks.size - 1).increaseLength(1);
}
}
// player spawn point
else if (BLOCK_TYPE.PLAYER_SPAWNPOINT.sameColor(currentPixel)) {
...
}
// feather
else if (BLOCK_TYPE.ITEM_FEATHER.sameColor(currentPixel)) {
...
}
// gold coin
else if (BLOCK_TYPE.ITEM_GOLD_COIN.sameColor(currentPixel)) {
...
}
// unknown object/pixel color
else {
int r = 0xff & (currentPixel >>> 24); //red color channel
int g = 0xff & (currentPixel >>> 16); //green color channel
int b = 0xff & (currentPixel >>> 8); //blue color channel
int a = 0xff & currentPixel; //alpha channel
Gdx.app.error(TAG, "Unknown object at x<" + ... + ">");
}
lastPixel = currentPixel;
}
}
... create and initialize decorations, which aren't part of the level map
User Interface
- GUI is a gargantuan (and inefficient) component of traditional
desktop applications
- Almost all games will not use traditional GUI components
- A lot of games' user interfaces are relatively primitive
So what is the user interface for a game?
- those parts of the display that tell the user how to interact?
- anything that is not part of the world/scene being rendered?
- ... since they stay fixed as the action moves, they are almost:
graphic objects attached to the camera, somehow
- "obvious" OpenGL approach: render scene, reset camera, and render GUI
- canyonbunny libgdx alternative: two camera objects
- one for the action and one for the GUI -- an interesting choice.
- The units for the GUI are not virtual world coordinates (meters)
- They are sized based on what makes sense for the font: pixels
In WorldRenderer:
private OrthographicCamera cameraGUI;
private void init () {
batch = new SpriteBatch();
camera = new OrthographicCamera(Constants.VIEWPORT_WIDTH,
Constants.VIEWPORT_HEIGHT);
camera.position.set(0, 0, 0);
camera.update();
cameraGUI = new OrthographicCamera(Constants.VIEWPORT_GUI_WIDTH,
Constants.VIEWPORT_GUI_HEIGHT);
cameraGUI.position.set(0, 0, 0);
cameraGUI.setToOrtho(true); // flip y-axis
cameraGUI.update();
}
public void resize (int width, int height) {
camera.viewportWidth = (Constants.VIEWPORT_HEIGHT
/ (float)height) * (float)width;
camera.update();
cameraGUI.viewportHeight = Constants.VIEWPORT_GUI_HEIGHT;
cameraGUI.viewportWidth = (Constants.VIEWPORT_GUI_HEIGHT
/ (float)height) * (float)width;
cameraGUI.position.set(cameraGUI.viewportWidth / 2,
cameraGUI.viewportHeight / 2, 0);
cameraGUI.update();
}
The actual display render() is then two calls to two render functions
using two cameras:
private void renderGui (SpriteBatch batch) {
batch.setProjectionMatrix(cameraGUI.combined);
batch.begin();
// draw collected gold coins icon + text
// (anchored to top left edge)
renderGuiScore(batch);
// draw extra lives icon + text (anchored to top right edge)
renderGuiExtraLive(batch);
// draw FPS text (anchored to bottom right edge)
renderGuiFpsCounter(batch);
batch.end();
}
public void render () {
renderWorld(batch);
renderGui(batch);
}
Side Scroller: potential next steps
- Actors
- Basic Game Physics
- Collision Detection
Actors
- "actor" == mutable or moving objects within a game
- The term is heavily overloaded in computer science;
there was even a programming language called Actor.
- The term "actor", like "agent", suggests an
entity that does things, i.e. one that is dynamic.
- Minimally it might be objects with non-trivial behavior/methods
- Maximally it might imply a separate thread of control or
behavior backed by game A/I
- CanyonBunny "actors": the bunny head, the feather, and the gold
coin.
- The gold coin and the feather are decorations, except with
a bool value (if it has been collected, it does not render) and a point
score awarded upon collection.
Bunny Head
It has to be able to move, jump, fall, and fly. It doesn't have to
violate OO principles of encapsulation with all these public variables,
but it does it anyway.
public class BunnyHead extends AbstractGameObject {
private final float JUMP_TIME_MAX = 0.3f;
private final float JUMP_TIME_MIN = 0.1f;
private final float JUMP_TIME_OFFSET_FLYING = JUMP_TIME_MAX - 0.018f;
public enum VIEW_DIRECTION {LEFT, RIGHT};
public enum JUMP_STATE {
GROUNDED, FALLING, JUMP_RISING, JUMP_FALLING
};
public VIEW_DIRECTION viewDirection;
public float timeJumping;
public JUMP_STATE jumpState;
public boolean hasFeatherPowerup;
public float timeLeftFeatherPowerup;
public BunnyHead() { init(); }
public void init() { ... }
public void setJumping(boolean jumpKeyPressed) { ... }
public void setFeatherPowerup(boolean pickedUp) { ... }
public boolean hasFeatherPowerup() { ... }
}
The init() code is fairly obvious constructors and physics initalization.
Not shown in class. Note that enum constants are referenced
(ENUMTYPE.ENUMVALUE) and the .set() APIs for Points and Rectangles.
The jumping code tracks the bunny behavior through a four-state finite
automaton.
- you can't jump if you are off the ground,
unless you have the feature.
- Also: you stop rising as soon as you let the jump key up.
public void setJumping (boolean jumpKeyPressed) {
switch (jumpState) {
case GROUNDED: // Character is standing on a platform
if (jumpKeyPressed) {
// Start counting jump time from the beginning
timeJumping = 0;
jumpState = JUMP_STATE.JUMP_RISING;
}
break;
case JUMP_RISING: // Rising in the air
if (!jumpKeyPressed)
jumpState = JUMP_STATE.JUMP_FALLING;
break;
case FALLING:// Falling down
case JUMP_FALLING: // Falling down after jump
if (jumpKeyPressed && hasFeatherPowerup) {
timeJumping = JUMP_TIME_OFFSET_FLYING;
jumpState = JUMP_STATE.JUMP_RISING;
}
break;
}
}
The real work in update() is mostly inherited, but the bunny overrides
and adds:
- calculation of what direction the 2D char is facing
- calculation of when the feather runs out
public void update (float deltaTime) {
super.update(deltaTime);
if (velocity.x != 0) {
viewDirection = velocity.x < 0 ? VIEW_DIRECTION.LEFT :
VIEW_DIRECTION.RIGHT;
}
if (timeLeftFeatherPowerup > 0) {
timeLeftFeatherPowerup -= deltaTime;
if (timeLeftFeatherPowerup < 0) {
// disable power-up
timeLeftFeatherPowerup = 0;
setFeatherPowerup(false);
}
}
}
The bunny's motion updater modifies default velocity updates using
the current jumping state.
protected void updateMotionY (float deltaTime) {
switch (jumpState) {
case GROUNDED:
jumpState = JUMP_STATE.FALLING;
break;
case JUMP_RISING:
// Keep track of jump time
timeJumping += deltaTime;
// Jump time left?
if (timeJumping <= JUMP_TIME_MAX) {
// Still jumping
velocity.y = terminalVelocity.y;
}
break;
case FALLING:
break;
case JUMP_FALLING:
// Add delta times to track jump time
timeJumping += deltaTime;
// Jump to minimal height if jump key was pressed too short
if (timeJumping > 0 && timeJumping <= JUMP_TIME_MIN) {
// Still jumping
velocity.y = terminalVelocity.y;
}
}
if (jumpState != JUMP_STATE.GROUNDED)
super.updateMotionY(deltaTime);
}
Bunny render()
The render() for the bunny overrides to change the color while powered up.
- it is a very object-oriented thing to have subobjects (say,
actors within a scene) know how to take care of themselves, e.g. render().
- Look at Spritebatch's
draw() method in more detail.
public void render (SpriteBatch batch) {
TextureRegion reg = null;
// Set special color when game object has a feather power-up
if (hasFeatherPowerup) {
batch.setColor(1.0f, 0.8f, 0.0f, 1.0f);
}
// Draw image
reg = regHead;
batch.draw(reg.getTexture(), position.x, position.y, origin.x,
origin.y, dimension.x, dimension.y, scale.x, scale.y, rotation,
reg.getRegionX(), reg.getRegionY(), reg.getRegionWidth(),
reg.getRegionHeight(), viewDirection == VIEW_DIRECTION.LEFT,
false);
// Reset color to white
batch.setColor(1, 1, 1, 1);
}
Completing the Level Loader
When we looked at class Level before, the loader:
- took an image file as input
- using a doubly-nested for-loop, it created (potentially) an object
at each pixel location.
- should have used a switch, based on the color in the image
- instead it was a long chain of if-statements and calls to
sameColor(pixel) to figure out what object is in that location.
Now that we have discussed dynamic objects (Actors?), we can finish the
Level loader, which was in class Level's init() method.
- There is some
dumb typecasting in here; btw Java typecasting is a heavyweight runtime
check, not just a compiler handwave.
- Speaking of handwaving, the .set() method bothered me before: why
was its x a nice clean
pixelX
, but its y was not a
nice clean pixelY
?
- y coordinates grow opposite of pixmap y coordinates
- ...so baseHeight === windowheight - pixelY.
- a obj.dimension.y scale factor applied..., but it is always 1 (?)
- textures draw from lower-left corner of image ("offset height", <= 0)
- so your set position == windowheight - pixelY + offsetheight
- Carmen SanDiego moment: what scales us up from pixel coordinates in the
level map (128x32 map, greatly scaled up), to pixel coordinates in the full-size render (800x480 window into much "larger" world)?
...
goldcoins = new Array<GoldCoin>();
feathers = new Array<Feather>();
...
for (int pixelY = 0; pixelY < pixmap.getHeight(); pixelY++) {
for (int pixelX = 0; pixelX < pixmap.getWidth(); pixelX++) {
...
... if's for static entities such as Rocks, seen earlier
...
// player spawn point
else if (BLOCK_TYPE.PLAYER_SPAWNPOINT.sameColor(currentPixel)) {
obj = new BunnyHead();
offsetHeight = -3.0f;
obj.position.set(pixelX, baseHeight * obj.dimension.y +
offsetHeight);
bunnyHead = (BunnyHead)obj;
}
// feather
else if(BLOCK_TYPE.ITEM_FEATHER.sameColor(currentPixel)) {
obj = new Feather();
offsetHeight = -1.5f;
obj.position.set(pixelX, baseHeight * obj.dimension.y
+ offsetHeight);
feathers.add((Feather)obj);
}
// gold coin
else if (BLOCK_TYPE.ITEM_GOLD_COIN.sameColor(currentPixel)) {
obj = new GoldCoin();
offsetHeight = -1.5f;
obj.position.set(pixelX, baseHeight * obj.dimension.y
+ offsetHeight);
goldcoins.add((GoldCoin)obj);
}
}
}
The code for methods render() and update() just show class Level to be a
true aggregate that calls render() and update() on all the Level's entities.
lecture 20
Bouncing and Collision Games (Stemkoski Ch 8)
Short-ish (18 pages), about a game kinda too simple for us to spend too much
time on, so we are just looking for interesting highlights again.
- an actor that tracks the mouse via Gdx.input.getX()
- this is polling again, instead of being event-driven
- the mouse might be at a different location now than it was at the
time the last processed event occurred
- linear interpolating the bounce angle from the position on the paddle
where the ball hits via MathUtils.lerp( fromV, toV, percent)
- ( what about the angle at which the ball hits?)
- lerp on floats is just a way to pick an intermediate point
- should at least mention here that libGDX has several
forms of
Interpolation available besides MathUtils.lerp().
- Vector2 and Vector3 have interpolate methods on Vectors
- Several built-in non-linear Interpolation classes, mostly for
programmatically in-betweening calculations used in animation
- the bad idea of counting how many bricks are left via
BaseActor.count(mainStage, "Brick")
- sound as a true afterthought (last 1.5 pages of text)
- 5 sound effects, 1 music that loops
We did slides 1-19.
lecture 21
- midterm exam Friday. CDA Students: schedule your midterm time slot with NIC Testing Center ASAP; if its slots are full you may have to take your test earlier than Friday. Report problems and Check Your Email for any last minute adjustments to the Test Plan.
- midterm review Wednesday. However, I have updated the midterm review materials and placed them nearby/below in the lecture notes in case you want to
study before then, for example if you were in CDA and had to schedule your test
earlier than Friday for some reason.
Drag and Drop (Stemkoski Ch 9)
This is a 20 page chapter. With screenshots, that is still pretty short, and
the games are perhaps not interesting to me personally. The user input
techniques are applicable to other games, so they are still worth studying.
The class Card developed here could be used in a wide range of games that
use cards. Perhaps additional card-related classes (Deck, Hand, ...) would
also be useful.
Per earlier discussion of input, Drag and Drop is based on the InputProcessor
interface, which has you implement the following event handlers. x and y are
offsets relative to the object clicked, i.e. where within the object
was it touched (or clicked).
- touchDown(ev, x, y, pointer, button)
- touchDragged(ev, x, y, pointer)
- touchUp(ev, x, y, pointer, button)
The main warning I had from previous libGDX experience is: there is not a
100% perfect sequence of touchDown()... followed by touchUp(), at least,
not on every libGDX platform. We will see whether Stemkoski deals with
this at all.
Identity Crisis: this
vs. self
One of the downsides of lambdas, it turns out, is that they run in a
temporary this
object. It is faintly absurd that you can
access the enclosing class instance's member variables but don't have
a way to reference that instance without creating a fake "this" variable.
DragAndDropActor and DropTargetActor: beware Java's Single Inheritance
Although it is the most natural thing in the world to create lots of
subclasses of BaseActor to describe different behaviors, I am not sure all
of them should always be mutually exclusive. In Java the solution is
always interfaces.
Operations on Pairs of Objects: another Object Conundrum
- In pure object-oriented style, all variables are private and an object
is accessed purely through its public methods.
- However in real life, LOTS of operations involve close interaction
between two or more objects, and need to access member variables
from them, perhaps man of them, very frequently in a loop.
- in LibGDX there are plenty of operations, such as Rectangle .overlaps(),
with this property.
- Some languages elaborately subvert their OO Encapsulation in order to
accommodate reality (e.g. C++ "friend functions").
- Some languages just live with the poor performance.
- A high percentage of cases where this is an issue is when two
instances of the same class need to work with each other.
- Hmm, I wonder if there is a language where member variables are
accessible from other instances of the same type (but not other types)
TextureRegion.split(texture, width, height)
- a nice image splitter library function!
- Static function takes a Texture and returns a 2D array of TextureRegion
- Regular version of this function splits a TextureRegion
into smaller TextureRegions
- Warning, works from top left corner, most of libGDX works from
bottom left.
Slides 20-43.
lecture 22
CDA Midterm Exam Revised Instructions
CDA students: our CDA Associate Chair Bob Rinker went over and looked at
the NIC Testing Center, and decided it would be better for you to take the
exam on Friday at 12:30 at the Den if possible. See Carrie Morrison there,
who will proctor your exam. If you cannot take the exam at the Den at
12:30, the NIC Testing Center is your backup plan and the midterm can be
taken there at your appointed time; in that case, your midterm grade next
Monday might or might not include your Midterm exam grade, since I might
or might not receive the exam from them before the weekend.
Midterm Exam Review
- The exam will be "comprehensive" and emphasize topics on which we
spent time in class or on homework assignments.
- The exam will be "closed book, closed notes"
- Dr. J exams typically consist of various question types, including
short answer, paragraph answer, and coding-type questions. If there
is a coding question, code will
be requested in pseudocode form, meaning syntax is anything-goes
but logic and data structure must be understandable.
- You should review all the assigned reading materials.
- You should review the major game genres we have discussed so far,
and be prepared to compare or contrast them.
- You should review 2D graphics and simple animation techniques that might
be used in arcade-style games
- You should think about how various kinds of games use "models" or
simplified approximate representations things that would be much larger
or more complex if they were real. For various games' models,
what information and how much detail is represented and
how is it depicted textually or graphically in-game.
- You should think about how the player's character or avatar
is modeled in various
first-person genres (side scroller, first person shooter, RPG...).
- Highlights from non-textbook lecture notes:
- History and evolution of computer games
- Game genres and mechanics
- Games and Society
- Game Design
- Highlights of Java (for GDX programmers)
- inheritance (extends), single inheritance, super
- everything is a class, static functions
- lambda functions
- no pointers, references
- Highlights of GDX
- overall architecture and organization
- six core modules
- ApplicationListener vs. ApplicationAdapter
- SpriteBatch, Sprite
- Texture, TextureRegion
- polling vs. event-driven input
- InputProcessor vs. InputAdapter
- Entities, Assets, AbstractGameObject
- TextureAtlases, TexturePacker
- Level Design, Level Data
- Game Physics (basic)
- Cameras, Multiple screens
- SceneGraphs, Scene2DUI, Stage, Actors, and widgets
- Special Effects
This old CS 328 midterm may be interesting to you. Since we changed
books, everything is different this year. Or is it?
Our exam might be a mixture of shorter questions inspired by
the reading assignments, plus something like this:
Collision Handling
After detecting the collision has happened, a game has to decide what to do
about it.
- If an avatar collides with a solid object, one might
simply be preventing solids from going through each other.
- Might have to determine one or more directions in which
movement is prevented
- Direction and velocity might immediately be changed
- Movement state (i.e. jumping/rising) might need to change
(e.g. to falling)
- Might need a sound effect ("ouch!") or calculate damage to the objects.
private void onCollisionBunnyHeadWithRock (Rock rock) {
BunnyHead bunnyHead = level.bunnyHead;
float heightDifference = Math.abs(bunnyHead.position.y
- ( rock.position.y + rock.bounds.height));
//
// Left/right collisions.
//
// only trigger if rock sticks up high enough. But note: abs() implies
// it will also occur if rock is too far below. Is this correct?
if (heightDifference > 0.25f) {
// if x > half way through the rock, bunny hit from the right
boolean hitRightEdge = bunnyHead.position.x > (
rock.position.x + rock.bounds.width / 2.0f);
if (hitRightEdge) {
bunnyHead.position.x = rock.position.x + rock.bounds.width;
} else {
bunnyHead.position.x = rock.position.x - bunnyHead.bounds.width;
}
// Does a left/right collision really preclude any other type?
return;
}
// A collision with rock was reported, but rock doesn't fit our definition
// of an obstacle to our side. It is above or below.
switch (bunnyHead.jumpState) {
case GROUNDED:
break;
case FALLING: // colliding while falling == not falling any more
case JUMP_FALLING:
bunnyHead.position.y = rock.position.y +
bunnyHead.bounds.height + bunnyHead.origin.y;
bunnyHead.jumpState = JUMP_STATE.GROUNDED;
break;
case JUMP_RISING: // colliding while rising == climb atop platform above?!
bunnyHead.position.y = rock.position.y +
bunnyHead.bounds.height + bunnyHead.origin.y;
break;
}
}
Input Processing
CanyonBunny calls the following method each frame from the WorldController's
update() method.
- Yes this is polling, which is bad.
- Checking if 3 keys or
a mouse/touch is happening each frame is apparently tolerable.
- a boolean in cameraHelper determines whether the
player has any key controls.
- (peek at cameraHelper in the
CanyonBunny source)
private void handleInputGame (float deltaTime) {
if (cameraHelper.hasTarget(level.bunnyHead)) {
// Player Movement
if (Gdx.input.isKeyPressed(Keys.LEFT)) {
level.bunnyHead.velocity.x =
-level.bunnyHead.terminalVelocity.x;
} else if (Gdx.input.isKeyPressed(Keys.RIGHT)) {
level.bunnyHead.velocity.x =
level.bunnyHead.terminalVelocity.x;
} else {
// Execute auto-forward movement on non-desktop platform
if (Gdx.app.getType() != ApplicationType.Desktop) {
level.bunnyHead.velocity.x =
level.bunnyHead.terminalVelocity.x;
}
}
// Bunny Jump
if (Gdx.input.isTouched() || Gdx.input.isKeyPressed(Keys.SPACE)) {
level.bunnyHead.setJumping(true);
} else {
level.bunnyHead.setJumping(false);
}
}
}
CanyonBunny: Losing Lives, and Ending the Game
public boolean isGameOver () {
return lives < 0;
}
public boolean isPlayerInWater () {
return level.bunnyHead.position.y < -5;
}
If the game is over, we need to print a message and stop processing long
enough for the player to digest their outcome. A variable timeLeftGameOverDelay
tracks how much time we've spent on it. WorldController's update() skips
input handling as long as you are in a "game over" state, which auto
resets after a bit. Other games would require a user action to reset.
public void update (float deltaTime) {
if (isGameOver()) {
timeLeftGameOverDelay -= deltaTime;
if (timeLeftGameOverDelay < 0) init();
} else {
handleInputGame(deltaTime);
}
level.update(deltaTime);
testCollisions();
cameraHelper.update(deltaTime);
if (!isGameOver() && isPlayerInWater()) {
lives--;
if (isGameOver())
timeLeftGameOverDelay = Constants.TIME_DELAY_GAME_OVER;
else
initLevel();
}
}
Multiple Screens
In order to have a separate main menu screen, what are our options?
- Add a variable to the Game class, to indicate what screen we are on.
Modify render() to switch on that variable.
- Subclass off of Game instead of off of ApplicationListener, and
implement the
Screen
interface on two or more screens.
Switch among them using Game's setScreen() method.
Canyonbunny uses the Screen interface; there may be good reasons on
some platforms.
- The Screen interface requires a class provide show() and hide() methods,
in place of create() and dispose()
- The main purpose of Screen is to make it cheap/fast
to switch back and forth among unrelated graphics, by not having to
allocate/deallocate resources each time.
- From Oehlke's class diagram below, what questions
would you have? Can you spot any bugs?
The AbstractGameScreen (re)allocates Assets whenever resumed, regardless of
which Screen is active at any given time.
public abstract class AbstractGameScreen implements Screen {
protected Game game;
public AbstractGameScreen (Game game) {
this.game = game;
}
public abstract void render (float deltaTime);
public abstract void resize (int width, int height);
public abstract void show ();
public abstract void hide ();
public abstract void pause ();
public void resume () {
Assets.instance.init(new AssetManager());
}
public void dispose () {
Assets.instance.dispose();
}
}
The actual instantiated subclasses of AbstractGameScreen include the
MenuScreen and GameScreen. MenuScreen eventually becomes artsy and has
a menu on it. At first, though, it is just a pass-through to the game screen.
In version 0 creates and switches to the game screen as soon as a touch
(including click) occurs.
public class MenuScreen extends AbstractGameScreen {
private static final String TAG = MenuScreen.class.getName();
public MenuScreen (Game game) {
super(game);
}
@Override
public void render (float deltaTime) {
Gdx.gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
if(Gdx.input.isTouched())
game.setScreen(new GameScreen(game));
}
@Override public void resize (int width, int height) { }
@Override public void show () { }
@Override public void hide () { }
@Override public void pause () { }
}
The game screen is longer, and contains mostly stuff we have already
seen, moved from the old Main class. Is there anything even remotely
fishy here? (The new CanyonBunnyMain, presented further below after this,
now extends GDX's Game class. Its create()
creates a new MenuScreen and sets it as the current screen.)
public class GameScreen extends AbstractGameScreen {
private static final String TAG = GameScreen.class.getName();
private WorldController worldController;
private WorldRenderer worldRenderer;
private boolean paused;
public GameScreen (Game game) {
super(game);
}
@Override
public void render (float deltaTime) {
// Do not update game world when paused.
if (!paused) {
// Update game world by the time that has passed
// since last rendered frame.
worldController.update(deltaTime);
}
// Sets the clear screen color to: Cornflower Blue
Gdx.gl.glClearColor(0x64 / 255.0f, 0x95 / 255.0f,0xed /
255.0f, 0xff / 255.0f);
// Clears the screen
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// Render game world to screen
worldRenderer.render();
}
@Override
public void resize (int width, int height) {
worldRenderer.resize(width, height);
}
@Override
public void show () {
worldController = new WorldController(game);
worldRenderer = new WorldRenderer(worldController);
Gdx.input.setCatchBackKey(true);
}
@Override
public void hide () {
worldRenderer.dispose();
Gdx.input.setCatchBackKey(false);
}
@Override
public void pause () {
paused = true;
}
@Override
public void resume () {
super.resume();
// Only called on Android!
paused = false;
}
}
In order to switch back and forth between screens:
- the WorldController calls new method backToMenu() whenever
the game is over or the user presses ESC.
- To do this the WorldController will have to have a reference to the Game
object, so add that to it constructor.
- Note: new MenuScreen object every time == memory leak.
private void backToMenu () {
// switch to menu screen
game.setScreen(new MenuScreen(game));
}
private Game game;
public WorldController (Game game) {
this.game = game;
init();
}
New, supershrunk CanyonBunnyMain
For what its worth, having emptied it out into screen classes,
it now just loads assets and sets the screen.
public class CanyonBunnyMain extends Game {
@Override
public void create () {
// Set Libgdx log level

Gdx.app.setLogLevel(Application.LOG_DEBUG);
// Load assets
Assets.instance.init(new AssetManager());
// Start game at menu screen
setScreen(new MenuScreen(this));
}
}
Canyonbunny's Menu Screen
How complicated is the following menu screen? How much code should it take?
For better or for worse, what I would have done as a single .png with a
input handler that checks mouse/touch within a couple squares on the right,
CanyonBunny does with an enormous additional set of high-powered GDX concepts
and classes that you can learn, if you want to use them in games.
Scene Graphs
A scene graph is a hierarchically organized structure of objects
similar to files and folders on a hard disk.
I thought that was all out of vogue in modern OpenGL/direct X, but
here it is again.
Scene2D
- mainly and ironically about input processing if you want to be able to
click on graphic objects
- functionality not built in to OpenGL and not part
of the earliest classic arcade games, although by the time of Centipede
and Missile Command such a functionality was obviously needed (and
special-cased).
- "hit detection" on Actors
- hierarchical; parents may intercept/handle input events, or
delegate to their children
Having said all that, in order to do hit detection, you have to know objects'
position and orientation, so of course it also has to take over rendering,
particularly animation.
- class
Stage
contains all the actors, i.e. it is the whole
scene graph.
- Actor and Stage both implement an
act(deltaTime)
method,
used the same as update().
-->
lecture 23
Where we are at in Class
- Not talking about midterm today, talk about it Wednesday.
- Midterm grades posted based HW#2, HW#3 and midterm scores;
feel free to visit with questions and concerns
- Lots more Stemkoski / libGDX to cover, going to start Chapter 10 soon
- Besides Stemkoski: going to finish discussion of game design/storytelling
and move on to topics in Game A/I
Slides 44-60. We might want to talk about slides 61-66, but then again,
maybe not necessary. I am fond of the short term memory limit, 7 +|- 2.
lecture 24
Mailbag
- Can we please have some more time to work on hw#4?
- Yeah OK, you can have through the weekend. The late clock will start
ticking Sunday night. But, I will go ahead and post HW#5. And this
game should be (for most of you) better than your last HW.
- Your talk about side scrollers sounds a lot like the topics of chapter
11, which describe platformers, do you want HW#4 to look like a side
scroller or a platformer?
- Hmm, it is worse than that. Chapter 10 on tilemaps is one way to
implement the level design for a platformer. I have not been a big
fan of tiled and do not intend to assign you another homework that is
a platformer. For HW#4 you should meet the specifications in the
way that seems best to you, I will be flexible about accepting a game
that scrolls over a larger area, whether it feels platformy or not.
Discussion of Midterm Exam
We only discussed slides 1-10. 11-12 look useful but not sure they are worth
going back in to that deck in class.
lecture 25
We had a nice long discussion of the CS 328 semester project.
Intro to Tilemaps and Tiled
We got through the first three pages of chapter 10.
Strategy games
In the old (pre-PC) days, there was this awesome magazine called "Strategy
& Tactics" from Simulations Publications, Inc.* Every couple months
subscribers received a new issue, with a new strategy game in it (usually a
"wargame"). A new map. New (cardboard counter) units. New rules. A new
virtual world in which to conduct strategic battles.
SPI was arguably the premier strategy gaming company, although its chief
competitor Avalon Hill Games was possibly larger, and there were 1-2 other
companies large enough to public magazines showcasing their new strategy
games on a regular basis. Most game companies didn't do strategy or didn't
come out with new titles very often.
What is strategy? What is tactics?
- strategy
- a plan.
how to string the battles together in order to win the war.
Usually coarse-grained or long-term.
Example: we will win the war by blockading the enemy's ports. They
will (eventually) run out of resources and will be forced to either
surrender, or leave their defensive position in order to obtain
food and supplies. (A strategy that works well for islands like, say,
England for example, or colonies dependent on port traffic).
- tactics
- techniques for using weapons/units in combination to win a battle.
Generally fine-grained or short-term.
Example: we will win the battle by sending drummers and torchbearers
(with torches lit, hidden under clay jars) around their camp. Suddenly
at like 2 in the morning, we will break the clay jars and make a ton of
noise like we are attacking from all sides. This wil convince the enemy
that we are
far more numerous and confuse them into panicking/fleeing, after which
we can pick them off easily.
Real Time vs. Turn-based
While Generals and battle commanders must function in real-time, they
often rehearse their plans for hours (or days, or weeks), trying to
test out all the unknowns,
especially variations in enemy strength and behavior. While real battles
involve massive-scale concurrent behavior on a continuous clock,
simulations may use fixed time-slices and allow
each unit to perform a selection of appropriate actions during their
time slice.
Real-time Strategy Games are usually a misnomer. The actions of
players of real-time strategy games are usually tactics.
Example Old-School Tabletop Turn-Based Strategy Games
- Empire (ascii art) (description)
- Ogre and G.E.V. (microgames) video description
- Civilization (there was a boardgame prior to Sid Meier's PC games, but
Sid Meier's Civilization is quite a bit different)
- SPI and Avalon Hill Games (detailed simulations of the most famous
battles in history, Civil War, WW II, etc.) Examples:
freedom in the galaxy
Example Turn-Based Strategy Computer Games
Lots of tabletop games have been implemented on computers.
Also, you might want to check out.
- battle for wesnoth
- ruse -- wwii -- scales from individual soldiers to miles-and-miles
- age of empires -- balancing, extensible unit behavior
- stronghold -- defensive, plan to withstand the onslaught
- simcity -- non-combat
- sins of a solar empire
Example Real-Time Strategy Games
This subgenre first appeared on computers. Pros: time slices are
tiny, in real life you can't micromanage thousands of units' behavior
each time slice. Nice real-time graphics animations. Cons: relies
very heavily on AI, even for human players.
- Warcraft (not World-of, just Warcraft)
- Dune II, Command and Conquer
- Dozens of me-too games, including many great ones
lecture 26
Tiled Demo, part 2
Chapter 10 is only 20 pages long; we covered the first three last time and
will walk through the following Tiled topics today:
- creating Object and Image layers
- creating tilesets, stamping them onto the tile layer
- adding properties to the tiles in tilesets
- inserting objects into the object layer, and
customizing their properties
Key Ideas from the TilemapActor Class
- factory:
new TmxMapLoader().load(filename)
- repetition:
(int)tiledMap.getProperties().get(s)
- tiledCamera updates each frame to follow main camera
- tiledMapRenderer uses tiledCamera, renders children
- finding the tiled objects.
See getTileList() in TilemapActor.java.
Checkers / Damas (e.g. JGB Chapter 6)
Checkers was one of the first games to be heavily studied in the field
of artificial intelligence. Rules:
- 8x8 grid, but only half the squares are used
- each side starts with 12 pieces
- diagonal only moves
- forward only, until rank 8 is reached.
- an opportunity to study minimax algorithm, and alpha beta pruning
In a previous edition of this class, one of the students wrote a very nice
checkers game, complete with a reasonably smart A/I computer player. The
algorithms used to optimize chess and checkers can be adapted to other
strategy games to some extent. The Artificial Intelligence class is not a
prerequisite for this one, so if you know it, use it, and if you don't know
it, consider this a gentle introduction.
This material adapted from wikipedia, and from the wonderful pages of
Dr. Bruce Rosen, with whom I once had the privilege of working.
There is (or was)
a nice interactive
visualization of minimax available if you dare to turnon your applets, and
this great YouTube
video from Shaul Markovitch of the Technion might help (note however
that he reverses the usual definition given below, and says circle is max
and square is min).
Let's start with a few definitions of special symbols:
- □ or △ : MAX node
- when it is your move you will maximize your position
- ○ or ▽ : MIN node
- your opponent will minimize your position
- β : Beta
- minimum upper bound of possible solutions
- α : Alpha
- maximum lower bound
Given a "board position" P we wish to calculate our best possible move.
This algorithm can be generalized to a broader class of strategy games,
with "board position" replaced by the complete game state (in MVC terms,
the model).
- a
tree of board positions allows us to evaluate possible moves.
- for non-trivial games, the complete tree of all possible moves
approaches infinite size rather rapidly due to combinatorial explosion.
- game tree algorithms are designed to work on an incomplete subtree
(Source: Wikipedia)
From the current position we select the next move
as the child whose board position is best (maximizing our position).
- Figuring which child is best must
consider further moves (deeper "look ahead") as much as
CPU allows.
- safe to pessimistically assume our adversary will make their
best possible move (minimizing our position).
- Our best position, short of forcing a win, will be whatever gives the
opponent the least opportunity.
lecture 27
Techniques for Platform Games (Chapter 11)
Stemkoski Ch. 11 is about a platformer game named
Jumping Jack, about a
panda, named Jack. We looked at bit at a platformer from another libGDX
book (Canyon Bunny), and platform games are pretty similar to the Starfish
Collector game. What is in fact different about them?
Highlights:
- Level has 50x20 32x32 tiles (1600x640, or ~2.5 screens wide)
- Uses Tiled; decorations on tile layer, entities on objects layer
- Separation of graphics from physics, arguably anti-object-oriented;
using Tiled for both graphical tiles and Rectangle Solid.
- an invisible Actor underneath the panda to tell whether it is standing
on a platform.
Back to MiniMax Algorithm
Youtube videos are great, but we need more examples.
Tic-Tac-Toe and the Minimax Algorithm
This is a wee bit of a tangent from checkers; file it under:
"if tic tac toe needs this much of an evaluation function,
how big would the evaluation of checkers (or chess) need to be?"
Consider an explicit representation of the tic-tac-toe board:
a list of lists of strings, with " " for unused, "x" for x, and "o" for o.
L := [ [" ", " ", " "],
[" ", " ", " "],
[" ", " ", " "] ]
Although this has appealing ascii-art properties,
(ironically?) while playing with the evaluation functions, I found
it useful to use numerically-based representation, with the different
player marks represented by different primes, say 2 for "x" and 3 for "o".
L := [ [1,1,1], [1,1,1], [1,1,1] ]
Using this representation, you can tell if you have 2 or 3 in a row,
and whether a row is blocked by the enemy, by multiplying the three
numbers together.
Based on this observation about tic-tac-toe, we might want to go and
re-code checkers to use a similarly more numeric representation, but
for now let's leave that question alone.
- We need an evaluation function for tic-tac-toe.
- For non-trivial games it is necessarily a heuristic function
- With simple games and
extreme cases you might come up with a heuristic that can be proved to
produce optimal results.
- we make no such claim about the evaluation function below
- in fact: can you suggest any improvements
procedure evaluate(L, player)
if player=="x" then { player := 2; enemy := 3 }
else { player := 3; enemy := 2 }
# absolute results
# 1000 if there exists a 3-in-a-row
# -1000 if there exists an enemy 3-in-a-row
if numNinaRow(L, player, 3)>0 then return 1000
if numNinaRow(L, enemy, 3)>0 then return -1000
# heuristic opinions
sum := 0
# + 100 for each (unblocked) 2-in-a-row
# - 100 for each enemy (unblocked) 2-in-a-row
sum +:= 100 * numNinaRow(L, player, 2)
sum -:= 100 * numNinaRow(L, enemy, 2)
# + 10 if we hold the center square
# - 10 if we hold the center square
if player = L[2,2] then sum +:= 10
if enemy = L[2,2] then sum -:= 10
# + 2 for each corner square
# - 2 for each enemy corner square
if player = L[1,1] then sum +:= 2
if player = L[1,3] then sum +:= 2
if player = L[3,1] then sum +:= 2
if player = L[3,3] then sum +:= 2
if enemy = L[1,1] then sum -:= 2
if enemy = L[1,3] then sum -:= 2
if enemy = L[3,1] then sum -:= 2
if enemy = L[3,3] then sum -:= 2
return sum
end
Counting how many N-in-a-row might look like:
procedure numNinaRow(L, player, n)
sum := 0
n := player ^ n
every i := 1 to 3 do {
if L[1,i] * L[2,i] * L[3,i] = n then sum +:= 1
if L[i,1] * L[i,2] * L[i,3] = n then sum +:= 1
}
if L[1,1] * L[2,2] * L[3,3] = n then sum +:= 1
if L[1,3] * L[2,2] * L[3,1] = n then sum +:= 1
return sum
end
Checkers Implementation
Reminder of purpose: a simple game in which to explore computer-controlled
moves/behavior/decision-making in strategy games.
-
Note: I have not found a Checkers implementation in libGDX, although
clearly someone has started
one as part of some homework assignment somewhere.
- Writing one would be fun, perhaps I should translate
my Unicon version.
- If you did one for HW#4 I would expect it to have more A/I
than other projects, since its graphics and rules are simple.
What comes below are Unicon checkers implementation notes. Consider for each
topic, how direct/easy would the LibGDX equivalents be?
First, you need to represent the board. In Unicon this might be:
a list of lists (in Java 2D array would be more native) of strings.
Each string would start as " " (a string consisting of a space) and
if it has a checkers piece, it would be assigned with a value like
"red" or "white". Java might instead use an ENUM with a few values
instead of a string in this situation..
Allocation:
Unicon | Java
|
---|
turn := "red"
square := list(8)
every !square := list(8, " ")
|
turn = "red";
String[][] square = new String[8][8];
for(int i = 0; i<8; i++)
for(int j = 0; j<8; j++)
square[i][j] = " ";
|
Initialization:
every row := 1 to 3 do
every col := 1 + (row % 2) to 8 by 2 do
square[row,col] := "white"
every row := 6 to 8 do
every col := 1 + (row % 2) to 8 by 2 do
square[row,col] := "red"
|
for(int row=1; row <= 3; row++)
for(int col = 1 + (row % 2); col <= 8; col += 2)
square[row-1][col-1] = "white";
for(int row=6; row <= 8; row++)
for(int col = 1 + (row % 2); col <= 8; col += 2)
square[row-1][col-1] = "red";
|
To print the board / configuration to the console:
write(" \\ 1 2 3 4 5 6 7 8 column")
write("row -----------------")
every i := 1 to 8 do {
writes(" ", i, " ")
every j := 1 to 8 do
writes("|",square[i,j,1])
write("|")
write(" -----------------")
}
Gameloop:
while find("red", !!square) & find("white", !!square) do {
# draw the board
# read the player's move and change the board
if turn == "red" then turn := "white" else turn := "red"
}
Input:
write(turn, "'s turn, move in row,col,...row,col format:")
input := read()
L := [ ]
input ? while put(L, integer(tab(many(&digits)))) do =","
...and it is time to go into the full
For what its worth (for comparison; not covering due to size):
Minimax for Checkers
Code here is "raw" and intended to resemble Wikipedia pseudocode.
Algorithm in checkers.icn is more "cooked",
i.e. packaged in an abstract class for better re-use.
procedure minimax(node, depth, maximizingPlayer)
if (depth = 0) | gameover(node) then {
return evaluate(node)
}
else if \maximizingPlayer then {
every child := possiblemoves(node) do {
if not (/α := (kidval := minimax(child, depth-1, &null))) then
α <:= kidval
}
}
else { # minimizing player
every child := possiblemoves(node) do {
if not (/α := (kidval := minimax(child, depth-1, 1))) then
α >:= kidval
}
}
return α
end
Good: allows perfect play [mathematically correct]
Bad: requires infinite compute resources!
Lecture 28
HW#4 Status
17/25 students have submitted solutions.
- as usual, quality varied widely.
- your final game should provide sufficient in-game instructions for
someone else to be able to play it.
Intro to Adventure Games (Stemkoski Ch. 12)
- One of the first PC games was titled Adventure, and it was a pure
text interactive fiction game. That genre is called text adventure.
They are mainly puzzle games.
- Graphical versions of text adventures were developed: things like
Monkey Island, King's Quest, or Leisure Suit Larry
They are still puzzle games.
- Stemkoski uses the term "Adventure Game" to describe games like the
Legend of Zelda family, which on wikipedia is called an "action-adventure"
so that is what Chapter 12 is about, although this genre pigeon-hole can
be debated. Early Zelda games featured very
primitive 2D graphics in the style vaguely approximated by Stemkoski.
- How many of us have played
Back to the Algorithm in checkers.icn
This version is more "cooked",
i.e. packaged in an abstract class for better re-use.
Extending Minimax to RTS or MMO
- Minimax is a centralized intelligence
- Different possible ways to develop concurrent plans for all units
- Stalin Model
- Central command plans all units' behavior together as one big "move".
All units know everything.
- Sauron Model
- Units are dumb except when the Eye is looking. Burn CPU on one or more
top priorities.
- Prioritized Hierarchical Model
- Each level gives commands to the level beneath it.
Allocate more CPU, calculate deeper for bosses/officers
- how much knowledge does each unit receive of what the other units are
doing, especially neighbors and important units?
- Perhaps each unit should
calculate what it is doing independently with limited information.
- Centralized management policies can be applied via dynamically selecting/
providing each unit's evaluation function
lecture 29
Highlights from Stemkoski's "Treasure Quest"
is an optimization to reduce the combinatorial explosion of minimax.
If you think about it, we can discard parts of the tree when we know
they cannot alter the result. Analogy: short-circuit boolean operators
skip right-hand sides once it is known they cannot change the outcome.
(Source: Wikipedia)
More Resources for 2D Game Art
You may need to make or buy some interesting game art, even for arcade
games. What other online resources would you recommend?
Got through this lecture: slides 1-4.
lecture 30
Reminder of Friday Presentations
On Friday I am expecting you to
- turn in 1 page premise document on bblearn
- give a 1-2 minute presentation in-class, graphics or A/V welcome but
not required;
- game title and basic idea
- who all will want to play this game, and why?
- solo or team project?
- anything else interesting about it?
Expect (and give) questions/suggestions from the audience. What would make
each game proposal better?
Reprise of Ch12 Demo
Yeah, the sword must be a machete because it kills shrubs, not just bats.
Got through this lecture: slides 5-20
lecture 30
This day was filled with student premises.
lecture 31
Bits of Java from Chapter 13
public static final int FOO = 13;
Alternative Sources of User Input
Gamepads / Game Controllers
- Originally
- one of the main differences between 25cents/play Arcade Games
and what you could play on videogame consoles at home was the user input
controllers. Arcade Game controllers varied spectacularly and were heavyduty.
- Over time
- consoles have spent a ton of money on design improvements to
their controllers. Differences in the Gamepads are a Major factor by which
game consoles distinguish themselves in the crowded market. Compared with
arcade games, they still suffer from "genericity" (often one controller for
all games on a console, although less so over time).
LibGDX Controller APIs:
- Controller and Controllers classes; ControllerListener interface
- gdx-controllers.jar et al not in regular GDX, sort of in but not in
- no standard for codes on different controllers ?!?
- polling (doh)
- joystick allows vector inputs instead of just boolean or int inputs
- better than keyboard, but allow keyboard as a backup
- getAxis(code) used for both joystick and trigger buttons.
Negate the y value!
- getButton(code) used for various (many) buttons
- getPov(code) used for "d pads" popularized on nintendo
- ControllerListener interface let's you receive buttonDown(), buttonUp(),
povMoved(), axisMoved(), accelerometerMoved(), xSliderMoved(),
ySliderMoved()
Typical polling every frame code:
if (Controllers.getControllers().size > 0) {
Controller g = Controllers.getControllers().get(0);
float x = g.getAxis(XBoxGamePad.AXIS_X_LEFT);
float y = -g.getAxis(XBoxGamePad.AXIS_Y_LEFT);
Vector2 d = new Vector2(x,y);
float length = d.len();
if (length > 0.10 /* "dead-zone" epsilon */) {
setSpeed(length * 100);
setMotionAngle(direction.angle());
}
}
else {
/* ... old keyboard-polling code here */
}
Typical event-driven handler methods:
public boolean buttonDown(Controller controller, int buttonCode)
{
if (buttonCode == XBoxGamepad.BUTTON_BACK) {
...
}
return false;
}
A Virtual Gamepad??
- Virtual keyboards are handy on devices that don't have physical keyboards;
how about a virtual joystick?
- Touchscreens are widely implemented and can
make it pretty easy to implement virtual input devices more sophisticated
than buttons or sliders.
- It turns out, GDX includes a virtual Touchpad akin to a joystick:
scene2D.ui.Touchpad
- Stemkoski spends a bunch of energy segregating the
game area from the HUD (why not earlier? and aren't modern
games trending away from this?)
Vector2 d = new Vector2(tpad.getKnobPercentX(),
tpad.getKnobPercentY());
float length = d.len();
if (length > 0 /* no "dead-zone" needed */) {
setSpeed(length * 100);
setMotionAngle(direction.angle());
}
GDX AI resources
- There is a GDX extension dedicated to AI, described at
https://github.com/libgdx/gdx-ai. We should check it out.
It says it has
- steering behaviors, including (unit) formations
- several pathfinding capabilities including A* pathfinding
- state machines and behavior trees for decision making
It is not really part of libGDX; to use it may require
some subtleties
-
Pax Britannica seems to have some AI code that might be
interesting. For example, see this
sample unit AI class
- proposed one "microcontroller" AI object per computer controlled
unit
- Unidirectional association. AI knows (i.e. has reference to)
its unit, unit does not need
to know whether it is controlled by human/GUI or by an AI
- simple "update() API" (what, no time parameter?)
- move/shoot based on a "current target" (retarget just picks
nearest unit of highest priority, with hardwired unit type
priorities)
- to make smarter: add state machine, self/wellness assessment,
target weaker or higher-value prey...
lecture 32
Slides 21-.
lecture 33
Short fuse, because we don't have too much semester left.
Due Sunday at midnight on bblearn. 3+ pages PDF to describe
the game's rules, user interface, and content in words and pictures.
lecture 34
Maze Games (Stemkoski Ch. 14)
24 pages, pac man clone, algorithms for generating mazes, segues perfectly
into our next A/I slide deck, which is on pathfinding.
Maze Generation
- procedurally generated, not e.g. drawn with Tiled
- randomized, difficulty will vary from game to game
- a major issue with randomly generated levels is: they might be
impossible, or unreasonably difficult
- start with fully disconnected cells, remove walls until fully connected
while there are rooms with neighbors that are not connected do
select a room that possibly has unconnected neighbors
(50%=most-recently-connected,50%=random selection)
if selected room has an unconnected neighbor then
select a random unconnected neighbor of thisRoom
remove the walls between thisRoom and the neighbor
mark the neighbor as connected
add it to the end of the list
else remove it from the list of rooms that may have unconnected neighbors
The Ghost
- pursues player via a shortest path from Ghost to Player
- brute-force-ish, breadthfirst search algorithm
- assumes perfect knowledge of the player
and the ghost locations, as well as of the maze itself.
input: startRoom and targetRoom
currentRoom = startRoom
currentRoom.visited := true, put(list, currentRoom)
while list is not empty do
currentRoom := pop(list)
for nextRoom := each unvisited neighbor do
nextRoom.previous := currentRoom
if nextRoom = targetRoom then return
nextroom.visited = true
put(list, nextroom)
Q: for how many cells/rooms, and how many times per second, do you want the
ghost resetting the visited flags on the entire map:
public void resetRooms()
{
for (int gridY = 0; gridY < roomCountY; gridY++)
for (int gridX = 0; gridX < roomCountX; gridX++) {
roomGrid[gridX][gridY].setVisited(false);
roomGrid[gridX][gridY].setPreviousRoom(null);
}
}
Note: Stemkoski doesn't say this, but space is cheap. For small to medium
sized game levels/maps, you can precalculate and store shortest paths from
every room to every room. For larger spaces, you might clump collections of
rooms together into equivalence classes... and/or implement a waypoint system.
We did slides 1-13.
lecture 35
Start at slide 14.
Peek at some extra reading/resources for game A/I
lecture 36
Reflections on Game A/I Chapters
- How much sensing, thinking, and acting can be done in *your* project?
- Recommend you build lowest-level ground-up first.
- Sensing:
- level 0=mob is a rock, no response
- level 1=tactile (senses attacks, sometimes their sources)
- level 2=hearing/smell (senses near-proximity)
- level 3=seeing (senses farther off)
- physical model? or just saving throws + modifications?
- character could have base obtrusiveness value based on size/class
- modifications for distance, other sights/sounds, monster alertness
- Thinking:
- assess attitude towards (cause of) sensed event, e.g. player
- models include: hardwired (kill-on-sight), alignment, to race/faction
- how much would this monster really know about player X?
- assess relative strength of self vs. opponent (fight or flight?)
- what stats are directly observable?
- what stats might a monster infer, or guess, and from what?
- select a response plan
- basic plans
- how often should plans be revised or replaced?
- Acting:
- movement (towards, away)
- attack or defend
- communicate (w/ neighbors or opponents)
Demo of Ch. 14 Maze Game
"Advanced 2D Graphics" (Stk Ch. 15)
Some of these signs of polish for a finished game add substantively to the
players' impression of how good your game actually is.
The Particle Editor
- code to manage a collection of many small non-entity graphic objects
- Visual effects such as fire, smoke, explosions, rain, snow, ...
- particles have independent size, speed, direction, color, etc. which
can change over time
- emitter is programmed in terms of particles/second and duration
the Particle Editor
ParticleEditor.jar is a standard-ish libGDX thing; see
here.
Note that Stemkoski's
book github has a version of ParticleEditor.jar
for you to use if you want; it seemed easier to me to get it there
than the instructions on the libgdx website.
- EmitterProperties panel controls the appearance of the particules
- Many properties, most are pretty explanatory, such as "life" for
how long the particle will be onscreen.
- Image is usually a grayscale that is then tinted separately under
the control of code
- For many properties, you can set min/max and plot a graph for how
the property varies over time.
(click for video)
- Stemkoski Ch 15 goes through the particle editor settings for a rocket
thruster effect and for an explosion effect, one or both of which may
be useful to some of your semester projects.
- He integrates them onto the SpaceRocks example game via
ParticleActor and ParticleEffect classes.
Intellectual Property, intro
We only did the first couple slides from this
slide deck.
lecture 37
Intro to Shader Programming
- The 2nd half of Stemkoski Ch. 15 is a gentle introduction
to Shader Programming.
- Shaders are little program fragments that run on the GPU. They were
introduced to make specific phases of the OpenGL graphics pipeline
more flexible, where vertex coordinates (vertex shader) and pixels' colors
(fragment shader) were being calculated.
- Stemkoski provides several interesting example fragment shaders.
- Shaders run in massively-parallel fashion on tiny special-purpose cores;
typical modern GPUs have thousands of such cores.
- Shaders are spectacularly successful; they have been used in many
non-graphics, but compute intensive, applications. The market price of
GPUs has been driven up at times by demand from applications such as
scientific research or bitcoin (and other blockchain) mining.
- The power of shaders is used in Ch. 15 to do a couple snazzy visual
effects.
- Typo alert: pg. 345 says "Shader programs use OpenGL" (Open Graphics
Library) which all GDX applications use, but this section of the book
appears to be talking about GLSL, OpenGL's Shader Language.
GLSL data types
-
float
, int
, and bool
-
vec2
, vec3
, and vec4
-
mat4
-
sampler2D
, an image-like 2D array
(presumably of vec4
)
GLSL operators and functions
- things like + - * / will work on scalars and vectors
- functions like sin/cos/sqrt/max/min
-
texture2D(sampler2D, vec2)
fetches an element
from a sampler2D
GLSL programs and variables
- attribute: per-instance, not shared across cores
- varying: per-instance, written in vertex shader,
read-only in the fragment shader
- uniform: global, shared across all instances/cores
- a vertex shader must contain a
main()
that assigns a value to a
magical global variable, a vec4
named gl_Position
- a fragment shader must contain a
main()
that assigns a
value to a magical global variable, a vec4
named
gl_FragColor
Default Vertex Shader
attribute vec4 a_position;
attribute vec4 a_color;
attribute vec2 a_texCoord0;
uniform mat4 u_projTrans;
varying vec4 v_color;
varying vec4 v_texCoords;
void main()
{
v_color = a_color;
v_texcoords = a_texCoord0;
gl_Position = u_projTrans * a_position;
}
What would have to be true for this program to make sense, executing
thousands or millions of times in parallel, 60 frames per second?
Default Fragment Shader
varying vec4 v_color;
varying vec2 v_texCoords;
uniform sampler2D u_texture;
void main()
{
gl_FragColor = v_color * texture2D(u_texture, v_texCoords);
}
Shaders in/from LibGDX
GrayScale Shader
varying vec4 v_color;
varying vec2 v_texCoords;
uniform sampler2D u_texture;
void main()
{
vec4 color = texture2D(u_texture, v_texCoords);
float avg = (color.r + color.g + color.b) / 3.0;
gl_FragColor = vec4(avg,avg,avg, color.a);
}
Custom Uniform Values
This example illustrates adding a variable from your Java program
into the shader in addition to all the parts transmitted by libGDX.
It uses many vector-at-a-time dataparallel arithmetic operations to
swing the turtle back and forth between color and grayscale along
a sine curve.
varying vec4 v_color;
varying vec2 v_texCoords;
uniform sampler2D u_texture;
uniform float u_time;
void main()
{
vec4 color = texture2D(u_texture, v_texCoords);
float avg = (color.r + color.g + color.b) / 3.0;
vec4 grayscale = vec4(avg,avg,avg, color.a);
float value = (sin(6.28 * u_time) + 1.0) * 0.5;
gl_FragColor = value * color + (1.0 - value) * grayscale;
}
To update the u_time
variable each time the shader is used
(after setting Java variable time
as the sum of all elapsed
time in the program.)
batch.setShader(sp);
sp.setUniformf("u_time", time);
super.draw(batch, alpha);
batch.setShader(null);
Border Shader
Bolds the border of an opaque object when it is surrounded by
transparent background. Note that texture coordinates are
"normalized" to (0.0-1.0) of the width and height of a texture.
To compute a corresponding pixel location with an image, multiply
the texture coordinates by image width and height.
varying vec4 v_color;
varying vec2 v_texCoords;
uniform sampler2D u_texture;
uniform vec2 u_imageSize;
uniform vec4 u_borderColor;
uniform float u_borderSize;
void main()
{
vec4 color = texture2D(u_texture, v_texCoords);
vec2 pixelToTextureCoords = 1 / u_imageSize;
bool isInteriorPoint = true;
bool isExteriorPoint = true;
for (float dx = -u_bordersize; dx < u_bordersize; dx++) {
for (float dy = -u_bordersize; dy < u_bordersize; dy++) {
vec2 point = v_texCoords + vec2(dx,dy) * pixelToTextureCoords;
float alpha = texture2D(u_texture, point).a;
if (alpha < 0.5) isInteriorPoint = false;
if (alpha > 0.5) isExteriorPoint = false;
}
}
if (!isInteriorPoint && !isExteriorPoint && color.a<0.5)
gl_FragColor = u_bordercolor;
else gl_FragColor = v_color * color;
}
To use that shader requires multiple Java variables to be passed in:
batch.setShader(sp);
sp.setUniformf("u_imageSize", new Vector2(getWidth(), getHeight()));
sp.setUniformf("u_borderColor", color.BLACK);
sp.setUniformf("u_borderSize", 3.0);
super.draw(batch, alpha); /* draw()'s alpha is not the GLSL alpha */
batch.setShader(null);
Blur Shader
varying vec4 v_color;
varying vec2 v_texCoords;
uniform sampler2D u_texture;
uniform vec2 u_imageSize;
uniform int u_blurRadius;
void main()
{
vec4 color = texture2D(u_texture, v_texCoords);
vec2 pixelToTextureCoords = 1 / u_imageSize;
for (float dx = -u_blurRadius; dx < u_blurRadius; dx++) {
for (float dy = -u_blurRadius; dy < u_blurRadius; dy++) {
vec2 point = v_texCoords + vec2(dx,dy) * pixelToTextureCoords;
averageColor += texture2D(u_texture, point);
}
}
averageColor /= pow(2.0 * u_blurRadius + 1.0, 2.0);
gl_FragColor = v_color * averageColor;
}
Glow Shader
varying vec4 v_color;
varying vec2 v_texCoords;
uniform sampler2D u_texture;
uniform vec2 u_imageSize;
uniform int u_glowRadius;
void main()
{
vec4 color = texture2D(u_texture, v_texCoords);
vec2 pixelToTextureCoords = 1 / u_imageSize;
vec4 averageColor = vec4(0.0, 0.0, 0.0, 0.0);
for (float dx = -u_glowRadius; dx < u_glowRadius; dx++) {
for (float dy = -u_glowRadius; dy < u_glowRadius; dy++) {
vec2 point = v_texCoords + vec2(dx,dy) * pixelToTextureCoords;
averageColor += texture2D(u_texture, point);
}
}
averageColor /= pow(2.0 * u_glowRadius + 1.0, 2.0);
float amount = (sin(6.0 * u_time) + 1.0) * 0.5;
vec4 glowFactor = vec4(2.0 * averageColor.rgb, averageColor.a);
gl_FragColor = v_color * (color + amount * glowFactor);
}
Wave Distortion Shader
This one features an interesting mixture of CPU and GPU work.
varying vec4 v_color;
varying vec2 v_texCoords;
uniform float u_time;
uniform vec2 u_imageSize;
uniform vec2 u_amplitude;
uniform vec2 u_wavelength;
uniform vec2 u_velocity;
uniform sampler2D u_texture;
void main()
{
vec2 pixelCoords = v_texCoords * u_imageSize;
vec2 offset = u_amplitude * sin(6.283/u_wavelength*
(pixelCoords.yx - u_velocity * u_time));
vec2 texCoords = v_texCoords + offset / u_imageSize;
gl_FragColor = v_color * texture2D(u_texture, texCoords);
}
When a shader requires most of its data from Java variables that are
passed in, one might say that the CPU is doing a fair part of the work,
albeit not in parallel.
batch.setShader(sp);
sp.setUniformf("u_time", time);
sp.setUniformf("u_imageSize", new Vector2(getWidth(), getHeight()));
sp.setUniformf("u_amplitude", new Vector2(2, 3));
sp.setUniformf("u_wavelength", new Vector2(7, 19));
sp.setUniformf("u_velocity", new Vector2(10, 11));
super.draw(batch, alpha);
batch.setShader(null);
lecture 38
lecture 39
Lectures 38-39 were replaced by student level 1 demos.
lecture 40
Intro to 3D Graphics and Games
3D is a major subject for CS 428/528, but it is fair game for us to
see what Stemkoski has to say in Ch 16. Consider this a bonus topic
for CS 328. It is also inevitable that much of this material will be
presented in that course, with a different textbook author's voice.
Ch 16 Demos
If possible we will run them.
3D and Cameras
- 2D graphics can be programmed in terms of pixels; having a camera model
is optional.
- in 3D what you see very much depends on a camera model.
- orthographic vs. perspective
- viewing volume, frustum, viewport, fov, near and far
Rendering Code vs. Data: Algorithms vs. Models
It is a bit of a false dichotomy, but OpenGL lets you render
graphics by either:
- calling functions for high-level graphics
primitives (like spheres or pyramids) or
- rendering from data structures called models, which
typically represent low-level data (triangles).
3D Code vs. Data Illustration
In the early days of my CVE collaborative virtual environment,
students wrote classes representing different kinds of things in
the virtual world, such as tables and chairs.
- They are good logical application domain classes.
- They are needed to this day, for dynamic state, collision detection etc.
- Their graphical rendering method consisted
of function calls that drew however many shapes, such as spheres,
cylinders, etc. that the student managed to write to approximate the shape
and appearance of that object in the real world.
- They were crude approximations.
- Later, CVE acquired the ability to load models
- Models can contain detailed arrays of the raw vertices, every face
(triangle) that those vertices connect, and texture images to draw
them with.
- For the graphics output, models are far more detailed than what
most programmers would do in code. It is like drawing a picture
pixel-by-pixel versus loading a JPEG image.
- (Of course, an amazing programmer might be able to algorithmically
draw chairs using fractals or L-systems or whatever.)
Code vs. Models in libGDX
In libGDX the Code vs. Data dichotomy is reflected in the classes
ModelBuilder
vs. ModelLoader
. If you are
coding your 3D scene, you are using a ModelBuilder with methods like
createBox():
ModelBuilder modelBuilder = new ModelBuilder();
Material boxMaterial = new Material();
boxMaterial.set(ColorAttribute.createDiffuse(Color.BLUE));
int usageCode = Usage.Position + Usage.ColorPacked + Usage.Normal;
Model boxModel = modelBuilder.createBox( 5, 5, 5, boxMaterial, usageCode);
boxInstance = new ModelInstance(boxModel);
To actually draw the mode:
modelBatch.begin(camera);
modelBatch.render(boxInstance, environment);
modelBatch.end();
We will look at the highlights.
The BaseActor3D class
- For his 3D game, we get Starfish Collector 3D
- All I can say is: Dr. Stemkoski brings whole new meaning to the phrase
"turtle graphics".
- Extending BaseActor to work in 3D requires more data, to represent
transformations on points and objects as their coordinates are
moved from object-local coordinate systems to world coordinate systems.
- I think of this as a linear algebra exercise, since 4x4 matrices
are multiplied together to compose multiple transformations. However,
Stemkoski (and libGDX) go a bit beyond intro to linear algebra and go
ahead and introduce a
Quaternion for representing the rotations, in addition to Vector3's
for (each of) translation and scaling.
class BaseActor3D {
private ModelInstance modelData;
// Could do all 3 of these with one mat4, but...
private final Vector3 position;
private final Quaternion rotation;
private final Vector3 scale;
// ... lots of easy one-liner methods:
public void moveBy(Vector3 v) { position.add(v); }
}
The only methods that aren't one liners in BaseActor3D are those that
tweak the model's color/texture, and it is almost the same for loop in
each case.
public void setColor(Color c)
{
for (Material m : modelData.materials)
m.set( ColorAttribute.createDiffuse(c));
}
public void loadTexture(String fileName)
{
Texture tex = new Texture(Gdx.files.internal(fileName, true));
tex.setFilter( TextureFilter.Linear, TextureFilter.Linear);
for (Material m : modelData.materials)
m.set( TextureAttribute.createDiffuse(tex));
}
lecture 41
Stemkoski's Stage3D class
public class Stage3D {
private Environment environment;
private PerspectiveCamera camera;
private final ModelBatch modelBatch;
private ArrayList<BaseActor3D> actorList;
// ... big constructor sets up lighting and camera
public Stage3D() {
environment = new Environment();
environment.set(new ColorAttribute(ColorAttribute.AmbientLight,
0.7f, 0.7f, 0.7f, 1));
DirectionalLight dLite = new DirectionalLight();
Color lightColor = new Color(0.9f, 0.9f, 0.9f, 1);
Vector3 lightVector = new Vector3(-1.0f, -0.75f, -0.25f);
dLite.set(lightColor, lightVector);
environment.add(dLite);
camera = new PerspectiveCamera(67,
Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
camera.position.set(10f,10f,10f);
camera.lookAt(0,0,0);
camera.near(0.01f);
camera.far(1000f);
camera.update();
modelBatch = new ModelBatch();
actorList = new ArrayList<BaseActor3D>()
}
// ... methods mainly iterate over actors on actorList
public void draw() {
modelBatch.begin(camera);
for (BaseActor3D ba : actorList)
ba.draw(modelBatch, environment);
modelBatch.end();
}
}
Reactions:
- BaseActor3D separated out position, rotation, scaling
in application domain entities, even though they could all
be represented in a single mat4 to libGDX...
- You might want to do similar for the camera instead of just
using the libGDX camera as-is.
- Stemkoski spends page 371 implementing free camera direction
and position controls, holding y constant. He reads camera
direction components (.x etc.) directly and modifies values
incrementally by applying deltas. He relies on Vector3 .nor() and .scl()
to normalize a (direction) vector to length 1 and then scale it based
on distance.
- There are easier ways to manage camera position/direction without
a lot of this complexity in FPS type games, e.g. as used in CVE.
But S's code gets the job done.
"Interactive 3D Demo" Code Notes
- simultaneous Stage (HUD) and Stage3D render loop:
public void render(float dt)
{
dt = Math.min(dt, 1/30f);
// "act"
uiStage.act(dt); // does this have to be in this order?
mainStage3D.act(dt);
// "update"
update(dt);
// "render"
Gdx.gl.glClearColor(0.5f,0.5f,0.5f,1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT, GL20.GL_DEPTH_BUFFER_BIT);
mainStage3D.draw(); // has to be in this order
uiStage.draw();
}
- InputProcessor multiplexes BaseScreen and uiStage !
This is odd because it is not mainStage3D and uiStage.
All BaseScreen event handlers are no-ops, so unclear why bother.
- update() method calls Gdx.input.isKeyPressed() 20x !
Polling has scalability limits and reduces your CPU budget e.g. for
game AI.
- class Box and class Sphere are almost identical.
Highlander OO Principle: There should be only one copy of this code.
public class Box extends BaseActor3D
{
public void Box(float, float y, float z, Stage3D s) {
super(x,y,z,s);
ModelBuilder modelBuilder = new ModelBuilder();
Material boxMaterial = new Material();
int usageCode = Usage.Position + Usage.ColorPacked +
Usage.Normal + Usage.TextureCoordinates;
Model boxModel = modelBuilder.createBox(1,1,1, boxMaterial, usageCode);
Vector3 position = new Vector3(0,0,0);
setModelInstance( new ModelInstance(boxModel, position) );
}
}
- Instantiating these 3D primitives:
Box b = Box(0,0,0, mainStage3D);
b.setColor(Color.BROWN);
b.loadTexture("assets/crate.JPG");
Starfish Collector 3D Code Highlights
- This is the point at which Stemkoski starts using ModelLoader instead of
just ModelBuilder.
- The texture at the end of the universe: Stemkoski uses the phrases
"sky sphere" and "sky dome" to refer to a texture-mapped spherical
primitive that will always be outside the navigable area and guarantees
that the sky will never be just a blank black or white or whatever.
- A 2D texture has to be distorted in a mathematically-interesting way
in order for it to look good when mapped onto a sphere.
- I have heard other technical terms besides these, but "sky dome" at
least is in widespread use denoting this concept.
- Since you are distorting the texture like crazy anyhow, and since the
sphere being rendered is usually a great distance away from the viewer,
there is probably very little reason to waste polygons
required to render a sphere and a
skybox
might be preferable.
- Problem: by default primitives in OpenGL only draw on their "outside"
- one side is deemed to be the one that might be facing the user.
- OpenGL let's you turn on drawing of both sides, at a cost in performance.
- Useful inside-out trick: scaling an object by -1 on the z-axis
has the effect of
flipping the faces and giving Stemkoski a sphere that is rendered inwards.
Actual skydome code looks anticlimactic. Hardest part was constructing
the sphere-distorted 2D rectangular texture image, which is not covered
in our book, but googling "how to construct a skydome texture" seems to
turn up some promising links. Anyhow, the code:
Sphere skydome = new Sphere(0,0,0,mainStage3D);
skydome.loadTexture("assets/sky-sphere.png");
skydome.setScale(500,500,-500); // one might use even bigger numbers
There are multiple levels of instantiation of factory classes that
produce things that require instantiation...but to sum up:
public class ObjModel extends BaseActor3D {
...
public void loadObjModel(String fileName) {
ObjLoader loader = new ObjLoader();
Model objModel = loader.loadModel(Gdx.files.internal(fileName), true);
setModelInstance(new ModelInstance(objModel));
}
}
and for the actual Turtle class in 3D:
public class Turtle extends ObjModel
{
public void Turtle(float, float y, float z, Stage3D s) {
super(x,y,z,s);
loadObjModel("assets/turtle.obj");
setBasePolygon(); // sets bounding box for collision detection
}
}
Major Sources of 3D Assets
These are all worth checking out. Personally I am most familiar with
TurboSquid, but have also used opengameart before.
lecture 42
These items are from Stemkoski Ch. 17.
More Resources
Game Jams
Packaging Your Games for Distribution
- In Java, you can make an executable jar file; BlueJ has a menu option
for that (Create Jar File)
- The .jar will (minimally) contain .class files and assets/
- Your .jar file will have to be shipped with the libGDX .jar files that
you use.
- Tools exist that will bundle Java with a .jar to produce a .exe, e.g.
JWrapper
- libGDX projects can easily be configured to build
for other platforms, especially if you used gdxsetup.
- What it takes to make an executable on Android or iOS looks somewhat
different than on the desktop Java platform.
- I have used the HTML5 target before, it compiles java to javascript
via GWT.
- Students have turned in semester projects on Android before; libGDX
is arguably a mostly-android or android-first library. Android SDK
is a decent-size learning curve without a tool like libGDX.
- libGDX iOS support was historically via RoboVM, then it sounded like
it was moving to Intel MultiOS Engine, but I am not sure that happened.
Whole thing is a ??
Games-Related Research
Here are some of the types of research
I want to work with students on, related to computer games or
game-like software systems. Some of these are pursuable via
directed studies while others are more like senior project or
M.S. thesis or Ph.D. dissertation material.
- programming language support for games
- "language support" means: portable multi-platform higher-level
facilities, includign data and control structures, operators and
built-in functions. Much of what I've added to Unicon has ended up
being I/O-oriented, such as easy graphics and networking. There is
room for more I/O, such as portable multi-platform touch/gestures,
and ports to more platforms.
To identify what is needed, besides supporting new hardware or OSes:
look at current
games and identify their costs/pain points. Wow took $150M to develop
initially. How can we reduce that cost and enable new garage startups
in such a space?
- asset tools and libraries
- For the vast majority of commercial games, the art and modeling assets
consume the vast majority of the budget. Example: a boss at Westwood
studios (makers of Command and Conquer, Dune, etc.) once told me that
for their typical titles, 3-5 programmers work with 30-50 artists.
What can't be done wholly by
code can be reduced in cost by improving tools and libraries.
- game AI, such as better NPC A/I
- NPC's in many major commercial titles (e.g. Wow) still have pretty trivial
AI. If we are in the midst of an AI revolution, and have CPU to
burn, we should be able to do way better.
Some games do better than
others already, of course. STALKER Shadow of Chernobyl is said to have
NPC's who live in packs and do stuff independent of player actions.
GTA apparently has a very clever police force.
I am interested
in NPC's that remember past interactions, have their own agenda that
they will pursue with or without you, etc.
A couple current M.S.
students are working on smarter NPC's with me at present.
- rebalancing persistent virtual worlds content
- One thing I hate in MMOs is that player successes have no lasting effect.
The world needs an MMO where if you topple the enemy boss, the world is
changed forever by that action. Instead of static state, games need to
move to state that rebalances as a result of players.
- user-generated virtual worlds content
- The main thing causing some folks to quit some games is that they run out
of new content. There have been major games that featured user-created
worlds (Minecraft, Second Life), but few that provided user-created
story lines and quests. City of Heroes started to do this towards the
end of its life, maybe there are others. This is a sort of crowd-sourcing
the content creation, and it needs to be quality-controlled, perhaps by
end-user rating system, perhaps by super-user vetting.
- procedurally-generated city structures
- I have started working with a city metaphor for software visualization.
I am interested in generating a city or collection of cities that are
reflections of software's static structures.
- execution-driven city population and behavior
- I did my Ph.D. on high performance execution monitoring; I want to
use program execution behavior to dynamically spawn NPC's and guide
their subsequent behavior within the generated cities.
More Intellectual Property
From the slide deck
Content Regulation
slide deck