経緯
- VPSのOSをUbuntu 20.04でクリーンインストール
- vsftpsをインストール
- ufwで20, 21ポートを開放
- クライアントからVPSへアクセスするとエラーになる
ufwのステータスは以下。
AngularでUndo Redo機能を実装する、と題して複数のポストをこちらに投稿していたのだけれど、コード表示が上手に行えなかったのでZenn.devに以下投稿した。
const rayCast = new Ray(origin=new Vector(400, 400), direction=new Vector(-0.8, convertYUp(1)));
const obb = new RotatingRectangle(pos=new Vector(150, 200), size=new Vector(80, 50));
const vectorFromOriginOfRayToCenterOfRect = obb.pos.subtract(rayCast.origin);


const t1 = (lengthBetweenOriginOfRayAndCenterOfRectProjectedOnXAxis + obb.size.x) / lengthOfDirectionOfRayProjectedOnXAxis; const t2 = (lengthBetweenOriginOfRayAndCenterOfRectProjectedOnXAxis - obb.size.x) / lengthOfDirectionOfRayProjectedOnXAxis; const t3 = (lengthBetweenOriginOfRayAndCenterOfRectProjectedOnYAxis + obb.size.y) / lengthOfDirectionOfRayProjectedOnYAxis; const t4 = (lengthBetweenOriginOfRayAndCenterOfRectProjectedOnYAxis - obb.size.y) / lengthOfDirectionOfRayProjectedOnYAxis;
// TestController.cs
public async Task<TestDto> GetTest(int id, CancellationToken cancellationToken){ // A
var dto = new TestDto();
var data = TestDataFacade.GetDataAsync(id, cancellationToken); // B
dto.Salary = CalculateSalary(); // C
dto.Data = await data; // D
return dto;
}
// TestDataFacade.cs
public async Task<IEnumerable<TestItem>> GetDataAsync(int id, CancellationToken cancellationToken){
using (var con = _connectionProvider.GetEditableConnection()){
await con.OpenAsync(cancellationToken); // A
return await con.QueryAsync<TestItem>( // B
new CommandDefinition( // C
"select * from Tests where id=@id", new { id }, cancellationToken: cancellationToken));
}
}
var currentXHR = $.ajax({
url: "/api/Test/GetTest/1",
type: "GET"
});
currentXHR.done(function (data) {
// 取得したデータで何かする
}).fail(function (jqXHR, textStatus, errorThrown) {
if (errorThrown === "abort") {
alert('処理を中断しました。');
} else {
alert('処理中にエラーが発生しました。エラー内容:' + errorThrown);
}
}).always(function () {
currentXHR = null;
});
// MEMO : 非同期処理中にページ遷移した場合はリクエストをキャンセルする
$(window).unload(function() {
if (currentXHR) {
currentXHR.abort();
}
});
// MEMO : 非同期処理中にキャンセルボタンを押下した場合はリクエストをキャンセルする
$('button.cancel').click(function() {
if (currentXHR) {
currentXHR.abort();
}
});
ページ遷移時に非同期リクエストが処理されている場合は明示的にリクエストをabortしないとキャンセルされない。また重たい処理などを実行する場合はキャンセル処理を実装しておくと不要な処理の実行を防ぐことによってパフォーマンス向上が見込めるので(エンドユーザがレスポンスを待たずにページ遷移してしまったとき用などに)積極的にキャンセル処理は実装したほうが良いだろう。class CancelledTaskBugWorkaroundMessageHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
// キャンセルされた場合はエラー内容を空っぽにして送り返す
if (cancellationToken.IsCancellationRequested)
{
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
return response;
}
}
protected void Application_Start(object sender, EventArgs e) {
GlobalConfiguration.Configure(config => {
// ...省略...
// abort処理で発生する例外出力を抑制するための処理
config.MessageHandlers.Add(new CancelledTaskBugWorkaroundMessageHandler());
});
}
[Subject("Test")]
public class TestSpec
{
static TestSpec()
{
// MEMO : ユニットテストでlog4netを使用するための設定
var consoleAppender = new log4net.Appender.ConsoleAppender { Layout = new SimpleLayout() };
BasicConfigurator.Configure(consoleAppender);
}
// 以下省略
}

