Stage1.きらきら星
「打鍵練習 × ドットキャラ」
・メロディーの押すべきキーとタイミングを、ドットキャラで可視化してみた
操作方法
・キャラの居る位置のキーを押すだけ
/**
* Copyright o_healer ( http://wonderfl.net/user/o_healer )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/w6VQ
*/
/*
「打鍵練習 × ドットキャラ」
・メロディーの押すべきキーとタイミングを、ドットキャラで可視化してみた
操作方法
・キャラの居る位置のキーを押すだけ
問題
・やはり「キー入力→音の再生」のラグがあるのはつらい
・しかし現状のFlashではこれが限界っぽい
・PCのキーボードをピアノの鍵盤に見立てるのも限界がある
・横の長さが足りないし、同時押しがサポートされているかも不確実
やりたかったことなど
・複数のキャラを出すことで、和音や複数のパートを再現
・スラーはジャンプでなくローリングで再現
・背景はもうちょっとちゃんとピアノっぽく
その他
・一応、MUSICの中をいじることで楽譜は変えられる
*/
package {
import flash.display.*;
import flash.events.*;
import flash.filters.*;
import flash.geom.*;
import flash.net.*;
import flash.system.*;
import flash.text.*;
import flash.ui.*;
//SiON (SE)
import org.si.sion.*;
import org.si.sion.utils.SiONPresetVoice;
import org.si.sion.sequencer.SiMMLTrack;
[SWF(width="465", height="465", frameRate="60", backgroundColor="0xFFFFFF")]
public class GameMain extends Sprite {
//==Const==
//画像URL
//*
static public const URL_BLOCK:String = "http://assets.wonderfl.net/images/related_images/e/ef/ef08/ef08de22b6acdcba7aa0aadd10c9787736441dea";
static public const URL_ONPU:String = "http://assets.wonderfl.net/images/related_images/d/d0/d027/d027688e8a475887996757d4ad0db28c0bcd54ed";
static public const URL_PLAYER:String = "http://assets.wonderfl.net/images/related_images/5/53/531b/531b465571ac568a02661d045842c74076ae9774";
/*/
static public const URL_BLOCK:String = "Block.png";
static public const URL_ONPU:String = "OnpuBlock.png";
static public const URL_PLAYER:String = "Player.png";
//*/
//画面サイズ
static public const STAGE_W:int = 465;
static public const STAGE_H:int = 465;
//パネルサイズ
static public const PANEL_LEN:int = 32;
//音階
static public const C:int = 60;//ド
static public const D:int = 62;//レ
static public const E:int = 64;//ミ
static public const F:int = 65;//ファ
static public const G:int = 67;//ソ
static public const A:int = 69;//ラ
static public const B:int = 71;//シ
static public const o:int = 0;//
static public const NOTE_LIST:Array = [C, D, E, F, G, A, B];
static public const NOTE_NUM:int = NOTE_LIST.length;
//キー入力
static public const KEY_A:int = 65;
static public const KEY_S:int = 83;
static public const KEY_D:int = 68;
static public const KEY_F:int = 70;
static public const KEY_G:int = 71;
static public const KEY_H:int = 72;
static public const KEY_J:int = 74;
//SiON
static public const PRESET_VOICE:SiONPresetVoice = new SiONPresetVoice();
//キー入力 => 音階 のマッピング
static public const KEY_to_NOTE:Object = {
//Keycode:Note
65 : C,//KEY_A : C,//A => ド
83 : D,//KEY_S : D,//S => レ
68 : E,//KEY_D : E,//D => ミ
70 : F,//KEY_F : F,//F => ファ
71 : G,//KEY_G : G,//G => ソ
72 : A,//KEY_H : A,//H => ラ
74 : B,//KEY_J : B,//J => シ
hoge:0//dummy
};
//楽譜
static public const MUSIC:Array = [
//Stage1. きらきら星
[C,C,G,G,A,A,G,o,F,F,E,E,D,D,C,o,G,G,F,F,E,E,D,o,G,G,F,F,E,E,D,o,C,C,G,G,A,A,G,o,F,F,E,E,D,D,C,o],
];
//BPM
static public const BPM:int = 120;//楽譜と合わせた方が良いか
//先行入力の受付時間
static public const ACCEPT_TIME:Number = 0.1;
//音階→位置のマッピング
static public const NOTE_to_POS:Object = {
60 : STAGE_W/2 - PANEL_LEN*3,//C : ~,
62 : STAGE_W/2 - PANEL_LEN*2,//D : ~,
64 : STAGE_W/2 - PANEL_LEN*1,//E : ~,
65 : STAGE_W/2 - PANEL_LEN*0,//F : ~,
67 : STAGE_W/2 + PANEL_LEN*1,//G : ~,
69 : STAGE_W/2 + PANEL_LEN*2,//A : ~,
71 : STAGE_W/2 + PANEL_LEN*3,//B : ~,
hoge:0//dummy
};
//地面の高さ
static public const GROUND_Y:int = STAGE_H * 0.6;
//重力
static public const GRAVITY:Number = 1000.0;
//==Var==
//背景
public var m_BG:BitmapData = new BitmapData(STAGE_W, STAGE_H, false, 0x000000);
//音符ブロック
public var m_OnpuBlocks:Array = new Array(NOTE_NUM);
public var m_OnpuBitmapData:BitmapData = new BitmapData(PANEL_LEN, PANEL_LEN, true, 0x00000000);
//プレイヤー
public var m_Player:Sprite = new Sprite();
public var m_PlayerBitmapData:BitmapData = new BitmapData(PANEL_LEN, PANEL_LEN, true, 0x00000000);
//プレイヤーの移動管理
public var m_SrcNote:int = MUSIC[0][0];
public var m_DstNote:int = MUSIC[0][0];
public var m_MoveTime:Number = 0.0;//移動にかかる時間
public var m_Timer:Number = 0.0;//移動開始からの時間
public var m_VY:Number = 0.0;
public var m_DstRot:Number = 0.0;
//楽譜管理
public var m_MusicIndex:int = 0;//選ばれた楽譜の番号
public var m_NoteIter:int = 0;//楽譜用イテレータ
//SiON
public var m_Driver:SiONDriver = new SiONDriver();
public var m_Voice:SiONVoice = PRESET_VOICE["valsound.piano8"];//default.sin
//Input
public var m_InputFlags:Object = {};
//ロード管理カウンタ
public var m_RestUnloadNum:int = 0;
//==Function==
//Init
public function GameMain():void{
var i:int;
var bmp:Bitmap;
//BG
{
addChild(new Bitmap(m_BG));
}
//Onpu Block
{
for(i = 0; i < NOTE_NUM; i++){
var note:int = NOTE_LIST[i];
m_OnpuBlocks[i] = new Bitmap(new BitmapData(PANEL_LEN, PANEL_LEN, false, 0x000000));
m_OnpuBlocks[i].x = NOTE_to_POS[note] - PANEL_LEN/2;
m_OnpuBlocks[i].y = GROUND_Y + PANEL_LEN/2;
addChild(m_OnpuBlocks[i]);
}
}
//Other OnpuBlocks
{
//だいぶ雑だが、他のところも音符で埋めてみる
var x:int;
var y:int;
//左
for(x = m_OnpuBlocks[0].x - PANEL_LEN; x > -PANEL_LEN; x -= PANEL_LEN){
bmp = new Bitmap(m_OnpuBitmapData);
bmp.x = x;
bmp.y = GROUND_Y + PANEL_LEN/2;
addChild(bmp);
}
//右
for(x = m_OnpuBlocks[NOTE_NUM-1].x + PANEL_LEN; x < STAGE_W+PANEL_LEN; x += PANEL_LEN){
bmp = new Bitmap(m_OnpuBitmapData);
bmp.x = x;
bmp.y = GROUND_Y + PANEL_LEN/2;
addChild(bmp);
}
}
//Player
{
//Graphic
{
bmp = new Bitmap(m_PlayerBitmapData);
bmp.x = -PANEL_LEN/2;
bmp.y = -PANEL_LEN/2;
m_Player.addChild(bmp);
}
//Regist
{
addChild(m_Player);
}
}
//SiON
{
//m_Driver
{
//ボリューム設定
m_Driver.volume = 0.3;
//ストリーミング開始
m_Driver.play();
}
}
//Input
{
addEventListener(
Event.ADDED_TO_STAGE,//ステージに追加されたら
function registInput(e:Event):void{
//キー入力を見る
stage.addEventListener(KeyboardEvent.KEY_DOWN, OnKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, OnKeyUp);
//Once
removeEventListener(Event.ADDED_TO_STAGE, registInput);
}
);
}
//Update
{
addEventListener(Event.ENTER_FRAME, Update);
//位置設定のため一回更新
Update();
}
//ロード開始
{
const LoadFunc:Function = function(in_URL:String, in_OnLoad:Function):void{
m_RestUnloadNum++;
var loader:Loader = new Loader();
loader.load(new URLRequest(in_URL), new LoaderContext(true));//画像のロードを開始して
loader.contentLoaderInfo.addEventListener(
Event.COMPLETE,//ロードが完了したら
function(e:Event):void{
m_RestUnloadNum--;
in_OnLoad(loader.content);//初期化に入る
}
);
}
LoadFunc(URL_BLOCK, OnLoadEnd_Block);
LoadFunc(URL_ONPU, OnLoadEnd_Onpu);
LoadFunc(URL_PLAYER, OnLoadEnd_Player);
}
}
//Load
public function OnLoadEnd_Block(in_Graphic:DisplayObject):void{
//できればもうちょっとちゃんとした背景にしたいところだが
var bd_bg:BitmapData;
{
bd_bg = new BitmapData(PANEL_LEN, PANEL_LEN, false, 0x000000);
bd_bg.draw(in_Graphic);
}
var pos:Point = new Point(0,0);
for(var y:int = 0; y < STAGE_H; y += PANEL_LEN){
pos.y = y;
for(var x:int = 0; x < STAGE_W; x += PANEL_LEN){
pos.x = x;
m_BG.copyPixels(bd_bg, bd_bg.rect, pos);
}
}
//下の方は暗くしてみる
var r:Number = 0.5;
m_BG.colorTransform(new Rectangle(0,GROUND_Y + PANEL_LEN/2, STAGE_W,STAGE_H), new ColorTransform(r,r,r));
//黒鍵っぽいのも描いてみる
{
var w:int = PANEL_LEN*0.9;
var rect:Rectangle = new Rectangle(0,GROUND_Y, w,PANEL_LEN);
for(var i:int = 0; i < NOTE_NUM-1; i++){
var SrcNote:int = NOTE_LIST[i];
var DstNote:int = NOTE_LIST[i+1];
if(SrcNote+1 < DstNote){//1以上離れているところに黒鍵を描く
rect.x = NOTE_to_POS[SrcNote] + PANEL_LEN/2 - w/2;
m_BG.fillRect(rect, 0xFF111111);
}
}
}
}
public function OnLoadEnd_Onpu(in_Graphic:DisplayObject):void{
const TEXT_LIST:Array = [
"A",
"S",
"D",
"F",
"G",
"H",
"J",
];
var mtx:Matrix = new Matrix();
for(var i:int = 0; i < NOTE_NUM; i++){
var bmd:BitmapData = m_OnpuBlocks[i].bitmapData;
//基本ブロック描画
{
bmd.draw(in_Graphic);
}
//キーボードの対応を明示する
{
var text:TextField = new TextField();
{
text.selectable = false;
text.autoSize = TextFieldAutoSize.LEFT;
text.defaultTextFormat = new TextFormat('Verdana', PANEL_LEN*3/4, 0xFFFF88, true);
text.text = TEXT_LIST[i];
text.filters = [new GlowFilter(0x440000)];
}
//mtx
{
mtx.tx = PANEL_LEN/2 - text.textWidth/2 -3;
mtx.ty = PANEL_LEN/2 - text.textHeight/2 -2;
}
bmd.draw(text, mtx);
}
}
{
m_OnpuBitmapData.draw(in_Graphic);
}
}
public function OnLoadEnd_Player(in_Graphic:DisplayObject):void{
m_PlayerBitmapData.draw(in_Graphic);
}
//Update
public function Update(e:Event=null):void{
//var DeltaTime:Number = 1.0 / stage.frameRate;
var DeltaTime:Number = 1.0 / 60.0;
//Move
{
var Ratio:Number;
{
m_Timer += DeltaTime;
if(m_Timer < m_MoveTime){
Ratio = m_Timer / m_MoveTime;
}else{
m_Timer = m_MoveTime;
Ratio = 1.0;
}
}
//X
{
var SrcX:int = NOTE_to_POS[m_SrcNote];
var DstX:int = NOTE_to_POS[m_DstNote];
m_Player.x = Lerp(SrcX, DstX, Ratio);
}
//Y
{
m_Player.y = (GROUND_Y) + (m_VY * m_Timer) + (0.5 * GRAVITY * m_Timer * m_Timer);
}
//Rot
{
m_Player.rotation = m_DstRot * Ratio;
}
}
}
//Input
public const len:int = 4;//再生する長さ
public const delay:int = 0;//遅延
public const quant:int = 0;//同期
private function OnKeyDown(event:KeyboardEvent):void{
PlaySE(event.keyCode, true);
}
private function OnKeyUp(event:KeyboardEvent):void{
PlaySE(event.keyCode, false);
}
private function PlaySE(in_KeyCode:int, in_Flag:Boolean):void{
//ロードが完了していなければ何もしない
{
if(m_RestUnloadNum > 0){
return;
}
}
var Flag_Old:* = m_InputFlags[in_KeyCode];
if(Flag_Old == null){
Flag_Old = m_InputFlags[in_KeyCode] = false;
}
//変更がないなら何もしない
if(Flag_Old == in_Flag){
return;
}
m_InputFlags[in_KeyCode] = in_Flag;
if(in_Flag){
var note:* = KEY_to_NOTE[in_KeyCode];
if(note != null){
//音を鳴らす
{
m_Driver.noteOn(note as int, m_Voice, len, delay, quant);
}
//*
//プレイヤーが居れば打ち上げる
{
var Music:Array = MUSIC[m_MusicIndex];
if(m_NoteIter < Music.length){//まだ次があって
if(Music[m_NoteIter] == note){//次に鳴らすべき音で
if(m_MoveTime - ACCEPT_TIME < m_Timer){//プレイヤーが居るようなら
//移動開始
//前回の到着位置が、今回の出発位置となる
m_SrcNote = m_DstNote;
//BMPから長さを決定
var Time:Number = 60.0 / BPM;
//そして移動時間として採用
m_MoveTime = Time;
//タイマーはリセット
m_Timer = 0.0;
//伸ばさないなら回転もしない
m_DstRot = 0.0;
for(;;){
++m_NoteIter;
if(m_NoteIter >= Music.length){
break;
}
var next_note:int = Music[m_NoteIter];
if(next_note == o){
//移動時間を伸ばす
m_MoveTime += Time;
//上の音を鳴らす処理はこれを反映すべきかもしれない
//回転量も増やす
m_DstRot += 360;
}else{
m_DstNote = next_note;//次に鳴らすべき音が次の到着位置となる
break;
}
}
//速度計算
m_VY = -0.5 * m_MoveTime * GRAVITY;
//向きによる制御
var SrcX:int = NOTE_to_POS[m_SrcNote];
var DstX:int = NOTE_to_POS[m_DstNote];
if(SrcX < DstX){
//向き変更
m_Player.scaleX = 1;
}
if(SrcX > DstX){
//向き変更
m_Player.scaleX = -1;
}
//回転の方向も変更
m_DstRot = m_Player.scaleX * m_DstRot;
}
}
}
}
//*/
}
}
//音符ブロックの上下動
{
//これまた面倒なので、ひとまずデジタルに動かす(補間しない)
for(var i:int = 0; i < NOTE_NUM; i++){//該当音符ブロックへのマッピングがないのでサーチ(これも配列アクセスにしたい)
if(NOTE_LIST[i] == KEY_to_NOTE[in_KeyCode]){
if(in_Flag){
m_OnpuBlocks[i].y = GROUND_Y + PANEL_LEN/2 - PANEL_LEN * 1/4;
}else{
//ここの位置は定数化しとくべきか(初期化とかぶる)
m_OnpuBlocks[i].y = GROUND_Y + PANEL_LEN/2;
}
break;
}
}
}
}
public function Lerp(in_Src:Number, in_Dst:Number, in_Ratio:Number):Number{
return (in_Src * (1 - in_Ratio)) + (in_Dst * in_Ratio);
}
}
}