1MAP-STG オートエイムを実装しよう


2018年 6月9日
 
 
 今回はオートエイム(自動照準)を実装します。

 スマホゲームって、どうしても操作に馴染まないというか、画面を操作すると指で隠れますから鑑賞できないというか、だから自動ゲームというか放置ゲーム向きだと思います。

 その点パズドラは良く考えられていると思うけど、精々そのくらいの操作が限界なのかとも思う。

 この 1MAP-STGもスマホだとイマイチゲームしずらいですね。

 そこで今回はスマホ用にオートエイムを作ってみる事にします。

 ver.0.0006


 
 
 オートエイムのやりかたですけど「標的とする敵の座標へ照準を動かす」だけです。

 どの敵を狙うかは、始めと標的を倒した時だけ捜査します。

 毎フレーム事だと計算が頻繁になるし、標的が都度変わるので照準がウロウロします。

 どの敵が一番近いかは、照準と全敵との距離を測ります。

 距離を測るプログラムは前回以前にやりました。

x2=ex(cnt)-cx : y2=ey(cnt)-cy
 nd2=sqrt(x2*x2+y2*y2); 距離

 毎フレーム事、標的にした敵の座標へ照準を動かし、もし照準と敵の距離が照準のスピード以下なら照準の座標を標的の座標に置き換えます。
 
 
 autofでオートモードとマニュアルモードの切り替えをします。

 autof=0でマニュアル、1でオートモードです。

 切り替えは真ん中の自機をクリックする事で行います。
 
 
 それと今回は #deffuncを使って、標的を特定するソースを、メインルーチンから呼び出して使ってます。

 これは標的を特定する箇所がソース内で複数のため、同じソースを何回も書くと多くなるし見づらいからです。

 合わせてこの方法だと、バグを潰しやすくなります。

 実は #deffunc returnについては、ラベルの gosub returnと違いありません。

 つまり *mainというラベルがあったとして #deffunc main と書かれるだけの事です。

 一種のラベル定義命令だと思えば良いと思います。

 ただ変数を渡せるのと、例えばソースが

n=10 : gosub *syori と書くのと
syori 10 と書くのとで

 小さく書けるので、頻繁に使ったりするのだと書きやすいし見易いです。

 HSPの場合の欠点は「呼び出される #deffuncソースが、呼び出しを行うソースより前に無いとエラー」になる事です。

 そのため HSPの場合どうしても、メインルーチンがソースの一番最後に置かれる、という奇妙な事になります。

 昔 BASIC何かのインタプリタ(逐次訳実行)言語では、メインルーチンは処理の早い前に置け、と言うのがセオリーでした(笑)(今のコンピュターの速度では大差無いんでしょうね)
 
 
 今一度キャラを全部置いておきます。







 右クリックして「画像を保存」で使って下さい。
 
 
 それではソースを

; 企画第一弾 : 1画面防衛シューティングを作ろう
; var.0.006

#include "hsp3dish.as" ; HSPdishの宣言


	randomize	; 乱数の初期化

	pai=3.141593
	pai2=pai*2

	rad45=0.7853982		; ラジアンで45度
	rad90=1.5707965		; ラジアンで90度


	wx=640	; 画面の大きさx
	wy=360	; 画面の大きさy

	gmode 2

	jw1=1 : jo=20 : jo2=jo/2	; 自機、マニュアル
	 celload "jiki_3.png",jw1
	  celdiv jw1,jo,jo,jo2,jo2

	jw2=2						; 自機、オート
	 celload "jiki_2.png",jw2
	  celdiv jw2,jo,jo,jo2,jo2

	sw=3 : so=6 : so2=so/2	; ショット
	 celload "shot_1.png",sw
	  celdiv sw,so,so,so2,so2

	ew=4 : eo=20 : eo2=eo/2	; 敵
	 celload "ene_1.png",ew
	  celdiv ew,eo,eo,eo2,eo2

	cw=5 : co=32 : co2=co/2	; カーソル
	 celload "csr_2.png",cw
	  celdiv cw,co,co,co2,co2

	hw=6 : ho=16 : ho2=ho/2	; ヒット
	 celload "hit_4.png",hw
	  celdiv hw,ho,ho,ho2,ho2

	vw=7 : vo=16 : vo2=vo/2	; バルカン
	 celload "vulcan_1.png",vw
	  celdiv vw,vo,vo,vo2,vo2