var arc = d3.svg.arc()
.innerRadius(50)
.outerRadius(radius);
var pie = d3.layout.pie()
.value(function (d) { return d.sales; })
.sort(null) // ソートはしない
.startAngle(-Math.PI / 3) // -60度から
.endAngle(Math.PI / 3); // 60度まで
var color = d3.scale.category10();
var container = svg.selectAll('g')
.data(pie(data))
.enter()
.append('g');
// 色をつけて弧を描画する
container.append('path')
.style("fill", function(d, i) { return color(i); })
.attr('d', arc);
// 真ん中に文字を描画する
container.append('text')
.attr('class', 'label value')
.attr('transform', function(d, i) {
return 'translate(' + arc.centroid(d) + ')';
})
.attr('text-anchor', 'middle')
.text(function (d, i) { return d.value; });
// 外側に文字を描画する
container.append('text')
.attr('class', 'label name')
.attr('transform', function(d, i) {
// 弧の外側を取得。パイチャートでは90度(Math.PI/2)の位置が0度計算になっているので注意。それなのでxは-する。yはSVGだと向きが逆になるので+する
var labelR = radius + 20
, x = labelR * Math.cos((d.endAngle - d.startAngle) / 2 + d.startAngle - Math.PI/2)
, y = -labelR * Math.sin((d.endAngle - d.startAngle) / 2 + d.startAngle + Math.PI/2);
return 'translate(' + x + ','+ y + ')';
})
.attr("text-anchor", function(d) {
var angle = (d.endAngle + d.startAngle) / 2;
return 0 < angle && angle < Math.PI ? "start" : "end";
})
.style("fill", function(d, i) { return color(i); })
.text(function(d, i) { return data[i].busho; });

// x軸は日付。y軸にくっつけたくなかったので30からはじめている
var x = d3.time.scale().nice()
.domain(d3.extent(data, function (d) { return d.date; }))
.range([30, width]);
var yearMonthFormat = d3.time.format("%Y/%m");
var xAxis = d3.svg.axis().scale(x)
.orient('bottom')
.tickFormat(yearMonthFormat);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0, ' + height + ')')
.call(xAxis);
// ツールチップ
var focus = svg.append('g')
.attr('class', 'focus');
focus.append('rect')
.attr({ x: -10, y: -25, width: 105, height: 50 });
// circle
focus.append('circle')
.attr('class', 'profit')
.attr({ r: 5, cy: -15 });
focus.append('circle')
.attr('class', 'sales')
.attr({ r: 5 });
focus.append('circle')
.attr('class', 'expense')
.attr({ r: 5, cy: 15 });
// text
focus.append('text')
.attr('class', 'profit')
.style('text-anchor', 'end')
.attr({ x: 90, y: -15, dy: '.35em' });
focus.append('text')
.attr('class', 'sales')
.style('text-anchor', 'end')
.attr({ x: 90, dy: '.35em' });
focus.append('text')
.attr('class', 'expense')
.style('text-anchor', 'end')
.attr({ x: 90, y: 15, dy: '.35em' });
// オーバーレイ
svg.append('rect')
.attr('class', 'overlay')
.attr({ width: width, height: height })
.on('mouseover', function () { focus.style('display', 'block'); })
.on('mouseout', function () { focus.style('display', 'none'); })
.on('mousemove', mousemove);
var bisectDate = d3.bisector(function (d) { return d.date; }).left
, formatValue = d3.format(",.2f")
, formatCurrency = function (d) { return formatValue(d) + '億円'; };
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0])
, i = bisectDate(data, x0, 1);
if (i < data.length) {
var d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0; // 一番近いデータを取得
focus.attr('transform', 'translate(' + (x(d.date)) + ',' + y(d.sales) + ')');
focus.select('text.profit').text(formatCurrency(d.profit));
focus.select('text.sales').text(formatCurrency(d.sales));
focus.select('text.expense').text(formatCurrency(d.sales - d.profit));
}
}

