この章ではテキストを読み進めていく形式のアドベンチャーゲームを作ってみましょうか…。
もう少し説明するとホラー系のノベルや恋愛シミュレーション(笑)が当てはまるジャンルです。
アナタはコレ系のゲームをしたことがありますか?
このテのゲームを作るソフトも存在しますが一から自分の手で組んでみても面白いかもしれません。

TIPSにテキストから定義してある命令とパラメータの読み込み方を以前載せました。
ソレの改良(と呼べるかわかりませんが)とモジュールの画像エフェクト命令を利用したいと思います。
まずはサンプルで使用する命令を決めておきましょうか…。
・まず学校等ステージとなる背景画像を表示する命令「stage」
・キャラクターを表示させる命令「charon」と、消去する命令「charoff」
・場面切り替え時にウィンドウ内をフェードアウトする命令「dark」
・画面を揺らす命令「quake」
・キャラクターの表情を変化させる命令「status」
・ユーザーに入力させる命令「input」
・ゲーム終了する(エンディングに行く)命令「end」
形として恋愛シミュレーションのような感じで、「とある学生が転校した所から」というシチュエーションで。
使用する画像…というかこの章のスクリプトまで全てが入ったものをお渡しします。コチラからどうぞ。
それではスクリプトの部分をモジュールから入りましょうか…。
#module "adventure" #deffunc retmes val mref p1,24 mref bmscr,67 fsize=bmscr.32/2 strlen len,p1 if len*fsize+csrx < winx : mes p1 : return mx=csrx my=csry repeat len peek tmp,p1,cnt if tmp > 128&(tmp < 160)|(tmp > 223) : tmp.1=2 : fsize=fsize*2 : else : tmp.1=1 if mx+fsize > 620 : mx=30 : my+=bmscr.32+10 if tmp=13 : tmp.1=2 : fsize=fsize*2 : msg="\n" : else : strmid msg,p1,cnt,tmp.1 pos mx,my : mes msg if tmp=13 : mx=30 : my+=bmscr.32+10 : else : mx+=fsize if tmp.1=2 : fsize=fsize/2 : tmp=cnt+2 : continue tmp loop return #deffunc quake int,int,int,int,int mref p1,0 mref p2,1 mref p3,2 mref p4,3 mref p5,4 direction=0 tmp=0 pos 0,0 repeat p5*2 redraw 0 direction=cnt\2 if p1=0 : gcopy p2,tmp,0,p3-5,p4-5 : if direction : tmp-=5 : else : tmp+=5 if p1=1 : gcopy p2,0,tmp,p3-5,p4-5 : if direction : tmp-=5 : else : tmp+=5 redraw wait 1 loop gcopy p2,0,0,p3,p4 return #global
retmesはテキストが端まで来たら自動改行させる命令です。
パラメータとして文字列の入った変数を渡します。
内部で自動的に位置指定をしてますので表示位置には気を使わなくて結構です。
quakeは画面を揺らす命令ですね。
パラメータ1で寄らす方向を決めます。0で横、1で縦揺れ。
パラメータ2で揺らす画像(ウィンドウ)を指定します。
パラメータ3で画像の横幅を。
パラメータ4で画像の高さを指定します。
パラメータ5で一揺れの回数を指定します。
サンプルではこれらのモジュールを「adventure.as」として読み込んでいます。
それではサンプルの心臓部に入りましょうか。
#include "adventure.as" #define WX 640 ; ウィンドウXサイズ #define WY 480 ; ウィンドウYサイズ #define ZOOM 3 ; 拡大率 #define CHARCTERS 4 ; 登場する人間の数 #define FACEX 48 ; 表示し始めるX位置 #define PMAX 3 ; 一度に登場できる最大人数 sdim scenario,2048 ; シナリオデータ sdim back,16,3 ; 背景 sdim myname,12 ; 自分の名前 sdim name,4,CHARCTERS ; 登場人物の名前 sdim msg,256 ; メッセージ用の変数 sdim command,8 ; 命令名が入る sdim tmp,128,3 ; 一時変数 sdim update,2 ; メッセージエリアの背景更新用フラグ dim tmpnum,2 ; 数値型用一時変数 dim status,CHARCTERS ; 登場人物の現在の状態 dim facey,CHARCTERS ; 登場人物の顔位置 dim key,1 ; キー判定 dim num,1 ; 現在登場している人数 dim position,PMAX ; それぞれの立ち位置に誰が立っているか dim posx,PMAX,PMAX ; ソレぞれの立ち位置 dim param1,1 ; 第一パラメータ用 dim param2,1 ; 第二パラメータ用 ;------------- ココからシナリオデータ -------------- back="gate.jpg","classroom.jpg","passage.jpg" name="A子","B子","A男","B男" posx.0.0=-64*ZOOM+WX/2 ; 1人 posx.0.1=-64*ZOOM-250+WX/2,-64*ZOOM+250+WX/2 ; 2人 posx.0.2=-64*ZOOM-400+WX/2,-64*ZOOM+WX/2,-64*ZOOM+400+WX/2 ; 3人 facey=37*ZOOM,33*ZOOM,23*ZOOM,18*ZOOM scenario ="stage\"0\"\n" scenario+="msg\"俺は先週引っ越してきたという設定になっている。\"\n" scenario+="msg\"今日からこの新しい学校に通うことになるというのが定番だ。\"\n" scenario+="wait\"10\"\n" scenario+="msg\"【主人公】中に入らないと話が進まないので入るとするか。\"\n" scenario+="wait\"100\"\n" scenario+="quake\"1,1\"\n" scenario+="charon\"2\"\n" scenario+="status\"2,2\"\n" scenario+="msg\"【&name2;】いてっ!ぶつかってくるなゴルァ!前見て歩きやがれ!\"\n" scenario+="wait\"10\"\n" scenario+="msg\"来て早々ガラの悪そうな奴に出会ってしまった…。\"\n" scenario+="msg\"構っているとロクなことがない。ココは逃げるに限るな。\"\n" scenario+="wait\"50\"\n" scenario+="status\"2,0\"\n" scenario+="msg\"【&name2;】あ、この野郎。逃げるな!謝れっ!\"\n" scenario+="wait\"10\"\n" scenario+="dark\"200\"\n" scenario+="stage\"1\"\n" scenario+="msg\"【担任】…というわけで彼は今日からこのクラスの一員の…えっと名前何だったかな?\"\n" scenario+="wait\"10\"\n" scenario+="input\"&myname;\"\n" scenario+="msg\"【&myname;】&myname;です。\"\n" scenario+="wait\"100\"\n" scenario+="msg\"【担任】そう&myname;君だ。&myname;君の席はアソコだ。\"\n" scenario+="msg\"それではコレで朝のHRを終了する。\"\n" scenario+="msg\"1時間目始まるまで各自授業準備しておきなさい。\"\n" scenario+="wait\"50\"\n" scenario+="msg\"【&myname;】テメェが覚えておけっつーの(ボソ\"\n" scenario+="wait\"10\"\n" scenario+="dark\"200\"\n" scenario+="stage\"1\"\n" scenario+="charon\"1\"\n" scenario+="status\"1,3\"\n" scenario+="msg\"【&name1;】どうもはじめまして。私&name1;。ヨロシクね。\"\n" scenario+="wait\"100\"\n" scenario+="msg\"【&myname;】あ、あぁヨロシク…。\"\n" scenario+="wait\"100\"\n" scenario+="msg\"【&myname;】あ…。\"\n" scenario+="wait\"10\"\n" scenario+="charon\"2\"\n" scenario+="status\"2,5\"\n" scenario+="msg\"【&name2;】ん?\"\n" scenario+="msg\"【&myname;】…。\"\n" scenario+="wait\"50\"\n" scenario+="msg\"【&name1;】!!\"\n" scenario+="status\"2,2\"\n" scenario+="status\"1,1\"\n" scenario+="wait\"100\"\n" scenario+="charoff\"1\"\n" scenario+="status\"2,0\"\n" scenario+="msg\"【&name2;】テメェ!\"\n" scenario+="wait\"10\"\n" scenario+="quake\"0,3\"\n" scenario+="dark\"200\"\n" scenario+="msg\"そして月日は流れ、転校して1年が経った…(早\"\n" scenario+="wait\"10\"\n" scenario+="stage\"2\"\n" scenario+="charon\"0\"\n" scenario+="status\"0,3\"\n" scenario+="charon\"3\"\n" scenario+="status\"3,3\"\n" scenario+="msg\"【&name3;】…だよなぁ〜(藁\"\n" scenario+="msg\"【&name0;】そうよねぇ〜アハハw\"\n" scenario+="wait\"100\"\n" scenario+="msg\"【&myname;】そうだったんだ…って何の話? つーかその前に誰なんだよっお前らは!\"\n" scenario+="wait\"100\"\n" scenario+="msg\"【&name0;・&name3;】…さぁ…。\"\n" scenario+="status\"0,4\"\n" scenario+="status\"3,4\"\n" scenario+="wait\"10\"\n" scenario+="dark\"100\"\n" scenario+="end\"0\"\n" ;------------------- ココまで ---------------------- buffer 3 picload "adv.bmp" bufx=winx bufy=winy buffer 2,bufx*ZOOM,bufy*ZOOM+100 gzoom bufx*ZOOM,bufy*ZOOM,3,0,0,bufx,bufy color 200,200,255 : boxf 0,bufy*ZOOM,600,bufy*ZOOM+100 buffer 4,600,100
はい。ココまでが準備ですね。
シナリオは外部ファイルにして読み込ませたほうがいろいろ便利です。
やり方は前章までと同じですので説明いたしません。
シナリオデータscenarioをよく見ると変数が入っていますね。
ココで言っているのはHSPの変数ではなく文字列に埋め込んでいる変数のことですよ?
具体的には「&myname;」「&name0;」「&name1;」「&name2;」「&name3;」の4つのテキストです。
HTMLタグの特殊文字と同じような感じにしてみました(^^;
コレを後のメイン処理で展開しています。
始めにZOOMという拡大率(倍数)が定義されていますが、
コレは人物画像の拡大率(倍数)です。300%にしています。
なんでこんなことをするかって言うと、他のゲーム等でも当てはまることなのですが、
無駄に細かい画像を使っても、その画像ファイルがハードディスクを圧迫するだけだからです。
3倍までするとこの章の初めにある完成図のように少しカクカクした画像になってしまいますが、
1.5〜2倍くらいなら大丈夫でしょう。
自分のパソコンだけで使うのはいいとして他人にプレイしてもらう時はそういう面まで気を配りましょう。
それでは続きましてメインの処理に入っていきましょう。
TIPSと同じ要領で一行を1データとして読み込み処理をしていきます。
*main
gsel 0
font "MS 明朝",18,1
gmode 2
notesel scenario
notemax max
repeat max ; シナリオデータの最後まで1行ずつ読み込む
noteget tmp,cnt
getstr command,tmp,0,'"' ; 命令名取出し
getstr tmp,tmp,strsize,'"' ; パラメータ取り出し
if command="msg" { ; メッセージエリアに表示させる命令
repeat
tmpnum.1=0 ; 変数を名前に変換するのに必要なフラグ変数
instr tmpnum,tmp,"&myname;",0
if tmpnum ! -1 {
tmp.2 = myname : tmpnum.1=8
} else {
instr tmpnum,tmp,"&name0;",0
if tmpnum > -1 : tmp.2 = name.0 : tmpnum.1 = 7
instr tmpnum,tmp,"&name1;",0
if tmpnum > -1 : tmp.2 = name.1 : tmpnum.1 = 7
instr tmpnum,tmp,"&name2;",0
if tmpnum > -1 : tmp.2 = name.2 : tmpnum.1 = 7
instr tmpnum,tmp,"&name3;",0
if tmpnum > -1 : tmp.2 = name.3 : tmpnum.1 = 7
}
if tmpnum.1 = 0 : break
strmid tmp.1,tmp,0,tmpnum
strmid tmp,tmp,tmpnum+tmpnum.1,128
tmp=""+tmp.1+""+tmp.2+""+tmp
loop
msg+=tmp+"\n"
}
if command="wait" { ; ウェイト(クリックするまでストップさせる)命令
int tmp
if msg!="" { ; 表示させるメッセージがある場合
redraw 0
gmode 4,,,200 ; 背景を少し透過させてメッセージエリアを表示
pos 20,WY-120 : gcopy 2,0,bufy*ZOOM,600,100
gmode 2
pos 30,WY-110 : retmes msg ; メッセージを実際に表示させるのはウェイトさせた時
msg=""
redraw
}
repeat
stick key,,1
if key&256 : break ; クリックされるまでループし続ける
wait 1
loop
wait tmp
pos 20,WY-120 : gcopy 4,0,0,600,100 ; メッセージエリアをクリアする
}
if command="input" { ; ユーザーに入力させる命令
color 192,192,192 : boxf : color ,,255
if tmp="&myname;" { ; コレしかないのだが...
pos 100,100 : mes "アナタの名前を決めてください(最大半角11文字)"
pos 200,200 : input myname,100
pos 350,190 : gcopy 2,ZOOM*224,ZOOM*128,ZOOM*32,ZOOM*16 ; ボタン
repeat
stick key,,1
if key&256 {
tmp=mousex
tmp.1=mousey
if myname ! "" {
if 350 < tmp & (ZOOM * 32 + 350 > tmp) {
if 190 < tmp.1 & (ZOOM * 16 + 190 > tmp.1) : break
}
}
}
wait 1
loop
>pos 350,190 : gcopy 2,ZOOM*224,ZOOM*144,ZOOM*32,ZOOM*16 ; ボタン凹
repeat 1
stick key,256,1
if key&256 : wait 1 : continue 0
loop
pos 350,190 : gcopy 2,ZOOM*224,ZOOM*128,ZOOM*32,ZOOM*16 ; ボタン凸
clrobj
pos 0,0 : gcopy 3,0,0,WX,WY
color
}
}
if command="quake" { ; ウィンドウ全体(ウィンドウ内)を揺らす命令
getstr param1,tmp,0,',' ; 揺らす方向を読み込む(0=横,1=縦)
getstr param2,tmp,strsize ; 揺らす回数を読み込む
int param1
int param2
quake param1,3,WX,WY,param2 ; 揺らす
}
if command="status" { ; キャラクター表情変更命令
getstr param1,tmp,0,',' ; キャラクタID
getstr param2,tmp,strsize ; キャラクタの表情
int param1
int param2
status.param1=param2 ; キャラの状態を更新
redraw 0
tmpnum=num-1
repeat num
tmp=position.cnt
pos posx.cnt.tmpnum + FACEX,facey.tmp
gcopy 2, ZOOM * 64 * tmp + FACEX, facey.tmp - 50, ZOOM * 32, ZOOM * 32
pos posx.cnt.tmpnum + FACEX,facey.tmp
gcopy 2, ZOOM * status.tmp * 32, ZOOM * 128, ZOOM * 32, ZOOM * 32
loop
redraw
}
if command="stage" { ; 背景画像読み込み命令
int tmp
buffer 3
picload back.tmp
gsel 0
redraw 0
pos 0,0 : gcopy 3,0,0,WX,WY
redraw
repeat PMAX
position.cnt=-1 ; キャラクタを全て消去
loop
num=0 ; 人数も0に戻しておく
update="1" ; メッセージエリアの背景記憶フラグ
}
if command="charon" { ; キャラクター表示命令
int tmp
position.num=tmp ; 誰を表示するか
redraw 0
pos 0,0 : gcopy 3,0,0,WX,WY
repeat num+1
pos posx.cnt.num, 50
gcopy 2, ZOOM * 64 * position.cnt, 0, ZOOM * 64, ZOOM * 128 ; キャラクタ
loop
redraw
num+
update="1"
}
if command="charoff" { ; キャラクター消去命令
int tmp
redraw 0
pos 0,0 : gcopy 3,0,0,WX,WY
repeat num
if position.cnt=tmp {
tmp=cnt
repeat num-cnt-1,tmp
tmpnum=cnt+1
position.cnt=position.tmpnum
loop
num-
}
break
loop
tmpnum=num-1
repeat num
pos posx.cnt.tmpnum, 50
gcopy 2, ZOOM * 64 * position.cnt, 0, ZOOM * 64, ZOOM * 128 ; 残りキャラクタ
loop
redraw
update="1"
}
if command="dark" { ; ウィンドウを暗くする命令
int tmp
repeat 32,1
redraw 0
color : boxf
gmode 4,,,-cnt*8+256
pos 0,0 : gcopy 3,0,0,WX,WY ; 徐々に暗くする
redraw
wait 1
loop
update="1"
gmode 2
wait tmp
}
if command="end" : break ; ゲーム終了(ループを抜ける)命令
if update="1" { ; テキストエリア背景更新
gsel 4
pos 0,0 : gcopy 0,20,WY-120,600,100 ; メッセージエリアの背景を更新
gsel 0
update="0"
}
loop
color : boxf : color 255
font "MS 明朝",400
pos 120,40 : mes "終"
stop
なんか非常に見難くなってしまいましたね(^^;
スクリプト内で新しく作成した命令たちをモジュールにした方がよかったですね…。
流れとしてこういう風に作っていけばアドベンチャーゲームになるよ、という程度とお考えください(^^;
必要だけど細かい処理は省いていたり簡略化したりしています。
例えば「表示テキストがエリアサイズより大きくなったらテキストエリアのページを切り替える処理」や、
「自分の名前を入力するところなどゲームに似合わないinputを使ったり」しています。
また「ゲームの進行を分岐させる話」がなかったり「セーブ・ロード機能」も付いていません。
ココでは必要最低限な「キャラが同画面内に入ったら配置変更」したり、
「キャラクターの表情の変更」といった基本部分のみ取り入れています。
細かい点については今後にアドベンチャーゲーム作成ツールを説明する講座が出るようであれば説明します。
ごちゃごちゃして難しそうに感じる方もいると思いますが、比較的簡単なので一度トライしてみてください。
まぁシナリオを考えるのが非常に面倒だとは思いますが…(^^;
今になってよく考えてみると、この章ではじめて背景に画像を使用するようですね…。
- 謝辞 -
今回サンプルで使用した下記の背景画像は「CHIC STUDIO(活動停止)」様の素材を使用させていただきました。
