[Pyxel]PC or スマホを判別してマウスカーソルの表示・非表示を制御する

# Pyxelでマウスカーソルの表示/非表示を制御したい

Pyxelには、mouse関数という、マウスカーソルの表示・非表示を制御する関数があります。

> mouse(visible)
visibleがTrueならマウスカーソルを表示し、Falseなら非表示にします。マウスカーソルが非表示でも座標は更新されます。

( [pyxel/docs/README.ja.md at main · kitao/pyxel](https://github.com/kitao/pyxel/blob/main/docs/README.ja.md#%E5%85%A5%E5%8A%9B) より引用)

Pyxelでクリック系のゲームを作成した場合、この関数を使ってマウスカーソルを表示することで画面上での自身の現在位置を把握することができます。

これはPCならば便利ですが、スマホやタブレットなどの画面タッチたんまつからゲームを起動した場合は、画面タップのたびにマウスカーソルがタップされた位置に飛び飛びで表示されてしまいます。

本記事では、端末のOSを判定してマウスカーソルの表示・非表示を制御してみます。

(なお、手動でマウスカーソルの表示・非表示を切り替える設定画面等を作成する、というのが一番確実ではあるのですが、ここではそれは考えないことにします)

# 環境

Pyxelのバージョンは2.2.8で実施しています。

# ローカル実行する場合

ローカル環境からPyxelを実行する場合(コマンドだと`pyxel run {pyファイル}` )、pythonのplatformモジュールを使用することで、OSを取得することができます。
イメージ的には以下のような感じです。

``` python
import pyxel
import platform

class App:
    def __init__(self):
        pyxel.init(160, 120)

        # PC(非タップ端末)からの実行時のみマウスカーソルを表示する
        os_name = platform.system()
        is_pc = os_name == "Windows" or os_name == "Darwin" or os_name == "Linux"
        pyxel.mouse(is_pc)

        pyxel.run(self.update, self.draw)

    def update(self)
        ...

    def draw(self)
        ...

App()
```

# Web Launcherから起動する場合

Web Launcher上では上記のplatform.system()で取得できる文字列は"Emscripten"の固定文字列となってしまいます。
そのため別の方法を使用する必要があるのですが、WebLauncherからの起動の場合、Pyodideの機能である、JavaScriptの実行が可能になるので、それを使用します。
具体的には、jsモジュールのnavigatorオブジェクトからユーザーエージェント情報を取得し、解析することでPCかスマホかを判定します。

以下、実装イメージです。

``` python
import pyxel
import js

class App:
    def __init__(self):
        pyxel.init(160, 120)

        # PC(非タップ端末)からの実行時のみマウスカーソルを表示する
        user_agent = navigator.userAgent.lower()
        is_pc = not ("android" in user_agent or "iphone" in user_agent or "ipad" in user_agent)
        pyxel.mouse(is_pc)

        pyxel.run(self.update, self.draw)

    def update(self)
        ...

    def draw(self)
        ...

App()
```

# 両方考慮する場合

上記のjsモジュールですが、WebLauncherからの実行以外ではモジュールインポート時に例外が発生してしまいます。
そのため、pyxel runによる実行とWeb Launcherからの実行の両方を考慮する場合は、例外発生も考慮したコーディングが必要となります。
具体例として、以下のようなOS判定用モジュールを作成する方法があります。

``` python
import platform
# from js はEmscripten環境以外では例外発生するのでcatchして環境を判定する
try:
    from js import navigator
    is_web_launcher = True
except ImportError:
    is_web_launcher = False

class DeviceChecker:
    def __init__(self):
        if is_web_launcher:
            # Web launcherから起動している場合、js関数でOS判定する
            self.user_agent = navigator.userAgent.lower()
            self.os_pc = not ("android" in self.user_agent or "iphone" in self.user_agent or "ipad" in self.user_agent)
        else:
            # ローカルから起動している場合、platformから判定する
            self.os_name = platform.system()
            self.os_pc =  self.os_name == "Windows" or self.os_name == "Darwin" or self.os_name == "Linux"

    def is_pc(self):
        return self.os_pc

    def is_web_launcher(self):
        return is_web_launcher
``` 

OS判定用モジュールをPyxelのコードで使用するイメージとしては、以下のような感じになります。

``` python
import pyxel
from device_checker import DeviceChecker

class App:
    def __init__(self):
        pyxel.init(160, 120)
            
        # PC(非タップ端末)からの実行時のみマウスカーソルを表示する
        self.deviceChecker = DeviceChecker()
        px.mouse(self.deviceChecker.is_pc())

        pyxel.run(self.update, self.draw)
    
    def update(self)
        ...
    
    def draw(self)
        ...

App()
``` 

# 補足

中華ゲーム機だとどうなるのかが未検証です。
plumOSを使用する場合、pyxappファイルを配置すると思うので、 `platform.system()` の戻り値を調べてそれをハンドリングすれば良さそうです。

# 実装例

試しに簡単なクリックゲームを作ってみました。
pyxappを直接実行しても、Web Launcherから実行しても、PCからだとマウスカーソルが表示され、スマホ(画面タッチ端末)からだとマウスカーソルが非表示になるはずです。

GitHubリンク:
[IgarashisanT/tap-numbers](https://github.com/IgarashisanT/tap-numbers/tree/main)

WebLauncherリンク:
[TAP NUMBERS](https://kitao.github.io/pyxel/wasm/launcher/?play=IgarashisanT.tap-numbers.tap-numbers)

# 注意事項

Web Launcherからの起動の場合、更新直後はキャッシュが効いていてソースコードの変更が反映されない場合があるようです。
更新が反映されていないと思ったら、キャッシュクリアを試してみると良いです。

# 参考URL

[pyxel/docs/README.ja.md at main · kitao/pyxel](https://github.com/kitao/pyxel/blob/main/docs/README.ja.md)
[Pyodideは今どれくらいできるのか #Python - Qiita](https://qiita.com/suzuki_sh/items/aa06142f2445b05b6331)
[Pythonが実行されている環境のOSやバージョン情報などを取得 | note.nkmk.me](https://note.nkmk.me/python-platform-system-release-version/)
[Pyxel（ブラウザ版）でJavaScriptの機能をいろいろ使う｜frenchbread](https://note.com/frenchbread/n/n03b7ca2d0933)