In part 2 of this tutorial series we created the obstacle and level classes and we created blueprints from those classes.
In this part of the tutorial we are going to spawn level parts in the game using a method called procedural level generation.
Level Spawner Class
In the C++ Classes -> SideRunner, Right Click -> New C++ Class. Make sure the class inherits the Actor class, name the class LevelSpawner and click Create Class button.
Open LevelSpawner.h file in Visual Studio, and add the following line of code above the class declaration:
class ABaseLevel;
We are forward declaring the BaseLevel class because we are going to add references to level parts that we created in the previous part of this tutorial series.
At the bottom of the class add the following lines:
public:
UFUNCTION()
void SpawnLevel(bool IsFirst);
UFUNCTION()
void OnOverlapBegin(UPrimitiveComponent* OverlappedComp,
AActor* OtherActor, UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
The UFUNCTION keyword above the function is a C++ function that is recognized by the Unreal Engine 4 (UE4) reflection system e.g. it’s a keyword we use to denote that this function is a C++ function that we will use in the class.
The name of the function is self explanatory, we are going to use the function to spawn level parts in the game.
OnOverlapBegin is a function that we will use to detect collision between the LevelSpawner and other actors.
Moving forward, below the lines where we declared the functions above, add the following lines:
protected:
UPROPERTY(EditAnywhere)
TSubclassOf Level1;
UPROPERTY(EditAnywhere)
TSubclassOf Level2;
UPROPERTY(EditAnywhere)
TSubclassOf Level3;
UPROPERTY(EditAnywhere)
TSubclassOf Level4;
UPROPERTY(EditAnywhere)
TSubclassOf Level5;
UPROPERTY(EditAnywhere)
TSubclassOf Level6;
UPROPERTY(EditAnywhere)
TSubclassOf Level7;
UPROPERTY(EditAnywhere)
TSubclassOf Level8;
UPROPERTY(EditAnywhere)
TSubclassOf Level9;
UPROPERTY(EditAnywhere)
TSubclassOf Level10;
TArray LevelList;
public:
int RandomLevel;
FVector SpawnLocation = FVector();
FRotator SpawnRotation = FRotator();
FActorSpawnParameters SpawnInfo = FActorSpawnParameters();
We are going to use RandomLevel variable to randomize the level part that we will spawn in the game.
The SpawnLocation and SpawnRotation variables are going to serve as the location and rotation for the new level part we will spawn, and SpawnInfo is required when we spawn a new actor, so we need to pass it as a parameter but we will not do anything with it.
Before we proceed to code the functionality in the LevelSpawner.cpp file, I am going to leave the finished LevelSpawner.h file below as a reference:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "LevelSpawner.generated.h"
class ABaseLevel;
UCLASS()
class SIDERUNNER_API ALevelSpawner : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ALevelSpawner();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
public:
UFUNCTION()
void SpawnLevel(bool IsFirst);
UFUNCTION()
void OnOverlapBegin(UPrimitiveComponent* OverlappedComp,
AActor* OtherActor, UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
protected:
APawn* Player;
UPROPERTY(EditAnywhere)
TSubclassOf Level1;
UPROPERTY(EditAnywhere)
TSubclassOf Level2;
UPROPERTY(EditAnywhere)
TSubclassOf Level3;
UPROPERTY(EditAnywhere)
TSubclassOf Level4;
UPROPERTY(EditAnywhere)
TSubclassOf Level5;
UPROPERTY(EditAnywhere)
TSubclassOf Level6;
UPROPERTY(EditAnywhere)
TSubclassOf Level7;
UPROPERTY(EditAnywhere)
TSubclassOf Level8;
UPROPERTY(EditAnywhere)
TSubclassOf Level9;
UPROPERTY(EditAnywhere)
TSubclassOf Level10;
TArray LevelList;
public:
int RandomLevel;
FVector SpawnLocation = FVector();
FRotator SpawnRotation = FRotator();
FActorSpawnParameters SpawnInfo = FActorSpawnParameters();
};
The first thing we are going to add in the LevelSpawner.cpp file are the includes of all classes we will use in that file. Below the first #include in the file add the following lines:
#include "BaseLevel.h"
#include "Engine.h"
#include "Components/BoxComponent.h"
All the magic in regards to spawning level parts is going to happen inside the SpawnLevel function. Here are the first lines we will add in that function:
void ALevelSpawner::SpawnLevel(bool IsFirst)
{
SpawnLocation = FVector(0.0f, 1000.0f, 0.0f);
SpawnRotation = FRotator(0, 90, 0);
if (!IsFirst)
{
ABaseLevel* LastLevel = LevelList.Last();
SpawnLocation = LastLevel->GetSpawnLocation()->GetComponentTransform().GetTranslation();
}
RandomLevel = FMath::RandRange(1, 10);
ABaseLevel* NewLevel = nullptr;
}
First we set the values for the SpawnLocation and SpawnRotation. If you are wondering why did I set 1000 as a parameter for the Y axis in the SpawnLocation vector, this is because we set the scale of the level part at 10, which means the level part will be 1000 units or cm long in the game, and in order to position every level part next to each other, we need to move it by 1000 units.
Same goes for the Y value of the SpawnRotation. How the camera is positioned for our game, we need to rotate the level part by 90 degrees on the Y axis so that we can see it properly and be able to play the game.
Next we use the IsFirst parameter and test if the level part that we are spawning is not the first level part. We use the exclamation mark(!) in front of the IsFirst variable which means if the value is false, the exclamation mark will make it the opposite and that is true, and if the value is true, the exclamation mark will make it the opposite which is false.
So essentially we are testing if the level part that is being spawned is not the first level part. The reason for this is because every level part we spawn we will put it in the LevelList array, this way we can keep track of the level parts that are currently in the game and we can always access the last level part in order to get its location.
We need the location of the last level part in order to spawn the next part after it:

We can get the last level part from the array by using the Last function of the array. After that, we can access the location of the last part by using the GetSpawnLocation function, then calling GetComponentTransform to get the transform component of the last level part, and lastly calling GetTranslation function we get the location of the last level part.
Next we use RandRange function from FMath to generate a random number between 1 and 10. This is because we have 10 level parts and based on the number that is returned by the RandRange function we will spawn that level part in the game.
The NewLevel variable is there so that we can store a reference to the new level part that we spawn in the level. If we don’t get a reference to the new level part we will not be able to pass it to the LevelList array and save it.
We set the initial value of NewLevel to be nullptr or null pointer, and when we create a new level part we will store it in NewLevel variable.
Moving forward, after we create the NewLevel variable, add the following lines of code:
if (RandomLevel == 1)
{
NewLevel = GetWorld()->SpawnActor(Level1,
SpawnLocation, SpawnRotation, SpawnInfo);
}
else if (RandomLevel == 2)
{
NewLevel = GetWorld()->SpawnActor(Level2,
SpawnLocation, SpawnRotation, SpawnInfo);
}
else if (RandomLevel == 3)
{
NewLevel = GetWorld()->SpawnActor(Level3,
SpawnLocation, SpawnRotation, SpawnInfo);
}
else if (RandomLevel == 4)
{
NewLevel = GetWorld()->SpawnActor(Level4,
SpawnLocation, SpawnRotation, SpawnInfo);
}
else if (RandomLevel == 5)
{
NewLevel = GetWorld()->SpawnActor(Level5,
SpawnLocation, SpawnRotation, SpawnInfo);
}
else if (RandomLevel == 6)
{
NewLevel = GetWorld()->SpawnActor(Level6,
SpawnLocation, SpawnRotation, SpawnInfo);
}
else if (RandomLevel == 7)
{
NewLevel = GetWorld()->SpawnActor(Level7,
SpawnLocation, SpawnRotation, SpawnInfo);
}
else if (RandomLevel == 8)
{
NewLevel = GetWorld()->SpawnActor(Level8,
SpawnLocation, SpawnRotation, SpawnInfo);
}
else if (RandomLevel == 9)
{
NewLevel = GetWorld()->SpawnActor(Level9,
SpawnLocation, SpawnRotation, SpawnInfo);
}
else if (RandomLevel == 10)
{
NewLevel = GetWorld()->SpawnActor(Level10,
SpawnLocation, SpawnRotation, SpawnInfo);
}
if (NewLevel)
{
if (NewLevel->GetTrigger())
{
NewLevel->GetTrigger()->OnComponentBeginOverlap.
AddDynamic(this, &ALevelSpawner::OnOverlapBegin);
}
}
First we test if we have a NewLevel variable, or to be more precise, we are testing if NewLevel is not equal to nullprt, which means we can write:
if (NewLevel)
{
}
Or we can write:
if (NewLevel != nullptr)
{
}
Both of these lines of code are testing for the same thing. After that we are testing if we have the trigger from the NewLevel variable by calling its GetTrigger function.
This is the same thing as with the NewLevel variable, because essentially we are testing if the trigger of NewLevel variable that is returned by GetTrigger function is not equal to nullptr.
If both of these if statements are true, then we get the trigger of NewLevel and we use its OnComponentBeginOverlap function to add a function listener which is our OnOverlapBegin function that will be informed when an actor collides with the trigger of NewLevel variable.
This this trigger is the one we declared in BaseLevel.h file:
// VARIABLE DECLARED IN BaseLevel.h
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "My Triggers")
UBoxComponent* Trigger;

