ADV/AVG 游戏引擎介绍

  • 吉里吉里: C++,老牌 AVG 游戏引擎,著名的 Fate/stay night 便是用其制作。但是上一个稳定版本已经是十年前,很久没有更新了。
  • Ren’Py: Python,代表作「心跳文学部」(我老婆的 Twitter),开源万岁,并且直到现在更新也很活跃,如果喜欢 Python,是个不错的选择。不过需要预编译,剧本与程序未分离。(定位于浏览器端的话,JS 更有优势。)
  • NScripter: C++,非商业免费,Windows 平台,代表作「寒蝉鸣泣之时」,上一个稳定版本发布于 2015 年。
  • AVG32、RealLive、SiglusEngineVisual Art’s 公司开发,Key 社游戏「CLANNAD」等均用此开发,但很明显这种商业级咱接触不到。
  • BKEngine: C++,面包工坊,非商业免费、跨平台,但是制作工具不跨平台(只有 Windows)。
  • AVG.js:JavaScript,开源,基于 Pixi.js 与 React,Web 端运行。但是作者 Icemic 是个大 🐦,所以已经几年没有更新了。(不过作者也在 BKEngine 的面包工坊。)
  • Librian: Python,开源,跨平台,Galgame | Visual Novel 引擎,作者 还有在做 Vtuber,可惜是个变态。
  • 橙光制作工具:免费易操作,只有 Windows 平台,但是因为 如何看待橙光游戏签约合同中版权永久属于橙光,而作者仅保留署名权?,好感直线下降。
  • Nova:基于Unity、对程序员友好的视觉小说框架,可以做出丰富的演出效果,并且免费开源。

参考链接:

ADV 游戏引擎计划

我们自主研发的文字AVG框架Nova(能叶)现已免费开源发布

【转】解决Mosquitto创建MQTT服务器提示Starting in local only mode

转载自:解决Mosquitto创建MQTT服务器提示Starting in local only mode


一、问题现象

使用新下载的Mosquitto创建MQTT服务器提示Starting in local only mode. Connections will only be possible from clients running on this machine.测试发现服务器只能接入本机的客户端,而其他设备上的客户端在连接时直接被重置了。

二、原因分析

随后查阅了一下Mosquitto的更新记录,原来在其2.0.0大版本更新后如果不加载配件文件则使用回环接口(仅可用于本地Socket通信)。

Breaking changes
· When the Mosquitto broker is run without configuring any listeners it will now bind to the loopback interfaces 127.0.0.1 and/or ::1. This means that only connections from the local host will be possible.
· Running the broker as mosquitto or mosquitto -p 1883 will bind to the loopback interface.
· Running the broker with a configuration file with no listeners configured will bind to the loopback interface with port 1883.
· Running the broker with a listener defined will bind by default to 0.0.0.0 / :: and so will be accessible from any interface. It is still possible to bind to a specific address/interface.

附:Mosquitto更新记录

三、解决方法

先修改软件安装目录中的配置文件mosquitto.conf然后加载配置文件来启动服务器。

  • 配置端口号,编辑mosquitto.conf,搜索listener去掉行首的#并加上端口号,示例端口为1883

# On systems that support Unix Domain Sockets, it is also possible
# to create a # Unix socket rather than opening a TCP socket. In
# this case, the port number should be set to 0 and a unix socket
# path must be provided, e.g.
# listener 0 /tmp/mosquitto.sock
#
# listener port-number [ip address/host name/unix socket path]
listener 1883

  • 启用匿名访问,若不允许匿名访问则只有添加客户端鉴权信息才能接入。为简化测试我们启用匿名访问:将mosquitto.conf里的allow_anonymous选项改为true并保存

# Boolean value that determines whether clients that connect
# without providing a username are allowed to connect. If set to
# false then a password file should be created (see the
# password_file option) to control authenticated client access.
#
# Defaults to false, unless there are no listeners defined in the configuration
# file, in which case it is set to true, but connections are only allowed from
# the local machine.
allow_anonymous true

  • 加载配置文件启动服务器测试,执行命令mosquitto.exe -c mosquitto.conf -v,此时LOG上已经没了Starting in local only mode且非本地客户端可以正常接入了

TouchDelegate相关文章

Android鲜为人知的TouchDelegate

Android使用TouchDelegate增加View的触摸范围

ListView Tips & Tricks #5: Enlarged Touchable Areas

TouchDelegateGroup

import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.TouchDelegate;
import android.view.View;

import androidx.annotation.NonNull;

import java.util.ArrayList;

public class TouchDelegateGroup extends TouchDelegate {
    private ArrayList<TouchDelegate> mTouchDelegates = new ArrayList<>();
    private TouchDelegate mCurrentTouchDelegate;

