001: /*
002: * Copyright (c) 2007, Sun Microsystems, Inc.
003: *
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * * Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: * * Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in
014: * the documentation and/or other materials provided with the
015: * distribution.
016: * * Neither the name of Sun Microsystems, Inc. nor the names of its
017: * contributors may be used to endorse or promote products derived
018: * from this software without specific prior written permission.
019: *
020: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
023: * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
024: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031: */
032:
033: package demo;
034:
035: import java.io.IOException;
036: import java.util.Timer;
037: import java.util.TimerTask;
038: import javax.microedition.lcdui.Graphics;
039: import javax.microedition.lcdui.game.GameCanvas;
040: import javax.microedition.lcdui.game.LayerManager;
041: import javax.microedition.lcdui.game.Sprite;
042: import javax.microedition.lcdui.game.TiledLayer;
043:
044: /**
045: *
046: * @author Karel Herink
047: * @version 1.0
048: */
049: public class DemoGameCanvas extends GameCanvas implements Runnable {
050:
051: private static final int SPEED = 3;
052: private static final int MIN_BUFFER = 20;
053: private int viewPortX = 0;
054: private int viewPortY = 0;
055: private byte lastDirection = -1;
056: private TiledLayer tlBase;
057: private boolean interrupted;
058: private LayerManager lm;
059: private GameDesign gameDesign;
060: private Timer timer;
061: private Sprite spriteKarel;
062: private SpriteAnimationTask spriteKarelAnimator;
063: private Sprite spriteThomas;
064: private SpriteAnimationTask spriteThomasAnimator;
065: private SpriteRandomMovement spriteThomasRandomMovement;
066: private TiledLayer tlWater;
067: private TiledLayer tlTrees;
068: private TiledLayer tlThings;
069: private TileAnimationTask waterAnimator;
070:
071: public DemoGameCanvas() {
072: super (true);
073: try {
074: this .setFullScreenMode(true);
075: this .init();
076: } catch (IOException ex) {
077: ex.printStackTrace();
078: }
079: }
080:
081: /**
082: * Initialize the Game Design, then load all layers and start animation threads.
083: */
084: private void init() throws IOException {
085: this .timer = new Timer();
086: this .gameDesign = new GameDesign();
087:
088: this .spriteKarel = gameDesign.getKarel();
089: //define the reference in the midle of sprites frame so that transformations work well
090: this .spriteKarel.defineReferencePixel(8, 8);
091: this .spriteKarelAnimator = new SpriteAnimationTask(
092: this .spriteKarel, false);
093: this .timer.scheduleAtFixedRate(this .spriteKarelAnimator, 0,
094: gameDesign.KarelSeqWalkDownDelay);
095:
096: this .waterAnimator = new TileAnimationTask(gameDesign
097: .getWater(), gameDesign.AnimWaterWater,
098: gameDesign.AnimWaterSeq001, true);
099: this .timer.scheduleAtFixedRate(this .waterAnimator, 0,
100: gameDesign.AnimWaterSeq001Delay);
101:
102: this .tlThings = this .gameDesign.getThings();
103: this .tlTrees = this .gameDesign.getTrees();
104: this .tlWater = this .gameDesign.getWater();
105: this .tlBase = this .gameDesign.getBase();
106:
107: this .lm = new LayerManager();
108: gameDesign.updateLayerManagerForForest(lm);
109:
110: this .spriteThomas = gameDesign.getThomas();
111: //define the reference in the midle of sprites frame so that transformations work well
112: this .spriteThomas.defineReferencePixel(8, 8);
113: this .spriteThomasAnimator = new SpriteAnimationTask(
114: this .spriteThomas, true);
115: this .spriteThomasAnimator.setMoving(true);
116: this .timer.scheduleAtFixedRate(this .spriteThomasAnimator, 0,
117: gameDesign.ThomasSeqWalkHorizDelay);
118: this .spriteThomasRandomMovement = new SpriteRandomMovement(
119: this , spriteThomas);
120: this .spriteThomasRandomMovement.setSequences(
121: gameDesign.ThomasSeqWalkVert, Sprite.TRANS_NONE,
122: gameDesign.ThomasSeqWalkVert, Sprite.TRANS_ROT180,
123: gameDesign.ThomasSeqWalkHoriz, Sprite.TRANS_ROT180,
124: gameDesign.ThomasSeqWalkHoriz, Sprite.TRANS_NONE);
125: (new Thread(spriteThomasRandomMovement)).start();
126: }
127:
128: /**
129: * Check if sprite collides with either the other sprite or
130: * with a layer that holds obstacles or with the edge of the base layer.
131: *
132: * @param sprite the sprite checked for collision with other layers
133: * @return true is sprite does collide, false otherwise
134: */
135: public boolean spriteCollides(Sprite sprite) {
136: return sprite.collidesWith(
137: sprite == this .spriteKarel ? this .spriteThomas
138: : this .spriteKarel, true)
139: || sprite.collidesWith(this .tlThings, true)
140: || sprite.collidesWith(this .tlTrees, true)
141: || sprite.collidesWith(this .tlWater, true)
142: || sprite.getX() < 0
143: || sprite.getY() < 0
144: || sprite.getX() > (this .tlBase.getWidth() - sprite
145: .getWidth())
146: || sprite.getY() > (this .tlBase.getHeight() - sprite
147: .getHeight());
148: }
149:
150: /**
151: * Adjust the viewport to keep the main animated sprite inside the screen.
152: * The coordinates are checked for game bounaries and adjusted only if it
153: * makes sense.
154: *
155: * @param x viewport X coordinate
156: * @param y viewport Y coordinate
157: */
158: private void adjustViewport(int x, int y) {
159:
160: int sx = this .spriteKarel.getX();
161: int sy = this .spriteKarel.getY();
162:
163: int xmin = this .viewPortX + MIN_BUFFER;
164: int xmax = this .viewPortX + this .getWidth()
165: - this .spriteKarel.getWidth() - MIN_BUFFER;
166: int ymin = this .viewPortY + MIN_BUFFER;
167: int ymax = this .viewPortY + this .getHeight()
168: - this .spriteKarel.getHeight() - MIN_BUFFER;
169:
170: //if the sprite is not near the any screen edges don't adjust
171: if (sx >= xmin && sx <= xmax && sy >= ymin && sy <= ymax) {
172: return;
173: }
174:
175: //if the sprite is moving left but isn't near the left edge of the screen don't adjust
176: if (this .lastDirection == LEFT && sx >= xmin) {
177: return;
178: }
179: //if the sprite is moving right but isn't near the right edge of the screen don't adjust
180: if (this .lastDirection == RIGHT && sx <= xmax) {
181: return;
182: }
183: //if the sprite is moving up but isn't at near top edge of the screen don't adjust
184: if (this .lastDirection == UP && sy >= ymin) {
185: return;
186: }
187: //if the sprite is moving down but isn't at near bottom edge of the screen don't adjust
188: if (this .lastDirection == DOWN && sy <= ymax) {
189: return;
190: }
191:
192: //only adjust x to values that ensure the base tiled layer remains visible
193: //and no white space is shown
194: if (x < this .tlBase.getX()) {
195: this .viewPortX = this .tlBase.getX();
196: } else if (x > this .tlBase.getX() + this .tlBase.getWidth()
197: - this .getWidth()) {
198: this .viewPortX = this .tlBase.getX()
199: + this .tlBase.getWidth() - this .getWidth();
200: } else {
201: this .viewPortX = x;
202: }
203:
204: //only adjust y to values that ensure the base tiled layer remains visible
205: //and no white space is shown
206: if (y < this .tlBase.getY()) {
207: this .viewPortY = this .tlBase.getY();
208: } else if (y > this .tlBase.getY() + this .tlBase.getHeight()
209: - this .getHeight()) {
210: this .viewPortY = this .tlBase.getY()
211: + this .tlBase.getHeight() - this .getHeight();
212: } else {
213: this .viewPortY = y;
214: }
215:
216: //adjust the viewport
217: this .lm.setViewWindow(this .viewPortX, this .viewPortY, this
218: .getWidth(), this .getHeight());
219: }
220:
221: /**
222: * The main game loop that checks for user input and repaints canvas.
223: */
224: public void run() {
225: Graphics g = getGraphics();
226:
227: while (!this .interrupted) {
228: //check for user input
229: int keyState = getKeyStates();
230:
231: //if user is pressing the left button
232: if ((keyState & LEFT_PRESSED) != 0) {
233: //if the previous direction was other than left set the sequence
234: //correct sequence & transform needed for walking to the left
235: if (this .lastDirection != LEFT) {
236: this .lastDirection = LEFT;
237: this .spriteKarel
238: .setFrameSequence(gameDesign.KarelSeqWalkSide);
239: this .spriteKarel.setTransform(Sprite.TRANS_MIRROR);
240: continue;
241: }
242: //assign the sequence playback direction
243: this .spriteKarelAnimator.forward();
244: //move the sprite to the left
245: this .spriteKarel.move(-SPEED, 0);
246: //if moving the sprite generates a collision return sprite back
247: //to its original position
248: if (this .spriteCollides(this .spriteKarel)) {
249: this .spriteKarel.move(SPEED, 0);
250: continue;
251: }
252: //attempt to adjust the viewport to keep the sprite on the screen
253: this .adjustViewport(this .viewPortX - SPEED,
254: this .viewPortY);
255: } else if ((keyState & RIGHT_PRESSED) != 0) {
256: if (this .lastDirection != RIGHT) {
257: this .lastDirection = RIGHT;
258: this .spriteKarel
259: .setFrameSequence(gameDesign.KarelSeqWalkSide);
260: this .spriteKarel.setTransform(Sprite.TRANS_NONE);
261: continue;
262: }
263: this .spriteKarelAnimator.forward();
264: this .spriteKarel.move(SPEED, 0);
265: if (this .spriteCollides(this .spriteKarel)) {
266: this .spriteKarel.move(-SPEED, 0);
267: continue;
268: }
269: this .adjustViewport(this .viewPortX + SPEED,
270: this .viewPortY);
271: } else if ((keyState & UP_PRESSED) != 0) {
272: if (this .lastDirection != UP) {
273: this .lastDirection = UP;
274: this .spriteKarel
275: .setFrameSequence(gameDesign.KarelSeqWalkUp);
276: this .spriteKarel.setTransform(Sprite.TRANS_NONE);
277: continue;
278: }
279: this .spriteKarelAnimator.forward();
280: this .spriteKarel.move(0, -SPEED);
281: if (this .spriteCollides(this .spriteKarel)) {
282: this .spriteKarel.move(0, SPEED);
283: continue;
284: }
285: this .adjustViewport(this .viewPortX, this .viewPortY
286: - SPEED);
287: } else if ((keyState & DOWN_PRESSED) != 0) {
288: if (this .lastDirection != DOWN) {
289: this .lastDirection = DOWN;
290: this .spriteKarel
291: .setFrameSequence(gameDesign.KarelSeqWalkDown);
292: this .spriteKarel.setTransform(Sprite.TRANS_NONE);
293: continue;
294: }
295: this .spriteKarelAnimator.forward();
296: this .spriteKarel.move(0, SPEED);
297: if (this .spriteCollides(this .spriteKarel)) {
298: this .spriteKarel.move(0, -SPEED);
299: continue;
300: }
301: this .adjustViewport(this .viewPortX, this .viewPortY
302: + SPEED);
303: } else {
304: this .spriteKarelAnimator.setMoving(false);
305: }
306:
307: this .lm.paint(g, 0, 0);
308: flushGraphics(0, 0, this .getWidth(), this .getHeight());
309:
310: try {
311: Thread.sleep(20);
312: } catch (InterruptedException ex) {
313: ex.printStackTrace();
314: }
315: }
316: }
317:
318: /**
319: * Stops the main game loop.
320: */
321: public void stop() {
322: this .interrupted = true;
323: this .spriteThomasRandomMovement.stop();
324: }
325:
326: /**
327: * Animates animated tiles in a tiled layer.
328: */
329: private class TileAnimationTask extends TimerTask {
330:
331: private boolean moving = true;
332: private boolean forward = true;
333: private TiledLayer tiledLayer;
334: private int animatedTileIndex;
335: private int[] sequence;
336: private int sequenceIndex;
337:
338: public TileAnimationTask(TiledLayer tiledLayer,
339: int animatedTileIndex, int[] sequence, boolean forward) {
340: this .tiledLayer = tiledLayer;
341: this .animatedTileIndex = animatedTileIndex;
342: this .sequence = sequence;
343: this .forward = forward;
344: }
345:
346: public void run() {
347: if (!this .moving) {
348: return;
349: }
350: if (forward) {
351: if (++this .sequenceIndex >= this .sequence.length) {
352: sequenceIndex = 0;
353: }
354: } else {
355: if (--this .sequenceIndex < 0) {
356: sequenceIndex = this .sequence.length - 1;
357: }
358: }
359: this .tiledLayer.setAnimatedTile(this .animatedTileIndex,
360: this .sequence[sequenceIndex]);
361: }
362:
363: public void forward() {
364: this .forward = true;
365: this .moving = true;
366: }
367:
368: public void backward() {
369: this .forward = false;
370: this .moving = true;
371: }
372:
373: public void setMoving(boolean isMoving) {
374: this .moving = isMoving;
375: }
376: }
377:
378: /**
379: * Animates a sprite.
380: */
381: private class SpriteAnimationTask extends TimerTask {
382:
383: private boolean moving = false;
384: private boolean forward = true;
385: private Sprite sprite;
386:
387: public SpriteAnimationTask(Sprite sprite, boolean forward) {
388: this .sprite = sprite;
389: this .forward = forward;
390: }
391:
392: public void run() {
393: if (!this .moving) {
394: return;
395: }
396: if (this .forward) {
397: this .sprite.nextFrame();
398: } else {
399: this .sprite.prevFrame();
400: }
401: }
402:
403: public void forward() {
404: this .forward = true;
405: this .moving = true;
406: }
407:
408: public void backward() {
409: this .forward = false;
410: this .moving = true;
411: }
412:
413: public void setMoving(boolean isMoving) {
414: this.moving = isMoving;
415: }
416: }
417: }
|