UE4: Randomly spawning Blueprint Class actors inheriting from single C++ class - c++

I am making a system in Unreal Engine to spawn items my character can pick up. I have a single Item C++ class with this in the header file :
UPROPERTY(EditAnywhere)
bool Inventoriable;
UPROPERTY(EditAnywhere)
float Power;
UPROPERTY(EditAnywhere)
int Spawn_Weight;
In my Game Mode header, I have an array meant to hold all possible items
UPROPERTY(EditAnywhere)
TArray<TSubclassOf<AItem>> PlayerRecharge;
And in the cpp file, i have a SpawnPlayerRecharge() function that looks like this:
for (int i = 0; i < NUM_ITEMS; i++) {
Item_Spawned = Cast<AItem>(PlayerRecharge[i]);
if (Item_Spawned) {
Total_Spawn_Weights += Item_Spawned->Spawn_Weight;
}
}
// Choose random number for spawn selection
int Rand = FMath::RandRange(0, Total_Spawn_Weights);
// Iterates through spawnable items and tries to cast as Item object
// Weight variable stores the summed weights to compare to the random number
//if Weight > the random number, store the index in Weight and break from loop
int Weight = 0;
for (int i = 0; i < NUM_ITEMS; i++) {
Item_Spawned = Cast<AItem>(PlayerRecharge[Weight]);
if (Item_Spawned) {
Weight += Item_Spawned->Spawn_Weight;
if (Weight > Rand) {
Weight = i;
break;
}
}
}
FActorSpawnParameters Params;
Params.Owner = this;
// Spawn AItem actor from the array based on index found above
GetWorld()->SpawnActor<AItem>(PlayerRecharge[Weight], SpawnPosition, SpawnRotation, Params);
Basically, all the types of items I have are in Blueprint classes that inherit from Item with different meshes, Power properties, etc. In SpawnPlayerRecharge(), I'm trying to randomly spawn one of the different types based on weights set in each child's Blueprint editor. In the function, the cast always fails (I checked with UE_LOG), and if I try to access the weights directly with PlayerRecharge[i], it gives me an error saying that UOBJECT does not have property Spawn_Weight. How do I get access to those Blueprint children, or is there a better way to do this?
If you need to see code I didn't post, it's on my GitHub: https://github.com/Chriq/BatteryMan

Related

How to write a unit test

everyone.
I'm new to unit testing and can't get the idea of it.
I have a module that have a process() function. Inside process() function module does a lot of non-trivial job. The task is to check module's output.
For example, there is a method calculateDistance() inside process() method that calculates distance value. Test requirement sounds like "check that module calculates distance..."
As I understand I have to:
Prepare input data
Call module's process() method
Calculate distance by hands
Get module's output value for distance
Compare this value to value calculated.
But it is easy for some trivial cases, for example some ariphmetical operations. But what if inside calculateDistance() there are lots of formulas. How should I calculate this distance's value? Should I just copy all source code from calculateDistance() function to the test's code to calculate this value from step 3?
Here is the source code example:
void process(PropertyList& propList)
{
m_properties = &propList;
...
for (int i = 0; i < m_properties.propertiesCount; i++)
{
calculateDistance();
}
}
void calculateDistance(int indx)
{
Property& property = m_properties->properties[indx];
property.distance = 0;
for (int i = 0; i < property.objectsCount - 1; i++)
{
property.distance += getDistanceBetweenObjs(i, i + 1);
}
}
int getDistanceBetweenObjects(indx1, indx2)
{
// here is some complex algorithm of calculating the distance
}
So, my question is: should I prepare input data where I know the resulting distance value and just compare this value to the output value? Or should I get input data structures, calculate distance value the same way as calculateDistance() method does (here is code duplication) and compare this value to the output distance?
But what if inside calculateSmth() there are lots of formulas.
Refactor the code and break out those formulas to separate functions and test them individually.

Inserting an object from a derived class into another instance of said object (using the vector.insert function) C++

