WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

C/C++でネットワークプログラミングをしてみよう(1)

目的

筆者の専門分野でもあり、最近は色んなネットワークのライブラリーーcurl、photonのようなーーが多いので、手作りのクライアントやサーバも少なくなっています。 そのために問題が生じてもどのような問題なのか分からず、ライブラリーのパッチをいつまでも待たないといけなくなったり、効率の悪い回避策を入れたりすることになります。
しかしながら、オープンソースが今のように発展していない一昔は、こういうライブラリは自前で作ってきたものです。
ライブラリーを使うのに慣れている現在のエンジニアさんには難しく思えるのかも知れませんが、実際にポイントを押さえていれば、そこまで難しいものではありません。
この連載によって、基本的なソケット通信のやり方を覚え、実戦で応用ができるならばいいと思っています。

対象

 ・ソケットプログラミングに興味のある方
 ・Photonなどのネットワークフレームワークやライブラリの内部構造が気になる方
 ・ハッキング?に興味のある方

内容

1話 TCP/IPにおけるOSIの参照モデルについて
2話 ソケットプログラミングを実際にやってみよう
3話 プロトコルを実装してみよう(HTTP編)
4話 OpenSSLをつかったSSL通信をしてみよう
5話 PROXYサーバを作ってみよう
6話 NAT超えをしてみよう(P2P通信)

1話 TCP/IPにおけるOSIの参照モデルについて

概要

ネットワークプログラミングに興味のある方は、一度は耳にしたことがあると思います。
このOSI参照モデルというのは、ISOで策定されたコンピュータの通信機能において、必要であるべき機能を階層化したものです。これは、厳密な規約ではありませんが、ネットワークの実装時に推奨されるモデルです。
今は、ほとんどの通信がTCP/IPプロトコル上で動いているので、こういったモデルの意味も薄れていますが、一昔はZModem、AppleTalk、NetBEUI・・・など無数な通信プロトコルがあったためです。
このモデルの階層は7階層でできており、これをOSI’s 7Layersと言います。
本章では、TCP/IPプロトコルにおける参照モデルを中心に説明していこうと思っています。

階層は以下の通り

  1. ハードウェア層
  2. データリンク層
  3. ネットワーク層
  4. トランスポート層
  5. セッション層
  6. プレゼンテーション層
  7. アプリケーション層

既にご存知の方々もいらっしゃると思いますが、初めての方はこれだけでは何を意味するのか分からないと思うので、これに関して、わかりやすく説明をしたいと思います。

1. ハードウェア層

ある人が面白いことを考えました。
パソコンとパソコンをつないで通信をさしょうと思ったわけです。
そうするためには、送受信ができる装置が必要となります。
この装置にはモデム、ISDN、ケーブルモデム、イーサネットカードなどがありますが、一旦ここではイーサネットカードのみを想定します。
この装置をパソコンに入れてパソコンとパソコン同士をつなぎます。
これがハードウェア層となります。

2. データリンク層

さて、パソコンとパソコンを繋いだ状態で、AからBへメッセージを送ろうとします。
イーサネットカードに何かのデータを書き込みと読み取りができないと行けません。
そこで、イーサネットカードのバッファーにデータを書き込み・読み込むモジュールを作成しました。保護モードで動くOSでは、デバイスドライバーに当てはまります。
このドライバーの書き込みや読み込みの機能がデータリンク層*1になります。
基本、データリンク層ではハードウェア層にリクエストを投げることになります。

3. ネットワーク層

ただ、これだけでは面白くありません。今度は複数のパソコンに繋いで見たくなりました。
そうするためには、送り先を指定しなければなりません。
なのでパソコンに住所をつけることにしました。
その一つの規約*2がip protocol (internet protocol)です。
ipアドレスというのは、ipヘッダーの識別子になります。
そういうわけで、デバイスの方にipprotocolを実装し、転送するようにしました。
送信パケットにipヘッダーをつけて、送り先のアドレスと一緒に送信するような感じです。
この場合、送信先までに送るための中継機器が必要となります。一旦、その機器をルーターとしておきます。
一方で、受信する側のパソコンでは、自分のipアドレスのみ*3を受信できるようになります。
このデバイスドライバーのipプロトコルの実装と中継機器がネットワーク層になります。
ちなみにpingはこのネットワーク層までで動いています。
ネットワーク層ではデータリンク層にリクエストを投げるようになります。

4. トランスポート層

