C++

Lenguaje de alto nivel con características de bajo nivel: que permite controlar la memoria de bajo nivel. Uno de los focos: programación orientada a objetos

Descargar el IDE

Compilar/Recompilar

Qu

es traducir los nodos o el lenguaje de programación a 1 y 0, es decir lenguaje máquina, para que la computadora pueda ejecutar el código

si quieres recompilar dale al nombre del proyecto click derecho y Recompilar
_assets/b7d0476c-0faf-4ca0-80c5-7cb14c0fa097.webp

Crear una clase de C++

Tools/New C++ Class

_assets/image88.webp

_assets/5c5c02e6-6a02-4559-a302-4f967cadcde5.webp

En el head se crean estas "variables":

_assets/178bd63b-f393-4612-bb9c-693b2c89a67f.webp

Dónde encontrar las clases en el editor de UE

_assets/b2342787-6e9c-4344-b812-ecf08250eda9.webp

Instanciar clase de C++ como BP

Buscar la clase de C++ en
C++ Classes/nombre del proyecto
y click derecho/Create BP Class based on {nombre de la clase}
se creará en la carpeta Content
image-2.webp

Archivos de C++

.h (.hpp .hxx .hh .h++) (archivos de encabezado, headers)
contiene los identificadores (nombres) de elementos
aquí se declaran variables

.cpp (archivos de cuerpo)
Siempre hay parejas de archivos que que se relacionan por su contenido
aquí se definen variables

Explicación del código inicial en una nueva clase

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Score.generated.h"

UCLASS()
class CPP_PROYECTOCURSO_API AScore : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AScore();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

};
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Score.generated.h"

aquí se están importando cosas de otros lados
CoreMinimal.h: algunas funciones básicas
GameFramework/Actor.h es la clase padre, en este caso Actor
Score.generated.h son los binarios que se crearon al crear esta clase (Score)
así como la comunicación entre blueprints se hace (por ejemplo) con casteos, en C++ se tiene que importar la referencia de la otra clase, aquí en la cabecera

UCLASS()
class CPP_PROYECTOCURSO_API AScore : public AActor
{
	GENERATED_BODY()
public:	
	// Sets default values for this actor's properties
	AScore();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

public: es accesible desde cualquier parte
protected: solo es accesible desde esta clase o clases que hereden de esta (hijos de esta clase)

BeginPlay y Tick, son métodos, que ejecutan al inicio del juego y en cada frame, respectivamente
virtual significa que puede ser reescrito en clases hijas
override significa que

Expresiones

unidad básica sobre la que se construye los programas
Una expresión tiene 2 propiedades

Statement

en español instrucción o enunciado
es la unidad básica de ejecución en C++
es una línea o bloque de código que manda una indicación para que el programa haga algo

diferentes statements

Comentarios

Es una forma de dejar notas en el código, no forma parte del código a compilar

//esto es un comentario de una sola linea

/*
así se hace un comentario
de 2 o más lineas
*/

Variables

la representación de un valor en memoria
siempre tiene un nombre para que podemos acceder a ella
siempre son declaradas en el .h (header)

Constante

A diferencia de una variable, una constante es un valor que no cambia después de ser definido
un dato que no varia ni cambia nunca

2 tipos de constantes:

Diferencia entre = y ==

a = 2 es como un set
a == 2 se usa para preguntar si a=2? como en un if

White spaces

El compilador ignora el excedente de espacios (o tabulaciones)
en programación una tab es igual a 2 espacios

_assets/c8ed8757-68c3-40f8-afcd-0c743abdfcf1.webp

pero poner espacios en medio de un identificador (como en el tercer ejemplo) 👆 está mal
el identificador es valu e

Keywords

Son palabras que no puedes usar como identificador, porque ya están tomadas por C++
así como wiggle en after effects, ya está tomado

Identificador: significado

sería el nombre de una variable...como registrar una marca en registro público...los nombre tienen que ser únicos, por eso hay algunos identificadores que ya están tomados por funciones y que no puedes usar)

Tipos de datos

Qué son "datos literales"

llamamos "literales" a los datos sin nombre, no se puede hacer referencia a ellos excepto cuando los usamos

Recomendación

Los tipos (de variable) escribirlo manualmente, no fiarse del predict (autocompletado) que a veces lo escribe mal

Signed vs Unsigned (variables)

signed y unsigned son "especificadores" se ponen delante del identificador (nombre de variable) como complementando

signed se refiere a "tiene signo" por lo que aceptará positivo o negativo

_assets/4bcc9acf-27ab-45b4-bfc0-61b93303feb5.webp

sizeof()

Overflow/Underflow

cuando se intenta guardar en una variable, un valor, que sale del rango, si es
255+1 = 0

Cuando se sobrepasa por arriba es overflow, cuando se sobrepasa por abajo es underflow

Ejemplo de underflow

0-1 = 255

El overflow/underflow se tienen que evitar, porque no se comporta consistentemente

Variables

Ejemplos de declaración de variable

int demoint;
short demoshort;
float demofloat;

Diferencia entre Declarar vs Definir vs Inicializar vs Asignar variables