    public TouchDelegateGroup(@NonNull Rect bounds, @NonNull View delegateView) {
        super(bounds, delegateView);
        mTouchDelegates.add(this);
    }

    public void addTouchDelegate(@NonNull TouchDelegate touchDelegate) {
        mTouchDelegates.add(touchDelegate);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        TouchDelegate delegate = null;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                for (TouchDelegate touchDelegate : mTouchDelegates) {
                    if (onDelegateTouchEvent(touchDelegate, event)) {
                        mCurrentTouchDelegate = touchDelegate;
                        return true;
                    }
                }
                break;
            case MotionEvent.ACTION_MOVE:
                delegate = mCurrentTouchDelegate;
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                delegate = mCurrentTouchDelegate;
                mCurrentTouchDelegate = null;
                break;
        }
        if (delegate != null) {
            return onDelegateTouchEvent(delegate, event);
        }
        return false;
    }

    public boolean onSelfTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }

    public boolean onDelegateTouchEvent(TouchDelegate touchDelegate, MotionEvent event) {
        if (touchDelegate instanceof TouchDelegateGroup) {
            return ((TouchDelegateGroup) touchDelegate).onSelfTouchEvent(event);
        } else {
            return touchDelegate.onTouchEvent(event);
        }
    }
}

对Jetpack Compose设计的初步解读与思考

节选自:对Jetpack Compose设计的初步解读与思考


Jetpack Compose前段时间进入了Alpha阶段。经过去年年底至今剧烈的api变化后,现在Compose整体上大致稳定,所以我们也能对于Compose的设计进行初步的解读和评价了。

Compose从整体技术风格上来说是这样一个产物:在语法上激进模仿SwiftUI,编译/运行过程充满Svelte风格,同时也综合了各方包括Android开发组自身对UI框架的思考结果。

使用Compose时,最值得关注的就是Compose的编译器插件。可以这么说,Compose的runtime、api都是依附于编译器插件的,那个巨大而无所不包的编译器插件才是Compose的本体。Compose插件强势的入侵了原版Kotlin的语法,导致包含了Compose的Kotlin基本上可以算作新语言(算成个新虚拟机都不过分)。初次了解的时候确实让我很困惑,因为这与React,Flutter推崇的趋势简直是背道而驰。但是了解了Svelte,SwiftUI之后,Compose显得没有那么突兀了。

Svelte

Svelte试图解决的问题是:如何用声明式的代码书写风格,一对一直接翻译成纯粹命令式的DOM操作,从而达到无额外开销+极小runtime的效果。最终Svelte选择发明一套自制的模板语法来翻译到Javascript上。这个好理解,因为大家很熟悉,显然javascript以及其工具链没有任何实现这个目标的可能。

Compose

Compose在框架设计方面的野心明显超越Flutter。Compose团队多次表示Compose就是对Android SDK的重写。Compose对自身的定位估计类似于SwiftUI在苹果系生态中的定位,那就是高层次、生态内通用、外加依靠自身定位尽可能挖掘以及定制工具链以实现先进的开发模式。Compose使用了语法上和Swift神似的Kotlin,也面临相似的问题,于是Compose(出于一些原因)选择了简单粗暴的魔改Kotlin编译器(而不是模仿SwiftUI玩类型系统杂耍)。

Compose团队解释过Compose的出发点:构建一个通用的、描述树状结构渲染过程的框架,不管是是手机UI组件树或者是浏览器HTML Element。

Compose一不做二不休,直接把Kotlin编译器魔改到底。最后利用编译器魔改实现了几大功能。

对Compose设计的思考

目前Compose的官方资料仍然较为缺乏,因此很难知道Compose除此之外的优化设计以及runtime具体调度机制。但总体来说,我认为

  1. Compose的编译期优化潜力较为巨大,在未来完全有实现SwiftUI所有编译期优化的潜力,尽管没有使用类型系统可能会导致某些实现更为困难。
  2. Compose对于原版Kotlin的强势侵入是其得以实现设计目标的重要原因。但也是一个隐患,Compose对语义的入侵过深,我们可以看看Compose的编译器可能会干什么事情
    1. 对每个树的节点,按照源代码位置,生成一个唯一的int型ID
    2. 插入start和group来表明节点的边界
    3. 收集向函数传递的参数,参数的性质
    4. 根据收集的信息指导一个指令机的工作,那个指令机工作在一个无类型的纸带上

这个已经可以算作在Kotlin/JVM上重新发明了一个虚拟机了。结构化的执行流程,内存,ABI,call site,复杂的调度策略都有了,我觉得就差来个人来证明能跑操作系统了。在已经因为DSL特性高度特化的Kotlin语言上继续发明新虚拟机,总归是有点奇怪的事情。与此对比,SwiftUI对语言的入侵很少而且是隐式的。

  1. 作为未来取代Android SDK的候选者,有强烈的风格取向,opinionated。为了实现对树状渲染结构“通用”的描述,Compose捆绑了一整套非常新的解决方案,从Positional Memoization,到安卓上第xxxxxxx个响应式数据解决方案@State(目前仍然缺乏资料以证明其通用性)。好是好,但是Angular前车之鉴在那里。公平的来说,SwiftUI也是强烈的风格取向,但SwiftUI在苹果生态中的地位个人觉得谷歌没法在Compose上复刻。而且加上强势侵入原语言语义,一旦要调整估计就要大调整。
  2. Compose大量功能处于编译器层,导致这些功能其实是没办法灵活调节的。Flutter我觉得官方的xxx不好还可以自己重写一个发布出去,才有了一堆群魔乱舞的东西,engine虽大但除了engine以外都可以自己写。Compose感觉很容易就会碰到编译器层。
  3. Compose由于基于编译器,很多的优化都是类似于编译器过一个pass的模式来的,尤其是Diff和常量消除那些地方,比较细碎,不容易归档解释,给人一种“想到哪里写到哪里”的感觉,目前官方的文档就有很多这种问题。
  4. 总之,非常期待以后真正理想的“通用”编程语言配上先进的前端框架。也许就是swift加上MPS。

Android Studio 无法保存插件的加密配置信息的解决方案

缘起

笔者转移Android Studio配置信息到新电脑,采用了简单粗暴的复制粘贴方案。然后发现在新电脑上,Translation插件的应用密钥没了而且无法保存到本地。

缘由

Android Studio 把加密配置信息保存在KeePass中,换电脑后无法访问数据库文件,然后保存密码的方案就会变为不保存密码。

缘解

打开 %USERPROFILE%\.AndroidStudio4.0\config,删除 c.kdbx 和 c.pwd。

进入 options ,删除security.xml。

重新打开Android Studio。

Win10清理WinSxS文件夹的方法

节选自:win10系统winsxs文件夹该如何删除?win10删除winsxs文件夹的两种方法


1、Windows10桌面右键点击左下角的开始按钮,在弹出菜单中选择“运行”的菜单项。

2、这时就会弹出Windows10的运行窗口,在窗口中输入命令powershell,然后点击确定按钮运行该命令。

3、接着就会弹出Windows10的PowerShell窗口,在窗口中输入命令:

dism.exe /Online /Cleanup-Image /AnalyzeComponentStore

4、这时就会自动的扫描Winsxs文件夹,显示出文件夹的大小等情况。

5、如果想要清理Winsxs文件夹的话,我们直接输入命令:

dism.exe /online /Cleanup-Image /StartComponentCleanup

6、这时系统就会自动的清理垃圾文件, 直到清理完成就可以了。

Android 获取本地IP地址和广播地址

在AndroidManifest文件中配置权限:

<!-- internet -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

NetUtil文件:

/**
 * 获取本地IP地址
 */
public static String getLocalIP() {
    try {
        // 获取本地设备的所有网络接口
        Enumeration<NetworkInterface> niEnum = NetworkInterface.getNetworkInterfaces();
        while (niEnum.hasMoreElements()) {
            // 获取网络接口的所有IP地址
            Enumeration<InetAddress> addressEnum = niEnum.nextElement().getInetAddresses();
            while (addressEnum.hasMoreElements()) {
                // 返回枚举集合中的下一个IP地址信息
                InetAddress inetAddress = addressEnum.nextElement();
                // 不是回环地址,并且是IPv4地址
                if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) {
                    return inetAddress.getHostAddress();
                }
            }
        }
    } catch (SocketException e) {
        e.printStackTrace();
    }
    return null;
}
/**
 * 获取广播地址
 */
public static String getBroadcastIP() {
    try {
        for (Enumeration<NetworkInterface> niEnum = NetworkInterface.getNetworkInterfaces(); niEnum.hasMoreElements(); ) {
            NetworkInterface ni = niEnum.nextElement();
            if (!ni.isLoopback()) {
                for (InterfaceAddress interfaceAddress : ni.getInterfaceAddresses()) {
                    if (interfaceAddress.getBroadcast() != null) {
                        return interfaceAddress.getBroadcast().getHostAddress();
                    }
                }
            }
        }
    } catch (SocketException e) {
        e.printStackTrace();
    }
    return null;
}

参考链接:

Android获取有线网ip地址

Android获取如何获取当前手机IP地址

[Android]获取局域网广播地址的两种方法