20200124のGoに関する記事は5件です。

Go言語とginをWindows上で動かしてみる

はじめに

始めてqiitaで記事を書くので緊張しています...
良い記事を書くには』を参考にしていますが、至らぬ点などございましたら申し訳ありません。

環境

  • OS: windows 10 Home
  • Go: 1.13.6
  • Git: 2.25.0
  • Text Editor: Atom

インストール

Go言語、Gitのインストールは、Windows用インストーラから行います。
ginのインストールはコマンドプロンプトより行います。

  1. Go言語をダウンロード&インストール -> https://golang.org/dl/

  2. Gitをダウンロード&インストール -> https://git-scm.com/download/win
     ginをgithubよりダウンロードするのですが、その際にGitが必要になります(当たり前か...)
     僕はこれに気が付かず、時間を浪費しました。

  3. ginをダウンロード&インストール -> https://gin-gonic.com/ja/docs/quickstart/
     上記ページを参考にginのダウンロード&インストールを行います。
     
     go get -u github.com/gin-gonic/gin

動作確認

クイックスタート | Gin Web Framework』を参考にさせていただきました。
以下のプログラムを、コマンドプロンプトよりgo run test.goで実行します。

test.go
package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run() // 0.0.0.0:8080 でサーバーを立てます。
}

参考ページには『ブラウザで 0.0.0.0:8080/ping にアクセスする』と書いてありますが、なぜかつながりませんでした。
かわりに、localhost:8080/pingから接続したところ無事アクセスすることができました。

Hello, world from Gin ! をしてみる

Go / Gin で超簡単なWebアプリを作る』を参考に、先ほどのtest.goを少し書き換えます。

test2.go
package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.LoadHTMLGlob("*.html")
    r.GET("/", func(c *gin.Context) {
        c.HTML(200, "index.html", gin.H{})
    })
    r.Run() // 0.0.0.0:8080 でサーバーを立てます。
}

index.htmlを用意します。

index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>test index</title>
    </head>
    <body>
        <h1>Hello, world from Gin !</h1>
    </body>
</html>

コマンドプロンプトよりgo run test2.goで実行し、ブラウザよりlocalhost:8080へ接続します。
以下が表示されて、無事動作していることがわかりました。
image.png

さいごに

Goとginを使ったWebアプリの開発がWindows上で行えることがわかり一安心です。

また、最近はWebアプリもどきをHeroku上で動かしたりしています。
データのやり取りなんかはJavaScript+GASなんかで行っていたのですが、限界が見えてしまったためサーバ側でも処理をさせようと思ったのが、今回 Goとginを触ったきっかけです。

今回書いたプログラムをベースに使えるアプリを作っていきたいと思う所存です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GO言語とginをWindows上で動かしてみる

はじめに

始めてqiitaで記事を書くので緊張しています...
良い記事を書くには』を参考にしていますが、至らぬ点などございましたら申し訳ありません。

環境

  • OS: windows 10 Home
  • Go: 1.13.6
  • Git: 2.25.0
  • Text Editor: Atom

インストール

Go言語、Gitのインストールは、Windows用インストーラから行います。
ginのインストールはコマンドプロンプトより行います。

  1. Go言語をダウンロード&インストール -> https://golang.org/dl/

  2. Gitをダウンロード&インストール -> https://git-scm.com/download/win
     ginをgithubよりダウンロードするのですが、その際にGitが必要になります(当たり前か...)
     僕はこれに気が付かず、時間を浪費しました。

  3. ginをダウンロード&インストール -> https://gin-gonic.com/ja/docs/quickstart/
     上記ページを参考にginのダウンロード&インストールを行います。
     
     go get -u github.com/gin-gonic/gin

動作確認

クイックスタート | Gin Web Framework』を参考にさせていただきました。
以下のプログラムを、コマンドプロンプトよりgo run test.goで実行します。

test.go
package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run() // 0.0.0.0:8080 でサーバーを立てます。
}