Declarar: Le dices al compilador que existe una variable, pero no reservas memoria todavía (si es solo declaración sin definición).

extern int Score;

Definir: Crea la variable sin un valor, pero aquí sí se reserva memoria para la variable. Si ya la declaraste antes, ahora la estás haciendo “real” en la memoria.

int Score;

Inicializar: Cuando defines la variable y le asignas un valor al mismo tiempo

int Score = 0;

Asignar: Cuando ya la has Definido previamente pero NO le habías asignado un valor, entonces lo haces ahora (más adelante en el código)

Score = 10;

el tipo de valor (int, bool, float, etc...) se debe indicar cuando declaras y defines la variable, NO cuando inicializas o asignas, porque se entendería que estás volviendo a definir el valor, daría error

Si

si tu valor es numérico, por recomendación siempre setearle un 0 como valor por defecto, por que si no le das un valor c++ le dará un espacio en memoria, lo llena con algo pero no sabes qué, podría estar llenándolo con "basura".

Tipos de inicialización

Tipo Sintaxis Seguro Notas
Copy Initialization int x = 5; Clásica, pero puede hacer copia
Direct Initialization int x(5); Llama constructor directamente
List Initialization int x{5}; ✅✅ Moderna y más segura
Value Initialization int x{}; Inicializa a 0 o constructor
Default Initialization int x; Puede tener basura
En el curso siempre usamos copy initialization

Operadores aritméticos

_assets/cc07dcab-ee97-4323-aec4-075babbfe958.webp

declarar variables en el .h (header) debajo de public

//Variables

char demochar;

int demoint;

short demoshort;

float demofloat;

dar valor (o setear) variables en el .cpp debajo del //sets default values

_assets/e882356e-6e05-463c-9902-17279bf696ed.webp

aqui en demo int queremos setearle un nuevo valor

entonces en la ultima linea demoint = demoint + 1;

si se le quieres sumar solo una unidad se puede hacer esto

Ternarios...

Operadores lógicos

_assets/ae6d5aaa-1426-4226-9061-53e7b35dd9ac.webp

recomendación: no usar la coma

_assets/88ca93a3-9b60-452c-baca-6effb08befb1.webp

atajo para compilar: Ctrl + Shift + B

PRINT STRING (tener a mano siempre)

GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("Some debug message")));

Funciones

serie de enunciados o statements que se ejecutan secuencialmente para ser reutilizadas
permite encapsulamiento de código
se debe declarar en .h (nombre, parámetros, tipo de retorno)
y definir en .cpp (el código que indica qué debe hacer)

_assets/be89debe-63d5-4b16-9f29-0955bd1cc145.webp

al declarar la función, se tiene que poner el tipo (int por ej.), identificador (nombre de la Funcion), y entre paréntesis los parámetros a usar, a los cuales también se especifica su tipo

_assets/0e6d29cc-46c6-4df9-8f95-589298d73e43.webp

_assets/491cdcf0-183f-4cbf-b09a-253bce041197.webp

_assets/23881076-baf9-4a22-849f-f9c2f0ad5f23.webp

Declarar función (.h)

se declaran debajo de public

int FuncionEntera();

_assets/edb81ac3-da98-4c9e-b91a-0f2dfd656896.webp

aquí ambos serían lo mismo, está creando una función que devuelve un valor entero (int) y también se le da un nombre (identificador) a la función

además dentro del () puede haber parámetros, estos serían los valores de entrada

int FuncionEntera(float parametro, int parametro2);

estos pueden ser de cualquier tipo y se tiene que poner el tipo e identificador igual que con una variable, si quieres poner varios parámetros, separarlos por comas

si vas a hacer una función que no devuelve nada, solamente ejecuta algo, sería un void y hay que especificarlo (el void solo se utiliza para funciones, nunca para variables)

void FuncionVacia(float parametro, int parametro2);

_assets/921d04b8-d31a-4939-a1e0-6044068d315c.webp

para que te cree la estructura de la función, puedes darle al screwdriver y a "Crear definición..."

esto lo que hará es abrir el .cpp y hacer ahí la estructura de la definición de función...

_assets/8cd2d8e0-eec6-4942-a7e3-599b0e051780.webp

Definir función (.cpp)

la definición se puede escribir al final...

...entonces con el truco de screwdriver,te crea esta estructura para definir la función en el .cpp

el color verde agua refiere al nombre de la clase

Te escribe entre {} y un return 0; el return quiere decir que está devolviendo un valor, y el 0, es normalmente en programación significado de que se ejecutado bien el código

int ATutFunctions::FunctionEntera(float parametro, int parametro2)
{
	return 0;
}

(si la función es un bool, el return tenemos que escribirlo como true o false)

Luego completar con lo que pasa dentro de la función, que en este caso sería multiplicar los dos parámetros, se puede guardar en una nueva variable, que en este caso se llama int resultado

y ponerle return...el resultado

int ATutFunctions::FuncionEntera (int parametro, int parametro 2) {
	int resultado = parametro * parametro2;
	return resultado;
}

