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