荏苒追寻个人博客

做一个有追求的青年


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 日程表

Kotlin——泛型

发表于 2023-03-18 | 分类于 Android , Kotlin

泛型

Kotlin 的泛型与 Java 一样,都是一种语法糖,即只在源代码中有泛型定义,到了class级别就被擦除了。 泛型(Generics)其实就是把类型参数化,真正的名字叫做类型参数,它的引入给强类型编程语言加入了更强的灵活性。

泛型的好处

  1. 增强代码的通用性
  2. 熟悉泛型有助于理解其他框架源码
  3. 增加代码的健壮性,避免运行时类型转换异常;
阅读全文 »

Kotlin——协程

发表于 2023-03-18 | 分类于 Android , Kotlin

参考文章

写给Android工程师的协程指南

协程概念

本质上,协程可以称为轻量级线程。协程在 CoroutineScope (协程作用域)的上下文中通过 launch、async 等协程构造器(coroutine builder)来启动。

1
2
3
4
5
6
7
8
9
10
import kotlinx.coroutines.*

fun main() {
GlobalScope.launch { // 在后台启动一个新协程,并继续执行之后的代码
delay(1000L) // 非阻塞式地延迟一秒
println("World!") // 延迟结束后打印
}
println("Hello,") //主线程继续执行,不受协程 delay 所影响
Thread.sleep(2000L) // 主线程阻塞式睡眠2秒,以此来保证JVM存活
}

输出结果

1
2
Hello,
World!

协程作用域

作用域构建器

协程构造器

挂起函数

Android 开源库分析——Fresco源码分析

发表于 2023-03-17 | 分类于 Android , 开源库分析
  • 本文链接:https://rainmonth.github.io/posts/A220317.html
  • 分析版本:Fresco 2.5

基本介绍

包结构

  • drawee
  • fbcore
  • fresco
  • imagepipeline,Fresco的核心模块
  • memory
  • nativeimagefilters
  • nativeimagetranscoder
阅读全文 »

Kotlin——基本概念

发表于 2023-03-17 | 分类于 Android , Kotlin

委托

将自己的事情交给别人做,自己只要关注这件事情处理的记过就好了,这就是委托的过程。

委托属性

属性委托通常是为了解决以下场景的问题

  • 延迟属性(lazy property),其值只在首次访问时计算(首次访问时分配内存)
  • 可观察属性(observable property),监听器或收到有关此属性变更的通知
  • 把多个属性存储在一个映射(map)中,而不是每个存在单独的字段中

为了解决以上几种场景,Kotlin支持属性的委托,基本格式如下:

val/var 属性名:属性类型 by 表达式。在 by 后面的表达式是该 委托, 因为属性对应的 get()(与 set())会被委托给它的 getValue() 与 setValue() 方法。 属性的委托不必实现任何的接口,但是需要提供一个 getValue() 函数(与 setValue()——对于 var 属性)。

委托实现的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import kotlin.reflect.KProperty

class Example {
var p: String by Delegate()
}


class Delegate() {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}

operator fun setValue(thisRef: Any?, property: KProperty<*>, value:String) {
println("$value has been assigned to '${property.name}' in $thisRef")
}

}

fun main() {
val e = Example()
println(e.p)
e.p = "Randy"
}

属性委托的要求:

  1. thisRef类型必须与属性所有者类型相同(或者是其超类型)(对于扩展属性的委托,必须与扩展属性所在类类型相同,或者是其超类型)
  2. property类型必须是KProperty类型或其超类型
  3. value类型必须与属性类型相同或是其超类型
  4. getValue必须返回与属性类型的同的类型或其子类型

注意:lambda表达式返回值总是返回函数体内部最后一行表达式的值

延迟委托 lazy

使用示例:var propertyName by lazy lambda

lazy() 是接受一个 lambda 并返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。

lateinit 和 by lazy比较 (结果由 chatGPT提供)