これで複数のパソコンで通信ができるようになりました。
でも、もう少し面白いことがしたいところです。
パソコン同士でチャットをしたり、ファイルのやりとりを同時にやりたい。
でも、ipプロトコルだけでは、どれがチャットのメッセージでどれがファイルのメッセージなのか区別がつきません。
そこで工夫された一つの規約がTCPプロトコルです。
ポート番号を設け、デバイスに届くデータを振り分ける仕組みです。
TCPプロトコルをデバイスに実装しました。*4
なので、データを送る際にTCPヘッダー+実データにして、ネットワーク層へ要求を出すことになります。
この機能がネットワーク層とトランスポート層に当てはまります。
このトランスポート層では、データのロスを防ぐために、データが失われた場合は、再要求を送信したりするような機能も実装してあります。
トランスポート層では基本的にネットワーク層にリクエストを投げるようになります。

5. セッション層

これで、パソコン同士が通信ができるようになりました。
ただ、LANケーブルはノイズがあり、データが失われたり、接続が不安定なことが多いです。
その接続の回復などの処理ーー通信の再送とは別ーーを行う機能を追加しており、この処理を行うのがセッション層であります。*5

TCP/IPにおいて、ハードウェア層からセッション層までは全てハードウェアとデバイスドライバーの内部の話になります。
実際にこの階層で綺麗に分かれているような実装になっているかどうかは不明ですが。。。
プレゼンテーション層というのは、アプリ層の方で、プレゼンテーション層へ要求を出す際に形式的な変換ーー例えば、文字コードの変換ーーのような事をしますが、TCP/IPにおいて、プレゼンテーション層というのは、デバイスドライバーの方で対応することが難しいです。
なぜなら、TCP/IPの上に色んなプロトコルが存在するために一概に処理をすることができないためです。
なので、TCP/IPのネットワークプログラミングの話は主にプレゼンテーション層とアプリ層の話になります。
抽象的で分かりづらいとは思いますが、実際にソケットプログラミングをすると分かってくるかと思います。

最後に次回の話のための何を使ってソケット通信のプログラムをするかを説明したいと思います。

Burkey Socket

ネットワークデバイスでは、以下のようなインターフェースを提供しています。
これらはシステムコールで、以下のシステムコールの形式をBurkey Socketと言います。BSD Unixに初めて実装され、今はほとんどのOSで採用されています。

  • socket
    openと同じようにdescriptorを生成します。ファイルと同じくdescriptorはi-nodeとして管理されます。
  • close
    socketで生成されたdescriptorを解放します。
  • send/write
    データを送信します。ファイルと同じくwriteもサポートしていますが、ioctlなどで調整が必要となります。なのでsendを使う方をお勧めします。
  • recv/read
    データを受信します。
  • ioctl/fctl
    ソケットの受信をnon-block状態にしたり、送信時のバッファーを設定したり、ブロードキャストの設定をしたり細かい設定を行うためのインターフェース。 OSの実装の仕方によって、どれをどう使うかになります。ほぼどちらも使えますが、OSによっては片方しか使えなかったりします。
  • select
    設定したdescriptorから何か変化があるまで、スレッドをスリープ状態にします。

ストリーム(TCPのコネクション型時の追加のシステムコール)

  • connect
    リモートと接続*6を計ります。
  • bind
    ポート番号とdescriptorを結びつけます。
  • listen
    クライアントのconnectを待ちます。
  • accept
    listenから接続が確立したセッションに対してdescriptorを生成します。

ソケットプログラミングにおいて、使うのはは上記のシステムコールのみとなります。
今回は文章が多く退屈だったかと思いますが、次回では簡単なクライアントとサーバのプログラムを作成したいと思います。

*1:厳密には、イーサネットカードのヘッダーも含まれる

*2:ipアドレスなしでどのように通信を行うのか疑問に思う方もいると思うので説明すると、そういう場合はMACアドレスやドメイン名などで通信することになります。ただし、ローカル内での通信までしかできない

*3:今のスウィッチングハブは、違うipアドレスの機器には転送しないが、その前まではつながっている全ての機器に送信していたの>で、各機器の方で違うipアドレスのパケットを弾く処理を行なっていた。

*4:デバイスドライバーの実装はここから複雑になっていく

*5:TCP/IPプロトコルに置いては、セッション層はデバイスの方ではそんなに処理がない。AppleTalkのような通信プロトコルでは、かなり多くの機能が入る

*6:ハンドシェーク