esta es la forma menos óptima ☝, porque en este caso no es necesario guardar esta variable, entonces podríamos directamente poner lo que hace en el return, ej: 👇

int ATutFunctions::FuncionEntera(int parametro, int parametro2) {
	return parametro*parametro2;
}

Llamar función (desde un Event)

OK ahora que ya declaramos (.h) y definimos (.cpp), la función existe, pero no se ejecuta porque no la hemos llamado (en BP es como si no estuviera en el event graph ni conectada a ningún nodo rojo de evento)

Para llamarla hay que escribirla, debajo de algún algún evento, como el BeginPlay, o el Tick

pero como ya la hemos definido (le hemos puesto qué tipo devuelve y qué tipo son sus parámetros) al llamarla solamente pondremos sus valores, ej:

void ATutFunctions::BeginPlay()
{
	Super::BeginPlay();
	FuncionEntera(2,3);
}

_assets/89fea0fc-a31b-42f1-a9ea-7e87f82f7f20.webp

aquí está FuncionEntera, debajo del BeginPlay y especificando sus parámetros 👆

En este caso, según lo que se escribió al definir la función, multiplicará ambos parámetros

En este caso deben ser 2 integers, porque así se puso en la definición de la función

pero...aunque se está ejecutando la función, no se está guardando el resultado en ningún lado, sería bueno guardarlo en una variable, entonces la guardaremos en una que creamos previamente: demoint

de esta manera estamos asignando demoint

void ATutFunctions::BeginPlay()
{
	Super::BeginPlay();
	demoint = FuncionEntera(2,3);
}

_assets/c38063d0-e24f-4872-9b0b-c8042cf8f57f.webp

Parámetros por defecto de una función

cuando se inicializa, en el .cpp (también se puede en el .h pero hacerlo en el .cpp es más ordenado)
se puede dar valores por defecto a los parámetros poniéndoles un = y un valor

int ATutFunctions::FuncionEntera(int parametro = 4, int parametro2 = 5){
	return parametro * parametro2;
}

_assets/f56a5e22-4a04-42e6-a72f-ea98740e763d.webp

Scope local

C++ variable scope explained 🌎

Hblar de un scope básicamente es hablar de los diferentes graph en BP (un scope es un graph)...
una función no sabe qué ocurre dentro de otra

hay variables locales y globales

para sacar algún valor de una función, puedes asignarle este valor a una variable global, así todo mundo podrá leerla

pero NO OLVIDAR que hay que llamar a la función, sino esa variable global seguirá teniendo su valor por defecto, porque la función existe pero no se está ejecutando...ejemplo...

tengo una función tipo void, que opera una multiplicación...quiero sacar el resultado de la función para usarlo en otro lado, pero que la función siga siendo void

int ATutFunctions::FuncionEntera(int parametro = 4, int parametro2 = 5){
	demoint = parametro * parametro2;
}

_assets/7db91112-0024-4ce3-a036-6de3bcbccf60.webp

aquí se está usando la variable demoint para guardar la operación de parámetro*parámetro2 , pero eso solo pasa dentro de la función, si quieres sacar ese valor, hay que llamar a la función

Overload

Pueden coexistir funciones con el mismo identificador, pero con diferentes firmas
el identificador es el "nombre" la firma es ...tipo que devuelve+firma+parámetros de entrada

_assets/f49fcd27-4eaa-4780-bed8-71075e337919.webp

así que cuidado con eso, puede ser útil para algunas cosas (Jaume dice que para gameplay) pero puede confundir, al volver a un proyecto después de tiempo y ves dos funciones que se llaman igual pero hacen cosas distintas, de preferencia siempre nombrarlas todo diferente

si tienes una función de 2 parámetros, y al llamarla solo quieres ponerle el primero, y que para el segundo use su valor por defecto, debes hacer así

FuncionEntera(2)

porque si lo haces así 👇

FuncionEntera(2, )

va a parecer que te has equivocado y el programa lo detectará como error

Ejemplo práctico:

Health es una variable (previamente declarada) a la que le estamos asignando un valor

este valor es el resultado de FuncionEntera (esta función, multiplica sus parámetros que en este caso es 2 y 3, el resultado es 6)

la segunda linea es otro ejemplo, en este caso es un booleano, que está comparando si Función entera, que en este caso además está multiplicado *3, resultaría 18...es mayor o igual que Helath, dependiendo si se cumple o no, el bool será true o false

Health -= FuncionEntera(2,3);
bool demobool = Health <= (FuncionEntera(2,3)*3)

PRIMERA LINEA
se está restando Health - resultado de FuncionEntera(2,3)...recordemos que programamos FuncionEntera para que multiplique sus argumentos, entonces FuncionEntera valdría 2*3=6
Si Health tenía un valor inicial de 10, tras la operación:
Health = Health - 6;
Health = 4;

SEGUNDA LINEA
Se está evaluando si Health es menor o igual al resultado de FuncionEntera(2,3) * 3.
Ya sabemos que FuncionEntera(2,3) es 6, entonces 6 * 3 = 18.
Como en la línea anterior dejamos Health = 4, la comparación es:
4 <= 18 --> verdadero (true)
Por lo tanto, la variable booleana demobool será igual a true (es decir, su valor será 1).