; 自機 ----------
	jx=wx/2	; プレイヤーの座標x
	jy=wy/2	; プレイヤーの座標y

	jx1=jx-jo2 ; 自機左座標
	jx2=jx+jo2 ; 自機右座標
	jy1=jy-jo2 ; 自機上座標
	jy2=jy+jo2 ; 自機下座標

	cx=double(jx) ; 照準座標x
	cy=double(jy) ; 照準座標y
	csp=15.0	  ; 照準スピード
	esn=0		  ; 標的

	autof=1	; オートフラグ(0=マニュアル、1=オート)


; SHOT ----------
	sb=20			; ショット最大数
	 dim sf,sb		; ショット発射フラグ
	 ddim sx,sb		; ショット座標x
	 ddim sy,sb		; ショット座標y
	 ddim sdx,sb	; ショット移動量x
	 ddim sdy,sb	; ショット移動量y
	 ddim sr,sb		; 向き

	 dim sxb,sb		; 終点座標x
	 dim syb,sb		; 終点座標y
	 ddim skn,sb	; 現移動距離
	 ddim skb,sb	; 最終移動距離

	ssp=20.0		; スピード
	skc=3			; 1フレームで移動計算する回数

	sc=0			; 出現カウント
	scb=0			; 発射間隔(フレーム)
	sp=6			; パワー

	su=32			; 揺れ幅(集弾値)

; VULCAN ----------
	vb=30			; バルカン最大数
	 dim vf,vb		; バルカン発射フラグ
	 ddim vx,vb		; バルカン座標x
	 ddim vy,vb		; バルカン座標y
	 ddim vdx,vb	; バルカン移動量x
	 ddim vdy,vb	; バルカン移動量y
	 ddim vr,vb		; 向き

	vkc=3			; 1フレームに出現する数
	vsp=20.0		; スピード
	vp=4			; パワー
	vu=30			; 揺れ幅(集弾値)
	vnb=140			; 1クリックで発射される数
	vc=0			; 残弾数
	vcb=20			; 1つ敵を倒した時に増える数


; ENEMY ----------
	eb=40			; 敵最大数
	 dim ef,eb		; 出現フラグ
	 dim ehp,eb		; 耐久度
	 ddim ex,eb		; 敵座標x
	 ddim ey,eb		; 敵座標y
	 ddim edx,eb	; 敵移動量x
	 ddim edy,eb	; 敵移動量y
	 ddim er,eb		; 向き

	ehpb=60			; 耐久度

	esp=0.8			; 敵スピード

	ec=0			; 出現カウント
	ecb=8			; 出現間隔(フレーム)

	scr=0		; スコア
	hiscr=0		; ハイスコア

	kf=0		; キーを押してるかフラグ
	kc=0		; 何フレームキーを押してるか

gosub *main : end


#deffunc esn_set int esn_setf ; 標的設定

	esn=0		; 一番近い敵No.
	nd1=1000.0	; 一番近い距離

	repeat eb
	 if ef(cnt)=0 : continue

	 if esn_setf=0 : x2=ex(cnt)-jx : y2=ey(cnt)-jy ; 自機に近い
	 if esn_setf=1 : x2=ex(cnt)-cx : y2=ey(cnt)-cy ; 照準に近い

	  nd2=sqrt(x2*x2+y2*y2); 距離

	 if nd2<nd1 : nd1=nd2 : esn=cnt
	loop

	return


*main

	mainf=0 ; ゲームフラグ

	repeat ; メインループ ----------------------------------

	 switch mainf
	  case 0 ; タイトル ----------
	   stick k : if k=0 : swbreak ; ボタンでゲーム再開
	   mainf=1
	  swbreak ; ----------

	  case 1 ; ゲーム ----------

