継承を用いないでクラスの機能を拡張する方法として複数のオブジェクトを組み合わせるコンポジション(composition:複合)がある.
コンポジションはオブジェクトBのインスタンスを別のオブジェクトAのインスタンス変数に入れることで実現する.オブジェクトBのインスタンスはオブジェクトAの機能も有することになる.
以下のサンプルでは,Circleのインスタンスc1を,LinearMoveとColorTransのインスタンスの引数に代入することで,c1に移動と変色の機能を加えている.
getRed(),getGreen(),getBlue(),getAlpha()メソッドはそれぞれ,色の赤,緑,青成分と透明度を取り出すことができる.
クラス間の関係は次のとおりである.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
public class Sample91 extends JPanel {
ArrayList<Figure> figs = new ArrayList<Figure>();
ArrayList<Animation> anim = new ArrayList<Animation>();
public Sample91() {
Color col1 = Color.BLACK, col2 = Color.RED;
Circle c1 = new Circle(col1, 100, 200, 20);
figs.add(c1);
//Circleクラスのc1をLinearMoveクラスのコンストラクタの引数にしている.
anim.add(new LinearMove(c1, 3, 100, 200, 5, 200, 60));
//Circleクラスのc1をColorTransクラスのコンストラクタの引数にしている.
anim.add(new ColorTrans(c1, 4, col1, 5, col2));
setOpaque(false);
final long tm0 = System.currentTimeMillis();
new javax.swing.Timer(30, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
float tm = 0.001f * (System.currentTimeMillis() - tm0);
//一定時間ごとにArrayList Animationの要素を動作させる
for (Animation a : anim) {
a.setTime(tm);
}
repaint();
}
}).start();
}
public void paintComponent(Graphics g) {
for (Figure f : figs) {
f.draw(g);
}
}
public static void main(String[] args) {
JFrame app = new JFrame();
app.add(new Sample91());
app.setSize(400, 240);
app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
app.setVisible(true);
}
interface Figure {
public void draw(Graphics g);
public void moveTo(float x, float y);
public void setColor(Color c);
}
interface Animation {
public void setTime(float dt);
}
static abstract class SimpleFigure implements Figure {
Color col;
float xpos, ypos;
public SimpleFigure(Color c, float x, float y) {
col = c;
xpos = x;
ypos = y;
}
public void moveTo(float x, float y) {
xpos = x;
ypos = y;
}
public void setColor(Color c) {
col = c;
}
public void draw(Graphics g) {
g.setColor(col);
}
}
static class Circle extends SimpleFigure {
float rad;
public Circle(Color c, float x, float y, float r) {
super(c, x, y);
rad = r;
}
public void draw(Graphics g) {
int x = (int) (xpos - rad), y = (int) (ypos - rad);
super.draw(g);
g.fillOval(x, y, (int) rad * 2, (int) rad * 2);
}
}
static class LinearMove implements Animation {
Figure fig;
float time1, xpos1, ypos1, time2, xpos2, ypos2;
//Figure figを時刻time1に(xpos1,ypos1)の位置から,時刻time2に(xpos2,ypos2)の位置まで移動させる.
public LinearMove(Figure f, float t1, float x1, float y1, float t2,
float x2, float y2) {
time1 = t1;
xpos1 = x1;
ypos1 = y1;
time2 = t2;
xpos2 = x2;
ypos2 = y2;
fig = f;
}
public void setTime(float t) {
//時刻tがtime1とtime2の間でなければ何もしない.
if (t < time1 || time2 < t) {
return;
}
//経過時間に応じて図形の位置を変化させる.
float p = (time2 - t) / (time2 - time1), q = 1.0f - p;
fig.moveTo(p * xpos1 + q * xpos2, p * ypos1 + q * ypos2);
}
}
static class ColorTrans implements Animation {
Figure fig;
float time1, time2;
int r1, g1, b1, a1, r2, g2, b2, a2;
//Figure figを時刻time1に(r1,g1,b1)の色から,時刻time2に(r2,g2,b2)の色まで変化させる.
public ColorTrans(Figure f, float t1, Color c1, float t2, Color c2) {
fig = f;
time1 = t1;
time2 = t2;
r1 = c1.getRed();//c1の赤色成分を取り出す.
g1 = c1.getGreen();//c1の緑色成分を取り出す.
b1 = c1.getBlue();//c1の青色成分を取り出す.
a1 = c1.getAlpha();//c1の透明度を取り出す.
r2 = c2.getRed();
g2 = c2.getGreen();
b2 = c2.getBlue();
a2 = c2.getAlpha();
}
public void setTime(float t) {
//時刻tがtime1とtime2の間でなければ何もしない.
if (t < time1 || time2 < t) {
return;
}
//経過時間に応じて図形の色を変化させる.
float p = (time2 - t) / (time2 - time1), q = 1.0f - p;
fig.setColor(new Color((int) (p * r1 + q * r2), (int) (p * g1 + q
* g2), (int) (p * b1 + q * b2), (int) (p * a1 + q * a2)));
}
}
}
アニメーションにおける場面転換は状態遷移を用いて実現できる.
以下のサンプルでは4つの場面(scene)が以下のように順次切り替わるようになっている.
場面はSceneクラスを継承して作られている.Sceneクラスには,構成オブジェクトの描画を行うdraw(g),時刻に応じた動作をさせるsetTime(t),マウスをクリックしたときの動作を記述するpress(x,y),場面が終了しているかどうかを示すisEnded(),次の場面を示すgetNext()メソッドなどが定義されている.
ended |= (t>5)はended = ended | (t>5)と同じ意味である.すなわりendedがtrueであるか,t>5がtrueであるとき,endedはtrueになる.x += 1がx = x + 1であるのと同様である.
サンプルの実行にはsun1.pngとkuno1.pngの画像ファイルが必要になる.ここからダウンロード可能である.
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;
import javax.imageio.*;
import javax.swing.*;
public class Sample92 extends JPanel {
Scene cur = new Scene1();// 初期場面をScene1にする.
long tm0 = System.currentTimeMillis();
public Sample92() {
setOpaque(false);
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent evt) {
cur.press(evt.getX(), evt.getY());
}
});
new javax.swing.Timer(30, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
float tm = 0.001f * (System.currentTimeMillis() - tm0);
cur.setTime(tm);
repaint();
// 場面が終了すれば次の場面にする
if (cur.isEnded()) {
cur = cur.getNext();
tm0 = System.currentTimeMillis();
}
}
}).start();
}
public void paintComponent(Graphics g) {
cur.draw(g);
}
public static void main(String[] args) {
JFrame app = new JFrame();
app.add(new Sample92());
app.setSize(400, 240);
app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
app.setVisible(true);
}
// 場面の定義
static class Scene {
ArrayList<Figure> figs = new ArrayList<Figure>();
ArrayList<Animation> anim = new ArrayList<Animation>();
boolean ended = false;// 終了のフラグ
Scene next = null;// 次の場面を示す変数
public void draw(Graphics g) {
for (Figure f : figs) {
f.draw(g);
}
}
public void setTime(float t) {
for (Animation a : anim) {
a.setTime(t);
}
}
public void press(int x, int y) {
}
public boolean isEnded() {
return ended;
}
public Scene getNext() {
return next;
}
}
// Scene1の定義
static class Scene1 extends Scene {
Rect r1 = new Rect(Color.YELLOW, 100, 100, 100, 100);
Rect r2 = new Rect(Color.YELLOW, 250, 100, 100, 100);
public Scene1() {
Picture p1 = new Picture("kuno1.png", 0, 0);
Picture p2 = new Picture("sun1.png", 0, 0);
figs.add(r1);
figs.add(r2);
figs.add(p1);
figs.add(p2);
anim.add(new ZigzagMove(p1, 1f, 95, 100, 105, 100));
anim.add(new ZigzagMove(p2, 1f, 250, 95, 250, 105));
figs.add(new Text(20, 25, "お好きな方をクリック", new Font("serif",
Font.BOLD, 18)));
}
public void press(int x, int y) {
// r1をクリックすれば終了して,Scene2へ
if (r1.hit(x, y)) {
next = new Scene2();
ended = true;
// r2をクリックすれば終了して,Scene4へ
} else if (r2.hit(x, y)) {
next = new Scene4();
ended = true;
}
}
}
// Scene2の定義
static class Scene2 extends Scene {
public Scene2() {
next = new Scene3();
figs.add(new Text(20, 90, "動画作品、見て下さい。", new Font("serif",
Font.BOLD, 18)));
}
public void press(int x, int y) {
ended = true;
}
public void setTime(float t) {
ended |= (t > 5);// endがtrueであるか,30*5ミリ秒経過すると終了
}
}
// Scene3の定義
static class Scene3 extends Scene {
public Scene3() {
next = new Scene1();// 次の場面はScene1
Color s1 = new Color(50, 50, 100), s2 = new Color(220, 220, 250);
Rect sky = new Rect(s1, 200, 100, 400, 210);
Circle c1 = new Circle(Color.YELLOW, 200, 60, 20);
Color pc1 = new Color(180, 110, 60), pc2 = new Color(150, 80, 30);
Color pc3 = new Color(160, 100, 40, 0);
Triangle p1 = new Triangle(pc1, 260, 110, 180, 180, 290, 180);
Triangle p2 = new Triangle(pc2, 260, 110, 290, 180, 350, 180);
Rect grnd = new Rect(new Color(100, 40, 40), 200, 220, 400, 80);
figs.add(sky);
figs.add(c1);
figs.add(p1);
figs.add(p2);
figs.add(grnd);
anim.add(new LinearMove(c1, 3, 200, 60, 5, 20, 220));
anim.add(new ColorTrans(c1, 5, Color.YELLOW, 6, Color.RED));
anim.add(new LinearMove(c1, 6, 20, 220, 8, 200, 60));
anim.add(new ColorTrans(sky, 6, s1, 8, s2));
anim.add(new ColorTrans(p1, 10, pc1, 12, pc3));
anim.add(new ColorTrans(p2, 10, pc2, 12, pc3));
}
public void setTime(float t) {
super.setTime(t);
ended |= (t > 14);// 30*14ミリ秒で終了
}
}
// Scene4の定義
static class Scene4 extends Scene {
Font fn = new Font("serif", Font.BOLD, 18);
Text t1 = new Text(20, 25, "円をクリックしてください", fn);
Text t2 = new Text(20, 55, "0.00", fn);
Circle c1 = new Circle(Color.BLUE, 60, 200, 20);
boolean ok = false;
float curtime = 0f, endtime = 60f;
public Scene4() {
next = new Scene1();
figs.add(t1);
figs.add(t2);
figs.add(c1);
anim.add(new ZigzagMove(c1, 0.7f, 60, 200, 340, 40));
}
public void press(int x, int y) {
if (!c1.hit(x, y)) {
return;
}
ok = true;
c1.setColor(Color.RED);
endtime = curtime + 5;
t1.setText((curtime < 3f) ? "Good Job!" : "So-so");
}
public void setTime(float t) {
super.setTime(t);
curtime = t;
ended |= (t > endtime);
if (!ok) {
t2.setText(String.format("%4.2f", t));
}
}
}
interface Figure {
public void draw(Graphics g);
public void moveTo(float x, float y);
public void setColor(Color c);
}
interface Animation {
public void setTime(float dt);
}
static abstract class SimpleFigure implements Figure {
Color col;
float xpos, ypos;
public SimpleFigure(Color c, float x, float y) {
col = c;
xpos = x;
ypos = y;
}
public void moveTo(float x, float y) {
xpos = x;
ypos = y;
}
public void setColor(Color c) {
col = c;
}
public void draw(Graphics g) {
g.setColor(col);
}
}
static class Circle extends SimpleFigure {
float rad;
public Circle(Color c, float x, float y, float r) {
super(c, x, y);
rad = r;
}
public boolean hit(float x, float y) {
return (xpos - x) * (xpos - x) + (ypos - y) * (ypos - y) <= rad
* rad;
}
public void draw(Graphics g) {
int x = (int) (xpos - rad), y = (int) (ypos - rad);
super.draw(g);
g.fillOval(x, y, (int) rad * 2, (int) rad * 2);
}
}
static class Rect extends SimpleFigure {
float width, height;
public Rect(Color c, float x, float y, float w, float h) {
super(c, x, y);
width = w;
height = h;
}
public boolean hit(float x, float y) {
return xpos - width / 2 <= x && x <= xpos + width / 2
&& ypos - height / 2 <= y && y <= ypos + height / 2;
}
public void draw(Graphics g) {
int x = (int) (xpos - width / 2), y = (int) (ypos - height / 2);
super.draw(g);
g.fillRect(x, y, (int) width, (int) height);
}
}
static class Triangle extends SimpleFigure {
float dx1, dy1, dx2, dy2;
public Triangle(Color c, float x, float y, float x1, float y1,
float x2, float y2) {
super(c, x, y);
dx1 = x1 - x;
dy1 = y1 - y;
dx2 = x2 - x;
dy2 = y2 - y;
}
public void draw(Graphics g) {
int[] xs = { (int) xpos, (int) (xpos + dx1), (int) (xpos + dx2) };
int[] ys = { (int) ypos, (int) (ypos + dy1), (int) (ypos + dy2) };
super.draw(g);
g.fillPolygon(xs, ys, 3);
}
}
static class Text extends SimpleFigure {
String txt;
Font fn;
public Text(int x, int y, String t, Font f) {
super(Color.BLACK, x, y);
txt = t;
fn = f;
}
public void setText(String t) {
txt = t;
}
public void draw(Graphics g) {
super.draw(g);
g.setFont(fn);
g.drawString(txt, (int) xpos, (int) ypos);
}
}
static class Picture extends SimpleFigure {
BufferedImage img;
int width, height;
public Picture(String fname, int x, int y) {
super(Color.WHITE, x, y);
try {
img = ImageIO.read(new File(fname));
} catch (Exception ex) {
}
xpos = x;
ypos = y;
width = img.getWidth();
height = img.getHeight();
}
public void draw(Graphics g) {
int x = (int) xpos - width / 2, y = (int) ypos - height / 2;
g.drawImage(img, x, y, null);
}
}
static class LinearMove implements Animation {
Figure fig;
float time1, xpos1, ypos1, time2, xpos2, ypos2;
public LinearMove(Figure f, float t1, float x1, float y1, float t2,
float x2, float y2) {
time1 = t1;
xpos1 = x1;
ypos1 = y1;
time2 = t2;
xpos2 = x2;
ypos2 = y2;
fig = f;
}
public void setTime(float t) {
if (t < time1 || time2 < t) {
return;
}
float p = (time2 - t) / (time2 - time1), q = 1.0f - p;
fig.moveTo(p * xpos1 + q * xpos2, p * ypos1 + q * ypos2);
}
}
static class ColorTrans implements Animation {
Figure fig;
float time1, time2;
int r1, g1, b1, a1, r2, g2, b2, a2;
public ColorTrans(Figure f, float t1, Color c1, float t2, Color c2) {
fig = f;
time1 = t1;
time2 = t2;
r1 = c1.getRed();
g1 = c1.getGreen();
b1 = c1.getBlue();
a1 = c1.getAlpha();
r2 = c2.getRed();
g2 = c2.getGreen();
b2 = c2.getBlue();
a2 = c2.getAlpha();
}
public void setTime(float t) {
if (t < time1 || time2 < t) {
return;
}
float p = (time2 - t) / (time2 - time1), q = 1.0f - p;
fig.setColor(new Color((int) (p * r1 + q * r2), (int) (p * g1 + q
* g2), (int) (p * b1 + q * b2), (int) (p * a1 + q * a2)));
}
}
static class ZigzagMove implements Animation {
Figure fig;
float time1, xpos1, ypos1, xpos2, ypos2;
public ZigzagMove(Figure f, float t1, float x1, float y1, float x2,
float y2) {
time1 = t1;
xpos1 = x1;
ypos1 = y1;
xpos2 = x2;
ypos2 = y2;
fig = f;
}
public void setTime(float t) {
float q = (t % time1) / time1, p = 1.0f - q;
fig.moveTo(p * xpos1 + q * xpos2, p * ypos1 + q * ypos2);
}
}
}