みんなの「教えて(疑問・質問)」にみんなで「答える」Q&Aコミュニティ

こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

Javaのアプレットについて

 Javaのアプレットでゲームを作ろうとしているのですが、フリー音楽制作ソフトの「Domino」を起動したままアプレットを使うと処理速度が速くなってしまうことがわかりました。
 一応自分なりに原因を考えたのですが確証がないので、原因をご存知の方がいらっしゃれば教えていただきたいです。また、そうでなくても、何か参考になることや、皆さんなりの考え、どのようなパソコン・ソフトで同じ症状が出るのか、など、些細なことでもいいので教えていただきたいです。
 わからないなりに僕が考えたのは、Dominoが使っているシステム時間とアプレットのシステム時間にはつながりがあって、Domino側がそこをいじってしまうせいでアプレットがおかしくなっているのではないか、というものです。そもそもアプレットの書き方がおかしいのかもしれませんが・・・(「考えた」ってほどよく考えてない・・・)
 一応確認している範囲では、「Domino」、「Music Studio Producer」、「RPGツクールXP」などのDirectX使用ソフト、「YouTube」などの動画サイト、などなどを同時に起動していると処理が速くなることを確認しています。
 問題が解決できないものであったとしても、説明書に対処法を書きたいので、よろしくお願いします。

 一応、適当に作った実験用のプログラムを書いておきます。


----------------------------------------------------

import java.applet.Applet;
import java.awt.*;

public class Test extends Applet implements Runnable{
Thread thread;
Image bufferImage;
Graphics bufG;

int to_x,to_y,to_x2,to_y2,count,time;

long preTime, nowTime;
final int fps = 60;
final double frameTime = 1000 / fps;

public void init(){
to_x=to_y=to_x2=to_y2=0;
count=0;
time=0;

preTime = 0;
nowTime = 0;

requestFocus();

thread = new Thread(this);
thread.start();
}

public void run(){
while(true){

count++;
if(count>=60)
{
count=0;
time++;
if(time>=60)
{
time=0;
}
}

to_x = (int)(200 * Math.cos(Math.PI*(time-15)/60*2)) + 400;
to_y = (int)(200 * Math.sin(Math.PI*(time-15)/60*2)) + 240;
to_x2 = (int)(100 * Math.cos(Math.PI*(count-15)/60*2)) + 130;
to_y2 = (int)(100 * Math.sin(Math.PI*(count-15)/60*2)) + 350;

while(nowTime-preTime < frameTime){
nowTime = System.currentTimeMillis();
}
preTime = nowTime;

repaint();
}
}

public void paint(Graphics g){
bufferImage = createImage(640,480);
bufG = bufferImage.getGraphics();
bufG.setColor(Color.black);
bufG.fillRect(0, 0, 640, 480);

bufG.setColor(Color.white);
bufG.drawLine(400,240,to_x,to_y);
bufG.drawLine(130,350,to_x2,to_y2);
bufG.setFont(new Font("Serif",Font.BOLD + Font.ITALIC,14));
bufG.drawString("time = " + time,20,30);
bufG.drawString("milli_second = " + 1000*count/60,20,60);

g.drawImage(bufferImage, 0, 0, this);
}

public void update(Graphics g){
paint(g);
}
}

投稿日時 - 2012-08-14 20:01:35

QNo.7643841

暇なときに回答ください

質問者が選んだベストアンサー

 #1です。

 ちょっと、気づいたことだけですが。

nowTime = System.currentTimeMillis();
times++;
sleepTime = firstTime + (frameTime * times) - nowTime;

 ここら辺の処理は、TimerクラスのscheduleAtFixedRateメソッドと同じような処理ですね。
 もし、何らかの原因で処理が遅れた場合は、sleepをキャンセルして、早送りで追いつかせるという形ですね。
 時計アプリのようなものは、針が正しい時間経過を指していないといけないので、早送りでも何でもして、追いつかなければなりません。

 ただ、シューティングゲームの場合は、これが適切な挙動とはいえないように思います。

 仮に何らかの理由で、1秒間、ゲームがフリーズしたとします。
 そうすると、フリーズから復帰した後、少なくとも1秒間は sleepTimeは負の値をとるようになり、sleepメソッドは、キャンセルされて、ゲームが早送りのような状態となり、遅れを取り戻そうとします。
 その間、ユーザーは自機のコントロールができず、運が悪ければ、敵にやられてしまいます。
 これでは、ユーザーにストレスがかかってしまうでしょう。

 フリーズから復帰後は、早送りで遅れを取り戻そうとはせず、そのまま、遅れたまま、普通の速度で、ゲームが進行した方がいいと思います。

 早い話が、普通に、

