3日坊主で終わらないためのアプリ開発日記 9日目

吾輩はやれば出来る子である。
    ∩∩
   (´・ω・)
   _| ⊃/(___
 / └-(____/
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

  • 本日の作業

ロジックパネルの個数化 2時間目(全5時間)

前回、プレーヤーの所持データとして必要なものを

1.どのロジックパネルを何個持っているか

2.獲得累計経験値

としてまとめたので、これを保存/読込するUserDataクラスを作成する。

ロジックパネルのデータは、

種別(“ACTION” もしくは “JUDGE”)

種類(“歩く”や”左を向く”や”前方が壁なら”など)

個数

の3つを1セットとし、この3つの数値をメンバとしてもつLogicPanelUnitクラスを作成する。

UserDataクラスには、このLogicPanelUnitクラスを配列で可変個と、獲得累計経験値を持たせ、saveとloadメソッドを実装する。

誰かの役に立つかはわかりませんが、コードを貼り付けます。

尚、swiftは最適化が進んでいないのかコンパイル/実行速度共に遅いので使ってません。

UserDataクラスにはレベルも持たせていますが、獲得累計経験値から逆算できるのでファイル保存はしていません。

———— LogicPanelUnit.h ————

#ifndef EggOfEvolution_LogicPanelUnit_h
#define EggOfEvolution_LogicPanelUnit_h

@interface LogicPanelUnit : NSObject <NSCoding> {
int _kind;
int _data;
int _num;
}

@property (nonatomic) int kind;
@property (nonatomic) int data;
@property (nonatomic) int num;

@end

#endif /* LogicPanelUnit_h */

———— LogicPanelUnit.m ————

#import <Foundation/Foundation.h>
#import “LogicPanelUnit.h”

@implementation LogicPanelUnit

@synthesize kind = _kind;
@synthesize data = _data;
@synthesize num = _num;

-(id)init {
if (self = [super init]) {
_kind = 0;
_data = 0;
_num = 0;
}
return self;
}

-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeInteger:_kind forKey:@”encKind”];
[aCoder encodeInteger:_data forKey:@”encData”];
[aCoder encodeInteger:_num forKey:@”encNum”];
}

-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];

_kind = (int)[aDecoder decodeIntegerForKey:@”encKind”];
_data = (int)[aDecoder decodeIntegerForKey:@”encData”];
_num = (int)[aDecoder decodeIntegerForKey:@”encNum”];

return self;
}

@end

———— UserData.h ————

#ifndef EggOfEvolution_UserData_h
#define EggOfEvolution_UserData_h

@interface UserData : NSObject {
int _lv;
long _exp;
NSMutableArray *_lpa;
}

@property (nonatomic, readonly) int lv;
@property (nonatomic, readonly) long exp;

-(BOOL)addExp:(int)exp;
-(BOOL)delExp:(int)exp;
-(BOOL)addLogicPanel:(int)kind data:(int)data num:(int)num;

-(void)dump;

-(BOOL)save;
-(BOOL)load;

@end

#endif /* UserData_h */

———— UserData.m ————

#import <Foundation/Foundation.h>
#import “UserData.h”
#import “LogicPanelUnit.h”

#define LOGIC_PANEL_MAX_NUM 99
#define MAX_EXP_NUM 38085

#define K_EXP @”kExp”
#define K_LOGICPANEL @”kLogicPanel”
#define K_VERSION @”kVersion”

#define FILENAME_USERDATA @”eoe.dat”

@implementation UserData

@synthesize lv = _lv;
@synthesize exp = _exp;

-(id)init {
if (self = [super init]) {
_lv = 1;
_exp = 0;
}
return self;
}

-(BOOL)addExp:(int)exp {
BOOL bRet = YES;
_exp += exp;
if (MAX_EXP_NUM < _exp) {
_exp = MAX_EXP_NUM;
bRet = NO;
}
[self recalcLv];
return bRet;
}

-(BOOL)delExp:(int)exp {
BOOL bRet = YES;
_exp -= exp;
if (_exp < 0) {
_exp = 0;
bRet = NO;
}
[self recalcLv];
return bRet;
}

-(BOOL)recalcLv {
int preLv = _lv;
int lv = 0;
long exp = _exp;
while (0 < exp) {
lv++;
exp -= [self expAtLv:lv];
}
_lv = lv;
return preLv != _lv;
}

