sample09_hgimg4-yoko-WebAssembly.hsp

sample\sample_hgimg4-yoko-WebAssembly\ sample09_hgimg4-yoko-WebAssembly.hsp

#include "hgimg4.as"

#include "hsp3dish.as"
#include "ezlocal-dish-js.hsp"
ezLocalJS ,2  ; 引数 - p1:自動操作オプション、p2:自動チェックオプション

#include "mod_ezphp.hsp"

#include "mod_posteffect.as"
// HSP3.7β10 から HSP3Dish.js でもポストエフェクトが使えるようになりました。
#if __hspver__ >= 14090
#define USE_EFFECT  ; ポストエフェクトを使用 : redraw → post_drawstart/post_drawend
#endif

#include "mod_vpad.as"

title "HGIMG4 Sample"

	;
	;  HSP3同梱サンプル
	;  sample\hgimg4\boxf_snake.hsp を改造
	;
	;	立方体を使った簡単なスネークゲーム
	;	赤いブロックを取っていってください
	;
	randomize
	myflag = -1
	goto *restart

*restart

	score=0
	level=1
*gstart
	gpreset
	hspvpad_init 2,1+4			; バーチャルパッドの初期化

#ifdef USE_EFFECT
	post_reset
	post_select POSTID_NONE
	post_select POSTID_GLOW2        ; ポストエフェクト設定
	post_setprm 80, 0.7, 6/*10*/    ; ポストエフェクトのパラメーター
	                                ; - HSP3Dish.js ではパラメータによってはinput命令の描画がバグるようです。。。
	                                ; - POSTID_GLOW2 だと第3パラメータ(level)が 7 以上だとバグります。
#endif

	gpresetlight 1,2			; ポイントライトを2個追加する

	setcolor GPOBJ_LIGHT, 0.8,0.8,0.8	; 標準ライトカラーを設定
	setdir GPOBJ_LIGHT, 0.2,0.2,0.2		; 標準アンビエントカラーを設定

	;	nullライトとして設定します
	gpnull id_model
	gplight id_model, GPOBJ_LGTOPT_POINT,16,0.8,0.5	; ポイントライトとして設定する
	setcolor id_model, 1,1,1			; ライトカラーを設定
	gpuselight id_model				; ポイントライトを使用する

	;	nullライトとして設定します
	gpnull id_target
	gplight id_target, GPOBJ_LGTOPT_POINT,16,1,0.5	; ポイントライトとして設定する
	setcolor id_target, 1,0,0			; ライトカラーを設定
	gpuselight id_target, 1				; ポイントライトを使用する

	wx=30:wz=30				; フィールドのサイズ
	basex=0.5-(wx/2):basez=0.5-(wz/2)	; ベース座標
	basey=0.5				; Y座標のベース
	dim map,wx,wz				; 仮想マップ
	dim boxcolor,8				; 色リスト	

	;	色リストを設定
	boxcolor(1) = $c0c0c0
	boxcolor(2) = $a00000
	boxcolor(3) = $a060

	;	方向リストを作成
	dim dirpx,4:dim dirpz,4:sdim dirstr,64,4
	dirpz(0)=-1:dirpz(2)=1
	dirpx(1)=1:dirpx(3)=-1
	dirstr(0)="↑"
	dirstr(1)="→"
	dirstr(2)="↓"
	dirstr(3)="←"

	;	壁を作成
	col_wall = $808080
	gpbox i, 1, col_wall	; 箱ノードを生成する
	setscale i, wx, 1, 1 : setpos i, 0 , basey , basez
	gpbox i, 1, col_wall	; 箱ノードを生成する
	setscale i, wx, 1, 1 : setpos i, 0 , basey , basez+wz-1
	gpbox i, 1, col_wall	; 箱ノードを生成する
	setscale i, 1, 1, wz : setpos i, basex , basey , 0
	gpbox i, 1, col_wall	; 箱ノードを生成する
	setscale i, 1, 1, wz : setpos i, basex+wx-1 , basey , 0
	repeat wx : map(cnt,0)=1 : map(cnt,wz-1)=1 : loop
	repeat wz : map(0,cnt)=1 : map(wx-1,cnt)=1 : loop

	histmax=64:curhist=8+level
	dim myid, histmax
	dim myposx, histmax
	dim myposz, histmax
	myx=wx/2:myz=wz/2
	if myflag > 0 : myflag=0
	mykey=2
	mydir=0
	mydist=10.0
	myspeed=15
	left=4+level
	gosub *move_player

	max=level*10+10
	repeat max
		gosub *rndaxis
		a=1:gosub *putbox
	loop
	gosub *add_target

	gpfloor id_floor, wx,wz, $606060	; 床ノードを追加

	gosub *camera_init
	frame=1
	hval=0
	if myflag == -1 {
		msg="tap to start!"
		repeat
			gosub *key_input
			gosub *camera_update
			gosub *screen_update
			if key & 256 : break
		loop
		myflag = 0
	}
	msg="Level..."+level
	repeat 120
		gosub *key_input
		gosub *camera_update
		gosub *screen_update
	loop
	msg=""