Thread.sleep(frameTime);

 という、単純な形にするか、
 工夫するにしても、ハードウェアの性能に影響されにくいように、1フレームの処理にかかる時間を計測して、それを frameTime から引いた時間でsleepするなどした方がいいのでは、ないでしょうか。

投稿日時 - 2012-08-17 21:13:35

お礼

 おおお、盲点でした。確かにこのままの形式だと処理遅れ時にゲームが成り立ちませんね。
 プレイヤーが不利にならないように作りなおしてみます。よく考えてみると、処理落ち時にプレイヤーが有利になるのはやむを得ないのかもしれないですね。

投稿日時 - 2012-08-17 21:53:29

このQ&Aは役に立ちましたか?

0人が「このQ&Aが役に立った」と投票しています

回答(6)

ANo.5

 こんにちは、またまた#1です。

 確かに今回のサンプルでもscheduleメソッドが明らかに遅れているのを確認しました。

 java.util.Timer のもっと単純なサンプルを作って、いろいろ調べてみたのですが、Timerの内部の処理に 10ms~15ms もかかっているようなのです。
 scheduleAtFixedRateメソッドでは、遅れた分を帳尻あわせしてくれるので何とか普通に動いて見えますが、フレーム間が 16ms のゲームを作ろうとするなら、適切とは言い難い時間です。

 Timerに頼らず、自前でスレッドを作り、Thread.sleep メソッドなどでタイミングを計るなどした方がよいかもしれません。

投稿日時 - 2012-08-16 18:28:51

お礼

 重ね重ねありがとうございます。
 指摘をもとに、sleepを使った処理を自分なりに作ってみました。
 しかし僕はThreadに対する知識が浅いので、プログラムに不備がある気がビンビンしています。ですから変な部分が多数あるかもしれません。今のところは正常に動いていますし、別のソフトからの影響もうけていませんが・・・
 一応書き直してみたものを貼っておきます。(long型のtimesっていう変数は「処理回数」です。適切な単語が思いつきませんでした・・・)

--------------------------------------------------
import java.applet.Applet;
import java.awt.*;

public class Test extends Applet implements Runnable{
 Thread thread;
 Image bufferImage;
 Graphics bufG;

 int to_x,to_y,to_x2,to_y2,count,time;

 long firstTime, nowTime, sleepTime;
 final int fps = 60;
 final long frameTime = 1000 / fps;
 long times;

 public void init(){
  to_x=to_y=to_x2=to_y2=0;
  count=0;
  time=0;

  firstTime = System.currentTimeMillis();
  nowTime = 0;
  sleepTime = 0;
  times = 0;

  requestFocus();

  thread = new Thread(this);
  thread.start();
 }

 public void run(){
  while(true){

   count++;
   if(count>=60)
   {
    count=0;
    time++;
    if(time>=60)
    {
     time=0;
    }
   }

   to_x = (int)(200 * Math.cos(Math.PI*(time-15)/60*2)) + 400;
   to_y = (int)(200 * Math.sin(Math.PI*(time-15)/60*2)) + 240;
   to_x2 = (int)(100 * Math.cos(Math.PI*(count-15)/60*2)) + 130;
   to_y2 = (int)(100 * Math.sin(Math.PI*(count-15)/60*2)) + 350;


   nowTime = System.currentTimeMillis();
   times++;
   sleepTime = firstTime + (frameTime * times) - nowTime;
   if(sleepTime>0){
    try{
     thread.sleep(sleepTime);
    }catch (InterruptedException e){
    }
   }

   repaint();
  }
 }

 public void paint(Graphics g){
  bufferImage = createImage(640,480);
  bufG = bufferImage.getGraphics();
  bufG.setColor(Color.black);
  bufG.fillRect(0, 0, 640, 480);

  bufG.setColor(Color.white);
  bufG.drawLine(400,240,to_x,to_y);
  bufG.drawLine(130,350,to_x2,to_y2);
  bufG.setFont(new Font("Serif",Font.BOLD + Font.ITALIC,14));
  bufG.drawString("time = " + time,20,30);
  bufG.drawString("milli_second = " + 1000*count/60,20,60);

  g.drawImage(bufferImage, 0, 0, this);
 }