; 入力 ----------

	   x=mousex : y=mousey	; マウス、スマホの座標

	   stick k,256	; マウス、スマホクリック
	
	   repeat 1 ; バルカンの入力判定
	    if k=256 : kf=1 : kc+ : break	; 押している
	    if kf=0 : break					; 一度も押してない

	    kf=0						; 放した、押しフラグリセット
	    if kc>2 : kc=0 : break					; 2フレーム以上押していた
         kc=0
		if jx1<x and x<jx2 and jy1<y and y<jy2 : autof=(autof+1)\2 : esn_set 0 : break ; 自機の位置の場合、オート切り替え
	    if vc=0 or vn>0 : break			; 弾切れ or バルカン発射中
	     vn+=vnb						; バルカン発射要求数
	     cx=double(x) : cy=double(y)	; 照準位置をカーソル位置にセット
	     esn_set 1						; 次の標的を探す
	   loop


; 照準 ----------

	   x2=ex(esn)-cx : y2=ey(esn)-cy
		nd=sqrt(x2*x2+y2*y2); 標的と照準の距離

	   repeat 1 ; 照準の移動
	    if autof=0 : cx=double(x) : cy=double(y) : break ; マニュアルの場合

	    if nd<csp : cx=ex(esn) : cy=ey(esn) : break

; ↑ 標的が照準スピード以下の距離なら、照準座標を標的の座標にする
; ↓ そうでないなら照準スピード分動かす

	    xn=double(ex(esn)-cx) ; 一番近い敵と照準の距離x
	    yn=double(ey(esn)-cy) ; 一番近い敵と照準の距離y

	    rad=atan(xn,yn) ; 距離xyから角度を求める
	
	    cx+=sin(rad)*csp	; 照準移動x
	    cy+=cos(rad)*csp	; 照準移動y
	   loop


; SHOT ----------
	   repeat 1 ; 発射
	    if sc>0 : sc- : break ; ウエイト中

	    repeat sb
	     if sf(cnt)!0 : continue ; 使用中

	     sf(cnt)=1 ; 使用フラグon
		 sx(cnt)=double(jx) : sy(cnt)=double(jy) ; 初期座標

		 xx=int(cx)-su/2+rnd(su) ; ショットの着弾点x
		 yy=int(cy)-su/2+rnd(su) ; ショットの着弾点y

		 xn=double(xx-jx)		; 自機とマウスの距離x
		 yn=double(yy-jy)		; 自機とマウスの距離y

		 rad=atan(xn,yn)		; 距離xyから角度を求める

		 sr(cnt)=-rad+pai		; 向き

	 	 sdx(cnt)=sin(rad)*ssp	; 角度とスピードから移動量xを求める
	 	 sdy(cnt)=cos(rad)*ssp	; 角度とスピードから移動量yを求める


		 sxb(cnt)=xx : syb(cnt)=yy	; 終点座標

		 skn(cnt)=0.0				; 現移動距離をリセット

		 x2=xx-jx : y2=yy-jy
		  skb(cnt)=sqrt(x2*x2+y2*y2); 最終移動距離
		 
		 break
		loop
		sc=scb ; 発射ウエイト
	   loop

	   repeat sb : scn=cnt ; ショット移動処理
		if sf(scn)=0 : continue ; 無し

		repeat skc : cnt2=cnt ; 1ショットの移動繰り返し ====================
		 sx(scn)+=sdx(scn) ; xの移動
		 sy(scn)+=sdy(scn) ; yの移動

		 skn(scn)+=ssp ; どれだけ距離を進んだか
		  if skn(scn)>=skb(scn) { ; 終点判定
			 sx(scn)=double(sxb(scn))
			 sy(scn)=double(syb(scn))
			 sf(scn)=-1 ; 着弾フラグ
		  }

		 sx1=int(sx(scn))-so2 ; ショット左座標
		 sx2=int(sx(scn))+so2 ; ショット右座標
		 sy1=int(sy(scn))-so2 ; ショット上座標
		 sy2=int(sy(scn))+so2 ; ショット下座標

		 repeat eb ; 敵との当たり判定
		  if ef(cnt)=0 : continue

		  ex1=int(ex(cnt))-eo2 ; 敵左座標
		  ex2=int(ex(cnt))+eo2 ; 敵右座標
		  ey1=int(ey(cnt))-eo2 ; 敵上座標
		  ey2=int(ey(cnt))+eo2 ; 敵下座標

		  if sx1<ex2 and ex1<sx2 and sy1<ey2 and ey1<sy2 {
		   sf(scn)=-1
		   ehp(cnt)-=sp ; 耐久度-
			if ehp(cnt)<=0 { ; 倒したか
			 ef(cnt)=0	; 当たった敵とショットを消す
			 esn_set 0	; 次の標的を探す
			 vc+=vcb	; バルカンの残弾数を増やす
			 scr+		; スコア
			 if hiscr<scr : hiscr=scr ; ハイスコアの更新
			 break
			}
		  }
		 loop

		 if sf(scn)=1 : if sx2<0 or wx<sx1 or sy2<0 or wy<sy1 : sf(scn)=0 : break ; 場外でフラグ off

		 if sf(scn)=-1 : break ; 着弾してる

		loop ; ====================
	   loop