I am currently working through code which has a Road class. This has a vector of lanes and includes a lane class. The lane class has a vector of vehicles and each vehicle has its derived classes (Car, Motorcycle, Lorry).
I would like a vehicle to be able to assess whether it can move into another lane, i.e. be inserted into another lane's vector of vehicles, safely (a car requires some safe distance, so I have already implemented how a vehicle knows if it is clear to switch lanes).
void Road::Simulate(double time)
{
for (unsigned int iLane = 0; iLane < getNLanes()-1; iLane++)
{
for (unsigned int iV = 0; iV < getCurrentLane(iLane)->getNVehiclesinLane(); iV++)
{
std::cout<< " AllowedOvertake TEST "<< getCurrentLane(iLane+1)->allowedOvertake(getCurrentLane(iLane)->getCurrentVehicle(iV)->getPosition(), getCurrentLane(iLane)->getCurrentVehicle(iV)->getMinDistance())<<std::endl;
getCurrentLane(iLane+1)->setCanOvertake(allowedOvertake(getCurrentLane(iLane)->getCurrentVehicle(iV)->getPosition(), getCurrentLane(iLane)->getCurrentVehicle(iV)->getMinDistance()));
if (getCurrentLane(iLane+1)->getCanOvertake() == true)
{
getCurrentLane(iLane+1)->insertVehicle(getCurrentLane(iLane)->getCurrentVehicle(iV), 0);
delete getCurrentLane(iLane)->getCurrentVehicle(iV);
}
}
}
for (unsigned int iLane = 0; iLane < getNLanes(); iLane++)
{
getCurrentLane(iLane)->Simulate(time);
}
}
I loop over all the present lanes, except the last one as any vehicle in this lane cannot overtake. After looping over the vehicles contained in each lane, I have a function which returns a Boolean which confirms whether an overtake scenario can be executed. This is done in allowedOvertake(). If this returns true, I implement an insert function.
My issue/question: How can I make this ideology work, and whether it is sensible to have these setCanOvertake() and getCanOvertake() functions.
One possible solution could be to just pushback a new vehicle into the intended lane, but with the appropriate positions, velocities etc. However, I am not sure how to ensure that the vehicle being entered has the same type (Car, Lorry...) too.
Currently, I do not get any build errors if I exclude the insertVehicle() function, and I have vehicle motion being drawn using QPainter. However, with the insertVehicle() function, I do not get any build errors but I do get a crash once I run the project.
Any help would be appreciated, and apologies for any coding errors (I'm a keen, but very inexperienced C++ user).
For reference, I have the above functions' definitions as follows
bool Lane::allowedOvertake(double pos, double mindist)
{
for (unsigned int iV = 0; iV < getNVehiclesinLane() - 1; iV++)
{
if ((fVehicles[iV]->getPosition() > pos - mindist)// If inside rear safety distance.
|| fVehicles[iV]->getPosition() < pos + mindist)// If inside front safety distance.
{}//continue
else {return false;}
}
return true;
}
//IN Lane.h
bool getCanOvertake() const{return FREE_LANE_OVERTAKE;}
//IN Lane.h
void setCanOvertake(bool overtake = true){FREE_LANE_OVERTAKE = overtake;}
Apologies, I was under the impression I had referenced my insertVehicle() function definition.
void Lane::insertVehicle(Vehicle*v, int ielement) {
Vehicle* vins = new Vehicle(v->getPosition(), v->getVelocity(), v->getAcceleration());
for (unsigned int iDist = 0; iDist < fVehicles.size()+1; iDist++){fVehicles.insert(fVehicles.begin() + (ielement+1), vins);}
}

C++ - Map contains way too many elements

I'm attempting to create a entity manager for a game I'm making so I can easily manage all the entities within the game (Player, Enemies, Objects) and be able to call their Update and Render functions without having to do it manually.
I'm recording the entities using a map that takes in a int and a Entity class
std::map<int, Entity*> entities;
Whenever the Entities are added to the map, they'll also be given a random ID with that ID being added to a vector just so I can record what ID's have been used so I don't end up using the exact same entity
IDS = std::vector<int>();
void EntityManager::AddEntity(Entity* entity)
{
int ID = rand();
for(int i = 0; i < IDS.size(); i++)
{
if(IDS[i] == ID)
{
ID = rand();
i = 0;
}
}
entities.insert(pair<int, Entity*>(ID, entity));
}
I then finally call the Update and Render functions of each entity
void EntityManager::Update(float deltaTime)
{
if(!entities.empty())
{
for(int i = 0; i < entities.size(); i++)
{
if(entities[i] != nullptr)
{
entities[i]->Update(deltaTime);
}
}
}
}
void EntityManager::Render()
{
if(!entities.empty())
{
for(int i = 0; i < entities.size(); i++)
{
if(entities[i] != nullptr)
{
entities[i]->Render();
}
}
}
}
The problem I'm having is the frame rate will instantly drop to something like 2FPS as it seems when I'm checking the size of the map within the Update and Render functions, the size is something like 16587 instead of what it should be which is just 2 as at this point I'm only inputting 2 entities into the map.
I've tried to figure out what the cause is but I just can't find it, the size is 2 after the "entities.insert" in the AddEntity function but once the Update and Render functions are called, the size instantly goes to 16587 or some number like that. I'm not calling AddEntity anywhere else except for in a constructor within another class:
entityManager->AddEntity(player);
entityManager->AddEntity(diamond);
entities is a map, and you're using random integers as the key. You cannot use an index loop to access the members of the map. Every iteration of that loop will add another entry in the map (with a nullptr Entity), increasing the size causing your loop to continue on.
To iterate thru the map, you can use
for (const auto &ent: entities)
ent.second->Render();
then you'd only see the entries in the map and not add any new ones.

How to use the QVector with multiple object

I'm trying to use the QVector class from Qt to work (for me :P). What I want to do is to put multiple instances of the object Question in a QVector.
I went on multiple forums, but they're all too complicated for me as I am a beginner.
This one post was perfect but I did not find a way to resolve my problem.
So I'm turning to you to help me!
Here's the function that I want to work :
The part that create the bundle/ the vector
/**
* #brief MenuQuestionnary::assembleQuiz
* Assemble the bundle of question that will be used in Quiz class
*/
void MenuQuestionnary::assembleQuiz(){
QVector<Question> vectorQuiz;
vectorQuiz.reserve(spinBoxNumberOfQuestion->value());
for(int i = 0; i <= spinBoxNumberOfQuestion->value(); i++){
vectorQuiz.append(Question((qrand()% maximumNumberOfQuestionAvailable)));
}
}
Here's my Question constructor :
Question::Question(int id)
{
this->questionId = id;
//TODO: Actually get it from DB
this->questionText = "2+2?";
this->explanation = "Addition mechanics";
this->creatorId = 1;
}
What i expect to do here is to put the selected number of the Question object in a vector. After that i can pass it to another class. From there i should be able to extract the text from them(questionText and questionExplanation).
You are trying to push objects of class type Question into a QVector<int>, which obviously is expecting an int instead. You should change it to QVector<Question> to begin with.
What I highly suggest, though, is that you read a good book on C++ before going any further, or your experience with it will just get more and more complicated.
Copy pasting code from forums on the internet is not programming and will get you in troubles soon.
I think what you want is this: QVector vectorQuiz; vectorQuiz.reserve(spinBoxNumberOfQuestion->value()); // reserve correct amount of space in vector for performance (not required). I don't know exactly how you are going to use vectorQuiz, but maybe you should use pointers to the questions i.e. QVector and insert questions using new Question()
/**
* #brief MenuQuestionnary::assembleQuiz
* Assemble the bundle of question that will be used in Quiz class
*/
void MenuQuestionnary::assembleQuiz(){
int iVectorSize = spinBoxNumberOfQuestion->value();
QVector<Question> vectorQuiz;
vectorQuiz.reserve(iVectorSize );
for(int i = 0; i <= iVectorSize ; ++i){
vectorQuiz.append(Question(i));
}
}
Your object (vectorQuiz) declared as vector of integers. If you want to add some integer value to it you should write something:
vectorQuiz.append( someIntegerValue );
or
vectorQuiz.push_back( someIntegerValue );
For vector of another type (i.e. Question), write code like this:
QVector<Question> vectorQuiz;
// ...
for(int i = 0; i <= spinBoxNumberOfQuestion->value(); ++i){
vectorQuiz.append(Question(i)); // without [i] after vector object
}
And possible your should use strict inequality < instead of <= in for-cycle (but I'm not sure).
I solved it by trying few things
Here's the explanation of what i did
I've split my function in two. The first one put the element in a QList and the second one shuffles it.
/**
* #brief MenuQuestionnary::assembleQuiz
* #param list
* Asseble a quiz in the Qlist and then ask shuffle to shuffle it...
*/
void MenuQuestionnary::assembleQuiz(QList<Question> &list){
for(int i = 0; i < spinBoxNumberOfQuestion->value(); ++i){
int rand = qrand() * maximumNumberOfQuestionAvailable;
Question newQuestion(rand);
list.append(newQuestion);
}
shuffleQuiz(list);
}
/**
* Method Shuffle
* equivalent to shuffling a deck of cards: we take a random one, move it to be the last one,
* then do it again enough times to have statistically touched every card.
*/
void MenuQuestionnary::shuffleQuiz(QList<Question> &list){
int iters = list.size() * list.size();
for (int i = 0; i < iters; ++i){
int rand = qrand() * list.size();
list.append(list[rand]);
list.removeAt(rand);
}
}
Thanks for the help though.

ORM Entity - Remove a record, and add a property

I am putting together a store finder which works on a radius from a postal code. I have done this many times using standard Queries and QoQs, but now trying to put one together using cf9 ORM... but seemed to have reached the limit of my capabilities with the last bit.
I am pulling out an entity and processing it. At the end, I need to:
a. Remove a record if it doesn't meet a certain criteria (distance is greater than specified by user)
OR b. Add a new property to the record to store the distance.
So at the end, all I want in my entity are those stores that are within the range specified by the user, with each record containing the calculated distance.
Best way to see what I am trying to do is to view the full function
Any suggestions greatly appreciated!!
public function getByPostcodeRadius(required postcode="",
required radius=""){
//set some initial vals
rs = {};
geo = New _com.util.geo().init();
local.postcodeGeo = geo.getGeoCode("#arguments.postcode#, Australia");
local.nearbyStores = "";
local.returnStores = {};
//load stores
local.stores = entityload("stores");
//loop over all stores and return list of stores inside radius
for(i=1; i <= ArrayLen(local.stores); i++){
store = {};
store.id = local.stores[i].getID();
store.geoCode = local.stores[i].getGeoCode();
store.Lat = ListgetAt(store.geoCode,1);
store.Lng = ListgetAt(store.geoCode,2);
distance = geo.getDistanceByGeocode(local.postcodeGeo.Lat,local.postcodeGeo.Lng,store.Lat,store.Lng);
//************************
//HERE IS WHERE I AM STUCK.
if (distance LT arguments.radius){
//here I need to add a property 'distance' and set it's value
local.stores[i].distance = distance; // this adds it to the object, but not with the PROPERTIES
} else {
// here i need to remove the store from the object as it's distance was greater than the one passed in
arrayDeleteAt(local.stores,i); //this clearly isn't working, as positions are changing with each loop over
}
}
return local.stores;
}
If you delete an object from an array it will mess up your loop.
Try either looping backwards:
var i = arrayLen( local.stores );
for ( i; i == 0; i-- )
Or looping like this
for ( var local.store in local.stores )
(That's rough code and may need some tweaks)
I'd approach this from a different angle:
1) Instead of deleting from the array of all stores those that don't match, I'd build an array of those that do and return that.
2) If the distance is specific to each query and not a property of the store object, then I wouldn't try adding it to the store, but just "associate" it with the specific data I'm returning for this search.
Putting to the 2 together, I'd return an array of structs containing the store object and its distance from the requested postcode. (You could just return a single struct of the store object and distance, with the store ID as key, but I prefer working with arrays.)
Here's how I'd code it (not tested because I don't have your geo class or entity code):
public array function getByPostcodeRadius(required postcode="", required radius=""){
hint="I return an array of structs each containing a store object within the requested radius and its distance from the requested post code"
// Geo settings
local.geo = New _com.util.geo().init();
local.postcodeGeo = local.geo.getGeoCode("#arguments.postcode#, Australia");
// initialise the array of structs to return, which will contain stores within the requested radius and their distance from the postcode
local.nearbyStores = [];
//load all stores
local.stores = entityload("stores");
//loop over all stores and add those inside the radius to the return array with their distance
for( var storeObject in local.stores ){
// determine the lat-lng for this store
local.storeLat = ListgetAt(storeObject.getGeoCode(),1);
local.storeLng = ListgetAt(storeObject.getGeoCode(),2);
// get the distance from the requested postcode
local.distance = local.geo.getDistanceByGeocode(local.postcodeGeo.Lat,local.postcodeGeo.Lng,local.storeLat,local.storeLong);
if (local.distance LT arguments.radius){
// create a struct of the store object and its distance and add to the nearby stores array
local.thisStore = {
store = storeObject
,distance = local.distance
};
ArrayAppend( local.nearbyStores,local.thisStore );
}
}
return local.nearbyStores;
}