SpriteStudioでアニメを作った後でも簡単データ整理

こんにちは、デザイナーの木村です。

本日は、少しプログラムを書き換えるだけで簡単にデータを整理できる方法をまとめたいと思います。
プログラムを書き換えると言っても、名称をちょっといじるだけなのでプログラムの知識は一切入りません。


例えばこんな風に、SpriteStudioのフォルダ構成を変更したい場合

変更点1:「common」フォルダ(pngとssceの素材入り)を「kaetai_sozai」にフォルダ名変更

変更点2:ssaeとsseeを新規フォルダ「kaetai_anime」にまとめる

     f:id:wp_harumura:20170804205550p:plain

変更してそのままプロジェクトを開くと、当然エラーでae,ce,eeは読み込めなくなってしまいます。
これを解消するのに、プログラムを書き換えるのが有効です。

解消するにあたり、まずプロジェクトをメモ帳などで開きます。
するとこの様な画面になります。
f:id:wp_harumura:20170804210942p:plain

次に開いた画面を一番下までスクロールしてください。
プログラムの下部に、ssce名,ssae名,ssee名と見慣れた名前が出てくると思います。
ここを書き換えてpjで読み取れる様にします。

【before】
f:id:wp_harumura:20170804212910p:plain

四角で囲った部分の名称を変更します。

【after】
f:id:wp_harumura:20170804213752p:plain

変更点は以下の通りです。

赤枠:ssceが入ったフォルダ名を「common」から「kaetai_sozai」に名称を変更したので、文字列を「common」から「kaetai_sozai」に変更
青枠:ssaeを「kaetai_anime」のフォルダに入れたため、文字列に「kaetai_anime/」を追加
緑枠:sseeを同じく「kaetai_anime」のフォルダに入れたため、文字列に「kaetai_anime/」を追加
紫枠:上記のルールに乗っ取り、文字列を変更

保存して終了です。
ただ、このままだとpngは読み取ってくれません。
pngを読み取れる様にするには、ssceとssaeもメモ帳などで開き、名称を変更する必要があります。
しかし、ssaeが複数存在したりアニメーション上でたくさんのレイヤーを使用している場合、書き換えがかなり発生してしまうため、pngに関してはプログラムを書き換える方法はお勧めしません。
アナログな方法ですが、SpriteStudio上でセルマップの「参照イメージ変更」を行うことをお勧めします。(大体の場合は素材はアトラス化されていると思うので)

これらの作業を行うことで、png以外はプロジェクトを開いてもエラーを吐き出さずにデータを開くことができます。

アニメーションを制作した後にフォルダ構成を変える際に、seやceなどを読み込み直すよりも時間をかなり短縮できると思いますので、お困りの方はぜひご活用ください。

Controller Emulatorを利用してDaydream実機なしでDaydream対応アプリを開発する方法

R&D事業部の近藤です。
今回はDaydreamのコントローラーのエミューレーターの導入方法を紹介します。

Daydreamにはレーターポインターのようなコントローラーが付属します。
WiiリモコンのようにポインターをオブジェクトやボタンなどのUIをクリックしてアプリを操作します。
しかしDaydreamは日本国内ではまだ販売されておらず入手するのはやや困難です。

そこで、Google VR SDKにはコントローラーをエミュレートして、Android端末をコントローラーとして使えるようにできるようになっています。
Daydream本体を持っていなくても開発ができるようになります。

導入前の準備

Daydream導入編の記事を参考にGoogle VR SDKをインポートして、Daydreamが使えるように準備しましょう。

Unity + Daydream開発はじめの一歩(2017.6月最新版) - WonderPlanet DEVELOPER BLOG

コントローラーエミュレーターのアプリのインストール

こちらのページからエミュレーターのアプリをダウンロードできますので、Android端末にインストールしましょう。
https://developers.google.com/vr/daydream/controller-emulator

インストールしたアプリを起動するとこのような画面が表示されます。
f:id:HidehikoKondo:20170707145604p:plain

Touchpad

タッチパッドの触っている部分により方向を判別して操作します。 ダブルクリックするとクリックできます。(本物のコントローラではシングルクリック)

Appボタン

Appボタンのクリックイベントをアプリに送信します。 このイベントをトリガーにして任意の動作をつけることができる。

Homeボタン

顔が向いている方向とカメラの方向がずれているときに、このボタンを押すと向きをあわせます(リセンター)。 本物のコントローラーの場合は、クリックするとDaydreamのホーム画面に戻ります。 長押しするとリセンターします。

Unity側の設定

コントローラエミュレータのプレハブの配置

インポートしたGoogle VR SDKにコントローラーエミュレーターを利用するのに必要なプレハブが含まれています。
「GvrControllerMain」「GvrEditorEmulator」「GvrControllerPointer」の3つを下のスクリーンショットのように配置します。 「GvrControllerPointer」はplayerオブジェクトの子オブジェクトとして配置します。

f:id:HidehikoKondo:20170628124821p:plain

コントローラーの設定

Hierarchyから「GvrControllerMain」を選択して、Inspectorを開きます。
「Emulator Connection Mode」の項目で「USB」または「Wi-Fi」の接続方法を選択します。 f:id:HidehikoKondo:20170627170437p:plain

USB

PCとAndroid端末をUSBケーブルを利用して接続します。
この接続方法でコントローラーを使用するには、事前にadbのパスを通しておく必要があります。

Wi-Fi

Wi-Fiを利用してワイヤレスでコントローラーエミュレーターを使用することができます。
PCとAndroid端末を同じWi-Fiアクセスポイントに接続します。
この接続方法でコントローラーを使用するためには「EmulatorConfig.cs」をIPアドレスを設定する必要があります。
IPアドレスは、コントローラーエミュレーターのアプリの画面上に記載されているIPアドレスを設定します。
f:id:HidehikoKondo:20170627171332p:plain

実行

エディターで実行ボタンを押します。
コントローラーエミュレーターの画面上部が緑色で「Connected」と表示されたら成功です!
コントローラーの傾きに応じてUnityの画面上に表示された白色のポインターが動きます。

f:id:HidehikoKondo:20170627172011p:plain
f:id:HidehikoKondo:20170627172854p:plain

これでDaydeamの実機がない状態でコントローラーの操作をエミュレートできるようになりました。
この記事ではとりあえずポインターが動かせる状態になっただけですが
もちろんクリックやスワイプの操作に応じてアプリ上のオブジェクトやUIを操作することもできます。
そのあたりの実装方法はまた次回の記事で紹介します。

負荷見積りについて

 インフラエンジニアの河井です。
 今回は、イベント時の負荷見積りについて書いてみます。


負荷見積りとは

 負荷見積りには大きく分けて「長期的な見積り」(長期間のサービス提供により増加した取扱データ量への対応)と、「短期的な見積り」(イベントやメディア露出による突発的なアクセス増への対応)がありますが、今回は後者について話をします。

取得すべき監視項目の決定

 AWSで言えば、ELBの時間辺りリクエスト数や、EC2のCPU使用率などの中から、利用すべき項目を抜き出します。たとえば、ELBの時間辺りリクエスト数であれば、以下のようなコマンドを実行することで取得することができます。

$ LB_NAME="LBの名前"
$ aws cloudwatch get-metric-statistics \
>   --namespace "AWS/ELB" \
>   --dimension Name=LoadBalancerName,Value=${LB_NAME} \
>   --metric-name RequestCount \
>   --start-time $(date --date '2 days ago' +%FT00:00:00Z) \
>   --end-time   $(date --date '1 days ago' +%FT23:59:59Z) \
>   --period 60 \
>   --statistics Sum

 必要最低限の項目の情報のみ取得する、という考え方もありますが、現時点では、取得できる項目はできるだけ取得しておく、ただし全てを見積りに使用するわけではない。という考えで見積りをおこなっています。使用しなかった項目は、見積りと結果にずれが生じた場合の資料として使用しています。

基準線の作成

 各項目の情報を取得したあとは、必要な項目ごとに時系列に分け、傾向を取得します。以下に示すのは、複数日のLBへのリクエスト数の平均を取り、グラフにしたものです。日に数回、アクセスの高くなる場所があることがわかります。これを、見積りの基準として使用します。 f:id:kwy:20170728184044p:plain

過去の特異日との比較

 次に示すのは、過去の特異日(青色のライン)と、基準線(橙色のライン)との比較です。Y軸の値が変わってしまったため、縦に押しつぶされてしまっていますが、右側から1/3ほどのところで、青線が跳ね上がっているのが判ると思います。このタイミングでイベント(プッシュ通知や新規クエストの開放、プレゼントの配布など)が発生しています。 f:id:kwy:20170728184108p:plain

 他にも、web/APIサーバーや、RDB, NoSQL等における、CPU,メモリ、ディスク、NW負荷などを併せて確認し、高負荷時の各サーバーの挙動の変化を確認します。

