格ゲーコマンド入力判定
格ゲーコマンド入力判定
Fighting game command detection
http://geoquake.jp/blog/2009/05/20/fightinggame/
Stick = arrow keys or numpad
Punch button = Space, Ctrl, Z
Kick button = Shift, N, M, X
/**
* Copyright k0rin ( http://wonderfl.net/user/k0rin )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/rqt5
*/
// 格ゲーコマンド入力判定
// Fighting game command detection
// http://geoquake.jp/blog/2009/05/20/fightinggame/
//
// Stick = arrow keys or numpad
// Punch button = Space, Ctrl, Z
// Kick button = Shift, N, M, X
//
package {
import flash.display.*;
import flash.events.*;
import flash.geom.*;
import flash.text.*;
[SWF(width = "465", height = "465", frameRate = "50")]
public class Main extends Sprite {
private const SCREEN_W:int = 465;
private const SCREEN_H:int = 465;
private var input:FightingGameInput = new FightingGameInput(stage);
private var technique:TextField = new TextField();
private var techniqueTicks:int = 0;
private var controlPanel:ControlPanel;
public function Main() {
// インストラクションを描画
// 0-7 = 矢印(右から時計回り)
// P = 「P」
// K = 「K」
// + = 「+」
// T = 「溜め」
// R = 「連打」
// H = 「離す」
// N = 「ニュートラル」
print("210+P");
print("021+P");
print("234+K");
print("44");
gotoRight();
print("4T0+P");
print("PKT H");
print("PR");
addChild(new Bitmap(instruction));
controlPanel = new ControlPanel();
controlPanel.x = SCREEN_W / 2;
controlPanel.y = 400;
addChild(controlPanel);
technique.defaultTextFormat = new TextFormat("_ゴシック", 50, 0x00000000, true);
technique.autoSize = TextFieldAutoSize.CENTER;
technique.selectable = false;
technique.x = 30;
technique.y = 210;
technique.width = SCREEN_W - 30 * 2;
addChild(technique);
addEventListener(Event.ENTER_FRAME, enterFrameHandler);
}
private function enterFrameHandler(event:Event):void {
input.update();
controlPanel.setState(input.getStick(),
input.isDown(FightingGameInput.BUTTON_K),
input.isDown(FightingGameInput.BUTTON_P));
function showTechnique(name:String):void {
technique.text = name;
techniqueTicks = 40;
}
// 必殺技チェック!
// checkCommand(判定フレーム数, コマンドスクリプト)
if (input.checkCommand(20, "2 1 0P")) { // 0-7(右から時計回り)でレバーの状態。0P=右にレバーが入った状態でパンチボタンが押し下げられた
showTechnique("著作権ッ!");
input.clearBuffer();
} else
if (input.checkCommand(20, "0 2 1P")) {
showTechnique("肖像権ッ!");
input.clearBuffer();
} else
if (input.checkCommand(20, "2 3 4K")) {
showTechnique("則巻千兵衛脚ッ!"); // なんかもうほんとごめんなさい
input.clearBuffer();
} else
if (input.checkCommand(10, "N 4 N 4")) { // N=ニュートラル
showTechnique("バックステップ");
input.clearBuffer();
} else
if (input.checkCommand(25, "4x20 0P")) { // 4x20=レバー左が20フレーム
showTechnique("溜めコマンド");
input.clearBuffer();
} else
if (input.checkCommand(25, "PdKdx20 PuKu")) { // Pd=パンチボタンが下がっている状態、Pu=同じく上がっている状態
showTechnique("溜めコマンド2");
input.clearBuffer();
} else
if (input.checkCommand(30, "P P P P P")) {
showTechnique("打つべし!打つべし!");
//input.clearBuffer();
} else
if (input.checkCommand(255, "N 6 N 6 N 2 N 2 N 4 N 0 N 4 N 0 N K P")) {
showTechnique("コナミコマンド");
input.clearBuffer();
} else
if (input.isPressed(FightingGameInput.BUTTON_P)) {
showTechnique("パンチ");
} else
if (input.isPressed(FightingGameInput.BUTTON_K)) {
showTechnique("キック");
}
if (techniqueTicks > 0) {
techniqueTicks--;
technique.visible = true;
} else {
technique.visible = false;
}
}
// インストラクションの描画
private var instruction:BitmapData = new BitmapData(SCREEN_W, SCREEN_H, false, 0xFF9020);
private var line:int = 33;
private var instX:int = 33;
private function print(s:String):void {
var x:int = instX;
var arrow:Shape = new Shape();
var g:Graphics = arrow.graphics;
g.clear();
g.beginFill(0x000000);
g.drawPath(Vector.<int>([1, 2, 2, 2, 2, 2, 2, 2]),
Vector.<Number>([
-15, -5, 0, -5, 0, -15, 15, 0, 0, 15, 0, 5, -15, 5, -15, -5
])
);
g.endFill();
var matrix:Matrix = new Matrix();
var textField:TextField = new TextField();
textField.defaultTextFormat = new TextFormat("_sans", 34, 0x00000000, true);
textField.autoSize = TextFieldAutoSize.LEFT;
// ボーダーを表示するとレイアウトが確認しやすい。
//textField.border = true;
for (var i:int = 0; i < s.length; i++) {
switch (s.charAt(i)) {
case "P":
case "K":
case "+":
case " ":
textField.text = s.charAt(i);
break;
case "T":
textField.text = "溜め";
break;
case "R":
textField.text = "連打";
break;
case "H":
textField.text = "離す";
break;
case "N":
textField.text = "ニュートラル";
break;
default:
// 矢印
matrix.identity();
matrix.rotate(Number(s.charAt(i)) * Math.PI / 4);
matrix.translate(x + 15, line + 15);
instruction.draw(arrow, matrix);
x += 35;
continue;
}
matrix.identity();
matrix.translate(x, line - 6);
instruction.draw(textField, matrix);
x += textField.width;
}
line += 40;
}
private function gotoRight():void {
instX = SCREEN_W / 2;
line = 33;
}
}
}
import flash.display.*;
import flash.events.*;
import flash.geom.*;
import flash.ui.Keyboard;
class ControlPanel extends Sprite {
private var stickBall:Shape = new Shape();
private var stickPole:Shape = new Shape();
private var buttons:Vector.<Shape> = new Vector.<Shape>();
private const STICK_X:int = -70;
private const STICK_Y:int = -80;
private const BUTTON_Y:Array = [ -43, -53 ];
function ControlPanel() {
var g:Graphics = graphics;
g.beginFill(0x1E2C2F);
g.drawRect(-200, -85, 400, 90);
g.endFill();
g.beginFill(0x000000);
g.drawEllipse(-90, -60, 40, 30);
g.endFill();
g.beginFill(0x01792B);
g.drawEllipse(0, -55, 40, 30);
g.drawEllipse(45, -65, 40, 30);
g.endFill();
g = stickBall.graphics;
var matrix:Matrix = new Matrix();
matrix.createGradientBox(40, 40, 0, -23, -25);
g.beginGradientFill(GradientType.RADIAL, [0xFFFFFF, 0x00BD51, 0x01692B], null, [10, 30, 230], matrix);
g.drawCircle(0, 0, 20);
g.endFill();
for (var i:int = 0; i < 2; i++) {
var button:Shape = new Shape();
g = button.graphics;
matrix.createGradientBox(34, 24, 0, -17, -13);
g.beginGradientFill(GradientType.RADIAL, [0x01A03C, 0x01A032, 0x20B040], null, [0, 245, 250], matrix);
g.drawEllipse( -17, -12, 34, 24);
g.endFill();
g.beginFill(0xFFFFFF);
g.endFill();
buttons.push(button);
}
addChild(stickPole);
stickBall.x = STICK_X;
stickBall.y = STICK_Y;
addChild(stickBall);
buttons[0].x = 20;
buttons[0].y = BUTTON_Y[0];
addChild(buttons[0]);
buttons[1].x = 65;
buttons[1].y = BUTTON_Y[1];
addChild(buttons[1]);
}
public function setState(stick:int, button0:Boolean, button1:Boolean):void {
var sx:Number = STICK_X;
var sy:Number = STICK_Y;
if (stick != GameInput.STICK_NEUTRAL) {
var angle:Number = stick * Math.PI / 4;
sx = STICK_X + Math.cos(angle) * 10;
sy = STICK_Y + Math.sin(angle) * 10 + 1;
}
stickBall.x = sx;
stickBall.y = sy;
var g:Graphics = stickPole.graphics;
g.clear();
g.lineStyle(8, 0x556666);
g.moveTo(-70, -45);
g.lineTo(sx, sy);
buttons[0].y = button0 ? BUTTON_Y[0] + 3 : BUTTON_Y[0];
buttons[1].y = button1 ? BUTTON_Y[1] + 3 : BUTTON_Y[1];
}
}
// ゲーム用入力クラス(つくりかけ)
//
// キーボードの状態を毎フレームのupdate()で仮想ボタンの状態に整理し、
// 仮想ボタンの上下左右をgetStick()でスティック方向に整理という流れです。
// スティック方向は0=右から時計回りで、8(STICK_NEUTRAL)でニュートラルになっています。
// これはベクトルに変換する際便利なためです(テンキー配列も直感的なので迷う……)。
//
// isDown()、isPressed()、isReleased()で、ボタンの押下状態、押した瞬間、離した瞬間を検出します。
// getButtonTicks()でボタンが押されているフレーム数を取得します。
//
// 仮想ボタン
class Button {
public static const UP:int = 0;
public static const DOWN:int = 1;
public static const LEFT:int = 2;
public static const RIGHT:int = 3;
public static const A:int = 4;
public static const B:int = 5;
public static const MAX:int = 6;
}
class GameInput {
public static const STICK_NEUTRAL:int = 8;
private var stage:Stage;
private const KEY_CODE_MAX:int = 256;
private var keyState:Vector.<Boolean> = new Vector.<Boolean>(KEY_CODE_MAX, true);
private var buttonState:Vector.<Boolean> = new Vector.<Boolean>(Button.MAX, true);
private var buttonTicks:Vector.<int> = new Vector.<int>(Button.MAX, true);
function GameInput(stage:Stage) {
this.stage = stage;
clearKeyState();
for (var i:int = 0; i < Button.MAX; i++) {
buttonState[i] = false;
buttonTicks[i] = 0;
}
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
stage.addEventListener(Event.DEACTIVATE, deactivateHandler);
}
private function clearKeyState():void {
for (var i:int = 0; i < KEY_CODE_MAX; i++) {
keyState[i] = false;
}
}
private function keyDownHandler(event:KeyboardEvent):void {
keyState[event.keyCode] = true;
}
private function keyUpHandler(event:KeyboardEvent):void {
keyState[event.keyCode] = false;
}
private function deactivateHandler(event:Event):void {
clearKeyState();
}
private const TO_STICK:Array = [ -1, 3, 2, 1, 4, STICK_NEUTRAL, 0, 5, 6, 7 ];
public function getStick():int {
// 一旦テンキー配列にする。
var d:int = 5;
if (isDown(Button.UP)) {
d += 3;
} else if (isDown(Button.DOWN)) {
d -= 3;
}
if (isDown(Button.RIGHT)) {
d += 1;
} else if (isDown(Button.LEFT)) {
d -= 1;
}
// テンキー配列を時計回り配列に変換
return TO_STICK[d];
}
public function getButtonTicks(button:int):int {
return buttonTicks[button];
}
public function isDown(button:int):Boolean {
return (getButtonTicks(button) > 0);
}
public function isPressed(button:int):Boolean {
return (getButtonTicks(button) == 1);
}
public function isReleased(button:int):Boolean {
return (getButtonTicks(button) < 0);
}
public function update():void {
buttonState[Button.UP] = keyState[Keyboard.UP] || keyState[Keyboard.NUMPAD_8];
buttonState[Button.DOWN] = keyState[Keyboard.DOWN] || keyState[Keyboard.NUMPAD_2];
buttonState[Button.LEFT] = keyState[Keyboard.LEFT] || keyState[Keyboard.NUMPAD_4];
buttonState[Button.RIGHT] = keyState[Keyboard.RIGHT] || keyState[Keyboard.NUMPAD_6];
buttonState[Button.A] = keyState[Keyboard.SPACE] || keyState[Keyboard.CONTROL] || keyState[90];
buttonState[Button.B] = keyState[Keyboard.SHIFT] || keyState[78] || keyState[77] || keyState[88];
for (var i:int = 0; i < Button.MAX; i++) {
if (buttonState[i]) {
buttonTicks[i]++;
} else {
if (buttonTicks[i] > 0) {
buttonTicks[i] = -buttonTicks[i];
} else if (buttonTicks[i] < 0) {
buttonTicks[i] = 0;
}
}
}
}
}
// GameInputクラスに格ゲーコマンド判定を付加
class FightingGameInput extends GameInput {
// ボタン別名
public static const BUTTON_P:int = Button.A;
public static const BUTTON_K:int = Button.B;
// 入力状態を溜め込むリングバッファ
private const BUFFER_SIZE:int = 256; // 2の乗数
private var buffer:Vector.<uint> = new Vector.<uint>(BUFFER_SIZE, true);
private var cursor:int = 0;
// bufferの要素の構造
// |76543210|76543210|76543210|76543210|
// | | |KKKKPPPP|<stick >|
// / |
// +--------+-------+--+----+
// |released|pressed|up|down|
// +--------+-------+--+----+
function FightingGameInput(stage:Stage) {
super(stage);
clearBuffer();
}
public function clearBuffer():void {
for (var i:int = 0; i < BUFFER_SIZE; i++) {
buffer[i] = STICK_NEUTRAL;
}
}
public override function update():void {
super.update();
buffer[cursor] = getStick()
| (int(isDown(BUTTON_P)) << 8)
| (int(!isDown(BUTTON_P)) << 9)
| (int(isPressed(BUTTON_P)) << 10)
| (int(isReleased(BUTTON_P)) << 11)
| (int(isDown(BUTTON_K)) << 12)
| (int(!isDown(BUTTON_K)) << 13)
| (int(isPressed(BUTTON_K)) << 14)
| (int(isReleased(BUTTON_K)) << 15);
if (++cursor >= BUFFER_SIZE) {
cursor = 0;
}
}
public function checkCommand(ticks:int, command:String):Boolean {
if (ticks >= BUFFER_SIZE) {
throw Error("too long ticks");
}
// メモ
// p cursor
// v v
// 88833333333882288
var p:int = cursor - ticks;
function search(state:uint, mask:uint, length:int):Boolean {
var matchLength:int = 0;
for (; (p & (BUFFER_SIZE - 1)) != cursor; p++) {
if ((buffer[p & (BUFFER_SIZE - 1)] & mask) == state) {
matchLength++;
if (matchLength >= length) {
p++;
return true;
}
} else {
matchLength = 0;
}
}
// バッファ末端までマッチしなかった。
return false;
}
var rex:RegExp = /([0-8NPKudpr]+)(x\d+)?/g;
for (var result:Object = rex.exec(command); result; result = rex.exec(command)) {
var length:int = result[2] ? result[2].substring(1) : 1;
var state:uint = 0x0000;
var mask:uint = 0x0000;
var r:RegExp = /([0-8N]|[PK])([udpr])?/g;
for (var button:Object = r.exec(result[1]); button; button = r.exec(result[1])) {
var shift:int = 2;
switch (button[2]) {
case "d": // down
shift = 0;
break;
case "u": // up
shift = 1;
break;
case "p": // pressed
shift = 2;
break;
case "r": // released
shift = 3;
break;
}
if (button[1] == "N") {
button[1] = "8";
}
switch (button[1]) {
case "P":
shift += 8;
mask |= 1 << shift;
state |= 1 << shift;
break;
case "K":
shift += 12;
mask |= 1 << shift;
state |= 1 << shift;
break;
default:
mask |= 0xFF;
state |= button[1];
break;
}
}
if (!search(state, mask, length)) {
return false;
}
}
return true;
}
}