精华内容
下载资源
问答
  • 如何让自己状态好起来
    千次阅读
    2020-08-01 23:23:18

    1 Flink中的状态

      当数据流中的许多操作只查看一个每次事件(如事件解析器),一些操作会跨多个事件的信息(如窗口操作)。这些操作称为有状态。状态由一个任务维护,并且用来计算某个结果的所有数据,都属于这个任务的状态。可以简单的任务状态就是一个本地变量,可以被任务的业务逻辑访问。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WppGyApk-1596294459379)(C:\资料\flink\笔记\7 状态编程和容错机制\assets\1595942654393.png)]

      有些算子有些任务是没有状态的,如map操作,只跟输入数据有关。像窗口操作不管是增量窗口函数还是全窗口函数都要保持里面的信息的,一开始在窗口到达结束时间之前是不输出数据的,所以最后输出数据的时候,他的计算是要依赖之前的,全窗口可以认为是把所有数据都作为状态保存下来。增量聚合窗口来一个聚合一次要保存的是中间聚合状态。像ProcessFunction可以有状态也可以没有状态。

      无状态流处理和有状态流处理的主要区别:无状态流处理分别接收每条输入数据,根据最新输入的数据生成输出数据;有状态流处理会维护状态,根据每条输入记录进行更新,并基于最新输入的记录和当前的状态值生成输出记录,即综合考虑多个事件之后的结果。

    需要状态操作的一些例子如下:

    • 应用程序搜索某些事件模式时,状态将存储迄今遇到的事件序列。
    • 每分钟/小时/天聚合事件时,将状态保存挂起的聚合。
    • 在数据流上训练机器学习模型时,状态保存模型参数的当前版本。
    • 需要管理历史数据时,状态允许有效访问过去发生的事件。

    2 状态类型

      每个状态都是当前任务去管理维护,每个状态都是和当前算子关联在一起的,如果需要Flink真正的把他管理起来的话在运行时的时候Flink就必须要知道当前状态定义的类型是什么,所以一开始必须注册对应的状态,要有所谓的描述器。Flink有两种基本的状态:Operator State算子状态和Keyed State键控状态,他们的主要区别就是作用范围不一样,算子状态的作用范围就是限定为算子任务(也就是当前一个分区执行的时候,所有数据来了都能访问到状态)。键控状态中并不是当前分区所有的数据都能访问所有的状态,而是按照keyby之后的key做划分,当前key只能访问自己的状态

    2.1 Operator State

      每个算子状态绑定到一个并行算子实例,作用范围限定为算子任务,同一并行任务的状态是共享的,并行处理的所有数据都可以访问到相同的状态。Kafka Connector就是使用算子状态的很好的一个例子,Kafka consumer的每个并行实例都维护一个主题分区和偏移,作为算子状态。当并行性发生变化时,算子状态接口支持在并行运算符实例之间重新分配状态。可以有不同的方案来进行这种再分配。

      因为同一个并行任务处理的所有数据都可以访问到当前的状态,所以就相当于本地变量

      算子状态有3种基本数据结构:①列表状态(List state):状态表示为一组数据的列表②联合列表状态(Union list state):也将状态表示为数据的列表。它与常规列表状态的区别在于,在发生故障时,或者从保存点(savepoint)启动应用程序时如何恢复。③广播状态(Broadcast state):如果一个算子有多项任务,而它的每项任务状态又都相同,那么这种特殊情况最适合应用广播状态。那就可以访问到别的并行子任务的状态。

      算子状态运用的时候可能应用场景没那么多,一般都是keyby之后根据不同的key做分区讨论。如果所有数据来了全部统一处理的话一般还要划分成不同的状态要保存为链表,并行度调整的时候可以根据这个列表拆开,做进一步调整。

      联合列表状态与列表状态的区别:主要是并行度调整状态怎样重新分配,列表状态本身分配的时候直接分配;联合列表状态的话就是把所有元素都联合起来,然后由每个任务自己定义最后留下哪些,也就是自己截取要哪一部分。

    2.2 Keyed State

      Keyed State只能在KeyedStream后使用,键控状态总是相对于键,根据键来维护和访问的

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sh5kgIiP-1596294459387)(C:\资料\flink\笔记\7 状态编程和容错机制\assets\1595941884637.png)]

      Keyed State很类似于一个分布式的key-value map数据结构,只能用于KeyedStream(keyBy算子处理之后)。键控状态基于每个key去管理,一般keyby进行HashCode重分区后基于它自己独享的内存空间就会针对每一个不同的key分别保存一份独立的存储状态,而且接下来来了一个新的数据只能访问自己的状态,不能访问其他key的,Flink会为每一个key维护一个状态。

      Flink的Keyed State支持的数据类型如下:

    序号类型说明方法
    1ValueState[T]用来保存单个的值ValueState.update(value: T)
    ValueState.value()
    2ListState[T]保存一个列表ListState.add(value: T)
    ListState.addAll(values: java.util.List[T])
    ListState.update(values: java.util.List[T])
    ListState.get()(注意:返回的是Iterable[T])
    3MapState[K, V]保存Key-Value对MapState.get(key: K)
    MapState.put(key: K, value: V)
    MapState.contains(key: K)
    MapState.remove(key: K)
    4ReducingState[T]保留一个值,该值表示添加到状态的所有值的汇总,需要用户提供ReduceFunctionReducingState.add(value: T)
    ReducingState.get()
    5AggregatingState[I, O]保留一个值,该值表示添加到状态的所有值的汇总,需要用户提供AggregateFunctionAggregatingState.add(value: T)
    AggregatingState.get()
    6FoldingState<T, ACC>保留一个值,该值表示添加到状态的所有值的汇总,需要用户提供FoldFunctionAggregatingState.add(value: T)
    AggregatingState.get()

      每个状态都有clear()是清空操作。

      在进行状态编程时需要通过RuntimeContext注册StateDescriptor。StateDescriptor以状态state的名字和存储的数据类型为参数。案例如下:

    class CountWindowAverage extends RichFlatMapFunction[(Long, Long), (Long, Long)] {
    
      private var sum: ValueState[(Long, Long)] = _
    
      override def flatMap(input: (Long, Long), out: Collector[(Long, Long)]): Unit = {
    
        // access the state value
        val tmpCurrentSum = sum.value
    
        // If it hasn't been used before, it will be null
        val currentSum = if (tmpCurrentSum != null) {
          tmpCurrentSum
        } else {
          (0L, 0L)
        }
    
        // update the count
        val newSum = (currentSum._1 + 1, currentSum._2 + input._2)
    
        // update the state
        sum.update(newSum)
    
        // if the count reaches 2, emit the average and clear the state
        if (newSum._1 >= 2) {
          out.collect((input._1, newSum._2 / newSum._1))
          sum.clear()
        }
      }
    
      override def open(parameters: Configuration): Unit = {
        sum = getRuntimeContext.getState(
          new ValueStateDescriptor[(Long, Long)]("average", createTypeInformation[(Long, Long)])
        )
      }
    }
    
    
    object ExampleCountWindowAverage extends App {
      val env = StreamExecutionEnvironment.getExecutionEnvironment
    
      env.fromCollection(List(
        (1L, 3L),
        (1L, 5L),
        (1L, 7L),
        (1L, 4L),
        (1L, 2L)
      )).keyBy(_._1)
        .flatMap(new CountWindowAverage())
        .print()
      // the printed output will be (1,4) and (1,5)
    
      env.execute("ExampleManagedState")
    }
    

    声明状态操作为:

        sum = getRuntimeContext.getState(
          new ValueStateDescriptor[(Long, Long)]("average", createTypeInformation[(Long, Long)])
        )
    

    读取状态为:

        val tmpCurrentSum = sum.value
    

      更新状态为:

        sum.update(newSum)
    

    3 状态后端

      Flink提供不同的State Backends状态后端,指定如何和在何处存储状态。

      (1)MemoryStateBackend

      状将键控状态作为内存中的对象进行管理,将它们存储在TaskManager的JVM堆上,将checkpoint存储在JobManager的内存中

      (2)FsStateBackend

      本地状态存在TaskManager的JVM堆上,checkpoint存到远程的持久化文件系统(FileSystem)上

      (3)RocksDBStateBackend

      将所有状态序列化后,存入本地的RocksDB中存储。

      设置状态后端如下:

    val env = StreamExecutionEnvironment.getExecutionEnvironment()
    //val checkpointPath: String = checkpoint_Path
    //val backend = new RocksDBStateBackend(checkpointPath)
    //env.setStateBackend(backend)
    
    env.setStateBackend(new FsStateBackend(YOUR_PATH))
    env.enableCheckpointing(1000)
    // 配置重启策略
    env.setRestartStrategy(RestartStrategies.fixedDelayRestart(60, Time.of(10, TimeUnit.SECONDS)))
    
    更多相关内容
  • 一个Android沉浸式状态栏上的黑科技

    千次阅读 多人点赞 2022-06-11 14:42:25
    起来,在不知不觉中,我竟然凑成了这沉浸式状态栏三部曲。其实最开始的时候,我主要是因为工作上的原因想要在Android版的Edge浏览器上实现首页图片沉浸式的功能。那么为了实现这个功能,我提前去做了一些技术调研...

    本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注,每个工作日都有文章更新。

    说起来,在不知不觉中,我竟然凑成了这沉浸式状态栏三部曲。

    其实最开始的时候,我主要是因为工作上的原因想要在Android版的Edge浏览器上实现首页图片沉浸式的功能。

    那么为了实现这个功能,我提前去做了一些技术调研,并将调研的结果整理成了一篇文章,具体可参阅 再学一遍android:fitsSystemWindows属性

    做完技术调研之后,接下来就是功能实现了。对于Android版的Edge浏览器而言,首页图片的沉浸式一直是部分网友长久以来的呼声,经过我的各种攻坚和踩坑之后,终于将这个功能完成了。具体可参阅 我为Android版Microsoft Edge所带来的变化

    实现沉浸式之后的效果如下图所示:

    在这里插入图片描述

    不过,有朋友在评论区提出了这样一个疑问:

    在这里插入图片描述

    确实,这是一个做沉浸式功能时比较容易被忽略的问题。如果背景图片的颜色和状态栏图标的颜色非常接近的话,那么的确会造成状态栏图标看不清楚的情况。

    这里我举了一些沉浸式效果做得不太好的案例,具体是什么App我就不提了。

    在这里插入图片描述
    在这里插入图片描述

    可以看到,这些App虽然实现了沉浸式状态栏的效果,但是由于状态栏上的图标变得难以看清,所以最终效果可能反而不好。

    但是,Edge浏览器是不会存在这种问题的。为什么呢?这就是我在上篇文章中说的,在实现沉浸式状态栏时运用了一些小黑科技。那么借助这些小黑科技,我终于可以凑成这沉浸式状态栏三部曲了。

    话不多说,下面技术开讲。

    其实想要解决上图中的这种由于颜色值接近,导致部分内容看不清的情况,我能想到两种解决方案。一种是从设计层面解决,一种是从技术层面解决。

    从设计层面解决相对会比较容易一些,同时应该也是大部分App会采用的方案,那就是在背景图的上方再盖一层阴影。有了这层阴影之后,我们可以让状态栏上的图标始终都是浅色的。即使出现浅色的背景图,由于阴影层的存在,状态栏上的图标依然是可以看得清的。

    但如果只是用这个方案解决的话,那么我就不会写本篇文章了。因为这里我们会采用第二种方案,从技术层面解决。

    首先从技术层面进行分析,要解决这个问题,无非就是需要将背景图颜色和状态栏图标的颜色区分开。

    Android系统其实给了我们API来控制状态栏图标的颜色,但是只能设置成黑、白这两种颜色,而不可以将状态栏图标改成五颜六色的样子。

    默认情况下,系统会认为我们拥有的是一个深色的状态栏,那么状态栏上面的图标自然就应该白色的,因为只有这样才能看得清上面的图标。

    而调用如下API则可以让系统认为我们拥有的是一个浅色的状态栏:

    private fun setLightStatusBar() {
        val flags = window.decorView.systemUiVisibility
        window.decorView.systemUiVisibility = flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
    }
    

    如此一来,状态栏上面的图标就会变成黑色的,以和浅色的状态栏相互映衬。

    如果要动态恢复成默认的深色状态栏,只需要这样设置:

    private fun setDarkStatusBar() {
        val flags = window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
        window.decorView.systemUiVisibility = flags xor View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
    }
    

    这就是我们拥有的用于控制状态栏图标颜色的API。

    好了,现在有了这个法宝来控制状态栏图标的颜色,那么接下来的问题就是,什么时候应该显示白色的状态栏图标?什么时候应该显示黑色的状态栏图标?

    答案是显而易见的,为了能让前景背景的颜色区分更加明显,当然应该是底部是深色背景图的时候显示白色的状态栏图标,底部是浅色背景图的时候显示黑色的状态栏图标。

    因此,现在的问题就转移成了,我们如何才能识别一张背景图的指定区域是属于深色还是浅色?

    非常幸运,在Android系统上我们是可以做到这一点的,只需要借助Google提供的Palette库即可。

    Palette是一个专门用于对图像进行颜色提取和识别的库,功能虽然不能说是非常强大,但是已经完全可以满足我们这里的需求了。

    要使用Palette库,首先需要将它引入到项目当中,如下所示:

    dependencies {
        implementation 'androidx.palette:palette:1.0.0'
    }
    

    接下来我们就可以借助Palette来进行一些颜色提取功能了,示例用法如下:

    Palette
        .from(bitmap)
        .setRegion(left, top, right, bottom)
        .maximumColorCount(colorCount)
        .generate {
    
    }
    

    这是Palette最基础、最常见的用法。

    首先,我们传入一个bitmap对象,这样Palette就会对它来进行图像解析。

    然后调用setRegion()方法来指定解析这个bitmap对象的哪个区域。比方说我们本篇文章是要解决状态栏图标的问题,那肯定就要去解析手机状态栏那个区域的颜色值,其他区域的颜色值对我们来说没有意义。

    接着调用maximumColorCount()方法来告诉Palette一共需要提取多少个颜色特征点。具体的颜色提取算法是由Palette自己控制的,我们无需关心。反正只需要知道,最终提取出来的这些颜色值都是这个bitmap的指定区域里最具代表性的就可以了。

    最后调用generate()方法开始解析,Palette会开启异步线程来执行解析操作,并将最终结果回调到Lambda表达式当中。

    现在我们已经得到这些提取出的特征点颜色值了,那么接下来,我们又该如何处理它们呢?

    需要说明的事,后续的处理逻辑其实并没有一个非常严格的规定。我只说一下我个人的处理方式,大家也完全可以去定义自己的处理逻辑。

    先贴一下代码,我再进行解释:

    Palette
        .from(bitmap)
        .maximumColorCount(colorCount)
        .setRegion(left, top, right, bottom)
        .generate {
            it?.let { palette ->
                var mostPopularSwatch: Palette.Swatch? = null
                for (swatch in palette.swatches) {
                    if (mostPopularSwatch == null
                        || swatch.population > mostPopularSwatch.population) {
                        mostPopularSwatch = swatch
                    }
                }
                mostPopularSwatch?.let { swatch ->
                    val luminance = ColorUtils.calculateLuminance(swatch.rgb)
                    // 当luminance小于0.5时,我们认为这是一个深色值.
                    if (luminance < 0.5) {
                        setDarkStatusBar()
                    } else {
                        setLightStatusBar()
                    }
                }
            }
        }
    

    由于刚才在maximumColorCount()方法中传入了提取颜色特征点的数量,因此generate()方法的回调当中我们就可以得到多个颜色特征点(Swatch)。

    而每个颜色特征点都会有一个权重值,调用getPopulation()方法可以获取,表示该特征点在选定的bitmap区域的重要程度。我选取了权重值最高的那个特征点来作为这个bitmap区域的代表颜色值。

    接下来再调用ColorUtils.calculateLuminance()方法来计算选取的这个颜色值的亮度。当亮度低于0.5时,我就认为这是一个深色的颜色值,那么此时将状态栏设置成深色模式,状态栏图标就会自动变成白色。反之就将状态栏设置成浅色模式,此时状态栏图标就会自动变成黑色。

    大概流程就是这个样子,我觉得原理还是非常简单的,我甚至都没有给出一个完整的实例,只是贴出了一些代码片段。

    至于Palette,终归只是一个比较小众的库,知道或使用过的人可能并不多,所以用上这种小众技术我觉得足以称得上是黑科技了。

    那么最后我们就来看一看实际的运行效果吧。

    这里我准备了几张不同的背景图,由Palette解析之后,会根据识别出的颜色值动态更改状态栏图标的颜色。

    这是深色背景图的效果。

    在这里插入图片描述

    这是浅色背景图的效果。

    在这里插入图片描述

    可以看到,不管在什么背景图下,状态栏图标的颜色都可以做到自动适配,保证图标始终是清晰可见的。

    目前这种使用Palette来动态进行颜色识别的方案,我感觉至少是可以保证99%以上的场景都能够正确适配的,但是也存在一些特别极端的场景。

    比如说背景图就是一张黑白左右分割的图片,这种情况下Palette会选取哪种颜色来作为代表色其实是不确定的。但不管是选中了黑还是白,都一定会导致状态栏上有一半区域的图标是不可见的。效果如下图所示:

    在这里插入图片描述

    不过对于这种极端情况,我觉得就没必要强求了。甚至我都并不认为这是一个Bug,反而觉得这是一种很酷的效果,你们觉得呢?

    好的,本篇文章就到这里。文中我只帖出了所有关键代码的示例,以及最终运行效果的截图。如果你不想自己动手去敲一遍,也可以直接参考我的完整源码:

    https://github.com/guolindev/ImmersiveStatusBar

    沉浸式状态栏三部曲到此完结。

    如果想要学习Kotlin和最新的Android知识,可以参考我的新书 《第一行代码 第3版》点击此处查看详情


    关注我的技术公众号,每天都有优质技术文章推送。

    微信扫一扫下方二维码即可关注:

    展开全文
  • 对玩家自己的数据进行预测 对其它的玩家 仅同步服务器状态 不预测 玩家的操作 需要发送时间切片的ID 服务器对当前切片下玩家的状态进行处理 且要判定回滚 间隔一段时间来校验玩家与服务器中玩家的状态 存在差异时 ...

    方案

    1. 对玩家自己的数据进行预测
    2. 对其它的玩家 仅同步服务器状态 不预测
    3. 玩家的操作 需要发送时间切片的ID 服务器对当前切片下玩家的状态进行处理 且要判定回滚
    4. 间隔一段时间来校验玩家与服务器中玩家的状态 存在差异时 回滚处理

    在这里插入图片描述

    帧预测

    客户端预表现来优化响应,需要满足一个前提:(如果游戏世界具有足够的确定性(游戏邦注:即给定游戏状态和一组输入),那么结果是完全可预测的)。

    即给定当前帧状态+当前帧输入,能够100%正确和到下一帧状态。
    Client-Prediction 可能会带来 Server Data 覆盖 Client Data 导致的抖动问题。
    在这里插入图片描述
    上图中,正确的表现应该是 10->11->12,因为Server Data慢于Client-Prediction,所以实际表现是 10 -> 11 -> 12 ->11 ->12,产生了抖动。
    解决方法是,给每个C->S包加上一个序号,当S->C时,带上最后处理的序号。

    在这里插入图片描述
    图中,当Client收到#1时,重复所有状态为服务器下发状态,并重新执行#2(进行预表现)。所以实际表现会是 10->11->12->12。

    Dead Reckoning 算法。

    	此算法用于状态同步下处理玩家的移动。可以缓解 high latency,并且降低带宽。
    

    核心思想:本地模拟,发现差异过大时上传。
      在跑的过程中,玩家A有一个值在不停的记录着其真实坐标和在后台模拟运动的坐标的差值,当差值大于极限误差的时候,则计算出当前的速度S、方向O、速度A、位置,并广播给网络中其他所有节点。其他节点在收到这条消息之后呢,就可以用一些很平滑的移动把路人甲拉扯过去。
     此种方法下,依赖客户端模拟计算,玩家A可以任意的走动,当不一致时,上报位置。
     
     在没有收到PDU的时候,需要模拟对方的运动,DR提供了几种经典算法:

    (1)位置1 = 位置0,就是保持不变
    (2)位置1 = 位置0 + 速度 × (T1 – T0),相当于根据PDU中的数据,做匀速运动
    (3)位置1 = 位置0 + 速度 × (T1 – T0)+ 1/2 × 加速度 × (T1 –T0)平方,比2)中多了加速度的方向

    帧回滚

    回滚逻辑,就是我们解决问题的方案。可以这样理解,客户端的时间,领先服务器,客户端不需要服务器确认帧返回才执行指令,而是玩家输入,立刻执行(其他玩家的输入,按照其最近一个输入做预测,或者其他更优化的预测方案),然后将指令发送给服务器,服务器收到后给客户端确认,客户端收到确认后,如果服务确认的操作,和之前执行的一样(自己和其他玩家预测的操作),将不做任何改变,如果不一样(预测错误),就会将游戏整体逻辑回滚到最后一次服务器确认的正确帧,然后再追上当前客户端的帧。

    此处逻辑较为复杂,我尝试举个例子说明下。

    当前客户端(A,B)执行到100帧,服务器执行到97帧。在100帧的时候,A执行了移动,B执行了攻击,A和B都通知服务器:我已经执行到100帧,我的操作是移动(A),攻击(B)。服务器在自己的98帧或99帧收到了A,B的消息,存在对应帧的操作数据中,等服务器执行到100帧的时候(或提前),将这个数据广播给AB。

    然后A和B立刻开始执行100帧,A执行移动,预测B不执行操作。而B执行攻击,预测A执行攻击(可能A的99帧也是攻击),A和B各自预测对方的操作。

    在A和B执行完100帧后,他们会各自保存100帧的状态快照,以及100帧各自的操作(包括预测的操作),以备万一预测错误,做逻辑回滚。

    执行几帧后,A,B来到了103帧,服务器到了100帧,他开始广播数据给AB,在一定延迟后,AB收到了服务器确认的100帧的数据,这时候,AB可能已经执行到104了。A和B各自去核对服务器的数据和自己预测的数据是否相同。例如A核对后,100帧的操作,和自己预测的一样,A不做任何处理,继续往前。而B核对后,发现在100帧,B对A的预测,和服务器确认的A的操作,是不一样的(B预测的是攻击,而实际A的操作是移动),B就回滚到上一个确认一样的帧,即99帧,然后根据确认的100帧操作去执行100帧,然后快速执行101103的帧逻辑,之后继续执行104帧,其中(101104)还是预测的逻辑帧。

    因为客户端对当前操作的立刻执行,这个操作手感,是完全和pve(不联网状态)是一样的,不存在任何delay。所以,能做到绝佳的操作手感。当预测不一样的时候,做逻辑回滚,快速追回当前操作。

    这样,对于网络好的玩家,和网络不好的玩家,都不会互相影响,不会像lockstep一样,网络好的玩家,会被网络不好的玩家lock住。也不会被网络延迟lock住,客户端可以一直往前预测。

    对于网络好的玩家(A),可以动态调整(根据动态的latency),让客户端领先服务器少一些,尽量减少预测量,就会尽量减少回滚,例如网络好的,可能客户端只领先2~3帧。

    对于网络不好的玩家(B),动态调整,领先服务器多一些,根据latency调整,例如领先5帧。

    那么,A可能预测错的情况,只有2~3帧,而网络不好的B,可能预测错误的帧有5帧。通过优化的预测技术,和消息通知的优化,可以进一步减少A和B的预测错误率。对于A而言,战斗是顺畅的,手感很好,少数情况的回滚,优化好了,并不会带来卡顿和延迟感。

    重点优化的是B,即网络不好的玩家,他的操作体验。因为客户端不等待服务器确认,就执行操作,所以B的操作手感,和A是一致的,区别只在于,B因为延迟,预测了比较多的帧,可能导致预测错,回滚会多一些。比如按照B的预测,应该在100帧击中A,但是因为预测错误A的操作,回滚重新执行后,B可能在100帧不会击中A。这对于B来说,通过插值和一些平滑方式,B的感受是不会有太大区别的,因为B看自己,操作自己都是及时反馈的,他感觉自己是平滑的。

    这种方式,保证了网络不好的B的操作手感,和A一致。回滚导致的一些轻微的抖动,都是B看A的抖动,通过优化(插值,平滑等),进一步减少这些后,B的感受是很好的。我们测试在200~300毫秒随机延迟的情况下,B的操作手感良好。

    这里,客户端提前服务器的方式,并且在延迟增大的情况下,客户端将加速。

    这里,我要强调的一点是,我们这里的预测执行,是真实逻辑的预测,和很多介绍帧同步文章提到的预测是不同的。有些文章介绍的预测执行,只是view层面的预测,例如前摇动作和位移,但是逻辑是不会提前执行的,还是要等服务器的返回。这两种预测执行(View的预测执行,和真实逻辑的预测执行)是完全不是一个概念的,这里需要仔细地区分。

    帧同步和状态同步的区别

    对于大部分游戏来说,两种同步方式都可以使用。但相比之下状态同步适用型更广,特别适合复杂度高,延迟要求高,玩家多的游戏,例如FPS,MMO等等。帧同步相对适合小兵很多,玩家少且固定,单局时间短,对打击感公平性要求高,追求一致性的游戏,例如格斗,运动,RTS,卡牌,MOBA等。

    从技术角度来说,帧同步有一些技术限制,大量玩家战斗,随时进入退出,难以预表现等,而状态同步有更多的优化手段可以更好的降低延迟感。可以说用帧同步的一定能用状态同步,但反过来不成立。

    当然帧同步也有自己的优势,实现成本相对简单开发比较快速(一套逻辑不太需要联调),在玩家较少小兵较多的情况下(由于只同步事件而非状态,所以网络传输的数据和游戏里的对象数量无关)服务器性能和带宽开销极低,甚至可以没有服务器(服务器可以完全不跑战斗逻辑只在需要反挂的时候跑),有点去中心化的意思。也非常适合一些单机游戏改成联网得游戏,非常适合中小公司(之前开发的一个MOBA游戏只有一个服务器同学)。

    我们在选择的时候需要综合考虑游戏类型,未来需求,战斗时长,游戏模式,网络带宽,延迟响应,防作弊,开发成本周期和实力等因素来选用不同的同步方案,甚至混合使用。没有最好的技术只有最适合的技术

    总结一下:

    1、对于回合制战斗来讲,其实选用哪种方式实现不是特别重要了,因为本身实现难度不是很高,采用状态同步也能实现离线战斗验证。所以采用帧同步的必要性不是很大。

    2、对于单位比较多的RTS游戏一定是帧同步,对于COC来讲,他虽然是离线游戏,但是他在一样输入的情况下是能得到一样结果的,所以也可以认为他是用帧同步方式实现的战斗系统。

    3、对于对操作要求比较高的,例如MOBA类游戏有碰撞(玩家、怪物可以互相卡位)、物理逻辑,纯物理类即时可玩休闲游戏,帧同步实现起来比较顺畅,(有开源的Dphysics 2D物理系统可用 它是Determisti的)。

    4、对于战斗时大地图MMORPG的,一个地图内会有成千上百的玩家,不是小房间性质的游戏,只能使用状态同步,只同步自己视野的状态。

    5、帧同步有个缺点,不能避免玩家采用作弊工具开图。

    展开全文
  • Android状态栏微技巧,带你真正理解沉浸式模式

    万次阅读 多人点赞 2016-08-23 07:32:55
    记得之前有朋友在留言里我写一篇关于沉浸式状态栏的文章,正巧我确实有这个打算,那么本篇就给大家带来一次沉浸式状态栏的微技巧讲解。 其实说到沉浸式状态栏这个名字我也是感到很无奈,真不知道这种叫法是谁先...

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/51763825

    本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注,每天都有文章更新。

    记得之前有朋友在留言里让我写一篇关于沉浸式状态栏的文章,正巧我确实有这个打算,那么本篇就给大家带来一次沉浸式状态栏的微技巧讲解。

    其实说到沉浸式状态栏这个名字我也是感到很无奈,真不知道这种叫法是谁先发起的。因为Android官方从来没有给出过沉浸式状态栏这样的命名,只有沉浸式模式(Immersive Mode)这种说法。而有些人在没有完全了解清楚沉浸模式到底是什么东西的情况下,就张冠李戴地认为一些系统提供的状态栏操作就是沉浸式的,并且还起了一个沉浸式状态栏的名字。

    比如之前就有一个QQ群友问过我,像饿了么这样的沉浸式状态栏效果该如何实现?

    这个效果其实就是让背景图片可以利用系统状态栏的空间,从而能够让背景图和状态栏融为一体。

    本篇文章当中我会教大家如何实现这样的效果,但这个真的不叫沉浸式状态栏。因此,这算是一篇技术+普及的文章吧,讲技术的同时也纠正一下大家之前错误的叫法。

    什么是沉浸式?

    先来分析一下叫错的原因吧,之所以很多人会叫错,是因为根本就不了解沉浸式是什么意思,然后就人云亦云跟着叫了。那么沉浸式到底是什么意思呢?

    根据百度百科上的定义,沉浸式就是要给用户提供完全沉浸的体验,使用户有一种置身于虚拟世界之中的感觉。

    比如说现在大热的VR就是主打的沉浸式体验。

    那么对应到Android操作系统上面,怎样才算是沉浸式体验呢?这个可能在大多数情况下都是用不到的,不过在玩游戏或者看电影的时候就非常重要了。因为游戏或者影视类的应用都希望能让用户完全沉浸在其中,享受它们提供的娱乐内容,但如果这个时候在屏幕的上方还显示一个系统状态栏的话,可能就会让用户分分钟产生跳戏的感觉。

    那么我们来看一下比较好的游戏都是怎么实现的,比如说海岛奇兵:

    海岛奇兵的这种模式就是典型的沉浸式模式,它的整个屏幕中显示都是游戏的内容,没有状态栏也没有导航栏,用户玩游戏的时候就可以完全沉浸在游戏当中,而不会被一些系统的界面元素所打扰。

    然后我们再来看一下爱奇艺的实现:

    同样也是类似的,爱奇艺将整个屏幕作为影视的展示区,用户在看电影的时候眼中就只会有电影的内容,这样就不会被其他一些无关的东西所分心。

    这才是沉浸式模式的真正含义,而所谓的什么沉浸式状态栏纯粹就是在瞎叫,完全都没搞懂“沉浸式” 这三个字是什么意思。

    不过虽然听上去好像是很高大上的沉浸式效果,实际看上去貌似就是将内容全屏化了而已嘛。没错,Android沉浸式模式的本质就是全屏化,不过我们今天的内容并不仅限于此,因为还要实现饿了么那样的状态栏效果。那么下面我们就开始来一步步学习吧。

    隐藏状态栏

    一个Android应用程序的界面上其实是有很多系统元素的,观察下图:

    可以看到,有状态栏、ActionBar、导航栏等。而打造沉浸式模式的用户体验,就是要将这些系统元素全部隐藏,只留下主体内容部分。

    比如说我现在新建了一个空项目,然后修改布局文件中的代码,在里面加入一个ImageView,如下所示:

    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@drawable/bg"
            android:scaleType="centerCrop" />
    
    </RelativeLayout>

    这里将ImageView的宽和高都设置成match_parent,让图片充满屏幕。现在运行一下程序,效果如下图所示。

    如果你将图片理解成游戏或者电影界面的话,那这个体验离沉浸式就差得太远了,至少状态栏和ActionBar得要隐藏起来了吧?没关系,我们一步步进行优化,并且在优化中学习。

    隐藏状态栏和ActionBar的方式在4.1系统之上和4.1系统之下还是不一样的,这里我就不准备考虑4.1系统之下的兼容性了,因为过于老的系统根本就没有提供沉浸式体验的支持。

    修改MainActivity中的代码,如下所示:

    public class MainActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            View decorView = getWindow().getDecorView();
            int option = View.SYSTEM_UI_FLAG_FULLSCREEN;
            decorView.setSystemUiVisibility(option);
            ActionBar actionBar = getSupportActionBar();
            actionBar.hide();
        }
    }

    这里先调用getWindow().getDecorView()方法获取到了当前界面的DecorView,然后调用它的setSystemUiVisibility()方法来设置系统UI元素的可见性。其中,SYSTEM_UI_FLAG_FULLSCREEN表示全屏的意思,也就是会将状态栏隐藏。另外,根据Android的设计建议,ActionBar是不应该独立于状态栏而单独显示的,因此状态栏如果隐藏了,我们同时也需要调用ActionBar的hide()方法将ActionBar也进行隐藏。

    现在重新运行一下程序,效果如下图所示。

    这样看上去就有点沉浸式效果的模样了。

    虽说这才是正统的沉浸式含义,但有些朋友可能想实现的就是饿了么那样的状态栏效果,而不是直接把整个系统状态栏给隐藏掉,那么又该如何实现呢?

    其实也很简单,只需要借助另外一种UI Flag就可以了,如下所示:

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    if (Build.VERSION.SDK_INT >= 21) {
        View decorView = getWindow().getDecorView();
        int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
        decorView.setSystemUiVisibility(option);
        getWindow().setStatusBarColor(Color.TRANSPARENT);
    }
    ActionBar actionBar = getSupportActionBar();
    actionBar.hide();

    首先需要注意,饿了么这样的效果是只有5.0及以上系统才支持,因此这里先进行了一层if判断,只有系统版本大于或等于5.0的时候才会执行下面的代码。

    接下来我们使用了SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN和SYSTEM_UI_FLAG_LAYOUT_STABLE,注意两个Flag必须要结合在一起使用,表示会让应用的主体内容占用系统状态栏的空间,最后再调用Window的setStatusBarColor()方法将状态栏设置成透明色就可以了。

    现在重新运行一下代码,效果如下图所示。

    可以看到,类似于饿了么的状态栏效果就成功实现了。

    再声明一次,这种效果不叫沉浸式状态栏,也完全没有沉浸式状态栏这种说法,我们估且可以把它叫做透明状态栏效果吧。

    隐藏导航栏

    现在我们已经成功实现隐藏状态栏的效果了,不过屏幕下方的导航栏还比较刺眼,接下来我们就学习一下如何将导航栏也进行隐藏。

    其实实现的原理都是一样的,隐藏导航栏也就是使用了不同的UI Flag而已,修改MainActivity中的代码,如下所示:

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    View decorView = getWindow().getDecorView();
    int option = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
            | View.SYSTEM_UI_FLAG_FULLSCREEN;
    decorView.setSystemUiVisibility(option);
    ActionBar actionBar = getSupportActionBar();
    actionBar.hide();

    这里我们同时使用了SYSTEM_UI_FLAG_HIDE_NAVIGATION和SYSTEM_UI_FLAG_FULLSCREEN,这样就可以将状态栏和导航栏同时隐藏了。现在重新运行一下程序,效果如图所示。

    这次看上去好像终于是完全全屏化了,但其实上这离真正的沉浸式模式还差得比较远,因为在这种模式下,我们触摸屏幕的任意位置都会退出全屏。

    这显然不是我们想要的效果,因此这种模式的使用场景比较有限。

    除了隐藏导航栏之外,我们同样也可以实现和刚才透明状态栏类似的效果,制作一个透明导航栏:

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    if (Build.VERSION.SDK_INT >= 21) {
        View decorView = getWindow().getDecorView();
        int option = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
        decorView.setSystemUiVisibility(option);
        getWindow().setNavigationBarColor(Color.TRANSPARENT);
        getWindow().setStatusBarColor(Color.TRANSPARENT);
    }
    ActionBar actionBar = getSupportActionBar();
    actionBar.hide();

    这里使用了SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,表示会让应用的主体内容占用系统导航栏的空间,然后又调用了setNavigationBarColor()方法将导航栏设置成透明色。现在重新运行一下程序,效果如下图所示。

    真正的沉浸式模式

    虽说沉浸式导航栏这个东西是被很多人误叫的一种称呼,但沉浸式模式的确是存在的。那么我们如何才能实现像海岛奇兵以及爱奇艺那样的沉浸式模式呢?

    首先你应该确定自己是否真的需要这个功能,因为除了像游戏或者视频软件这类特殊的应用,大多数的应用程序都是用不到沉浸式模式的。

    当你确定要使用沉浸式模式,那么只需要重写Activity的onWindowFocusChanged()方法,然后加入如下逻辑即可:

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    
        @Override
        public void onWindowFocusChanged(boolean hasFocus) {
            super.onWindowFocusChanged(hasFocus);
            if (hasFocus && Build.VERSION.SDK_INT >= 19) {
                View decorView = getWindow().getDecorView();
                decorView.setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
            }
        }
    
    }

    沉浸式模式的UI Flag就这些,也没什么好解释的,如果你需要实现沉浸式模式,直接将上面的代码复制过去就行了。需要注意的是,只有在Android 4.4及以上系统才支持沉浸式模式,因此这里也是加入了if判断。

    另外,为了让我们的界面看上去更像是游戏,这里我将MainActivity设置成了横屏模式:

    <activity android:name=".MainActivity" 
              android:screenOrientation="landscape">
        ...
    </activity>

    这样我们就实现类似于海岛奇兵和爱奇艺的沉浸式模式效果了,如下图所示。

    可以看到,界面默认情况下是全屏的,状态栏和导航栏都不会显示。而当我们需要用到状态栏或导航栏时,只需要在屏幕顶部向下拉,或者在屏幕右侧向左拉,状态栏和导航栏就会显示出来,此时界面上任何元素的显示或大小都不会受影响。过一段时间后如果没有任何操作,状态栏和导航栏又会自动隐藏起来,重新回到全屏状态。

    这就是最标准的沉浸式模式。


    关注我的技术公众号,每天都有优质技术文章推送。关注我的娱乐公众号,工作、学习累了的时候放松一下自己。

    微信扫一扫下方二维码即可关注:

            

    展开全文
  • 详解以太坊世界状态

    万次阅读 2019-05-12 09:21:15
    以太坊是由多个组成部分构成的。这篇文章旨在解构以太坊,使你能...本文同时和一篇手把手学习指引相关联,它能指导你安装并配置好自己的以太坊私有网络(包括挖矿)。学习之后你将能够执行交易,并且探索以太坊的“...
  • 状态模式

    千次阅读 2018-05-30 10:05:43
    定义: 允许一个对象在其内部...状态模式是通过状态对象来改变动作行为,而不同的状态对象里不同的动作行为表现会不一样,这使得它看起来就像在改变它的类一样。 设计类图: 状态模式中的角色: Context...
  • 同时教大家如何在自己的项目中采用最合适的同步方式。接下来从以下3个方面来阐述: 1: 状态同步的原理与常用的处理方式; 2: 帧同步的原理与常用的处理方式; 3: 哪些游戏适合帧同步,哪些游戏适合状态同步; 这里有...
  • 如果你不熟悉 App 定制型状态通道的话,先读本系列 Part-2,它能给你提供一个很的概览。 什么是广义状态通道? 广义状态通道的意思是,用户可以用同一个通道做多种不同的事情。 广义状态通道有何意义...
  • 游戏开发过程中,各种游戏状态的切换无处不在。但很多时候,简单粗暴的if else加标志位的方式并不能很地道地解决问题,这时,就可以运用到状态模式以及状态机来高效地完成任务。状态模式与状态机,因为他们关联紧密...
  • Spring 有状态bean 无状态bean

    千次阅读 2018-08-16 09:56:15
    但无状态会话bean 并非没有状态,如果它有自己的属性(变量),那么这些变量就会受到所有调用它的用户的影响,这是在实际应用中必须注意的。 1.无状态会话Bean  从字面意思来理解,无状态会话Bean是没有能够...
  • 有限状态机详解(转载)

    万次阅读 多人点赞 2017-08-23 07:34:44
    以前总觉得有限状态机和无限状态机非常的难理解,原来也就是自己一直没有一个直观的认识,今天看到一篇博客,总算对有限状态机入门了。一看就懂。转载地址:...
  •    从本文开始,在之后的一段时间里,我会通过本系列文章,详细介绍如何从零开始用51单片机去实现智能小车的控制,本文作为本系列的第一篇文章,主要介绍如何小车动起来。 一、硬件的选择    1、底盘和电机  ...
  • 关注公众号前端开发博客,领27本电子书回复加群,自助秒进前端群状态管理是前端整天遇到的概念,但是大家是否思考过什么是状态,管理的又是什么呢?我们知道,程序是处理数据的,数据是信息的载体,比如颜色是红色或...
  • 扩容解决方案:状态通道

    万次阅读 2019-05-13 18:19:22
    我们会简单说明此次会议的目标,什么叫做扩容,最近主流的扩容方案,以及对于这些解决方案目前状态的想法。 目标 -会议前的多个团队晚宴- 我们坚信在现有的生态系统中,“扩容仍然是区块链的关键阻碍”。全世界...
  • Git笔记(5) 状态记录

    万次阅读 2020-02-10 20:24:32
    文件的状态变化周期、检查当前文件状态、跟踪新文件、暂存已修改文件、状态简览、忽略文件、查看已暂存和未暂存的修改、提交更新、跳过使用暂存区域、移除文件、移动文件
  • 一,系统设计:有状态、无状态惯例,先看栗子网站登录校验,很普通的一个功能 对于这个功能我们要如何实现?先分析一下登录校验是个啥意思 举个栗子,比如我们在登陆页输入用户名密码,登录了社交网站 这时候想去...
  • 程序员请照顾好自己,周末病魔差点一套带走我。

    万次阅读 多人点赞 2019-12-22 21:18:37
    程序员在一个周末的时间,得了重病,差点当场去世,还及时挽救回来了。
  • 动态规划(一)一一状态定义和状态转移方程

    万次阅读 多人点赞 2019-09-12 10:57:56
    动态规划真人看得头疼,这只是一种思想,并没有一定的解题规律,当问题出现的时候,对于不太熟悉动态规划的人来说,确实有点难以想到,一般都是采用暴力求法。这里贴一个知乎链接,我觉得动态规划讲的还挺好的,...
  • Android状态栏微技巧,动态控制状态栏显示和隐藏

    万次阅读 多人点赞 2017-04-18 15:31:29
    记得之前有朋友在留言里我写一篇关于沉浸式状态栏的文章,正巧我确实有这个打算,那么本篇就给大家带来一次沉浸式状态栏的微技巧讲解。其实说到沉浸式状态栏这个名字我也是感到很无奈,真不知道这种叫法是谁先发起...
  • 单片机的状态机介绍

    万次阅读 多人点赞 2016-05-24 11:50:24
    单片机的葵花宝典 霍宏鹏著 目录 第1章 单片机初试牛刀 1 ...第2章 状态机的通俗解释 3 2.2 状态机具体化 4 第3章 状态机在单片机上的应用 5 3.1 代码实现步骤 5 3.2 应用代码详解 5 第4章 简单的举例 8...
  • 状态机详解(一段式、二段式、三段式)

    万次阅读 多人点赞 2018-12-26 17:13:34
    一、有限状态机FSM(Finite State Machine) 组成元素: 输入、状态状态转移条件、输出。 可以分为两类: Mealy状态机:时序逻辑的输出不仅取决于当前状态,还与输入有关; Moore状态机:时序逻辑的输出只...
  • 手里已经几个 offer 了,而有些人,笔试受挫,面试受挫,自己明明复习了那么久,学习了那么多,特么在笔试就被刷了,有些甚至连笔试都不给,好不容易进入面试环节,自己明明每个问题都回答出来了,但一查状态,才...
  • vue状态管理、Vuex使用详解

    千次阅读 多人点赞 2019-03-02 14:09:47
    state :全局访问的state对象,存放要设置的初始状态名及值(必须要有) mutations :里面可以存放改变 state 的初始值的方法 ( 同步操作--必须要有 ) getters :实时监听state值的变化可对...
  • 说起状态模式游戏开发者们第一个想到的一定是AI的有限状态机FSMs,状态模式确实是实现有限状态机的一种方法。在有限状态机中,一般都是观察者模式与状态模式连用,状态模式把每个状态封装成类,来对每个输入消息...
  • 帧同步和状态同步区别

    千次阅读 多人点赞 2020-09-14 11:26:28
    一、同步 所谓同步,就是要多个客户端表现效果是一致的,例如我们玩王者荣耀的时候,需要十个玩家的屏幕显示...最大的区别就是战斗核心逻辑写在哪,状态同步的战斗逻辑在服务端,帧同步的战斗逻辑在客户端。战斗逻辑是
  • 状态码415解决

    千次阅读 2020-04-25 21:44:59
    状态码415这是个什么鬼,常见的转态码,是200,204,206,301,302,303,304,400,401,403,404,405,500,503这个HTTP 协议原生的状态码,自己项目封装指定的那就另当别论。415很少亮相舞台有点蒙,搜了一圈...
  • 实现一个状态机引擎,教你看清DSL的本质

    万次阅读 多人点赞 2020-03-20 18:53:54
    最近在一个项目中,因为涉及很多状态的流转,我们选择使用状态机引擎来表达状态...一开始我们选用了一个开源的状态机引擎,但我觉得不好用,就自己写了一个能满足我们要求的简洁版状态机,这样比较KISS(Keep It ...
  • 简单层次状态机的C语言实现

    千次阅读 2018-02-09 17:35:29
    简单层次状态机的C语言实现 一年前同学推荐开个博客,还确实有写博客的想法,当时正在过年,可能吃的太了吧,反正后来这事就不了了之。临近年关,突然又想起来这事。工作两年多来也积累了不少东西,不过一直苦于...
  • JavaScript状态模式及状态机模型这是一篇,我自己都看不完的文章…文章大体就两部分: 状态模式的介绍 状态机模型的函数库javascript-state-machine的用法和源码解析 场景及问题背景:我们平时开发时本质上就是对...
  • 为了第一时间体验炸弹的感觉,我还给我妹发了红包,他更新了内测版本,我截了个图。 还有微信的状态,换上短视频后,更是炸裂,效果我上传到 CSDN 的视频库了。 作为程序员,如果你还设置微信

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 787,231
精华内容 314,892
关键字:

如何让自己状态好起来