イベント規模の聞き取りと負荷見積り

 負荷に対する各サーバーの挙動がわかったところで、今度はプランナーチームから、今回のイベント規模について聞き取りをおこないます。
 この時のコツは、「できるだけ楽観的な予測を出してもらうこと」です。楽観的な見積もりの結果、想定を遥かに下回る負荷で、サーバー費用が無駄にかかる、と言うのはそれはそれで問題なのですが、サービスを止めてしまうことに比べれば遥かにマシです。サービスを止めてしまうと、現在のユーザーのみでなく、将来のユーザーを得る機会も失うことになりかねませんので。
 聞き取りの結果得られた情報から、イベントの想定負荷を見積ります。

システム増強メンテナンス

 得られた想定負荷と、過去のイベント時の処理量などから、現在のシステム構成で耐えきれるかを計算し、耐えきれないと判断した場合はシステム増強メンテナンスを実施します。これは、場合によっては数十分〜数時間のサービス停止を伴うことがあります。サービス停止が発生する場合は、関係するチームに連絡し、メンテナンス日時の調整やお知らせの発行、メンテナンス後のお知らせなどについて相談します。
 メンテナンスが完了すると、もうあとは当日を待つのみです。


 いかがでしたでしょうか? 実際には、このような見積りをしていても、当日は不安に押しつぶされそうになりながらイベント開始を待っていたりするのですが、それはまた別の話。
 ということで、今回は、こんな風に負荷見積りを行っている、という雰囲気を感じていただければ幸いです。

CircleCI 2.0の正式版がリリースされたので試してみた

始めまして、サーバエンジニアの山脇です。
サーバアプリケーションのテストと開発環境へのデプロイをCircleCIに任せているのですが、最近CircleCIが2.0リリースされたのでどんなことができるのか試してみたのでその内容を書きたいと思います。

1.0と2.0の比較

まず1.0から2.0に変わった点として設定ファイルの配置場所が変わりました。
1.0ではリポジトリ直下の circle.yml に設定ファイルを配置していましたが、2.0ではリポジトリ直下から .circleci/config.yml に設定ファイルを配置するようになりました。
以下で1.0と2.0でそれぞれテストをして動かし、masterブランチの場合デプロイする例(実行するコマンドはただのechoです)を記述してみました。

1.0の書き方

machine:
  python:
    version: 3.4.2
test:
  override:
    - echo test
deployment:
  deploy:
    branch: master
    commands:
      - echo deploy

1.0ではmachineのsetupやtest、deploymentなどをそれぞれ書くようになっていました。
実行順はあらかじめ決められており、上の場合だとmachine -> test -> deploymentの順に実行されていきます。

2.0の書き方

version: 2
jobs:
  build:
    docker:
      - image: python:3.4.2
    steps:
      - checkout
  test:
    docker:
      - image: python:3.4.2
    steps:
      - checkout
      - run: echo test
  deploy:
    machine: True
    steps:
      - run: echo deploy
workflows:
  version: 2
  build_and_test:
    jobs:
      - build
      - test:
          requires:
            - build
      - deploy:
          requires:
            - test
          filters:
            branches:
              only: master

2.0では自分たちでjobsで実行するタスクを定義して、workflowsでその実行順を定義するようになりました。
実際にCircleCIを使っていた際にdeployなどで「このコマンドは何をしているんだ?」という状態に陥ったことが何度もあるのでjobごとにやることを分けるのはすごくありがたいと感じています。

2.0の設定できること

① jobsで定義するjobはmachineとdockerがあります。machineの場合VMに、dockerはdockerのコンテナに必要な環境を構築してstepsの内容を実行します。
dockerを利用した場合にCircleCIのCLIでローカルマシンでの確認もできるようなので便利です。
② workflowsのjobsで実行するjobを呼びます。ここで定義されているjobが実行されるものになります。
(jobsに定義されていないjobを呼ぼうとするともちろんエラーになります。)

version: 2
jobs:
  build:
    docker:                   # ①
      - image: python:3.4.2
    steps:
      - checkout
      - run: echo build
  test1:
    docker:
      - image: python:3.4.2
    steps:
      - checkout
      - run: python --version
  test2:
    docker:
      - image: python:2.7.13
    steps:
      - checkout
      - run: python --version
  deploy:
    machine: True             # ①
    steps:
      - run: echo deploy