LevelList.Add(NewLevel);
if (LevelList.Num() > 5)
{
LevelList.RemoveAt(0);
}
First we add the NewLevel to the array by using its Add function. After that we test if number of elements that are inside the LevelList array are greater than 5, we do this using the Num function.
If that is true, then we will remove the first element in that array using the RemoveAt function and passing 0 as the parameter because RemoveAt function will remove the element that is on the index we specify as the parameter, and we set that index to 0.
This is the final version of the SpawnLevel function:
void ALevelSpawner::SpawnLevel(bool IsFirst)
{
SpawnLocation = FVector(0.0f, 1000.0f, 0.0f);
SpawnRotation = FRotator(0, 90, 0);
if (!IsFirst)
{
ABaseLevel* LastLevel = LevelList.Last();
SpawnLocation = LastLevel->GetSpawnLocation()->GetComponentTransform().GetTranslation();
}
RandomLevel = FMath::RandRange(1, 10);
ABaseLevel* NewLevel = nullptr;
if (RandomLevel == 1)
{
NewLevel = GetWorld()->SpawnActor(Level1,
SpawnLocation, SpawnRotation, SpawnInfo);
}
else if (RandomLevel == 2)
{
NewLevel = GetWorld()->SpawnActor(Level2,
SpawnLocation, SpawnRotation, SpawnInfo);
}
else if (RandomLevel == 3)
{
NewLevel = GetWorld()->SpawnActor(Level3,
SpawnLocation, SpawnRotation, SpawnInfo);
}
else if (RandomLevel == 4)
{
NewLevel = GetWorld()->SpawnActor(Level4,
SpawnLocation, SpawnRotation, SpawnInfo);
}
else if (RandomLevel == 5)
{
NewLevel = GetWorld()->SpawnActor(Level5,
SpawnLocation, SpawnRotation, SpawnInfo);
}
else if (RandomLevel == 6)
{
NewLevel = GetWorld()->SpawnActor(Level6,
SpawnLocation, SpawnRotation, SpawnInfo);
}
else if (RandomLevel == 7)
{
NewLevel = GetWorld()->SpawnActor(Level7,
SpawnLocation, SpawnRotation, SpawnInfo);
}
else if (RandomLevel == 8)
{
NewLevel = GetWorld()->SpawnActor(Level8,
SpawnLocation, SpawnRotation, SpawnInfo);
}
else if (RandomLevel == 9)
{
NewLevel = GetWorld()->SpawnActor(Level9,
SpawnLocation, SpawnRotation, SpawnInfo);
}
else if (RandomLevel == 10)
{
NewLevel = GetWorld()->SpawnActor(Level10,
SpawnLocation, SpawnRotation, SpawnInfo);
}
if (NewLevel)
{
if (NewLevel->GetTrigger())
{
NewLevel->GetTrigger()->OnComponentBeginOverlap.
AddDynamic(this, &ALevelSpawner::OnOverlapBegin);
}
}
LevelList.Add(NewLevel);
if (LevelList.Num() > 5)
{
LevelList.RemoveAt(0);
}
}
void ALevelSpawner::OnOverlapBegin(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
SpawnLevel(false);
}
void ALevelSpawner::BeginPlay()
{
Super::BeginPlay();
SpawnLevel(true);
SpawnLevel(false);
SpawnLevel(false);
SpawnLevel(false);
}
You will notice that I am passing true as the parameter for SpawnLevel function the first time I am calling it. This is because we need to spawn the initial level part which is the first level part in the game, after that we pass false to SpawnLevel function when we create other level parts.
Make sure that you compile the class from Visual Studio or Unreal Engine editor before we proceed.
Level Spawner Blueprint
Now that we are finished with our LevelSpawner class, we can create a blueprint out of it.
Inside the Content -> Blueprints folder, Right Click -> Blueprint Class. Make sure that you inherit LevelSpawner class:

Name the blueprint BP_LevelSpawner and open it in the editor. In the Components tab click on the BP_LevelSpawner(self) top parent, and in the Details tab locate the empty fields where we need to attach the level parts:

We are able to attach these level parts because when we declared them, we added the EditAnywhere paramter in the UPROPERTY for every level part that we declared.
The EditAnywhere parameter will allow us to edit the variables on blueprint instances of that class:
UPROPERTY(EditAnywhere)
TSubclassOf Level1;


Compile and Save the changes we made to BP_LevelSpawner. Now that we attached all level parts in the appropriate fields, we can drag the BP_LevelSpawner blueprint in the level and test our game:
As soon as we run the game we saw that the level parts are created.
You probably noticed two issues, one is that when we touch the spike nothing happens, this is because we didn’t code that part of the functionality yet, so that is normal.
The second issue is that our levels stopped spawning the further we went into the game. The problem is that we jumped over the Trigger Box component that is a part of every level. The player actor needs to pass through the Trigger Box and then the code to spawn a new level part will be executed.
To fix this, we can go inside BP_Level1 blueprint, select the Trigger Box in the Components tab, and change the Z Location and Z Scale in the Transform property:

This will make the Trigger Box component larger, and it will move its position upwards, so now the player actor will not be able to jump over it and we will not have the issue where we don’t spawn new level parts as we did before.
Where To Go From Here
In this tutorial we created the LevelSpawner class and we coded the level spawning functionality, so now when we play the game, we have an infinite level where we can move.
In the next part titled Detecting Collision Between Player And Obstacles And Wrapping Up Our Game we will detect when the player actor collides with the obstacles and restart the game when that happens, and with that we will finish the game.
24 thoughts on “Create A Side Scroller C++ Game In Unreal Engine Part 3: Creating The LevelSpawner Class And Generating The Game Level Using Procedural Level Generation”
Can you be more specific about the content of your article? After reading it, I still have some doubts. Hope you can help me.
Reasons Real Estate Companies Would Hire a Qualified Roofing Company in Coastal NC
Homes in coastal areas, including Wilmington, NC, deal with distinct problems concerning roof maintenance. Engaging a qualified roofing company is crucial for managing these issues and ensuring the lifespan of roofing systems.
Listed below are some reasons why real estate companies must think about employing professional roofing services in Wilmington, North Carolina:
Causes Property Owners Need to a Certified Roofing Expert in Wilmington
Properties near the shore, such as Wilmington, North Carolina, face unique challenges concerning roof maintenance. Engaging a certified roofing expert is vital for handling these problems and maintaining the durability of roofing systems.
Listed below are multiple causes why property owners should consider hiring professional roofing services in Wilmington:
Hurricane Damage Repair
Coastal areas frequently face extreme conditions like hurricanes, resulting in extensive harm to roofs. A certified expert can precisely evaluate and fix storm-related issues to avert subsequent damage.
Ocean Atmosphere Oxidation Avoidance
Ocean breeze can cause corrosion of roofing materials, especially metal roofs. Frequent checks by a qualified individual can find early signs of corrosion and utilize preventative measures to enhance the longevity of the roof.
Dampness Control along with Leak Fix
Seaside areas are subject to damp conditions, which can lead to water accumulation below roof surfaces, causing leaks and structural harm. A qualified company can handle dampness issues efficiently and carry out necessary repairs to prevent subsequent issues.
Adequate Airflow Implementation
Sufficient covering ventilation is vital for avoiding dampness accumulation and ensuring the condition of roof components. A licensed roofer can set up ventilation systems that fit the distinct conditions of seaside residences.
Substance Understanding plus Recommendations
Certified experts hold comprehensive awareness of suitable options for seaside conditions. They can recommend options which can endure rust, humidity, and wind damage, guaranteeing extended durability for roofing systems.
Knowing this information is vital since it can reduce costs eventually. Regular upkeep and timely repairs can prevent costly damage and enhance the durability of the covering, maintaining the protection of the home in seaside regions.
Seaside regions many times enjoy temperatures such as hurricanes, that can cause extensive harm to coverings. A qualified company can accurately assess and fix weather-related issues to avert later damage.
Salt Breeze Oxidation Prevention
Ocean breeze may lead to corrosion of roof components, notably aluminium roofs. Periodic examinations by an expert can detect early signs of rust and apply protective treatments to extend the durability of the roof.
Moisture Regulation along with Drip Repair
Coastal regions experience high humidity, that might cause moisture buildup below roof surfaces, causing leaks and/or interior issues. A trained expert can manage humidity efficiently to essential restorations to prevent succeeding issues.
Proper Ventilation Setup
Proper roof air-flow is essential for avoiding moisture buildup and ensuring the integrity of the roofing materials. A skilled expert can install airflow solutions that fit the distinct conditions of seaside houses.
Material Understanding and Suggestions
Advanced roofers possess thorough understanding of suitable options for shore environments. They could advise materials that can endure rust, humidity, and the wind damage, ensuring a prolonged lifespan for roofs.
informed this info is interesting mainly because it will save money over time. preventive care and easy renovation can avoid expensive harm and prolong the life of the roof, ensuring the protection of the homes in a coast area.
[url=https://portcityexteriors.com/cedar-shake-roofing/]Local reliable roofing specialists around Leland North Carolina[/url]
[url=https://theshieldg.com/250-youth-trained-on-fish-farming-in-kaduna/#comment-25001]Proactive Rooftop Upkeep Tips for Coastal Residences[/url] 0_ab94f
Expert Covering Businesses That Focus at Coastal Coverings
Coastal zones display many of Earth’s very stunning residences, but their weather and susceptibility to humidity can lead to detrimental consequences on coverings, such as oxidation, moisture absorption, as well as storm harm. For protecting their homes from those dangers, property owners commonly entrust professional covering companies who specialize in seaside roofs with those unique obstacles considered as well as aid homeowners in selecting resources, patterns and end products offering enhanced protection versus conditions.
Property owners seeking a covering provider ought to ensure they choose a supplier with a superb track record and satisfied customers, and who are certified with insurance against laborer compensation also liability claims. Additionally, these specialists should possess adequate knowledge and background to conduct an exhaustive examination also recommend any necessary fixes, as well as giving precise assessments that include charges or further expenses connected to the task.
Reliable roof providers provide homeowners with documented quotes that thoroughly outline the extent and charges linked with the job, such as materials required and prices. Moreover, they should provide a minimum of 30 year craftsmanship guarantees and be ready to respond to any queries that emerge in this operation.
Though the best roof contractors can be truthful and straightforward with the customers, they ought to not forceful when making decisions. Prior to making a last decision they ought to take the time to describe all aspects of a project and answer any queries from customers ahead of coming up with their answer. Similarly they must work within patrons’ timelines to guarantee it’s completed on schedule.
A trustworthy roofing contractor ought to have strong partnerships with local vendors and hold an in-depth awareness of regional materials available to buy, enabling them best equipped to advise items that suit the local weather and are acquired at a just rate. They ought to be knowledgeable knowledgeable of all guarantees or warranties provided by manufacturers for helping maximize homeowners’ enhance their roofing worth.
Trustworthy covering providers utilize modern CRM software for improving internal operations and increase client happiness. Such software offers live financial oversight and report generation features, enabling contractors to monitor on income, expenses and margins more efficiently while improving project oversight abilities for making better business decisions – with greater productivity, enhanced profitability, and improved viability in mind.
[url=https://portcityexteriors.com/metal-roofing/]Detailed roofing analysis services in Burgaw North Carolina[/url]
[url=https://kobietapo20.pl/jak-zrobic-karmel-do-wodki/#comment-5981]Installing a Extended Eaves for Additional Protection[/url] f428788
Creating an effective pallet rack layout is essential for better space utilization Start by analyzing your available space.
Place racks in a way that avoids bottlenecks. Different layouts suit different storage goals, so choose carefully.
Incorporate safety measures like adequate aisle width and rack anchors. Using layout design software aids visualization.
Investing in good design ensures a productive setup.
[url=https://rgpalletracking.com/]Pallet Rack Brokers[/url]
[url=https://www.osmanbalci.com.tr/integer-gravida-turpis-at-sapien-copy-3/#comment-13781]Warehouse Shelving Designed for Busy Facilities[/url] 471c10f
Ascertaining Experienced Specialists and Avoiding Initial Fees
Property owners must verify they employ certified professionals for roofing and stay alert of roofing companies that request for the full payment in advance. Certified professionals are more likely to be dependable and high-quality services, as they follow to regulations and are responsible for their work. Non-qualified providers could take shortcuts or offer subpar services, resulting in potential problems and later expenses. Furthermore, reputable covering firms usually request a fair deposit with remaining completed after finishing. Agreeing to give the full price up front may leave property owners exposed to dishonesty or unfinished projects. Verifying credentials and fair payment terms is essential for protecting residents’ interests.
For the purpose of instance An dweller employs a roofing company that requests for the full cost up front. Following payment, the business does a subpar project and rejects to resolve the concerns unless more money is provided. The homeowner ends up with a poorly done roofing and extra costs to fix the work.
In order to illustrate A tenant verifies they contract a licensed contractor and consents to a reasonable payment plan. The contractor completes the work promptly and to high standards, resulting in the homeowner pleased with the outcome and protected from future problems.
[url=https://portcityexteriors.com/]Industrial roofing services near by Leland NC[/url]
[url=https://www.sos563.com/services/#comment-427231]Stopping Algae and Algae on Your Roofing[/url] 7471c10
[url=https://9humantypes.com/how-can-i-know-my-spiritual-gift]The relationship between spiritual gifts and faith[/url]
[url=https://njregenerative.com/portugal-2013-road-trip-gallery/#comment-43496]Steps to develop God-given talents in new converts[/url] 8788e0c
Thank you for your sharing. I am worried that I lack creative ideas. It is your article that makes me full of hope. Thank you. But, I have a question, can you help me?
achat kamagra: kamagra en ligne – Kamagra Oral Jelly pas cher
Authorized Technicians in Wilmington North Carolina
Staying by the shore brings many advantages: briny breezes, beautiful scenery and the distinct noise of surf pounding on the shore are some of them. But living there also presents unique difficulties: gusts, rain and the persistent salt-filled atmosphere can wreak havoc on roofing surfaces causing leaks, moisture intrusion and potentially fungus growth under shingles or roof coverings, thus necessitating engaging an professional roofing expert in Wilmington North Carolina to tackle these issues effectively. That is why maintaining availability to reliable roofing contractors experts in Wilmington North Carolina is vital!
Roofers authorized with the State can evaluate possible concerns and take preventive measures to keep roofs in good condition – this could save expenses in repairs while offering a safer working environment or living environment for workers or residents.
These businesses concentrate in house and industrial roof services such as steel roof installations, foam roof applications, reflective roof coating jobs, shingle repair methods, entire replacements of both house and commercial rooftops and maintenance services. Furthermore, they can take care of fittings for HOAs to meet requirements.
Roof experts certified in Wilmington must carry insurance and surety coverage when performing roof work at homes and companies, to shield residents versus possible damages that may occur during a project and ensure their rooftop is repaired or renewed by competent experts. Furthermore, it aids the property owner validate whether their chosen certified roofer has the expertise and skill to deliver premium services.
[url=https://portcityexteriors.com/asphalt-roofing/]Industrial roofing specialists close to Southport NC[/url]
[url=https://grouprocket.net/blog/basecamp-clone-project-management-software.html]Increasing the Lifespan of Your Roofing[/url] 50d3747
I don’t think the title of your article matches the content lol. Just kidding, mainly because I had some doubts after reading the article.
https://pharmafst.com/# Pharmacie sans ordonnance
Achat Cialis en ligne fiable: Cialis sans ordonnance 24h – Tadalafil 20 mg prix sans ordonnance tadalmed.shop
Cialis sans ordonnance 24h [url=https://tadalmed.com/#]Tadalafil 20 mg prix en pharmacie[/url] Tadalafil 20 mg prix en pharmacie tadalmed.com
п»їpharmacie en ligne france: pharmacie en ligne sans ordonnance – п»їpharmacie en ligne france pharmafst.com
Achat mГ©dicament en ligne fiable: Meilleure pharmacie en ligne – Pharmacie sans ordonnance pharmafst.com
pharmacie en ligne france livraison belgique: Livraison rapide – pharmacie en ligne france livraison belgique pharmafst.com
pharmacie en ligne france fiable: Pharmacies en ligne certifiees – pharmacie en ligne france pas cher pharmafst.com
Cialis en ligne [url=https://tadalmed.shop/#]Tadalafil achat en ligne[/url] Cialis generique prix tadalmed.com
https://kamagraprix.com/# Kamagra pharmacie en ligne
cialis generique: cialis sans ordonnance – Achat Cialis en ligne fiable tadalmed.shop
kamagra 100mg prix: Kamagra Commander maintenant – achat kamagra
cialis generique: Pharmacie en ligne Cialis sans ordonnance – Cialis sans ordonnance pas cher tadalmed.shop
Acheter Viagra Cialis sans ordonnance: Acheter Viagra Cialis sans ordonnance – cialis generique tadalmed.shop
https://kamagraprix.com/# achat kamagra