参考ページには『ブラウザで 0.0.0.0:8080/ping にアクセスする』と書いてありますが、なぜかつながりませんでした。
かわりに、localhost:8080/pingから接続したところ無事アクセスすることができました。

Hello, world from Gin ! をしてみる

Go / Gin で超簡単なWebアプリを作る』を参考に、先ほどのtest.goを少し書き換えます。

test2.go
package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.LoadHTMLGlob("*.html")
    r.GET("/", func(c *gin.Context) {
        c.HTML(200, "index.html", gin.H{})
    })
    r.Run() // 0.0.0.0:8080 でサーバーを立てます。
}

index.htmlを用意します。

index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>test index</title>
    </head>
    <body>
        <h1>Hello, world from Gin !</h1>
    </body>
</html>

コマンドプロンプトよりgo run test2.goで実行し、ブラウザよりlocalhost:8080へ接続します。
以下が表示されて、無事動作していることがわかりました。
image.png

さいごに

Goとginを使ったWebアプリの開発がWindows上で行えることがわかり一安心です。

また、最近はWebアプリもどきをHeroku上で動かしたりしています。
データのやり取りなんかはJavaScript+GASなんかで行っていたのですが、限界が見えてしまったためサーバ側でも処理をさせようと思ったのが、今回 Goとginを触ったきっかけです。

今回書いたプログラムをベースに使えるアプリを作っていきたいと思う所存です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift4 の String.count を Go でやる

やりたいこと

家族👨‍👩‍👦‍👦 を3文字として数えたい。

Swift4 の String.count はすごい

内部でGrapheme Clusterなるアルゴリズムを使っていて、それによってUnicode文字列の文字数を正確に計算できているらしい(参考)。

print("家族👨‍👩‍👧‍👦".count) // -> 3

Go の utf8.RuneCountInString では対応できない

👨‍👩‍👧‍👦が7文字として数えられてしまう。

import "unicode/utf8"

func main() {
    print(utf8.RuneCountInString("家族👨‍👩‍👧‍👦")) // -> 9
}

Go でも Swift4 の String.count がやりたい

github.com/rivo/unisegを使うとGrapheme Clusterを利用して文字列を処理できる。
Playgroundでやるとこんな感じになる。

import "github.com/rivo/uniseg"