workflows:
  version: 2
  build_and_test:
    jobs:                     # ②
      - build
      - test1
      - test2
      - deploy

③ jobsで実行順番を指定したい場合はrequiresにどのjobが終了したらこのjobを実行するかを定義できます。
複数のjobが終了した時に動作されたい場合はリストにjobを追加(④ )するだけです。

workflows:
  version: 2
  build_and_test:
    jobs:
      - build
      - test1
          requires:    # ③
            - build
      - test2
          requires:    # ③
            - build
      - deploy
          requires:    # ④
            - test1
            - test2

②と③、④のように実行順を指定すると左側に実行順が早いものがきます。
実行順を指定しない場合 f:id:YHiroyuki:20170725162240p:plain 実行順を指定した場合 f:id:YHiroyuki:20170725162328p:plain

⑤ 特定のブランチだけで動作させたい場合はfiltersを追加います。
特定のブランチ以外を実行したい場合はonlyignoreに変更すれば指定したブランチ以外で実行が可能です。
ブランチの正規表現を使いたい場合は/でくくって内部で正規表現がかけます。(これは1.0と同じです)

workflows:
  version: 2
  build_and_test:
    jobs:
      - build
      - test1:
          requires:
            - build
      - test2:
          requires:
            - build
      - deploy:
          requires:
            - test1
            - test2
          filters:          # ⑤ 
            branches:
              only: master

↓ブランチを指定した状態で別ブランチをプッシュした場合deployのjobはスキップされます。 f:id:YHiroyuki:20170725162917p:plain ⑥ jobの実行前に「approve」しないと次のタスクを実行しないこともできます。

workflows:
  version: 2
  build_and_test:
    jobs:
      - build
      - test1:
          requires:
            - build
      - test2:
          type: approval   # ⑥
          requires:
            - build
      - deploy:
          requires:
            - test1
            - test2
          filters:
            branches:
              only: master

approve待ちの状態(ユーザがapproveを押すまで待機します) f:id:YHiroyuki:20170725160809p:plain

まとめ
jobとworkflowsを自分で設定できるようになったので必要な機能単位で設定しやすくなった。
実行順を変更しやすいため、ちょっとした入れ替えが簡単になった。
作業前にチェックしたいjobにはapprovalをいれる。

他にもできることはたくさんあるので公式のドキュメントを確認してみてください。
https://circleci.com/docs/2.0/

Cocos2d-x の EditBox で複数行表示する

おつかれさまです。エンジニアの藤澤です。

最近 Cocos2d-x の EditBox で比較的長い文章を入力したいケースがあったのですが、標準の EditBox では複数行表示に対応していないようで、文章が見切れてしまったため少々手を加えて対応しました。今回はそのときの内容を書いてみようと思います。

この記事は Cocos2d-x v3.2 をもとに書いていますが、v3.9 からは標準で複数行に対応しているようです(泣)

f:id:wp-fujisawa:20170718173454p:plainf:id:wp-fujisawa:20170718173502p:plain

下準備

標準の EditBox に手を加えるのに抵抗がある場合、自分の project に EditBox をコピーして標準のものと使い分けるのも手だと思います。必要なファイルは以下のとおりです。

  • extensions/GUI/CCEditBox/CCEditBox.h
  • extensions/GUI/CCEditBox/CCEditBox.cpp
  • extensions/GUI/CCEditBox/CCEditBoxImpl.h
  • extensions/GUI/CCEditBox/CCEditBoxImplIOS.h
  • extensions/GUI/CCEditBox/CCEditBoxImplIOS.mm
  • extensions/GUI/CCEditBox/CCEditBoxImplAndroid.h
  • extensions/GUI/CCEditBox/CCEditBoxImplAndroid.mm

コピーしたらまず namespace を独自のものに変更します。おもに NS_CC_EXT_BEGINNS_CC_EXT_END のところを変更するのと、それに伴いコンパイルエラーになるところを直してあげればよいです。

#define getEditBoxImplIOS() ((cocos2d::extension::EditBoxImplIOS*)editBox_)

のところも忘れずに……。

つぎに、CCEditBoxImpl.h の以下の関数を、自前の EditBoxImpl を返すように変更してやります(iOS, Android とも)

extern EditBoxImpl* __createSystemEditBox(EditBox* pEditBox);

最後に、CCEditBoxImplIOS.h の CCCustomUITextField, CCEditBoxImplIOS_objc が二重定義で怒られるのでいったんコメントアウトしてオリジナルの方を見るようにしておきます(あとで変更しますが)