*main
	;	キー入力
	gosub *key_input
	if (frame\myspeed)=0 {
		; プレイヤーを更新
		gosub *move_player
	}
	if (frame\240)=0 {
		gosub *speedup
	}
	gosub *camera_update
	gosub *screen_update
	if myflag>0 : goto *main_ov
	if left=0 : goto *main_clr
	goto *main

*main_ov
	;	ゲームオーバー
	if myflag == 1 {
		msg="Crash...!"
		// ゲームオーバー ループ初回
		gosub *gameover_1
		myflag = 2
	}
	if myflag == 2 {
		// ユーザー名入力待ちループ
		gosub *gameover_2
	}
	stick key
	if key&128 : goto *byebye
	;if key&$130 : goto *restart
	gosub *camera_update2
	gosub *screen_update
	goto *main_ov

*screen_gameovermes_update
	if myflag == 2 {
		// ユーザー名入力待ちループ
		gosub *gameover_2_mes
	}
	if myflag == 3 {
		// スコアsend後のループ
		gosub *gameover_3_mes
	}
	return

*main_clr
	;	クリア
	msg="Clear...!"
	repeat 120
	stick key
	if key&128 : goto *byebye
	gosub *camera_update2
	gosub *screen_update
	loop
	level++
	goto *gstart

*byebye
	end

*screen_update
	;	描画処理
#ifdef USE_EFFECT
	post_drawstart			; 描画開始
#else
	redraw 0
#endif

	;	ライトの色を変化させる
	;	hsvcolorの値をRGB値に変換する
	hsvcolor hval,255,255
	hval+:if hval>192 : hval=0
	rval=double(ginfo_r)/255.0
	gval=double(ginfo_g)/255.0
	bval=double(ginfo_b)/255.0
	setcolor id_model, rval,gval,bval	; ライトカラーを設定

	rgbcolor $305070:boxf 0,0,1040,480		; 背景クリア
	gpdraw				; シーンの描画
	gmode 0
	font "",20
	color 255,255,255
	pos 28,8:mes "SCORE:"+score
	if msg!="" {
		font "",120,,4 : pos 210,210
		if msg == "tap to start!" : font "",90,,4 : pos 270,210
		; - fontsystemの影響で(?)文字の描画サイズがあまり大きくなるとhtmlが全体(fontsystem込み)を表示しようと縮小してしまう。
		;   結果、ゲーム画面の右側に余白ができてしまうので大きさを制限する必要がある。
		mes msg,mesopt_outline
	}
	gosub *screen_gameovermes_update

	hspvpad_put mykey


#ifdef USE_EFFECT
	post_drawend			; 描画終了
#else
	redraw 1
#endif
	await 1000/60			; 待ち時間
	frame++
	return