 public void update(Graphics g){
  paint(g);
 }
}

投稿日時 - 2012-08-17 00:39:51

ANo.4

ご呈示のプログラムは検証してないけど、threadでよくあるのが、知らないうちに2重3重にthreadが起動して同じカウンターを回して、2倍速、3倍速になるというやつ。
こ提示ソースだとwindow 行ったり来たりで、init() が再実行されるのかな??はて?
経過時間表示なら、preTimeにスタート時刻を保持し、これは変更せず, nowTimeとの時刻の差分から表示用の分針、秒針位置(time と count)を割り出せば、2倍速になっても、同じところに描くだけなので、針の回りが速くはならないはず。

投稿日時 - 2012-08-16 01:55:46

お礼

 回答ありがとうございます。
 原因を考える参考にさせていただきます。
 しかし実は何度か検証していて気がついたのですが、どうやら処理が速くなっているのではなく、むしろ正常になっているようです。もともとは処理落ちしているところに、別のソフトを開いた影響で処理速度が正常に修正されて、結果的には速く動いているように見えていたのだと思います。
 質問文に不備があり、すいませんでした。

投稿日時 - 2012-08-17 00:14:21

ANo.3

 すいません、#1です。

 質問を読み返してみたら、「速くなる」と書いてありますね。
 てっきり「遅くなる」かと思いこんで読んでました。

 摩訶不思議ですね。
 なんでなんでしょう?

 できるなら、改良版で、その現象がどうなるか、ご報告をお願いします。

投稿日時 - 2012-08-15 21:49:48

お礼

 いただいた改良版で問題が解決しました!ありがとうございます!
 ただ、ちょっと不思議な解決をしていましたので、報告をしておきます。
 とりあえずいただいたままで実行してみると、以前「速くなった」と言っていた速度で処理が行われるようになりました。この状態だと、別のソフトを起動しても処理速度はそのままでした。おそらくもともと、「速くなった」のではなく、普段が処理遅れしていて、別ソフト起動時が正常な処理速度だったのだと思います。
 で、しかしそのあと、おっしゃっていた通りにscheduleAtFixedRateをscheduleに変えて実行してみたのですが、なぜか症状が再発しました。相変わらず別ソフトを使用すると処理速度が変化します。
 原因ははっきりわかりませんが、どうやら別ソフトの影響で「前回処理からの経過時間」の計算が正常になるみたいです。

 なにはともあれ、作っていたゲームもscheduleAtFixedRateを利用して処理速度を一定に保てるようになりました。ありがとうございました。
 (症状の謎については、もうちょっと調べてみようかと思います。)

投稿日時 - 2012-08-16 00:54:34

ANo.2

 #1です。

 ああ、なるほど、それで60という数字が出てきたのですね。
 そういうことなら、私なら java.util.Timer クラスを使って↓みたいにします。

 最初プログラムを動かすときにタスクマネージャーか何かで、CPUの使用率を見てください。
 1コア分がフル回転していると思います。
 フル回転しているので、ハードウェアの性能や同時に動いている他のプログラムの影響で、速度が変わってしまいます。
 そもそもスレッドをフル回転させて、countを1ずつ足していっていますが、これが1msとは全く限りません。

 改良版を動かしてみて、CPU使用率を見てください。
 ほとんどCPUに負担をかけていないことがわかると思います。
 負担がかかっておらず余裕があるので、少しばかり他のプログラムの影響があったり、低性能ハードウェアでも、問題はありません。
 もちろん、高性能ハードウェアでも、速度が上がることもありません。

 java.util.Timer は、フレーム間の1000/60ms をsleepメソッドで時間をつぶしています。
 その間は、スレッドは休止状態なので、CPUに負担もかからず、フレームの速度も一定に保つことができるのです。

 あと、今回は、時計だと思ったので、TimerクラスのscheduleAtFixedRateメソッドを使いましたが、ゲームならscheduleメソッドを使うのが適切かと思います。

(コンパイルするときは全角スペースを半角スペースに変換してからにしてください)


import java.applet.Applet;
import java.awt.*;
import java.util.Timer;
import java.util.TimerTask;

public class Test extends Applet {

