2011年2月18日金曜日

魔王なんてたおしちゃうから!2ndリリースしました



この度「魔王なんてたおしちゃうから!2nd」をAndroidMarketにリリースしました。
開発期間が長かったのでリリース前は結構しんどかったです。

リリース後の微調整もだいぶ終りまして、やっとブログも記事を投稿できるぐらいの余裕ができてきました。

かなり作り込んだので面白いゲームに仕上がったと思います。是非お試しください!

魔王なんてたおしちゃうから!2nd

2010年12月5日日曜日

OpenFeintの不具合修正




地下鉄などでゲームを遊んでいるときに、ネットワークがつながらないことがよくあると思います。携帯電話のゲームでは、そういう突然電波が悪くなっても遊べるようにするためのエラー処理がとても大事です。
OpenFeintのバージョン1.0.1ではそのパターンに致命的な不具合が残っているらしく、「初回起動時に電波が通じないと、その後常に画像がすべて表示されなくなる」という現象があります。もしOpenFeintのJava版を使っていて、そういった現象に悩まされた場合com.openfeint.internal.ui.WebViewCache.javaを下記の様に修正すると治ります。


/* この関数をコメントアウトし、処理を置き換える。
private void copyDefaultItems() {
final File baseDir = appContext.getFilesDir();
// deleteAll(new File(baseDir, "webui")); //!!!!!!DELETE THIS!!!!!!
if(!(new File(baseDir, "webui").isDirectory())) {
Thread t = new Thread(new Runnable() {
public void run() {
copyDefaultBackground(baseDir);
}
});
t.start();
}
else {
clientManifest = getDefaultClientManifest();
}
}
*/
/**
* 電波の通じない場所で起動すると画像が出ないバグがあるため、常に
* 画像ファイルをコピーする。
*/
private void copyDefaultItems() {
final File baseDir = appContext.getFilesDir();
Thread t = new Thread(new Runnable() {
public void run() {
copyDefaultBackground(baseDir);
}
});
t.start();
}


たぶんwebuiディレクトリだけできてしまうと次から画像ファイルをコピーしないため、オリジナルの画像を修復できないのが致命的になっている原因だと思います。この修正ではWebViewで使用する画像ファイルのキャッシュを起動時に常に初期化します。
(もしかすると次のバージョンでは治っているかもしれません。)

2010年10月5日火曜日

Androidマーケットのライセンスチェック実装サンプル



いよいよ本格的にAndroid2.2(Floyo)が市場に流通しそうな時期になってきました。2.2から処理が速くなったり、Flashがサポートされたりと、いよいよAndroidが盛り上がりそうな予感です。

ただ、「SDカードへのアプリのコピー機能」はアプリ開発者からすると気になりますよね。Androidはアプリが比較的安かったり、ファミコンエミュレータが出回ったりと比較的無法地帯ですので、ライセンス管理が気になります。
そんななかAndroidMarketはライセンスチェックライブラリを正式に提供した模様です。

Google公式ドキュメント(英語)Licensing Your Applications

ただ、まだ日本語のドキュメントがないのが実状ですので、コピペで使いまわせる簡単な実装を公開することにしました。下記のソースコードをActivityから呼び出してください。


AndroidMarketLicenseCheck.java
---------------------------

package com.yourapp.license;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.provider.Settings.Secure;

import com.android.vending.licensing.LicenseChecker;
import com.android.vending.licensing.LicenseCheckerCallback;
import com.android.vending.licensing.ServerManagedPolicy;
import com.android.vending.licensing.AESObfuscator;

