According to the unreal documentation, GetRelativeRotation is a member variable of USceneComponent. However, when I run my code, it tells me that it's not. I have tried switching my Camera to a UCameraComponent(as it was originally before I checked the documenation) and still no luck. Any ideas? heres the code for the .cpp file:
#include "FirstPersonCharacter.h"
AFirstPersonCharacter::AFirstPersonCharacter()
{
PrimaryActorTick.bCanEverTick = true;
AutoPossessPlayer = EAutoReceiveInput::Player0;
bUseControllerRotationYaw = false;
Cam = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
Cam->AttachTo(RootComponent);
Cam->SetRelativeLocation(FVector(0.f, 0.f, 40.f)); //places the camera 40cm above the centre of the body.
}
void AFirstPersonCharacter::BeginPlay()
{
Super::BeginPlay();
}
void AFirstPersonCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void AFirstPersonCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
InputComponent->BindAxis("HorizontalMovement", this, &AFirstPersonCharacter::HorizontalMove);
InputComponent->BindAxis("VerticalMovement", this, &AFirstPersonCharacter::VerticalMove);
InputComponent->BindAxis("VerticalLook", this, &AFirstPersonCharacter::VerticalRot);
InputComponent->BindAxis("HorizontalLook", this, &AFirstPersonCharacter::HorizontalRot);
}
void AFirstPersonCharacter::HorizontalMove(float value)
{
if (value) //make sure it doesn't constantly call on addmovementinput if theres no value to add to it
{
AddMovementInput(GetActorRightVector(), value);
}
}
void AFirstPersonCharacter::VerticalMove(float value)
{
if (value) //make sure it doesn't constantly call on addmovementinput if theres no value to add to it
{
AddMovementInput(GetActorForwardVector(), value);
}
}
void AFirstPersonCharacter::HorizontalRot(float value)
{
if (value)
{
AddActorLocalRotation(FRotator(0, value, 0));
}
}
void AFirstPersonCharacter::VerticalRot(float value)
{
if (value)
{
float temp = Cam->GetRelativeRotation().Pitch + value; //THIS IS WHERE THE ISSUE IS
if (temp < 65 && temp > -65)
{
Cam->AddLocalRotation(FRotator(value, 0, 0));
}
}
and the code for the header file:
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Components/InputComponent.h"
#include "Components/SceneComponent.h"
#include "Camera/CameraComponent.h"
#include "FirstPersonCharacter.generated.h"
UCLASS()
class MOMENTUMPROJECTMAIN_API AFirstPersonCharacter : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AFirstPersonCharacter();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
private:
void HorizontalMove(float value);
void VerticalMove(float value);
void HorizontalRot(float value);
void VerticalRot(float value);
//allows me to edit in the editor, without going into source code.
UPROPERTY(EditAnywhere, Category = "Camera")
UCameraComponent* Cam;
};
thanks.
Related
I am a beginner in UE5 (I started learning yesterday), and I am now working on a simple task. It takes the WSAD and mouse to do simple movement and camera operations using input mapping context.
However, after my self-learning, I end up with this:
UCLASS()
class ASSIGNMENT1_API AFirstPersonCharactor : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AFirstPersonCharactor();
UPROPERTY(EditAnywhere, Category = "Components|Camera")
UCameraComponent* camera;
UPROPERTY(EditAnywhere, Category = "Input|Actions")
UInputAction *IA_KB_CM_InOut;
UPROPERTY(EditAnywhere, Category = "Input|Actions")
UInputAction* IA_KB_CM_LeftRight;
UPROPERTY(EditAnywhere, Category = "Input|Actions")
UInputAction* IA_KB_CL_UpDown;
UPROPERTY(EditAnywhere, Category = "Input|Actions")
UInputAction* IA_KB_CL_LeftRight;
UPROPERTY(EditAnywhere, Category = "Input|Actions")
TSoftObjectPtr<UInputMappingContext> IMCControls;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
void KB_CM_InOut(const FInputActionInstance& Instance);
void KB_CM_LeftRight(const FInputActionInstance& Instance);
void KB_CL_UpDown(const FInputActionInstance& Instance);
void KB_CL_LeftRight(const FInputActionInstance& Instance);
};
and this is my cpp file:
// Sets default values
AFirstPersonCharactor::AFirstPersonCharactor()
{
PrimaryActorTick.bCanEverTick = true;
camera = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
camera->SetupAttachment(RootComponent);
camera->SetRelativeLocation(FVector(0));
}
void AFirstPersonCharactor::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AFirstPersonCharactor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Called to bind functionality to input
void AFirstPersonCharactor::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
if (ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(GetController()))
{
if (UEnhancedInputLocalPlayerSubsystem* InputSystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
{
InputSystem->AddMappingContext(IMCControls.LoadSynchronous(),10);
}
}
UEnhancedInputComponent* input = Cast<UEnhancedInputComponent>(PlayerInputComponent);
if (IA_KB_CM_InOut) {
input->BindAction(IA_KB_CM_InOut, ETriggerEvent::Started, this, &AFirstPersonCharactor::KB_CM_InOut);
}
if (IA_KB_CM_LeftRight) {
input->BindAction(IA_KB_CM_LeftRight, ETriggerEvent::Started, this, &AFirstPersonCharactor::KB_CM_LeftRight);
}
if (IA_KB_CL_UpDown) {
input->BindAction(IA_KB_CL_UpDown, ETriggerEvent::Started, this, &AFirstPersonCharactor::KB_CL_UpDown);
}
if (IA_KB_CL_LeftRight) {
input->BindAction(IA_KB_CL_LeftRight, ETriggerEvent::Started, this, &AFirstPersonCharactor::KB_CL_LeftRight);
}
}
void AFirstPersonCharactor::KB_CM_InOut(const FInputActionInstance &Instance)
{
printf("a");
float FloatValue = Instance.GetValue().Get<float>();
if (FloatValue != 0) {
AddMovementInput(GetActorForwardVector(),FloatValue);
}
}
void AFirstPersonCharactor::KB_CM_LeftRight(const FInputActionInstance& Instance)
{
float FloatValue = Instance.GetValue().Get<float>();
if (FloatValue != 0) {
AddMovementInput(GetActorRightVector(), FloatValue);
}
}
void AFirstPersonCharactor::KB_CL_LeftRight(const FInputActionInstance& Instance)
{
float FloatValue = Instance.GetValue().Get<float>();
if (FloatValue != 0) {
AddControllerYawInput(FloatValue);
}
}
void AFirstPersonCharactor::KB_CL_UpDown(const FInputActionInstance& Instance)
{
float FloatValue = Instance.GetValue().Get<float>();
if (FloatValue != 0) {
AddControllerPitchInput(FloatValue);
}
}
This ends with no bug, and I have the inputAction and inputMappingContext assets set in the outliner. But no matter which key I press, there are always no behaviours I can observe.
EDIT: after I add GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("haha")); to debug, I found that my SetupPlayerInputComponent is working, but
if (ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(GetController()))
probably not.
You shouldn't be trying to cast your controller to local player, and also you'd rather setup MappingContext in BeginPlay() rather than in SetupInputComponent(...).
So instead of doing that cast and then trying to add mapping context do this:
if (auto PlayerController = Cast<APlayerController>(Controller))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
Subsystem->AddMappingContext(DefaultMappingContext, 0);
}
}
I'm working on a small game in UE4 and hit a small issue.
So far, I've got the player dealing damage to enemies (via raycast/line trace) all in C++. Now I'm trying to make the enemy cause damage to the player when their collision boxes overlap.
The player uses the TakeDamage() function as does the enemy to deal damage to each other.
Currently, the enemy does deal damage to the player with this Blueprint setup when both of the collisons overlap.
I'm trying to translate this into C++.
I have been looking at many websites which say to use either OnActorBeginOverlap() or OnComponentBeginOverlap() with the AddDynamic() function. However, when trying to call OnActorBeginOverlap() it doesn't appear when I do BoxCollider-> but OnComponentBeginOverlap() does, but does not have the AddDynamic() function. From what I understand, AddDynamic is a macro which I'm not too familiar with. OnActorBeginOverlap() does appear without the BoxCollider->. Both objects are Actors.
Any ideas?
Code Example:
BasicZombie.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/BoxComponent.h"
#include "BasicZombie.generated.h"
UCLASS()
class MERCENARIES_API ABasicZombie : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ABasicZombie();
void Destroy();
UPROPERTY(EditAnywhere)
UBoxComponent* BoxCollider;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
virtual float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, AActor* DamageCauser) override;
private:
UPROPERTY(EditDefaultsOnly)
float maxHealth = 100.0f;
UPROPERTY(VisibleAnywhere)
float health;
void DealDamage();
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
BasicZombie.cpp
#include "BasicZombie.h"
#include "MainCharacter.h"
#include "GameFramework/Actor.h"
#include "DrawDebugHelpers.h"
#include "Engine/Engine.h"
// Sets default values
ABasicZombie::ABasicZombie()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = false;
BoxCollider = CreateDefaultSubobject<UBoxComponent>(TEXT("BoxCollider"));
BoxCollider->OnComponentBeginOverlap.AddDynamic(this, &AMainCharacter::OnCollision);
health = maxHealth;
}
// Called when the game starts or when spawned
void ABasicZombie::BeginPlay()
{
Super::BeginPlay();
}
float ABasicZombie::TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, AActor* DamageCauser)
{
float DamageToApply = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
DamageToApply = FMath::Min(health, DamageToApply);
health -= DamageToApply;
UE_LOG(LogTemp, Warning, (TEXT("Health Remaining: %f")), health);
//GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Red, FString::Printf(TEXT("Health Remaining: %f"), health));
Destroy();
return DamageToApply;
}
//void ABasicZombie::DealDamage()
//{
//
//}
void ABasicZombie::Destroy()
{
if (health <= 0)
{
UWorld* WorldRef = GetWorld();
AMainCharacter* mainCharacter = Cast<AMainCharacter>(WorldRef->GetFirstPlayerController()->GetCharacter());
mainCharacter->currentScore += 500;
GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Green, FString::Printf(TEXT("Score: %i"), mainCharacter->currentScore));
AActor::Destroy();
}
}
// Called every frame
void ABasicZombie::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
MainCharacter.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "GameFramework/PlayerController.h"
#include "MainCharacter.generated.h"
class AHandgun;
UCLASS()
class MERCENARIES_API AMainCharacter : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
AMainCharacter();
void OnCollision(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& Hit);
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
virtual float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, AActor* DamageCauser) override;
private:
UPROPERTY(EditDefaultsOnly)
TSubclassOf<AHandgun> HandgunClass;
UPROPERTY()
AHandgun* Handgun;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
//Handles Input for moving FORWARD and BACK
UFUNCTION()
void MoveForward(float value);
//Handles input for moving RIGHT and LEFT
UFUNCTION()
void MoveRight(float value);
UFUNCTION()
void Shoot();
UFUNCTION()
void printHealth();
//FPS camera
UPROPERTY(VisibleAnywhere)
UCameraComponent* MainCharacterCameraComponent;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay)
FVector MuzzleOffset;
UPROPERTY()
int32 playerScore;
UPROPERTY()
int32 currentScore;
UPROPERTY()
float maxHealth = 1000.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float currentHealth;
};
MainCharacter.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "MainCharacter.h"
#include "Engine/Engine.h"
#include "GameFramework/Actor.h"
#include "Handgun.h"
#include "GameFramework/Character.h"
#include "Components/SkeletalMeshComponent.h"
// Sets default values
AMainCharacter::AMainCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
//Create a first person camera component
MainCharacterCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
check(MainCharacterCameraComponent != nullptr);
//Attach the camera component to our capsule component
MainCharacterCameraComponent->SetupAttachment(CastChecked<USceneComponent, UCapsuleComponent>(GetCapsuleComponent()));
//Position the camera slightly above the eyes
MainCharacterCameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 50.0f + BaseEyeHeight));
//Enable the pawn to control camera rotation
MainCharacterCameraComponent->bUsePawnControlRotation = true;
currentHealth = maxHealth;
playerScore = currentScore;
}
// Called when the game starts or when spawned
void AMainCharacter::BeginPlay()
{
Super::BeginPlay();
check(GEngine != nullptr);
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow, TEXT("[MainCharacter DEBUG] - MainCharacter in use."));
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, FString::Printf(TEXT("[MainCharacter DEBUG] - Current Score: %f"), playerScore));
Handgun = GetWorld()->SpawnActor<AHandgun>(HandgunClass);
Handgun->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, TEXT("WeaponSocket"));
Handgun->SetOwner(this);
}
// Called every frame
void AMainCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
//printHealth();
}
// Called to bind functionality to input
void AMainCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
//Set up movement bindings.
PlayerInputComponent->BindAxis("MoveForward", this, &AMainCharacter::MoveForward);
PlayerInputComponent->BindAxis("MoveRight", this, &AMainCharacter::MoveRight);
//Set up Look Bindings
PlayerInputComponent->BindAxis("Turn", this, &AMainCharacter::AddControllerYawInput);
PlayerInputComponent->BindAxis("LookUp", this, &AMainCharacter::AddControllerPitchInput);
//Setup Weapon Shooting
PlayerInputComponent->BindAction(TEXT("Fire"), EInputEvent::IE_Pressed, this, &AMainCharacter::Shoot);
PlayerInputComponent->BindAction(TEXT("printHealth"), EInputEvent::IE_Pressed, this, &AMainCharacter::printHealth);
}
void AMainCharacter::Shoot()
{
Handgun->Shoot();
}
void AMainCharacter::printHealth()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, FString::Printf(TEXT("[MainCharacter DEBUG] - Current Health: %f"), currentHealth));
}
float AMainCharacter::TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, AActor* DamageCauser)
{
float DamageToApply = Super::TakeDamage(DamageAmount, DamageEvent, EventInstigator, DamageCauser);
DamageToApply = FMath::Min(currentHealth, DamageToApply);
currentHealth -= DamageToApply;
GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Red, FString::Printf(TEXT("Health Remaining: %f"), currentHealth));
if (currentHealth <= 0)
{
GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Red, TEXT("You're Dead!"));
}
return DamageAmount;
}
void AMainCharacter::MoveForward(float value)
{
//Find out which way is "forward" and reocrd that the player wants to move that way
FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::X);
AddMovementInput(Direction, value);
}
void AMainCharacter::MoveRight(float value)
{
//Find out which way is "right" and record that the player wants to move that way
FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::Y);
AddMovementInput(Direction, value);
}
However, the BoxCollider->OnComponentBeginOverlap.AddDynamic(this, &AMainCharacter::OnCollision); in BasicZombie.cpp has a red line under AddDynamic.
I am in a Discord Server for UE4 development, but I haven't had much luck getting any help from there.
Can I see your C++ code? Are you calling AddDynamic from the UBoxComponent?
Try this
UPROPERTY(EditAnywhere)
UBoxComponent* BoxCollider = nullptr;
BoxCollider = GetOwner()->FindComponentByClass<UBoxComponent>();
BoxCollider->OnComponentBeginOverlap.AddDynamic(this, &YourActor::OnCollision);
Use this as your OnCollision signature
void OnCollision(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& Hit);
Also when I was learning unreal engine I dealt with a lot of pitfalls. Try to find a community of unreal engine developers and learn with them.
I want to open the door when pressing left mouse button, but nothing happens
OpenDoor.h
UCLASS()
class HOME_API ADoorOpen : public APawn
{
GENERATED_BODY()
public:
ADoorOpen();
protected:
virtual void BeginPlay() override;
public:
virtual void Tick(float DeltaTime) override;
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
UPROPERTY()
USceneComponent* Root;
UPROPERTY(EditAnywhere)
UStaticMeshComponent* Door;
UPROPERTY(EditAnywhere, Category = "DoorOpen")
float rotateValue;
UPROPERTY(BlueprintReadOnly, Category = "DoorOpen")
FRotator doorRotation;
UPROPERTY(BlueprintReadOnly, Category = "DoorOpen")
bool open;
UFUNCTION(BlueprintCallable, Category = "DoorOpen")
void OpenCloseDoor();
};
OpenDoor.cpp
#include "DoorOpen.h"
#include "DrawDebugHelpers.h"
#include "Engine.h"
ADoorOpen::ADoorOpen()
{
PrimaryActorTick.bCanEverTick = true;
rotateValue = 0.0f;
open = false;
Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
RootComponent = Root;
Door = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Door"));
Door->SetRelativeLocation(FVector(0.f, 50.f, -50.f));
Door->SetupAttachment(RootComponent);
}
void ADoorOpen::OpenCloseDoor()
{
// don't see this while playing
GEngine->AddOnScreenDebugMessage(-1, 0.f, FColor::Red, "OpenCloseDoor");
//...
}
void ADoorOpen::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
// Can't call this
PlayerInputComponent->BindAction("Fire", IE_Pressed, this, &ADoorOpen::OpenCloseDoor);
}
Increase the display time for AddOnScreenDebugMessage
GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Red, "OpenCloseDoor");
Otherwise, the text will show for just a frame.
I have a custom QGraphicsView and QGraphicsScene. Inside QGraphicsScene I have overriden void drawBackground(QPainter *painter, const QRectF &rect) and based on a boolean flag I want to toggle a grid on and off. I tried calling clear() or calling the painter's eraseRect(sceneRect()) inside my function but it didn't work. So after doing some reading I guess it wasn't supposed to work since after changing the scene you need to refresh the view. That's why I'm emitting a signal called signalUpdateViewport()
void Scene::drawBackground(QPainter *painter, const QRectF &rect) {
if(this->gridEnabled) {
// Draw grid
}
else {
// Erase using the painter
painter->eraseRect(sceneRect());
// or by calling
//clear();
}
// Trigger refresh of view
emit signalUpdateViewport();
QGraphicsScene::drawBackground(painter, rect);
}
which is then captured by my view:
void View::slotUpdateViewport() {
this->viewport()->update();
}
Needless to say this didn't work. With doesn't work I mean that the results (be it a refresh from inside the scene or inside the view) are made visible only when changing the widget for example triggering a resize event.
How do I properly refresh the view to my scene to display the changed that I have made in the scene's background?
The code:
scene.h
#ifndef SCENE_HPP
#define SCENE_HPP
#include <QGraphicsScene>
class View;
class Scene : public QGraphicsScene
{
Q_OBJECT
Q_ENUMS(Mode)
Q_ENUMS(ItemType)
public:
enum Mode { Default, Insert, Select };
enum ItemType { None, ConnectionCurve, ConnectionLine, Node };
Scene(QObject* parent = Q_NULLPTR);
~Scene();
void setMode(Mode mode, ItemType itemType);
signals:
void signalCursorCoords(int x, int y);
void signalSceneModeChanged(Scene::Mode sceneMode);
void signalSceneItemTypeChanged(Scene::ItemType sceneItemType);
void signalGridDisplayChanged(bool gridEnabled);
void signalUpdateViewport();
public slots:
void slotSetSceneMode(Scene::Mode sceneMode);
void slotSetSceneItemType(Scene::ItemType sceneItemType);
void slotSetGridStep(int gridStep);
void slotToggleGrid(bool flag);
private:
Mode sceneMode;
ItemType sceneItemType;
bool gridEnabled;
int gridStep;
void makeItemsControllable(bool areControllable);
double round(double val);
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
void keyPressEvent(QKeyEvent *event);
void drawBackground(QPainter *painter, const QRectF &rect);
};
Q_DECLARE_METATYPE(Scene::Mode)
Q_DECLARE_METATYPE(Scene::ItemType)
#endif // SCENE_HPP
scene.cpp
#include <QGraphicsItem>
#include <QGraphicsView>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <QRectF>
#include <QKeyEvent>
#include <QtDebug>
#include "scene.h"
Scene::Scene(QObject* parent)
: QGraphicsScene (parent),
sceneMode(Default),
sceneItemType(None),
gridEnabled(true),
gridStep(30)
{
}
Scene::~Scene()
{
}
void Scene::setMode(Mode mode, ItemType itemType)
{
this->sceneMode = mode;
this->sceneItemType = itemType;
QGraphicsView::DragMode vMode = QGraphicsView::NoDrag;
switch(mode) {
case Insert:
{
makeItemsControllable(false);
vMode = QGraphicsView::NoDrag;
break;
}
case Select:
{
makeItemsControllable(true);
vMode = QGraphicsView::RubberBandDrag;
break;
}
case Default:
{
makeItemsControllable(false);
vMode = QGraphicsView::NoDrag;
break;
}
}
QGraphicsView* mView = views().at(0);
if(mView) {
mView->setDragMode(vMode);
}
}
void Scene::slotSetSceneMode(Scene::Mode sceneMode)
{
this->sceneMode = sceneMode;
qDebug() << "SM" << (int)this->sceneMode;
emit signalSceneModeChanged(this->sceneMode);
}
void Scene::slotSetSceneItemType(Scene::ItemType sceneItemType)
{
this->sceneItemType = sceneItemType;
qDebug() << "SIT:" << (int)this->sceneItemType;
emit signalSceneItemTypeChanged(this->sceneItemType);
}
void Scene::slotSetGridStep(int gridStep)
{
this->gridStep = gridStep;
}
void Scene::slotToggleGrid(bool flag)
{
this->gridEnabled = flag;
invalidate(sceneRect(), BackgroundLayer);
qDebug() << "Grid " << (this->gridEnabled ? "enabled" : "disabled");
update(sceneRect());
emit signalGridDisplayChanged(this->gridEnabled);
}
void Scene::makeItemsControllable(bool areControllable)
{
foreach(QGraphicsItem* item, items()) {
if(/*item->type() == QCVN_Node_Top::Type
||*/ item->type() == QGraphicsLineItem::Type
|| item->type() == QGraphicsPathItem::Type) {
item->setFlag(QGraphicsItem::ItemIsMovable, areControllable);
item->setFlag(QGraphicsItem::ItemIsSelectable, areControllable);
}
}
}
double Scene::round(double val)
{
int tmp = int(val) + this->gridStep/2;
tmp -= tmp % this->gridStep;
return double(tmp);
}
void Scene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsScene::mousePressEvent(event);
}
void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
emit signalCursorCoords(int(event->scenePos().x()), int(event->scenePos().y()));
QGraphicsScene::mouseMoveEvent(event);
}
void Scene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsScene::mouseReleaseEvent(event);
}
void Scene::keyPressEvent(QKeyEvent* event)
{
if(event->key() == Qt::Key_M) {
slotSetSceneMode(static_cast<Mode>((int(this->sceneMode) + 1) % 3));
}
else if(event->key() == Qt::Key_G) {
slotToggleGrid(!this->gridEnabled);
}
QGraphicsScene::keyPressEvent(event);
}
void Scene::drawBackground(QPainter *painter, const QRectF &rect)
{
// FIXME Clearing and drawing the grid happens only when scene size or something else changes
if(this->gridEnabled) {
painter->setPen(QPen(QColor(200, 200, 255, 125)));
// draw horizontal grid
double start = round(rect.top());
if (start > rect.top()) {
start -= this->gridStep;
}
for (double y = start - this->gridStep; y < rect.bottom(); ) {
y += this->gridStep;
painter->drawLine(int(rect.left()), int(y), int(rect.right()), int(y));
}
// now draw vertical grid
start = round(rect.left());
if (start > rect.left()) {
start -= this->gridStep;
}
for (double x = start - this->gridStep; x < rect.right(); x += this->gridStep) {
painter->drawLine(int(x), int(rect.top()), int(x), int(rect.bottom()));
}
}
else {
// Erase whole scene's background
painter->eraseRect(sceneRect());
}
slotToggleGrid(this->gridEnabled);
QGraphicsScene::drawBackground(painter, rect);
}
The View currently doesn't contain anything - all is default. The setting of my Scene and View instance inside my QMainWindow is as follows:
void App::initViewer()
{
this->scene = new Scene(this);
this->view = new View(this->scene, this);
this->viewport = new QGLWidget(QGLFormat(QGL::SampleBuffers), this->view);
this->view->setRenderHints(QPainter::Antialiasing);
this->view->setViewport(this->viewport);
this->view->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
this->view->setCacheMode(QGraphicsView::CacheBackground);
setCentralWidget(this->view);
}
EDIT: I tried calling invalidate(sceneRect(), BackgroundLayer) before the update(), clear() or whatever I tried to trigger a refresh.
I also tried QGraphicsScene::update() from within the scene but it didn't work Passing both no argument to the function call and then passing sceneRect() didn't result in anything different then what I've described above.
Found the issue - I forgot to set the size of the scene's rectangle:
this->scene->setSceneRect(QRectF(QPointF(-1000, 1000), QSizeF(2000, 2000)));
I actually found the problem by deciding to print the size of the QRectF returned by the sceneRect() call and when I looked at the output I saw 0, 0 so basically I was indeed triggering the update but on a scene with the area of 0 which (obviously) would result in nothing.
Another thing that I tested and worked was to remove the background caching of my view.
When you change your grid settings, whether it's on or off (or color, etc.), you need to call QGraphicsScene::update. That's also a slot, so you can connect a signal to it if you want. You can also specify the exposed area; if you don't, then it uses a default of everything. For a grid, that's probably what you want.
You don't need to clear the grid. The update call ensures that the updated area gets cleared, and then you either paint on it if you want the grid, or don't paint on it if the grid shouldn't be there.
Sometimes when the view/scene refuse to run update() directly, processing the app events before the update() fixes it, like so:
qApp->processEvents();
update();
I'm trying to port my iPhone app to windows 8, I have a problem with this line (it's objective-c) :
[self schedule:#selector(fire:)];
The equivalent on c++ should be :
this->schedule(schedule_selector(AirScene::fire));
Where AirScene is the name of my class, but I have this error returning by Visual Studio 2012 :
error C2064: term does not evaluate to a function taking 1 arguments
So, in other words the function schedule(selector) is not found. It's strange because I have no problem with unschedule method, do you have any idea please ?
EDIT : AirScene.h
#include "cocos2d.h"
#include "Box2D\Box2D.h"
#include "AirSceneDelegate.h"
class AirScene : public cocos2d::CCLayer {
public:
AirScene::~AirScene(void);
// Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone
virtual bool init();
static cocos2d::CCScene* scene();
LAYER_NODE_FUNC(AirScene);
//Methods
void selectSpriteForTouch(cocos2d::CCPoint touchLocation);
cocos2d::CCPoint checkPuckPosition(cocos2d::CCPoint newPosition);
void panForTranslation(cocos2d::CCPoint translation);
void updateQuestion(string question);
void restoreScene();
void enableScreensaver();
void disableScreensaver(cocos2d::CCPoint touchPosition);
//Setters
void setDelegate(AirSceneDelegate *delegate);
private:
// Init
void initBackground(cocos2d::CCSize winSize);
void initPuck(cocos2d::CCSize winSize, cocos2d::CCPoint position);
void initHoles(cocos2d::CCSize winSize);
void initQuestion(cocos2d::CCSize winSize);
// Methods
void fire(cocos2d::ccTime dt = 0);
void updateHoles(cocos2d::ccTime dt);
void validateVote(bool isPositiveVote);
float getVelocity();
// Attributes
...
//Delegate
AirSceneDelegate* _delegate;
};
AirScene.cpp
#include <iostream>
#include "AirScene.h"
USING_NS_CC;
#pragma region Delete
AirScene::~AirScene(void)
{
///TODO : DELETE ALL
delete(_delegate);
_delegate = 0;
}
#pragma endregion Delete
#pragma region Init
CCScene* AirScene::scene()
{
// 'scene' is an autorelease object
CCScene *scene = CCScene::node();
// 'layer' is an autorelease object
AirScene *layer = AirScene::node();
// add layer as a child to scene
scene->addChild(layer);
// return the scene
return scene;
}
// on "init" you need to initialize your instance
bool AirScene::init()
{
// Super Init
if (!CCLayer::init())
return false;
//Init Attributes
screenSaverMode = false;
hasVoted = false;
schedule = false;
_delegate = 0;
_selSprite = NULL;
//Create World
b2Vec2 gravity = b2Vec2(0, 0);
_world = new b2World(gravity);
CCSize size = CCDirector::sharedDirector()->getWinSize();
//Inits
AirScene::initBackground(size);
AirScene::initPuck(size, ccp(512, 200));
AirScene::initHoles(size);
AirScene::initQuestion(size);
return true;
}
// Init Background and set walls
void AirScene::initBackground(CCSize winSize)
{
...
}
/** Init Puck : create puck body and shape */
void AirScene::initPuck(CCSize winSize, CCPoint position)
{
...
}
void AirScene::initHoles(CCSize winSize)
{
...
}
// Set Question Label
void AirScene::initQuestion(CCSize winSize)
{
...
}
#pragma endregion Init
#pragma region Private
void AirScene::fire(ccTime dt)
{
_world->Step(dt, 8, 8);
//CCSprite *ballData = ((CCSprite *)_body->GetUserData())
((CCSprite *)_body->GetUserData())->setPosition(ccp(_body->GetPosition().x * PTM_RATIO, _body->GetPosition().y * PTM_RATIO));
_puckShadow->setPosition(ccp(((CCSprite *)_body->GetUserData())->getPosition().x + 2, ((CCSprite *)_body->GetUserData())->getPosition().y - 2));
if (screenSaverMode)
AirScene::updateHoles(0);
}
//Ajust Glow Effect and Validate Vote
void AirScene::updateHoles(cocos2d::ccTime dt)
{
...
}
float AirScene::getVelocity()
{
...
}
void AirScene::validateVote(bool isPositiveVote)
{
...
}
#pragma endregion Private
#pragma region Public
void AirScene::selectSpriteForTouch(CCPoint touchLocation)
{
...
}
// Check if the puck is not outside the view
CCPoint AirScene::checkPuckPosition(CCPoint newPosition)
{
...
}
// Move Puck
void AirScene::panForTranslation(CCPoint translation)
{
...
}
// Update Question
void AirScene::updateQuestion(string question)
{
...
}
void AirScene::restoreScene()
{
...
}
void AirScene::enableScreensaver()
{
screenSaverMode = true;
CCSize winSize = CCDirector::sharedDirector()->getWinSize();
//Unschedule actions
this->unscheduleAllSelectors();
//Delete Puck
CCPoint puckPosition = _puck->getPosition();
_puck->removeAllChildrenWithCleanup(true);
_puck->removeFromParentAndCleanup(true);
_puckShadow->removeAllChildrenWithCleanup(true);
_puckShadow->removeFromParentAndCleanup(true);
_world->DestroyBody(_body);
delete(_puck);
delete(_puckShadow);
//RecreatePuck
this->initPuck(winSize, ccp(512, 200));
//Impulse
_body->SetLinearVelocity(b2Vec2(0, 0));
/** ERROR IS CAUSED BY THIS LINE */
this->schedule(schedule_selector(AirScene::fire));
}
void AirScene::disableScreensaver(cocos2d::CCPoint touchPosition)
{
}
#pragma endregion Public
#pragma region Getters & Setters
void AirScene::setDelegate(AirSceneDelegate *delegate)
{
_delegate = delegate;
}
#pragma endregion Getters & Setters
Fixed.
The version of cocos 2d which is used for windows 8 template is an old version, so the good way to schedule a selector is this one :
CCScheduler::sharedScheduler()->scheduleSelector(schedule_selector(AirScene::fire), this, 0, false);