iOS

iOS では内部的に UITextField を使用しているため、 UITextView を使うように変更します。

- @interface CCCustomUITextField : UITextField
+ @interface CustomUITextField : UITextView

これにあわせて UITextFieldDelegateUITextViewDelegate に変更します。

- @interface CCEditBoxImplIOS_objc : NSObject <UITextFieldDelegate>
+ @interface EditBoxImplIOS_objc : NSObject <UITextViewDelegate>

また、 UITextFieldUITextView で互換性のないところも適宜変更します。

        [textField_ setTextColor:[UIColor whiteColor]];
        textField_.font = [UIFont systemFontOfSize:frameRect.size.height*2/3]; //TODO need to delete hard code here.
-         textField_.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
        textField_.backgroundColor = [UIColor clearColor];
-         textField_.borderStyle = UITextBorderStyleNone;
        textField_.delegate = self;
        textField_.hidden = true;
        textField_.returnKeyType = UIReturnKeyDefault;
-         [textField_ addTarget:self action:@selector(textChanged) forControlEvents:UIControlEventEditingChanged];
        self.editBox = editBox;
- - (BOOL)textFieldShouldBeginEditing:(UITextField *)sender
+ - (BOOL)textViewShouldBeginEditing:(UITextView *)sender
- - (BOOL)textFieldShouldEndEditing:(UITextField *)sender
+ - (BOOL)textViewShouldEndEditing:(UITextView *)sender
- - (BOOL)textField:(UITextField *) textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
- {
-     if (getEditBoxImplIOS()->getMaxLength() < 0)
-     {
-         return YES;
-     }
-     
-     NSUInteger oldLength = [textField.text length];
-     NSUInteger replacementLength = [string length];
-     NSUInteger rangeLength = range.length;
-     
-     NSUInteger newLength = oldLength - rangeLength + replacementLength;
-     
-     return newLength <= getEditBoxImplIOS()->getMaxLength();
- }
+ - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
+ {
+     if ([text isEqualToString:@"\n"])
+     {
+         [textView resignFirstResponder];
+         return NO;
+     }
+     
+     if (getEditBoxImplIOS()->getMaxLength() >= 0)
+     {
+         NSUInteger oldLength = [textView.text length];
+         NSUInteger replacementLength = [text length];
+         NSUInteger rangeLength = range.length;
+         
+         NSUInteger newLength = oldLength - rangeLength + replacementLength;
+         
+         if (newLength > getEditBoxImplIOS()->getMaxLength())
+             return NO;
+     }
+     
+     [self textChanged];
+     
+     return YES;
+ }
void EditBoxImplIOS::setPlaceHolder(const char* pText)
{
-     _systemControl.textField.placeholder = [NSString stringWithUTF8String:pText];
    _labelPlaceHolder->setString(pText);
}

このままだと EditBox の領域外をタップしてもフォーカスが外れないので、platform/ios/CCEAGLView.mm をちょっといじってやる必要があります。

- if([view isKindOfClass:NSClassFromString(@"CCCustomUITextField")])
+ if([view isKindOfClass:NSClassFromString(@"CCCustomUITextField")] || [view isKindOfClass:NSClassFromString(@"CustomUITextField")])

これで入力中のテキストが折り返し表示されるようになりましたが、編集モードを終わるとテキストがセンター寄せ & 見切れてしまいますので、表示を調整します。

    _label = Label::create();
    _label->setAnchorPoint(Vec2(0, 0.5f));
    _label->setColor(Color3B::WHITE);
    _label->setVisible(false);
+    _label->setDimensions(size.width - CC_EDIT_BOX_PADDING * 2, size.height - CC_EDIT_BOX_PADDING * 2);
    _editBox->addChild(_label, kLabelZOrder);
    
    _labelPlaceHolder = Label::create();
    // align the text vertically center
    _labelPlaceHolder->setAnchorPoint(Vec2(0, 0.5f));
    _labelPlaceHolder->setColor(Color3B::GRAY);
+    _labelPlaceHolder->setDimensions(size.width - CC_EDIT_BOX_PADDING * 2, size.height - CC_EDIT_BOX_PADDING * 2);
    _editBox->addChild(_labelPlaceHolder, kLabelZOrder);

これでいい感じに表示されるようになりました。

Android