1
2
3
4
5
6
7
8
9
10
11
lateinit 和 by lazy 都是延迟初始化属性的方式,但它们有以下区别:

lateinit 只能用于 var 变量,而 by lazy 只能用于 val 变量。

lateinit 只适用于可空类型,而 by lazy 可以适用于任何类型。

lateinit 只是延迟初始化,而 by lazy 是惰性初始化,只有在第一次访问属性时才会初始化。

lateinit 变量必须在使用前进行初始化,否则会抛出异常;而 by lazy 变量则只有在第一次访问时才会进行初始化,如果不访问就不会初始化。

因此,如果您需要在声明时初始化变量,就应该使用 lateinit;如果您需要在第一次访问时初始化变量,就应该使用 by lazy。

可观察属性 Observable

主要实现是在Delegates.observable()中,例如:

1
2
var test0: String by Delegates.observable("Randy") { property, oldValue, newValue -> println("${property.name}:$oldValue->$newValue") }
test0 = "Jack"

输出:

1
test0:Randy->Jack

还可以通过Delegate.vetoable来防止属性被更改,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var test: String by Delegates.vetoable("ss") { property: KProperty<*>, oldValue: String, newValue: String ->
ss(
oldValue,
newValue
)
}
println(test)
test = "Jack"
println(test)


fun ss(oldValue: String, newValue: String): Boolean {
return true;
}

当ss函数返回true是,表示允许修改test属性,反之则不允许修改

委托给另一个属性

从1.4起,支持将一个属性的getter和setter方法委托给另一个属性处理,这种委托对顶层和类的属性(成员属性或扩展属性)都可用。通常这种委托方式在 向后兼容 处理是会非常有用。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var topPro3: String = "ss"

class MyClass(var pro1: String, anotherClass: AnotherClass) {
/**
* 将属性委托给自身的另一个属性
*/
var deprecatePro1: String by this::pro1
/**
* 将deprecatePro3委托给灵位一个类 AnotherClass的aPro4属性
*/
var deprecatePro3: String by anotherClass::aPro4

}

class AnotherClass(var aPro4: String)

/**
* 扩展属性委托给顶层属性
*/
var MyClass.deprecateTopPro3: String by ::topPro3

将属性存储在映射中

利用映射来存储 属性的值,非常适用于JSON解析这种场景

1
2
3
4
5
6
7
8
9
10
class TestUser(var map: Map<String, Any?>) {
val name: String by map
val age: Int by map

}

class TestUser2(var map: MutableMap<String, Any?>) {
var name: String by map// name 是读写的,所以使用的 map 必须支持 读写
var age: Int by map// age 是读写的 所以使用的 map 必须支持 读写
}

局部属性委托

1
2
3
4
5
6
7
fun example(computeFoo: () -> Foo) {
val memoizedFoo by lazy(computeFoo)

if (someCondition && memoizedFoo.isValid()) {
memoizedFoo.doSomething()
}
}

上面的代码将局部属性 memoizedFoo 委托给了 computeFoo这个 lambda表达式,这样做的好处是 如果 if 条件不满足,memoizeFoo 根本就不会初始化

委托实现

委托实现就是通过使用组合的方式来代替通过继承的方式来进行一些业务逻辑的视线。

为什么要这样做呢?继承的缺点:

  1. 无法通过继承的方式,重用多个类的代码,(当然可以通过接口实现)
  2. 通过继承,子类必须无条件就收父类的内容,没得选
  3. 父类继承而来的实现是静态的,不能动态改变,虽然可以通过重写做一些改善,但还不够灵活

所以,后面就提出用组合的方式来取代通过继承来实现代码的复用。

闲话不多扯了,来看看Kotlin 中类委托实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Base {
fun print()
}

class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}

class Derived(b: Base) : Base by b

fun main() {
val b = BaseImpl(10)
Derived(b).print()
}

