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