var data = [
{ name:'Branch A', sales: 5400, forecast: 7000 },
{ name:'Branch B', sales: 2800, forecast: 4500 },
{ name:'Branch C', sales: 3600, forecast: 3300 },
{ name:'Branch D', sales: 1700, forecast: 4700 },
{ name:'Branch E', sales: 2200, forecast: 5500 }
];
var margin = { top: 50, right: 100, bottom: 40, left: 40 }
, width = 800 - margin.left - margin.right
, height = 300 - margin.top - margin.bottom
, svg = d3.select('#item-container').append('div').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// x軸は文字列なのでordinalにするのとrangeRoundPointsでちょうど良い場所を取得する
var x = d3.scale.ordinal()
.domain(data.map(function (d) { return d.name; }))
.rangeRoundPoints([0, width], 0.5);
var xAxis = d3.svg.axis().scale(x)
.orient('bottom');
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0, ' + height + ')')
.call(xAxis);
// x軸のTickの文字を斜めにする
svg.selectAll(".x text")
.attr("transform", function (d) {
return "translate(" + this.getBBox().height * -2 + "," + this.getBBox().height + ")rotate(-45)";
});
// 一つ目のy軸。売り上げと予想から最大値を取得する
var y = d3.scale.linear().nice()
.domain([0, d3.max(data, function (d) { return Math.max(d.sales, d.forecast); })])
.range([height, 0]);
var yAxis = d3.svg.axis().scale(y)
.orient('left')
.ticks(5);
var gy = svg.append('g')
.attr('class', 'y axis')
.call(yAxis);
// テキストを表示する
gy.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', 16)
.style('text-anchor', 'end')
.text('万円');
// 二つ目のy軸。売り上げと予想の割合を表示する
var y2 = d3.scale.linear().nice()
.domain([0, 150])
.range([height, 0]);
var yAxis2 = d3.svg.axis().scale(y2)
.orient('right');
var gy2 = svg.append('g')
.attr('transform', 'translate(' + width + ',0)')
.attr('class', 'y2 axis')
.call(yAxis2);
// 横線を描画する
gy2.selectAll('g').filter(function(d) { return d; })
.append('line')
.attr('x1', -width)
.attr('x2', 0)
.classed('minor', true);
// テキストを表示する
gy2.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', -10)
.style('text-anchor', 'end')
.text('%');
// ラインチャート。売り上げと予想の割合を表示する
var linePlaceHolder = svg.append('g');
var percentLine = d3.svg.line()
.x(function (d) { return x(d.name); })
.y(function (d) { return y2((d.sales / d.forecast) * 100); });
linePlaceHolder.append("path")
.datum(data)
.attr("class", "line percent")
.attr("d", percentLine);
// バーチャート。売り上げと予想を表示する
// bars
var bar = svg.selectAll('.bar')
.data(data)
.enter()
.append('g')
.attr('class', 'bar')
.attr('transform', function(d) { return 'translate(' + (x.rangeBand() / 2 + x(d.name)) + ',0)'; });
// 売り上げ
bar.append('rect')
.attr('class', 'sales')
.attr('x', -10)
.attr('y', function (d) { return y(d.sales); })
.attr('height', function (d) { return height - y(d.sales); })
.attr('width', 10);
// 予想
bar.append('rect')
.attr('class', 'forecast')
.attr('y', function (d) { return y(d.forecast); })
.attr('height', function (d) { return height - y(d.forecast); })
.attr('width', 10);