public class AndroidMarketLicenseCheck {
private static AndroidMarketLicenseCheck me = new AndroidMarketLicenseCheck();
public static AndroidMarketLicenseCheck getInstance(){
return me;
}

//ここの数字を何となく変えておく
private static final byte[] SALT = new byte[] {
-2, 8, 30, -12, -10, -57, 74, -64, 51, 88, -95,
-2, 98, -17, -36, -11, -11, 2, -64, 89
};
private static final String BASE64_PUBLIC_KEY
= "MIIBIjA....ここに公開鍵を書いておく";

private Activity activity;

private LicenseCheckerCallback mLicenseCheckerCallback;
private LicenseChecker mChecker;

public void onCreate(Activity activity){
this.activity = activity;
mLicenseCheckerCallback = new MyLicenseCheckerCallback();
String deviceId = Secure.getString(activity.getContentResolver(),
Secure.ANDROID_ID);

mChecker = new LicenseChecker(
activity,
new ServerManagedPolicy(
activity,
new AESObfuscator(SALT,
activity.getPackageName(),
deviceId)),
BASE64_PUBLIC_KEY
);

mChecker.checkAccess(mLicenseCheckerCallback);
}

public void onDestroy(){
mChecker.onDestroy();
}

class MyLicenseCheckerCallback implements LicenseCheckerCallback {
public void allow() {
if(false){
//debug
showLicenseErrorDialog("License OK.");
}
}

public void dontAllow() {
showLicenseErrorDialog(
"Not licensed or The network is not connected. "
+ "Please check Google CheckOut or Android Market.");
}

public void applicationError(ApplicationErrorCode errorCode) {
showLicenseErrorDialog("Android Market Server Error: " + errorCode.name());
}

private void showLicenseErrorDialog(String msg){
AlertDialog.Builder dialog = new AlertDialog.Builder(activity);
dialog.setTitle("Error: Android Market License");
dialog.setMessage(msg);
dialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
activity.finish();
}
});
dialog.show();
}
}
}

---------------------------
ここまで


これを呼び出すときは下記のようにActivityのonCreateとonDestroyから呼び出します。


public class HogeActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
AndroidMarketLicenseCheck.getInstance().onCreate(this);
}

@Override
protected void onDestroy(){
super.onDestroy();
AndroidMarketLicenseCheck.getInstance().onDestroy();
}


なおこのプログラムをコンパイルするには下記の操作が必要です

1. EclipseのAMDマネージャツールでAndroid用のライブラリを全て最新のものに置き換え下記が揃っていることを確認する。

- Market Licesing Package, revision 1 以上
- Google APIs by Google Inc, Android API 8 revision 2 以上

2. Market Licesing PackageのソースコードをEclipseプロジェクトにインポートしてしまう。Linuxの場合、例えば下記にあるソースを一緒にコンパイルする。

android-sdk-linux_86/market_licensing/library/src


3. AndroidManifest.xmlにライセンスチェックをしますよと記述しておく

<uses-permission android:name="com.android.vending.CHECK_LICENSE" />


4. デベロッパーコンソールのプロフィール変更画面から公開鍵をコピーして、プログラムに記述する(画面下の方にあります)