Nomenclatura

bool bIsMoving;

Otros conceptos

_assets/a1af9a93-ef26-403f-bd10-6cb97eb11e58.webp

una firma: tipo, nombre, inputs (parámetros de entrada)

_assets/5bc82dc4-5550-4c0e-b75c-79c594d2b5d7.webp

Control de Flujo

en C++ se suelen usar if y switch
siempre dentro de un evento, tipo begin play, porque si se pone fuera, no se ejecutará

IF

if (20 == 30){     //entre los () se pone la condición
	//CODIGO
}

si la condición se cumple, ejecuta el "//CODIGO", sino, lo saltea y va a la siguiente línea

if inline

este es una versión del if en una sola linea, pero solamente puede ejecutar en caso el if sea verdadero, no se puede definir qué ejecuta si el if es falso, ejemplo...

if (20 == 30) example = true;

si la condición 20==30 se cumple, entonces seteará example (que es una variable booleana) como verdadero, en este caso como no se cumple, example mantendrá su valor anterior

Ejemplo haciendo un Print

copiar pegar esta linea:

GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("Some debug message")));

_assets/04d63a00-3622-43fa-8e3a-139fbbe344e2.webp

el segundo encerrado con un cuadro verde

_assets/46514b4b-3307-458b-b4dc-0be8e2bf1fdd.webp

Fstring es una clase que maneja y manipula cadenas de texto dentro de UE

FString:Printf es un método de la clase FString, que permite crear una cadena de texto formateada, insertando valores variables en la cadena utilizando un formato similar al de la función printf en C.

Else

_assets/b846b5c6-a880-4ad9-9827-94852a568e67.webp

Si el if no se cumple, luego de cerrar el } podemos poner un else

_assets/14934215-d82d-4bae-a587-5394c419a329.webp

En este caso
setea a number1 = 21
pero luego compara number1 == 20...lo cuál es falso, porque ya lo hemos seteado como 21
entonces no se cumplirá el if, y se irá al else

en este caso, como no estamos formateando textos, no es necesario el FString::Printf, solamente luego del FColor::Yellow se pone una coma y se escribe el TEXT("")

else if

es poner un if else tras otro

_assets/761448b2-ede6-45b1-b9dd-0979eccd164f.webp

aquí, después de no cumplirse al primera condición, luego del else, pone un if, para comprobar si se cumple otra condición...y en este caso ahí termina, en el siguiente else ya no pone otro if, pero podría, aunque no es óptimo

Switch

switch (condición){
case 1: bloque de ejecución; break;
case 2: bloque de ejecución; break;
case 3: bloque de ejecución; break;
default: bloque de ejecución;
}

_assets/cc5c213c-fc55-4e04-90b7-747e9beeb596.webp

En este caso imprimirá

faso
animal1

primero "faso" por la función anterior, resultante del else (no tiene nada que ver con el switch)

luego "animal1" porque se cumplió la función de number1 = 42

si no pusiera un break en el primer bloque de ejecución, lo que imprimiría sería

faso
animal1
animal2

Ejercicios if else

_assets/c11ecae2-402a-4c5d-a419-2ab169d852ff.webp

Ejercicio 1

_assets/3efa7141-ec06-44ba-921f-93b70b4bd06a.webp

cuando declaras una variable, sí le pones el tipo, y para asignar le pones un =

pero cuando la llamas para el if, solamente escribes el nombre, y le pones == para comparar en el if

Ejercicio 2

_assets/3f629b1f-a016-47ee-bc11-957114aab9d6.webp

Ejercicio 3

_assets/d02d8774-c7b3-48da-8695-9e868b54d672.webp

Loops

image96.webp

_assets/861b9f1a-ff7a-4af4-a9e4-3681d40c7842.webp

While

mientras

cuando la ejecución llega al while, evalúa la condición, si es verdadero (cualquier valor diferente a 0) se ejecuta otra vez y así hasta que en una de esas evalúe la condición y salga falso

ese cambio debe suceder dentro de las {} del while
si la condición es 0, ya no se ejecuta y sigue el hilo de ejecución

Sintaxis (igual que un if)

while (condition) {
//Este bloque solo se ejecutará si se cumple la condición
}

Do While

Es parecido al while, con la excepción de que la primera vez se ejecuta sin preguntar, luego las demás ya comprueba si la condición se cumple, pero el Do While siempre se ejecuta al menos una vez sí o sí

Sintaxis

do {
//Este bloque se ejecutará sí o sí la primera vez, luego entrará en un loop de comprobar si la condición se cumple para ejecutar el bloque de código
} while (condition);

_assets/0a2040c3-14ac-4f35-9256-db3c34e2f442.webp

For

Estructura básica de un for
_assets/1da29218-04b1-42da-b666-15de628995ef.webp

En BP sería así
_assets/0eb11371-db63-4783-bf58-bc3833e5053c.webp

int i = 0 es el primer index o index actual
i<10 es el último index
i++ es cuánto va a aumentar entre cada iteración