    Timer timer;
    Image bufferImage;
    int to_x, to_y, to_x2, to_y2;
    int count, time;
    long preTime, nowTime;
    final int fps = 60;
    final double frameTime = 1000 / fps;

    public void init() {
        to_x = to_y = to_x2 = to_y2 = 0;
        count = 0;
        time = 0;

        preTime = 0;
        nowTime = 0;

        bufferImage = createImage(640, 480);
        requestFocus();

        timer = new Timer();
        UpdateTask ut = new UpdateTask(1000L / 60L);
        timer.scheduleAtFixedRate(ut, ut.period, ut.period);
    }

    public void paint(Graphics g) {
        Graphics bufG = bufferImage.getGraphics();

        bufG.setColor(Color.black);
        bufG.fillRect(0, 0, 640, 480);

        bufG.setColor(Color.white);
        bufG.drawLine(400,240,to_x,to_y);
        bufG.drawLine(130,350,to_x2,to_y2);
        bufG.setFont(new Font("Serif", Font.BOLD + Font.ITALIC, 14));
        bufG.drawString("time = " + time, 20, 30);
        bufG.drawString("milli_second = " + 1000 * count / 60, 20, 60);
        bufG.dispose();
        
        g.drawImage(bufferImage, 0, 0, this);
    }

    public void update(Graphics g) {
        paint(g);
    }
    
    private class UpdateTask extends TimerTask {
        long period;
        
        UpdateTask(long period) {
            this.period = period;
        }
        
        @Override
        public void run() {
            count++;
            if (count >= 60) {
                count = 0;
                time++;
                if (time >= 60) {
                    time = 0;
                }
            }

            to_x = (int)(200 * Math.cos(Math.PI * (time - 15) / 60 * 2)) + 400;
            to_y = (int)(200 * Math.sin(Math.PI * (time - 15) / 60 * 2)) + 240;
            to_x2 = (int)(100 * Math.cos(Math.PI * (count - 15) / 60 * 2)) + 130;
            to_y2 = (int)(100 * Math.sin(Math.PI * (count - 15) / 60 * 2)) + 350;
            
            repaint();
        }
        
    }
}

投稿日時 - 2012-08-15 21:33:40

ANo.1

 こんにちは。

 ちょっと、補足をお願いしたいのですが、これは、

1.違う性能のハードウェアでも一定の動きをしてほしい、時計もしくはアニメのようなものを作っている
2.ハードウェアの性能に比例した動きをしてほしい、ベンチマークのようなものを作っている

 どちらなのでしょうか?
 たぶん、1.だと思うのですが、できているのは2.ですよね。

 仮に1.を目指しているのだとして、この短い針は秒針なのでしょうか?(つまり一周回って1分)
 その割にぶんぶん回ってますが。

 補足お願いします。

投稿日時 - 2012-08-15 08:06:55

補足

 回答ありがとうございます。
 おっしゃる通り、1です。
 実際に作っているゲームの方はシューティングゲームで、キー入力があったり敵との撃ち合いがあったりするので、環境によって性能差が出てしまうと(特に今回の問題は敵の動きが速くなってしまうので)ゲームの難易度が安定しないという事態に陥ると思い、原因を探っていました。
 あと、小さい方の針はミリ秒に対応して回っています。約1秒で1回転です(厳密には1秒×1/50くらい遅いのかな?)。
 プログラミングが分かる方であったら僕みたいな初心者が説明するのは失礼なのかもしれませんが、一応貼っておいたプログラムについて説明しておきます。
 まず、1秒間に60回画面を更新したかったので(作っているゲームがそうだからです)、1秒÷60を計算しました。実際のパソコンの処理では、時間は最小値がミリ秒で扱われているので、1000/60(プログラム文内では1000/fpsの事)を計算しています。で、パソコンのシステムで動いてる時計から、「前回の処理から何ミリ秒かかったか」を調べ出し、これが先ほどの1000/60を超えたところで1回、処理をしています。処理内容は「システム時間とは別個の独自のタイマーを進め、針をそれに合わせる」だけです。
 あくまで「どんな状況において処理が速くなるのか」を調べるためのプログラムだったので、細かいことは考えていませんでした。(1000/60って16.6666・・・だから、実際には毎処理17-16.6666・・・ずつ遅れてる気がする・・・)
 ともかく、どんな環境でも一定の動作をするのを目指して作っています。

投稿日時 - 2012-08-15 18:08:27

あなたにオススメの質問