同上上面这种写法,Kotlin编译后会自动生成 Base中定义的方法,同时对象b会存储在 Derived中,看看编译后 decompile来的Java代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public interface Base {
void print();
}

public final class BaseImpl implements Base {
private final int x;

public void print() {
int var1 = this.x;
boolean var2 = false;
System.out.print(var1);
}

public final int getX() {
return this.x;
}

public BaseImpl(int x) {
this.x = x;
}
}

public final class Derived implements Base {
// $FF: synthetic field
private final Base $$delegate_0;

public Derived(@NotNull Base b) {
Intrinsics.checkNotNullParameter(b, "b");
super();
this.$$delegate_0 = b;
}

public void print() {
this.$$delegate_0.print();
}
}

可以看到,Derived 中最终处理 print的都是 $$delegate_0这个属性,这个属性就是通过by 指定的由Kotlin自动生成的。

属性

幕后字段

在Kotlin中, 如果属性至少一个访问器使用默认实现,那么Kotlin会自动提供幕后字段,用关键字field表示,幕后字段主要用于自定义getter和setter中,并且只能在getter 和setter中访问。

幕后属性

对外表现为只读,对内表现为可读可写,我们将这个属性成为幕后属性。

参考文章:什么是幕后字段和幕后属性

SAM接口

只有一个单一抽象方法(但可以有多个非抽象的方法)的接口,这种借口可以直接用lambda函数来实现,从而使代码简洁

扩展

Kotlin 能够对一个类或接口扩展新功能而无需继承该类或者使用像装饰者这样的设计模式。 这通过叫做扩展的特殊声明完成。

扩展函数

声明一个扩展函数需用一个接收者类型也就是被扩展的类型来作为他的前缀。 下面代码为 MutableList<Int> 添加一个swap 函数:

1
2
3
4
5
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // “this”对应该列表
this[index1] = this[index2]
this[index2] = tmp
}

扩展是静态的解析的

扩展不能真正的修改他们所扩展的类。通过定义一个扩展,并没有在一个类中插入新成员, 只不过是可以通过该类型的变量用点表达式去调用这个新函数。

扩展函数是静态分发的,即他们不是根据接收者类型的虚方法。 调用的扩展函数是由函数调用所在的表达式的类型来决定的, 而不是由表达式运行时求值结果决定的。例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 这个例子说明扩展式静态的,s 传入的是Rectangle类型,但定义时用的时Shape类型,所以调用扩展方法时会采用Shape类型的扩展方法
*/
fun main2() {
open class Shape
class Rectangle : Shape()

fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"

fun printClassName(s: Shape) {
println(s.getName())
}

printClassName(Rectangle())
}

可空接收者

可以为可空的接收者定义扩展,这样的扩展可以在对象变量上调用(即使为空也可以调用),例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main3() {
var name:String? = null

println(name.toString())
name = "RandyZhang"

println(name.toString())
}

fun Any?.toString() :String {
if (this == null) {
return "null"
}
return toString()
}

输出结果:

1
2
3
4
null
RandyZhang

Process finished with exit code 0

扩展属性

可以为List扩展一个 lastIndex 属性,用来表示最后一个元素的下标

1
2
3
4
5
6
7
8
9
val <T> List<T>.lastIndex: Int
get() = size - 1
fun main4() {
var list = arrayListOf(1, 3, 5)
var list1 = arrayListOf<Int>()

println("lastIndexOf list: ${list.lastIndex}")
println("lastIndexOf list1: ${list1.lastIndex}")
}

注意:

  1. 扩展属性不能有初始化器
  2. 要注意扩展属性定义的位置(大多数情况下都直接定义在包里)

kotlin单例实现

  1. 对象声明

    1
    2
    3
    4
    5
    6
    object Singleton {
        val property: String = "I am a singleton"
        fun method() {
            println("Method of singleton")
        }
    }
  2. 伴生对象

    1
    2
    3
    4
    5
    6
    7
    8
    class MyClass {
    companion object {
    val property: String = "I am a singleton"
    fun method() {
    println("Method of singleton")
    }
    }
    }