-(long)expAtLv:(int)lv {
if (1 <= lv && lv <= 10) {
return (10 + lv);
} else if (11 <= lv && lv <= 30) {
return (20 + (lv – 10) * 5);
} else if (31 <= lv && lv <= 90) {
return (120 + (lv – 30) * 10);
} else if (91 <= lv && lv <= 99) {
return (720 + (lv – 90) * 100);
}
return 0;
}

-(BOOL)addLogicPanel:(int)kind data:(int)data num:(int)num {
BOOL bRet = NO;

if (nil == _lpa) {
_lpa = [[NSMutableArray alloc] init];
}

for (LogicPanelUnit *unit in _lpa) {
if (unit.kind == kind && unit.data == data) {
unit.num += num;
if (LOGIC_PANEL_MAX_NUM < unit.num) {
unit.num = LOGIC_PANEL_MAX_NUM;
bRet = NO;
}
return bRet;
}
}

LogicPanelUnit *newUnit = [[LogicPanelUnit alloc] init];
newUnit.kind = kind;
newUnit.data = data;
newUnit.num = num;
if (LOGIC_PANEL_MAX_NUM < newUnit.num) {
newUnit.num = LOGIC_PANEL_MAX_NUM;
bRet = NO;
}
[_lpa addObject:newUnit];

return bRet;
}

-(BOOL)save {

NSMutableDictionary *dicSettings = [[NSMutableDictionary alloc]init];

NSString *dir = [NSHomeDirectory() stringByAppendingPathComponent:@”Documents”];
NSString *path = [dir stringByAppendingPathComponent:FILENAME_USERDATA];

[dicSettings setObject:@”1.0.0″ forKey:K_VERSION];
[dicSettings setObject:[NSNumber numberWithLong:_exp] forKey:K_EXP];
if (nil != _lpa) {
[dicSettings setObject:_lpa forKey:K_LOGICPANEL];
}

if ([NSKeyedArchiver archiveRootObject:dicSettings toFile:path]) {
return YES;
}
return NO;
}

-(BOOL)load {
NSString *dir = [NSHomeDirectory() stringByAppendingPathComponent:@”Documents”];
NSString *path = [dir stringByAppendingPathComponent:FILENAME_USERDATA];

NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:path]) {

NSMutableDictionary *dicSettings = (NSMutableDictionary*)[NSKeyedUnarchiver unarchiveObjectWithFile:path];

_exp = [[dicSettings objectForKey:K_EXP] longValue];
[self recalcLv];

_lpa = [dicSettings objectForKey:K_LOGICPANEL];

} else {
return NO;
}
return YES;
}

-(void)dump {
NSLog(@”LV=[%d]”, _lv);
NSLog(@”EXP=[%ld]”, _exp);
for (LogicPanelUnit *unit in _lpa) {
NSLog(@”LOGICPANEL=[%d,%d,%d]”, unit.kind, unit.data, unit.num);
}
}

@end

  • 明日の作業

ロジックパネルの個数化 3時間目(全5時間)

3日坊主で終わらないためのアプリ開発日記 8日目