; VULCAN ----------

	   repeat vkc : cnt2=cnt ; 1フレームに発射される数
	    if vn=0 or vc=0 : break ; 発射要求数、または残弾が0

		repeat vb : vcn=cnt
	     if vf(vcn)!0 : continue ; 使用中

	     vn- ; 発射要求数-
	     vc- : if vc=0 : vn=0 ; 残弾-

	     vf(vcn)=1 ; 使用フラグon
		 vx(vcn)=double(jx) : vy(vcn)=double(jy) ; 初期座標

		 xn=cx-double(jx)		; 自機とマウスの距離x
		 yn=cy-double(jy)		; 自機とマウスの距離y

		 rd=double(-(vu/2)+rnd(vu+1))/100.0 ; 揺れ幅(ラジアン)
		  rad=atan(xn,yn)+rd	; 距離xyから角度を求める

		 vr(vcn)=-rad+pai		; 向き

	 	 vdx(vcn)=sin(rad)*vsp	; 角度とスピードから移動量xを求める
	 	 vdy(vcn)=cos(rad)*vsp	; 角度とスピードから移動量yを求める

		 repeat cnt2
		  vx(vcn)-=vdx(vcn) ; 始点座標をバックさせておくx
		  vy(vcn)-=vdy(vcn) ; 始点座標をバックさせておくy
		 loop

		 break
		loop
	   loop

	   repeat vkc ; vkc回移動
	    repeat vb : vcn=cnt ; バルカン移動処理
		 if vf(vcn)=0 : continue ; 無し

	     vx(vcn)+=vdx(vcn) ; xの移動
	     vy(vcn)+=vdy(vcn) ; yの移動

	     vx1=int(vx(vcn))-vo2 ; バルカン左座標
	     vx2=int(vx(vcn))+vo2 ; バルカン右座標
	     vy1=int(vy(vcn))-vo2 ; バルカン上座標
	     vy2=int(vy(vcn))+vo2 ; バルカン下座標

	     repeat eb ; 敵との当たり判定
	      if ef(cnt)=0 : continue

	      ex1=int(ex(cnt))-eo2 ; 敵左座標
	      ex2=int(ex(cnt))+eo2 ; 敵右座標
	      ey1=int(ey(cnt))-eo2 ; 敵上座標
	      ey2=int(ey(cnt))+eo2 ; 敵下座標

	      if vx1<ex2 and ex1<vx2 and vy1<ey2 and ey1<vy2 {
	       vf(vcn)=-1
	       ehp(cnt)-=vp ; 耐久度-
	        if ehp(cnt)<=0 { ; 倒したか
	         ef(cnt)=0 ; 敵を消す
			 esn_set 1	; 次の標的を探す
	         vcr+ ; スコア
	         if hiscr<scr : hiscr=scr ; ハイスコアの更新
	         break
	        }
	      }
	     loop

	     if vf(vcn)=1 : if vx2<0 or wx<vx1 or vy2<0 or wy<vy1 : vf(vcn)=0 : continue ; 場外でフラグ off

	     if vf(vcn)=-1 : continue ; 着弾してる
	    loop
	   loop