伴生对象的扩展

可以直接用伴生对象所在的类的类名访问伴生对象的扩展方法和扩展属性

1
2
3
4
5
6
7
8
9
10
11
class MyCompanion {
companion object
}

fun MyCompanion.Companion.printCompanion() {
println("companion")
}

fun main5() {
MyCompanion.printCompanion()
}
  • 扩展声明为成员

    这个好像比较复杂

数据类

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 数据类
* 1. 主构造函数至少要有一个var或val修饰的属性
* 2. 会利用主构造函数里面的属性 自动生成对应的 hashCode、equals、和toString方法
* 3. 会按照中构造函数中声明的顺序,生成相应的considerN方法
* 4. 支持通过copy来产生对象,并对对象进行修改
* 5. 不能为considerN、copy提供显示的实现
* 6. 数据类不能是抽象、开放、密封或内部的
*/
data class User(var id: Int, var name: String) {
var state: String? = null
}

生成的.class文件如下:

1
2
3
4
5
6
7
8
9
10
11
public final data class User public constructor(id: kotlin.Int, name: kotlin.String) {
public final var id: kotlin.Int /* compiled code */

public final var name: kotlin.String /* compiled code */

public final var state: kotlin.String? /* compiled code */

public final operator fun component1(): kotlin.Int { /* compiled code */ }

public final operator fun component2(): kotlin.String { /* compiled code */ }
}

密封类

1
sealed class Error

密封类用来表示受限的类继承结构:当一个值为有限几种的类型、而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。

密封类作为when的判断条件时,是不需要再最后写else判断的,因为密封类的值集合是受限的

嵌套类

普通类中定义的一个无特殊修饰关键字的类就成为嵌套类、嵌套接口

1
2
3
4
5
6
7
8
class Outer {
private val bar: Int = 1
class Nested {
fun foo() = 2
}
}

val demo = Outer.Nested().foo() // == 2

嵌套接口

1
2
3
4
5
6
7
8
9
interface OuterInterface {
class InnerClass
interface InnerInterface
}

class OuterClass {
class InnerClass
interface InnerInterface
}

内部类

类内部定义一个用 inner关键字修饰的类,这个inner关键字修饰的类就成为内部类,同Java一样,内部类会持有外部类的引用

1
2
3
4
5
6
7
8
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}

val demo = Outer().Inner().foo() // == 1

匿名内部类

匿名内部类通常用对象表达式表示

1
2
3
4
5
6
window.addMouseListener(object : MouseAdapter() {

override fun mouseClicked(e: MouseEvent) { …… }

override fun mouseEntered(e: MouseEvent) { …… }
})

作用域函数

  • let,扩展函数,调用let中使用的函数参数,并将调用let的对象作为参数,然后返回函数参数的结果
  • with,非扩展函数,将上下文对象作为参数传递到lambda表达式中,在lambda表达式中可以同伙this来访问这个上下文对象
  • run(非扩展形式的),就是开辟一个临时作用域来运行一段代码,并将运行代码的结果返回
  • run(扩展形式的),将上下文对象传递到lambda表达式中,在lambda表达式中可以通过this来访问这个对象,同时将lambda的结果返回
  • apply,扩展函数,对上下文对象应用一些列操作,并将对象本身返回
  • also,扩展函数,可以理解为在做某个操作时顺带做下某事,上下文对象会作为参数用(it)表示,传递到lambda中,并最终在lambda执行后返回上下文对象本身

takeIf和takeUnless

作为作用域函数的补充,这两个函数配合作用域函数使得可以作用域函数调用链中进行值的检查与筛选

  • takeIf,如果满足给定的条件,就返回对象本身,否则返回null

  • takeUnless,和takeIf相反,如果不满足,就返回对象本身,否则返回null