吾輩はやれば出来る子である。
    ∩∩
   (´・ω・)
   _| ⊃/(___
 / └-(____/
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

  • 本日の作業

ロジックパネルの個数化 1時間目(全5時間)

ロジックパネルの個数化作業ということで、とりあえず初回はコードを書かず、作業内容を文書でまとめる。

ロジックパネルは現在、ロジックボードへ無制限に置くことができる。これを所持数以上は使えないようにする。やはりゲームの要素としてアイテムを集めるというのは楽しい作業だと思う。

プレーヤーの所持データとして各種ロジックパネルとその個数を保存する。1種類のロジックパネルは最大99個まで持てるものとする。
昨日のレベルの概念で決定した経験値も一緒に保存する。保存するデータは以下となる。

1.どのロジックパネルを何個持っているか

2.獲得累計経験値

保存データは可変長となる。

将来的に、対プレーヤー戦も見据えて保存データは暗号したり、サーバー上に置くことも考える。

保存処理を担当するクラスを1つ作成し、MainViewクラスに所持させることとする。

大枠の構成として、現在はGameViewControllerクラスがMainViewクラスやGameSceneクラスを生成して制御している。

MainViewクラスはGLKViewを継承しており、OpenGLによりマップやキャラを描画するクラスである。描画パフォーマンスは高いが、制御が大変で作成に時間がかかる。

GameSceneクラスはSKSceneを継承した2Dグラフィックを扱うクラスで、主にジャギーのない綺麗なUIを描画するクラスである。描画パフォーマンスは低いが、簡単なコードで素早く作成することができる。

場面に応じて、適材適所でMainViewとGameSceneを切り替えて制作を行う。

  • 明日の作業

ロジックパネルの個数化 2時間目(全5時間)

3日坊主で終わらないためのアプリ開発日記 7日目

奇跡が起きた。
1週間アプリ開発続いた。
吾輩はやれば出来る子である。
    ∩∩
   (´・ω・)
   _| ⊃/(___
 / └-(____/
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

  • 本日の作業

レベルの概念をどうするか考える。

1.レベルアップでHPが増えるものとする。

2.レベルアップである程度ゴリ押しでクリアできるようになるが、基本的にはちゃんとロジックを組まないとクリアできない(対プレーヤー戦では勝てない)ようにする。

3.レベルは1〜最大99までとし、レベルごとのHPは以下の通りとする。

レベル1〜90のHPは4+レベル
(レベル1は5、レベル2は6、… レベル90は94)
レベル91〜92のHPは95
レベル93〜94のHPは96
レベル95〜96のHPは97
レベル97〜98のHPは98
レベル99のHPは99

4.レベルアップに必要な経験値は以下の通りとする。

レベル1〜10は10+レベル
(レベル1は11、レベル2は12、… レベル10は20)
レベル11〜30は20+(レベル-10)*5
(レベル11は25、…レベル30は120)
レベル31〜90は120+(レベル-30)*10
(レベル31は130、…レベル90は720)
レベル90〜99は720+(レベル-90)*100
(レベル91は820、…レベル99は1620)

レベル99になるのに必要なトータル経験値は38085

5.通常ステージでは敵を倒すことで、敵が所有している経験値の3%(小数切り上げ)を得る。敵を倒す定義は、攻撃により敵のHPを最大HPの1/3以上減らすこととする。

レベル1のキャラが仮にレベル99のキャラを倒した場合、貰える経験値は、38085 * 0.03 = 1143(切り上げ)で、一気にレベル28になる。

6.”競技場”で相手プレーヤーに勝ったら、相手プレーヤーが所有している経験値の3%(小数切り上げ)を得る。競技場で負けたプレーヤーは、経験値の3%を失う。

7.ロジックパネル”コピー”により複製したキャラは、”コピー”を実行したキャラのレベルを引き継ぐものとする。

  • 明日の作業

ロジックパネルの個数化 1時間目(全5時間)

3日坊主で終わらないためのアプリ開発日記 6日目

吾輩はやれば出来る子である。
    ∩∩
   (´・ω・)
   _| ⊃/(___
 / └-(____/
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

  • 本日の作業

HP0でのゲームオーバー演出 3時間目(全3時間)

“味方キャラ”が全員死んだら「LOGIC FAILED…」と表示して画面を暗くする。

画面を暗くした際に「LOGIC FAILED…」と表示するだけだと何もできないので、「Retry?」と続け、「YES」と「NO」の選択肢を表示するようにした。(既存のYES/NO選択画面の使い回し)

「YES」を選択すると、ステージの初期状態に戻り、「NO」を選択すると、まだ未作成だがタイトル画面に戻る想定。

  • 明日の作業

レベルの概念をどうするか考える。

3日坊主で終わらないためのアプリ開発日記 5日目

吾輩はやれば出来る子である。
    ∩∩
   (´・ω・)
   _| ⊃/(___
 / └-(____/
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

  • 本日の作業

HP0でのゲームオーバー演出 2時間目(全3時間)

HP0でいきなりキャラが消えるのは寂しいので、一定時間点滅してから消えるようにしました。

  • 明日の作業

“味方キャラ”が全員死んだら「LOGIC FAILED…」と表示して画面を暗くする。

3日坊主で終わらないためのアプリ開発日記 4日目

吾輩はやれば出来る子である。
    ∩∩
   (´・ω・)
   _| ⊃/(___
 / └-(____/
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

  • 本日の作業

HP0でのゲームオーバー演出 1時間目(全3時間)

HP0でキャラクター管理クラス(Charas)から該当キャラ(CharaUnit)を削除するようにしました。

  • 明日の予定

HP0でのゲームオーバー演出 2時間目(全3時間)

HP0でいきなりキャラが消えるのは寂しいので、一定時間点滅してから消えるようにします。

3日坊主で終わらないためのアプリ開発日記 3日目

吾輩はやれば出来る子である。
    ∩∩
   (´・ω・)
   _| ⊃/(___
 / └-(____/
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

  • 本日の作業

残り作業と予定工数の洗い出し

やる気の低下を防ぐため、なるべく作業を細かく分ける。かつ時間に余裕を持たせる。

とりあえず面クリ型の単純なアプリとして完成させる。

1日の作業時間は1時間で良い。ただし毎日作業する事とする。
以下作業項目と工数を記述する。

5/16現在、面クリ型の単純なアプリとして完成しました!
23まで完了!現在、追加作業として24を実施中!

  1. HP0でのゲームオーバー演出 3時間
  2. レベルの概念どうするか考える 1時間
  3. ロジックパネルの個数化 5時間
  4. ロジックパネルの取得方法どうするか考える 2時間
  5. ステージクリア演出 3パターン 5時間
  6. タイトル画面の実装 7時間(※1/22追加 ※2時間オーバー)
  7. クリア条件「特定時間生存」の追加 2時間(※1/30追加)
  8. マップサイズの可変化 1時間(※1/30追加)
  9. 1〜5ステージの作成 10時間
  10. 6〜10ステージの作成 10時間
  11. 11〜15ステージの作成 10時間
  12. 16〜20ステージの作成 10時間
  13. 21〜25ステージの作成 10時間
  14. 26〜30ステージの作成 10時間
  15. 31〜35ステージの作成 10時間(※1/30追加)(※3/2削除)
  16. 36〜40ステージの作成 10時間(※1/30追加)(※3/2削除)
  17. 41〜45ステージの作成 10時間(※1/30追加)(※3/2削除)
  18. 46〜50ステージの作成 10時間(※1/30追加)(※3/2削除)
  19. ログのローカライズ(英語) 5時間(※2/17追加)
  20. サウンドの追加 10時間(※2/17追加)
  21. 壁判定Judgeの実装 3時間(※1/30削除 各ステージ作成工数に含める)
  22. 水場(HP回復)の実装 3時間(※1/30削除 各ステージ作成工数に含める)
  23. アイテム取得演出 3時間(※5/16追加)
  24. 対人戦の実装 20時間(※5/16追加)

他の作業項目が発生した場合、逐次追加していく。

  • 明日の予定

HP0でのゲームオーバー演出 1時間目(全3時間)

3日坊主で終わらないためのアプリ開発日記 2日目

吾輩はやれば出来る子である。
    ∩∩
   (´・ω・)
   _| ⊃/(___
 / └-(____/
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

  • 本日の作業

作りかけのアプリ「進化の卵(仮)」の簡単な説明や動画をアップする。

アプリ概要

“ロジックボード” 内のパネルに “ロジックパネル” を配置してキャラの行動を定義する。ロジックボードは、六角形のパネルが並んだいわゆる蜂の巣構造で出来ており、個々のパネルは互いに6か所で隣のパネルと辺を共有している。ステージは2Dマップで、ロジックを定義したキャラはその中で行動する。ステージクリアの種類は

1.特定位置に移動
2.敵を全滅させる
3.個体数を特定数以上にする

とする。

キャラはステータスとしてHPを持つものとする。HPは、壁にぶつかったり敵から攻撃を受けると減るものとする。

ロジックパネルは、大きく分けて “Action” と “Judge” がある。

Actionは

歩く
後退
左旋回
右旋回
コピー(自分のロジックを有した個体を複製する。ただしHP半減)

などである。

Judgeは、特定の条件を記述するパネルで、例えば

前に壁があれば
左に敵がいれば
後方に味方がいれば

などの条件で処理を分岐できる。

2Dマップ内には、

平地

水場(HP回復)

などが存在する。

特定のステージ環境においては、そのステージ内に存在する個体のロジックパネルを低確率でランダムに変化させる。その結果、自然界における淘汰の仕組みを再現する。

将来的には、”飼育場”などで淘汰の仕組みを利用してロジックを自動で最適化し、”競技場”で、他のプレーヤーと個体数を競う環境を用意する。minecraftのような3Dマップも案として有。

 

  • 明日の予定

残り作業と予定工数の洗い出し。

3日坊主で終わらないためのアプリ開発日記 1日目

吾輩はやれば出来る子である。
    ∩∩
   (´・ω・)
   _| ⊃/(___
 / └-(____/
  ̄ ̄ ̄ ̄ ̄ ̄ ̄

  • 3日坊主で終わらないためのアプリ開発日記の目的

今作っているアプリ「進化の卵(仮)」を完成させるために毎日何らかの作業をし、その作業内容を本サポートサイトで報告する。

作りかけのアプリを半年程度放置してるので何とかしたいです。
ヽ(`д´)ノ うわーん

  • 本日の作業

アプリを完成させるための手段を考慮し、本サイトでの報告を始める。

  • 明日の予定

作りかけのアプリ「進化の卵(仮)」の簡単な説明や動画をアップする。