*rndaxis
	;	ランダムな位置を取得->(x,y)
	x=rnd(wx-2)+1:z=rnd(wz-2)+1
	if map(x,z) : goto *rndaxis
	if abs(myx-x)+abs(myz-z)<4 : goto *rndaxis
	return
*putbox
	;	箱を設置
	;	(x,z)の位置にboxを設置、aの値が色リストのコード
	map(x,z)=a
	gpbox i, 1, boxcolor(a)	; 箱ノードを生成する
	setpos i, basex+x, basey, basez+z
	return

*add_target
	;	ターゲットを設置
	gosub *rndaxis
	a=map(x+1,z)+map(x-1,z)+map(x,z+1)+map(x,z-1)
	if a>=2 : goto *add_target

	a=2:gosub *putbox:redid=i
	tarx=basex+x:tarz=basez+z
	setpos id_target,tarx,1,tarz
	return

*key_input
	;	キー入力処理
	stick key,$100
	hspvpad_key key

	if key&128 : goto *byebye
	if key&2 : mydir=0 : mykey=2
	if key&8 : mydir=2 : mykey=8
	if key&1 : mydir=3 : mykey=1
	if key&4 : mydir=1 : mykey=4
	return

*move_player
	;	プレイヤーを進める
	if myflag>0 : return
	if curhist>0 {
		i=curhist
		if myid(i)>0 {
			map(myposx(i),myposz(i))=0
			delobj myid(i)
		}
		repeat
			if i=0 : break
			myid(i)=myid(i-1)
			myposx(i)=myposx(i-1)
			myposz(i)=myposz(i-1)
			i--
		loop
	}
	myx+=dirpx(mydir)
	myz+=dirpz(mydir)
	a=map(myx,myz)
	if a=1|a=3 {				; やられた?
		myflag=1
		return
	}
	if a=2 {				; ターゲット
		gosub *add_player
		delobj redid
		score+=level*10
		left--
		if left>0 : gosub *add_target
	}
	x=myx:z=myz:a=3:gosub *putbox
	newid=i
	realx=basex+x:realz=basez+z		; 先頭のプレイヤー座標
	setpos id_model,realx,1,realz

	myid(0)=newid
	myposx(0)=x
	myposz(0)=z

	return
*add_player
	;	プレイヤーを伸ばす
	curhist++
	if curhist>=histmax : curhist=histmax-1
	return
*speedup
	;	プレイヤーのスピードアップ
	if myspeed>4 : myspeed--
	return

*camera_init
	;	カメラ初期化
	camx=0.0:camy=15.0:camz=0.0
	camadjz=13.0:camyfix=0
	mydist=camadjz
	camang = 0.0
	return

*camera_update
	;	カメラ設定サブルーチン
	dx = realx-tarx
	dz = realz-tarz
	if myflag>=0 {
		todist = sqrt(dx*dx+dz*dz)*0.5+5
		mydist+=(todist-mydist)*0.01
	}

	dx=(realx-camx)*0.02
	dz=(realz-camz)*0.02
	camx+=dx:camz+=dz
	camadjz = 5 + mydist
	setpos GPOBJ_CAMERA, camx,camy,camz+mydist		; カメラ位置を設定
	gplookat GPOBJ_CAMERA, camx,camyfix,camz		; カメラから指定した座標を見る
	return

*camera_update2
	;	カメラ設定サブルーチン(周回)
	dx=sin(camang)*mydist
	dz=cos(camang)*mydist
	if camy>3 : camy-=0.05
	setpos GPOBJ_CAMERA, camx+dx,camy,camz+dz		; カメラ位置を設定
	gplookat GPOBJ_CAMERA, camx,camyfix,camz		; カメラから指定した座標を見る
	camang+=0.01
	return

	