其他

相等判断

  • 构成相等使用 == 运算符,并调用 equals() 来确定两个实例是否相等。
  • 引用相等使用 === 运算符,以检查两个引用是否指向同一对象。
  • ?,左边调用对象如果为空,则返回null,不为空正常执行(空安全调用)
  • !!,左边对象如果为空,则抛出NPE,否则正常执行(确定不为空)
  • ?:,elvis 表达式,例子

    1
    2
    3
    var firstName : String?
    ...
    println(firstName?:"Unknown")// firstName 不为空则输出firstName,为空则输出Unknown

默认参数于具名参数

假如定义了以下数据类:

1
data class User(var firstName: String? = null, var lastName: String?)

那么,主构造函数中的 firstName参数就是默认参数,具体使用例子如下:

1
2
3
var user1 = User("randy")// 这种写法是错误,由于默认参数将居于未设默认值的参数之前,使用时必须
// 指定参数的名字
var user2 = User(lastName = "randy")

Android 软键盘相关知识点梳理

发表于 2023-01-05 | 分类于 Android

Android 软键盘相关知识点梳理

先说说开发过程中遇到的几个常用的软键盘场景

场景一

屏幕存在多个输入框,点击输入框,软键盘不遮挡输入框

使用adjustPan有效,使用adjustResize无效(找出无效的原因,搜索到的结果都是说改这个)

场景二

WebView界面存在输入框,点击输入框,软键盘不遮挡输入框

场景三

点击非输入框部分,隐藏软键盘

分两步:

  1. 判断touch的是不是输入框View对应的区域;
  2. 重写Activity的dispatchTouchEvent方法,触摸的不是输入框对应View的时候,隐藏软键盘;

场景四

进入带输入框的页面,自动弹出软键盘

知识储备

软键盘控制

Android 软键盘的控制主要通过在AndroidManifest.xml文件声明activity时添加 android:windowSoftInputMode属性或者在代码中在Activity的onCreate函数调用setContentView之前通过修改Window的getWindow().setSoftInputMode(int mode)方法来控制,代码示例如下:

  1. AndroidManifest.xml文件中

    1
    2
    3
    4
    <activity android:name=".module.login.LoginActivity"
    ...
    ...
    android:windowSoftInputMode="adjustPan" />
  2. 代码中

    1
    2
    3
    4
    5
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
    }

注意:在AndroidMainfest.xml文件中在此Activity中写入 android:windowSoftInputMode="adjustPan" 可以让界面不被弹出的键盘挤上去;