lo que está en la segunda línea sería el loop body

En este caso, dice que:
(int i = 0; i<10; i++)
int i = 0 indica que el primer index será 0
i<10 que el último será menor a 10
i++ indica que contará de uno en uno

es decir que irá contando de 1 en 1 de 0 a 9, en total 10 iteraciones

loop body: imprimirá "Resultado %f" siendo f la variable resultado, pero aumentando su valor en +1 cada vez (esto indicado por que en la siguiente línea, dice resultado++, es decir que en cada iteración subirá +1

_assets/37d2c7c8-9823-4a95-adf9-109d77be3677.webp

este en cambio, indica que contará desde 50 hasta menos que 500, de 50 en 50→ 50, 100, 150, 200, 250, 300, 350, 400, 450, es decir 9 veces

la variable resultado comenzará en 0 y en cada iteración aumentará 5_assets/56d69bed-828a-467f-8852-7380500f7f34.webp

es decir imprimirá así:
Resultado: 0
Resultado: 5
Resultado: 10
Resultado: 15
Resultado: 20
Resultado: 25
Resultado: 30
Resultado: 35
Resultado: 40

Break en loops

El break sirve para romper el loop en cuando se cumpla una condición específica, por ejemplo

_assets/0355cdbf-e596-48d6-8318-126e22527787.webp

aquí el index va a ir de 0 a 9 aumentando de 1 en 1

en cada iteración de resultado va a aumentar 1, pero además en cada iteración, va a comprobar que si index es == 5, si es igual a 5, se detendrá la ejecución

Más ejercicios (no resueltos)

_assets/5748ddaf-f337-4024-b320-a6009a063922.webp

Convención de nombres

Variable de tipo entero

los que llevan u, se refiere a unsigned, que no permite los negativos para que el valor sea el doble

Variables de tipo decimal

Variables de tipo boolean

Variables de tipo char

Variables de tipo string

Conversiones de tipo

_assets/06eee97f-0746-4f61-aede-6a4f81259678.webp

acceder a variables y funciones

_assets/2950f171-ee4b-4b93-ac98-e55c13b46dcc.webp

los morados son funciones

los azules son variables

_assets/fe3c5758-60c6-4e17-9d1b-ee5d98349a62.webp

como en blueprints para hacer referencia una propiedad de un componente, se usa get...GetActorLocation por ejemplo, y siempre con el punto.

por ejemplo

vector.X = GetActorLocation().X;

en este caso todos son absolutos, a menos que lo especifique que sea relativo

también está GetScale3D()

cómo hacer un set:

_assets/ca2b7008-d9b2-4a74-a007-ddf1b137fbbf.webp

faltan:

hacer los ejercicios 1-6

preguntar por conversiones sanitize

Reflexión

Consiste en que el propio código sea consciente de sí mismo (nombres de las funciones, variables, etc.)

Permite exponer las clases en el editor. promoverlas para que puedan ser editadas en el editor de unreal

esto en el .h

_assets/9df1bc70-9ba4-481b-a97e-f54d951b72f9.webp

_assets/1336ba54-1d30-40c2-b0b1-7057c4e41e35.webp

_assets/e76bfed2-b63b-4b6e-82ea-cc76bddcb276.webp

Qué es el CDO?

Class Default Object, es una instancia única de una clase que contiene los valores predeterminados de todos los miembros de esa clase. Es como una plantilla que define cómo deben comportarse y cómo deben estar inicializados los objetos de esa clase cuando se crean en tiempo de ejecución o en el editor.

Constructor: es la primera función del .cpp lanza todo el código cuando compila

BeginPlay y Tick: El BeginPlay se ejecuta 1 vez al inicio del juego y el Tick una vez cada pulso

Están en el .h

_assets/9462b07c-c72b-4b8e-8b02-491d69be107a.webp

y en el .cpp

_assets/e182f91d-56cc-426f-95f2-16d723f67527.webp_assets/08a84f24-17ea-4271-8e82-027b3de368d0.webp

_assets/ae412a96-e740-4909-8695-4fc38885d7e6.webp

Diferencia entre función pura e impura

pura: para hacer operaciones matemáticas, no están atadas al flujo de ejecución porque solo devuelven un valor. Estas funciones sí que podrían implementar en h y llamarlas desde el cpp. Si pueden tener inputs de entrada, de algún float, int, bool, etc...pero no necesita estar conectada al flujo de ejecución (hilo blanco)

impura: se hacen en el flujo de ejecución, sí o sí en el cpp, y está atada el flujo de ejecución

pero por lo general siempre se hacen en el cpp

El especificador UCLASS que está en el .h tiene que estar para que pueda ser visible cuando querramos materializar esta clase de c++ en el editor a través de un blueprint

UPROPERTYS

exponer cosas de c++ en el editor!

Primero materializar la clase de c++ en un blueprint...

_assets/35de86c3-7d03-4570-9327-4dff2e5c4d5a.webp

crear un blueprint, en la clase, buscamos el nombre de la clase que creamos en c++

_assets/4757e918-8877-4848-a093-1800c329b669.webp

El Uproperty (para variables) o Ufunctions (para funciones) son especificadores (al igual que el UCLASS) que se tiene que poner encima de todas las variables que quieras tener disponible en el sistema de reflexión (las que quieras disponible en blueprints)

se tiene que poner encima de CADA UNA! no por tener varias debajo va a englobar varias

por ejemplo para exponer 2, tiene que ser así, en cada una

_assets/8c59125b-e59a-4d58-8f04-2ac6f7aaf90d.webp

Qué cosas hacer en c++ y qué cosas en BP?

  • C++, todo lo de generación procedural, cálculos matemáticos, sistemas, es más óptimo
  • Blueprints: Gameplay, mecánicas, dinámicas, es más fácil para implementación

_assets/efdde86f-2af8-46b8-a253-05eb24d2101e.webp

Especificadores de UPROPERTYS

_assets/b86ad3e4-ae62-43a0-9e8a-1763ff604a29.webp

Por ejemplo si le ponemo el especificador BlueprintReadOnly en BP solo podremos acceder a su get, no a set

_assets/0bfa4803-d373-42a2-99a0-a2a6b351a2f0.webp

_assets/e43839a3-60f1-4ea2-9d68-ec0196d3b423.webp

Si usamos BlueprintReadwrite podemos usar tanto set como get en bp

El VisibleAnywhere lo hace visible en cualquier parte del editor pero no es editable

VisibleInstanceOnly hace que solo sea visible en las instancias o cuando está en el nivel

EditAnywhere este expone el valor en el editor, pero no en el blueprint, por ejemplo aquí las variables IsMoving y Health

_assets/9a4e7832-8d58-4294-ae1b-c8017b1b0a7f.webp

EditInstanceOnly

_assets/dd5471a6-e999-4d28-be7d-a9788e2067f7.webp

_assets/49c7c41a-221c-44d8-bf00-de6c3d3ee66b.webp

El de Category es para que cuando llames, te aparezcan por categoría, por ejemplo aquí se le puso al Health la categoría de enemigo, entonces salen agrupadas

_assets/6d0cb541-5f06-42cb-928a-a361dc406a86.webp

BORRAR TEMPORALES

  • Borrar Binaries, Intemediate, DerivedDataCache
  • Borrar la solución
  • volver a generar la solución con click derecho/Generate Visual Studio project files
    _assets/3948806b-9cb0-4551-ab92-64134218fcf2.webp

UFUNCTIONS

Las funciones de una clase pueden usarse desde C++, pero con las UFUNCTIONS también se pueden usar desde BP. Hay 2 formas de hacerlo

Igual que los Uproperties, solo se lleva a BP la primera función que lee debajo del UFunction, OJO que este 👇 ya tiene un especificador "BlueprintCallable" que hace que sea llamable desde BP

UFUNCTION(BlueprintCallable) //El macro "UFUNCTION" hace la función disponible en el sistema de reflexión y el especificador "BlueprintCallable" la hace llamable desde BP
bool IsMovingPeroEsUnaFuncion(); //Esta es una función cualquiera

_assets/66bb6d36-b786-4b55-a904-6690b3321358.webp
_assets/c3cdb4df-3eca-4ccc-bd96-6c5c749a9415.webp

//EJEMPLO DE ESPECIFICADORES: (SYNTAXIS)
UFUNCTION(specs_1,specs_2,specs_3,...,meta=(metaspec_1,metaspec_2,...))
tipoEjemplo FuncionEjemplo(params_1,params_2,params_3,...);

Especificadores de UFUNCTION

los primeros 4 son los más usados

Ejemplo

UFUNCTION(BlueprintCallable, Category="Function Category")
void DoSomethingInEditor() {};

Implementable/Native Event (especificadores)

sobrescribir funciones en BP

_assets/f399e17c-78fb-4cdd-b872-ca494aa42142.webp

BluerprintImplementableEvent: sirve para solamente declarar la función en C++ pero implementarla en el event graph de blueprints, por si por alguna razón no se puede o no se sabe hacer en c++ (en blueprint lo crea como un evento, nodo rojo)

UFUNCTION(BlueprintImplementableEvent)
void Funcion_Por_Implementar();

_assets/d10fd48a-5baa-40e8-b35b-94ec278dbd35.webp

BlueprintNativeEvent
tiene que tener su implementación en C, si gustas puedes sobreescribirla en BP
este especificador es más usual cuando se trabaja en equipo, si es en solitario hay formas más fáciles de hacer esto como con el BlueprintImplementableEvent

UFUNCTION(BlueprintNativeEvent)
void Funcion_Nativa();
2 Condiciones para que funcione NativeEvent

  • ponerle el identificador en .h (BlueprintNativeEvent)
  • cuando la definas en .cpp hay que agregarle al nombre un _Implementation

hay que crearle su estructura en el .cpp, entonces desde el .h ayudándonos del destornillador
_assets/03a34d55-8427-4b3f-bc36-c7992135345b.webp

_assets/ffddb275-b8a7-4382-a004-c9a5ba242a33.webp

Hay que agregarle la palabra "Implementation" a la función, SINO NO FUNCIONA 👇
esto en el .cpp

_assets/61a4914b-b38a-4523-a1cb-6d45619075f2.webp

_assets/e9103187-dacd-4814-806a-b9371d9aa62c.webp

Punteros y Referencias

Puntero (*)

int a = 10;
int* p = &a;   // p guarda la dirección de a
*p = 20;       // cambia el valor de a a 20`

Referencia (&)

int a = 10;
int& r = a;    // r es otro nombre para a
r = 20;        // cambia el valor de a a 20`
Característica Puntero Referencia
Puede ser nullptr ✅Sí ❌No
Se puede cambiar ✅Puede apuntar a otra cosa ❌ Siempre apunta a lo mismo
Sintaxis Usa * y & explícitamente Se usa como una variable común
Necesita inicialización ❌ No es obligatorio ✅ Obligatorio

Componentes

Solo los actores pueden tener componentes, es una forma de separar la lógica de una entidad en varias entidades

Generalmente se parte de 2 componentes:

Sobre el RootComponent

Cuando creas un nuevo BP siempre viene con un DefaultSceneRoot
_assets/cba9e20a-8ea9-49c9-93df-b4f8254fd063.webp
este elemento en C++ se llama "RootComponent" y es de tipo USceneComponent
La función del RootComponent en C++ (o el DefaultSceneRoot en BP) es ser la raíz de todos los demás componentes y organizarlos, sin él, la clase no podría tener representación espacial (PSR) y daría error

el DefaultSceneRoot de BP está pensado como un placeholder para que sea reemplazado con un componente real, por ejemplo un Static mesh 👇
_assets/553e58c0-b072-43c2-8513-9fd8ae8cc23d.webp
y que quede así
_assets/310b0091-d7c6-4b2b-823a-375257de6731.webp

de la misma manera C++, el RootComponent (aunque no aparece en el código inicial de C++) debe ser sobreescrito por un componente real, entonces para crear un componente nuevo hay que hacer lo siguiente

  1. Declarar un nuevo componente (en .h)
  2. Instanciarlo (hacerlo real) en (.cpp)
  3. Ponerlo en la jerarquía en (.cpp)
    1. Si es el primer que creamos debemos setearlo como Root Component
    2. Si ya existe un root component, Atacharlo a la jerarquía

Declarar un primer componente (.h)

al igual que cuando creamos un componente en BP, debemos indicar qué tipo de componente es
Si es el primer componente que creamos, debemos setearlo como el RootComponent, por lo tanto sí o sí debe ser tipo USceneComponent (este tipo es como si dijéramos tipo float, bool, int, etc.) y también ponerle el identificador, por ej.

en .h debajo de algún public:

public:
	UPROPERTY(EditAnywhere);//Especificador necesario para editarlo en BP
	USceneComponent * SceneComponentDePrueba

El * Es una Referencia

Instanciarlo en la escena (.cpp)

hasta aquí hemos declarado, pero falta instanciarlo (hacerlo real en la escena)

Materializar

No es un término técnico pero en este caso equivaldría a Instanciar o hacerlo real en el mundo

En este caso lo debemos instanciar desde el constructor

porque si lo hacemos por ejemplo desde el BeginPlay, se crearía recién cuando ejecutamos el juego y no lo podríamos ver en el Graph editor del BP
no siempre debe ser así, por ejemplo si queremos crear componentes en ejecución, como en el Teleport de XR, pero en este caso sí porque es parte de la estructura de la clase

en .cpp en el constructor

AScore::AScore() //👈el constructor, recuerda que siempre se llama como el nombre de la clase, aquí la clase se llama Score
{
 	PrimaryActorTick.bCanEverTick = true;

	currentScore = 100;
	//abajo está lo que importa 👇
	
	SceneComponentDePrueba = CreateDefaultSubobject<USceneComponent>(TEXT("Pepito"));
/* la estructura de esta linea va así:
NombreDelComponente =
CreateDefaultSubobject
<tipoDelComponenteQueSeEstáCreando>
*/
		RootComponent = SceneComponentDePrueba;
//Esta linea estamos seteando al compononente que creamos como RootComponent (raíz) en este caso es necesario porque es indispensable que exsta un Root sino daría error (el root por defecto está vacío, por eso no podemos dejarlo así)
}
  1. Nombre del componente (identificador), "=" setear...
  2. Función CreateDefaultSubject
  3. entre <> el tipo de componente que estamos instanciando ,en este caso es un USceneComponent
  4. (TEXT("...")) en vez del ... poner el nombre que queremos que aparezca en el Blueprint, y no olvidar el ;
  5. en caso que sea el 1er componente Una linea abajo, setear RootComponent = SceneComponentDePrueba, aquí estamos sustituyendo el placeholder por nuestro componente creado

En el editor de UE

Declarar un segundo componente (.h)

como no es el primero, ya existe un root component seteado, solo es necesario atacharlo a esa jerarquía

en .h debajo de algún public

public:	
	UPROPERTY(EditAnywhere);
	USceneComponent* SceneComponentDePrueba; //Este es el RootComponent
	
	UPROPERTY(EditAnywhere); //👉NO OLVIDAR para poder editarlo en BP
	UStaticMeshComponent* SMComponente//Este es el nuevo Componente, en este caso es un StaticMesh por eso le ponemos el tipo correspondiente

en .cpp en el constructor

AScore::AScore() 
{
 	PrimaryActorTick.bCanEverTick = true;

	currentScore = 100; 

	SceneComponentDePrueba = CreateDefaultSubobject<USceneComponent>(TEXT("Pepito"));
		RootComponent = SceneComponentDePrueba;

//nuevo componente 👇

	SMComponente = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("SMComponente"));
		RootComponent = SceneComponentDePrueba;
	S = CreateDefaultSubobject<USceneComponent>(TEXT("Pepito"));

	/*aquí la primera linea sigue la misma estructura que el componete anterior:
	NombreDelComponente =
	CreateDefualtSubobject
	<tipoDelComponente> en este caso es un StaticMesh, usamos su tipo correspondiente
	(TEXT("NombreDelComponenteEnElBP"));
*/
	SMComponente -> SetupAttachment(SceneComponentDePrueba);
/* la segunda linea sí es diferente
	NombreDelComponente ->
	SetupAttachment
	(NombreDelComponente al que se attacha);
}

_assets/15b34c11-9e07-43ef-85ad-a5de1d06e72c.webp

_assets/5743f494-dce2-41e7-bad2-d98d2aafb1a1.webp

_assets/d70cf373-dc2f-4efc-bcce-eb348df727f4.webp

Rotar Componentes

Es decir acceder a las propiedades de una variable/función y modificarlas
primero hay que pensar dónde (desde qué evento) se ejecutará por ejemplo si queremos que una Mesh rote perpetuamente, podemos ponerlo en el Tick (.cpp)

void APuntaje::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	SMComponente -> AddWorldRotation(FRotator(0.f, 2.f, 0.f));
}

Primero se pone el nombre del Componente
-> la flecha es para acceder a una propiedad/función dentro del Componente
En este caso accede a AddWorldRotation
FRotator es una estructura conformada por Floats
también existen FVector (Vector conformado por floats)
FTransform (Transform conformado por floats)

(0.f, 2.f, 0.f)
los números adentro, son floats
si los escribiéramos como (0.0, 2.0, 0.0) no serían floats sino doubles, que son floats de doble precisión, son más exactos, pero ocupan más memoria, en este caso que estamos haciendo matemáticas 3D, usar floats es suficiente
(0.0f, 2.0f, 0.0f) también se considera como si fueran floats, cualquiera de las 2 maneras vale

Añadir #includes o dependencias

a veces algunos componentes no aparecen disponibles, necesitan que se importen librerías al principio en los includes para que estén disponibles, esto pasa con un USpotLightComponent por ejemplo

Cómo saber qué dependencias necesita?

Buscar ese componente en BP
image-5.webp
y luego en google buscar por ejemplo USpotLight y buscar su "Include" recordar que los componentes siempre comiezan con U

image-6.webp
Esto es de dev.epicgames.com

Entonces habría que copiar y pegar ese include en .h arriba de todo

en .h

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/SpotLightComponent.h" //👈Agregar el include necesario
#include "Puntaje.generated.h" //👉 el .generated.h siempre debe ser el último de los includes

Operar Componentes

en .h en public (previo include del componente correspondiente()

USpotLightComponent* LuzParpadeante;

en .cpp, dentro del Tick porque quiero que cambie cada frame


Acceder a las variables de una clase desde otra clase

Primero hay que añadir con un #include a la clase a la cual queremos hacer referencia, añadir el .h, no el .cpp, sería algo así

#include "MySecondActor.h" //👈aquí lo estamos añadiendo, sin la A
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/SpotLightComponent.h"
#include "Puntaje.generated.h"

Luego en el .h hay que crear la referencia a esa clase

UPROPERTY(EditAnywhere) //imprescindible: que haya una variable de este tipo en el nivel
AMySecondActor*secondactor;
//Se pone el nombre de la clase con un A adelante y con un puntero se referencia a un  se usa puntero porque estamos tratando con clases personalizadas,componetes o clases propias de Unreal

Es requisito! que en el Actor que estamos referenciando sus variables estén guardadas bajo public:

ahora podríamos acceder a las variables de la clase referenciada, esto en .cpp en el BeginPlay
me quedé en el 1:07 masomenos de la penultima clase

secondactor->variable que queremos

acceder a las variables, ejemplo damage

secondactor->damage

setear una variable

secondactor->damage = 300.f;

pero faltaba inicializar la variable! entonces lo hacemos en el Constructor, como los componentes

secondactor = AMySecondActor::GetClass();
// aquí se está seteando el second actor como...se llama al actor referenciado :: (doble dos puntos) se llama a la función GetClass(); es como setear una variable, ahora cada vez que escribamos secondactor, nos estamos refiriendo a la clase del actor MySecondActor

en el .h del actor principal

UPROPERTY
AMySecondActor

C++ Components

1. Programación en C++ || Primer programa en C++ - YouTube