*gameover_1
	your_name = ""

	// 既に自分の名前があるか確認
	exist "save/myname.txt"
	if strsize != -1 {
		notesel your_name
		noteload "save/myname.txt"
		noteunsel
	}
	// HSP3Dish.js で input は 闇なので独自のインターフェイスを用意した方が良いと思う。。。
	objsize 280,32
	pos 370,100 : input your_name : id_input_yourName = stat
	pxy = ginfo_cx, ginfo_cy
	pos pxy(0),pxy(1) //: mes "※ 英数記号のみ"
	objsize 80,32
	pos 670,100 : button gosub "送信", *send
	// スマホでキーボードを出させる苦肉の策(要素を作成)
	sdim strbuf, 1024  ; Androidでinput入力を捕捉する必要があるため
	sdim mae_strbuf, 1024
	your_name_ = your_name
	strrep your_name_, "'", "\\'"  ; エスケープ
	
	inputSizeX = 280
	inputSizeY = 32
	inputPosX = 370
	inputPosY = 100
	exec {"
		// Androidはinput内容を捕捉する必要がある
		var mae_hsp_input_value = '"}+your_name_+{"';

        if (navigator.userAgent.match(/iPhone|iPad|Android/)) {
			if(document.getElementById('hsp_dummy') === null){
				// キーボード表示用のinput作成(上部に極透明なオブジェクトを作成)
				const inputElement = document.createElement('input');
				inputElement.id = 'hsp_input';
				inputElement.type = 'text';
				inputElement.inputmode = 'email';
				inputElement.style.position = 'fixed';
				inputElement.style.left = '0';
				inputElement.style.top = '0';
				inputElement.style.fontSize = '100px';
				inputElement.style.display = 'none';
				inputElement.style.zIndex = -1;
    		    if (navigator.userAgent.match(/Android/)) {
					inputElement.value = mae_hsp_input_value;
					inputElement.style.width = '98%';
					inputElement.style.height = '100px';
					inputElement.style.opacity = '.2';
				}else{
					inputElement.style.width = '1px';
					inputElement.style.height = '1px';
					inputElement.style.opacity = '.01';
				}
				// フォーカスアウトでinputを非表示にして、dummyを表示する
				inputElement.addEventListener('focusout', (event) => {
					document.getElementById('hsp_input').style.display = 'none';
					document.getElementById('hsp_dummy').style.display = '';
				});
				// Androidはinput内容を捕捉する必要がある
				if (navigator.userAgent.match(/Android/)) {
					inputElement.addEventListener('input', (event) => {
						if(!event.target.value.match(/^[a-zA-Z0-9!-/:-@\[-`{-~ ]*$/)){
							// 半角英数以外は入力禁止(他デバイスに合わせてる)
							event.target.value = mae_hsp_input_value;
						}
						mae_hsp_input_value = event.target.value;
						stringToUTF8Array(mae_hsp_input_value, Module.HEAP8, "} + varptr(strbuf) + {", 1024);
					});
				}
				document.body.appendChild(inputElement);
				
				// click->focus()のためのダミーElement
				const dummyElement = document.createElement('div');
				dummyElement.id = 'hsp_dummy';
				dummyElement.style.position = 'absolute';
				dummyElement.style.fontSize = '100px';
				SetRectDummyDiv(dummyElement);
				dummyElement.style.opacity = '.01';
				//dummyElement.style.opacity = '.5';           // test用
				//dummyElement.style.backgroundColor = 'red';  // test用
				dummyElement.addEventListener('click', () => {
					// スマホはタッチ系のアクション内じゃないとfocus()効かない
					document.getElementById('hsp_input').style.display = '';
					document.getElementById('hsp_dummy').style.display = 'none';
					document.getElementById("hsp_input").focus();
				});
				document.body.appendChild(dummyElement);
				// ダミーElementのリサイズ
				function SetRectDummyDiv(erement){
					if (erement === null) return;
					const inputSizeX = "} + inputSizeX + {";
					const inputSizeY = "} + inputSizeY + {";
					const inputPosX = "} + inputPosX + {";
					const inputPosY = "} + inputPosY + {";
					const wx = ENV.HSP_WX;  // 480
					const wy = ENV.HSP_WY;  // 800
					const sx = ENV.HSP_SX;  // String(window.innerWidth)
					const sy = ENV.HSP_SY;  // String(window.innerHeight)
					const rect = document.getElementById("canvas").getBoundingClientRect();
					if( (sx/sy) < (wx/wy) ){
						// 横幅に余白がなく縦方向に余白があるとき
						const rate = wy / wx * sx / sy;  // ディスプレイ高さに対してゲーム画面高さがどのくらいか
						erement.style.left = rect.left + window.pageXOffset + rect.width*(inputPosX/wx)+'px';
						erement.style.width = rect.width*(inputSizeX/wx)+'px';
						erement.style.top = rect.top + window.pageYOffset + rect.height*((1-rate)/2 + (inputPosY/wy) * rate)+'px';
						erement.style.height = rect.height*((inputSizeY/wy) * rate)+'px';
					}else {
						// 縦方向に余白がなく横幅に余白があるとき
						const rate = wx / wy * sy / sx;  // ディスプレイ幅に対してゲーム画面幅がどのくらいか
						erement.style.left = rect.left + window.pageXOffset + rect.width*((1-rate)/2 + (inputPosX/wx) * rate)+'px';
						erement.style.width = rect.width*((inputSizeX/wx) * rate)+'px';
						erement.style.top = rect.top + window.pageYOffset + rect.height*(inputPosY/wy)+'px';
						erement.style.height = rect.height*(inputSizeY/wy)+'px';
					}
				}
				// デバイス回転時にダミーElementをリサイズ
				function DeviceOrientation_InputDummy() {
					setTimeout(() => {
						SetRectDummyDiv(document.getElementById('hsp_dummy'));
					}, 100);
				}
				window.removeEventListener('orientationchange', DeviceOrientation_InputDummy);
				window.addEventListener('orientationchange', DeviceOrientation_InputDummy);
			}else{
				document.getElementById('hsp_dummy').style.display = '';
			}
		}
	"}
	objsel id_input_yourName
	return

*gameover_2
	mtlist touchid            ; マルチタッチ
	if stat {
		// input部以外タッチでフォーカス外す
		exec {"
			if(document.getElementById("hsp_input")){
				document.getElementById("hsp_input").blur();
			}
		"}
	}
	if mae_strbuf != strbuf {
		// Androidではinput入力を捕捉する必要があるため
		mae_strbuf = strbuf
		your_name = strbuf
		objprm id_input_yourName, your_name
	}
	return
*gameover_2_mes
	color 255,255,255 : font "", 20
	pos 230,100:mes "YOUR NAME : " 
	pos pxy(0),pxy(1) : mes "※ 英数記号のみ"
	return

// データ保存
*send
	clrobj id_input_yourName, id_input_yourName+1  ; inputとbuttonを削除
	; ↑ 引数なしで全てのオブジェクトを消すとmod_posteffect.as内で作られるlayerobjも消えてしまうので注意

	// スマホでキーボードを出させる苦肉の策(要素を削除)
	exec {"
		if(document.getElementById('hsp_input')){
			document.getElementById('hsp_input').remove();
		}
		if(document.getElementById('hsp_dummy')){
			document.getElementById('hsp_dummy').remove();
		}
	"}
	if your_name == "" : your_name = "NO NAME"
	// 自分の名前を登録
	notesel your_name
	notesave "save/myname.txt"
	noteunsel
	devcontrol "syncfs"
	
	// ユニークID生成(テキトウ20桁)
	exist "save/uniqid.txt"
	if strsize == -1 {
		// まだブラウザに保存されていなかったとき
		uniqId = TekitouUniqId(20)  ; 20桁のUniqueID生成
		dirlist dummy,"save",5 : if stat == 0 : mkdir "save"  ; ← Windowsアプリとして実行するとき用に
		notesel uniqId
		notesave "save/uniqid.txt"  ; ブラウザに保存
		devcontrol "syncfs"
		noteunsel
	}else {
		// すでにブラウザにUniqueIDが保存されていたとき。読み込み
		notesel uniqId
		noteload "save/uniqid.txt"
		noteunsel
	}
	
	// データベースファイル「sample.db」を開き(ファイルが無ければ作られる)、「UserScore」テーブルを作成 (すでにテーブルがあれば作成されません)
	// - 基本的にデータベースファイルは事前に準備しておきhtmlファイル等と共にアップロードしてください。
	ezphpsql "sample.db", "CREATE TABLE IF NOT EXISTS UserScore (ID INTEGER PRIMARY KEY, uniqid TEXT, score INTEGER, name TEXT, UNiQUE(uniqid))"
	// とりあえずスコア0でユーザーデータを新規挿入。ただしUNiQUE制約があるので既に同じuniqidがある(ゲームを遊んだことがある)場合はErrorが返る(stat=0、がそれは無視してOK)
	ezphpsql "sample.db", "INSERT INTO UserScore (uniqid, score, name) VALUES (?, ?, ?)", uniqId, 0, your_name  ; 第3引数以降はプレースホルダにセットする値で「?」の位置に前から順で割り当てられます
	// nameは常に更新。scoreは高い時だけ更新。uniqidを目印にして。
	query = {"
		UPDATE UserScore
		SET
			name = ?,
			score = CASE
				WHEN score < ? THEN ?
				ELSE score
			END
		WHERE uniqid = ?;
	"}
	ezphpsql "sample.db", query, your_name, score, score, uniqId  ; ezphpsqlは中身が#defineなので第2パラメータまでのときでも直接複数行文字列を置くことはできません。
	// score降順ですべてのユーザー分取り出す(単純な平文出力。jsonで出力したい場合はezphpsql_jsonを使ってください。)
	ezphpsql "sample.db", "SELECT uniqid, score, name FROM UserScore ORDER BY score DESC"
	rankText = refstr  ; シンプルなSELECT文なら平文で受け取って処理すると良いでしょう。(データ内の改行は「<BR>」で置換して出力されます)
	notesel rankText
	myflag = 3
	msg = ""
	objsize 150,100
	pos 50,50 : button "再チャレンジ", *pre_restart
	return
*gameover_3_mes
	// ランキング描画
	pos 250, 50 : font "", 25
	maeScore = -1 : maeJunni = 1
	repeat notemax / 3  ; 自分が何項目要求したかで何人分返ってきたかを計算。(ここでは uniqid, score, name の3項目要求している)
		noteget rank_uniqid, cnt*3 : strrep rank_uniqid, "uniqid:", ""  ; keyが邪魔なので取り除く
		noteget rank_score, cnt*3+1	: strrep rank_score, "score:","" : rank_score = int(rank_score)
		noteget rank_name, cnt*3+2 : strrep rank_name, "name:", ""
		if rank_uniqid == uniqId : color 255,128,128 : rank_uniqid = " ("+rank_uniqid+")" : else :rank_uniqid = "" : color 255,255,255  ; 自分だけUniqID表示
		junni = (cnt+1) : if maeScore == rank_score : junni = maeJunni : else : maeJunni = junni : maeScore = rank_score  ; 同スコアは同率順位
		mes ""+junni+"位  "+rank_score+" - "+ rank_name + rank_uniqid
	loop
	return

*pre_restart
	clrobj
	goto *restart
	stop

// テキトウに N桁 Unique な ID を生成する関数
#module
#defcfunc TekitouUniqId int keta
	randomize
	uniqId = ""
	repeat keta
		uniqid += "_"
		id = rnd(10+26+26)
		if id >= 36 : poke uniqId, cnt, id+61 : continue  ; a-z
		if id >= 10 : poke uniqId, cnt, id+55 : continue  ; A-Z
		poke uniqId, cnt, id+48  ; 0-9
	loop
	return uniqId
#global