プロフィールの編集 URL(http://market.android.com/publish/editProfile)


5. プログラムも準備できたらテストします。デベロッパーコンソールのプロフィール変更画面の下の方に、「テスト応答」という項目があります。ここを変更すると、エラーが返ったり認証OKが返ったりとテストできます。ちなみにテストで使用するアカウントはAndroidMarketに開発者アカウントとして登録したgmailアカウントがデフォルトで使えるので、特に設定する必要はありません。

2010年9月2日木曜日

XMLのシリアライズ




「魔王なんて!」にネットワーク機能を付けようと思い必要な機能を洗い出したのですが、ログインや友達リスト等ゲームの処理に入る前にユーザー管理周りでも通信種類って結構多いのですよね。(メッセージ送信とかいろいろありますよね。)

相変わらず独りで実装しているので通信種類が増えすぎるとプログラム量がどんどん増えて管理しきれなくなるのですよね。個人でプログラムを作る場合はプログラムや処理種類を減らす工夫も大事なので、まずオブジェクトのシリアライズを検討する事にしました。これはサーバーとクライアントで同じクラスを使いたいためです。Android以外の端末にも将来展開したいので、バイナリデータで通信せずXMLが良いかなと思います。

そこで何か使えるライブラリを調べてみたのですが、どうも定義ファイルが必要なものが多いのですよね。で、実装がシンプルなものも少ないですし、ドキュメントは英語ですし。

なんだか英語のドキュメント読んでいる時間で実装できそうな気がしたので実装してみました。

XMLReaderがデシリアライズ。XMLWriterがシリアライズを行います。
key, valueで表しつつ、内部クラスも表現できる最小セットの実装かなとおもいます。


package com.cosmicdragon.darkgreen.xml;

public interface XMLDefine {

public static final String ELEMENT_ROOT = "d";
public static final String ELEMENT_ROW = "r";
public static final String ELEMENT_CLASS = "c";

public static final String ATTR_NAME = "n";
public static final String ATTR_VERSION = "v";
public static final String ATTR_KEY = "k";
public static final String ATTR_TYPE = "t";

public static final String TYPE_INT = "i";
public static final String TYPE_BOOL = "b";
public static final String TYPE_LONG = "l";
public static final String TYPE_STRING = "s";
public static final String TYPE_DATE = "d";

}




package com.cosmicdragon.darkgreen.xml;

import java.util.Date;

public class XMLReadListener {
public void readStart(String version){}
public void readEnd(){}
public void readClassStart(String name){}
public void readClassEnd(){}
public void readRowInt(String key, int value){}
public void readRowLong(String key, long value){}
public void readRowBoolean(String key, boolean value){}
public void readRowString(String key, String value){}
public void readRowDate(String key, Date value){}
}



package com.cosmicdragon.darkgreen.xml;

import java.io.IOException;
import java.util.Date;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

public class XMLReader implements XMLDefine {
private SAXParser parser = null;
private Handler hdl = new Handler();

public void readXML(XMLReadListener listener, InputSource is)
throws ParserConfigurationException, SAXParseException, SAXException, IOException{
hdl.reset(listener);

if(parser == null){
parser = SAXParserFactory.newInstance().newSAXParser();
}

parser.parse(is, hdl);
if(hdl.getException() != null)throw hdl.getException();

hdl.reset(null);
}
}

class Handler extends DefaultHandler {
private SAXParseException exception;
private XMLReadListener listener;

private boolean rowFlag = false;
private String rowType = "";
private String rowKey = "";
private String rowValue= "";

public void reset(XMLReadListener listener){
this.listener = listener;
this.exception = null;
}

public SAXParseException getException(){
return this.exception;
}

public void startElement(String uri, String localName, String qName, Attributes atts)
throws SAXException {
rowFlag = false;
if(qName.equals(XMLDefine.ELEMENT_ROOT)){
listener.readStart(atts.getValue(XMLDefine.ATTR_VERSION));
}else if(qName.equals(XMLDefine.ELEMENT_CLASS)){
listener.readClassStart(atts.getValue(XMLDefine.ATTR_NAME));
}else if(qName.equals(XMLDefine.ELEMENT_ROW)){
rowFlag = true;
rowValue = "";
rowType = atts.getValue(XMLDefine.ATTR_TYPE);
rowKey = atts.getValue(XMLDefine.ATTR_KEY);
System.out.println("startElement() : " + rowType + " : " + rowKey);
}
}

public void endElement(String uri, String localName, String qName)
throws SAXException {
if(qName == XMLDefine.ELEMENT_ROOT){
listener.readEnd();
}else if(qName == XMLDefine.ELEMENT_CLASS){
listener.readClassEnd();
}else if(qName == XMLDefine.ELEMENT_ROW){
if(rowType.equals(XMLDefine.TYPE_INT))
listener.readRowInt(rowKey, Integer.parseInt(rowValue));
else if (rowType.equals(XMLDefine.TYPE_LONG))
listener.readRowLong(rowKey, Long.parseLong(rowValue));
else if (rowType.equals(XMLDefine.TYPE_BOOL))
listener.readRowBoolean(rowKey, Boolean.parseBoolean(rowValue));
else if (rowType.equals(XMLDefine.TYPE_STRING))
listener.readRowString(rowKey, rowValue);
else if (rowType.equals(XMLDefine.TYPE_DATE))
listener.readRowDate(rowKey, new Date(Long.parseLong(rowValue)));
}
}

public void characters(char[] ch, int start, int length)
throws SAXException {
if(rowFlag)
rowValue += String.copyValueOf(ch, start, length);
}

public void error(SAXParseException e) {
exception = e;
}

public void fatalError(SAXParseException e) {
exception = e;
}

}




package com.cosmicdragon.darkgreen.xml;

import java.io.PrintStream;
import java.util.Date;

public class XMLWriter implements XMLDefine {

private int tabNum;

//---------------------------------------- write APIs.
public final void writeStartDocument(PrintStream out){
writeStartDocument(out, "UTF-8", "1.0");
}

public final void writeStartDocument(PrintStream out, String encode, String version){
tabNum = 0;
out.print("<?xml version=\"1.0\" encoding=\""
+ encode +"\"?>");
out.print("\n");
out.print("<" + ELEMENT_ROOT
+ " " + ATTR_VERSION + "=\"" + version + "\""
+ ">");
out.print("\n");
tabNum = 1;
}

public final void writeEndDocument(PrintStream out){
out.print("</" + ELEMENT_ROOT + ">");
}

public final void writeStartClass(PrintStream out, String name){
writeTab(out);
out.print("<" + ELEMENT_CLASS
+ " " + ATTR_NAME + "=\"" + name + "\""
+ ">");
out.print("\n");
tabNum++;
}

public final void writeEndClass(PrintStream out){
tabNum--;
writeTab(out);
out.print("</" + ELEMENT_CLASS + ">");
out.print("\n");
}

public final void writeInt(PrintStream out, String key, int v) {
writeRowStart(out, key, TYPE_INT);
out.print(v);
writeRowEnd(out);
}

public final void writeBoolean(PrintStream out, String key, boolean v){
writeRowStart(out, key, TYPE_BOOL);
out.print(v);
writeRowEnd(out);
}

public final void writeLong(PrintStream out, String key, long v){
writeRowStart(out, key, TYPE_LONG);
out.print(v);
writeRowEnd(out);
}

public final void writeString(PrintStream out, String key, String v){
writeRowStart(out, key, TYPE_STRING);
out.print(v);
writeRowEnd(out);
}

public final void writeDate(PrintStream out, String key, Date v){
writeRowStart(out, key, TYPE_DATE);
out.print(v.getTime());
writeRowEnd(out);
}

private final void writeRowStart(PrintStream out, String key, String type){
writeTab(out);
out.print("<" + ELEMENT_ROW
+ " " + ATTR_KEY + "=\"" + key + "\""
+ " " + ATTR_TYPE + "=\"" + type + "\""
+ ">");
}

private final void writeRowEnd(PrintStream out){
out.print("</" + ELEMENT_ROW + ">");
out.print("\n");
}

private final void writeTab(PrintStream out){
for(int i=0; i<this.tabNum; i++) out.print('\t');
}
}




テストプログラムを簡単ですが書いてみました。PersonクラスをいったんXMLに出力して、XML文字列からクラスを復元しています。


import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.PrintStream;

import java.util.Date;

import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import junit.framework.TestCase;

import com.cosmicdragon.darkgreen.xml.XMLReader;
import com.cosmicdragon.darkgreen.xml.XMLWriter;
import com.cosmicdragon.darkgreen.xml.XMLReadListener;

public class Test01 extends TestCase {

public void testA(){
ByteArrayOutputStream buff = new ByteArrayOutputStream();
PrintStream print = new PrintStream(buff);
PersonSerializer serial = new PersonSerializer();

Person p1 = new Person("hero", 18, true);

try{Thread.sleep(2000);}catch(Exception e){}

Person p2 = new Person("lynn", 17, false);


System.out.println("p1: " + p1);
System.out.println("p2: " + p2);

serial.writeXML(print, p1);
System.out.println(buff.toString());

try{
serial.readXML(buff.toByteArray(), p2);
}catch(Exception e){
e.printStackTrace();
}

System.out.println("p1: " + p1);
System.out.println("p2: " + p2);

super.assertEquals(p1.age, p2.age);
super.assertEquals(p1.name, p2.name);
super.assertEquals(p1.flag, p2.flag);
super.assertEquals(p1.date.getTime(), p2.date.getTime());
}
}

class Person extends XMLWriter {
int age;
String name;
boolean flag;
Date date;

public Person(String name, int age, boolean flag){
this.name = name;
this.age = age;
this.flag = flag;
this.date = new Date();
}

public String toString(){
return "Person : "
+ "age:" + age
+ ":name:" + name
+ ":flag:" + flag
+ ":date:" + date
;
}
}

class PersonSerializer extends XMLReadListener {
Person p;

public void writeXML(PrintStream out, Person p){
XMLWriter writer = new XMLWriter();
writer.writeStartDocument(out);
writer.writeStartClass(out, "Person");
writer.writeString(out, "name", p.name);
writer.writeInt(out, "age", p.age);
writer.writeBoolean(out, "flag", p.flag);
writer.writeDate(out, "date", p.date);
writer.writeEndClass(out);
writer.writeEndDocument(out);
}

public void readXML(byte[] in, Person p)
throws ParserConfigurationException, SAXException, IOException{
this.p = p;
InputSource src = new InputSource(new ByteArrayInputStream(in));
XMLReader reader = new XMLReader();
reader.readXML(this, src);
}

public void readRowInt(String key, int value){
p.age = value;
}

public void readRowBoolean(String key, boolean value){
p.flag = value;
}

public void readRowString(String key, String value){
p.name = value;
}

public void readRowDate(String key, Date value){
p.date = value;
}
}


下記は実行結果です。p1をシリアライズしてp2にデシリアライズをしています。


p1: Person : age:18:name:hero:flag:true:date:Thu Sep 02 01:15:23 JST 2010
p2: Person : age:17:name:lynn:flag:false:date:Thu Sep 02 01:15:25 JST 2010
<?xml version="1.0" encoding="UTF-8"?>
<d v="1.0">
<c n="Person">
<r k="name" t="s">hero</r>
<r k="age" t="i">18</r>
<r k="flag" t="b">true</r>
<r k="date" t="d">1283357723441</r>
</c>
</d>
p1: Person : age:18:name:hero:flag:true:date:Thu Sep 02 01:15:23 JST 2010
p2: Person : age:18:name:hero:flag:true:date:Thu Sep 02 01:15:23 JST 2010

2010年7月11日日曜日

面白いゲームについての考察 1



ゲーム開発は設計云々より、想像力と根性と感情移入が大事なのかなと感じました。これはいろいろ意見が分かれそうではありますが。

数ヶ月前にシステムに感情移入するなみたいなことをTwitterに書いたんですが、ゲーム開発は真逆ですね。「問題なく操作できる」ではなく「面白くて手放せない」というものを作らないといけない訳ですし。普通のシステム開発とは違う貴重な体験でした。

「ゲーム開発とは」みたいな書籍をずっと読みあさっていた時期があるのですが、ゲームの面白さは思いつく限りあげると「パズル」「スリル」「感情移入」「コレクション」「ソーシャル」に分類されるのかなと思えて仕方が無いのです。

『「おもしろい」のゲームデザイン』 http://j.mp/96fiJk 
と言う本があるのですが、一番印象に残っているのは「新しいパターンを学習するときに脳が活性化して面白いと感じる」というような内容です。これはまさにパズル的な面白さだと思います。

初めてグアムに行った時、空港についただけで楽しくなりますが、次の年に再びいっても、単なる移動作業みたいな感覚にとらわれます。こういう感覚にとらわれた事がある人は大勢というかほとんどの人がそう感じると思うのです。

いかに複雑なパターンをプレイヤーに提供し続けるかは、ゲーム開発の鉄則だと個人的に思います。さらにこの「新しいパターンで脳活性」の理屈で究極のAI、NPCとは何かということも妄想するのですが、結局それは人間だと思うのですよね。つまりオンライン対戦です。

対戦ゲームは相手がいろいろ学習して、予想できない動きをしてくる訳でして、人間以上に複雑な動きが出来る NPCはちょっと思いつきませんし。

次に「スリル」なのですが、安全欲求とか基本的な感情をいかにゲームで刺激しようか模索してまして、夜中に大学の心理学の教科書を引っ張りだして、内容を確認しながら調整していました。今思うとよくこんな事一人でやってたなと思うのですが。

極端に強い「中ボス」をランダム出現させる機能を実装した事があってこれはプレイヤーを理不尽に倒すためです。語弊があるかもしれませんが、安全な環境でヌクヌク過ごすよりも突然変異種に急に追いつめられるスリルが大事かなと思いました。

あと「感情移入」なのですがこれはミラーニューロンが関係していると思うのです。僕は茂木健一郎さんの『脳と仮想』という本に感銘をうけて一時期読みあさっていたのですが、ある書籍でミラーニューロンの記述がありました。

専門でないのでざっくりと書くのですが「他人がしている行動を自分の脳の中で同じ事をしたようにシュミレートする」ような機能が脳にはあるようです。逆にこの機能があるから「鏡に映った自分が他人ではなく自分と認識できる」ような記述があったように思えます。

諸説あると思うのですが、シナリオに感情移入する本質的な原理は「ミラーニューロンが働きすぎて錯覚を起こしている状態」と個人的には思えるのです。

いかに錯覚を起こさせるかは、まずゲームの世界に引きずり込むような導線が必要だと思うのですよね。これはゲームニクス http://j.mp/duUlDC という本がとても参考になりました。とにかく引きづり込むことをしないと次が始まらないですし。

次にキャラに感情移入させるにはどうするかなのですが、これは2chの萌え板を見た方が早そうです。ただ「ツンデレ」はアメリカの心理学の研究で有効だとデータがあるそうです。ゲイン・ロス効果というらしいです。

魅力的なキャラクタはつまり現実世界で他人に魅力を感じる心理だったりするので、広範囲に及ぶので割愛しますが、もう一つ別角度から考えると「どう記憶に定着させるか」も大事だと思えます。

キャラに感情移入が出来て長期記憶に定着させる事に成功したらたぶん開発者としては成功なのですよね。一番分かりやすい手法は強烈な事件を起こす事ですよね。いろいろな映画でもよくある方法ですし。これ以上書くとネタバレになるのでゲームが発売中は書けませんが。

次に「コレクション」「ソーシャル」は、これから実装しようと考えているので、まだ調査不足で深い知識も無くかけません。分野的には社会心理学が関係しているかとおもって大学で受講したのですが、多岐にわたるので要点をまとめづらいですね。

あれこれ考えていた事を吐き出してスッキリしました。移植作業に戻ります。ほんとに独り言です。

2010年7月8日木曜日

小技 無料版と有料版アプリのプロジェクトを効率的に管理

有料版のAndroidアプリを作った場合、有料アプリとあわせてお試し版も用意すると思います。ただ、Androidマーケットは同じjavaのパッケージでアプリをアップできません。また、EclipseでAndroidアプリを作るときはプロジェクト毎に一つのActivityを用意すると思います。しかも、そのActivityのパッケージ位置にRクラスが自動生成されます。この制約があると、アプリでRクラスを参照しているとimport文がプロジェクト毎にべた書きになってしまいます。ちなみにRクラスはfinalクラスとして生成されるので、継承で他のクラスに引き継ぐ事も出来ません。

細かい話なのでまったく伝わらないような気もするのですが。。

こういった事情をあまり考慮せずに進めると、有料版のソースコード等をまるまる無料版のプロジェクトにコピーして、しばらく二重管理が続くような事になります。

この問題に関して2,3日試行錯誤をしたところ、下記の方法で解決しました。

1. Rクラスをコピーして別のパッケージに移動させる(自動生成されたRクラスは直接使わない)

2. 有料版のソースフォルダをsrcとsrc_gameの二つに分け、srcの方にActivityだけ置く。

3. 無料版は有料版のsrc_gameを参照させる([Project]-> [Properties] -> [Java Build Path] -> [Source]で追加できる)

簡単にいうと、Activity以外は極力プロジェクト間で共有させれば良いという事です。
たぶん、この小技は細かすぎて伝わらなさそうですが、もし有料版アプリを作るときがあればこういう話があったなぁと一つの解決法として捉えてくれればと思います。

小技 Eclipse pluginの入れ過ぎで落ちる現象はインストールし直すべき

Eclipseでプラグインを入れすぎるとメモリが足りなくなり落ちるようになります。いくつかのサイトでeclipse.iniに下記の行を追加すると直ると書いてあったりしますが、マシンスペックの問題かもしれませんが私の環境では完全に直す事は出来ませんでした。


-XX:MaxPermSize=512m


スペックの低いノートPCや32bit版のEclipseだと限界があるでしょうね。

2、3日試行錯誤した結果、必要最低限のプラグインに留めるしか完全に直す事は出来ませんでした。
また、大抵この現象が起きたときはプラグインを削除しようにも入れ過ぎのためどれを削除して良いか分からないことになっていると思います。
使う機会のない大量のプラグインに悩まされるのは時間がもったいないのでEclipseをインストールし直すことをお勧めします。これが一番早いです。