// Knockoutjs周りは省略
var canvas = document.getElementById("canvas")
, c = canvas.getContext("2d");
function drawRader(){
var points = viewModel.points()
, eachRad = (Math.PI*2) / points.length
, i
, accumRad = Math.PI/2
, radius = 150
, center = 200
, sin
, cos
, endOfAxisPt
, firstPt
, prevPt
, currentPt;
c.clearRect(0,0,400,400);
for(i = 0; i < points.length; i ++){
c.strokeStyle = "#ffa500";
c.beginPath();
sin = Math.sin(accumRad);
cos = Math.cos(accumRad);
endOfAxisPt = {
x: center + (cos * radius),
y: center - (sin * radius)
};
accumRad += eachRad;
c.moveTo(center, center);
c.lineTo(endOfAxisPt.x, endOfAxisPt.y);
c.stroke();
c.fillStyle = "#00A0E9";
c.fillText(points[i].text(),
endOfAxisPt.x - 10,
endOfAxisPt.y - (sin * 20));
c.strokeStyle = "#00A0E9";
c.beginPath();
currentPt = {
x: center + (cos * (radius * points[i].ratio())),
y: center - (sin * (radius * points[i].ratio()))
};
if(prevPt){
c.moveTo(prevPt.x, prevPt.y);
c.lineTo(currentPt.x, currentPt.y);
}
else
firstPt = currentPt;
prevPt = currentPt;
c.stroke();
}
c.beginPath();
c.moveTo(prevPt.x, prevPt.y);
c.lineTo(firstPt.x, firstPt.y);
c.stroke();
}
drawRader();
drawRader()の冒頭でviewModel.points()と取得しているのはKnockoutjs用のViewModelが内部的に保持しているObservableArrayだ(Knockoutjsを知らない人は配列のようなものと考えてもらって構わない)。描画の処理は単純で配列のアイテム数で360度を割ってその角度ごとに軸線を描画し、前後の軸線上の点と点を結んでいくだけ。
b(t) = p0*(1-t)^3 + p1*3t(1-t)^2 + p2*3(1-t)t^2 + p3*t^3p0は始点、p3が終点、p1、p2はそれぞれ制御点の1と2だ。これに時間であるtを0~1の間で指定すると曲線上の任意の点が取得できる。
b'(t) = 3(1-t)^2(p1-p0) + 6(1-t)t(p2-p1) + 3t^2(p3-p2)p0~p3とtの説明は前述したものと同じだ。
function b0(t) { return Math.pow(1 - t, 3); };
function b1(t) { return t * Math.pow(1 - t, 2) * 3; };
function b2(t) { return (1 - t) * Math.pow(t, 2) * 3; };
function b3(t) { return Math.pow(t, 3); };
function getPointOnBezier(points, t){
var x = points[0].x * b0(t) + points[1].x * b1(t) + points[2].x * b2(t) + points[3].x * b3(t)
, y = points[0].y * b0(t) + points[1].y * b1(t) + points[2].y * b2(t) + points[3].y * b3(t);
return new Point(x, y);
};
function bd0(t) { return 3 * Math.pow(1-t, 2); };
function bd1(t) { return 6 * (1-t) * t; };
function bd2(t) { return 3 * Math.pow(t, 2); };
function getSlopeOfTangentLine(points, t){
var x = bd0(t) * (points[1].x-points[0].x) + bd1(t) * (points[2].x-points[1].x) + bd2(t) * (points[3].x-points[2].x)
, y = bd0(t) * (points[1].y-points[0].y) + bd1(t) * (points[2].y-points[1].y) + bd2(t) * (points[3].y-points[2].y);
return y/x;
};
function calculateDividingPoints(){
var points = []
, bi = bezierInfo
, i;
for(i = 0.1; i <= 1.0; i += 0.1){
divideCurveRecursively(bi, points, i-0.1, i);
}
points.push(bi.points[bi.points.length-1]);
bi.dividingPoints = points;
};
function divideCurveRecursively(bi, points, t1, t2){
var prevD = getSlopeOfTangentLine(bi.points, t1)
, currentD = getSlopeOfTangentLine(bi.points, t2)
, dDiff = Math.abs(Math.abs(prevD)-Math.abs(currentD))
, mDiff = t2-t1
, middleT = t1 + mDiff / 2;
if(mDiff > 0.05 && (sign(prevD) !== sign(currentD) || dDiff > 0.2)){
divideCurveRecursively(bi, points, t1, middleT);
divideCurveRecursively(bi, points, middleT, t2);
}
else
points.push(getPointOnBezier(bi.points, t1));
};
function onStraightLine(pt){
var bi = bezierInfo
, i;
for(i = 1; i < bi.dividingPoints.length; i ++){
var p1 = bi.dividingPoints[i-1]
, p2 = bi.dividingPoints[i]
, trueDistance = calculateDistance(p1, p2)
, testDistance1 = calculateDistance(p1, pt)
, testDistance2 = calculateDistance(pt, p2);
if(Math.abs(trueDistance - (testDistance1 + testDistance2)) < 0.2)
return true;
}
return false;
};
private Font _font;
@Override
protected void onCreateResources() {
_font = FontFactory.create(this.getFontManager(), this.getTextureManager(), 256, 256, Typeface.create(Typeface.DEFAULT, Typeface.BOLD), 24, Color.WHITE_ARGB_PACKED_INT);
_font.load();
}
@Override
protected Scene onCreateScene() {
_scene = new Scene();
Text text = new Text(100, 10, _font, "Click on the characters!", this.getVertexBufferObjectManager());
text.setColor(new Color(0, 0, 0)); // 色ごとにFontを用意するのは難儀なので、WhiteでFontを読み込み、後から色を設定する
_scene.attachChild(text);
}
文字を表示するのは一見簡単そうなのだけど柔軟さに欠けるので中々に面倒くさい。第一にフォント読み込み時に色を指定するので文字色を後から任意に変更できない。また色毎にフォントを読み込むのは難儀だ。それなので白色フォントで読み込んだ後にEntityの色変更メソッドを通して任意の色へと設定している。第二にフォント読み込み時の領域確保に指定する値の最適値が判然としない。文字の大きさと文字数から判断するしかないのだけど直感的でないので良く分からない。多量の文字を表示するなら大きくなければならないし、かといって無駄に領域を確保するとメモリを無駄に圧迫するのでやっかいな部分だ。最後にこれはバグなのだけど、コンストラクタで指定した文字列よりも長い文字列をsetTextで後から設定すると例外が発生する。そういう状況が起こりえる場合の回避策はコンストラクタで長い文字列を渡しておくしかないだろう。final ButtonSprite ship = new ButtonSprite(50, 50, _shipRegion.getTextureRegion(0), this.getVertexBufferObjectManager());
final AnimatedSprite shipAnimation = new AnimatedSprite(10, 10, _shipRegion, this.getVertexBufferObjectManager());
shipAnimation.animate(50);
shipAnimation.setPosition(50, 50); // コンストラクタで指定しているけれど、このコードがないとRotationの挙動がおかしい
ship.setOnClickListener( new ButtonSprite.OnClickListener(){
@Override
public void onClick(ButtonSprite pButtonSprite, float pTouchAreaLocalX, float pTouchAreaLocalY) {
_scene.detachChild(pButtonSprite);
shipAnimation.setRotation(0);
SequenceEntityModifier shipModifier =
new SequenceEntityModifier(
new IEntityModifier.IEntityModifierListener() {
@Override
public void onModifierStarted(IModifier<ientity> pModifier, IEntity pItem) {
}
@Override
public void onModifierFinished(final IModifier<ientity> pEntityModifier, final IEntity pEntity) {
MainActivity.this.mEngine.runOnUpdateThread(new Runnable() {
@Override
public void run() {
_scene.attachChild(ship);
_scene.detachChild(shipAnimation);
pEntity.unregisterEntityModifier((IEntityModifier)pEntityModifier);
}
});
}
},
new RotationByModifier(0.5f, 90)
,new MoveModifier(1.0f, 50, CAMERA_WIDTH+50, 50, 50, EaseExponentialOut.getInstance())
,new RotationByModifier(0.1f, 180)
,new MoveModifier(1.0f, CAMERA_WIDTH+50, 50, 50, 50, EaseExponentialOut.getInstance())
,new RotationByModifier(0.5f, 90)
);
shipAnimation.registerEntityModifier(shipModifier);
_scene.attachChild(shipAnimation);
}
});
_scene.registerTouchArea(ship);
_scene.attachChild(ship);
EntityModifier周りだけを抜き取ると余計にわけが分からなくなりそうなのでそこらへんをごっそりと持ってきているけれど、注目して欲しいのは10行目から。SequenceEntityModifierを指定すると順番にEntityの値を変更していくことができる。12行目から27行目までは開始と終了のイベントハンドラーでそれ以降が値の変更を記述している。Tweenjsと同様の働きをする。同時に2つ以上の値を変更したい場合は下記のようにParallelEntityModifierを使用する。// 省略 new RotationByModifier(0.5f, 90), new ParallelEntityModifier( new ScaleModifier(3, 0.5f, 5), new RotationByModifier(3, 90) ), new RotationByModifier(0.5f, 90), // 省略またTweenjsと同様にどのように値を変更させるかEase関数を使って指定ができる。Ease関数の種類は下記を参照してほしい。
private BitmapTextureAtlas _charactersAtlas;
private TiledTextureRegion _shipRegion;
private TiledTextureRegion _squareRegion;
@Override
protected void onCreateResources() {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("gfx/");
_charactersAtlas = new BitmapTextureAtlas(this.getTextureManager(), 1024, 256); // 指定のサイズで領域を作る
_shipRegion = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(_charactersAtlas, this, "BlueShip.png", 0, 0, 6, 1);
_squareRegion = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(_charactersAtlas, this, "WalkingSquare.png", 0, 100, 5, 1);
_charactersAtlas.load();
}
BitmapTextureAtlasのコンストラクタで指定する数値は2のべき乗である必要がある。つまり2,4,8,16,32,64,128,256...というわけだ。上記のコードが実行されると下図のような様子になる。
final AnimatedSprite shipAnimation = new AnimatedSprite(10, 10, _shipRegion, this.getVertexBufferObjectManager()); shipAnimation.animate(50); //アニメーション速度 _scene.attachChild(shipAnimation);また下記のようにTiledTextureRegionから指定のフレームを使ってスプライトオブジェクトを作ることも可能。
final Sprite ship = new Sprite(50, 50, _shipRegion.getTextureRegion(0), this.getVertexBufferObjectManager());
private BitmapTextureAtlas _backgroundAtlas;
private TextureRegion _backgroundRegion;
@Override
protected void onCreateResources() {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("gfx/");
_backgroundAtlas = new BitmapTextureAtlas(this.getTextureManager(), 512, 512);
_backgroundRegion = BitmapTextureAtlasTextureRegionFactory.createFromAsset(_backgroundAtlas, this, "Background02.png", 0, 0);
_backgroundAtlas.load();
}
@Override
protected Scene onCreateScene() {
_scene = new Scene();
Sprite bg = new Sprite(0, 0, _backgroundRegion, this.getVertexBufferObjectManager());
bg.setScaleCenter(0, 0);
bg.setScale(CAMERA_WIDTH/400f, CAMERA_HEIGHT/300f);
_scene.attachChild(bg);
}