; ENEMY ----------

	   repeat 1 ; 敵出現
	    if ec>0 : ec- : break ; ウエイト中

		repeat eb ; フラグ確認
		 if ef(cnt)>0 : continue ; 使用中
		 ef(cnt)=1 ; 使用フラグon

		 ehp(cnt)=ehpb

		 r=rnd(4) ; 出現方向
		  if r=0 : exs=rnd(wx)-eo2 : eys=0-eo2	; 上
		  if r=1 : exs=rnd(wx)-eo2 : eys=wy+eo2	; 下
		  if r=2 : exs=-eo2	  : eys=rnd(wy)-eo2	; 左
		  if r=3 : exs=wx+eo2 : eys=rnd(wy)-eo2	; 右

		  ex(cnt)=double(exs)	; 初期位置x
		  ey(cnt)=double(eys)	; 初期位置y

		  xn=double(jx-exs)	; 敵と自機の距離x
		  yn=double(jy-eys)	; 敵と自機の距離y

		  rad=atan(xn,yn) 	; 距離xyから角度を求める

		  edx(cnt)=sin(rad)*esp	; 角度とスピードから移動量xを求める
		  edy(cnt)=cos(rad)*esp	; 角度とスピードから移動量yを求める

		  er(cnt)=-rad+pai	; 向き

		  ec=ecb ; 出現ウエイト

		  break ; 登録終わり
		loop
	   loop

	   repeat eb ; 敵移動処理
		if ef(cnt)=0 : continue ; 無し

		ex(cnt)+=edx(cnt) : ey(cnt)+=edy(cnt) ; 移動

		ex1=int(ex(cnt))-eo2 ; 敵左座標
		ex2=int(ex(cnt))+eo2 ; 敵右座標
		ey1=int(ey(cnt))-eo2 ; 敵上座標
		ey2=int(ey(cnt))+eo2 ; 敵下座標

		if jx1<ex2 and ex1<jx2 and jy1<ey2 and ey1<jy2 {
			mainf=2 : break ; 自機と当たりで GAME OVER
		}

		if ex2<0 or wx<ex1 or ey2<0 or wy<ey1 : ef(cnt)=0 ; 枠外でフラグ off
	   loop
	  swbreak ; ----------

	  case 2 ; gameover ----------
	   stick k : if k=0 : swbreak ; ボタンでゲーム再開

	   cx=double(jx) ; 照準座標xリセット
	   cy=double(jy) ; 照準座標yリセット

	   repeat sb
	    sf(cnt)=0 ; ショットリセット
	   loop

	   repeat vb
	    vf(cnt)=0 ; バルカンリセット
	   loop
	   vn=0 : vc=0 ; 残弾リセット

	  repeat eb
	    ef(cnt)=0 ; 敵リセット
	   loop

	   scr=0 ; スコアリセット

	   mainf=0 ; ゲーム再開
	  swbreak ; ----------
	 swend


	 redraw 0 ; 表示 ====================
	  color 1,1,1 : boxf : color 255,255,255 ; 画面クリア

	  pos jx,jy
	   if autof=0 : celput jw1 ; 自機、マニュアル
	   if autof=1 : celput jw2 ; 自機、オート

	  repeat eb ; 敵
	   if ef(cnt)=0 : continue ; 無い
	   pos ex(cnt),ey(cnt) : celput ew,,,,er(cnt) ; 敵
	  loop

	  repeat sb ; ショット
	   if sf(cnt)=0 : continue ; 無い
	   pos sx(cnt),sy(cnt)
	    if sf(cnt)=-1 : celput hw,,,,sr(cnt) : sf(cnt)=0 : continue ; フラグリセット
	    celput sw ; ショット
	  loop

	  repeat vb ; バルカン
	   if vf(cnt)=0 : continue ; 無い
	   pos vx(cnt),vy(cnt)
	    if vf(cnt)=-1 : celput hw,,,,vr(cnt) : vf(cnt)=0 : continue ; フラグリセット
	    celput vw,,,,vr(cnt) ; バルカン
	  loop

	  pos 10,wy-20 : mes vc ; バルカンの残弾表示
	  repeat vc/10
	   pos 50+cnt*6,wy-12 : celput vw
	   if cnt=100 : break ; 最大
	  loop

	  pos cx,cy : celput cw ; 照準

	  switch mainf
	   case 0 ; タイトル
	    pos wx/2-120,wy/2-50 : mes "1画面防衛シューティング"
	    pos wx/2-43,wy/2+30 : mes "var.0.006"
	   swbreak

	   case 1 ; ゲーム
	   swbreak

	   case 2 ; gameover
	    pos wx/2-43,wy/2+50 : mes "GAME OVER"
	   swbreak
	  swend

	  pos wx-300,5 : mes "SCORE "+scr
	  pos wx-150,5 : mes "HI SCORE "+hiscr
	 redraw 1 ; ====================


	 await 1000/15	; ウエイト、FPS 15

	loop ; --------------------------------------------------


	return ; 終わり

 
 

