【UE4 C++】编程子系统 Subsystem
概述
定义
- Subsystems 是一套可以定义、自动实例化和释放的类的框架。可以将其理解为 GamePlay 级别的 Component
- 不支持网络赋值
- 4.22开始引入,4.24完善。(可以移植源码到更早之前的版本使用。源码在手,为所欲为)
五类 Subsystems 及其生命周期
UEngineSubsystem(继承自 UDynamicSubsystem,UDynamicSubsystem继承自 USubsystem)
UEngine* GEngine
代表引擎,数量1。 Editor或Runtime模式都是全局唯一,从进程启动开始创建,进程退出时销毁。UEngine::Init()
UEditorSubsystem(继承自 UDynamicSubsystem,UDynamicSubsystem继承自 USubsystem)
UEditorEngine* GEditor
代表编辑器,数量1。 顾名思义,只在编辑器下存在且全局唯一,从编辑器启动开始创建,到编辑器退出时销毁。UGameInstanceSubsystem (继承自 USubsystem)
UGameInstance* GameInstance
代表一场游戏,数量1。 从游戏的启动开始创建,游戏退出时销毁。这里的一场游戏指的是Runtime或PIE模式的运行的都算,一场游戏里可能会创建多个World切换。UWorldSubsystem (继承自 USubsystem)
UWorld* World
代表一个世界,数量可能>1。其生命周期,跟GameMode是一起的。(EWorldType:Game,Editor,PIE,EditorPreview,GamePreview等 )ULocalPlayerSubsystem (继承自 USubsystem)
ULocalPlayer* LocalPlayer:代表本地玩家,数量可能>1。
UE支持本地分屏多玩家类型的游戏,但往往最常见的是就只有一个。LocalPlayer虽然往往跟PlayerController一起访问,但是其生命周期其实是跟UGameInstance一起的(默认一开始的时候就创建好一定数量的本地玩家),或者更准确的说是跟LocalPlayer的具体数量挂钩(当然你也可以运行时动态调用AddLocalPlayer)。
为什么要使用 Subsystems
- 更适用的生命周期
- 引擎只支持一个 GameInstance ,运行周期是整个引擎的生命周期
- 自定义 ManagerActor,生命周期一般为当前 level 的生命周期
- Subsystems 的生命周期可以依存于Engine,Editor,World,LocalPlayer
- 更简
- GameInstance 或者自定义 ManagerActor,需要手动维控制创建、释放
- Subsystems 自动创建、释放,提供 Initialize()、Deinitialize(),并且可重载
- 更模块化、更优雅、更封装、更易于维护、移植复用
- GameInstance 中将任务系统,计分系统,经济系统、对话系统等多个Manager 写在一起,会变得臃肿
- 模块间的数据访问封装不够良好,容易污染
- 不利于业务逻辑模块的复用,特别是需要进行移植的时候,以及多个插件同时都有自己的 GameInstance
- Subsystems 可以为不同的 Manager 创建对应的Subsystems
- 如 Manager 划分,
- 任务系统Subsystem : UGameInstanceSubsystem
- 计分系统Subsystem : UGameInstanceSubsystem
- 经济系统Subsystem : UGameInstanceSubsystem
- 对话系统Subsystem : UGameInstanceSubsystem
- 更模块化,代码显得优雅
- 解耦性高,易于维护、分工协作;易于移植复用
- 模块间的数据访问具有更好的封装性
- 如 Manager 划分,
- GameInstance 中将任务系统,计分系统,经济系统、对话系统等多个Manager 写在一起,会变得臃肿
- 更友好的访问接口
- Subsystem 更像以全局变量的形式来访问
- 提供了 Python 脚本的访问,用于编写编辑器脚本或编写测试代码
- Subsystem 无需覆盖引擎类。
Subsystems 的使用
创建过程,涉及 FSubsystemCollectionBase
FSubsystemCollectionBase::Initialize()
FSubsystemCollectionBase::AddAndInitializeSubsystem()
FSubsystemCollectionBase::Deinitialize()
参考附录源码 或者
EngineDir\Engine\Source\Runtime\Engine\Private\Subsystems\SubsystemCollection.cpp
默认重载函数
- ShouldCreateSubsystem
- Initialize()
- Deinitialize()
C++ 访问
// UMyEngineSubsystem 获取
UMyEngineSubsystem* MyEngineSubsystem = GEngine->GetEngineSubsystem<UMyEngineSubsystem>(); // UMyEditorSubsystem 获取
UMyEditorSubsystem* MyEditorSubsystem = GEditor->GetEditorSubsystem<UMyEditorSubsystem>(); // UMyGameInstanceSubsystem 获取
//UGameInstance* GameInstance = GetWorld()->GetGameInstance();
UGameInstance* GameInstance = UGameplayStatics::GetGameInstance();
UMyGameInstanceSubsystem* MyGameInstanceSubsystem = GameInstance->GetSubsystem<UMyGameInstanceSubsystem>(); // UMyWorldSubsystem 获取
UMyWorldSubsystem* MyWorldSubsystem = GetWorld()->GetSubsystem<UMyWorldSubsystem>(); // UMyLocalPlayerSubsystem 获取
ULocalPlayer* LocalPlayer = UGameplayStatics::GetPlayerController()->GetLocalPlayer();
UMyLocalPlayerSubsystem* MyLocalPlayerSubsystem = LocalPlayer->GetSubsystem<UMyLocalPlayerSubsystem>();
- 引擎自带 USubsystemBlueprintLibrary 访问方法
UCLASS()
class ENGINE_API USubsystemBlueprintLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY() public: /** Get a Game Instance Subsystem from the Game Instance associated with the provided context */
UFUNCTION(BlueprintPure, Category = "Engine Subsystems", meta = (BlueprintInternalUseOnly = "true"))
static UEngineSubsystem* GetEngineSubsystem(TSubclassOf<UEngineSubsystem> Class); /** Get a Game Instance Subsystem from the Game Instance associated with the provided context */
UFUNCTION(BlueprintPure, Category = "GameInstance Subsystems", meta = (WorldContext = "ContextObject", BlueprintInternalUseOnly = "true"))
static UGameInstanceSubsystem* GetGameInstanceSubsystem(UObject* ContextObject, TSubclassOf<UGameInstanceSubsystem> Class); /** Get a Local Player Subsystem from the Local Player associated with the provided context */
UFUNCTION(BlueprintPure, Category = "LocalPlayer Subsystems", meta = (WorldContext = "ContextObject", BlueprintInternalUseOnly = "true"))
static ULocalPlayerSubsystem* GetLocalPlayerSubsystem(UObject* ContextObject, TSubclassOf<ULocalPlayerSubsystem> Class); /** Get a World Subsystem from the World associated with the provided context */
UFUNCTION(BlueprintPure, Category = "GameInstance Subsystems", meta = (WorldContext = "ContextObject", BlueprintInternalUseOnly = "true"))
static UWorldSubsystem* GetWorldSubsystem(UObject* ContextObject, TSubclassOf<UWorldSubsystem> Class); /**
* Get a Local Player Subsystem from the LocalPlayer associated with the provided context
* If the player controller isn't associated to a LocalPlayer nullptr is returned
*/
UFUNCTION(BlueprintPure, Category = "LocalPlayer Subsystems", meta = (BlueprintInternalUseOnly = "true"))
static ULocalPlayerSubsystem* GetLocalPlayerSubSystemFromPlayerController(APlayerController* PlayerController, TSubclassOf<ULocalPlayerSubsystem> Class); private:
static UWorld* GetWorldFrom(UObject* ContextObject);
};
UGameInstanceSubsystem
用法一
支持委托
支持普通变量和函数
支持蓝图调用
UCLASS()
class DESIGNPATTERNS_API UScoreGameInsSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
// 是否允许被创建
virtual bool ShouldCreateSubsystem(UObject* Outer) const override { return true; }
// 初始化
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
// 释放
virtual void Deinitialize() override; // 声明委托
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FScoreChanged, int32, CurrentScore);
UPROPERTY(BlueprintAssignable)
FScoreChanged ScoreChange; UFUNCTION(BlueprintCallable, Category = "MySubsystem | ScoreGameInsSubsystem")
int32 AddScore(int32 BaseScore);
private:
int32 Score;
};
void UScoreGameInsSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__""));
} void UScoreGameInsSubsystem::Deinitialize()
{
Super::Deinitialize();
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__""));
} int32 UScoreGameInsSubsystem::AddScore(int32 BaseScore)
{
Score = UKismetMathLibrary::Max(0, Score + BaseScore);
// 调用委托
ScoreChange.Broadcast(Score);
return Score;
}
用法二
支持创建抽象类,多个派生类,支持蓝图继承,支持遍历访问
- 注意UCLASS(Type)
- 父类为 Abstract抽象类,防止实例化
- Blueprintable 蓝图继承
- BlueprintType 可定义蓝图变量
- 注意每种类型的 Subsystem 只能创建一个实例
- 注意UCLASS(Type)
C++访问
- 访问单个
GetWorld()->GetGameInstance()->GetSubsystem<T>()
- 访问多个
GetWorld()->GetGameInstance()->GetSubsystemArray<T>()
- 访问单个
使用示例
/**
* 抽象类 USourceControlSubsystem
*/
UCLASS(Abstract, Blueprintable, BlueprintType)
class DESIGNPATTERNS_API USourceControlSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
// ShouldCreateSubsystem 默认返回 True // 可重载
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "MySubsystem | SourceControl")
FString GetPlatformName();
}; /**
* 派生类 UGitSubsystem
*/
UCLASS()
class DESIGNPATTERNS_API UGitSubsystem : public USourceControlSubsystem
{
GENERATED_BODY()
public: // 初始化
virtual void Initialize(FSubsystemCollectionBase& Collection) override {
Super::Initialize(Collection);
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"【%s】"),*GetName());
} // 释放
virtual void Deinitialize() override {
Super::Deinitialize();
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"【%s】"),*GetName());
} virtual FString GetPlatformName_Implementation()override {
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"【Git】"));
return TEXT("Git");
} void Help() {
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" Help Command"));
}
}; /**
* 派生类 USVNSubsystem
*/
UCLASS()
class DESIGNPATTERNS_API USVNSubsystem : public USourceControlSubsystem
{
GENERATED_BODY()
public: // 初始化
virtual void Initialize(FSubsystemCollectionBase& Collection) override {
Super::Initialize(Collection);
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"【%s】"), *GetName());
} // 释放
virtual void Deinitialize() override {
Super::Deinitialize();
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"【%s】"), *GetName());
} virtual FString GetPlatformName_Implementation()override {
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"【SVN】"));
return TEXT("SVN");
}
}; /**
* 用于测试 ASourceControlActor
*/
UCLASS(Blueprintable, BlueprintType)
class DESIGNPATTERNS_API ASourceControlActor : public AActor
{
GENERATED_BODY()
public:
ASourceControlActor(){} virtual void BeginPlay()override {
Super::BeginPlay(); // 获取单个 Subsystem
UGitSubsystem* GitSubsystem = GetWorld()->GetGameInstance()->GetSubsystem<UGitSubsystem>();
GitSubsystem->Help(); // 获取多个 Subsystem,继承自同个抽象类
const TArray<USourceControlSubsystem*> SourceControlSubsystems = GetWorld()->GetGameInstance()->GetSubsystemArray<USourceControlSubsystem>();
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"********************* 遍历USourceControlSubsystem ********************"));
for (USourceControlSubsystem* ItemSubSystem : SourceControlSubsystems)
{
ItemSubSystem->GetPlatformName();
}
}
};
蓝图调用
其他用法
支持Tick, 需继承 FTickableGameObject,参考 UAutoDestroySubsystem 写法
UCLASS()
class DESIGNPATTERNS_API UScoreGameInsSubsystem : public UGameInstanceSubsystem, public FTickableGameObject
{
GENERATED_BODY()
public: virtual void Tick(float DeltaTime)override{ UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__"Ping Pong"));}
virtual bool IsTickable()const override { return !IsTemplate(); } //判断是否是 CDO,避免执行两次 Tick
virtual TStatId GetStatId() const override { RETURN_QUICK_DECLARE_CYCLE_STAT(UMyScoreSubsystem, STATGROUP_Tickables); }
};
支持 Subsystem 之间的访问
支持多个Subsystem定义依赖顺序,再初始化时调用
Collection.InitializeDependency(UScoreGameInsSubsystem::StaticClass());
UWorldSubsystem
可以再编辑器 和 运行时 使用
UCLASS()
class DESIGNPATTERNS_API UMyWorldSubsystem : public UWorldSubsystem
{
GENERATED_BODY()
public:
// 是否允许被创建
virtual bool ShouldCreateSubsystem(UObject* Outer) const override { return true; }
// 初始化
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
// 释放
virtual void Deinitialize() override; FString GetWorldType();
};
void UMyWorldSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" [World] %-20s\t[Type] %s\t"), *GetWorld()->GetName(), *GetWorldType());
} void UMyWorldSubsystem::Deinitialize()
{
UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" [World] %-20s\t[Type] %s\t"), *GetWorld()->GetName(), *GetWorldType());
} FString UMyWorldSubsystem::GetWorldType()
{
FString WorldTypeName;
switch (GetWorld()->WorldType) {
case EWorldType::None: {WorldTypeName = TEXT("None"); }break;
case EWorldType::Game: {WorldTypeName = TEXT("Game"); }break;
case EWorldType::Editor: {WorldTypeName = TEXT("Editor"); }break;
case EWorldType::PIE: {WorldTypeName = TEXT("PIE"); }break;
case EWorldType::EditorPreview: {WorldTypeName = TEXT("EditorPreview"); }break;
case EWorldType::GamePreview: {WorldTypeName = TEXT("GamePreview"); }break;
case EWorldType::GameRPC: {WorldTypeName = TEXT("GameRPC"); }break;
case EWorldType::Inactive: {WorldTypeName = TEXT("Inactive"); }break;
default: WorldTypeName = TEXT("default");
};
return WorldTypeName;
}
UEditorSubsystem
需要再 .build.cs 添加 EditorSubsystem 模块
if (Target.bBuildEditor)
{
PublicDependencyModuleNames.AddRange(new string[] { "EditorSubsystem" });
}
需要添加头文件
编译模式记得选Editor 模式
#include "EditorSubsystem.h"
UCLASS()
class DESIGNPATTERNS_API UMyEditorSubsystem : public UEditorSubsystem
{
GENERATED_BODY()
public: // 是否允许被创建
virtual bool ShouldCreateSubsystem(UObject* Outer) const override { return true; }
// 初始化
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
// 释放
virtual void Deinitialize() override; };
系统自带 Subsystem 使用蓝图示例
Asset Editor Subsystem
Asset Tag Subsystem
Editor Utility Subsystem
Layer Subsystem
Import Subsystem
Editor Validator Subsystem
检查、验证资产
从 EditorUtilityBlueprint 中选择父类 EditorValidatorBase
参考
- 编程子系统
- 【B站】[英文直播]Programming Subsystems(真实字幕组)
- 《InsideUE4》GamePlay架构(十一)Subsystems
- 【PPT】猫でも分かるUE4.22から入ったSubsystem
- Does 'Data Validation' not work for Blueprints?
附录
SubsystemCollection.cpp
- FSubsystemCollectionBase::Initialize()
- FSubsystemCollectionBase::AddAndInitializeSubsystem()
- FSubsystemCollectionBase::Deinitialize()
void FSubsystemCollectionBase::Initialize(UObject* NewOuter)
{
if (Outer != nullptr)
{
// already initialized
return;
} Outer = NewOuter;
check(Outer);
if (ensure(BaseType) && ensureMsgf(SubsystemMap.Num() == 0, TEXT("Currently don't support repopulation of Subsystem Collections.")))
{
check(!bPopulating); //Populating collections on multiple threads? if (SubsystemCollections.Num() == 0)
{
FSubsystemModuleWatcher::InitializeModuleWatcher();
} TGuardValue<bool> PopulatingGuard(bPopulating, true); if (BaseType->IsChildOf(UDynamicSubsystem::StaticClass())) // 判断是否是 UDynamicSubsystem 的子类
{
for (const TPair<FName, TArray<TSubclassOf<UDynamicSubsystem>>>& SubsystemClasses : DynamicSystemModuleMap)
{
for (const TSubclassOf<UDynamicSubsystem>& SubsystemClass : SubsystemClasses.Value)
{
if (SubsystemClass->IsChildOf(BaseType))
{
AddAndInitializeSubsystem(SubsystemClass);
}
}
}
}
else // 不是 UDynamicSubsystem 的子类
{
TArray<UClass*> SubsystemClasses;
GetDerivedClasses(BaseType, SubsystemClasses, true); for (UClass* SubsystemClass : SubsystemClasses)
{
AddAndInitializeSubsystem(SubsystemClass);
}
} // Statically track collections
SubsystemCollections.Add(this);
}
} void FSubsystemCollectionBase::Deinitialize()
{
// Remove static tracking
SubsystemCollections.Remove(this);
if (SubsystemCollections.Num() == 0)
{
FSubsystemModuleWatcher::DeinitializeModuleWatcher();
} // Deinit and clean up existing systems
SubsystemArrayMap.Empty();
for (auto Iter = SubsystemMap.CreateIterator(); Iter; ++Iter) // 遍历 SubsystemMap
{
UClass* KeyClass = Iter.Key();
USubsystem* Subsystem = Iter.Value();
if (Subsystem->GetClass() == KeyClass)
{
Subsystem->Deinitialize(); // 清理、释放
Subsystem->InternalOwningSubsystem = nullptr;
}
}
SubsystemMap.Empty();
Outer = nullptr;
} USubsystem* FSubsystemCollectionBase::AddAndInitializeSubsystem(UClass* SubsystemClass)
{
if (!SubsystemMap.Contains(SubsystemClass))
{
// Only add instances for non abstract Subsystems
if (SubsystemClass && !SubsystemClass->HasAllClassFlags(CLASS_Abstract))
{
// Catch any attempt to add a subsystem of the wrong type
checkf(SubsystemClass->IsChildOf(BaseType), TEXT("ClassType (%s) must be a subclass of BaseType(%s)."), *SubsystemClass->GetName(), *BaseType->GetName()); // Do not create instances of classes aren't authoritative
if (SubsystemClass->GetAuthoritativeClass() != SubsystemClass)
{
return nullptr;
} const USubsystem* CDO = SubsystemClass->GetDefaultObject<USubsystem>();
if (CDO->ShouldCreateSubsystem(Outer)) // 从CDO调用ShouldCreateSubsystem来判断是否要创建
{
USubsystem* Subsystem = NewObject<USubsystem>(Outer, SubsystemClass); //创建
SubsystemMap.Add(SubsystemClass,Subsystem); // 添加到 SubsystemMap
Subsystem->InternalOwningSubsystem = this;
Subsystem->Initialize(*this); //调用Initialize
return Subsystem;
}
}
return nullptr;
} return SubsystemMap.FindRef(SubsystemClass);
}
【UE4 C++】编程子系统 Subsystem的更多相关文章
- Windows - 子系统(subsystem)错误
Windows - 子系统(subsystem)错误 本文地址: http://blog.csdn.net/caroline_wendy VS2012生成错误: "error LNK2019 ...
- UE4蓝图编程的第一步
认识UE4蓝图中颜色与变量类型: UE4中各个颜色对应着不同的变量,连接点和连线的颜色都在表示此处是什么类型的变量.对于初学者来说一开始看到那么多连接点, 可能会很茫然,搞不清还怎么连,如果知道了颜色 ...
- UE4的编程C++创建一个FPSproject(两)角色网格、动画、HUD、子弹类
立即归还,本文将总结所有这些整理UE4有关角色的网络格.动画.子弹类HUD一个简单的实现. (五)角色加入网格 Character类为我们默认创建了一个SkeletaMeshComponent组件,所 ...
- UE4新手编程之创建C++项目
虚幻4中常用的按键和快捷键 虚幻4中有一些按键和快捷键很常用,牢记它们并运动到实际的项目开发中,将会大大地提高你的工作效率和使得工作更简便快捷.下面将列举它们出来: 按键 动作 鼠标左键 选 ...
- UE4新手编程之创建空白关卡和添加碰撞体
让我们接着上次继续学习UE4引擎,今天我们学习下怎样创建空白的关卡以及添加碰撞物体. 一. 创建空白关卡 1) 点击文件 -> 新建关卡(或者按快捷键Ctrl+N). 2) 你可以选择Defau ...
- ue4 C++ 编程 通过三个点的位置算出夹角
const FVector2D& Pt1 = 第一个点的位置; const FVector2D& Pt2 = 第二个点的位置; float EdgeRadians1 = FMath:: ...
- 【UE4 C++】学习笔记汇总
UE4 概念知识 基础概念--文件结构.类型.反射.编译.接口.垃圾回收.序列化[导图] GamePlay架构[导图] 类的继承层级关系[导图] 反射机制 垃圾回收机制/算法 序列化 Actor 的生 ...
- 【UE4】GamePlay架构
新标签打开或者下载看大图 更新: 增加 编程子系统 Subsystem 思维导图 Character pipeline
- UE4编程之C++创建一个FPS工程(一)创建模式&角色&处理输入
转自:http://blog.csdn.net/u011707076/article/details/44180951 从今天开始,我们一起来学习一下,如何使用C++将一个不带有任何初学者内容的空模板 ...
随机推荐
- Python实现Thrift Server
近期在项目中存在跨编程语言协作的需求,使用到了Thrift.本文将记录用python实现Thrift服务端的方法. 环境准备 根据自身实际情况下载对应的Thrift编译器,比如我在Windows系统上 ...
- Linux档案权限篇之一
一.查看档案的属性 "ls" 第一列为档案的权限: d:代表是目录 -:代表是文件 l:代表是连接文件(相当于windows里面的快捷方式) b:代表块设备(如硬盘) c:代表字符 ...
- Java基础(二)——内部类
一.内部类 内部类(Inner Class)就是定义在一个类里面的类.与之对应,包含内部类的类被称为外部类.内部类可以用private修饰. 1.为什么要定义内部类?或者内部类的作用是什么? 内部类提 ...
- list类型数据的操作指令
1. 结果是 3 2 1 还可以继续追加如下: 2. 3. 4.删除表头元素(最左侧的元素),并返回该元素 5. 6. 7.删除表尾的元素(最右侧的元素),并返回该元素 8.
- 【Python从入门到精通】(二十五)Python多进程的使用
您好,我是码农飞哥,感谢您阅读本文,欢迎一键三连哦. 本篇重点介绍Python多进程的使用,读者朋友们可以将多进程和多线程两者做一个对比学习. 干货满满,建议收藏,需要用到时常看看. 小伙伴们如有问题 ...
- http接口实现附件对接
1.推送附件 filebody /** * 推送附件方法 * @param args */ public static void main2(String[] args){ CloseableHttp ...
- python库--requests
requests 方法 返回 参数 方法详情 .get() r url get请求 params url?后面的内容会以'key=value'的方式接到url后面 proxies 设置代理ip ...
- EL-ADMIN学习笔记
一,支持接口限流,避免恶意请求导致服务层压力过大 常见的限流功能一般有两个关注点: 1.限流原则,即以什么样的条件对请求进行识别以及放行.常见的作法是给予每个调用API的系统不同的唯一编码,用于监控某 ...
- 基于django2.2的网页构建
安装django pip install django==2.2 建一个在线商城的项目 django-admin startproject pyshop 启动项目 python manage.py r ...
- Learning How to Learn 学习如何学习
Introduction 这是 UCSD 开设在 Coursera 上的课程 Learning How to Learn 的课程笔记.这门课程主要基于神经科学和认知心理学的一些研究成果讲述高效学习的理 ...