Programowanie na komórki
J2ME, MIDP 2.0
Adam Sawicki
http://regedit.gamedev.pl/
sawickiap@poczta.onet.pl
Grudzień 2007
W prezentacji wykorzystane są fragmenty dokumentacji MIDP 2.1 firmy Sun.
Programowanie na komórki
W czym pisać programy na komórki?
To zależy od klasy telefonu.
(nic) Java, C++ i inne...
Java
2
Agenda
Wprowadzenie do J2ME
Midlet
GUI
Wejście-wyjście
Internet
Grafika 2D
Programowanie gier 2D
Kilka drobiazgów
Baza danych
Multimedia
Rozszerzenia
3
Wprowadzenie do J2ME
J2ME
Wireless Toolkit
Język Java w J2ME
4
Co to jest J2ME?
Skróty, dużo skrótów...
J2ME Java 2 Platform, Micro Edition
Platforma Java na komórki (inne to J2SE i J2EE)
CLDC Connected Limited Device Configuration
MIDP Mobile Information Device Profile
JSR Java Specification Request
Specyfikacje dodatków (np. JSR-184 to M3G)
W praktyce używamy dokumentacji MIDP, ona
zawiera też dokumentację CLDC
MIDP 1.0 CLCD 1.0, MIDP 2.0 CLDC 1.1
5
Jakie jest J2ME?
Ten sam język Java
Używamy tego samego kompilatora javac
Zupełnie nowa biblioteka standardowa
Podajemy niestandardowy classpath
Liczne ograniczenia sprzętu
Mało pamięci RAM
Wolny procesor
Niska rozdzielczość wyświetlacza, mało kolorów
Kiepska klawiatura
Duże zróżnicowanie klawiatur, ekranów, wydajności
6
Co jest potrzebne?
JDK Java Development Kit
Zawiera w sobie JRE - Java Runtime Environment
Java Wireless Toolkit
Edytor lub IDE
jEdit, Notatnik, Vim, Emacs, ...
NetBeans, Eclipse, ...
Wszystko dostępne za darmo (java.sun.com).
7
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class HelloWorld
extends MIDlet
Hello World!
implements CommandListener
{
public HelloWorld()
{
}
//////
// Implementacja MIDlet
protected void startApp()
throws MIDletStateChangeException
{
Alert alert = new Alert("Hello World!");
alert.setCommandListener(this);
alert.addCommand(new Command("Koniec", Command.EXIT, 0));
Display display = Display.getDisplay(this);
display.setCurrent(alert);
}
protected void pauseApp()
{
}
protected void destroyApp(boolean unconditional)
throws MIDletStateChangeException
{
}
//////
// Implementacja CommandListener
public void commandAction(Command c, Displayable d)
{
if (c.getCommandType() == Command.EXIT)
notifyDestroyed();
}
8
}
Ręczne budowanie midletu
Midlet można zbudować ręcznie z wiersza
poleceń
Kompilacja: javac (JDK)
Preweryfikacja: preverify (WTK)
Napisanie pliku MANIFEST.MF
Utworzenie paczki: jar (JDK)
Napisanie pliku JAD
Emulacja: emulator (WTK)
Wysłanie na komórkę
...ale nie trzeba, można prościej!
9
Wireless Toolkit
Sun Java(TM) Wireless Toolkit for CLDC
Tytułowy program pakietu Wireless Toolkit
Proste IDE do midletów
10
Wireless Toolkit
Operuje na projektach
Przechowuje je w Documents and
Settings\LOGIN\j2mewtk\apps\NAZWA
Zakłada kilka katalogów
src pliki JAVA
res obrazki, dzwięki i inne zasoby
lib dodatkowe biblioteki
classes, tmpclasses pliki tymczasowe
bin pliki wynikowe JAR, JAD
Polecenia
Kompilacja: Build
Emulacja: Run
Utworzenie paczki: Create Package
Nie posiada edytora!
11
Język Java
Dostępny mamy normalny język Java
Klasy, wyjątki
byte, short, int, long, boolean, char itd.
String, StringBuffer
float, double od wersji MIDP 2.0
NIE MA generics, enum itp. to jest Java 1.2
12
Wielowątkowość
J2ME to platforma ograniczona, ale
nowoczesna.
Można, a nawet trzeba pisać wielowątkowo i
zajmować się synchronizacją.
JEST dostęna wielowątkowość:
Słowo kluczowe synchronized
Metody klasy bazowej Object: wait, notify, notifyAll
Klasa Thread, interfejs Runnable
13
Biblioteka standardowa
Podstawowe typy
java.lang Boolean, Character, Integer itd.
java.lang String, StringBuffer
Matematyka
java.lang.Math abs, max, sin, floor, sqrt, ...
BRAKUJE exp, log, pow, atan2
java.util.Random generator liczb pseudolosowych
Struktury danych
java.util Vector, Stack, Hashtable
14
Midlet
javax.microedition.midlet
15
Midlet
Midlet program na komórkę w J2ME
Analogicznie do: aplet, servlet
NIE MA kolejki komunikatów ani jawnej pętli jak w Windows API
JEST sterowany zdarzeniami, jak programy w Delphi czy C#
Posiada STAN
16
Midlet szkielet klasy
import javax.microedition.midlet.*;
public class HelloWorld extends MIDlet
{
public HelloWorld()
{
}
protected void startApp()
throws MIDletStateChangeException
{
}
protected void pauseApp()
{
}
protected void destroyApp(boolean unconditional)
throws MIDletStateChangeException
{
}
}
17
Midlet - zdarzenia
startApp()
Program staje się aktywny
Dokonać inicjalizacji, wczytać zasoby, pokazać coś na ekranie
Jeśli nie udaje się uaktywnić, rzucić wyjątek
MIDletStateChangeException
Jeśli nieodwracalny błąd, wywołać notifyDestroyed
pauseApp()
Program staje się nieaktywny
Zwolnić wszystkie zasoby zajmujące dużo procesora lub pamięci
destroyApp(boolean unconditional)
Program zostaje wyłączony
Zapisać trwałe dane
Jeśli odmawia wyłączenia, rzucić wyjątek
MIDletStateChangeException (tylko jeśli unconditional == false)
18
Midlet zmiana stanu
notifyDestroyed()
Wywołać jeśli program chce się zakończyć
destroyApp nie zostanie już wywołane!
notifyPaused()
Wywołać jeśli program chce się stać nieaktywny
resumeRequest()
Wywołać jeśli program chce się stać aktywny
platformRequest(String URL)
Wywołać aby system obsłużył podany URL, np.
tel:NUMER
19
GUI
javax.microedition.lcdui
20
GUI - Klasy
Pakiet:
javax.microedition.lcdui
21
Wysoki i niski poziom
API wysokiego poziomu
Zawiera: kontrolki GUI i polecenia
Dla: aplikacji biznesowych
Abstrakcyjne, niezależne od rozdzielczości
Rysowane wg stylu danego telefonu
API niskiego poziomu
Zawiera: możliwość rysowania i reakcji na wejście z
klawiatury
Dla: gier
Zależne od wyświetlacza i klawiatury danego
telefonu
Wygląd i obsługa implementowane samodzielnie
22
Alert
Alert ekran z komunikatem tekstowym
Coś jak znany z Windowsa MessageBox :)
// Utwórz ekran komunikatu
Alert alert = new Alert(
// title
"Błąd",
"Nie można otworzyć pliku.", // alertText
// alertImage
null,
// alertType
AlertType.ERROR);
// Pobierz singleton wyświetlacza
Display display = Display.getDisplay(this);
// Pokaż komunikat na wyświetlaczu
display.setCurrent(alert);
23
Command i CommandListener
Zaimplementować w jakiejś klasie L interfejs CommandListener
Utworzyć jakiś ekran E typu pochodnego od Displayable
Wywołać:
E.setCommandListener(L_obj);
Wywołać dowolną liczbę razy:
E.addCommand(new Command(label, commandType, priority));
Pokazać E na wyświetlaczu:
display.setCurrent(E);
Czekać na zdarzenia...
24
Command i CommandListener
public class TestMidlet extends MIDlet implements CommandListener
{
protected void startApp() throws MIDletStateChangeException
{
Alert alert = new Alert("Wyjście", "Czy na pewno chcesz wyjść?",
null, null);
alert.setCommandListener(this);
alert.addCommand(new Command("Zakończ", Command.EXIT, 0));
alert.addCommand(new Command("Wróć", Command.BACK, 0));
Display display = Display.getDisplay(this);
display.setCurrent(alert);
}
public void commandAction(Command c, Displayable d)
{
if (c.getCommandType() == Command.EXIT)
notifyDestroyed();
else if (c.getCommandType() == Command.BACK)
Display.getDisplay(this).setCurrent(m_OtherScreen);
}
25
Command
label nazwa
commandType semantyka (znaczenie)
Stałe: Command.BACK, CANCEL, EXIT, HELP,
OK, SCREEN, STOP, do pozostałych ITEM
priority określa kolejność
26
Command
Telefon sam określa rozmieszczenie i sposób
uruchamiania poleceń
Kiedy poleceń jest dużo, zapewnia menu
27
Obrazek
Obrazek w pamięci reprezentuje klasa Image
Obrazek może być Mutable lub Immutable
Jedyny na pewno wspierany format to PNG
28
Obrazek - wykorzystanie
try
{
Image image = Image.createImage("/Ikonka.png");
Alert alert = new Alert(
"Wyjście",
"Czy na pewno chcesz wyjść?",
Plik w podkatalogu res/
image,
null);
alert.setCommandListener(this);
alert.addCommand(new Command("Zakończ", Command.EXIT, 0));
alert.addCommand(new Command("Wróć", Command.BACK, 0));
Display display = Display.getDisplay(this);
display.setCurrent(alert);
}
catch (java.io.IOException e)
{
e.printStackTrace();
}
29
Lista
List m_List;
protected void startApp() throws MIDletStateChangeException
{
m_List = new List("Lista", Choice.EXCLUSIVE);
m_List.append("Opcja 1", null);
m_List.append("Opcja 2", null);
m_List.append("Opcja 3", null);
m_List.setCommandListener(this);
m_List.addCommand(new Command("Zakończ", Command.EXIT, 0));
m_List.addCommand(new Command("OK", Command.OK, 0));
Display display = Display.getDisplay(this);
display.setCurrent(m_List);
}
public void commandAction(Command c, Displayable d)
{
if (c.getCommandType() == Command.EXIT)
notifyDestroyed();
else if (c.getCommandType() == Command.OK)
GoFurther(m_List.getSelectedIndex());
}
30
Lista wielokrotnego wyboru
List m_List;
protected void startApp() throws MIDletStateChangeException
{
m_List = new List("Lista", Choice.MULTIPLE);
m_List.append("Opcja 1", null);
m_List.append("Opcja 2", null);
m_List.append("Opcja 3", null);
m_List.setCommandListener(this);
m_List.addCommand(new Command("Zakończ", Command.EXIT, 0));
m_List.addCommand(new Command("OK", Command.OK, 0));
Display display = Display.getDisplay(this);
display.setCurrent(m_List);
}
public void commandAction(Command c, Displayable d)
{
if (c.getCommandType() == Command.EXIT)
notifyDestroyed();
else if (c.getCommandType() == Command.OK)
GoFurther(m_List.isSelected(0), m_List.isSelected(1), m_List.isSelected(2));
}
31
Lista jako menu
List m_List;
protected void startApp() throws MIDletStateChangeException
{
m_List = new List("Lista", Choice.IMPLICIT);
m_List.append("Opcja 1", null);
m_List.append("Opcja 2", null);
m_List.append("Opcja 3", null);
m_List.setCommandListener(this);
m_List.setSelectCommand(new Command("Wybierz", Command.ITEM, 0));
Display display = Display.getDisplay(this);
display.setCurrent(m_List);
}
public void commandAction(Command c, Displayable d)
{
if (c.getCommandType() == Command.ITEM)
GoFurther(m_List.getSelectedIndex());
}
32
Czcionka
Font font = Font.getFont(
Font.FACE_MONOSPACE,
Font.STYLE_BOLD,
Font.SIZE_LARGE);
m_List = new List("Lista", Choice.IMPLICIT);
m_List.append("Opcja 1", null);
m_List.append("Opcja 2", null);
m_List.append("Opcja 3", null);
m_List.setFont(0, font);
m_List.setFont(1, font);
m_List.setFont(2, font);
33
Czcionka
face: size:
FACE_MONOSPACE SIZE_LARGE
FACE_PROPORTIONAL SIZE_MEDIUM
FACE_SYSTEM SIZE_SMALL
Nie zawsze dostajemy
style:
pożądane, różniące się
STYLE_BOLD
czcionki!
STYLE_ITALIC
STYLE_PLAIN
STYLE_UNDERLINED
34
TextBox
TextBox m_TextBox;
protected void startApp() throws MIDletStateChangeException
{
m_TextBox = new TextBox("Login", "", 32, 0);
m_TextBox.setCommandListener(this);
m_TextBox.addCommand(new Command("OK", Command.OK, 0));
Display display = Display.getDisplay(this);
display.setCurrent(m_TextBox);
}
public void commandAction(Command c, Displayable d)
{
if (c.getCommandType() == Command.OK)
GoFurther(m_TextBox.getString());
}
35
TextBox - Constraints
Ograniczenia:
TextField.ANY
TextField.EMAILADDR
TextField.NUMERIC
TextField.PHONENUMBER
TextField.URL
TextField.DECIMAL
Modyfikatory:
TextField.PASSWORD
TextField.UNEDITABLE
TextField.SENSITIVE
TextField.NON_PREDICTIVE
TextField.INITIAL_CAPS_WORD
TextField.INITIAL_CAPS_SENTENCE
36
Formularz
m_Form = new Form("Formularz");
Item item;
item = new StringItem("Twoje dane", "Tu wpisz swoje dane", 0);
item.setLayout(Item.LAYOUT_2 | Item.LAYOUT_NEWLINE_AFTER);
m_Form.append(item);
item = new TextField("Imię", "Twoje imię", 32, 0);
item.setLayout(Item.LAYOUT_2 | Item.LAYOUT_NEWLINE_AFTER);
m_Form.append(item);
item = new TextField("Nazwisko", "Twoje nazwisko", 32, 0);
item.setLayout(Item.LAYOUT_2 | Item.LAYOUT_NEWLINE_AFTER);
m_Form.append(item);
m_Form.setCommandListener(this);
m_Form.addCommand(new Command("OK", Command.OK, 0));
Display display = Display.getDisplay(this);
display.setCurrent(m_Form);
Kilka różnych kontrolek na jednym ekranie.
37
Kontrolki statyczne
m_Form = new Form("Formularz");
Image image;
try
{
image = Image.createImage("/Ikonka2.png");
}
catch (java.io.IOException e)
{
e.printStackTrace();
image = null;
}
m_Form.append(new StringItem(null, "Napis...", Item.PLAIN));
m_Form.append(new Spacer(32, 32));
m_Form.append(new ImageItem(null, image, ImageItem.LAYOUT_DEFAULT, null));
m_Form.setCommandListener(this);
m_Form.addCommand(new Command("OK", Command.OK, 0));
Display display = Display.getDisplay(this);
display.setCurrent(m_Form);
38
TextField
Pole do wprowadzania tekstu
Flagi takie same jak dla TextBox
m_Form.append(new TextField(
"URL",
"http://www.google.pl/",
256,
TextField.URL));
39
Gauge
Suwak do wybierania wartości liczbowej
m_Form.append(new Gauge(
"Głośność", // label
true, // interactive
// maxValue
9,
5)); // initialValue
40
DateTime
Wybór daty i/lub czasu
m_Form.append(new DateField(
"Data", DateField.DATE));
m_Form.append(new DateField(
"Czas", DateField.TIME));
m_Form.append(new DateField(
"Data i czas", DateField.DATE_TIME));
41
ChoiceGroup
Lista, podobnie jak ekran List
String[] elements = {
"Opcja 1", "Opcja 2", "Opcja 3"
};
m_Form.append(new ChoiceGroup(
"ChoiceGroup", // label
Choice.EXCLUSIVE, // choiceType
// elements
elements,
// imageElements
null));
42
ChoiceGroup - rodzaje
Choice.EXCLUSIVE
Choice.MULTIPLE
Choice.POPUP
43
Alert raz jeszcze - Indicator
Inne zastosowanie kontrolki Gauge
Pokazywanie postępu wewnątrz ekranu Alert
Alert alert = new Alert(
"Loading",
"Trwa ładowanie gry, proszę czekać...",
null,
AlertType.INFO);
alert.setIndicator(new Gauge(
null, // label
Musi tak być
false, // interactive
9, // maxValue
Flagi: INDEFINITE,
0)); // initialValue
CONTINUOUS_IDLE,
alert.setTimeout(Alert.FOREVER);
CONTINUOUS_RUNNING,
INCREMENTAL_IDLE,
INCREMENTAL_UPDATING
44
Ticker
Tekst przelatujący u góry ekranu
Dostępny na dowolnym ekranie metoda setTicker
klasy Displayable
Alert alert = new Alert(
"Loading",
"Trwa ładowanie gry, proszę czekać...",
null,
AlertType.INFO);
alert.setTimeout(Alert.FOREVER);
Ticker ticker = new Ticker("REKLAMA!!!");
alert.setTicker(ticker);
45
Timer, TimerTask
java.util.Timer pozwala zakolejkować
zadania do wykonania za/co określony czas
java.util.TimerTask klasa bazowa dla zadań
Każdy Timer to osobny wątek
Treść zadania jest wykonywana w osobnym wątku.
Czy nie warto czasem napisać własny wątek
zamiast używać Timera?
46
Timer - przykład
Gauge m_Gauge; private class MyTask
java.util.Timer m_Timer;
extends java.util.TimerTask
{
protected void startApp()
public void run()
throws MIDletStateChangeException {
{ m_Gauge.setValue(
m_Gauge.getValue() + 1);
Alert alert = new Alert(
"Loading",
}
"Trwa ładowanie gry, proszę czekać...", }
null,
AlertType.INFO);
alert.setTimeout(Alert.FOREVER);
m_Gauge = new Gauge(null, false, 9, 0);
alert.setIndicator(m_Gauge);
Display display = Display.getDisplay(this);
display.setCurrent(alert);
m_Timer = new java.util.Timer();
m_Timer.schedule(new MyTask(), 500, 500);
}
47
Wejście-wyjście
java.io
48
Wejście-wyjście
Pakiet java.io - strumienie
49
Wejście-wyjście
InputStream - abstrakcyjna klasa bazowa do odczytywania danych binarych
jako bajty
OutputStream - abstrakcyjna klasa bazowa do zapisywania bajtów jako
dane binarne
DataInput - interfejs do odczytywania danych binarnych jako różne typy
DataInputStream - nakładka na InputStream do odczytywania danych
binarnych jako różne typy
DataOutput - interfejs do zapisywania różnych typów jako dane binarne
DataOutputStream - nakładka na OutputStream do zapisywania
różnych typów jako dane binarne
Writer abstrakcyjna klasa bazowa do zapisywania znaków
OutputStreamWriter - nakładka na strumień zapisująca do niego znaki
Reader - abstrakcyjna klasa bazowa do odczytywania znaków
InputStreamReader - nakładka na strumień odczytująca z niego znaki
50
Wczytanie pliku tekstowego
String s;
try
{
java.io.InputStream is =
this.getClass().getResourceAsStream("/Test.txt");
StringBuffer sb = new StringBuffer();
int i;
while ( (i = is.read()) != -1 )
sb.append( (char)i );
s = sb.toString();
}
catch (java.io.IOException e)
{
e.printStackTrace();
s = "";
}
// Plik res\Test.txt
51
Wczytanie pliku binarnego
String s;
try
{
java.io.InputStream is =
this.getClass().getResourceAsStream("/Test.bin");
java.io.DataInputStream dis =
new java.io.DataInputStream(is);
int i = dis.readInt();
s = Integer.toString(i);
}
catch (java.io.IOException e)
{
e.printStackTrace();
s = "";
}
// Plik res\Test.bin (Big Endian!)
52
Internet
javax.microedition.io
53
Internet
HTTP
javax.microedition.io.HttpConnection
HTTPS, SSL/TLS
javax.microedition.io.HttpsConnection
javax.microedition.io.SecureConnection
TCP, UDP
javax.microedition.io.SocketConnection
javax.microedition.io.ServerSocketConnection
javax.microedition.io.DatagramConnection
javax.microedition.io.UDPDatagramConnection
Push
javax.microedition.io.PushRegistry
Port szeregowy
javax.microedition.io.CommConnection
54
HTTP - przykład
import java.io.*;
import javax.microedition.io.*;
try {
HttpConnection c =
(HttpConnection)Connector.open("http://www.google.pl");
int ResponseCode = c.getResponseCode();
if (ResponseCode != HttpConnection.HTTP_OK)
// Błąd!
String ContentType = c.getType();
int Length = (int)c.getLength();
InputStream is = c.openInputStream();
...
}
catch (java.io.IOException e)
{
...
}
55
Grafika 2D
Canvas
Graphics
56
Canvas
Klasa javax.microedition.lcdui.Canvas
Dziedziczy z Displayable
Ekran całkowicie do dyspozycji dla programisty
Ręczne rysowanie i obsługa wejścia
Sposób użycia:
Zdefiniować klasę dziedziczącą z Canvas
Zaimplementować metody:
Obowiązkowo: paint
Opcjonalnie: sizeChanged, showNotify, hideNotify,
keyPressed, keyReleased, keyRepeated,
pointerDragged, pointerPressed, pointerReleased
57
Canvas - przykład
private class MyCanvas extends Canvas
{
protected void paint(Graphics g)
{
g.setColor(0xFF000000);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(0xFFFFFFFF);
g.drawString("Hello World!", 4, 4,
Graphics.TOP | Graphics.LEFT);
}
}
protected void startApp()
throws MIDletStateChangeException
{
MyCanvas canvas = new MyCanvas();
Display display = Display.getDisplay(this);
display.setCurrent(canvas);
}
58
Sprawdzanie możliwości
Pamięć
Runtime rt = Runtime.getRuntime();
rt.totalMemory(), rt.freeMemory()
Wyświetlacz
Display d = Display.getDisplay(this);
d.isColor(), d.numColors(), d.numAlphaLevels()
Canvas
c.getWidth(), c.getHeight(),
c.isDoubleBuffered(), c.hasPointerEvents(),
c.hasPointerMotionEvents(), c.hasRepeatEvents()
59
Różnorodność możliwości
Klawiatura
Zestaw wymaganych klawiszy + niestandardowe
Problemy z klawiszami wciskanymi jednocześnie
Urządzenie wskazujące
Całkowicie opcjonalnie
Ekran
Bardzo różnorodne rozdzielczości
128x128, 128x160 176x220, 240x320 i inne...
Niektóre ekrany są wyższe, inne szersze!
640x200, 320x240 i inne...
FullScreen nawet wtedy mogą pozostać paski!
canvas.setFullScreenMode(true);
60
Graphics ustawienia
Prostokąt przycinania
Kolor
Czcionka
Styl linii SOLID, DOTTED
Początek układu współrzędnych
61
Graphics rysowanie
g.drawLine(4, 4, 32, 32);
g.drawRect(36, 4, 28, 28);
g.drawRoundRect(68, 4, 28, 28, 16, 16);
g.drawArc(100, 4, 28, 28, 20, 200);
g.fillTriangle(4, 36, 32, 50, 4, 64);
g.fillRect(36, 36, 28, 28);
g.fillRoundRect(68, 36, 28, 28, 16, 16);
g.fillArc(100, 36, 28, 28, 20, 200);
62
Graphics tekst, obrazek
g.drawString("Hello World!", getWidth()/2, 4,
Graphics.TOP | Graphics.HCENTER);
g.drawImage(m_Image, getWidth()/2, getHeight()/2,
Graphics.HCENTER | Graphics.VCENTER);
Anchor:
Graphics.BOTTOM, TOP,
VCENTER, BASELINE,
LEFT, VCENTER, RIGHT
63
Canvas - klawiatura
private class MyCanvas extends Canvas
{
protected void keyPressed(int keyCode)
{
// ...
}
Canvas.KEY_NUM0 KEY_NUM9
KEY_STAR (*)
protected void keyReleased(int keyCode)
{
KEY_POUND (#)
// ...
if (keyCode > 0)
}
char ch = (char)keyCode;
Niestandardowe...
protected void keyRepeated(int keyCode)
{
// ...
}
// ...
Działa tylko jeśli hasRepeatEvents()
64
Klawisze do gier
Mapowanie klawiszy zwykłych na akcje gry:
getGameAction
Stałe akcji gry:
Canvas.UP, DOWN, LEFT, RIGHT, FIRE,
GAME_A, GAME_B, GAME_C, GAME_D
protected void keyPressed(int keyCode)
{
int game_action = getGameAction(keyCode);
if (game_action == Canvas.FIRE)
Fire();
else if (game_action == Canvas.UP)
Jump();
// ...
}
65
Programowanie gier 2D
GameCanvas
Layer
LayerManager
66
GameCanvas
Pakiet javax.microedition.lcdui.game
Klasy przeznaczone do pisania gier 2D
Klasa GameCanvas
Dziedziczy z Canvas
Zapewnie podwójne buforowanie obrazu
Umożliwia odpytywanie o wciśnięte klawisze
Pętlę gry trzeba zrealizować samemu
Za pomocą Timera lub
Za pomocą własnego wątku
67
Pętla gry propozycja
MyCanvas m_MyCanvas;
private class MyCanvas
Thread m_Thread;
extends GameCanvas
implements Runnable
protected void startApp()
{
throws MIDletStateChangeException
public MyCanvas()
{
{
m_MyCanvas = new MyCanvas();
super(false);
setFullScreenMode(true);
Display display =
}
Display.getDisplay(this);
display.setCurrent(m_MyCanvas);
public void run()
{
m_Thread = new Thread(m_MyCanvas);
Graphics g = getGraphics();
m_Thread.start();
while (true) {
}
int keyState = getKeyStates();
if ((keyState & LEFT_PRESSED) != 0)
// ...
// Obliczenia...
// Rysowanie...
g.setColor(0xFF000000);
g.fillRect(0, 0,
getWidth(), getHeight());
flushGraphics(); // Koniec rysowania
}
}
}
68
Wejście z klawiatury
Stałe GameCanvas.
LEFT_PRESSED
getKeyStates()
RIGHT_PRESSED
UP_PRESSED
Zwraca flagi bitowe z wciśniętymi w
DOWN_PRESSED
danej chwili klawiszami
FIRE_PRESSED
GAME_A_PRESSED
GAME_B_PRESSED
super(false);
GAME_C_PRESSED
Zdarzenia keyPressed, keyReleased,
GAME_D_PRESSED
keyRepeated nie będą generowane -
wydajność!
69
Pomiar czasu i FPS
long StartTime = System.currentTimeMillis();
long LastTime = 0;
long t = 0, dt = 0;
long LastFpsTime = 0;
int FpsCounter = 0, Fps = 0;
while (true) {
int KeyStates = getKeyStates();
CalcFrame(t, dt, KeyStates);
DrawFrame(g);
DrawFPS(Fps);
flushGraphics();
LastTime = t;
t = System.currentTimeMillis() - StartTime;
dt = t LastTime;
FpsCounter++;
if (LastFpsTime + 1000 <= t) {
Fps = FpsCounter;
FpsCounter = 0;
LastFpsTime += 1000;
}
}
System.currentTimeMillis() - zwraca 64-bitową liczbę milisekund od 1
stycznia 1970.
Warto stosować Thread.sleep() - oszczędność energii!
70
Layer
Klasa javax.microedition.lcdui.game.Layer
Reprezentuje pojedynczy obiekt graficzny 2D
(wbrew nazwie!)
Dwie klasy pochodne: Sprite, TiledLayer
Zawiera:
Pozycję
Stan widoczności
Rozmiar (tylko do odczytu)
Umiejętność narysowania
się na Graphics
71
Sprite
Sprite m_Sprite;
public MyCanvas()
{
// ...
m_Sprite = new Sprite(image);
m_Sprite.setPosition(4, 4);
}
public void run()
{
// ...
int keyState = getKeyStates();
if ((keyState & LEFT_PRESSED ) != 0) m_Sprite.move(-1 * (int)dt, 0);
if ((keyState & RIGHT_PRESSED) != 0) m_Sprite.move( 1 * (int)dt, 0);
if ((keyState & UP_PRESSED ) != 0) m_Sprite.move(0, -1 * (int)dt);
if ((keyState & DOWN_PRESSED ) != 0) m_Sprite.move(0, 1 * (int)dt);
// ...
m_Sprite.paint(g);
// ...
}
Pojedynczy obrazek
72
Sprite - transformacje
Obrazek może być obrócony lub odbity
Niedostępne w zwykłym Image!
Skalowanie i obracanie o dowolny kąt nadal
niedostępne :(
73
Sprite piksel odniesienia
defineReferencePixel() - ustawia piksel odniesienia
setRefPixelPosition() - ustawia obrazek tak żeby
piksel odniesienia był w danym miejscu
Działa także z obrazkami obróconymi
74
Sprite - animacja
Obrazek może się składać z wielu klatek animacji
Domyślnie animację tworzą klatki pokazywane kolejno
Można ustawić własną sekwencję klatek
setFrameSequence()
Animacją trzeba sterować ręcznie
setFrame(), prevFrame(), nextFrame()
75
TiledLayer
Obiekt pokazujący tablicę kafelków
kopiowanych z obrazka zródłowego
76
TiledLayer rodzaje kafelków
= 0 pusta komórka
> 0 fragment obrazka zródłowego
< 0 komórka animowana
Zastosowanie np. animowana woda
77
Sprite - kolizje
Wsparcie dla kolizji między obiektami
Także z kafelkami TiledLayer
Także na poziomie przezroczystości
pojedynczych pikseli !!!
78
LayerManager
Klasa upraszczająca zarządzanie obiektami 2D
Jej wykorzystanie jest opcjonalne.
Przechowuje listę obiektów klasy Layer
Pamięta kolejność
Pozwala je narysować jednym wywołaniem
Oddzielenie prostokąta ekranu od prostokąta
rysowanej sceny
Zaleta #1: Rysowanie widoku tylko
w ograniczonym prostokącie
Zaleta #2: Przewijanie mapy
79
Kilka drobiazgów
Wibracja
Właściwości systemu
Obsługa błędów
80
Wibracja
Dodatkowy efekt zmysłowy!
Niedostępny na PC
Chyba, że gracz ma joypad z Force Feedback i
używasz DirectInput...
Display display = Display.getDisplay(this);
display.vibrate(200);
Wywołanie jest asynchroniczne nie blokuje
Podawany czas jest w milisekundach
0 oznacza wyłączenie wibracji
81
Właściwości systemu
System.getProperty
Właściwości standardowe
microedition.profiles
microedition.configuration
microedition.locale
itd...
Właściwości dodatkowe
microedition.m3g.version
Bluetooth.api.version
video.encodings
itd...
82
Obsługa błędów
Trzeba zawsze łapać wyjątki lub deklarować ich
zgłaszanie
Jak zawsze w Javie.
Są to np.:
java.io.IOException
podczas wczytywania plików
java.lang.InterruptedException
przy oczekiwaniu w Thread, np. join(), sleep()
W emulatorze działa System.out.println()
A więc także Throwable.printStackTrace()
Image image;
try {
image = Image.createImage("/Obrazek.png );
}
catch (java.io.IOException e) {
e.printStackTrace();
image = null;
}
83
Baza danych
javax.microedition.rms.RecordStore
84
Baza danych???
MIDP nie daje dostępu do systemu plików
Chyba, że przez rozszerzenie JSR-75
Nie ma innej możliwości zachowania trwałych
danych niż RMS
RMS Record Management System
Pakiet javax.microedition.rms, klasa
RecordStore
Aplikacja może tworzyć i używać baz danych.
Każda ma nazwę.
Baza danych składa się z rekordów. Każdy ma
identyfikator oraz treść surowe dane binarne.
85
Dodawanie rekordu
import java.io.*;
import javax.microedition.rms.*;
try
{
RecordStore rs = RecordStore.openRecordStore("Baza1", true);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeUTF("Mieczysław");
dos.writeInt(1000);
byte[] data = baos.toByteArray();
int rec_id = rs.addRecord(data, 0, data.length);
}
catch (RecordStoreException e) { }
catch (IOException e) { }
86
Enumeracja rekordów
try
{
RecordStore rs = RecordStore.openRecordStore("Baza1", true);
RecordEnumeration re = rs.enumerateRecords(null, null, false);
while (re.hasNextElement())
{
int id = re.nextRecordId();
ByteArrayInputStream bais =
new ByteArrayInputStream(rs.getRecord(id));
DataInputStream dis = new DataInputStream(bais);
String name = dis.readUTF();
int score = dis.readInt();
System.out.println(name + " - " + score);
}
}
catch (RecordStoreException e) { }
catch (IOException e) { }
87
Enumeracja - filtrowanie
private class MyFilter implements RecordFilter
{
public boolean matches(byte[] candidate)
{
try {
ByteArrayInputStream bais = new ByteArrayInputStream(candidate);
DataInputStream dis = new DataInputStream(bais);
String name = dis.readUTF();
int score = dis.readInt();
return (score > 1000);
}
catch (IOException e) { e.printStackTrace(); return false; }
}
}
...
RecordEnumeration re = rs.enumerateRecords(new MyFilter(), null, false);
88
Enumeracja - sortowanie
private class MyComparator implements RecordComparator
{
public int compare(byte[] rec1, byte[] rec2)
{
try {
ByteArrayInputStream bais1 = new ByteArrayInputStream(rec1);
ByteArrayInputStream bais2 = new ByteArrayInputStream(rec2);
DataInputStream dis1 = new DataInputStream(bais1);
DataInputStream dis2 = new DataInputStream(bais2);
int score1 = dis1.readInt();
int score2 = dis2.readInt();
String name1 = dis1.readUTF();
String name2 = dis2.readUTF();
int cmp = name1.compareTo(name2);
if (cmp < 0) return RecordComparator.PRECEDES;
else if (cmp == 0) return RecordComparator.EQUIVALENT;
else return RecordComparator.FOLLOWS;
}
catch (IOException e) {
e.printStackTrace();
return RecordComparator.PRECEDES;
}
}
}
...
RecordEnumeration re = rs.enumerateRecords(null, new MyComparator(), false);
89
Inne możliwości
Operacje na rekordach
addRecord, getRecord, getRecordSize, deleteRecord
Nasłuchiwanie zmian
addRecordListener, removeRecordListener,
interfejs RecordListener
Informacje o bazie danych
getNumRecords, getSize, getSizeAvailable,
getLastModified, getVersion
Operacje na bazach danych
listRecordStores, openRecordStore,
deleteRecordStore
Współdzielenie baz danych między midletami...
90
MIDP 2.0 Media API
javax.microedition.media
91
MIDP 2.0 Media API
Zapewnia podstawowe API do odtwarzania
multimediów
Tylko dzwięk
Rozszerzane przez Mobile Media API (JSR-135)
Nie gwarantuje obsługi żadnych formatów
Jedynie proste generowanie odgłosów
92
MIDP 2.0 Media API
<
>
Manager
Manager
<>
Klasa statyczna
Dostarcza informacji na temat możliwości multimedialnych
urządzenia
Umożliwia tworzenie Playerów
<>
Player
Player
Odtwarza konkretny zasób multimedialny
<>
Pozwala na sterowanie odtwarzaniem
<>
Posiada stany
Control
Pozwala na pobieranie kontrolerów
Control
Pozwala na regulację parametrów odtwarzania, np.
głośności
93
Odtwarzanie dzwięku
try {
Player p = Manager.createPlayer("http://adres.pl/ding.wav");
p.setLoopCount(5);
p.start();
}
catch (IOException ioe) { }
catch (MediaException me) { }
try {
InputStream is = getClass().getResourceAsStream("/ding.wav");
Player p = Manager.createPlayer(is, "audio/X-wav");
p.start();
}
catch (IOException ioe) { }
catch (MediaException me) { }
94
Obsługiwane formaty
Content Type typ MIME, np.:
Dzwięk WAV: audio/x-wav
Dzwięk AU: audio/basic
Dzwięk MP3: audio/mpeg
Dzwięk MIDI: audio/midi
Tone sequence: audio/x-tone-seq
95
Player stany
96
Rozszerzenia
MIDP 2.0 to nie koniec...
97
Przykłady rozszerzeń
Grafika 3D
Mobile 3D Graphics API M3G (JSR-184)
Mascot Capsule
Grafika i multimedia
Mobile Media API (JSR-135)
Nokia UI API
Dostęp do plików
FileConnection (JSR-75)
Dostęp do danych PIM (kontakty itp.)
PIM (JSR-75)
Aączność
Java APIs for Bluetooth (JSR-82)
Wireless Messaging API WMA (JSR-120)
Wiele innych...
98
Wyszukiwarka
Podobne podstrony:
Sieci komputerowe wyklady dr Furtak
Wykład 05 Opadanie i fluidyzacja
WYKŁAD 1 Wprowadzenie do biotechnologii farmaceutycznej
mo3 wykladyJJ
ZARZĄDZANIE WARTOŚCIĄ PRZEDSIĘBIORSTWA Z DNIA 26 MARZEC 2011 WYKŁAD NR 3
Wyklad 2 PNOP 08 9 zaoczne
Wyklad studport 8
Kryptografia wyklad
Budownictwo Ogolne II zaoczne wyklad 13 ppoz
wyklad09
Sporzadzanie rachunku przepływów pienieżnych wykład 1 i 2
fcs wyklad 5
Wyklad08 Zaopatrz wWode
więcej podobnych podstron