不管是通过xml文件还是通过代码,效果都是一样的,这个属性控制着软键盘和Activity交互的两种状态:

  1. 软键盘的状态——当活动(Activity)成为用户关注的焦点时软键盘是隐藏或显示,对应于state开头的属性值。
  2. 活动的主窗口调整——窗口获取焦点后弹出软键盘时窗口讲如何调整(是否减少活动主窗口大小以便腾出空间放软键盘,是否直接让软键盘覆盖活动窗口,对应于adjust开头的属性值。

参考文章:

  1. 5种方法完美解决android软键盘挡住输入框方法详解

Unity基础——C#问题点记录

发表于 2022-11-12 | 分类于 C#
  1. 如何定义C#特性,类似于Java的注解?
  2. 如何实现C#泛型单例?
  3. C#协程的概念及C#协程的理解?
  4. Unity中可见的GameObject都有SortingLayer和OrderInLayer属性,两个属性决定了元素的渲染顺序
    1. 通过SortingLayer可以设置GameObject对应的层,通过这个层,可以统一控制这一层的GameObject,可以自己添加自定义的层,数字大的层显示在上面;
    2. OrderInLayer表示的时SortingLayer的内部排序;
  5. CreateAssetMenu,Unity属性,对 ScriptableObject 派生类型进行标记,使其自动列在 Assets/Create 子菜单中,以便能够轻松创建该类型的实例并将其作为“.asset”文件存储在项目中。
  6. ScriptObject, 如果需要创建无需附加到游戏对象的对象时,可从该类派生。它对仅用于存储数据的资源最有用。
  7. C# event的使用?
  8. 拉入Unity的精灵尺寸和实际的不一致?
  9. 过渡效果不一致
  10. 运行老项目,提示No cloud project ID was found by the Analytics SDK

    Edit->Project Settings,左侧选择 Service, 然后选择自己的证书,然后创建一个ProjectId 即可。

Unity基础——摄像机

发表于 2022-11-11 | 分类于 Unity

摄像机

Unity通过摄像机来实现将三维游戏对象捕获并将其平面化显示到二维屏幕这一过程。主要通过Camera Component来实现这一功能,所以Unity中摄像机的创建都是通过Camera Component来实现的。

Camera组件

先看看摄像机组件在Inspector中支持的属性设置。Unity Camera Inspector会根据当前项目使用的渲染管线(Render-Pipeline)来显示不同的属性。

  • 使用通用渲染管线(URP)

  • 使用高清渲染管线(HDRP)

  • 使用内置渲染管线,Unity会显示以下属性:

    • ClearFlag,确定将清除屏幕的哪些部分。(在使用多摄像机来绘制不同元素时会非常方便)

    • Background,在绘制视图中的所有元素之后但没有天空盒的情况下,应用于剩余屏幕部分的颜色。

    • Culling Mask,剔除蒙版,包含或忽略要由摄像机渲染的对象层。在检视面板中将层分配到对象。

    • Projection,模拟透视功能,不同的视图会有不同的设置选项,具体如下:

      • Perspective,透视视图,摄像机将按透视角度渲染对象,有近大远小的特点;

        • FOV Axis,摄像机视野轴,有以下两个值:
          • Horizontal,摄像机使用水平视野轴;
          • Vertical,摄像机使用垂直视野轴;
        • Field of View,摄像机视角(沿视野轴的的度数)
        • Physical Camera,物理相机,勾选后可为摄像机启用该属性,该属性支持模拟真实物理相机的属性,具体有:
          • Focus Length,设置摄像机传感器和摄像机镜头之间的距离(以毫米为单位)。较小的值产生更宽的 Field of View,反之亦然。更改此值时,Unity 会相应自动更新 Field of View 属性。
          • Sensor Type,设置模拟物理相机传感器的类型,更新后,下面的Sensor size的x、y值会自动更新
          • Sensor Size,模拟传感器的大小,有x和y两个值,可以自己设定,淡然选定Sensor Type后这个会自动更新到合适的值;
          • Length Shift,略;
          • Gate Fit,略;
      • Orthographic,正交视图,摄像机将均匀暄软对象,无透视感。正交模式下不支持延时渲染,始终是向前渲染;

        • Size,正交视图事业的大小(仅正交模式下游这个属性)

          注意:摄像机高度,Game视图的显示比例决定宽度;

    • Clipping Planes,直译为裁剪的平面,其实指的就是摄像机渲染的范围,有一个最近值Near(相对摄像机最近绘制点)和最远值Far(相对摄像机最远绘制点)

    • Viewport Rect,视口范围,显然是一个方位,由x、y、width、height决定;

    • Depth,摄像机在绘制顺序中的位置。具有更大值的摄像机将绘制在具有更小值的摄像机之上。

    • Rendering Path,定义摄像机将使用的渲染方法的选项。

      • Use Player Settings,使用 Player Settings 中设置的任何渲染路径 (Rendering Path);

      • Vertex Lit,所有对象都将渲染为顶点光照对象。

      • Forward, 每种材质采用一个通道渲染所有对象。

      • Deferred Lighting,将在没有光照的情况下一次性绘制所有对象,然后在渲染队列末尾一起渲染所有对象的光照。

        注意:如果摄像机的投影模式设置为 Orthographic,则会覆盖该值,并且摄像机将始终使用前向渲染。

    • Target Texture,将摄像机视图渲染内容输出到指定的渲染纹理。设置此引用将禁用此摄像机的渲染到屏幕功能。

    • Occlusion Culling,为此摄像机启用遮挡剔除 (Occlusion Culling)。遮挡剔除意味着隐藏在其他对象后面的对象不会被渲染,例如,如果对象在墙后面。请参阅遮挡剔除 (Occlusion Culling) 以了解详细信息。

    • Allow HDR,用高动态范围渲染。

    • Allow MSAA,启用多重采样抗锯齿。

    • Allow Dynamic Resolution,启用动态分辨率渲染;

    • Target Display,定义要渲染到的外部设备。值为 1 到 8 之间。

摄像机组件,

常见游戏的主Camera位置设置

Unity基础——Create User Interface

发表于 2022-10-13 | 分类于 Unity

系列文章

  • Unity基础——音频
  • Unity基础——视频概述
  • Unity基础——物理系统
  • Unity基础——动画
  • Unity基础——Create User Interface

Unity基础——动画

发表于 2022-10-12 | 分类于 Unity

系列文章

  • Unity基础——音频
  • Unity基础——视频概述
  • Unity基础——物理系统
  • Unity基础——动画
  • Unity基础——Create User Interface
  • Unity基础——Camera

动画使用的一般步骤

Unity 本身可以制作简单的动画,一般步骤如下:

  1. Project 面板下找到Assets文件夹,创建Animations文件夹用以存放管理动画文件;

  2. Animations文件夹右键,选择Create,新建 Animation;

  3. 在Hierarchy 中选择一个GameObject,将新建的 Animationti 添加到这个 GameObject上;

  4. 然后选择这个GameObject,在Inspector面板中就可以看到Animator组件了,这个Animator组件就是 动画的播放器,而这个选择的GameObject就成了 动画的控制器;

  5. 双击打开这个 动画控制器(即Animator组件的第一个属性),就可以进行动画控制器(其实就是一个状态机)的编辑,可以新建不同的状态;

    动画控制器打开 会有几个默认的矩形,代表几种不同的动作,其中:

    • Entry 表示进入;

    • Exit 表示退出;

    • 橙黄色的矩形块表示默认的状态;

  6. 点击上面关联了动画的GameObject对象,然后打开Animation窗口(Window->Animation->Animation,或者直接快捷键 CMD+6),这样就可以编辑制作 Animation了。

  7. Animation窗口,左边为添加属性窗口,右边为时间线窗口(添加属性前,请先选择要编辑哪个Animation,窗口左边顶部有一个下拉框,可以选择具体哪个Animation),点击Add Property按钮,可以选择要对GameObject下的哪个属性(或者哪个子物体的属性)进行动画


Animator组件

使用Animator组件可以将动画分配给场景中的游戏对象。


Animator相关属性

  • Controller, 通常是AnimatorController,用来定义动画要使用那些剪辑(动画片段),以及如何在动画中组织这些剪辑(动画片段),如何过渡;
  • Avatar,游戏对象为人形角色时,还要在此组件中分配Avatar

AnimatorController


Unity基础——物理系统

发表于 2022-10-11 | 分类于 Unity

系列文章

  • Unity基础——音频
  • Unity基础——视频概述
  • Unity基础——物理系统
  • Unity基础——动画
  • Unity基础——Create User Interface

物理系统

Unity可以在游戏中模拟使用物理系统,以确保对象正确加速并对碰撞、重力和各种其他力做出响应。Unity 提供了以下不同的物理引擎实现方案,您可以根据自己的项目需求选用:3D、2D、面向对象或面向数据。

面向对象的项目内置的物理引擎

  • 内置3D物理系统,集成了Nvidia PhysX引擎;
  • 内置2D物理系统,集成Box2D引擎;

面向数据的项目物理引擎包

如果项目中使用的时面向数据的技术堆栈(DOTS),则需要安装专用的DOTS物理包,可用的包括:

  • Unity Physics包:默认需要安装的 DOTS 物理引擎,用于在任何面向数据的项目中模拟物理系统。
  • Havok Physics for Unity包:适用于 Unity 的 Havok 物理引擎的实现方案,用作 Unity Physics 包的扩展。请注意,此包受制于特定的许可方案。

内置3D物理引擎

Unity内置物理引擎集成的是Nvidia PhysX,使用该引擎,可以方用以下内容来进行物理系统的模拟:

  • CharacterControl,角色控制,为第一人称及第三人称角色听基础的物理配置;
  • Rigidbody Physics,刚体物理,为GameObject赋予物理行为;
  • Collision,碰撞体,使用碰撞体来完成多个GameObject间的碰撞行为物理模拟;
  • Joints,应用并配置连接GameObjects并模拟旋转、移动和限制的物理力的关节;
  • Articulations,配置刚体和关节组成的复杂物理系统;
  • Ragdoll Physics,为角色配置布娃娃物理;
  • Cloth,布料系统,用来模拟人物服装和其他应用纺织品的织物运动;
  • Multi-scene Physics,使用多个专用物理场景在一个项目中管理不同的物理环境;

CharacterControl

第一人称和第三人称视角的游戏中,游戏角色通常需要一直基础的物理碰撞属性支持,这样游戏角色就不会从地板上掉下去或者不会穿墙。在3D应用中,可以通过CharacterControl来创建并配置一个这样的游戏角色,主要通过CharacterController和CharacterControl 组件来配置。

CharacterController控制器

CharacterController的属性

区别于直接用Transform或者RigidBody,CharacterController有着更好的效果,它拥有RigidBody的一些重要特性,但是又去掉了很多物理效果,这样可以避免诸如穿模,滑步,被撞飞或者将其他物体撞位移等情况。主要属性如下:

  • Slope limit,斜度限制,代表的是角色能爬上的最大坡度;

  • Step offset,每部偏移量,其实就是能走上的台阶的高度。这其实是很有用的一个属性,走上一些高度不是很高的台阶或者石头之类的东西不再需要跳跃,这也更符合实际的移动情况。

    这个值有一个限制即该值必须小于等于(高度+半径*2)(上分图片中的最下方两个值),这个设定其实很好理解,毕竟你一个一米七的人不可能一步跨上一米多的台阶吧

  • Skin width,蒙皮宽度,碰撞体外层再添加一层,用来放置衣服等装饰物品;

    1. 要正确调整角色控制器,Skin Width 属性是最重要的属性之一。 如果角色被卡住,那么很可能是因为 Skin Width 设置过小。Skin Width 允许对象轻微穿透控制器,但可消除抖动并防止被卡住。

    2. 最好是让 Skin Width 的值至少大于 0.01 并且比 Radius 的值大 10%。

    3. 两个碰撞体可以穿透彼此且穿透深度最多为皮肤宽度 (Skin Width)。较大的皮肤宽度可减少抖动。较小的皮肤宽度可能导致角色卡住。合理设置是将此值设为半径的 10%。
  • Min move Distance,最小移动距离

    1. 建议将 Min Move Distance 保持为 0。
    2. 此设置可以用来减少抖动。在大多数情况下,此值应保留为 0。
  • Center,碰撞体的中心

  • Radius,碰撞体的半径

  • Height,碰撞体的高度

CharacterController的操作方法

<i class="fa fa-angle-left" aria-label="上一页"></i>1…567…22<i class="fa fa-angle-right" aria-label="下一页"></i>

216 日志
43 分类
43 标签
GitHub
© 2025 Randy Zhang
由 Hexo 强力驱动
|
主题 — NexT.Gemini v6.1.0