プログラム解説

標的の選定

#deffunc esn_set int esn_setf ; 標的設定
 esn=0 ; 一番近い敵No.
 nd1=1000.0 ; 一番近い距離
 repeat eb
  if ef(cnt)=0 : continue
  if esn_setf=0 : x2=ex(cnt)-jx : y2=ey(cnt)-jy ; 自機に近い
  if esn_setf=1 : x2=ex(cnt)-cx : y2=ey(cnt)-cy ; 照準に近い
  nd2=sqrt(x2*x2+y2*y2); 距離
  if nd2<nd1 : nd1=nd2 : esn=cnt
 loop
return

#deffunc esn_set int esn_setf
 呼び出しルーチン定義。
 int esn_setfは int(整数値)を渡すと言う意味で、呼び出し側は esn_set 0のような書き方になり、この場合 0の値を渡す。

  if esn_setf=0 : x2=ex(cnt)-jx : y2=ey(cnt)-jy ; 自機に近い
  if esn_setf=1 : x2=ex(cnt)-cx : y2=ey(cnt)-cy ; 照準に近い

 渡された esn_setfの値を元に、0の場合は画面中心の自機に近い敵を標的とし、1の場合は動き回る照準に近い敵を標的とする。

 呼び出しが複数個所で行われていて、ショットで敵を倒した場合は 0、つまり自機に近い敵、バルカンで倒した場合は 1、つまり照準のその場所から近い敵となってる。

return
 呼び出されたメインのルーチンへ戻る。

 
 
オートモード

if jx1<x and x<jx2 and jy1<y and y<jy2 : autof=(autof+1)\2 : break
 クリック発動の時に、座標が自機の位置ならオートモードの on off切り替え。

 autof=(autof+1)\2の\(HSPエディタのソース表示では ¥ の表示)は、割った数の余りを求める記号で、この場合実効事に autofの値は 0 1 0 1 0 ・・・となる。
 つまり\3なら、0 1 2 0 1 2と変化する。

if autof=0 : cx=double(x) : cy=double(y) : break ; マニュアルの場合
 マニュアルモードの時は照準の座標、cx cyを、マウス座標 x yにする。

if nd<csp : cx=ex(esn) : cy=ey(esn) : break
 オートモードの時、照準と標的の距離が照準のスピードより近いなら、照準の座標は標的の座標で置き換えるだけで良い。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です