func main() {
    print(uniseg.GraphemeClusterCount("家族👨‍👩‍👧‍👦")) // -> 3
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スタンドアローンな自作ゲームに通信機能を追加する

この記事では

通信の事を勉強するためにスタンドアローンな自作ゲームに通信機能を追加して勉強しています。操作キャラのアクションと座標の同期が完成したのでその方法を投稿したいと思います。
ゲームは3Dアクションゲームです。複数人で協力しながら戦える機能を追加していきます。具体的にはモンスターハンターの様に通信して遊べるようにします。

サーバー

並列処理を簡単にできそうという理由からGo言語を使用しています。

参考URL(https://qiita.com/ak-ymst/items/259960bfffa42d51d246)

package main

import (
    "fmt"
    "io"
    "net"
    "strconv"
)

var connList []net.Conn

func main() {
    service := ":10000"
    listener, error := net.Listen("tcp", service)

    if error != nil {
        panic(error)
    }

    fmt.Printf("Server running at %s)\n", service)
    //fmt.Println("Server running at localhost:10000")
    fmt.Println("サーバ受付待機中…")

    waitClient(listener)

}

func waitClient(listener net.Listener) {
    connection, error := listener.Accept()

    connList = append(connList, connection)

    fmt.Println("通信スタート!")
    fmt.Printf("接続数 : %v \n", len(connList))
    fmt.Println("ローカルアドレス :", connList[len(connList)-1].LocalAddr())
    fmt.Println("リモートアドレス :", connList[len(connList)-1].RemoteAddr())
    fmt.Println()

    // 接続先に、サーバーに何番目に接続したかを伝える(クライアントを親と子で分ける)
    var buf = make([]byte, 1024)
    var connCnt = len(connList) - 1 // 接続数を送信 0スタートにしたいから-1
    var str string
    str = strconv.Itoa(connCnt)
    buf = []byte(str)
    _, error = connection.Write(buf[:1])

    if error != nil {
        panic(error)
    }

    go goEcho(connection)

    waitClient(listener)
}

func goEcho(connection net.Conn) {
    defer connection.Close()
    echo(connection)
}

func echo(connection net.Conn) {

    var buf = make([]byte, 1024)

    // 受信
    n, error := connection.Read(buf)
    if error != nil {
        if error == io.EOF {
            return
        } else {
            panic(error)
        }
    }

    fmt.Printf("受信(%s) : %s \n",
        connection.RemoteAddr(), buf[:n]) // リモートアドレスと受け取った内容を表示

    //接続先全てに送信
    for i := range connList {
        fmt.Printf("***送信元と送信先の宛先が同じ場合は送信しない***\n")
        fmt.Printf("受信先(%s)\n", connList[i].RemoteAddr())
        fmt.Printf("送信元(%s)\n", connection.RemoteAddr())

        // 送信元には返さないようにする
        if(connList[i] == connection){
            fmt.Printf("送信元と送信先の宛先が同じなのでcontinue")
            continue
            } 

        n, error = connList[i].Write(buf[:n])
        if error != nil {
            panic(error)
        }
        fmt.Printf("送信(%s) : %s \n",
            connList[i].RemoteAddr(), buf[:n])
    }
    fmt.Println()

    echo(connection)
}

解説

サーバーは受信した文字列を受信元以外の通信相手全員に送信するようにしています。
補足:(A,B,C,D が通信しているとしたらAから受信したデータをB,C,Dに送信します。Bから受信したデータはA,C,Dに送信...というイメージです。)

座標の同期(クライアント)

json形式で入力情報を送受信して同期する様にしました。

Game.cpp
    void Game::Update() {
    m_Ms.UpdatePointLight();

    /*状態管理*/
    Input();
    m_GameState->Update(this);

    m_EffectMgr->Update();
    m_FragmentMgr->Update();
    m_BltMgr->Update();

    /*キャラクリア*/
    std::vector<SPTR<Character>>::iterator it = m_CharaList.begin();
    while (it != m_CharaList.end()) {
        if (!(*it)->GetVisibleFlg() && (*it) != m_Player) {
            it = m_CharaList.erase(it);
        }
        else {
            it++;
        }
    } //end while

    /*=====送受信=====*/
    // jsonデータにして座標を送る
    string jsonStr; // jsonファイルの文字列
    json11::Json json; // jsonデータ

    // 送信
    m_Player->UpdateJson();
    json = m_Player->GetJson();
    jsonStr = json.dump();
    m_TcpClient->Send<string>(jsonStr);

    // 受信
    jsonStr = m_TcpClient->RecvAndGetString();
    json = Helper::JsonStrToPureJson(jsonStr);
    m_OnlinePlayer->SetJson(json);
    /*================*/

    // カリングと描画順を決める
    m_RenderMgr->Update();
}

解説

ゲームの更新を大方終わらした後に、自分の入力情報を送信する事と通信相手の入力情報を受信しています。

サーバーから受け取った情報を元にキャラクターを動作させる

OnlineCharacterInputComponent.cpp
void My::OnlineCharacterInputComponent::Update()
{
    // 入力情報をjsonで更新
    auto&& json = m_Owner->GetJson();
    if (json.dump() != "null") { // 何も入っていないときの文字列は"null"
        auto&& inputData = json["inputData"].array_items()[0];
        m_MouseLButton = inputData["mouseLButton"].bool_value();
        m_MouseRButton = inputData["mouseRButton"].bool_value();
        m_Key_W = inputData["key_W"].bool_value();
        m_Key_A = inputData["key_A"].bool_value();
        m_Key_S = inputData["key_S"].bool_value();
        m_Key_D = inputData["key_D"].bool_value();
        m_HistoryMouseLButton = inputData["HoldMouseLButton"].bool_value();
    }
}
PlayerState.cpp
SPTR<PlayerState> IdleState::HandleInput(const Player& player){
    if (player.GetDamageCnt() > 0.0f) {
        return MAKE_S<DamageState>();
    }

    auto&& input = player.GetComponent<InputComponent>();
    if (input->GetKey_W() ||
        input->GetKey_A() ||
        input->GetKey_S() ||
        input->GetKey_D())
    {
        return MAKE_S<DushState>();
    }

    if (input->GetMouseLButton() &&
        !input->IsHoldMouseLButton()) {
        return MAKE_S<AttackState>();
    }

    if (input->GetMouseRButton()) {
        return MAKE_S<ChantState>();
    }

    return nullptr;
}

解説

サーバーから受け取った入力情報をinputDataに格納します。inputData通りに動作するキャラクターを用意してアクションと座標の同期を実装しました。

最後に

初めて通信機能を作成したので改善点は多いと思います。自分のようにゲーム通信に初めて手を出す方の参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初めての同期通信(ゲーム)

この記事では

スタンドアローンな自作ゲームに通信機能を追加して勉強しています。操作キャラのアクションと座標の同期が完成したのでその方法を投稿したいと思います。
ゲームは3Dアクションゲームです。複数人で協力しながら戦える機能を追加していきます。具体的にはモンスターハンターの様に通信して遊べるようにします。

サーバー

並列処理を簡単にできそうという理由からGo言語を使用しています。

参考URL(https://qiita.com/ak-ymst/items/259960bfffa42d51d246)

package main

import (
    "fmt"
    "io"
    "net"
    "strconv"
)

var connList []net.Conn

func main() {
    service := ":10000"
    listener, error := net.Listen("tcp", service)

    if error != nil {
        panic(error)
    }

    fmt.Printf("Server running at %s)\n", service)
    //fmt.Println("Server running at localhost:10000")
    fmt.Println("サーバ受付待機中…")

    waitClient(listener)

}

func waitClient(listener net.Listener) {
    connection, error := listener.Accept()

    connList = append(connList, connection)

    fmt.Println("通信スタート!")
    fmt.Printf("接続数 : %v \n", len(connList))
    fmt.Println("ローカルアドレス :", connList[len(connList)-1].LocalAddr())
    fmt.Println("リモートアドレス :", connList[len(connList)-1].RemoteAddr())
    fmt.Println()

    // 接続先に、サーバーに何番目に接続したかを伝える(クライアントを親と子で分ける)
    var buf = make([]byte, 1024)
    var connCnt = len(connList) - 1 // 接続数を送信 0スタートにしたいから-1
    var str string
    str = strconv.Itoa(connCnt)
    buf = []byte(str)
    _, error = connection.Write(buf[:1])

    if error != nil {
        panic(error)
    }

    go goEcho(connection)

    waitClient(listener)
}

func goEcho(connection net.Conn) {
    defer connection.Close()
    echo(connection)
}

func echo(connection net.Conn) {

    var buf = make([]byte, 1024)

    // 受信
    n, error := connection.Read(buf)
    if error != nil {
        if error == io.EOF {
            return
        } else {
            panic(error)
        }
    }

    fmt.Printf("受信(%s) : %s \n",
        connection.RemoteAddr(), buf[:n]) // リモートアドレスと受け取った内容を表示

    //接続先全てに送信
    for i := range connList {
        fmt.Printf("***送信元と送信先の宛先が同じ場合は送信しない***\n")
        fmt.Printf("受信先(%s)\n", connList[i].RemoteAddr())
        fmt.Printf("送信元(%s)\n", connection.RemoteAddr())

        // 送信元には返さないようにする
        if(connList[i] == connection){
            fmt.Printf("送信元と送信先の宛先が同じなのでcontinue")
            continue
            } 

        n, error = connList[i].Write(buf[:n])
        if error != nil {
            panic(error)
        }
        fmt.Printf("送信(%s) : %s \n",
            connList[i].RemoteAddr(), buf[:n])
    }
    fmt.Println()

    echo(connection)
}

解説

サーバーは受信した文字列を受信元以外の通信相手全員に送信するようにしています。
補足:(A,B,C,D が通信しているとしたらAから受信したデータをB,C,Dに送信します。Bから受信したデータはA,C,Dに送信...というイメージです。)

座標の同期(クライアント)

json形式で入力情報を送受信して同期する様にしました。

Game.cpp
    void Game::Update() {
    m_Ms.UpdatePointLight();

    /*状態管理*/
    Input();
    m_GameState->Update(this);

    m_EffectMgr->Update();
    m_FragmentMgr->Update();
    m_BltMgr->Update();

    /*キャラクリア*/
    std::vector<SPTR<Character>>::iterator it = m_CharaList.begin();
    while (it != m_CharaList.end()) {
        if (!(*it)->GetVisibleFlg() && (*it) != m_Player) {
            it = m_CharaList.erase(it);
        }
        else {
            it++;
        }
    } //end while

    /*=====送受信=====*/
    // jsonデータにして座標を送る
    string jsonStr; // jsonファイルの文字列
    json11::Json json; // jsonデータ

    // 送信
    m_Player->UpdateJson();
    json = m_Player->GetJson();
    jsonStr = json.dump();
    m_TcpClient->Send<string>(jsonStr);

    // 受信
    jsonStr = m_TcpClient->RecvAndGetString();
    json = Helper::JsonStrToPureJson(jsonStr);
    m_OnlinePlayer->SetJson(json);
    /*================*/

    // カリングと描画順を決める
    m_RenderMgr->Update();
}

解説

ゲームの更新を大方終わらした後に、自分の入力情報を送信する事と通信相手の入力情報を受信しています。

サーバーから受け取った情報を元にキャラクターを動作させる

OnlineCharacterInputComponent.cpp
void My::OnlineCharacterInputComponent::Update()
{
    // 入力情報をjsonで更新
    auto&& json = m_Owner->GetJson();
    if (json.dump() != "null") { // 何も入っていないときの文字列は"null"
        auto&& inputData = json["inputData"].array_items()[0];
        m_MouseLButton = inputData["mouseLButton"].bool_value();
        m_MouseRButton = inputData["mouseRButton"].bool_value();
        m_Key_W = inputData["key_W"].bool_value();
        m_Key_A = inputData["key_A"].bool_value();
        m_Key_S = inputData["key_S"].bool_value();
        m_Key_D = inputData["key_D"].bool_value();
        m_HistoryMouseLButton = inputData["HoldMouseLButton"].bool_value();
    }
}
PlayerState.cpp
SPTR<PlayerState> IdleState::HandleInput(const Player& player){
    if (player.GetDamageCnt() > 0.0f) {
        return MAKE_S<DamageState>();
    }

    auto&& input = player.GetComponent<InputComponent>();
    if (input->GetKey_W() ||
        input->GetKey_A() ||
        input->GetKey_S() ||
        input->GetKey_D())
    {
        return MAKE_S<DushState>();
    }

    if (input->GetMouseLButton() &&
        !input->IsHoldMouseLButton()) {
        return MAKE_S<AttackState>();
    }

    if (input->GetMouseRButton()) {
        return MAKE_S<ChantState>();
    }

    return nullptr;
}

解説

サーバーから受け取った入力情報をinputDataに格納します。inputData通りに動作するキャラクターを用意してアクションと座標の同期を実装しました。

最後に

初めて通信機能を作成したので改善点は多いと思います。自分のようにゲーム通信に初めて手を出す方の参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む