关于如何通过定义自己的CameraManager来控制视角
2016.8.30
发现了这个函数,可以直接获得摄像机的位置和旋转。
- Controller->GetPlayerViewPoint(CamLoc, CamRot);
最近看了几天PlayerCameraManager的代码,大致看明白了,本着分享的原则,在此分享一些经验。
PlayerCameraManager,顾名思义就是管理角色摄像仪与视角的,你可以通过继承的方式,编写自己的PlayerCameraManager,之后在你角色的控制类中指定即可。
下面说一下主要的运行过程:
这里可以参考一下ShooterGame(ShooterPlayerCameraManager),从函数UpdateCamera开始看
- void AShooterPlayerCameraManager::UpdateCamera(float DeltaTime)
- {
//尝试获取角色类指针,如果成功就执行一些第一人称视角相关的修正- AShooterCharacter* MyPawn = PCOwner ? Cast<AShooterCharacter>(PCOwner->GetPawn()) : NULL;
- if (MyPawn && MyPawn->IsFirstPerson())
- {
//Fov切换的相关逻辑,(右键的微瞄切换)- const float TargetFOV = MyPawn->IsTargeting() ? TargetingFOV : NormalFOV;
- DefaultFOV = FMath::FInterpTo(DefaultFOV, TargetFOV, DeltaTime, 20.0f);
- }
- //执行父类函数
- Super::UpdateCamera(DeltaTime);
- //对角色类的模型进行变换
- if (MyPawn && MyPawn->IsFirstPerson())
- {
- MyPawn->OnCameraUpdate(GetCameraLocation(), GetCameraRotation());
- }
- }
这里并没有视角的相关逻辑,所以进一步看父类的UpdateCamera函数。
- void APlayerCameraManager::UpdateCamera(float DeltaTime)
- {
- if ((PCOwner->Player && PCOwner->IsLocalPlayerController()) || !bUseClientSideCameraUpdates || bDebugClientSideCamera)
- {
- DoUpdateCamera(DeltaTime);
- if (GetNetMode() == NM_Client && bShouldSendClientSideCameraUpdate)
- {
- // compress the rotation down to 4 bytes
- int32 const ShortYaw = FRotator::CompressAxisToShort(CameraCache.POV.Rotation.Yaw);
- int32 const ShortPitch = FRotator::CompressAxisToShort(CameraCache.POV.Rotation.Pitch);
- int32 const CompressedRotation = (ShortYaw << ) | ShortPitch;
- PCOwner->ServerUpdateCamera(CameraCache.POV.Location, CompressedRotation);
- bShouldSendClientSideCameraUpdate = false;
- }
- }
- }
这里是一些网络的逻辑,继续看DoUpdateCamera函数
- void APlayerCameraManager::DoUpdateCamera(float DeltaTime)
- {
//一些后期效果融合- // update color scale interpolation
- if (bEnableColorScaleInterp)
- {
- float BlendPct = FMath::Clamp((GetWorld()->TimeSeconds - ColorScaleInterpStartTime) / ColorScaleInterpDuration, .f, 1.0f);
- ColorScale = FMath::Lerp(OriginalColorScale, DesiredColorScale, BlendPct);
- // if we've maxed
- if (BlendPct == 1.0f)
- {
- // disable further interpolation
- bEnableColorScaleInterp = false;
- }
- }
//是否停止工作,不然就执行UpdateViewTarget- // Don't update outgoing viewtarget during an interpolation when bLockOutgoing is set.
- if ((PendingViewTarget.Target == NULL) || !BlendParams.bLockOutgoing)
- {
- // Update current view target
- ViewTarget.CheckViewTarget(PCOwner);
- UpdateViewTarget(ViewTarget, DeltaTime);
- }
- //下面都是一些视角更新的循环逻辑,看到最后你会发现他们一直都是用的引用,设置新的视角的逻辑并不在这
- // our camera is now viewing there
- FMinimalViewInfo NewPOV = ViewTarget.POV;
- // if we have a pending view target, perform transition from one to another.
- if (PendingViewTarget.Target != NULL)
- {
- BlendTimeToGo -= DeltaTime;
- // Update pending view target
- PendingViewTarget.CheckViewTarget(PCOwner);
- UpdateViewTarget(PendingViewTarget, DeltaTime);
- // blend....
- if (BlendTimeToGo > )
- {
- float DurationPct = (BlendParams.BlendTime - BlendTimeToGo) / BlendParams.BlendTime;
- float BlendPct = .f;
- switch (BlendParams.BlendFunction)
- {
- case VTBlend_Linear:
- BlendPct = FMath::Lerp(.f, .f, DurationPct);
- break;
- case VTBlend_Cubic:
- BlendPct = FMath::CubicInterp(.f, .f, .f, .f, DurationPct);
- break;
- case VTBlend_EaseIn:
- BlendPct = FMath::Lerp(.f, .f, FMath::Pow(DurationPct, BlendParams.BlendExp));
- break;
- case VTBlend_EaseOut:
- BlendPct = FMath::Lerp(.f, .f, FMath::Pow(DurationPct, .f / BlendParams.BlendExp));
- break;
- case VTBlend_EaseInOut:
- BlendPct = FMath::InterpEaseInOut(.f, .f, DurationPct, BlendParams.BlendExp);
- break;
- default:
- break;
- }
- // Update pending view target blend
- NewPOV = ViewTarget.POV;
- NewPOV.BlendViewInfo(PendingViewTarget.POV, BlendPct);//@TODO: CAMERA: Make sure the sense is correct! BlendViewTargets(ViewTarget, PendingViewTarget, BlendPct);
- }
- else
- {
- // we're done blending, set new view target
- ViewTarget = PendingViewTarget;
- // clear pending view target
- PendingViewTarget.Target = NULL;
- BlendTimeToGo = ;
- // our camera is now viewing there
- NewPOV = PendingViewTarget.POV;
- }
- }
- // Cache results
- FillCameraCache(NewPOV);
- if (bEnableFading)
- {
- if (bAutoAnimateFade)
- {
- FadeTimeRemaining = FMath::Max(FadeTimeRemaining - DeltaTime, 0.0f);
- if (FadeTime > 0.0f)
- {
- FadeAmount = FadeAlpha.X + ((.f - FadeTimeRemaining / FadeTime) * (FadeAlpha.Y - FadeAlpha.X));
- }
- if ((bHoldFadeWhenFinished == false) && (FadeTimeRemaining <= .f))
- {
- // done
- StopCameraFade();
- }
- }
- if (bFadeAudio)
- {
- ApplyAudioFade();
- }
- }
- }
接下来看UpdateViewTarget函数,计算视角的逻辑都在里面
- void APlayerCameraManager::UpdateViewTarget(FTViewTarget& OutVT, float DeltaTime)
- {
- // Don't update outgoing viewtarget during an interpolation
- if ((PendingViewTarget.Target != NULL) && BlendParams.bLockOutgoing && OutVT.Equal(ViewTarget))
- {
- return;
- }
- //设置默认属性的摄像机
- // store previous POV, in case we need it later
- FMinimalViewInfo OrigPOV = OutVT.POV;
- //@TODO: CAMERA: Should probably reset the view target POV fully here
- OutVT.POV.FOV = DefaultFOV;
- OutVT.POV.OrthoWidth = DefaultOrthoWidth;
- OutVT.POV.bConstrainAspectRatio = false;
- OutVT.POV.bUseFieldOfViewForLOD = true;
- OutVT.POV.ProjectionMode = bIsOrthographic ? ECameraProjectionMode::Orthographic : ECameraProjectionMode::Perspective;
- OutVT.POV.PostProcessSettings.SetBaseValues();
- OutVT.POV.PostProcessBlendWeight = 1.0f;
- bool bDoNotApplyModifiers = false;
- //如果ViewTarget是个摄像机的话,就直接获取摄像机的视角
- if (ACameraActor* CamActor = Cast<ACameraActor>(OutVT.Target))
- {
- // Viewing through a camera actor.
- CamActor->GetCameraComponent()->GetCameraView(DeltaTime, OutVT.POV);
- }
- else
- {
- //下面都是一些不同模式的摄像机的逻辑,你可以在游戏中按下~,输入Camera XXXX的方式切换这个摄像机
//默认的摄像机是Default- static const FName NAME_Fixed = FName(TEXT("Fixed"));
- static const FName NAME_ThirdPerson = FName(TEXT("ThirdPerson"));
- static const FName NAME_FreeCam = FName(TEXT("FreeCam"));
- static const FName NAME_FreeCam_Default = FName(TEXT("FreeCam_Default"));
- static const FName NAME_FirstPerson = FName(TEXT("FirstPerson"));
- if (CameraStyle == NAME_Fixed)
- {
- // do not update, keep previous camera position by restoring
- // saved POV, in case CalcCamera changes it but still returns false
- OutVT.POV = OrigPOV;
- // don't apply modifiers when using this debug camera mode
- bDoNotApplyModifiers = true;
- }
- else if (CameraStyle == NAME_ThirdPerson || CameraStyle == NAME_FreeCam || CameraStyle == NAME_FreeCam_Default)
- {
- // Simple third person view implementation
- FVector Loc = OutVT.Target->GetActorLocation();
- FRotator Rotator = OutVT.Target->GetActorRotation();
- if (OutVT.Target == PCOwner)
- {
- Loc = PCOwner->GetFocalLocation();
- }
- // Take into account Mesh Translation so it takes into account the PostProcessing we do there.
- // @fixme, can crash in certain BP cases where default mesh is null
- // APawn* TPawn = Cast<APawn>(OutVT.Target);
- // if ((TPawn != NULL) && (TPawn->Mesh != NULL))
- // {
- // Loc += FQuatRotationMatrix(OutVT.Target->GetActorQuat()).TransformVector(TPawn->Mesh->RelativeLocation - GetDefault<APawn>(TPawn->GetClass())->Mesh->RelativeLocation);
- // }
- //OutVT.Target.GetActorEyesViewPoint(Loc, Rot);
- if( CameraStyle == NAME_FreeCam || CameraStyle == NAME_FreeCam_Default )
- {
- Rotator = PCOwner->GetControlRotation();
- }
- FVector Pos = Loc + ViewTargetOffset + FRotationMatrix(Rotator).TransformVector(FreeCamOffset) - Rotator.Vector() * FreeCamDistance;
- FCollisionQueryParams BoxParams(NAME_FreeCam, false, this);
- BoxParams.AddIgnoredActor(OutVT.Target);
- FHitResult Result;
- GetWorld()->SweepSingleByChannel(Result, Loc, Pos, FQuat::Identity, ECC_Camera, FCollisionShape::MakeBox(FVector(.f)), BoxParams);
- OutVT.POV.Location = !Result.bBlockingHit ? Pos : Result.Location;
- OutVT.POV.Rotation = Rotator;
- // don't apply modifiers when using this debug camera mode
- bDoNotApplyModifiers = true;
- }
- else if (CameraStyle == NAME_FirstPerson)
- {
- // Simple first person, view through viewtarget's 'eyes'
- OutVT.Target->GetActorEyesViewPoint(OutVT.POV.Location, OutVT.POV.Rotation);
- // don't apply modifiers when using this debug camera mode
- bDoNotApplyModifiers = true;
- }
- else
- {
//默认摄像机会执行这个函数- UpdateViewTargetInternal(OutVT, DeltaTime);
- }
- }
- //这个应该是执行CameraShakes的逻辑
- if (!bDoNotApplyModifiers || bAlwaysApplyModifiers)
- {
- // Apply camera modifiers at the end (view shakes for example)
- ApplyCameraModifiers(DeltaTime, OutVT.POV);
- }
- //头戴设备的视角逻辑
- if (bFollowHmdOrientation)
- {
- if (GEngine->HMDDevice.IsValid() && GEngine->HMDDevice->IsHeadTrackingAllowed())
- {
- GEngine->HMDDevice->UpdatePlayerCameraRotation(this, OutVT.POV);
- }
- }
- // Synchronize the actor with the view target results
- SetActorLocationAndRotation(OutVT.POV.Location, OutVT.POV.Rotation, false);
- UpdateCameraLensEffects(OutVT);
- }
综上所述,你应该把摄像机逻辑写在这,如果想了解得更加清楚,可以继续从UpdateViewTargetInternal函数看下去,一直看到CalcCamera为止。
下面大致说一下编写思路:
在使用编辑器新建完自己的CameraManager后,在控制类中制定,例如:
PlayerCameraManagerClass = ADemoCameraManager::StaticClass();
然后我贴一下自己写的代码,这里我实现了一个固定视角:
- // Fill out your copyright notice in the Description page of Project Settings.
- #pragma once
- #include "Camera/PlayerCameraManager.h"
- #include "DemoCameraManager.generated.h"
- /**
- *
- */
- UCLASS()
- class DEMO_API ADemoCameraManager : public APlayerCameraManager
- {
- GENERATED_BODY()
- protected:
- virtual void UpdateViewTarget(FTViewTarget& OutVT, float DeltaTime) override;
- //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = TViewTarget)
- //struct FMinimalViewInfo SceneFixedPOV;
- };
- // Fill out your copyright notice in the Description page of Project Settings.
- #include "Demo.h"
- #include "DemoCameraManager.h"
- #include "DemoCharacter.h"
- void ADemoCameraManager::UpdateViewTarget(FTViewTarget& OutVT, float DeltaTime)
- {
- if (bFollowHmdOrientation)
- {
- //如果有VR设备就直接运行引擎原始函数
- Super::UpdateViewTarget(OutVT, DeltaTime);
- return;
- }
- // Don't update outgoing viewtarget during an interpolation
- if ((PendingViewTarget.Target != NULL) && BlendParams.bLockOutgoing && OutVT.Equal(ViewTarget))
- {
- return;
- }
- bool bDoNotApplyModifiers = false;
- if (ACameraActor* CamActor = Cast<ACameraActor>(OutVT.Target))
- {
- // Viewing through a camera actor.
- CamActor->GetCameraComponent()->GetCameraView(DeltaTime, OutVT.POV);
- }
- else
- {
- if (CameraStyle == FName("SceneFixed"))
- {
//这里我感觉可能用PendingViewTarget来传递摄像机坐标和旋转比较好,但是还没测试过- //自己定义一个场景固定视角
- ADemoCharacter *Character = Cast<ADemoCharacter>(GetOwningPlayerController()->GetPawn());
- OutVT.POV.Location = Character->ViewTargetLocation;
- OutVT.POV.Rotation = Character->ViewTargetRotator;
- //DesiredView.FOV = FieldOfView;
- //DesiredView.AspectRatio = AspectRatio;
- // don't apply modifiers when using this debug camera mode
- bDoNotApplyModifiers = true;
- }else if (CameraStyle==FName("Default"))
- {
- //默认方式是直接取得摄像机的参数来设置FTViewTarget.pov,而摄像机被控制类、SpringArm控制。
- UpdateViewTargetInternal(OutVT, DeltaTime);
- }
- else
- {
- Super::UpdateViewTarget(OutVT, DeltaTime);
- }
- }
- if (!bDoNotApplyModifiers || bAlwaysApplyModifiers)
- {
- // Apply camera modifiers at the end (view shakes for example)
- ApplyCameraModifiers(DeltaTime, OutVT.POV);
- }
- // Synchronize the actor with the view target results
- SetActorLocationAndRotation(OutVT.POV.Location, OutVT.POV.Rotation, false);
- UpdateCameraLensEffects(OutVT);
- }
后面是固定视角VolumeActor的代码:
- // Fill out your copyright notice in the Description page of Project Settings.
- #pragma once
- #include "GameFramework/Actor.h"
- #include "Character/DemoCharacter.h"
- #include "Character/DemoCameraManager.h"
- #include "Character/DemoPlayerController.h"
- #include "Runtime/Engine/Classes/Kismet/KismetMathLibrary.h"
- #include "BaseSceneFixedViewVolume.generated.h"
- UCLASS()
- class DEMO_API ABaseSceneFixedViewVolume : public AActor
- {
- GENERATED_BODY()
- public:
- // Sets default values for this actor's properties
- ABaseSceneFixedViewVolume();
- // Called when the game starts or when spawned
- virtual void BeginPlay() override;
- // Called every frame
- virtual void Tick( float DeltaSeconds ) override;
- FVector Lcation;
- FRotator Rotator;
- UPROPERTY(EditDefaultsOnly, Category = "SceneFixedView", meta = (AllowPrivateAccess = "true"))
- UBoxComponent* Triggers;
- UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "SceneFixedView", meta = (AllowPrivateAccess = "true"))
- UCameraComponent* Camera;
- UFUNCTION()
- virtual void OnBeginOverlap(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweeoResult);
- UFUNCTION()
- virtual void OnEndOverlap(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
- UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "SceneFixedView")
- bool bCalView=false;
- UPROPERTY(EditDefaultsOnly,Category="SceneFixedView")
- FVector VolumeSize = FVector(, , );
- ADemoCharacter* Character;
- ADemoCameraManager* CameraManage;
- FName OriginMode;
- };
- // Fill out your copyright notice in the Description page of Project Settings.
- #include "Demo.h"
- #include "BaseSceneFixedViewVolume.h"
- // Sets default values
- ABaseSceneFixedViewVolume::ABaseSceneFixedViewVolume()
- {
- // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
- PrimaryActorTick.bCanEverTick = true;
- Triggers = CreateDefaultSubobject<UBoxComponent>(TEXT("Triggers"));
- Triggers->SetBoxExtent(VolumeSize);
- Triggers->SetRelativeLocation(FVector(,,VolumeSize.Z/));
- RootComponent = Triggers;
- Camera= CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
- Camera->SetRelativeLocation(FVector(VolumeSize.X,VolumeSize.Y,VolumeSize.Z));
- Camera->AttachTo(RootComponent);
- //FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
- //FollowCamera->AttachTo(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
- //FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm
- Triggers->OnComponentBeginOverlap.AddDynamic(this, &ABaseSceneFixedViewVolume::OnBeginOverlap);
- Triggers->OnComponentEndOverlap.AddDynamic(this, &ABaseSceneFixedViewVolume::OnEndOverlap);
- }
- // Called when the game starts or when spawned
- void ABaseSceneFixedViewVolume::BeginPlay()
- {
- Super::BeginPlay();
- }
- // Called every frame
- void ABaseSceneFixedViewVolume::Tick( float DeltaTime )
- {
- Super::Tick( DeltaTime );
- if (bCalView)
- {
- if (Character)
- {
- Character->ViewTargetLocation = Camera->K2_GetComponentLocation();
- Character->ViewTargetRotator = UKismetMathLibrary::FindLookAtRotation(Camera->K2_GetComponentLocation(), Character->GetActorLocation());
- }
- }
- }
- void ABaseSceneFixedViewVolume::OnBeginOverlap(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweeoResult)
- {
- Character = Cast<ADemoCharacter>(OtherActor);
- if (Character)
- {
- bCalView = true;
- ADemoPlayerController* Controller = Cast<ADemoPlayerController>(Character->GetController());
- if (Controller)
- {
- OriginMode=Controller->PlayerCameraManager->CameraStyle;
- Controller->SetCameraMode(FName("SceneFixed"));
- //因为我使用控制类来管理视角,为了解决一些视角问题,所以在进入时,必须重新设置视角,屏蔽鼠标修改视角
- Controller->SetControlRotation(FRotator(0.0f,90.0f,0.0f));
- Controller->SetIgnoreLookInput(true);
- }
- }
- }
- void ABaseSceneFixedViewVolume::OnEndOverlap(class AActor* OtherActor ,class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
- {
- if (OtherActor==Character)
- {
- bCalView = false;
- ADemoPlayerController* Controller = Cast<ADemoPlayerController>(Character->GetController());
- if (Controller)
- {
- if (OriginMode!=FName(""))
- {
- Controller->SetCameraMode(OriginMode);
- }
- else
- {
- Controller->SetCameraMode(FName("Default"));
- }
- Controller->SetIgnoreLookInput(false);
- }
- }
- Character= nullptr;
- }
关于如何通过定义自己的CameraManager来控制视角的更多相关文章
- struts自己定义拦截器--登录权限控制
说明:该自己定义的拦截器实现用户登录的权限控制. login.jsp--->LoginAction--重定向-->MainAction--->main.jsp 一.1.整体的步骤: ...
- VC中预处理指令与宏定义详解
刚接触到MFC编程的人往往会被MFC 向导生成的各种宏定义和预处理指令所吓倒,但是预处理和宏定义又是C语言的一个强大工具.使用它们可以进行简单的源代码控制,版本控制,预警或者完成一些特殊的功能. 一个 ...
- C 宏定义
C/C++中宏使用总结 .C/C++中宏总结C程序的源代码中可包括各种编译指令,这些指令称为预处理命令.虽然它们实际上不是C语言的一部分,但却扩展了C程序设计的环境.本节将介绍如何应用预处理程序和注释 ...
- linux C宏定义 转
写好C语言,漂亮的宏定义很重要,使用宏定义可以防止出错,提高可移植性,可读性,方便性等等.下面列举一些成熟软件中常用得宏定义...... 1,防止一个头文件被重复包含 #ifndef COMDEF_H ...
- c c++面试c工程开发之宏定义和条件编译
多数c语言的初学者对c工程开发过程各个阶段的作用理解不到位,而这方面的的知识又是实际开发过程中经常用到的技能点,所以就成为面试考察中一个重要的考察方面.例如:头文件的作用.头文件的内容:链接的作用和意 ...
- 交换机VLAN的定义、意义以及划分方式
什么是VLAN 虚拟网技术(VLAN,Virtual Local Area Network)的诞生主要源于广播.广播在网络中起着非常重要的作用,如发现新设备.调整网络路径.IP地址租赁等等,许多网络协 ...
- C语言预处理命令总结大全 :宏定义
C程序的源代码中可包括各种编译指令,这些指令称为预处理命令.虽然它们实际上不是C语言的一部分,但却扩展了C程序设计的环境.本节将介绍如何应用预处理程序和注释简化程序开发过程,并提高程序的可读性.ANS ...
- Java中常见方法详解合集(方法的定义及语法结构)
Java的方法定义 1.方法的定义 方法是控制对象的动作行为方式与准则,在Java中方法位于类体下又有另一种含义. 普通的方法在类中称为"实例方法",因为方法的调用需要创建对象,而 ...
- JavaScript性能优化
如今主流浏览器都在比拼JavaScript引擎的执行速度,但最终都会达到一个理论极限,即无限接近编译后程序执行速度. 这种情况下决定程序速度的另一个重要因素就是代码本身. 在这里我们会分门别类的介绍J ...
随机推荐
- ecshop后台 计划任务
计划任务定时清理掉设置后的内容 主要针对单表删除(日志,):对于多表删除,不太好用(订单+订单商品+订单日志) 结构: 1.计划任务语言包:languages\zh_cn\cron\ 2.php文件: ...
- memcached的key,value,过期时间的限制
1. key值最大长度? memcached的key的最大长度是250个字符,是memcached服务端的限制. 如果您使用的客户端支持"key的前缀"或类似特性,那么key( ...
- php提供更快的文件下载
在微博上偶然看到一篇介绍php更快下载文件的方法,其实就是利用web服务器的xsendfile特性,鸟哥的博客中只说了apache的实现方式,我找到了介绍nginx实现方式的文章,整理一下! let' ...
- github提交代码时,报permission denied publickey
在像github提交代码时,报permission denied publickey. 查找了一下,可能是因为github的key失效了. 按照以下步骤,重新生成key. ssh-keygen 一路默 ...
- Rails的三种环境----开发环境,生产环境和测试环境
Rails 的三个环境 Rails 的应用程序预设提供了三种不同的执行模式: development environment 开发模式,用在你的开发的时候 test environment 测试模式, ...
- 【阿里李战】解剖JavaScript中的 null 和 undefined
在JavaScript开发中,被人问到:null与undefined到底有啥区别? 一时间不好回答,特别是undefined,因为这涉及到undefined的实现原理.于是,细想之后,写下本文,请各位 ...
- python解析git log后生成页面显示git更新日志信息
使用git log可以查到git上项目的更新日志. 如下两个git项目,我想把git的日志信息解析成一个便于在浏览器上查看的页面. https://github.com/gityf/lua https ...
- iOS抓包利器Charles
iOS抓包利器Charles http://wonderffee.github.io/blog/2013/07/13/best-packet-capture-tool-charles-in-ios/ ...
- 安装第三方模块方法和requests
如何安装第三方模块 pip3 pip3 install xxxx 源码 下载,解压 进入目录 python setup.py inst ...
- 文本框value联动修改
<input id="ipt-edit" value="" /><input id="ipt-target" value= ...