Android のほうは入力中のテキストは標準で折り返し表示されるので、編集モード以外の表示を調整してやれば OK です。

    _label = Label::create();
    _label->setSystemFontSize(size.height-12);
    // align the text vertically center
    _label->setAnchorPoint(Vec2(0, 0.5f));
    _label->setPosition(Vec2(CC_EDIT_BOX_PADDING, size.height / 2.0f));
    _label->setColor(_colText);
+     _label->setDimensions(size.width - CC_EDIT_BOX_PADDING * 2, size.height - CC_EDIT_BOX_PADDING * 2);
    _editBox->addChild(_label);
    
    _labelPlaceHolder = Label::create();
    _labelPlaceHolder->setSystemFontSize(size.height-12);
    // align the text vertically center
    _labelPlaceHolder->setAnchorPoint(Vec2(0, 0.5f));
    _labelPlaceHolder->setPosition(Vec2(CC_EDIT_BOX_PADDING, size.height / 2.0f));
    _labelPlaceHolder->setVisible(false);
    _labelPlaceHolder->setColor(_colPlaceHolder);
+     _labelPlaceHolder->setDimensions(size.width - CC_EDIT_BOX_PADDING * 2, size.height - CC_EDIT_BOX_PADDING * 2);
    _editBox->addChild(_labelPlaceHolder);

以上です。

Unity ShurikenのSubEmitterを使った季節の風物詩

デザイナーの宮澤です。 

ですね!

といったことでUnityのshurikenを使って簡単な花火を打ち上げようと思います。

テクスチャは用意せず、主にSub Emitterを活用して、

簡単に機能紹介ができたらと思います。

 

1.花火の元を作る。

メニューのGameObjectからParticle Systemを選択します。

f:id:wp_miyazawa:20170706112333p:plain

するとScene内に花火の元(Particle System)が生成されます。

ここから花火っぽくしていきます。

 

2.打ち上がりを作る。

花火の元を準備できたので、早速打ち上げていきます。

作成した状態ですでにたくさん打ちあがっています。

f:id:wp_miyazawa:20170706142739g:plain

ですがこのままだと花火っぽくないので、

尾をひくようにしていきます。

 

まず、Hierarchy内のParticle Systemを選択し、

Sub Emittersにチェックを入れます。

f:id:wp_miyazawa:20170706115807p:plain

チェックを入れると機能がアクティブになり編集できるようになるので、

Birthの右横にある+をポチッと押します。

f:id:wp_miyazawa:20170706115954p:plain

するとSubEmitterBirthという新しいParticleSystemが生成されます。

Sub Emitterは親のParticleをEmitterとして発生するため、

親が動くと発生位置がずれていく、といった感じで

親の通ったところに残って尾を引いているようになります。 

f:id:wp_miyazawa:20170706143109g:plain

ひとまずこの状態で次にすすみます。

 

3.メインの部分を作る。

打ちあがりましたので、次は爆発させます。

前項ではSub EmitterのBirthを使用しましたが、

次はDeathを使用します。

先ほど同様、今度はSub Emitters内のDeathの右横にある+をポチッと押します。

するとSubEmitterDeathという新しいParticleSystemが生成されます。

この段階でSub Emittersの中は下図のようになると思います。

f:id:wp_miyazawa:20170706123025p:plain

 

Deathは親が消滅したタイミングで発生させる機能で、

デフォルトだと親のParticleから球状に新しいParticleが発生します。

f:id:wp_miyazawa:20170706151801g:plain

 

まとめ

以下三段階でSubEmitterを利用した花火ができあがります。

①Particle Systemを作成

②SubEmittersにチェックを入れる

③2つ"+"をポチッ

 

補足

 このままでは花火風のもので終わってしまうため、

次の手順を行うとより花火に近づけると思います。

・花火の元(Particle System)

 -Start Lifetime(寿命)、Start Speed(打ち上がる速さ)の調整

 -EmissionのRateで出る数を調整

 -Color Over Lifetimeでフェードインアウト、色味を調整

・打ちあがっている時の尾(SubEmitterBirth)

 -Color Over Lifetimeでフェードインアウト、色味を調整

 -Size Over Lifetimeで大きさの調整

・最後の爆発(SubEmitterDeath) 

 -Gravity Modifierで重力をつける

 -Color Over Lifetimeでフェードインアウト、色味を調整

 -Size Over Lifetimeで大きさの調整

f:id:wp_miyazawa:20170706142505g:plain

 

shurikenでは多彩な表現が可能です。

たくさんのパラメータが自由に設定できるので、

追々紹介できたらと思います。