public class MainActivity extends SimpleBaseGameActivity {
public static final int CAMERA_WIDTH = 480;
public static final int CAMERA_HEIGHT = 320;
private Camera _camera;
@Override
public EngineOptions onCreateEngineOptions() {
_camera = new Camera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT);
return new EngineOptions(true, ScreenOrientation.LANDSCAPE_SENSOR, new RatioResolutionPolicy(CAMERA_WIDTH, CAMERA_HEIGHT), _camera);
}
@Override
protected void onCreateResources() {
BitmapTextureAtlasTextureRegionFactory.setAssetBasePath("gfx/");
// 画像などのResourceを読み込む
}
@Override
protected Scene onCreateScene() {
_scene = new Scene();
_scene.setTouchAreaBindingOnActionDownEnabled(true);
// this.mEngine.registerUpdateHandler()に登録するオブジェクトがいわゆるゲームのメインループになる
// _sceneに画像をぺたぺた貼り付けていく
return _scene;
}
}
上記はもっともシンプルな構成のActivityだ。ちなみに上記を実行しても何も表示されない。AndEngineはSimpleBaseGameActivityから派生したActivityひとつだけで動作する。開発者はSceneオブジェクトにぺたぺたと画像を貼り付けつつ、this.mEngine.registerUpdateHandler(Obj)で登録したObjのonUpdateが定期的に呼ばれるのでそこで当たり判定などを行いつつ開発をすすめていく。ただ基本は上記の3つのメソッドで、エンジンを作り、リソースを読み込み、シーンを使って描画するという流れだ。
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.main);
WebView webview = (WebView)findViewById(R.id.webview);
webview.getSettings().setJavaScriptEnabled(true);
webview.loadUrl("file:///android_asset/index.html");
}
}
いたってシンプルである。が、順に見ていこう。class GamePlatform
constructor: (@stage, width, height, contentManager)->
# 背景の用意
background = new createjs.Bitmap contentManager.background
background.setTransform 0, 0, width/400.0, height/300.0 # 背景画像は400*300でCanvasサイズと合わないので拡大する
@stage.addChild background
# 文字列表示
text = new createjs.Text "Click on the characters!", "bold 24px Meiryo", "#000"
text.x = (width/2)-(text.getMeasuredWidth()/2)
text.y = 10
# text.align = 'right' # align指定はこの方法で
@stage.addChild text
# スプライトシートの設定
shipSpriteSheet = new createjs.SpriteSheet
images: [contentManager.ship]
frames:
width:100 # 1フレームの幅を指定
height:100 # 1フレームの高さを指定
regX:50 # 中心点(拡大縮小、回転、移動用の)を指定
regY:50 #
animations:
move:
frames: [0, 1, 2, 3, 4, 5] # アニメーションフレームの指定
frequency: 10 # フレームごとの速度
# スプライトアニメーションの設定
shipAnimation = new createjs.BitmapAnimation shipSpriteSheet
shipAnimation.x = shipAnimation.y = 100
# スプライトシートから指定のフレームの画像を抜き取ってBitmapオブジェクトを作成する
ship = new createjs.Bitmap createjs.SpriteSheetUtils.extractFrame shipSpriteSheet, 0
ship.regX = ship.regY = 50 # 中心点をスプライトアニメーションに合わせる
ship.x = ship.y = 100
ship.onClick = =>
shipAnimation.rotation = 90
@stage.addChild shipAnimation
shipAnimation.gotoAndPlay "move"
@stage.removeChild ship
# オブジェクトアニメーションを設定する
createjs.Tween.get(shipAnimation)
.to({x:width+100, scaleX:2, scaleY:2}, 1000, createjs.Ease.cubicIn)
.to({rotation:270})
.to({x:100, scaleX:1, scaleY:1}, 1000, createjs.Ease.cubicOut)
.to({rotation:0}, 500)
.wait(500)
.call =>
@stage.addChild ship
@stage.removeChild shipAnimation
shipAnimation.stop()
@stage.addChild ship
sqSpriteSheet = new createjs.SpriteSheet
images: [contentManager.square]
frames:
width:100
height:130
regX:50
regY:65
animations:
walk:
frames: [0, 1, 2, 3, 4]
frequency: 20
sqAnimation = new createjs.BitmapAnimation sqSpriteSheet
sqAnimation.x = 100
sqAnimation.y = 250
sq = new createjs.Bitmap createjs.SpriteSheetUtils.extractFrame sqSpriteSheet, 0
sq.regX = 50
sq.regY = 65
sq.x = 100
sq.y = 250
sq.onClick = =>
@stage.addChild sqAnimation
sqAnimation.gotoAndPlay "walk"
@stage.removeChild sq
createjs.Tween.get(sqAnimation)
.to({x:width+100}, 1000, createjs.Ease.quadInOut)
.to({scaleX:-1}) # Y軸を中心にフリップする
.to({x:100}, 1000, createjs.Ease.bounceOut)
.to({scaleX:1})
.wait(500)
.call =>
@stage.addChild sq
@stage.removeChild sqAnimation
sqAnimation.stop()
@stage.addChild sq
# Containerを使用してオブジェクトをまとめて管理したりもできる
# container = new createjs.Container()
# player = new createjs.Bitmap somethingImage
# container.addChild player
# player2 = new createjs.Bitmap somethingImage2
# container.addChild player2
# @stage.addChild container
@fps = new createjs.Text "0 fps", "bold 14px Arial", "#000"
@fps.x = @fps.y = 10
@stage.addChild @fps
startGame: ->
createjs.Ticker.userRAF = true
createjs.Ticker.setFPS 60
createjs.Ticker.addListener this
tick: =>
#
# キー入力にあわせたキャラクタの動作やあたり判定などをここで行う
#
@fps.text = (Math.round createjs.Ticker.getMeasuredFPS())+" fps"
@stage.update()
window.GamePlatform = GamePlatform
・4行目、画像からBitmapオブジェクトを生成する