mirror of
https://github.com/RGBCube/serenity
synced 2025-07-28 16:57:36 +00:00
Snake: Implement image-based skins
Co-authored-by: HawDevelopment <hawdevelopment@gmail.com>
This commit is contained in:
parent
da7c883dfa
commit
5708a47157
20 changed files with 425 additions and 33 deletions
48
Userland/Games/Snake/Skins/ClassicSkin.cpp
Normal file
48
Userland/Games/Snake/Skins/ClassicSkin.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
* Copyright (c) 2023, the SerenityOS developers.
|
||||
* Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "ClassicSkin.h"
|
||||
|
||||
namespace Snake {
|
||||
|
||||
ClassicSkin::ClassicSkin(Color color)
|
||||
: m_skin_color(color)
|
||||
{
|
||||
}
|
||||
|
||||
void ClassicSkin::draw_tile_at(Gfx::Painter& painter, Gfx::IntRect const& rect)
|
||||
{
|
||||
painter.fill_rect(rect, m_skin_color.darkened(0.77));
|
||||
|
||||
Gfx::IntRect left_side(rect.x(), rect.y(), 2, rect.height());
|
||||
Gfx::IntRect top_side(rect.x(), rect.y(), rect.width(), 2);
|
||||
Gfx::IntRect right_side(rect.right() - 1, rect.y(), 2, rect.height());
|
||||
Gfx::IntRect bottom_side(rect.x(), rect.bottom() - 1, rect.width(), 2);
|
||||
auto top_left_color = m_skin_color.lightened(0.88);
|
||||
auto bottom_right_color = m_skin_color.darkened(0.55);
|
||||
painter.fill_rect(left_side, top_left_color);
|
||||
painter.fill_rect(right_side, bottom_right_color);
|
||||
painter.fill_rect(top_side, top_left_color);
|
||||
painter.fill_rect(bottom_side, bottom_right_color);
|
||||
}
|
||||
|
||||
void ClassicSkin::draw_head(Gfx::Painter& painter, Gfx::IntRect const& head, Direction)
|
||||
{
|
||||
painter.fill_rect(head, m_skin_color);
|
||||
}
|
||||
void ClassicSkin::draw_body(Gfx::Painter& painter, Gfx::IntRect const& rect, Direction, Direction)
|
||||
{
|
||||
draw_tile_at(painter, rect);
|
||||
}
|
||||
void ClassicSkin::draw_tail(Gfx::Painter& painter, Gfx::IntRect const& tail, Direction)
|
||||
{
|
||||
draw_tile_at(painter, tail);
|
||||
}
|
||||
|
||||
}
|
32
Userland/Games/Snake/Skins/ClassicSkin.h
Normal file
32
Userland/Games/Snake/Skins/ClassicSkin.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
* Copyright (c) 2023, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SnakeSkin.h"
|
||||
#include <LibGfx/Color.h>
|
||||
|
||||
namespace Snake {
|
||||
|
||||
class ClassicSkin : public SnakeSkin {
|
||||
public:
|
||||
ClassicSkin(Color);
|
||||
|
||||
virtual ~ClassicSkin() override = default;
|
||||
|
||||
void draw_head(Gfx::Painter&, Gfx::IntRect const& head, Direction body_direction) override;
|
||||
void draw_body(Gfx::Painter&, Gfx::IntRect const& rect, Direction previous_direction, Direction next_direction) override;
|
||||
void draw_tail(Gfx::Painter& painter, Gfx::IntRect const& tail, Direction body_direction) override;
|
||||
|
||||
private:
|
||||
void draw_tile_at(Gfx::Painter&, Gfx::IntRect const&);
|
||||
|
||||
Gfx::Color m_skin_color = { Color::Yellow };
|
||||
};
|
||||
|
||||
}
|
103
Userland/Games/Snake/Skins/ImageSkin.cpp
Normal file
103
Userland/Games/Snake/Skins/ImageSkin.cpp
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
* Copyright (c) 2023, the SerenityOS developers.
|
||||
* Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "ImageSkin.h"
|
||||
#include <LibCore/Directory.h>
|
||||
|
||||
namespace Snake {
|
||||
|
||||
ErrorOr<NonnullOwnPtr<ImageSkin>> ImageSkin::create(StringView skin_name)
|
||||
{
|
||||
auto skin_directory = TRY(Core::Directory::create(DeprecatedString::formatted("/res/graphics/snake/skins/{}", skin_name), Core::Directory::CreateDirectories::No));
|
||||
|
||||
auto head = TRY(Gfx::Bitmap::load_from_file(TRY(skin_directory.open("head.png"sv, Core::File::OpenMode::Read)), "head.png"sv));
|
||||
Vector<NonnullRefPtr<Gfx::Bitmap>> head_bitmaps;
|
||||
TRY(head_bitmaps.try_ensure_capacity(4));
|
||||
TRY(head_bitmaps.try_append(head));
|
||||
TRY(head_bitmaps.try_append(TRY(head->rotated(Gfx::RotationDirection::Clockwise))));
|
||||
TRY(head_bitmaps.try_append(TRY(head_bitmaps[1]->rotated(Gfx::RotationDirection::Clockwise))));
|
||||
TRY(head_bitmaps.try_append(TRY(head_bitmaps[2]->rotated(Gfx::RotationDirection::Clockwise))));
|
||||
|
||||
Vector<NonnullRefPtr<Gfx::Bitmap>> body_bitmaps;
|
||||
TRY(body_bitmaps.try_ensure_capacity(16));
|
||||
auto tail_up = TRY(Gfx::Bitmap::load_from_file(TRY(skin_directory.open("tail.png"sv, Core::File::OpenMode::Read)), "tail.png"sv));
|
||||
auto tail_right = TRY(tail_up->rotated(Gfx::RotationDirection::Clockwise));
|
||||
auto tail_down = TRY(tail_right->rotated(Gfx::RotationDirection::Clockwise));
|
||||
auto tail_left = TRY(tail_down->rotated(Gfx::RotationDirection::Clockwise));
|
||||
auto corner_ur = TRY(Gfx::Bitmap::load_from_file(TRY(skin_directory.open("corner.png"sv, Core::File::OpenMode::Read)), "corner.png"sv));
|
||||
auto corner_dr = TRY(corner_ur->rotated(Gfx::RotationDirection::Clockwise));
|
||||
auto corner_dl = TRY(corner_dr->rotated(Gfx::RotationDirection::Clockwise));
|
||||
auto corner_ul = TRY(corner_dl->rotated(Gfx::RotationDirection::Clockwise));
|
||||
auto horizontal = TRY(Gfx::Bitmap::load_from_file(TRY(skin_directory.open("horizontal.png"sv, Core::File::OpenMode::Read)), "horizontal.png"sv));
|
||||
auto vertical = TRY(Gfx::Bitmap::load_from_file(TRY(skin_directory.open("vertical.png"sv, Core::File::OpenMode::Read)), "vertical.png"sv));
|
||||
|
||||
TRY(body_bitmaps.try_append(tail_up));
|
||||
TRY(body_bitmaps.try_append(corner_ur));
|
||||
TRY(body_bitmaps.try_append(vertical));
|
||||
TRY(body_bitmaps.try_append(corner_ul));
|
||||
|
||||
TRY(body_bitmaps.try_append(corner_ur));
|
||||
TRY(body_bitmaps.try_append(tail_right));
|
||||
TRY(body_bitmaps.try_append(corner_dr));
|
||||
TRY(body_bitmaps.try_append(horizontal));
|
||||
|
||||
TRY(body_bitmaps.try_append(vertical));
|
||||
TRY(body_bitmaps.try_append(corner_dr));
|
||||
TRY(body_bitmaps.try_append(tail_down));
|
||||
TRY(body_bitmaps.try_append(corner_dl));
|
||||
|
||||
TRY(body_bitmaps.try_append(corner_ul));
|
||||
TRY(body_bitmaps.try_append(horizontal));
|
||||
TRY(body_bitmaps.try_append(corner_dl));
|
||||
TRY(body_bitmaps.try_append(tail_left));
|
||||
|
||||
return adopt_nonnull_own_or_enomem(new (nothrow) ImageSkin(skin_name, move(head_bitmaps), move(body_bitmaps)));
|
||||
}
|
||||
|
||||
ImageSkin::ImageSkin(StringView skin_name, Vector<NonnullRefPtr<Gfx::Bitmap>> head_bitmaps, Vector<NonnullRefPtr<Gfx::Bitmap>> body_bitmaps)
|
||||
: m_skin_name(skin_name)
|
||||
, m_head_bitmaps(move(head_bitmaps))
|
||||
, m_body_bitmaps(move(body_bitmaps))
|
||||
{
|
||||
}
|
||||
|
||||
static int image_index_from_directions(Direction from, Direction to)
|
||||
{
|
||||
// Sprites are ordered in memory like this, to make the calculation easier:
|
||||
//
|
||||
// From direction
|
||||
// U R D L
|
||||
// ╹ ┗ ┃ ┛ Up To direction
|
||||
// ┗ ╺ ┏ ━ Right
|
||||
// ┃ ┏ ╻ ┓ Down
|
||||
// ┛ ━ ┓ ╸ Left
|
||||
// (Numbered 0-15, starting top left, one row at a time.)
|
||||
//
|
||||
// This does cause some redundancy for now, but RefPtrs are small.
|
||||
return to_underlying(to) * 4 + to_underlying(from);
|
||||
}
|
||||
|
||||
void ImageSkin::draw_head(Gfx::Painter& painter, Gfx::IntRect const& head, Direction facing_direction)
|
||||
{
|
||||
auto& bitmap = m_head_bitmaps[to_underlying(facing_direction)];
|
||||
painter.draw_scaled_bitmap(head, bitmap, bitmap->rect());
|
||||
}
|
||||
|
||||
void ImageSkin::draw_body(Gfx::Painter& painter, Gfx::IntRect const& rect, Direction previous_direction, Direction next_direction)
|
||||
{
|
||||
auto& bitmap = m_body_bitmaps[image_index_from_directions(previous_direction, next_direction)];
|
||||
painter.draw_scaled_bitmap(rect, bitmap, bitmap->rect());
|
||||
}
|
||||
|
||||
void ImageSkin::draw_tail(Gfx::Painter& painter, Gfx::IntRect const& rect, Direction body_direction)
|
||||
{
|
||||
draw_body(painter, rect, body_direction, body_direction);
|
||||
}
|
||||
|
||||
}
|
38
Userland/Games/Snake/Skins/ImageSkin.h
Normal file
38
Userland/Games/Snake/Skins/ImageSkin.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
* Copyright (c) 2023, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SnakeSkin.h"
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/Point.h>
|
||||
|
||||
namespace Snake {
|
||||
|
||||
class ImageSkin : public SnakeSkin {
|
||||
public:
|
||||
static ErrorOr<NonnullOwnPtr<ImageSkin>> create(StringView skin_name);
|
||||
|
||||
virtual ~ImageSkin() override = default;
|
||||
|
||||
void draw_head(Gfx::Painter&, Gfx::IntRect const& head, Direction facing_direction) override;
|
||||
void draw_body(Gfx::Painter&, Gfx::IntRect const& rect, Direction previous_direction, Direction next_direction) override;
|
||||
void draw_tail(Gfx::Painter&, Gfx::IntRect const& tail, Direction body_direction) override;
|
||||
|
||||
private:
|
||||
ImageSkin(StringView skin_name, Vector<NonnullRefPtr<Gfx::Bitmap>> head_bitmaps, Vector<NonnullRefPtr<Gfx::Bitmap>> body_bitmaps);
|
||||
|
||||
DeprecatedString m_skin_name;
|
||||
|
||||
Vector<NonnullRefPtr<Gfx::Bitmap>> m_head_bitmaps;
|
||||
Vector<NonnullRefPtr<Gfx::Bitmap>> m_body_bitmaps;
|
||||
};
|
||||
|
||||
}
|
28
Userland/Games/Snake/Skins/SnakeSkin.cpp
Normal file
28
Userland/Games/Snake/Skins/SnakeSkin.cpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "SnakeSkin.h"
|
||||
#include "ClassicSkin.h"
|
||||
#include "ImageSkin.h"
|
||||
#include <AK/String.h>
|
||||
#include <LibCore/DeprecatedFile.h>
|
||||
|
||||
namespace Snake {
|
||||
|
||||
ErrorOr<NonnullOwnPtr<SnakeSkin>> SnakeSkin::create(StringView skin_name, Color color)
|
||||
{
|
||||
if (skin_name == "classic"sv)
|
||||
return try_make<ClassicSkin>(color);
|
||||
|
||||
// Try to find an image-based skin matching the name.
|
||||
if (Core::DeprecatedFile::exists(TRY(String::formatted("/res/graphics/snake/skins/{}", skin_name))))
|
||||
return ImageSkin::create(skin_name);
|
||||
|
||||
// Fall-back on classic
|
||||
return try_make<ClassicSkin>(color);
|
||||
}
|
||||
|
||||
}
|
27
Userland/Games/Snake/Skins/SnakeSkin.h
Normal file
27
Userland/Games/Snake/Skins/SnakeSkin.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright (c) 2023, the SerenityOS developers.
|
||||
* Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Geometry.h"
|
||||
#include <AK/Error.h>
|
||||
#include <LibGfx/Painter.h>
|
||||
|
||||
namespace Snake {
|
||||
|
||||
class SnakeSkin {
|
||||
public:
|
||||
static ErrorOr<NonnullOwnPtr<SnakeSkin>> create(StringView skin_name, Color color);
|
||||
|
||||
virtual ~SnakeSkin() = default;
|
||||
|
||||
virtual void draw_head(Gfx::Painter&, Gfx::IntRect const& rect, Direction facing_direction) = 0;
|
||||
virtual void draw_body(Gfx::Painter&, Gfx::IntRect const& rect, Direction previous_direction, Direction next_direction) = 0;
|
||||
virtual void draw_tail(Gfx::Painter&, Gfx::IntRect const& rect, Direction body_direction) = 0;
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue